From feac4e0d9423cb1ed84246d95d05f2d9aafd59c7 Mon Sep 17 00:00:00 2001 From: Dmytro Afanasiev Date: Thu, 9 May 2024 23:23:53 +0300 Subject: [PATCH 1/4] Rc 5.1.0 --- .dockerignore | 21 +- .gitignore | 3 +- CHANGELOG.md | 985 +- Makefile | 132 + README.md | 127 +- c7n/CHANGELOG.md | 146 +- c7n/autocomplete/.c7n-complete.bash | 29 - c7n/autocomplete/.c7n-complete.fish | 22 - c7n/autocomplete/.c7n-complete.zsh | 35 - c7n/c7ncli/group/__init__.py | 522 +- c7n/c7ncli/group/application.py | 110 - c7n/c7ncli/group/application_access.py | 102 - c7n/c7ncli/group/application_dojo.py | 78 - c7n/c7ncli/group/c7n.py | 60 +- c7n/c7ncli/group/customer.py | 47 +- c7n/c7ncli/group/customer_rabbitmq.py | 12 +- c7n/c7ncli/group/integrations.py | 13 + c7n/c7ncli/group/integrations_dojo.py | 172 + c7n/c7ncli/group/integrations_sre.py | 113 + c7n/c7ncli/group/job.py | 263 +- c7n/c7ncli/group/job_event.py | 46 +- c7n/c7ncli/group/job_scheduled.py | 41 +- c7n/c7ncli/group/license.py | 140 +- c7n/c7ncli/group/meta.py | 11 +- c7n/c7ncli/group/metrics.py | 19 +- c7n/c7ncli/group/parent.py | 96 - c7n/c7ncli/group/platform_k8s.py | 77 +- c7n/c7ncli/group/policy.py | 116 +- c7n/c7ncli/group/report.py | 140 +- c7n/c7ncli/group/report_compliance.py | 37 +- c7n/c7ncli/group/report_details.py | 86 +- c7n/c7ncli/group/report_digests.py | 79 +- c7n/c7ncli/group/report_errors.py | 37 +- c7n/c7ncli/group/report_errors_accumulated.py | 99 - c7n/c7ncli/group/report_errors_jobs.py | 73 - c7n/c7ncli/group/report_findings.py | 60 + c7n/c7ncli/group/report_push.py | 65 +- c7n/c7ncli/group/report_raw.py | 28 + c7n/c7ncli/group/report_resource.py | 211 +- c7n/c7ncli/group/report_rules.py | 81 +- c7n/c7ncli/group/results.py | 30 +- c7n/c7ncli/group/role.py | 65 +- c7n/c7ncli/group/rule.py | 30 +- c7n/c7ncli/group/ruleset.py | 65 +- c7n/c7ncli/group/ruleset_eventdriven.py | 13 +- c7n/c7ncli/group/rulesource.py | 63 +- c7n/c7ncli/group/setting.py | 2 + c7n/c7ncli/group/setting_lm.py | 3 +- c7n/c7ncli/group/setting_lm_client.py | 38 +- c7n/c7ncli/group/setting_lm_config.py | 9 +- c7n/c7ncli/group/setting_mail.py | 15 +- c7n/c7ncli/group/setting_report.py | 32 + c7n/c7ncli/group/siem.py | 17 - c7n/c7ncli/group/siem_add.py | 100 - c7n/c7ncli/group/siem_delete.py | 32 - c7n/c7ncli/group/siem_describe.py | 32 - c7n/c7ncli/group/siem_update.py | 104 - c7n/c7ncli/group/tenant.py | 149 +- c7n/c7ncli/group/tenant_credentials.py | 147 +- c7n/c7ncli/group/tenant_findings.py | 86 - c7n/c7ncli/group/tenant_region.py | 27 - c7n/c7ncli/group/trigger.py | 16 - c7n/c7ncli/group/user.py | 68 - c7n/c7ncli/group/user_tenants.py | 59 - c7n/c7ncli/group/users.py | 64 + c7n/c7ncli/service/adapter_client.py | 2401 ++-- c7n/c7ncli/service/config.py | 102 +- c7n/c7ncli/service/constants.py | 492 +- c7n/c7ncli/service/helpers.py | 202 +- c7n/c7ncli/service/logger.py | 59 +- c7n/c7ncli/version.py | 4 +- c7n/docs/README.md | 87 - c7n/docs/application/add.md | 46 - c7n/docs/application/delete.md | 21 - c7n/docs/application/describe.md | 18 - c7n/docs/application/index.md | 15 - c7n/docs/application/update.md | 51 - c7n/docs/cleanup.md | 18 - c7n/docs/configure.md | 26 - c7n/docs/customer/describe.md | 21 - c7n/docs/customer/index.md | 12 - c7n/docs/health_check.md | 26 - c7n/docs/job/describe.md | 36 - c7n/docs/job/index.md | 15 - c7n/docs/job/scheduled/add.md | 41 - c7n/docs/job/scheduled/delete.md | 21 - c7n/docs/job/scheduled/describe.md | 26 - c7n/docs/job/scheduled/index.md | 15 - c7n/docs/job/scheduled/update.md | 31 - c7n/docs/job/submit.md | 46 - c7n/docs/job/terminate.md | 21 - c7n/docs/lm/delete.md | 21 - c7n/docs/lm/describe.md | 21 - c7n/docs/lm/index.md | 14 - c7n/docs/lm/sync.md | 21 - c7n/docs/login.md | 26 - c7n/docs/parent/add.md | 41 - c7n/docs/parent/delete.md | 21 - c7n/docs/parent/describe.md | 21 - c7n/docs/parent/index.md | 17 - c7n/docs/parent/link_tenant.md | 26 - c7n/docs/parent/unlink_tenant.md | 21 - c7n/docs/parent/update.md | 46 - c7n/docs/policy/add.md | 36 - c7n/docs/policy/clean_cache.md | 21 - c7n/docs/policy/delete.md | 21 - c7n/docs/policy/describe.md | 21 - c7n/docs/policy/index.md | 16 - c7n/docs/policy/update.md | 31 - c7n/docs/report/clevel.md | 18 - c7n/docs/report/compliance/accumulated.md | 26 - c7n/docs/report/compliance/index.md | 13 - c7n/docs/report/compliance/jobs.md | 31 - c7n/docs/report/department.md | 18 - c7n/docs/report/details/accumulated.md | 41 - c7n/docs/report/details/index.md | 13 - c7n/docs/report/details/jobs.md | 46 - c7n/docs/report/digests/accumulated.md | 41 - c7n/docs/report/digests/index.md | 13 - c7n/docs/report/digests/jobs.md | 46 - c7n/docs/report/errors/accumulated/access.md | 46 - c7n/docs/report/errors/accumulated/core.md | 46 - c7n/docs/report/errors/accumulated/index.md | 14 - c7n/docs/report/errors/accumulated/total.md | 46 - c7n/docs/report/errors/index.md | 13 - c7n/docs/report/errors/jobs/access.md | 36 - c7n/docs/report/errors/jobs/core.md | 36 - c7n/docs/report/errors/jobs/index.md | 14 - c7n/docs/report/errors/jobs/total.md | 36 - c7n/docs/report/index.md | 21 - c7n/docs/report/operational.md | 21 - c7n/docs/report/project.md | 21 - c7n/docs/report/push/dojo.md | 41 - c7n/docs/report/push/index.md | 13 - c7n/docs/report/push/security_hub.md | 61 - c7n/docs/report/rules/accumulated.md | 51 - c7n/docs/report/rules/index.md | 13 - c7n/docs/report/rules/jobs.md | 41 - c7n/docs/results/describe.md | 46 - c7n/docs/results/index.md | 12 - c7n/docs/role/add.md | 31 - c7n/docs/role/clean_cache.md | 21 - c7n/docs/role/delete.md | 21 - c7n/docs/role/describe.md | 21 - c7n/docs/role/index.md | 16 - c7n/docs/role/update.md | 36 - c7n/docs/rule/delete.md | 21 - c7n/docs/rule/describe.md | 46 - c7n/docs/rule/index.md | 14 - c7n/docs/rule/update.md | 26 - c7n/docs/ruleset/add.md | 71 - c7n/docs/ruleset/delete.md | 27 - c7n/docs/ruleset/describe.md | 46 - c7n/docs/ruleset/eventdriven/add.md | 41 - c7n/docs/ruleset/eventdriven/delete.md | 26 - c7n/docs/ruleset/eventdriven/describe.md | 26 - c7n/docs/ruleset/eventdriven/index.md | 16 - c7n/docs/ruleset/index.md | 16 - c7n/docs/ruleset/update.md | 51 - c7n/docs/rulesource/add.md | 46 - c7n/docs/rulesource/delete.md | 21 - c7n/docs/rulesource/describe.md | 21 - c7n/docs/rulesource/index.md | 15 - c7n/docs/rulesource/update.md | 51 - c7n/docs/setting/index.md | 13 - c7n/docs/setting/lm/client/add.md | 41 - c7n/docs/setting/lm/client/delete.md | 21 - c7n/docs/setting/lm/client/describe.md | 21 - c7n/docs/setting/lm/client/index.md | 14 - c7n/docs/setting/lm/config/add.md | 31 - c7n/docs/setting/lm/config/delete.md | 21 - c7n/docs/setting/lm/config/describe.md | 18 - c7n/docs/setting/lm/config/index.md | 14 - c7n/docs/setting/lm/index.md | 13 - c7n/docs/setting/mail/add.md | 56 - c7n/docs/setting/mail/delete.md | 21 - c7n/docs/setting/mail/describe.md | 21 - c7n/docs/setting/mail/index.md | 14 - c7n/docs/show_config.md | 18 - c7n/docs/siem/add/dojo.md | 76 - c7n/docs/siem/add/index.md | 13 - c7n/docs/siem/add/security_hub.md | 36 - c7n/docs/siem/delete/dojo.md | 21 - c7n/docs/siem/delete/index.md | 13 - c7n/docs/siem/delete/security_hub.md | 21 - c7n/docs/siem/describe/dojo.md | 21 - c7n/docs/siem/describe/index.md | 13 - c7n/docs/siem/describe/security_hub.md | 21 - c7n/docs/siem/index.md | 15 - c7n/docs/siem/update/dojo.md | 81 - c7n/docs/siem/update/index.md | 13 - c7n/docs/siem/update/security_hub.md | 36 - c7n/docs/tenant/create.md | 56 - c7n/docs/tenant/credentials/add.md | 36 - c7n/docs/tenant/credentials/delete.md | 26 - c7n/docs/tenant/credentials/describe.md | 26 - c7n/docs/tenant/credentials/index.md | 15 - c7n/docs/tenant/credentials/update.md | 36 - c7n/docs/tenant/describe.md | 41 - c7n/docs/tenant/findings/delete.md | 21 - c7n/docs/tenant/findings/describe.md | 61 - c7n/docs/tenant/findings/index.md | 13 - c7n/docs/tenant/index.md | 17 - c7n/docs/tenant/region/activate.md | 26 - c7n/docs/tenant/region/index.md | 12 - c7n/docs/tenant/update.md | 31 - c7n/docs/trigger/configuration_backup.md | 18 - c7n/docs/trigger/index.md | 12 - c7n/docs/user/index.md | 13 - c7n/docs/user/signup.md | 36 - c7n/docs/user/tenants/assign.md | 26 - c7n/docs/user/tenants/describe.md | 21 - c7n/docs/user/tenants/index.md | 14 - c7n/docs/user/tenants/unassign.md | 31 - c7n/pyproject.toml | 37 + c7n/setup.py | 23 - obfuscation_manager/README.md | 55 + obfuscation_manager/obfuscation_manager.py | 759 ++ obfuscation_manager/pyproject.toml | 36 + src/.env.example | 79 +- .../auth_extension/base_auth_client.py | 84 - .../auth_extension/cognito_to_jwt_adapter.py | 244 - .../batch_extension/base_job_client.py | 76 - .../batch_to_subprocess_adapter.py | 125 - .../logs_extension/base_logs_client.py | 46 - .../logs_extension/cw_to_log_file_adapter.py | 20 - src/deployment_resources.json | 7114 +++++++---- src/executor/Dockerfile | 69 + src/executor/Dockerfile-opensource | 30 + src/executor/README.md | 34 + src/{connections => executor}/__init__.py | 0 .../helpers}/__init__.py | 0 src/executor/helpers/constants.py | 63 + src/executor/helpers/profiling.py | 63 + src/executor/requirements.txt | 15 + src/executor/services/__init__.py | 76 + src/executor/services/credentials_service.py | 51 + src/executor/services/environment_service.py | 161 + .../services/license_manager_service.py | 286 + .../services/notification_service.py | 131 +- src/executor/services/policy_service.py | 134 + src/executor/services/report_service.py | 379 + src/exported_module/api/__init__.py | 0 src/exported_module/api/app.py | 171 - .../api/deployment_resources_parser.py | 84 - src/exported_module/api/license_sync.py | 52 - src/exported_module/requirements.txt | 41 - src/exported_module/scripts/__init__.py | 0 src/exported_module/scripts/init_minio.py | 36 - src/exported_module/scripts/init_mongo.py | 111 - src/exported_module/scripts/init_vault.py | 30 - src/exported_module/scripts/run.sh | 34 - src/handlers/__init__.py | 24 + src/handlers/abstracts/__init__.py | 0 .../abstract_credentials_manager_handler.py | 438 - src/handlers/abstracts/abstract_handler.py | 125 - .../abstract_modular_entity_handler.py | 161 - .../abstracts/abstract_user_handler.py | 173 - src/handlers/applications_handler.py | 653 - src/handlers/base_handler.py | 320 - src/handlers/compliance_handler.py | 528 +- src/handlers/credentials_handler.py | 216 + src/handlers/credentials_manager_handler.py | 203 - src/handlers/customer_handler.py | 449 +- src/handlers/defect_dojo_handler.py | 192 + src/handlers/details_handler.py | 737 +- src/handlers/digest_handler.py | 684 +- src/handlers/errors_handler.py | 647 +- src/handlers/event_assembler_handler.py | 294 +- src/handlers/findings_handler.py | 382 +- src/handlers/license_handler.py | 387 +- .../license_manager_setting_handler.py | 339 +- src/handlers/mail_setting_handler.py | 125 +- src/handlers/parents_handler.py | 340 - src/handlers/platforms_handler.py | 258 +- src/handlers/policy_handler.py | 183 +- src/handlers/push_handler.py | 626 +- src/handlers/rabbitmq_handler.py | 116 +- src/handlers/raw_report_handler.py | 75 + src/handlers/report_status_handler.py | 47 + src/handlers/resource_report_handler.py | 804 +- src/handlers/role_handler.py | 200 +- src/handlers/rule_handler.py | 39 +- src/handlers/rule_meta_handler.py | 50 +- src/handlers/rule_source_handler.py | 370 +- src/handlers/rules_handler.py | 632 +- src/handlers/ruleset_handler.py | 299 +- src/handlers/self_integration_handler.py | 280 + src/handlers/send_report_setting_handler.py | 119 + src/handlers/tenant_handler.py | 294 + src/handlers/tenants/__init__.py | 21 - .../tenants/license_priority_handler.py | 1494 --- src/handlers/tenants/tenant_handler.py | 468 - src/handlers/user_customer_handler.py | 72 - src/handlers/user_role_handler.py | 74 - src/handlers/user_tenants_handler.py | 209 - src/helpers/__init__.py | 701 +- src/helpers/__version__.py | 2 +- src/helpers/constants.py | 890 +- src/helpers/docker.py | 114 - src/helpers/enums.py | 24 - src/helpers/exception.py | 54 - src/helpers/json_util.py | 77 - src/helpers/lambda_response.py | 245 + src/helpers/log_helper.py | 123 +- src/helpers/recommendations.py | 21 + src/helpers/regions.py | 12 +- src/helpers/reports.py | 274 +- src/helpers/security.py | 100 - src/helpers/system_customer.py | 17 +- src/helpers/time_helper.py | 24 +- src/helpers/utils.py | 17 - src/integrations/__init__.py | 74 - src/integrations/defect_dojo_adapter.py | 325 - src/integrations/security_hub_adapter.py | 78 - src/lambdas/caas_jobs_backupper/__init__.py | 0 .../deployment_resources.json | 13 - src/lambdas/caas_jobs_backupper/handler.py | 328 - .../lambda_config.json.old | 33 - .../local_requirements.txt | 0 .../caas_jobs_backupper/requirements.txt | 0 src/lambdas/custodian_api_handler/README.md | 8 - .../deployment_resources.json | 2 +- src/lambdas/custodian_api_handler/handler.py | 387 +- .../handlers/__init__.py | 26 - .../handlers/batch_result_handler.py | 186 +- .../handlers/events_handler.py | 67 + .../handlers/health_check_handler.py | 74 +- .../handlers/job_handler.py | 857 +- .../handlers/metrics_status_handler.py | 78 +- .../handlers/new_swagger_handler.py | 98 + .../handlers/users_handler.py | 253 + .../custodian_api_handler/lambda_config.json | 38 +- .../custodian_api_handler/requirements.txt | 4 +- .../README.md | 2 - .../deployment_resources.json | 11 + .../handler.py | 264 +- .../lambda_config.json | 25 +- .../requirements.txt | 2 +- .../README.md | 68 - .../__init__.py | 0 .../deployment_resources.json | 37 - .../handler.py | 405 - .../lambda_config.json | 42 - .../local_requirements.txt | 0 .../requirements.txt | 0 .../custodian_configuration_updater/README.md | 68 - .../__init__.py | 0 .../deployment_resources.json | 29 - .../handler.py | 354 - .../lambda_config.json | 38 - .../local_requirements.txt | 0 .../requirements.txt | 0 .../custodian_event_handler/handler.py | 24 +- .../lambda_config.json | 25 +- .../deployment_resources.json | 3 + src/lambdas/custodian_job_updater/handler.py | 357 +- .../custodian_job_updater/lambda_config.json | 4 + .../custodian_job_updater/requirements.txt | 2 +- .../custodian_license_updater/README.md | 1 - .../custodian_license_updater/handler.py | 322 +- .../lambda_config.json | 10 +- .../requirements.txt | 2 +- .../deployment_resources.json | 5 + .../custodian_metrics_updater/handler.py | 83 +- .../lambda_config.json | 33 +- .../processors/customer_metrics_processor.py | 282 - .../diagnostic_metrics_processor.py | 220 + .../processors/findings_processor.py | 88 +- .../processors/metric_difference_processor.py | 40 +- .../processors/recommendation_processor.py | 411 +- .../tenant_group_metrics_processor.py | 102 +- .../processors/tenant_metrics_processor.py | 854 +- .../processors/top_metrics_processor.py | 169 +- .../requirements.txt | 2 +- .../custodian_notification_handler/README.md | 1 - .../__init__.py | 0 .../deployment_resources.json | 33 - .../custodian_notification_handler/handler.py | 303 - .../lambda_config.json | 36 - .../local_requirements.txt | 0 .../requirements.txt | 2 - .../deployment_resources.json | 10 + .../handler.py | 899 +- .../handlers}/__init__.py | 0 .../handlers/diagnostic_handler.py | 103 + .../handlers/operational_handler.py | 406 + .../handlers/retry_handler.py | 119 + .../lambda_config.json | 14 +- .../requirements.txt | 2 +- .../retry_testing.md | 97 + .../deployment_resources.json | 1 + .../custodian_report_generator/handler.py | 125 +- .../lambda_config.json | 14 +- .../requirements.txt | 4 +- .../custodian_rule_meta_updater/handler.py | 145 +- .../lambda_config.json | 6 +- .../local_requirements.txt | 5 +- .../requirements.txt | 8 +- .../matplotlib_layer/lambda_layer_config.json | 8 - .../matplotlib_layer/local_requirements.txt | 0 .../layers/matplotlib_layer/requirements.txt | 1 - src/main.py | 1020 +- src/models/__init__.py | 47 + src/models/batch_results.py | 82 +- src/models/credentials_manager.py | 61 - src/models/customer_metrics.py | 6 +- src/models/event.py | 6 +- src/models/event_stats.py | 34 - src/models/job.py | 105 +- src/models/job_statistics.py | 10 +- src/models/licenses.py | 40 - src/models/modular/__init__.py | 40 - src/models/modular/application.py | 51 - src/models/modular/customer.py | 1 - src/models/modular/jobs.py | 1 - src/models/modular/parents.py | 23 - src/models/modular/tenant_settings.py | 1 - src/models/modular/tenants.py | 1 - src/models/policy.py | 19 +- src/models/pynamodb_extension/__init__.py | 0 src/models/pynamodb_extension/index.py | 114 - src/models/pynamodb_extension/pagination.py | 162 - src/models/report_statistics.py | 49 + src/models/retries.py | 6 +- src/models/role.py | 16 +- src/models/rule.py | 216 +- src/models/rule_meta.py | 58 - src/models/rule_source.py | 6 +- src/models/ruleset.py | 7 +- src/models/scheduled_job.py | 31 +- src/models/setting.py | 6 +- src/models/tenant_metrics.py | 6 +- src/models/user.py | 8 +- src/{exported_module => onprem}/Dockerfile | 59 +- src/onprem/Dockerfile-opensource | 33 + src/{exported_module => onprem}/README.md | 14 +- .../logs_extension => onprem}/__init__.py | 0 .../api}/__init__.py | 0 src/onprem/api/app.py | 198 + .../api/app_gunicorn.py | 0 src/onprem/api/cron_jobs.py | 58 + src/onprem/api/deployment_resources_parser.py | 104 + src/onprem/api/schedule_retry.py | 60 + src/onprem/requirements.txt | 28 + src/requirements.txt | 15 - src/run.py | 1302 ++ src/scheduler/__init__.py | 2 +- src/scheduler/ap_job_scheduler.py | 197 +- src/services/abs_lambda.py | 615 + src/services/abstract_api_handler_lambda.py | 258 - src/services/abstract_lambda.py | 60 - src/services/abstract_rule_service.py | 68 - src/services/ambiguous_job_service.py | 465 +- src/services/assemble_service.py | 86 +- src/services/azure_subscriptions_service.py | 49 - src/services/base_data_service.py | 16 +- src/services/batch_results_service.py | 386 +- src/services/cache.py | 4 +- src/services/clients/__init__.py | 119 + .../clients/abstract_key_management.py | 98 - src/services/clients/batch.py | 224 +- src/services/clients/cloudwatch.py | 34 - src/services/clients/cognito.py | 641 +- src/services/clients/dojo_client.py | 153 +- src/services/clients/ecr.py | 54 - src/services/clients/eks_client.py | 29 + src/services/clients/event_bridge.py | 54 +- src/services/clients/git_service_clients.py | 38 +- src/services/clients/iam.py | 3 +- src/services/clients/jwt_management_client.py | 157 + src/services/clients/kms.py | 36 - src/services/clients/lambda_func.py | 11 +- src/services/clients/license_manager.py | 134 +- src/services/clients/mongo_ssm_auth_client.py | 250 + src/services/clients/s3.py | 742 +- src/services/clients/scheduler.py | 109 +- src/services/clients/smtp.py | 3 - src/services/clients/ssm.py | 56 +- .../clients/standalone_key_management.py | 537 - src/services/clients/step_function.py | 93 + src/services/clients/sts.py | 104 +- src/services/coverage_service.py | 200 +- src/services/credentials_manager_service.py | 158 - src/services/defect_dojo_service.py | 267 + src/services/environment_service.py | 275 +- src/services/event_processor_service.py | 146 +- src/services/event_service.py | 49 +- src/services/findings_service.py | 488 - src/services/health_check_service.py | 317 +- src/services/integration_service.py | 50 + src/services/job_lock.py | 172 + src/services/job_service.py | 709 +- src/services/job_statistics_service.py | 4 +- src/services/key_management_service.py | 203 - src/services/license_manager_service.py | 137 +- src/services/license_manager_token.py | 105 + src/services/license_service.py | 398 +- src/services/mappings_collector.py | 291 + src/services/metrics_service.py | 158 +- src/services/modular_helpers.py | 331 + src/services/modular_service.py | 1319 -- src/services/obfuscation.py | 59 + src/services/openapi_spec_generator.py | 259 + src/services/platform_service.py | 240 + src/services/rabbitmq_service.py | 183 +- src/services/rbac/__init__.py | 0 src/services/rbac/access_control_service.py | 168 - .../rbac/endpoint_to_permission_mapping.py | 812 -- src/services/rbac/governance/__init__.py | 0 .../governance/abstract_governance_service.py | 58 - .../governance/priority_governance_service.py | 377 - src/services/rbac/iam_cache_service.py | 236 - src/services/rbac/restriction_service.py | 408 - src/services/rbac_service.py | 304 + src/services/report_convertors.py | 403 + src/services/report_service.py | 1106 +- src/services/report_statistics_service.py | 147 + src/services/reports_bucket.py | 364 + src/services/rule_meta_service.py | 594 +- src/services/rule_report_service.py | 400 - src/services/rule_source_service.py | 270 +- src/services/ruleset_service.py | 143 +- src/services/s3_settings_service.py | 151 +- src/services/scheduler_service.py | 10 +- src/services/service_provider.py | 869 +- src/services/setting_service.py | 222 +- src/services/sharding.py | 637 + src/services/ssm_service.py | 23 +- src/services/token_service.py | 54 - src/services/user_service.py | 101 - src/services/xlsx_writer.py | 166 + src/validators/deployment_resources.json | 10301 ++++++++-------- src/validators/registry.py | 1197 ++ src/validators/request_validation.py | 1492 --- src/validators/response_validation.py | 562 - src/validators/swagger_request_models.py | 1383 +++ src/validators/swagger_response_models.py | 681 + src/validators/utils.py | 79 +- tox.ini | 26 +- 540 files changed, 41293 insertions(+), 49265 deletions(-) create mode 100644 Makefile delete mode 100644 c7n/autocomplete/.c7n-complete.bash delete mode 100644 c7n/autocomplete/.c7n-complete.fish delete mode 100644 c7n/autocomplete/.c7n-complete.zsh delete mode 100644 c7n/c7ncli/group/application.py delete mode 100644 c7n/c7ncli/group/application_access.py delete mode 100644 c7n/c7ncli/group/application_dojo.py create mode 100644 c7n/c7ncli/group/integrations.py create mode 100644 c7n/c7ncli/group/integrations_dojo.py create mode 100644 c7n/c7ncli/group/integrations_sre.py delete mode 100644 c7n/c7ncli/group/parent.py delete mode 100644 c7n/c7ncli/group/report_errors_accumulated.py delete mode 100644 c7n/c7ncli/group/report_errors_jobs.py create mode 100644 c7n/c7ncli/group/report_findings.py create mode 100644 c7n/c7ncli/group/report_raw.py create mode 100644 c7n/c7ncli/group/setting_report.py delete mode 100644 c7n/c7ncli/group/siem.py delete mode 100644 c7n/c7ncli/group/siem_add.py delete mode 100644 c7n/c7ncli/group/siem_delete.py delete mode 100644 c7n/c7ncli/group/siem_describe.py delete mode 100644 c7n/c7ncli/group/siem_update.py delete mode 100644 c7n/c7ncli/group/tenant_findings.py delete mode 100644 c7n/c7ncli/group/tenant_region.py delete mode 100644 c7n/c7ncli/group/trigger.py delete mode 100644 c7n/c7ncli/group/user.py delete mode 100644 c7n/c7ncli/group/user_tenants.py create mode 100644 c7n/c7ncli/group/users.py delete mode 100644 c7n/docs/README.md delete mode 100644 c7n/docs/application/add.md delete mode 100644 c7n/docs/application/delete.md delete mode 100644 c7n/docs/application/describe.md delete mode 100644 c7n/docs/application/index.md delete mode 100644 c7n/docs/application/update.md delete mode 100644 c7n/docs/cleanup.md delete mode 100644 c7n/docs/configure.md delete mode 100644 c7n/docs/customer/describe.md delete mode 100644 c7n/docs/customer/index.md delete mode 100644 c7n/docs/health_check.md delete mode 100644 c7n/docs/job/describe.md delete mode 100644 c7n/docs/job/index.md delete mode 100644 c7n/docs/job/scheduled/add.md delete mode 100644 c7n/docs/job/scheduled/delete.md delete mode 100644 c7n/docs/job/scheduled/describe.md delete mode 100644 c7n/docs/job/scheduled/index.md delete mode 100644 c7n/docs/job/scheduled/update.md delete mode 100644 c7n/docs/job/submit.md delete mode 100644 c7n/docs/job/terminate.md delete mode 100644 c7n/docs/lm/delete.md delete mode 100644 c7n/docs/lm/describe.md delete mode 100644 c7n/docs/lm/index.md delete mode 100644 c7n/docs/lm/sync.md delete mode 100644 c7n/docs/login.md delete mode 100644 c7n/docs/parent/add.md delete mode 100644 c7n/docs/parent/delete.md delete mode 100644 c7n/docs/parent/describe.md delete mode 100644 c7n/docs/parent/index.md delete mode 100644 c7n/docs/parent/link_tenant.md delete mode 100644 c7n/docs/parent/unlink_tenant.md delete mode 100644 c7n/docs/parent/update.md delete mode 100644 c7n/docs/policy/add.md delete mode 100644 c7n/docs/policy/clean_cache.md delete mode 100644 c7n/docs/policy/delete.md delete mode 100644 c7n/docs/policy/describe.md delete mode 100644 c7n/docs/policy/index.md delete mode 100644 c7n/docs/policy/update.md delete mode 100644 c7n/docs/report/clevel.md delete mode 100644 c7n/docs/report/compliance/accumulated.md delete mode 100644 c7n/docs/report/compliance/index.md delete mode 100644 c7n/docs/report/compliance/jobs.md delete mode 100644 c7n/docs/report/department.md delete mode 100644 c7n/docs/report/details/accumulated.md delete mode 100644 c7n/docs/report/details/index.md delete mode 100644 c7n/docs/report/details/jobs.md delete mode 100644 c7n/docs/report/digests/accumulated.md delete mode 100644 c7n/docs/report/digests/index.md delete mode 100644 c7n/docs/report/digests/jobs.md delete mode 100644 c7n/docs/report/errors/accumulated/access.md delete mode 100644 c7n/docs/report/errors/accumulated/core.md delete mode 100644 c7n/docs/report/errors/accumulated/index.md delete mode 100644 c7n/docs/report/errors/accumulated/total.md delete mode 100644 c7n/docs/report/errors/index.md delete mode 100644 c7n/docs/report/errors/jobs/access.md delete mode 100644 c7n/docs/report/errors/jobs/core.md delete mode 100644 c7n/docs/report/errors/jobs/index.md delete mode 100644 c7n/docs/report/errors/jobs/total.md delete mode 100644 c7n/docs/report/index.md delete mode 100644 c7n/docs/report/operational.md delete mode 100644 c7n/docs/report/project.md delete mode 100644 c7n/docs/report/push/dojo.md delete mode 100644 c7n/docs/report/push/index.md delete mode 100644 c7n/docs/report/push/security_hub.md delete mode 100644 c7n/docs/report/rules/accumulated.md delete mode 100644 c7n/docs/report/rules/index.md delete mode 100644 c7n/docs/report/rules/jobs.md delete mode 100644 c7n/docs/results/describe.md delete mode 100644 c7n/docs/results/index.md delete mode 100644 c7n/docs/role/add.md delete mode 100644 c7n/docs/role/clean_cache.md delete mode 100644 c7n/docs/role/delete.md delete mode 100644 c7n/docs/role/describe.md delete mode 100644 c7n/docs/role/index.md delete mode 100644 c7n/docs/role/update.md delete mode 100644 c7n/docs/rule/delete.md delete mode 100644 c7n/docs/rule/describe.md delete mode 100644 c7n/docs/rule/index.md delete mode 100644 c7n/docs/rule/update.md delete mode 100644 c7n/docs/ruleset/add.md delete mode 100644 c7n/docs/ruleset/delete.md delete mode 100644 c7n/docs/ruleset/describe.md delete mode 100644 c7n/docs/ruleset/eventdriven/add.md delete mode 100644 c7n/docs/ruleset/eventdriven/delete.md delete mode 100644 c7n/docs/ruleset/eventdriven/describe.md delete mode 100644 c7n/docs/ruleset/eventdriven/index.md delete mode 100644 c7n/docs/ruleset/index.md delete mode 100644 c7n/docs/ruleset/update.md delete mode 100644 c7n/docs/rulesource/add.md delete mode 100644 c7n/docs/rulesource/delete.md delete mode 100644 c7n/docs/rulesource/describe.md delete mode 100644 c7n/docs/rulesource/index.md delete mode 100644 c7n/docs/rulesource/update.md delete mode 100644 c7n/docs/setting/index.md delete mode 100644 c7n/docs/setting/lm/client/add.md delete mode 100644 c7n/docs/setting/lm/client/delete.md delete mode 100644 c7n/docs/setting/lm/client/describe.md delete mode 100644 c7n/docs/setting/lm/client/index.md delete mode 100644 c7n/docs/setting/lm/config/add.md delete mode 100644 c7n/docs/setting/lm/config/delete.md delete mode 100644 c7n/docs/setting/lm/config/describe.md delete mode 100644 c7n/docs/setting/lm/config/index.md delete mode 100644 c7n/docs/setting/lm/index.md delete mode 100644 c7n/docs/setting/mail/add.md delete mode 100644 c7n/docs/setting/mail/delete.md delete mode 100644 c7n/docs/setting/mail/describe.md delete mode 100644 c7n/docs/setting/mail/index.md delete mode 100644 c7n/docs/show_config.md delete mode 100644 c7n/docs/siem/add/dojo.md delete mode 100644 c7n/docs/siem/add/index.md delete mode 100644 c7n/docs/siem/add/security_hub.md delete mode 100644 c7n/docs/siem/delete/dojo.md delete mode 100644 c7n/docs/siem/delete/index.md delete mode 100644 c7n/docs/siem/delete/security_hub.md delete mode 100644 c7n/docs/siem/describe/dojo.md delete mode 100644 c7n/docs/siem/describe/index.md delete mode 100644 c7n/docs/siem/describe/security_hub.md delete mode 100644 c7n/docs/siem/index.md delete mode 100644 c7n/docs/siem/update/dojo.md delete mode 100644 c7n/docs/siem/update/index.md delete mode 100644 c7n/docs/siem/update/security_hub.md delete mode 100644 c7n/docs/tenant/create.md delete mode 100644 c7n/docs/tenant/credentials/add.md delete mode 100644 c7n/docs/tenant/credentials/delete.md delete mode 100644 c7n/docs/tenant/credentials/describe.md delete mode 100644 c7n/docs/tenant/credentials/index.md delete mode 100644 c7n/docs/tenant/credentials/update.md delete mode 100644 c7n/docs/tenant/describe.md delete mode 100644 c7n/docs/tenant/findings/delete.md delete mode 100644 c7n/docs/tenant/findings/describe.md delete mode 100644 c7n/docs/tenant/findings/index.md delete mode 100644 c7n/docs/tenant/index.md delete mode 100644 c7n/docs/tenant/region/activate.md delete mode 100644 c7n/docs/tenant/region/index.md delete mode 100644 c7n/docs/tenant/update.md delete mode 100644 c7n/docs/trigger/configuration_backup.md delete mode 100644 c7n/docs/trigger/index.md delete mode 100644 c7n/docs/user/index.md delete mode 100644 c7n/docs/user/signup.md delete mode 100644 c7n/docs/user/tenants/assign.md delete mode 100644 c7n/docs/user/tenants/describe.md delete mode 100644 c7n/docs/user/tenants/index.md delete mode 100644 c7n/docs/user/tenants/unassign.md create mode 100644 c7n/pyproject.toml delete mode 100644 c7n/setup.py create mode 100644 obfuscation_manager/README.md create mode 100644 obfuscation_manager/obfuscation_manager.py create mode 100644 obfuscation_manager/pyproject.toml delete mode 100644 src/connections/auth_extension/base_auth_client.py delete mode 100644 src/connections/auth_extension/cognito_to_jwt_adapter.py delete mode 100644 src/connections/batch_extension/base_job_client.py delete mode 100644 src/connections/batch_extension/batch_to_subprocess_adapter.py delete mode 100644 src/connections/logs_extension/base_logs_client.py delete mode 100644 src/connections/logs_extension/cw_to_log_file_adapter.py create mode 100644 src/executor/Dockerfile create mode 100644 src/executor/Dockerfile-opensource create mode 100644 src/executor/README.md rename src/{connections => executor}/__init__.py (100%) rename src/{connections/auth_extension => executor/helpers}/__init__.py (100%) create mode 100644 src/executor/helpers/constants.py create mode 100644 src/executor/helpers/profiling.py create mode 100644 src/executor/requirements.txt create mode 100644 src/executor/services/__init__.py create mode 100644 src/executor/services/credentials_service.py create mode 100644 src/executor/services/environment_service.py create mode 100644 src/executor/services/license_manager_service.py rename src/{ => executor}/services/notification_service.py (58%) create mode 100644 src/executor/services/policy_service.py create mode 100644 src/executor/services/report_service.py delete mode 100644 src/exported_module/api/__init__.py delete mode 100644 src/exported_module/api/app.py delete mode 100644 src/exported_module/api/deployment_resources_parser.py delete mode 100644 src/exported_module/api/license_sync.py delete mode 100644 src/exported_module/requirements.txt delete mode 100644 src/exported_module/scripts/__init__.py delete mode 100644 src/exported_module/scripts/init_minio.py delete mode 100644 src/exported_module/scripts/init_mongo.py delete mode 100644 src/exported_module/scripts/init_vault.py delete mode 100644 src/exported_module/scripts/run.sh delete mode 100644 src/handlers/abstracts/__init__.py delete mode 100644 src/handlers/abstracts/abstract_credentials_manager_handler.py delete mode 100644 src/handlers/abstracts/abstract_handler.py delete mode 100644 src/handlers/abstracts/abstract_modular_entity_handler.py delete mode 100644 src/handlers/abstracts/abstract_user_handler.py delete mode 100644 src/handlers/applications_handler.py delete mode 100644 src/handlers/base_handler.py create mode 100644 src/handlers/credentials_handler.py delete mode 100644 src/handlers/credentials_manager_handler.py create mode 100644 src/handlers/defect_dojo_handler.py delete mode 100644 src/handlers/parents_handler.py create mode 100644 src/handlers/raw_report_handler.py create mode 100644 src/handlers/report_status_handler.py create mode 100644 src/handlers/self_integration_handler.py create mode 100644 src/handlers/send_report_setting_handler.py create mode 100644 src/handlers/tenant_handler.py delete mode 100644 src/handlers/tenants/__init__.py delete mode 100644 src/handlers/tenants/license_priority_handler.py delete mode 100644 src/handlers/tenants/tenant_handler.py delete mode 100644 src/handlers/user_customer_handler.py delete mode 100644 src/handlers/user_role_handler.py delete mode 100644 src/handlers/user_tenants_handler.py delete mode 100644 src/helpers/docker.py delete mode 100644 src/helpers/enums.py delete mode 100644 src/helpers/exception.py delete mode 100644 src/helpers/json_util.py create mode 100644 src/helpers/lambda_response.py delete mode 100644 src/helpers/security.py delete mode 100644 src/helpers/utils.py delete mode 100644 src/integrations/__init__.py delete mode 100644 src/integrations/defect_dojo_adapter.py delete mode 100644 src/integrations/security_hub_adapter.py delete mode 100644 src/lambdas/caas_jobs_backupper/__init__.py delete mode 100644 src/lambdas/caas_jobs_backupper/deployment_resources.json delete mode 100644 src/lambdas/caas_jobs_backupper/handler.py delete mode 100644 src/lambdas/caas_jobs_backupper/lambda_config.json.old delete mode 100644 src/lambdas/caas_jobs_backupper/local_requirements.txt delete mode 100644 src/lambdas/caas_jobs_backupper/requirements.txt create mode 100644 src/lambdas/custodian_api_handler/handlers/events_handler.py create mode 100644 src/lambdas/custodian_api_handler/handlers/new_swagger_handler.py create mode 100644 src/lambdas/custodian_api_handler/handlers/users_handler.py delete mode 100644 src/lambdas/custodian_configuration_backupper/README.md delete mode 100644 src/lambdas/custodian_configuration_backupper/__init__.py delete mode 100644 src/lambdas/custodian_configuration_backupper/deployment_resources.json delete mode 100644 src/lambdas/custodian_configuration_backupper/handler.py delete mode 100644 src/lambdas/custodian_configuration_backupper/lambda_config.json delete mode 100644 src/lambdas/custodian_configuration_backupper/local_requirements.txt delete mode 100644 src/lambdas/custodian_configuration_backupper/requirements.txt delete mode 100644 src/lambdas/custodian_configuration_updater/README.md delete mode 100644 src/lambdas/custodian_configuration_updater/__init__.py delete mode 100644 src/lambdas/custodian_configuration_updater/deployment_resources.json delete mode 100644 src/lambdas/custodian_configuration_updater/handler.py delete mode 100644 src/lambdas/custodian_configuration_updater/lambda_config.json delete mode 100644 src/lambdas/custodian_configuration_updater/local_requirements.txt delete mode 100644 src/lambdas/custodian_configuration_updater/requirements.txt delete mode 100644 src/lambdas/custodian_metrics_updater/processors/customer_metrics_processor.py create mode 100644 src/lambdas/custodian_metrics_updater/processors/diagnostic_metrics_processor.py delete mode 100644 src/lambdas/custodian_notification_handler/README.md delete mode 100644 src/lambdas/custodian_notification_handler/__init__.py delete mode 100644 src/lambdas/custodian_notification_handler/deployment_resources.json delete mode 100644 src/lambdas/custodian_notification_handler/handler.py delete mode 100644 src/lambdas/custodian_notification_handler/lambda_config.json delete mode 100644 src/lambdas/custodian_notification_handler/local_requirements.txt delete mode 100644 src/lambdas/custodian_notification_handler/requirements.txt rename src/{connections/batch_extension => lambdas/custodian_report_generation_handler/handlers}/__init__.py (100%) create mode 100644 src/lambdas/custodian_report_generation_handler/handlers/diagnostic_handler.py create mode 100644 src/lambdas/custodian_report_generation_handler/handlers/operational_handler.py create mode 100644 src/lambdas/custodian_report_generation_handler/handlers/retry_handler.py create mode 100644 src/lambdas/custodian_report_generation_handler/retry_testing.md delete mode 100644 src/lambdas/layers/matplotlib_layer/lambda_layer_config.json delete mode 100644 src/lambdas/layers/matplotlib_layer/local_requirements.txt delete mode 100644 src/lambdas/layers/matplotlib_layer/requirements.txt delete mode 100644 src/models/credentials_manager.py delete mode 100644 src/models/event_stats.py delete mode 100644 src/models/licenses.py delete mode 100644 src/models/modular/__init__.py delete mode 100644 src/models/modular/application.py delete mode 100644 src/models/modular/customer.py delete mode 100644 src/models/modular/jobs.py delete mode 100644 src/models/modular/parents.py delete mode 100644 src/models/modular/tenant_settings.py delete mode 100644 src/models/modular/tenants.py delete mode 100644 src/models/pynamodb_extension/__init__.py delete mode 100644 src/models/pynamodb_extension/index.py delete mode 100644 src/models/pynamodb_extension/pagination.py create mode 100644 src/models/report_statistics.py delete mode 100644 src/models/rule_meta.py rename src/{exported_module => onprem}/Dockerfile (54%) create mode 100644 src/onprem/Dockerfile-opensource rename src/{exported_module => onprem}/README.md (86%) rename src/{connections/logs_extension => onprem}/__init__.py (100%) rename src/{exported_module => onprem/api}/__init__.py (100%) create mode 100644 src/onprem/api/app.py rename src/{exported_module => onprem}/api/app_gunicorn.py (100%) create mode 100644 src/onprem/api/cron_jobs.py create mode 100644 src/onprem/api/deployment_resources_parser.py create mode 100644 src/onprem/api/schedule_retry.py create mode 100644 src/onprem/requirements.txt delete mode 100644 src/requirements.txt create mode 100644 src/run.py create mode 100644 src/services/abs_lambda.py delete mode 100644 src/services/abstract_api_handler_lambda.py delete mode 100644 src/services/abstract_lambda.py delete mode 100644 src/services/abstract_rule_service.py delete mode 100644 src/services/azure_subscriptions_service.py delete mode 100644 src/services/clients/abstract_key_management.py delete mode 100644 src/services/clients/cloudwatch.py delete mode 100644 src/services/clients/ecr.py create mode 100644 src/services/clients/eks_client.py create mode 100644 src/services/clients/jwt_management_client.py delete mode 100644 src/services/clients/kms.py create mode 100644 src/services/clients/mongo_ssm_auth_client.py delete mode 100644 src/services/clients/standalone_key_management.py create mode 100644 src/services/clients/step_function.py delete mode 100644 src/services/credentials_manager_service.py create mode 100644 src/services/defect_dojo_service.py delete mode 100644 src/services/findings_service.py create mode 100644 src/services/integration_service.py create mode 100644 src/services/job_lock.py delete mode 100644 src/services/key_management_service.py create mode 100644 src/services/license_manager_token.py create mode 100644 src/services/mappings_collector.py create mode 100644 src/services/modular_helpers.py delete mode 100644 src/services/modular_service.py create mode 100644 src/services/obfuscation.py create mode 100644 src/services/openapi_spec_generator.py create mode 100644 src/services/platform_service.py delete mode 100644 src/services/rbac/__init__.py delete mode 100644 src/services/rbac/access_control_service.py delete mode 100644 src/services/rbac/endpoint_to_permission_mapping.py delete mode 100644 src/services/rbac/governance/__init__.py delete mode 100644 src/services/rbac/governance/abstract_governance_service.py delete mode 100644 src/services/rbac/governance/priority_governance_service.py delete mode 100644 src/services/rbac/iam_cache_service.py delete mode 100644 src/services/rbac/restriction_service.py create mode 100644 src/services/rbac_service.py create mode 100644 src/services/report_convertors.py create mode 100644 src/services/report_statistics_service.py create mode 100644 src/services/reports_bucket.py delete mode 100644 src/services/rule_report_service.py create mode 100644 src/services/sharding.py delete mode 100644 src/services/token_service.py delete mode 100644 src/services/user_service.py create mode 100644 src/services/xlsx_writer.py create mode 100644 src/validators/registry.py delete mode 100644 src/validators/request_validation.py delete mode 100644 src/validators/response_validation.py create mode 100644 src/validators/swagger_request_models.py create mode 100644 src/validators/swagger_response_models.py diff --git a/.dockerignore b/.dockerignore index 16e81b366..c6050d6fd 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,26 +1,27 @@ -**/venv +**/*venv* **/*.pyc **/__pycache__ +**/.tmp +**/*.md +**/.env .pytest_cache .coverage htmlcov - .gitlab .git .cache dist build -*.egg-info +**/*.egg-info .idea .vscode -*.md *syndicate* -c7n/ -docker/logs/ -docs/ -scripts/ -patch_scripts/ -tests/ \ No newline at end of file +c7n +docs +deployment +patch_scripts +tests +obfuscation_manager \ No newline at end of file diff --git a/.gitignore b/.gitignore index cf8526545..eef04a9ef 100644 --- a/.gitignore +++ b/.gitignore @@ -250,4 +250,5 @@ pip-selfcheck.json # End of https://www.toptal.com/developers/gitignore/api/python,pycharm+all,virtualenv .syndicate -.tmp/ \ No newline at end of file +.tmp/ +**/.DS_Store \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 4121fe368..13e394e57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,212 @@ # Changelog + All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -# [4.15.0] - 2023-10-16 +## [5.1.0] - 2024-04-17 +- add `tenant` and `effect` to policy model to allow more flexible policies configurations +- apply tenant restrictions to most endpoints +- refactor `report-generation-handler` slightly +- moved to the latest vault version for k8s and docker +- add `href` param to job resources report. Added `impact`, `remediation` and `article` to that report + +## [5.0.3] - 2024-04-09 +- added instance profile support in case no credentials for scan can be resolved +- added human-readable errors for jobs + +## [5.0.2] - 2024-04-02 +- allow users endpoints for standard customers + +## [5.0.1] - 2024-02-14 +- optimize shards a bit +- add refresh token for onprem + +## [5.0.0] - 2024-02-14 + +- Removed endpoints + - `POST /tenant/regions` **(replaced with /tenants/{tenant_name}/regions)** + - `POST /backup` **(obsolete)** + - `DELETE /license` **(moved to /licenses/{license_key})** + - `GET /license` **(moved to /licenses/{license_key})** + - `POST /license` **(moved to /licenses)** + - `POST /license/sync` **(moved to /licenses/{license_key}/sync)** + - `GET /reports/digests/tenants/jobs` **(obsolete)** + - `GET /reports/digests/tenants` **(obsolete)** + - `GET /reports/digests/tenants/{tenant_name}` **(obsolete)** + - `GET /reports/details/tenants/jobs` **(obsolete)** + - `GET /reports/details/tenants` **(obsolete)** + - `GET /reports/details/tenants/{tenant_name}` **(obsolete)** + - `POST /reports/push/security-hub/{job_id}` **(obsolete)** + - `POST /reports/push/security-hub` **(obsolete)** + - `ANY /applications/dojo` **(moved to /integrations/defect-dojo)** + - `ANY /applications/dojo/{application_id}` **(moved to /integrations/defect-dojo)** + - `ANY /applications/access` **(moved to /integrations/temp/sre)** + - `ANY /applications/access/{application_id}` **(moved to /integrations/temp/sre)** + - `ANY /applications` **(moved to /licenses)** + - `ANY /applications/{application_id}` **(moved to /licenses)** + - `ANY /parents` **(moved to /integrations//activation and under abstraction)*** + - `ANY /parents/{parent_id}` **(moved to /integrations/*/activation and under abstraction)** + +- Added endpoints: + + - `POST /refresh` **(allows to refresh access token)** + - `GET /tenants/{tenant_name}` + - `DELETE /tenants/{tenant_name}` + - `POST /tenants/tenant_name}/regions` + - `GET /tenants/{tenant_name}/active-licenses` **(Get licenses that are active for tenant)** + - `POST /licenses` **(add license from LM, instead of creating application CUSTODIAN_LICENSES)** + - `GET /licenses` + - `GET /licenses/{license_key}` + - `DELETE /licenses/{license_key}` + - `POST /licenses/{license_key}/sync` + - `GET /licenses/{license_key}/activation` + - `PATCH /licenses/{license_key}/activation` + - `DELETE /licenses/{license_key}/activation` + - `PUT /licenses/{license_key}/activation` **(link license to tenants)** + - `POST /settings/send_reports` + - `GET /integrations/defect-dojo` + - `POST /integrations/defect-dojo` **(add defect dojo installation, instead of creating application DEFECT_DOJO)** + - `GET /integrations/defect-dojo/{id}` + - `DELETE /integrations/defect-dojo/{id}` + - `PUT /integrations/defect-dojo/{id}/activation` **(link dojo to tenants)** + - `GET /integrations/defect-dojo/{id}/activation` + - `DELETE /integrations/defect-dojo/{id}/activation` + - `PUT /integrations/temp/sre` **(add application CUSTODIAN and link)** + - `GET /integrations/temp/sre` + - `PUT /integrations/temp/sre` + - `PATCH /integrations/temp/sre` + - `PUT /tenants/{tenant_name}/excluded-rules` **(set excluded rules for tenant)** + - `GET /tenants/{tenant_name}/excluded-rules` + - `PUT /customers/excluded-rules` **(set excluded rules for customer)** + - `GET /customers/excluded-rules` + - `GET /doc` **(swagger UI)** + - `GET /credentials` + - `GET /credentials/{id}` + - `GET /credentials/{id}/binding` + +- added severity to digest reports +- all endpoints that returned one item in a list or an empty list in case the requested item does not exist + (GET /jobs/{job_id}) not return 404 of JSON with one key `data` and the requested object: + ```json + { + "data": {"id": "qwerty", "status": "SUBMITTED"} + } + ``` + Generally, all the endpoints that returned one logic item now return it inside `data` key instead of `items`. +- trace_id attribute is returned in headers and removed from body +- Validation errors now returned in such a format: + ```json + {"errors": [ + {"location": ["status"], "message": "not available status, choose from: RUNNING, SUBMITTED, ..."}, + {"location": ["limit"], "message": "limit cannot be negative"} + ]} + ``` +- Added swagger UI to onprem and SAAS, added OpenApi v3 spec generator. +- removed Pycryptodome and other requirements +- moved to Pydantic V2 +- added more tests +- used classes with slots instead of dataclasses for performance reasons +- removed `CaaSLicenses`, `CaaSRuleMeta` dynamodb tables +- removed `caas-configuration-updater` & `caas-configuration-backupper` lambdas + +## [4.20.0] - 2024-01-22 +- fix issue with report type was ignored for all high-level reports. +- correct response models for most actions + +## [4.19.4] - 2024-01-26 +- upgraded `modular-sdk` version from 4.0.0 to 5.0.0 + +## [4.19.3] - 2024-01-19 +- fixed an issue related to request validation error in case of adding a + `parent` without the `--scope` parameter specified +- upgraded `modular-sdk` version from 3.3.10 to 4.0.0 +- implemented adding the parameter `sub` of Cognito user to attributes + `created_by` and `updated_by` of entities `Parent` and `Application` during related operations +- set the "logs_expiration" parameter for lambdas + +## [4.19.2] - 2024-01-16 +- fix dates bug in diagnostic reports +- slightly change dates resolving in metrics + +## [4.19.1] - 2024-01-11 +- add `namespace` for k8s to the list of compulsory report fields +- fixed an issue related to the previous week date definition when the server starts + +## [4.19.0] - 2023-12-22 +- added `/reports/status` endpoint to retrieve report job status by its ID +- changed response mapping for high-level reports +- added `MAX_RABBITMQ_REQUEST_SIZE` setting + +## [4.18.0] - 2023-12-20 +- added ability to send big requests with report data to the RabbitMQ + +## [4.17.2] - 2023-12-19 +- fixed bug in detection duplicates in pending reports +- added `status-index` to the `CaaSReportStatistics` table + +## [4.17.1] - 2023-12-14 +- upgraded `modular-sdk` version from 3.3.7 to 3.3.10 + +## [4.17.0] - 2023-12-08 +- added fail-safe logic for reports sending; +- added new endpoint `settings/send_reports`; +- added two step-functions: `send_reports` and `retry_send_reports` +- some report endpoints now point to the step functions instead of lambda: + - `/reports/clevel` + - `/reports/project` + - `/reports/operational` + - `/reports/department` +- added new settings `SEND_REPORT`, `MAX_CRON_NUMBER`(only for onprem) +- remove `docker` folder, move and merge everything with `src`. Change `Dockerfile` +- added a class to resolve Rule comment (https://github.com/epam/ecc-kubernetes-rulepack/wiki/Rule-Index-(Comment)-Structure). + +## [4.16.4] - 2023-12-04 +- added cache for RabbitMQ transport configuration +- now metrics updater rewrites diagnostic report data with each lambda execution with the appropriate parameters + +## [4.16.3] - 2023-12-01 +- fixed bug in metrics when both CaaSJobs and CaaSBatchResults items were processed equally despite having +different parameters + +## [4.16.2] - 2023-11-29 +- fixed bug in kubernetes recommendations when all resources were overwritten by the last resource in the list + +## [4.16.1] - 2023-11-28 +- updated the project attack report to use all sub-techniques and severity +- added all kubernetes findings to the recommendations without binding to severity + +## [4.16.0] - 2023-11-24 +- added new metrics type - kubernetes +- added new operational level report type - kubernetes +- added ability to archive the metrics of those tenants that have not been scanned for more than a month +- added kubernetes clusters recommendations +- added `/reports/diagnostic` endpoint +- changed s3 files format and paths. Now data is divided into shards +- removed not used and obsolete code +- added resources report. Added xlsx format for different reports. +- global refactoring + +## [4.15.4] - 2023-11-08 +- make job lock consider regions + +## [4.15.3] - 2023-11-08 +- fixed `patch_customer_data_2.0` patch to make it compatible with on-prem version + +## [4.15.2] - 2023-11-01 +- fixed bug when the coverage list was empty for tenant that violates every rules +- fixed bug when metrics lambda deletes archived file of the specific tenant instead of non-archived file of the same tenant +- Implement License Manager auth token storage/rotation per customer + +## [4.15.1] - 2023-10-23 +- marked the metrics of deactivated tenants or tenants with expired license with `archive-` prefix and +ignore such tenants when aggregate metrics +- event-driven jobs are now also included in the rule report +- optimized calculation of a large number of jobs for rules report + +## [4.15.0] - 2023-10-16 - removed a lot of not used code - use python built-in enums for HTTP methods and statuses instead of constants - added k8s scans support: @@ -17,14 +218,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - refactored a lot - moved to new parent scopes (modular-sdk 3.3.0+) -# [4.14.2] - 2023-10-13 -* changed the logic for duplicating metrics for those tenants that have not been scanned for a week+ - +## [4.14.2] - 2023-10-13 +- changed the logic for duplicating metrics for those tenants that have not been scanned for a week+ - now duplicated only once per week -# [4.14.1] - 2023-10-12 -* added a new field to the model of each report, which will display a list of tenants with outdated data +## [4.14.1] - 2023-10-12 +- added a new field to the model of each report, which will display a list of tenants with outdated data -# [4.14.0] - 2023-10-09 +## [4.14.0] - 2023-10-09 - moved to python3.10 - updated dependencies versions: - pymongo: `3.12.0` -> `4.5.0` @@ -47,434 +248,454 @@ now duplicated only once per week - add env `INNER_CACHE_TTL_SECONDS` which controls inner cache -# [4.13.7] - 2023-10-05 -* fixed invalid data format for finops metrics after difference calculation - -# [4.13.6] - 2023-10-05 -* fixed invalid time range when calculating weekly scan statistics - -# [4.13.5] - 2023-10-04 -* fixed bug when google accounts were not being added to MITRE high-level reports -* fixed bug when empty service section in FinOps metrics was skipped - -# [4.13.4] - 2023-10-04 -* updated `modular-sdk` version to `2.2.6a0` -* fixed bug when `end_date` is string not datetime at the `tenant_groups` metrics stage - -# [4.13.3] - 2023-10-03 -* fixed metrics difference calculation for finops project report - -# [4.13.2] - 2023-09-28 -* added optional `recievers` parameter for the following endpoints: - * `/reports/operational GET` - * `/reports/project GET` -* changed type of `types` parameter from list to str for the following endpoints: - * `/reports/operational GET` - * `/reports/project GET` - * `/reports/department GET` - * `/reports/clevel GET` - -# [4.13.1] - 2023-09-28 -* Fixed a bug where failed event-driven scans were skipped when aggregating metrics and collecting weekly scan statistics - -# [4.13.0] - 2023-09-28 -* added finops metrics for operational and project reports -* removed optional `type` parameter for the following endpoints: - * `/reports/operational GET` - * `/reports/project GET` - * `/reports/department GET` - * `/reports/clevel GET` -* added optional `types` parameter of list type for the following endpoints: - * `/reports/operational GET` - * `/reports/project GET` - * `/reports/department GET` - * `/reports/clevel GET` - -# [4.12.7] - 2023-09-25 -* fixed a bug when resources in the event-driven report were displayed on one line instead of several -* make cloud not required in rule-meta model, add platform - -# [4.12.6] - 2023-09-22 -* added new field with rule description to operational MITRE report - -# [4.12.5] - 2023-09-21 -* refactored metrics aggregation sequence and moved department metrics aggregation to a separate step. The new +## [4.13.7] - 2023-10-05 +- fixed invalid data format for finops metrics after difference calculation + +## [4.13.6] - 2023-10-05 +- fixed invalid time range when calculating weekly scan statistics + +## [4.13.5] - 2023-10-04 +- fixed bug when google accounts were not being added to MITRE high-level reports +- fixed bug when empty service section in FinOps metrics was skipped + +## [4.13.4] - 2023-10-04 +- updated `modular-sdk` version to `2.2.6a0` +- fixed bug when `end_date` is string not datetime at the `tenant_groups` metrics stage + +## [4.13.3] - 2023-10-03 +- fixed metrics difference calculation for finops project report + +## [4.13.2] - 2023-09-28 +- added optional `recievers` parameter for the following endpoints: + - `/reports/operational GET` + - `/reports/project GET` +- changed type of `types` parameter from list to str for the following endpoints: + - `/reports/operational GET` + - `/reports/project GET` + - `/reports/department GET` + - `/reports/clevel GET` + +## [4.13.1] - 2023-09-28 +- Fixed a bug where failed event-driven scans were skipped when aggregating metrics and collecting weekly scan statistics + +## [4.13.0] - 2023-09-28 +- added finops metrics for operational and project reports +- removed optional `type` parameter for the following endpoints: + - `/reports/operational GET` + - `/reports/project GET` + - `/reports/department GET` + - `/reports/clevel GET` +- added optional `types` parameter of list type for the following endpoints: + - `/reports/operational GET` + - `/reports/project GET` + - `/reports/department GET` + - `/reports/clevel GET` + +## [4.12.7] - 2023-09-25 +- fixed a bug when resources in the event-driven report were displayed on one line instead of several +- make cloud not required in rule-meta model, add platform + +## [4.12.6] - 2023-09-22 +- added new field with rule description to operational MITRE report + +## [4.12.5] - 2023-09-21 +- refactored metrics aggregation sequence and moved department metrics aggregation to a separate step. The new sequence provides the same data source for the department and chief levels: - * Old sequence: operational(tenant) -> project(tenant_group) + department level + mitre, compliance chief level) -> overview chief level -> difference - * New sequence: operational(tenant) -> project(tenant_group) -> top(department + chief level) -> difference -* The summation of weekly statistics on the number of scans has been moved from the project level to the operational level. + - Old sequence: operational(tenant) -> project(tenant_group) + department level + mitre, compliance chief level) -> overview chief level -> difference + - New sequence: operational(tenant) -> project(tenant_group) -> top(department + chief level) -> difference +- The summation of weekly statistics on the number of scans has been moved from the project level to the operational level. -# [4.12.4] - 2023-09-21 -* added tenant job lock to allow only one job per tenant in a certain moment +## [4.12.4] - 2023-09-21 +- added tenant job lock to allow only one job per tenant in a certain moment -# [4.12.3] - 2023-09-15 -* fixed copying of findings files to a folder with a new date if the files are already there -* severity deduplication moved to the `helpers/utils.py` -* look up resources by only top-level attributes +## [4.12.3] - 2023-09-15 +- fixed copying of findings files to a folder with a new date if the files are already there +- severity deduplication moved to the `helpers/utils.py` +- look up resources by only top-level attributes -# [4.12.2] - 2023-09-13 -* fixed metrics for tenants that were not scanned for current week and do not remove resources severity from overview type -* reset attack customer variable for each customer +## [4.12.2] - 2023-09-13 +- fixed metrics for tenants that were not scanned for current week and do not remove resources severity from overview type +- reset attack customer variable for each customer -# [4.12.1] - 2023-09-12 -* fixed collecting metrics for a specific date -* fix retrieving the latest findings for metrics +## [4.12.1] - 2023-09-12 +- fixed collecting metrics for a specific date +- fix retrieving the latest findings for metrics -# [4.12.0] - 2023-09-05 -* added `GET /reports/resources/tenants/{tenant_name}/state/latest` endpoint to +## [4.12.0] - 2023-09-05 +- added `GET /reports/resources/tenants/{tenant_name}/state/latest` endpoint to allow to retrieve specific resource report -* added `GET /reports/resources/tenants/{tenant_name}/jobs` endpoint to +- added `GET /reports/resources/tenants/{tenant_name}/jobs` endpoint to retrieve a list of the latest jobs where the resource was found -* added `GET /reports/resources/jobs/{id}` endpoint -* added rules_to_scan validation +- added `GET /reports/resources/jobs/{id}` endpoint +- added rules_to_scan validation -# [4.11.2] - 2023-09-05 -* fix querying tenants by its account number only for google tenants +## [4.11.2] - 2023-09-05 +- fix querying tenants by its account number only for google tenants -# [4.11.1] - 2023-09-01 -* refactor `customer_metrics_processor`, move common code to the metrics service -* add ability to query tenants by its account number (NOT project id) +## [4.11.1] - 2023-09-01 +- refactor `customer_metrics_processor`, move common code to the metrics service +- add ability to query tenants by its account number (NOT project id) -# [4.11.0] - 2023-09-01 -* added `POST /rule-meta/standards`, `POST /rule-meta/meta`, `POST /rule-meta/mappings`; -* fix accumulated reports; +## [4.11.0] - 2023-09-01 +- added `POST /rule-meta/standards`, `POST /rule-meta/meta`, `POST /rule-meta/mappings`; +- fix accumulated reports; -# [4.10.2] - 2023-08-31 -* reduce items limit for batch_save() method and catch PutError when saving department and c-level metrics -* change the `report_type` field in metrics to more human-readable names -* add `service` field to policy metadata -* fix Internal in case user does not exist when you try to change its tenants +## [4.10.2] - 2023-08-31 +- reduce items limit for batch_save() method and catch PutError when saving department and c-level metrics +- change the `report_type` field in metrics to more human-readable names +- add `service` field to policy metadata +- fix Internal in case user does not exist when you try to change its tenants -# [4.10.1] - 2023-08-28 -* fix bug that occurs when selecting tenants for an event-driven report -* resources in overview reports are now saved with the highest severity level -* fix resources number for tenant_metrics_updater. Refactor and optimize tenant_metrics_updater +## [4.10.1] - 2023-08-28 +- fix bug that occurs when selecting tenants for an event-driven report +- resources in overview reports are now saved with the highest severity level +- fix resources number for tenant_metrics_updater. Refactor and optimize tenant_metrics_updater -# [4.10.0] - 2023-08-23 (hotfix) -* change the interface for interacting with the Tenants table when generating event-driven report +## [4.10.0] - 2023-08-23 (hotfix) +- change the interface for interacting with the Tenants table when generating event-driven report (from customer-index to query) -* remove duplicates from operational and project OVERVIEW report when splitting resources by +- remove duplicates from operational and project OVERVIEW report when splitting resources by severity and by resource_type -* fix monthly metrics storage path from current month to next month (so the metrics for recreating +- fix monthly metrics storage path from current month to next month (so the metrics for recreating reports from 01-08 to 01-09 will be stored in 01-09 folder and will be overwritten only in August) -* return the nearest found findings in case today's do not exist -* fix a bug with caas-job-updater when it did not send status to LM -* add an ability to specify LM api version -* add a missing changelog -* fix permissions POST validator - -# [4.9.4] - 2023-08-15 -* fix `last_scan_date` comparison in c-level OVERVIEW report - -# [4.9.3] - 2023-08-15 -* add a missing condition to the standard iteration method -* get impact, article, remediation for dojo reports from meta - -# [4.9.2] - 2023-08-14 -* fix convert region coverages to percents (ex. 0.24 -> 24) -* add `raw` param to get findings action to allow to return raw findings content -* add the ability to handle multiple customer applications in c-level reports -* fix issue in request forming while sending email body to RabbitMQ -* fix weekly job statistics calculation - -# [4.9.1] - 2023-08-11 -* fix typo in `customer_metrics_processor` (last_scan -> last_scan_date) - -# [4.9.0] - 2023-08-10 -* changed rules format; -* updated `CaaSRules` model - moved `version`, `standard`, `min_core_version`, `impact`, `severity`, `service_section` +- return the nearest found findings in case today's do not exist +- fix a bug with caas-job-updater when it did not send status to LM +- add an ability to specify LM api version +- add a missing changelog +- fix permissions POST validator + +## [4.9.4] - 2023-08-15 +- fix `last_scan_date` comparison in c-level OVERVIEW report + +## [4.9.3] - 2023-08-15 +- add a missing condition to the standard iteration method +- get impact, article, remediation for dojo reports from meta + +## [4.9.2] - 2023-08-14 +- fix convert region coverages to percents (ex. 0.24 -> 24) +- add `raw` param to get findings action to allow to return raw findings content +- add the ability to handle multiple customer applications in c-level reports +- fix issue in request forming while sending email body to RabbitMQ +- fix weekly job statistics calculation + +## [4.9.1] - 2023-08-11 +- fix typo in `customer_metrics_processor` (last_scan -> last_scan_date) + +## [4.9.0] - 2023-08-10 +- changed rules format; +- updated `CaaSRules` model - moved `version`, `standard`, `min_core_version`, `impact`, `severity`, `service_section` fields to the `CaaSRulesMeta` table; -* added `CaaSRulesMeta` table to store policies' metadata; -* removed `caas-rulset-compiler` lambda. There is no longer a need for it, since the ruleset is collected immediately +- added `CaaSRulesMeta` table to store policies' metadata; +- removed `caas-rulset-compiler` lambda. There is no longer a need for it, since the ruleset is collected immediately from the data in the `CaaSRules` table; -* changed ruleset format from `.yml` to `.json` -* updated `CaaSRuleset` table: - * deleted `customer-ruleset-name` and `customer-cloud` GSI; - * added `customer-id-index'` and `license_manager_id-index` GSI; - * made `id` field a composite key - `customer#L|S#name#version`; - * removed `name`, `licensed` and `version` fields; -* added new health-check - rules_meta_access_data; -* renamed lambda environment variables that contained `mcdm` with `modular`; -* removed user detailed report; -* added an ability to build rulesets by git project id and ref; -* added an ability to describe and delete rules by git project id and ref; -* refactored compliance report - removed excessive code; -* parse eventdriven maps dynamically; -* getting impact and article for recommendation from `CaaSRulesMeta`; - - -# [4.8.1] - 2023-08-09 -* fixed start date in ED report generation - -# [4.8.0] - 2023-08-04 -* replaced MCDM with Modular - -# [4.7.3] - 2023-08-02 -* added `CaaSJobStatistics` table; -* collect c-level `OVERVIEW` report using the weekly data that stored in `JobStatistics` table; -* fix duplicate recommendation bug; - -# [4.7.2] - 2023-08-01 -* fix `NEXT_STEP` value for `findings` step in metrics updater -* fix recommendations bucket name in environment_service -* add a parameter `is_zipped` to s3 service to save the file in an uncompressed format -* added endpoints to manage Defect Dojo applications: +- changed ruleset format from `.yml` to `.json` +- updated `CaaSRuleset` table: + - deleted `customer-ruleset-name` and `customer-cloud` GSI; + - added `customer-id-index'` and `license_manager_id-index` GSI; + - made `id` field a composite key - `customer#L|S#name#version`; + - removed `name`, `licensed` and `version` fields; +- added new health-check - rules_meta_access_data; +- renamed lambda environment variables that contained `mcdm` with `modular`; +- removed user detailed report; +- added an ability to build rulesets by git project id and ref; +- added an ability to describe and delete rules by git project id and ref; +- refactored compliance report - removed excessive code; +- parse eventdriven maps dynamically; +- getting impact and article for recommendation from `CaaSRulesMeta`; + + +## [4.8.1] - 2023-08-09 +- fixed start date in ED report generation + +## [4.8.0] - 2023-08-04 +- replaced MCDM with Modular + +## [4.7.3] - 2023-08-02 +- added `CaaSJobStatistics` table; +- collect c-level `OVERVIEW` report using the weekly data that stored in `JobStatistics` table; +- fix duplicate recommendation bug; + +## [4.7.2] - 2023-08-01 +- fix `NEXT_STEP` value for `findings` step in metrics updater +- fix recommendations bucket name in environment_service +- add a parameter `is_zipped` to s3 service to save the file in an uncompressed format +- added endpoints to manage Defect Dojo applications: - `POST /applications/dojo` - `GET /applications/dojo` - `GET /applications/dojo/{application_id}` - `DELETE /applications/dojo/{application_id}` - `PATCH /applications/dojo/{application_id}` -# [4.7.1] - 2023-07-31 -* add bucket for recommendation -* implement new metrics step for EC2 insights calculation -* add new service to interact with s3 from another account -* remove `type` parameter from `POST /parents/tenant-link` command -* changed inner CaaSEvents model. Removed index and added random partition +## [4.7.1] - 2023-07-31 +- add bucket for recommendation +- implement new metrics step for EC2 insights calculation +- add new service to interact with s3 from another account +- remove `type` parameter from `POST /parents/tenant-link` command +- changed inner CaaSEvents model. Removed index and added random partition -# [4.7.0] - 2023-07-27 -* added an ability to create parent with type `CUSTODIAN_ACCESS` which is +## [4.7.0] - 2023-07-27 +- added an ability to create parent with type `CUSTODIAN_ACCESS` which is linked to credentials application. After that you can link this parent to tenant. In case tenant has `CUSTODIAN_ACCESS` in its parent_map, these credentials will be preferred. -* added an ability to specify `rules_to_scan` for `POST /jobs`. Only +- added an ability to specify `rules_to_scan` for `POST /jobs`. Only specified rules will be scanned -# [4.6.7] - 2023-07-25 -* fixed bug with updating the last execution date of event-driven report -* fixed error related to the high request rate to the `BatchResults` table +## [4.6.7] - 2023-07-25 +- fixed bug with updating the last execution date of event-driven report +- fixed error related to the high request rate to the `BatchResults` table -# [4.6.6] - 2023-07-21 -* fixed bug in last scan date calculation for customer reports -* added support for GOOGLE Maestro events +## [4.6.6] - 2023-07-21 +- fixed bug in last scan date calculation for customer reports +- added support for GOOGLE Maestro events -# [4.6.5] - 2023-07-20 -* fixed some issues found by pylint +## [4.6.5] - 2023-07-20 +- fixed some issues found by pylint -# [4.6.4] - 2023-07-19 +## [4.6.4] - 2023-07-19 - add `service_type` parameter to License Manager POST job request -# [4.6.3] - 2023-07-19 +## [4.6.3] - 2023-07-19 - fixed invalid data range and findings data in c-level report; - separated findings by data, then by project identifier; - added ability to run metrics from specific date; -# [4.6.2] - 2023-07-14 +## [4.6.2] - 2023-07-14 - change filter for maestro AZURE events so that the filter can retrieve cloud from eventMetadata.request.cloud; -# [4.6.1] - 2023-07-07 -* fix issue with duplicates in c-level reports -* fix issue with department-level reports: cannot parse empty cloud value +## [4.6.1] - 2023-07-07 +- fix issue with duplicates in c-level reports +- fix issue with department-level reports: cannot parse empty cloud value -# [4.6.0] - 2023-07-06 -* added `GET /metrics/status` API endpoint; -* added custom exception `MetricsUpdateException` to track metrics updating jobs; -* added environment variable `component_name` to `caas-api-handler`; +## [4.6.0] - 2023-07-06 +- added `GET /metrics/status` API endpoint; +- added custom exception `MetricsUpdateException` to track metrics updating jobs; +- added environment variable `component_name` to `caas-api-handler`; -# [4.5.12] - 2023-07-05 -* fixed a bug when metric difference values were saved recursively +## [4.5.12] - 2023-07-05 +- fixed a bug when metric difference values were saved recursively -# [4.5.11] - 2023-07-03 -* fixed a bug when event-driven jobs were not included to metrics +## [4.5.11] - 2023-07-03 +- fixed a bug when event-driven jobs were not included to metrics -# [4.5.10] - 2023-06-30 -* changed `project_id` to `account_number` for gcp tenants; -* fixed a bug that was adding resources to a tenant with zero scan per period +## [4.5.10] - 2023-06-30 +- changed `project_id` to `account_number` for gcp tenants; +- fixed a bug that was adding resources to a tenant with zero scan per period -# [4.5.9] - 2023-06-29 -* added ability to reset overview data for tenant that has not been scanned in a +## [4.5.9] - 2023-06-29 +- added ability to reset overview data for tenant that has not been scanned in a specific period -* added ability to check available report by type and automatically create the missing one - -# [4.5.8] - 2023-06-26 -* fixed import to dojo -* fixed resource duplication in MITRE reports; -* fixed calculation of the total amount of resources in OVERVIEW report; -* fixed pagination in `CaaSTenantMetrics` and `CaaSCustomerMetrics` tables; - -# [4.5.7] - 2023-06-22 -* removed redundant fields in compliance, resources and event-driven reports; - -# [4.5.6] - 2023-06-20 -* fixed bug in pagination of `CaaSJobs` table; -* fixed bug in top 10 tenants by resources and compliance calculation; - -# [4.5.5] - 2023-06-19 -* fixed last sending date of event-driven reports; -* add more logs to s3 client - -# [4.5.4] - 2023-06-14 (hotfix) -* fixed bugs related to invalid parsing of the scans submit date; -* fixed lambda ARNs in `deployment_resources.json`; -* added items limit on scanning `BatchResults` table; -* fixed: get DefectDojo secret from mcdm_assume_arn account; -* fixed internal server error in case dojo config is not set; -* add a list of endpoints which require customer to work to restriction service; -* removed deletion of standard points from account findings at the end of an event-driven scan - -# [4.5.3] - 2023-06-09 -* changed rule_report collection logic to aggregate manual job statistics only - -# [4.5.2] - 2023-06-08 -* fixed typo in lambdas naming in `lambda_client` service - -# [4.5.1] - 2023-06-07 -* fixed calculation of missed accounts in the `caas-metrics-updater` lambda - -# [4.5.0] - 2023-06-06 -* moved siem configurations to applications -* added endpoints: +- added ability to check available report by type and automatically create the missing one + +## [4.5.8] - 2023-06-26 +- fixed import to dojo +- fixed resource duplication in MITRE reports; +- fixed calculation of the total amount of resources in OVERVIEW report; +- fixed pagination in `CaaSTenantMetrics` and `CaaSCustomerMetrics` tables; + +## [4.5.7] - 2023-06-22 +- removed redundant fields in compliance, resources and event-driven reports; + +## [4.5.6] - 2023-06-20 +- fixed bug in pagination of `CaaSJobs` table; +- fixed bug in top 10 tenants by resources and compliance calculation; + +## [4.5.5] - 2023-06-19 +- fixed last sending date of event-driven reports; +- add more logs to s3 client + +## [4.5.4] - 2023-06-14 (hotfix) +- fixed bugs related to invalid parsing of the scans submit date; +- fixed lambda ARNs in `deployment_resources.json`; +- added items limit on scanning `BatchResults` table; +- fixed: get DefectDojo secret from mcdm_assume_arn account; +- fixed internal server error in case dojo config is not set; +- add a list of endpoints which require customer to work to restriction service; +- removed deletion of standard points from account findings at the end of an event-driven scan + +## [4.5.3] - 2023-06-09 +- changed rule_report collection logic to aggregate manual job statistics only + +## [4.5.2] - 2023-06-08 +- fixed typo in lambdas naming in `lambda_client` service + +## [4.5.1] - 2023-06-07 +- fixed calculation of missed accounts in the `caas-metrics-updater` lambda + +## [4.5.0] - 2023-06-06 +- moved siem configurations to applications +- added endpoints: - `/reports/push/dojo/{job_id}`; - `/reports/push/security-hub/{job_id}`; - `/reports/push/dojo`; - `/reports/push/security-hub`; -# [4.4.3] - 2023-06-01 -* fix invalid new week calculation in metric-updater -* fix collecting event-driven metrics -* add new requirement `python-dateutil` to the `caas-metrics-updater` lambda +## [4.4.3] - 2023-06-01 +- fix invalid new week calculation in metric-updater +- fix collecting event-driven metrics +- add new requirement `python-dateutil` to the `caas-metrics-updater` lambda -# [4.4.2] - 2023-05-31 -* fix invalid import in metrics updater +## [4.4.2] - 2023-05-31 +- fix invalid import in metrics updater -# [4.4.1] - 2023-05-26 -* implemented event-driven reports that are sent once for the period described in the license -* split application with type `CUSTODIAN` into two different applications: +## [4.4.1] - 2023-05-26 +- implemented event-driven reports that are sent once for the period described in the license +- split application with type `CUSTODIAN` into two different applications: `CUSTODIAN` and `CUSTODIAN_LICENSES`. The first one contains information about access to custodian, the second one - licenses. -* added endpoints: +- added endpoints: - POST /applications/access - GET /applications/access - PATCH /applications/access/{application_id} - GET /applications/access/{application_id} - DELETE /applications/access/{application_id} -* update `ACCESS_DATA_LM` format +- update `ACCESS_DATA_LM` format -# [4.4.0] - 2023-05-24 -* made metrics collection daily. Each day at 1:00am metrics are collected and the difference between +## [4.4.0] - 2023-05-24 +- made metrics collection daily. Each day at 1:00am metrics are collected and the difference between the current data and the last week's data is calculated; -* remove `permissions_admin` parameter from `POST /policies`; -* updated jwt auth for on-prem, token is not encrypted now. +- remove `permissions_admin` parameter from `POST /policies`; +- updated jwt auth for on-prem, token is not encrypted now. Only PyJWT is used, jwtcrypto is removed. -* added endpoint to trigger metrics update: +- added endpoint to trigger metrics update: - POST /metrics -# [4.3.0] - 2023-05-22 -* add endpoints to manage rabbitMQ configuration for customers: +## [4.3.0] - 2023-05-22 +- add endpoints to manage rabbitMQ configuration for customers: - POST /customers/rabbitmq - GET /customers/rabbitmq - DELETE /customers/rabbitmq -* move rabbitmq configuration to Applications with type `RABBITMQ` and use them; -* fix default value for POST /role `expiration` parameter. Not it's today + 2 months; - -# [4.2.1] - 2023-05-19 -* redone the existing difference calculation function using dataclasses - -# [4.2.0] - 2023-05-15 -* changed `RuleSource` primary key from [`id`:`hash-key`/`customer`:`range-key`] to a unique [`id`:`hash-key`] - * updated `/rule-sources` endpoint `*` handlers - * updated `/ruleset` endpoint POST handler -* added `rule_source_id` attribute & GSI to `Rule` entity: - * updated `/rules` endpoint `GET`, `DELETE` handlers - * updated `caas-rule-meta-updater` accordingly -* add an ability to create multiple applications with type `CUSTODIAN` +- move rabbitmq configuration to Applications with type `RABBITMQ` and use them; +- fix default value for POST /role `expiration` parameter. Not it's today + 2 months; + +## [4.2.1] - 2023-05-19 +**Api:** +- redone the existing difference calculation function using dataclasses + +**Executor:** +- Implement License Manager auth token storage/rotation per customer + + +## [4.2.0] - 2023-05-15 +**Api:** +- changed `RuleSource` primary key from [`id`:`hash-key`/`customer`:`range-key`] to a unique [`id`:`hash-key`] + - updated `/rule-sources` endpoint `*` handlers + - updated `/ruleset` endpoint POST handler +- added `rule_source_id` attribute & GSI to `Rule` entity: + - updated `/rules` endpoint `GET`, `DELETE` handlers + - updated `caas-rule-meta-updater` accordingly +- add an ability to create multiple applications with type `CUSTODIAN` within a customer. -# [4.1.9] - 2023-05-15 -* fixed bug of department report generation -* fixed lambda ARN creation in `caas-metrics-updater` +**Executor:** +- added aws xray profiling. Some jobs are sampled and the statistics will be + pushed to statistics bucket; + +## [4.1.9] - 2023-05-15 +- fixed bug of department report generation +- fixed lambda ARN creation in `caas-metrics-updater` -# [4.1.8] - 2023-05-12 -* added error handling to reports if rules have incorrect MITRE format; -* implement ED notification json-model +## [4.1.8] - 2023-05-12 +- added error handling to reports if rules have incorrect MITRE format; +- implement ED notification json-model -# [4.1.7] - 2023-05-10 -* adjusted to MCDM SDK 2.0.0 +## [4.1.7] - 2023-05-10 +- adjusted to MCDM SDK 2.0.0 -# [4.1.6] - 2023-05-05 -* put gitlab sdk to layer requirements to prevent urllib3==2.0.x from being +## [4.1.6] - 2023-05-05 +- put gitlab sdk to layer requirements to prevent urllib3==2.0.x from being installed, because it fails; -* rewrite link * unlink parent according to new MCDM api; +- rewrite link * unlink parent according to new MCDM api; -# [4.1.5] - 2023-05-03 (hotfix) -* fix a bug with `GET /scheduled-job`; -* updated `buckets-exist` health-check; -* fixed datetime of current metrics collection; -* add new available instance types to batch comp-env +## [4.1.5] - 2023-05-03 (hotfix) +- fix a bug with `GET /scheduled-job`; +- updated `buckets-exist` health-check; +- fixed datetime of current metrics collection; +- add new available instance types to batch comp-env -# [4.1.4] - 2023-05-02 -* fix minor bug with standard google jobs; +## [4.1.4] - 2023-05-02 +- fix minor bug with standard google jobs; -# [4.1.3] - 2023-05-01 -* update event-driven template and add filters to repo; +## [4.1.3] - 2023-05-01 +- update event-driven template and add filters to repo; -# [4.1.2] - 2023-04-26 -* changed type of `tenant_names` and `tenant_display_names` parameters in `/report/operational` and +## [4.1.2] - 2023-04-26 +- changed type of `tenant_names` and `tenant_display_names` parameters in `/report/operational` and `/report/project` endpoints from array to string; -* fixed bug when sending a report without specifying a tenant name - -# [4.1.1] - 2023-04-25 -* added `from` date calculation to reports if there is no previous date in settings; -* added the ability to send multiple reports at once via RabbitMQ; -* added `key_id` parameter for the DELETE `/settings/license-manager/client` action - -# [4.1.0] - 2023-04-20 -* `csv` replaced with `xlsx` for `/reports/rules/...` endpoints; -* updated validation for the `/tenants/license-priorities` actions; -* concealed `/tenants/license-priorities` resource; - -# [4.0.0] - 2023-02-03 - 2023-03-27 -* removed CaaSAccounts and all the bound code. Tenants are fully used +- fixed bug when sending a report without specifying a tenant name + +## [4.1.1] - 2023-04-25 +- added `from` date calculation to reports if there is no previous date in settings; +- added the ability to send multiple reports at once via RabbitMQ; +- added `key_id` parameter for the DELETE `/settings/license-manager/client` action + +## [4.1.0] - 2023-04-20 +**Api:** +- `csv` replaced with `xlsx` for `/reports/rules/...` endpoints; +- updated validation for the `/tenants/license-priorities` actions; +- concealed `/tenants/license-priorities` resource; + +**Executor:** +- removed `report_fields`, `severity` and `standard_points` from findings +- get impact, article, remediation for dojo reports from meta + +## [4.0.0] - 2023-02-03 - 2023-03-27 +**Api:** +- removed CaaSAccounts and all the bound code. Tenants are fully used instead of accounts. (this includes a lot of changes inside, but outside everything is more-of-less the same); -* all the endpoints that required `account_name` and|or `tenant_name` now just +- all the endpoints that required `account_name` and|or `tenant_name` now just require `tenant_name`. Tenants contain almost all the information accounts used to have. Custodian-specific information that does not have its place in Maestro Tenant model we put to TenantSettings; -* removed `syndicate_configs` for on-prem. Dotenv is used instead. Added +- removed `syndicate_configs` for on-prem. Dotenv is used instead. Added entry script `main.py` with such actions `init_vault`, `create_buckets`, `create_indexes`, `run`; -* moved all the scripts to `main.py`; -* implemented mechanism that collects scans data and group them for reports: - * added `caas-metrics-updater` and `caas-report-generation-handler` lambdas; - * added `CaaSCustomerMetrics` and `CaaSTenantMetrics` tables; - * added `rabbitmq`, `tenant_metrics` and `customer_metrics` services; - * added new settings `MAESTRO_RABBITMQ_CONFIG` and `REPORT_DATE_MARKER`; - * removed `/reports/event-driven` endpoint; -* added `attack_vector` field to the scan detailed report; -* refactored access_control_service, added new cache, and an ability to set +- moved all the scripts to `main.py`; +- implemented mechanism that collects scans data and group them for reports: + - added `caas-metrics-updater` and `caas-report-generation-handler` lambdas; + - added `CaaSCustomerMetrics` and `CaaSTenantMetrics` tables; + - added `rabbitmq`, `tenant_metrics` and `customer_metrics` services; + - added new settings `MAESTRO_RABBITMQ_CONFIG` and `REPORT_DATE_MARKER`; + - removed `/reports/event-driven` endpoint; +- added `attack_vector` field to the scan detailed report; +- refactored access_control_service, added new cache, and an ability to set a wildcard as part of permission to allow all the groups and subgroups (only SYSTEM can set currently); -* added endpoints to manage Custodian application for Maestro Customer: - * `POST /application` - * `GET /application` - * `DELETE /application` - * `PATCH /application` -* added endpoints to manage Custodian parents for Maestro Customer: - * `POST /parents` - * `GET /parents` - * `GET /parents/{parent_id}` - * `DELETE /parents/{parent_id}` - * `PATCH /parents/{parent_id}` - * `POST /parents/tenant-link` - * `DELETE /parents/tenant-link` -* Removed `POST /license`, refactored `GET /license`, `DELETE /license` -* Refactored `POST /jobs` so that it could submit only licensed jobs and +- added endpoints to manage Custodian application for Maestro Customer: + - `POST /application` + - `GET /application` + - `DELETE /application` + - `PATCH /application` +- added endpoints to manage Custodian parents for Maestro Customer: + - `POST /parents` + - `GET /parents` + - `GET /parents/{parent_id}` + - `DELETE /parents/{parent_id}` + - `PATCH /parents/{parent_id}` + - `POST /parents/tenant-link` + - `DELETE /parents/tenant-link` +- Removed `POST /license`, refactored `GET /license`, `DELETE /license` +- Refactored `POST /jobs` so that it could submit only licensed jobs and could resolve rulesets automatically; -* added new API endpoints: - * `GET /reports/department`; - * `GET /reports/operational`; - * `GET /reports/project`; - * `GET /reports/clevel`; -* added new CLI commands: - * `c7n report department`; - * `c7n report operational`; - * `c7n report project`; - * `c7n report clevel`; -* updated deployment guide; - -# [3.4.0] - 2023-01-30 - 2023-02-03 +- added new API endpoints: + - `GET /reports/department`; + - `GET /reports/operational`; + - `GET /reports/project`; + - `GET /reports/clevel`; +- added new CLI commands: + - `c7n report department`; + - `c7n report operational`; + - `c7n report project`; + - `c7n report clevel`; +- updated deployment guide; + +**Executor:** +- removed CaaSAccounts and all the bound code. + Now Tenants are used instead of accounts. + +## [3.4.0] - 2023-01-30 - 2023-02-03 - fix a bug with push to siem - add an ability to describe tenants by project id - fix a bug with not found rule-source branch; @@ -482,18 +703,23 @@ specific period - process events only for tenants which has event-driven enabled by license; Process only rules allowed by license -# [3.3.2] - 2023-01-28 - 2023-01-30 +## [3.3.2] - 2023-01-28 - 2023-01-30 - add env `event_consider_tenant_region_activity` to be able to disable tenant region activity validation; - Add some filters to caas-api-handler concerning EventBridge events; - Amend registration timestamping; - Adapt to Maestro's `active-region` states of Tenants. -# [3.3.1] - 2022-12-23 - 2023-01-20 +## [3.3.1] - 2022-12-23 - 2023-01-20 +**Api:** - add new lambda to process Event-Driven events from SQS: caas-sqs-events-processor; -# [3.3.0] 2022-12-23 - 2023-01-20 +**Executor:** +- add a missing attribute to BatchResults; + +## [3.3.0] 2022-12-23 - 2023-01-20 +**Api:** - Add siem and license configuration to configuration backupper and configuration updater; - Refactor CredentialsManager: swap its hash_key and range_key; - new event-driven 2.0: collect and save events to CaaSEvents and then @@ -501,19 +727,42 @@ specific period - Add CaaSEventStatistics; - Rewrite reports API drastically; -# [3.2.3] 2022-12-20 - 2022-12-22 (hotfix) +**Executor:** +- split event-driven and standard job flows; +- integrate new event-driven and BatchResults; + +## [3.2.3] 2022-12-20 - 2022-12-22 (hotfix) +**Api:** - implement customer-tenant restriction for actions related to: - scheduled-job management - credentials manager configuration -# [3.2.2] 2022-12-14 - 2022-12-16 +**Executor:** +- add to statistics the policies that were skipped due to the time threshold +- fix job lifetime threshold (use job's `startedAt` instead of `createdAt`) + +## [3.2.2] 2022-12-14 - 2022-12-16 +**Api:** - remove LM_ACCESS_DATA_HOST, MAIL_CONFIGURATION, SYSTEM_CUSTOMER env variables from api-handler -# [3.2.1] 2022-12-09 - 2022-12-13 +**Executor:** +- update notification content; +- replace settings passing through environment variables with settings service + +## [3.2.1] 2022-12-09 - 2022-12-13 +**Api:** - optimize `caas-job-updater` a bit: send request to LM only if the job is licensed. -# [3.2.0] 2022-11-28 - 2022-12-09 +**Executor:** +- make some optimizations: + - upload all the report files to S3 bucket concurrently; + - use `libyaml` from C in order to speed up Yaml files loading and dumping; + - send requests to LM in job_updater_service only if the job is licensed; + - increase the number of retry attempts in boto3 S3 config; + +## [3.2.0] 2022-11-28 - 2022-12-09 +**Api:** - remove PynamoDBToMongoDBAdapter and BaseModel and use the ones from MCDM SDK; - changed response code from `200` to `202` on the following endpoints: - `/job POST`; @@ -559,14 +808,21 @@ specific period - remove git source access data validation in case a user wants to update only source's permissions; +**Executor:** +- remove PynamoDBToMongoDBAdapter and BaseModel and use the ones from MCDM SDK +- add one more report `user_detailed_report.json`. It's the same + as `detailed_report.json` but does not contain standards +- `TARGET_RULESET` env now must contain rulesets' ids instead of names. -# [3.1.0] 2022-11-21 - 2022-11-25 (hotfix) + +## [3.1.0] 2022-11-21 - 2022-11-25 (hotfix) - fix bug with `attribute_values` with pynamodb DynamicMapAttribute; - correct license sync action to sync only for allowed by LM client types; - add tenant-scope permission for rulesets/rule_sources and rules; - add tenant-scope permission to `get_account_by_cloud_id`; -# [3.0.0] 2022-09-14 - 2022-11-02 +## [3.0.0] 2022-09-14 - 2022-11-02 +**Api:** - integrate Maestro common domain model (MCDM) and its SDK; - adjusted compliance_report action in `caas-report-generator` to the new parsed coverage's data; calculate compliance the right way based on points; @@ -588,15 +844,15 @@ specific period - added an ability to send an email with scan results of a specific account in case new violating resources are found; - added an ability to register scheduled jobs both on Premises and on AWS: - * `GET` `/{stage}/scheduled-job/`; - * `POST` `/{stage}/scheduled-job/`; - * `DELETE` `/{stage}/scheduled-job/`; - * `UPDATE` `/{stage}/scheduled-job/`; + - `GET` `/{stage}/scheduled-job/`; + - `POST` `/{stage}/scheduled-job/`; + - `DELETE` `/{stage}/scheduled-job/`; + - `UPDATE` `/{stage}/scheduled-job/`; - added permissions restriction by user tenants; - added new optional custom attribute `tenants` for cognito user and an ability to assign/unassign tenants via API: - * `GET` `/{stage}/users/tenants/`; - * `POST` `/{stage}/users/tenants/`; - * `DELETE` `/{stage}/users/tenants/`; + - `GET` `/{stage}/users/tenants/`; + - `POST` `/{stage}/users/tenants/`; + - `DELETE` `/{stage}/users/tenants/`; - migrated all the inner date to UTC; - add license priorities for rulesets - added token management service and token-based authentication for SAAS to @@ -604,6 +860,15 @@ specific period - added an ability to remove your user: `DELETE /users` and an ability to reset you password `POST /users/password-reset` +**Executor:** +- integrate Maestro common domain model (MCDM) and its SDK; +- Refactored to allow to execute the docker without command but only with envs. +- Refactored the main flow and split to separate classes. Added an ability to + execute policies in ThreadPoolExecutor. Set `EXECUTOR_MODE` env to + either `consistent` or `concurrent` +- If job item does not exist in DB after the executor has started, + it will be created; + ## [2.2.1] - 2022-09-08 - remove `root_policy.json`, `root_role.json`, `iam_permissions.json`; @@ -644,6 +909,7 @@ specific period ## [2.0.0] - 2022-06-24 +**Api** - Added integration with Custodian License Manager. - Behaviour of objects that holds configuration (Customers, Tenants, Accounts). Rulesets and rule sources now stored in a separate tables: @@ -662,6 +928,9 @@ specific period violated resources by setting `resource_per_finding` to `true` in SIEMManager config; By default each finding will still represent a separate rule with a bunch of resources; + +**Executor** +- Added integration with Custodian License Manager. ## [1.3.0] - 2022-06-20 - `/report/compliance/`, `/report/error/`, `/report/rule` endpoints can diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..8bee82f1a --- /dev/null +++ b/Makefile @@ -0,0 +1,132 @@ + +.DEFAULT_GOAL := test +.PHONY: check-syndicate test test-coverage install install-cli update-meta clean open-source-executor-image fork-executor-image aws-ecr-login aws-ecr-push syndicate-update-lambdas syndicate-update-api-gateway syndicate-update-meta + + +COVERAGE_TYPE := html +DOCKER_EXECUTABLE := podman +CLI_VENV_NAME := c7n_venv + +# assuming that python is more likely to be installed than jq +AWS_ACCOUNT_ID = $(shell aws sts get-caller-identity | python3 -c "import sys,json;print(json.load(sys.stdin)['Account'])") +AWS_REGION = $(shell aws configure get region) + +EXECUTOR_IMAGE_NAME := caas-custodian-service-dev +EXECUTOR_IMAGE_TAG := latest +SERVER_IMAGE_NAME := caas-custodian-k8s-dev +SERVER_IMAGE_TAG := latest + + +SYNDICATE_EXECUTABLE_PATH ?= $(shell which syndicate) +SYNDICATE_CONFIG_PATH ?= .syndicate-config-main +SYNDICATE_BUNDLE_NAME := custodian-service + + +check-syndicate: + @if [[ -z "$(SYNDICATE_EXECUTABLE_PATH)" ]]; then echo "No syndicate executable found"; exit 1; fi + @if [[ ! -d "$(SYNDICATE_CONFIG_PATH)" ]]; then echo "Syndicate config directory $(SYNDICATE_CONFIG_PATH) not found"; exit 1; fi + + +test: + pytest --verbose tests/ + + +test-coverage: + pytest --cov=src/ --cov-report=$(COVERAGE_TYPE) tests/ + + +install: + # install for local usage + @if [[ -z "$(VIRTUAL_ENV)" ]]; then echo "Creating python virtual env"; python -m venv venv; fi + venv/bin/pip install c7n + venv/bin/pip install c7n-azure + venv/bin/pip install c7n-gcp + venv/bin/pip install c7n-kube + venv/bin/pip install -r src/onprem/requirements.txt + venv/bin/pip install -r src/executor/requirements.txt + @echo "Execute:\nsource ./venv/bin/activate" + + +install-cli: + # installing CLI in editable mode + python -m venv $(CLI_VENV_NAME) + $(CLI_VENV_NAME)/bin/pip install -e ./c7n + @echo "Execute:\nsource ./$(CLI_VENV_NAME)/bin/activate" + + +update-meta: + # updating src/deployment_resources.json (may need to adjust manually after that) + python src/main.py update_api_models + # updating src/admin_policy.json + python src/main.py show_permissions | python -c "import sys,json;json.dump({'customer':'', 'name':'admin_policy','permissions': json.load(sys.stdin)},sys.stdout,indent=2)" > src/admin_policy.json + + +openapi-spec.json: src/validators/registry.py src/validators/swagger_request_models.py src/validators/swagger_response_models.py src/helpers/constants.py + python src/main.py generate_openapi > openapi-spec.json + + +clean: + -rm -rf .pytest_cache .coverage custodian_common_dependencies_layer.zip ./logs htmlcov openapi-spec.json + -if [[ -d "$(SYNDICATE_CONFIG_PATH)/logs" ]]; then rm -rf "$(SYNDICATE_CONFIG_PATH)/logs"; fi + -if [[ -d "$(SYNDICATE_CONFIG_PATH)/bundles" ]]; then rm -rf "$(SYNDICATE_CONFIG_PATH)/bundles"; fi + + +open-source-executor-image: + $(DOCKER_EXECUTABLE) build -t $(EXECUTOR_IMAGE_NAME):$(EXECUTOR_IMAGE_TAG) -f src/executor/Dockerfile-opensource . + + +fork-executor-image: + $(DOCKER_EXECUTABLE) build -t $(EXECUTOR_IMAGE_NAME):$(EXECUTOR_IMAGE_TAG) -f src/executor/Dockerfile . + # $(DOCKER_EXECUTABLE) build -t $(EXECUTOR_IMAGE_NAME):$(EXECUTOR_IMAGE_TAG) -f src/executor/Dockerfile --build-arg CUSTODIAN_SERVICE_PATH=custodian-as-a-service --build-arg CLOUD_CUSTODIAN_PATH=custodian-custom-core .. + + +open-source-server-image: + $(DOCKER_EXECUTABLE) build -t $(SERVER_IMAGE_NAME):$(SERVER_IMAGE_TAG) -f src/onprem/Dockerfile-opensource . + + +open-source-server-image-to-minikube: + eval $(minikube -p minikube docker-env) && \ + $(DOCKER_EXECUTABLE) build -t $(SERVER_IMAGE_NAME):$(SERVER_IMAGE_TAG) -f src/onprem/Dockerfile-opensource . + +cli-dist: + python -m build --sdist c7n/ + +obfuscation-manager-dist: + python -m build --sdist obfuscation_manager/ + +aws-ecr-login: + @if ! aws --version; then echo "Error: install awscli"; exit 1; fi + aws ecr get-login-password --region $(AWS_REGION) | $(DOCKER_EXECUTABLE) login --username AWS --password-stdin $(AWS_ACCOUNT_ID).dkr.ecr.$(AWS_REGION).amazonaws.com + + +aws-ecr-push-executor: + export AWS_REGION=$(AWS_REGION) AWS_ACCOUNT_ID=$(AWS_ACCOUNT_ID); \ + $(DOCKER_EXECUTABLE) tag $(EXECUTOR_IMAGE_NAME):$(EXECUTOR_IMAGE_TAG) $$AWS_ACCOUNT_ID.dkr.ecr.$$AWS_REGION.amazonaws.com/$(EXECUTOR_IMAGE_NAME):$(EXECUTOR_IMAGE_TAG); \ + $(DOCKER_EXECUTABLE) push $$AWS_ACCOUNT_ID.dkr.ecr.$$AWS_REGION.amazonaws.com/$(EXECUTOR_IMAGE_NAME):$(EXECUTOR_IMAGE_TAG) + + +aws-ecr-push-server: + export AWS_REGION=$(AWS_REGION) AWS_ACCOUNT_ID=$(AWS_ACCOUNT_ID); \ + $(DOCKER_EXECUTABLE) tag $(SERVER_IMAGE_NAME):$(SERVER_IMAGE_TAG) $$AWS_ACCOUNT_ID.dkr.ecr.$$AWS_REGION.amazonaws.com/$(SERVER_IMAGE_NAME):$(SERVER_IMAGE_TAG); \ + $(DOCKER_EXECUTABLE) push $$AWS_ACCOUNT_ID.dkr.ecr.$$AWS_REGION.amazonaws.com/$(SERVER_IMAGE_NAME):$(SERVER_IMAGE_TAG) + + +syndicate-update-lambdas: check-syndicate + SDCT_CONF=$(SYNDICATE_CONFIG_PATH) $(SYNDICATE_EXECUTABLE_PATH) build --errors_allowed --bundle_name $(SYNDICATE_BUNDLE_NAME) -F + SDCT_CONF=$(SYNDICATE_CONFIG_PATH) $(SYNDICATE_EXECUTABLE_PATH) update --update_only_types lambda --update_only_types lambda_layer --bundle_name $(SYNDICATE_BUNDLE_NAME) --replace_output + + +syndicate-update-meta: check-syndicate + -rm .$(SYNDICATE_CONFIG_PATH)/bundles/$(SYNDICATE_BUNDLE_NAME)/build_meta.json + SDCT_CONF=$(SYNDICATE_CONFIG_PATH) $(SYNDICATE_EXECUTABLE_PATH) package_meta -b $(SYNDICATE_BUNDLE_NAME) + SDCT_CONF=$(SYNDICATE_CONFIG_PATH) $(SYNDICATE_EXECUTABLE_PATH) upload -b $(SYNDICATE_BUNDLE_NAME) -F + + +syndicate-update-api-gateway: check-syndicate + # it does not remove the old api gateway + SDCT_CONF=$(SYNDICATE_CONFIG_PATH) $(SYNDICATE_EXECUTABLE_PATH) deploy --deploy_only_types api_gateway --replace_output --bundle_name $(SYNDICATE_BUNDLE_NAME) + + +syndicate-update-step-functions: check-syndicate + # it does not remove the old api gateway + SDCT_CONF=$(SYNDICATE_CONFIG_PATH) $(SYNDICATE_EXECUTABLE_PATH) deploy --deploy_only_types step_functions --replace_output --bundle_name $(SYNDICATE_BUNDLE_NAME) diff --git a/README.md b/README.md index 732dd30e8..494d86dd4 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ By default, the solution covers hundreds of security, compliance, utilization an All the technical details described below are actual for the particular version, or a range of versions of the software. -### Actual for versions: 3.0.0 +### Actual for versions: 5.0.0 ## Lambdas description @@ -16,13 +16,10 @@ or a range of versions of the software. This lambda is designed as a handler for all API resources: -* `/job POST` - initiates the custodian scan for the requested account; -* `/job GET` - returns job details for the requested query with the paths to +* `/jobs POST` - initiates the custodian scan for the requested account; +* `/jobs GET` - returns job details for the requested query with the paths to result reports (if any); -* `/job DELETE` - terminates the custodian scan; -* `/report GET` - returns scan details with the result reports contents. - With `?detailed=true` - returns scan details with the detailed result reports contents; +* `/jobs DELETE` - terminates the custodian scan; * `/signin POST` - returns access and refresh tokens for specific user. This user must be in Cognito user pool (first go through the signup resource); @@ -32,14 +29,6 @@ This lambda is designed as a handler for all API resources: which will be executed according to the given cron; * `/event POST` - resource for starting job in event-driven; -Possible parameters for GET requests: - -* `account (str)` - requested account display name; -* `job_id (str)` - requested AWS Batch job id; -* `latest (bool)` - request the latest succeeded job details/report. - -Note that either `job_id` or `account` must be provided for GET request. - Refer to [custodian-api-handler](src/lambdas/custodian_api_handler/README.md) for more details. @@ -75,61 +64,6 @@ for more details. --- -### Lambda `custodian-configuration-backupper` - -Back up current service configuration, push configuration data to: - -1. Backup repo: - - Accounts - - Settings - - Rules meta - - Ruleset files - -2. Backup s3 bucket: - - encrypted credentials - -Refer -to [custodian-configuration-backupper](src/lambdas/custodian_configuration_backupper/README.md) -for more details. - ---- - -### Lambda `custodian-configuration-updater` - -Synchronize configurations according to state stored in git repo Push -configuration data to: - -1. Dynamodb: - - Accounts - - Rules - - Settings - -2. S3: - - rulesets - -3. Secrets: - - SSM (Parameter Store) - -Refer -to [custodian-configuration-updater](src/lambdas/custodian_configuration_updater/README.md) -for more details. - ---- - -### Lambda `custodian-ruleset-compiler` - -This lambda is designed to assemble all the rules required for the specific scan -and put resulted `.yml` files into s3 bucket. - -While working, lambda will set/change Customer/Tenant/Account rulesets -configuration `s3_path` and `status` attributes. - -Refer -to [custodian-ruleset-compiler](src/lambdas/custodian_ruleset_compiler/README.md) -for more details. - ---- - ### Lambda `custodian-report-generator` This lambda generates statistics reports based on a Batch jobs result. @@ -149,43 +83,6 @@ Refer to [custodian-configuration-api-handler](src/lambdas/custodian_configuration_api_handler/README.md) for more details. -## Statistics collecting -Rules statistics are stored in the S3 bucket. - -#### Env Variables: -- `STATS_S3_BUCKET_NAME` - name of s3 bucket to store statistic files. DEV - bucket: `caas-statistics-dev2`. - -#### Statistics format: -```text -[ - { - "id": "rule_id", - "region": "region", - "started_at": "ISO_time", - "finished_at": "ISO_time", - "status": "SUCCEEDED|SKIPPED|FAILED", - "resourced_scanned": resources_amount, - "elapsed_time": "time_in_seconds", - "failed_resources": [ - { - resource1_description - }, - { - resource2_description - }, - ... - { - resourceN_description - } - ] - "account_display_name": "account_name", - "tenant_display_name": "tenant_name", - "customer_display_name": "customer_name" - } -] -``` - ## Rules format Each rule file in the repository must be in the following format: @@ -217,22 +114,8 @@ All fields are required. ## Tests To run tests use the command below: ```bash -python -m unittest discover -s tests -v -``` -The unittests are in the folder custodian-as-a-service/tests. If you want to -see the coverage install `coverage` library: -```bash -pip install coverage -``` -Execute the following command to run tests with coverage: -```bash -coverage run -m unittest discover -s tests -v -``` -Execute to generate the HTML-report: -```bash -coverage html --omit "tests*" +pytest tests/ ``` -The generated files are in the `htmlcov` directory that appeared in your working dir. Use the `index.html` from within. ## Event-Driven scans diff --git a/c7n/CHANGELOG.md b/c7n/CHANGELOG.md index aad91a0be..1c4bb175d 100644 --- a/c7n/CHANGELOG.md +++ b/c7n/CHANGELOG.md @@ -4,7 +4,79 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -# [4.15.0] - 2023-10-16 +## [5.1.0] - 2024-04-17 +- added `--effect` and `--tenant` and `--description` to `c7n policy add|update` +- added `--description` to `c7n role add|update` +- added `--href` param to `c7n report resource job` + +## [5.0.2] - 2024-04-02 +- add `--customer_id` hidden param to `c7n users ...` + +## [5.0.0] - 2024-02-19 +- get `trace_id` from headers instead of body +- refactor, minor optimization +- adapt response formatters to new Server responses (`data`, `items`, `errors`) +- Removed commands: + - security hub + - backupper + - `c7n tenant update` + - `c7n application *` + - `c7n parent *` + - `c7n siem *` +- Added commands: + - `c7n customer set_excluded_rules` + - `c7n customer get_excluded_rules` + - `c7n tenant delete` + - `c7n tenant active_licenses` + - `c7n tenant set_excluded_rules` + - `c7n tenant get_excluded_rules` + - `c7n integrations dojo add` + - `c7n integrations dojo describe` + - `c7n integrations dojo delete` + - `c7n integrations dojo activate` + - `c7n integrations dojo deactivate` + - `c7n integrations dojo get_activation` + - `c7n integrations sre add` + - `c7n integrations sre describe` + - `c7n integrations sre delete` + - `c7n integrations sre update` + - `c7n license activate` + - `c7n license deactivate` + - `c7n license get_activation` + - `c7n license update_activation` + - `c7n tenant credentials *` + - `c7n whoami` + + +## [4.19.0] - 2023-12-22 +- added new command `c7n report status` + +## [4.18.0] - 2023-12-20 +* rename some parameters in the commands `c7n metrics status` and `c7n job describe`: + * `--from` to `--from_date` + * `--to` to `--to_date` +* added new command `c7n report status` + +## [4.17.1] - 2023-12-13 +* added parameters `--from` and `--to` for the command `c7n metrics status` + +## [4.17.0] - 2023-12-08 +* added new commands: + * `c7n setting report enable_sending` + * `c7n setting report disable_sending` + +## [4.16.1] - 2023-12-05 +* made `--tenant_name` parameter in the `c7n report operational` command multiple + +## [4.16.0] - 2023-11-27 +* added new report command: + * `c7n report diagnostic` +* removed some obsolete cli commands + +## [4.15.1] - 2023-11-16 +* added `KUBERNETES` report type to the `c7n report operational` command + +## [4.15.0] - 2023-10-16 - added new commands to manage k8s platforms: - `c7n platform k8s create_eks` - `c7n platform k8s create_native` @@ -13,55 +85,55 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `c7n platform k8s describe` -# [4.14.0] - 2023-10-04 +## [4.14.0] - 2023-10-04 * Update libraries to support Python 3.10 * tabulate from 0.8.9 to 0.9.0 * requests from 2.27.1 to 2.31.0 * Make python3.10 a min required version -# [4.13.2] - 2023-10-04 +## [4.13.2] - 2023-10-04 * added `FINOPS` report type to the commands: * `c7n report operational` * `c7n report project` -# [4.13.1] - 2023-09-28 +## [4.13.1] - 2023-09-28 * changed format of the `--report_types` parameter at the time of sending the request in commands: * `c7n report operational` * `c7n report project` * `c7n report department` * `c7n report clevel` -# [4.13.0] - 2023-09-28 +## [4.13.0] - 2023-09-28 * made the `--report_types` parameter multiple in commands: * `c7n report operational` * `c7n report project` * `c7n report department` * `c7n report clevel` -# [4.12.0] - 2023-09-05 +## [4.12.0] - 2023-09-05 - added `c7n report resource latest` command to allow to retrieve resource specific info - added `c7n report resource jobs` command - added `c7n report resource job` -# [4.11.0] - 2023-09-01 +## [4.11.0] - 2023-09-01 - added `c7n meta udpate_mappings`, `c7n meta update_standards`, `c7n meta update_meta` commands -# [4.10.1] - 2023-08-23 +## [4.10.1] - 2023-08-23 - rename `--tenants` to '--tenant' in `c7n user signup` command - update some descriptions -# [4.10.0] - 2023-08-23 +## [4.10.0] - 2023-08-23 - add `--api_version` param to `c7n setting lm config add` -# [4.9.3] - 2023-08-15 +## [4.9.3] - 2023-08-15 - fix typo in constant name (from `RULES` to `RULE`) in the `c7n report operational` command -# [4.9.2] - 2023-08-10 +## [4.9.2] - 2023-08-10 - added `--raw` flag to `c7n tenant findings describe` command -# [4.9.0] - 2023-08-10 +## [4.9.0] - 2023-08-10 - added parameter `--description` to the `c7n rulesource add`, `c7n rulesource update` commands - updated `c7n rulesource add` command: - made parameter `--git_access_secret` optional; @@ -82,39 +154,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - removed `--all_rules`, `--rule_version` parameters; - made `--standard` parameters multiple; -# [4.7.2] - 2023-08-01 +## [4.7.2] - 2023-08-01 * added commands to manage Defect Dojo applications: - `c7n application dojo add` - `c7n application dojo delete` - `c7n application dojo update` - `c7n application dojo describe` -# [4.7.1] - 2023-07-28 +## [4.7.1] - 2023-07-28 * added `--results_storage` attribute to `c7n application access update`, `c7n application access add` commands * remove `--type` parameter from `c7n parent link_tenant` command -# [4.7.0] - 2023-07-27 +## [4.7.0] - 2023-07-27 * Add `CUSTODIAN_ACCESS` parent type to `c7n parent ...` commands * added `rules_to_scan` parameter to `c7n job submit`. The parameter can accept either string or JSON string or path to a file containing JSON -# [4.6.1] - 2023-07-11 +## [4.6.1] - 2023-07-11 * Integrate `modular-sdk` instead of the usage of `mcdm-sdk` -# [4.6.0] - 2023-07-04 +## [4.6.0] - 2023-07-04 * added command: * `c7n metrics status` * renamed old command: * `c7n trigger metrics_update` to `c7n metrics update` -# [4.5.2] - 2023-07-03 +## [4.5.2] - 2023-07-03 * made `--tenant_name` and `--tenant_display_name` case-insensitive; -# [4.5.1] - 2023-06-26 +## [4.5.1] - 2023-06-26 * fix a bug with m3-modular and custom name for click option; -# [4.5.0] - 2023-05-30 +## [4.5.0] - 2023-05-30 * added new parameter `report_type` to the following commands: * `c7n report operational`; * `c7n report project`; @@ -122,7 +194,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * `c7n report clevel`. * removed `c7n siem` group -# [4.4.1] - 2023-05-25 +## [4.4.1] - 2023-05-25 * hid some secured params from logs * added commands: - c7n application access add @@ -131,7 +203,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - c7n application access update * added `--protocol`, `--stage` attributes to `c7n setting lm config add` -# [4.4.0] - 2023-05-24 +## [4.4.0] - 2023-05-24 * added command to trigger metrics update:t - c7n trigger metrics_update * renamed license-related commands: @@ -142,13 +214,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 and `c7n setting mail delete` commands; * remove `--permissions_admin` from `c7n policy add` -# [4.3.0] - 2023-05-22 +## [4.3.0] - 2023-05-22 * added commands to manage rabbitMQ: - c7n customer rabbitmq add; - c7n customer rabbitmq describe - c7n customer rabbitmq delete; -# [4.2.0] - 2023-05-02 +## [4.2.0] - 2023-05-02 * integrate mcdm cli sdk * added [`-rsid`/`--rule_source_id`] parameter to the following commands: * `c7n rulesource` [`describe`|`update`|`delete`] @@ -159,11 +231,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * [`--git_ref`/`-gref`] * [`git_rules_prefix`/`-gprefix`] -# [4.1.4] - 2023-04-28 +## [4.1.4] - 2023-04-28 * removed default name for `--cloud_application_id` option in `c7n application update` and `c7n application add` commands -# [4.1.3] - 2023-04-27 +## [4.1.3] - 2023-04-27 * rename `c7n ruleset event_driven` command to `c7n ruleset eventdriven` * fix _issues_ found by QA team: * #52. Updated description of `c7n parent --help` command @@ -189,17 +261,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * #72. Updated description of `c7n user tenants` group * #73. Updated parameter description of `c7n user tenants describe` command -# [4.1.2] - 2023-04-26 +## [4.1.2] - 2023-04-26 * removed default argument name for `--customer_id` option * changed `tenant_name` to `tenant_names` parameter in `c7n report operational` command; * changed `tenant_display_name` to `tenant_display_names` parameter in `c7n report project` command; * made `--tenant_names` parameter required for `c7n report operational` command -# [4.1.1] - 2023-04-25 +## [4.1.1] - 2023-04-25 * added missing parameter `name` to the `show_config` command. Required for compatibility with `m3modular` -# [4.1.0] - 2023-04-20 +## [4.1.0] - 2023-04-20 * fix issues found by QA team: 1. Removed `c7n job submit aws`, `c7n job submit azure`, `c7n job submit google`. Now there is only one command `c7n job submit` for which you can optionally pass `--cloud` attr in case you need to resolve credentials from envs locally; 2. - @@ -253,11 +325,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 50. `--access_application_id` renamed to `--cloud_application_id` for `c7n application add|update` commands; 51. `--application_id` is added to commands `c7n application delete`, `c7n application update`; -# [4.0.1] - 2023-04-14 +## [4.0.1] - 2023-04-14 * fix `c7n results describe` for modular -# [4.0.0] - 2023-02-03 - 2023-03-07 +## [4.0.0] - 2023-02-03 - 2023-03-07 * `c7n account findings` moved to `c7n tenant findings`; * `c7n account credentials` moved to `c7n tenant credentials`; * removed `--account_name` from `c7n report ... accumulated`; @@ -278,11 +350,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 as M3 Module -# [3.4.0] - 2023-01-30 - 2023-02-03 +## [3.4.0] - 2023-01-30 - 2023-02-03 - add an ability to describe tenants by project id - add `cloud_identifier` param to `c7n tenant describe` -# [3.3.0] - 2023-01-16 +## [3.3.0] - 2023-01-16 - add `c7n job event` to simulate event-driven requests, only in developer mode. - add `c7n result describe` to describe BatchResults; - rewrite `c7n report`: add @@ -293,7 +365,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `rules jobs/accumulated`; -# [3.2.0] - from end of November till now +## [3.2.0] - from end of November till now - add `rule_source_id` to `c7n rule describe` - replace `c7n siem delete -type [dojo|security_hub]` with `c7n siem delete dojo|security_hub` @@ -303,10 +375,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - add `schedule` parameter to `c7n job update` - remove `--customer` attr from siem-related commands and add `--tenant_name` -# [3.1.0] (hotfix) +## [3.1.0] (hotfix) -# [3.0.0] 2022-09-14 - 2022-11-02 +## [3.0.0] 2022-09-14 - 2022-11-02 - integrate Maestro common domain model (MCDM) and its SDK; * Added Click's autocomplete; * Removed `--start_date` & `--end_date` from `c7n report compliance` and added @@ -358,7 +430,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Added `c7n user signup`; * Added `c7n user delete` -# [2.2.0] - 2022-08-30 +## [2.2.0] - 2022-08-30 * Added the `--verbose` flag to write detailed information to the log file; * Parse `trace_id` from each lambdas' response; * Added `--event_driven` attr to `c7n job describe` to be able to retrieve diff --git a/c7n/autocomplete/.c7n-complete.bash b/c7n/autocomplete/.c7n-complete.bash deleted file mode 100644 index 80b7e1cb7..000000000 --- a/c7n/autocomplete/.c7n-complete.bash +++ /dev/null @@ -1,29 +0,0 @@ -_c7n_completion() { - local IFS=$'\n' - local response - - response=$(env COMP_WORDS="${COMP_WORDS[*]}" COMP_CWORD=$COMP_CWORD _C7N_COMPLETE=bash_complete $1) - - for completion in $response; do - IFS=',' read type value <<< "$completion" - - if [[ $type == 'dir' ]]; then - COMREPLY=() - compopt -o dirnames - elif [[ $type == 'file' ]]; then - COMREPLY=() - compopt -o default - elif [[ $type == 'plain' ]]; then - COMPREPLY+=($value) - fi - done - - return 0 -} - -_c7n_completion_setup() { - complete -o nosort -F _c7n_completion c7n -} - -_c7n_completion_setup; - diff --git a/c7n/autocomplete/.c7n-complete.fish b/c7n/autocomplete/.c7n-complete.fish deleted file mode 100644 index 3b05a685b..000000000 --- a/c7n/autocomplete/.c7n-complete.fish +++ /dev/null @@ -1,22 +0,0 @@ -function _c7n_completion; - set -l response; - - for value in (env _C7N_COMPLETE=fish_complete COMP_WORDS=(commandline -cp) COMP_CWORD=(commandline -t) c7n); - set response $response $value; - end; - - for completion in $response; - set -l metadata (string split "," $completion); - - if test $metadata[1] = "dir"; - __fish_complete_directories $metadata[2]; - else if test $metadata[1] = "file"; - __fish_complete_path $metadata[2]; - else if test $metadata[1] = "plain"; - echo $metadata[2]; - end; - end; -end; - -complete --no-files --command c7n --arguments "(_c7n_completion)"; - diff --git a/c7n/autocomplete/.c7n-complete.zsh b/c7n/autocomplete/.c7n-complete.zsh deleted file mode 100644 index 8677157b3..000000000 --- a/c7n/autocomplete/.c7n-complete.zsh +++ /dev/null @@ -1,35 +0,0 @@ -#compdef c7n - -_c7n_completion() { - local -a completions - local -a completions_with_descriptions - local -a response - (( ! $+commands[c7n] )) && return 1 - - response=("${(@f)$(env COMP_WORDS="${words[*]}" COMP_CWORD=$((CURRENT-1)) _C7N_COMPLETE=zsh_complete c7n)}") - - for type key descr in ${response}; do - if [[ "$type" == "plain" ]]; then - if [[ "$descr" == "_" ]]; then - completions+=("$key") - else - completions_with_descriptions+=("$key":"$descr") - fi - elif [[ "$type" == "dir" ]]; then - _path_files -/ - elif [[ "$type" == "file" ]]; then - _path_files -f - fi - done - - if [ -n "$completions_with_descriptions" ]; then - _describe -V unsorted completions_with_descriptions -U - fi - - if [ -n "$completions" ]; then - compadd -U -V unsorted -a completions - fi -} - -compdef _c7n_completion c7n; - diff --git a/c7n/c7ncli/group/__init__.py b/c7n/c7ncli/group/__init__.py index 8c55d3045..3acba1656 100644 --- a/c7n/c7ncli/group/__init__.py +++ b/c7n/c7ncli/group/__init__.py @@ -1,40 +1,58 @@ -import json -import os +from abc import ABC, abstractmethod from datetime import timezone -from functools import wraps, reduce, cached_property +from functools import reduce, wraps +from http import HTTPStatus +import operator from itertools import islice +import json +import os from pathlib import Path -from typing import Union, Dict, Optional, Iterable, List, Callable, TypedDict +from typing import Any, Callable, TypedDict, cast +import urllib.error import click -import requests.exceptions from dateutil.parser import isoparse -from requests.models import Response from tabulate import tabulate -from c7ncli.service.adapter_client import AdapterClient -from c7ncli.service.config import CustodianCLIConfig, AbstractCustodianConfig, \ - CustodianWithCliSDKConfig -from c7ncli.service.constants import MESSAGE_ATTR, TRACE_ID_ATTR, ITEMS_ATTR, \ - NEXT_TOKEN_ATTR, RESPONSE_NO_CONTENT, NO_CONTENT_RESPONSE_MESSAGE, \ - MALFORMED_RESPONSE_MESSAGE, NO_ITEMS_TO_DISPLAY_RESPONSE_MESSAGE, \ - CONTEXT_API_CLIENT, CONTEXT_CONFIG, \ - AVAILABLE_JOB_TYPES, MODULE_NAME, CONTEXT_MODULAR_ADMIN_USERNAME -from c7ncli.service.logger import get_logger, get_user_logger, \ - write_verbose_logs +from c7ncli.service.adapter_client import CustodianApiClient, CustodianResponse +from c7ncli.service.config import ( + AbstractCustodianConfig, + CustodianCLIConfig, + CustodianWithCliSDKConfig, +) +from c7ncli.service.constants import ( + CONTEXT_MODULAR_ADMIN_USERNAME, + DATA_ATTR, + ITEMS_ATTR, + ERRORS_ATTR, + MESSAGE_ATTR, + MODULE_NAME, + NEXT_TOKEN_ATTR, + NO_CONTENT_RESPONSE_MESSAGE, + NO_ITEMS_TO_DISPLAY_RESPONSE_MESSAGE, + JobType +) +from c7ncli.service.logger import get_logger, get_user_logger, write_verbose_logs + +CredentialsProvider = None +try: + from modular_cli_sdk.services.credentials_manager import \ + CredentialsProvider +except ImportError: + pass + # modular cli MODULAR_ADMIN = 'modules' SUCCESS_STATUS = 'SUCCESS' -FAILED_STATUS = 'FAILED' +ERROR_STATUS = 'FAILED' STATUS_ATTR = 'status' CODE_ATTR = 'code' TABLE_TITLE_ATTR = 'table_title' # ----------- -SYSTEM_LOG = get_logger(__name__) +_LOG = get_logger(__name__) USER_LOG = get_user_logger(__name__) -ApiResponseType = Union[Response, Dict] REVERT_TO_JSON_MESSAGE = 'The command`s response is pretty huge and the ' \ 'result table structure can be broken.\nDo you want ' \ @@ -66,22 +84,22 @@ class ContextObj(TypedDict): as keys in TypedDict class ContextObj(TypedDict): CONTEXT_CONFIG: CustodianCLIConfig - CONTEXT_API_CLIENT: AdapterClient + CONTEXT_API_CLIENT: CustodianApiClient - that does not work """ config: AbstractCustodianConfig - api_client: AdapterClient + api_client: CustodianApiClient + +class cli_response: # noqa + __slots__ = ('_attributes_order', '_check_api_link', '_check_access_token') -class cli_response: - def __init__(self, attributes_order: Optional[List[str]] = None, - check_api_link: Optional[bool] = True, - check_access_token: Optional[bool] = True, - secured_params: Optional[List[str]] = None): + def __init__(self, attributes_order: tuple[str, ...] = (), + check_api_link: bool = True, + check_access_token: bool = True): self._attributes_order = attributes_order self._check_api_link = check_api_link self._check_access_token = check_access_token - self._secured_params = secured_params # for what @staticmethod def update_context(ctx: click.Context): @@ -93,18 +111,15 @@ def update_context(ctx: click.Context): """ if not isinstance(ctx.obj, dict): ctx.obj = {} - try: - from modular_cli_sdk.services.credentials_manager import \ - CredentialsProvider - SYSTEM_LOG.debug('Cli sdk is installed. ' - 'Using its credentials provider') + if CredentialsProvider: + _LOG.debug('Cli sdk is installed. Using its credentials provider') config = CustodianWithCliSDKConfig( credentials_manager=CredentialsProvider( module_name=MODULE_NAME, context=ctx ).credentials_manager ) - except ImportError: - SYSTEM_LOG.warning( + else: + _LOG.warning( 'Could not import modular_cli_sdk. Using standard ' 'config instead of the one provided by cli skd' ) @@ -113,10 +128,11 @@ def update_context(ctx: click.Context): config = CustodianCLIConfig(prefix=m3_username) # modular else: config = CustodianCLIConfig() # standard - adapter = AdapterClient(config) + + # ContextObj ctx.obj.update({ - CONTEXT_API_CLIENT: adapter, - CONTEXT_CONFIG: config + 'api_client': CustodianApiClient(config), + 'config': config }) def _check_context(self, ctx: click.Context): @@ -125,7 +141,7 @@ def _check_context(self, ctx: click.Context): :param ctx: :return: """ - obj: ContextObj = ctx.obj + obj: ContextObj = cast(ContextObj, ctx.obj) config = obj['config'] if self._check_api_link and not config.api_link: raise click.UsageError( @@ -142,301 +158,236 @@ def __call__(self, func: Callable) -> Callable: @wraps(func) def wrapper(*args, **kwargs): modular_mode = False - if Path(__file__).parents[3].name == MODULAR_ADMIN: + if Path(__file__).parents[3].name == MODULAR_ADMIN: # TODO check some other way modular_mode = True json_view = kwargs.pop('json') verbose = kwargs.pop('verbose') if verbose: write_verbose_logs() - ctx = click.get_current_context() + ctx = cast(click.Context, click.get_current_context()) self.update_context(ctx) try: self._check_context(ctx) - # TODO, add pass_obj to each method? - resp: Optional[ApiResponseType] = \ - click.pass_obj(func)(*args, **kwargs) + resp: CustodianResponse = click.pass_obj(func)(*args, **kwargs) except click.ClickException as e: - SYSTEM_LOG.info('Click exception has occurred') + _LOG.info('Click exception has occurred') resp = response(e.format_message()) except Exception as e: - SYSTEM_LOG.error(f'Unexpected error has occurred: {e}') + _LOG.error(f'Unexpected error has occurred: {e}') resp = response(str(e)) - api_response = ApiResponse(resp, ctx.obj['config'], - self._attributes_order) - if modular_mode: - SYSTEM_LOG.info('The cli is installed as a module. ' - 'Returning m3 modular cli response') - js = api_response.to_modular_json() - return api_response.json_view(_from=js) + _LOG.info('The cli is installed as a module. ' + 'Returning m3 modular cli response') + formatted = ModularResponseProcessor().format(resp) + return json.dumps(formatted, separators=(',', ':')) if not json_view: # table view - SYSTEM_LOG.info('Returning table view') - js = api_response.to_json() - trace_id = js.get(TRACE_ID_ATTR) - next_token = js.get(NEXT_TOKEN_ATTR) + _LOG.info('Returning table view') + prepared = TableResponseProcessor().format(resp) + trace_id = resp.trace_id + next_token = (resp.data or {}).get(NEXT_TOKEN_ATTR) try: - table_kwargs = dict(_from=js, _raise_on_overflow=True) - table_rep = api_response.table_view(**table_kwargs) - + printer = TablePrinter( + items_per_column=ctx.obj['config'].items_per_column, + attributes_order=self._attributes_order + ) + table = printer.print(prepared) except ColumnOverflow as ce: - SYSTEM_LOG.info(f'Awaiting user to respond to - {ce!r}.') + _LOG.info(f'Awaiting user to respond to - {ce!r}.') to_revert = click.prompt( REVERT_TO_JSON_MESSAGE, - type=click.Choice(['y', 'n']) + type=click.Choice(('y', 'n')) ) if to_revert == 'n': - table_rep = ce.table + table = ce.table else: - table_rep, json_view = None, True - - except (BaseException, Exception) as e: - serialized = response(content=str(e)) - table_rep = api_response.table_view(_from=serialized) + table, json_view = None, True - if table_rep: + if table: if verbose: click.echo(f'Trace id: \'{trace_id}\'') if next_token: click.echo(f'Next token: \'{next_token}\'') - click.echo(table_rep) - SYSTEM_LOG.info(f'Finished request: \'{trace_id}\'') + click.echo(table) + _LOG.info(f'Finished request: \'{trace_id}\'') if json_view: - SYSTEM_LOG.info('Returning json view') - click.echo(api_response.json_view()) - return + _LOG.info('Returning json view') + data = JsonResponseProcessor().format(resp) + click.echo(json.dumps(data, indent=4)) return wrapper -class ApiResponse: - table_datetime_format: str = '%A, %B %d, %Y %I:%M:%S %p' - table_format = 'pretty' - modular_table_title = 'Custodian as a service' - - def __init__(self, resp: ApiResponseType, config: CustodianCLIConfig, - attributes_order: Optional[List[str]] = None): - self._resp: ApiResponseType = resp - self._order = attributes_order - self._config = config - - @staticmethod - def _build_order_func(order: List[str]) -> Callable: +class ResponseProcessor(ABC): + @abstractmethod + def format(self, resp: CustodianResponse) -> Any: """ - Builds order lambda func for one dict item, i.e a tuple with - key and value - :param order: + Returns a dict that can be printed or used for printing + :param resp: :return: """ - return lambda x: order.index(x[0]) if x[0] in order \ - else 100 + ord(x[0][0].lower()) - @cached_property - def order_key(self) -> Optional[Callable]: - """ - Returns a lambda function to use in sorted(dct.items(), key=...) - to sort a dict according to the given order. If the order is not - given, returns None - :return: - """ - if not self._order: - return - return self._build_order_func(self._order) - def prepare_value(self, value: Union[str, list, dict, None], - _item_limit: Optional[int] = None) -> str: - """ - Makes the given value human-readable. Should be applied only for - table view since it can reduce the total amount of useful information - within the value in favor of better view. - :param value: - :param _item_limit: Optional[int] = None, number of - items per column. - :return: - """ - limit = _item_limit - to_limit = limit is not None - f = self.prepare_value +class JsonResponseProcessor(ResponseProcessor): + """ + Processes the json before it can be printed + """ - if not value and not isinstance(value, (int, bool)): - return '—' + def format(self, resp: CustodianResponse) -> dict: + if resp.code == HTTPStatus.NO_CONTENT: + return {MESSAGE_ATTR: NO_CONTENT_RESPONSE_MESSAGE} + elif isinstance(resp.exc, json.JSONDecodeError): + return {MESSAGE_ATTR: f'Invalid JSON received: {resp.exc.msg}'} + elif isinstance(resp.exc, urllib.error.URLError): + return {MESSAGE_ATTR: f'Cannot send a request: {resp.exc.reason}'} + return resp.data or {} - if isinstance(value, list): - i_recurse = (f(each, limit) for each in value) - result = ', '.join(islice(i_recurse, limit)) - if to_limit and len(value) > limit: - result += f'... ({len(value)})' # or len(value) - limit - return result - - elif isinstance(value, dict): - i_prepare = ( - f'{f(value=k)}: {f(value=v, _item_limit=limit)}' - for k, v in islice(value.items(), limit) - ) - result = reduce(lambda a, b: f'{a}; {b}', i_prepare) - if to_limit and len(value) > limit: - result += f'... ({len(value)})' - return result - elif isinstance(value, str): - try: - obj = isoparse(value) - # we assume that everything from the server is UTC even - # if it is a naive object - obj.replace(tzinfo=timezone.utc) - return obj.astimezone().strftime(self.table_datetime_format) - except ValueError: - return value - else: # bool, int, etc - return str(value) +class TableResponseProcessor(JsonResponseProcessor): + """ + Processes the json before it can be converted to table and printed + """ - @staticmethod - def response_to_json(resp: Response) -> dict: - if resp.status_code == RESPONSE_NO_CONTENT: - return ApiResponse.response(NO_CONTENT_RESPONSE_MESSAGE) - try: - return resp.json() - except requests.exceptions.JSONDecodeError as e: - SYSTEM_LOG.error( - f'Error occurred while loading response json: {e}' - ) - return ApiResponse.response(MALFORMED_RESPONSE_MESSAGE) + def format(self, resp: CustodianResponse) -> list[dict]: + dct = super().format(resp) + if data := dct.get(DATA_ATTR): + return [data] + if errors := dct.get(ERRORS_ATTR): + return errors + if items := dct.get(ITEMS_ATTR): + return items + if ITEMS_ATTR in dct and not dct.get(ITEMS_ATTR): # empty + return [{MESSAGE_ATTR: NO_ITEMS_TO_DISPLAY_RESPONSE_MESSAGE}] + return [dct] - def to_json(self) -> dict: - raw = self._resp - if isinstance(raw, dict): - return raw - # isinstance(raw, Response): - return self.response_to_json(raw) - def to_modular_json(self) -> dict: - """ - Changed output dict so that it can be read by m3 modular admin - :return: dict - """ - code = 200 - status = SUCCESS_STATUS - raw = self._resp - if isinstance(raw, Response): - code = raw.status_code - raw = self.response_to_json(raw) - # modular does not accept an empty list in "items". Dict must - # contain either "message" or not empty "items" - if ITEMS_ATTR in raw and not raw[ITEMS_ATTR]: # empty - raw.pop(ITEMS_ATTR) - raw.setdefault(MESSAGE_ATTR, NO_ITEMS_TO_DISPLAY_RESPONSE_MESSAGE) - return { - **raw, - CODE_ATTR: code, - STATUS_ATTR: status, +class ModularResponseProcessor(JsonResponseProcessor): + modular_table_title = 'Custodian as a service' + + def format(self, resp: CustodianResponse) -> dict: + base = { + CODE_ATTR: resp.code, + STATUS_ATTR: SUCCESS_STATUS if resp.ok else ERROR_STATUS, TABLE_TITLE_ATTR: self.modular_table_title } + dct = super().format(resp) + if data := dct.get(DATA_ATTR): + base[ITEMS_ATTR] = [data] + elif errors := dct.get(ERRORS_ATTR): + base[ITEMS_ATTR] = errors + elif dct.get(ITEMS_ATTR): + base.update(dct) + elif ITEMS_ATTR in dct: # empty + base[MESSAGE_ATTR] = NO_ITEMS_TO_DISPLAY_RESPONSE_MESSAGE + elif message := dct.get(MESSAGE_ATTR): + base[MESSAGE_ATTR] = message + else: + base[ITEMS_ATTR] = [dct] + return base + + +class TablePrinter: + default_datetime_format: str = '%A, %B %d, %Y %I:%M:%S %p' + default_format = 'pretty' + + def __init__(self, format: str = default_format, + datetime_format: str = default_datetime_format, + items_per_column: int | None = None, + attributes_order: tuple[str, ...] = ()): + self._format = format + self._datetime_format = datetime_format + self._items_per_column = items_per_column + if attributes_order: + self._order = {x: i for i, x in enumerate(attributes_order)} + else: + self._order = None - @staticmethod - def response(content: Union[str, List, Dict, Iterable]) -> Dict: - body = {} - if isinstance(content, str): - body.update({MESSAGE_ATTR: content}) - elif isinstance(content, dict) and content: - body.update(content) - elif isinstance(content, list): - body.update({ITEMS_ATTR: content}) - elif isinstance(content, Iterable): - body.update(({ITEMS_ATTR: list(content)})) - return body - - @staticmethod - def format_title(title: str) -> str: - """ - Human-readable - """ - return title.replace('_', ' ').capitalize() - - @staticmethod - def sorted_dict(dct: dict, key: Optional[Callable] = None) -> dict: - return dict(sorted(dct.items(), key=key)) - - def json_view(self, _from: Optional[dict] = None) -> str: + def prepare_value(self, value: str | list | dict | None) -> str: """ - Sorts keys before returning + Makes the given value human-readable. Should be applied only for + table view since it can reduce the total amount of useful information + within the value in favor of better view. + :param value: + items per column. :return: """ - resp = _from or self.to_json() - if isinstance(resp.get(ITEMS_ATTR), list): - resp[ITEMS_ATTR] = [ - self.sorted_dict(dct, self.order_key) - for dct in resp[ITEMS_ATTR] - ] - return json.dumps(resp, indent=4) - - def table_view(self, _from: Optional[dict] = None, - _raise_on_overflow: bool = True) -> str: - """ - Currently, the response is kind of valid in case it contains either - 'message' or 'items'. It sorts keys before returning - - :raise ColumnOverflow: if the column has overflown with - respect to the terminal size. - - :return: str - """ + if not value and not isinstance(value, (int, bool)): + return '—' - bounds = os.get_terminal_size().columns + limit = self._items_per_column + to_limit = limit is not None + f = self.prepare_value - resp = _from or self.to_json() - if ITEMS_ATTR in resp and isinstance(resp[ITEMS_ATTR], list): - _items = resp[ITEMS_ATTR] - if _items: # not empty - formatted = self._items_table([ - self.sorted_dict(dct, self.order_key) for dct in _items - ]) - else: - # empty - formatted = self._items_table([ - self.response(NO_ITEMS_TO_DISPLAY_RESPONSE_MESSAGE) - ]) + # todo, maybe use just list comprehensions instead of iterators + match value: + case list(): + i_recurse = map(f, value) + result = ', '.join(islice(i_recurse, limit)) + if to_limit and len(value) > limit: + result += f'... ({len(value)})' # or len(value) - limit + return result + case dict(): + i_prepare = ( + f'{f(value=k)}: {f(value=v)}' + for k, v in islice(value.items(), limit) + ) + result = reduce(lambda a, b: f'{a}; {b}', i_prepare) + if to_limit and len(value) > limit: + result += f'... ({len(value)})' + return result + case str(): + try: + obj = isoparse(value) + # we assume that everything from the server is UTC even + # if it is a naive object + obj.replace(tzinfo=timezone.utc) + return obj.astimezone().strftime(self._datetime_format) + except ValueError: + return value + case _: # bool, int + return str(value) + + def print(self, data: list[dict], raise_on_overflow: bool = True) -> str: + if order := self._order: + def key(tpl): + return order.get(tpl[0], 4096) # just some big int + formatted = self._items_table([ + dict(sorted(dct.items(), key=key)) for dct in data + ]) else: - # no items in resp, probably some message - resp.pop(TRACE_ID_ATTR, None) - formatted = self._items_table([resp]) - - overflown = formatted.index('\n') > bounds + formatted = self._items_table(data) - if overflown and _raise_on_overflow: + overflow = formatted.index('\n') > os.get_terminal_size().columns + if overflow and raise_on_overflow: raise ColumnOverflow(table=formatted) return formatted - def _items_table(self, items: List) -> str: + def _items_table(self, items: list[dict]) -> str: prepare_value = self.prepare_value - format_title = self.format_title - items_per_column = self._config.items_per_column rows, title_to_key = [], {} for entry in items: for key in entry: - title = format_title(title=key) + title = key.replace('_', ' ').capitalize() # title if title not in title_to_key: title_to_key[title] = key for entry in items: rows.append([ - prepare_value( - value=entry.get(key), - _item_limit=items_per_column - ) + prepare_value(value=entry.get(key)) for key in title_to_key.values() ]) return tabulate( rows, headers=list(title_to_key), - tablefmt=self.table_format + tablefmt=self._format ) @@ -444,20 +395,37 @@ class ViewCommand(click.core.Command): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.params.append( - click.core.Option(('--json',), is_flag=True, - help='Response as a JSON')) + click.core.Option( + ('--json',), + is_flag=True, + help='Response as a JSON' + ) + ) + self.params.append( + click.core.Option( + ('--verbose',), + is_flag=True, + help='Save detailed information to the log file' + ) + ) self.params.append( - click.core.Option(('--verbose',), is_flag=True, - help='Save detailed information to ' - 'the log file')) + click.core.Option( + ('--customer_id', '-cid'), + type=str, + help='Hidden customer option to make a request on other ' + 'customer`s behalf. Only for system customer', + required=False, + hidden=True + ) + ) -response: Callable = ApiResponse.response +response = CustodianResponse.build # callbacks def convert_in_upper_case_if_present(ctx, param, value): - if isinstance(value, list): + if isinstance(value, list | tuple): return [each.upper() for each in value] elif value: return value.upper() @@ -470,17 +438,6 @@ def convert_in_lower_case_if_present(ctx, param, value): return value.lower() -def build_customer_option(**kwargs) -> Callable: - params = dict( - type=str, - required=False, - help='Customer name which specifies whose entity to manage', - hidden=True, # can be used, but hidden - ) - params.update(kwargs) - return click.option('--customer_id', '-cid', **params) - - def build_tenant_option(**kwargs) -> Callable: params = dict( type=str, @@ -538,7 +495,7 @@ def build_job_id_option(*args, **kwargs) -> Callable: def build_job_type_option(*args, **kwargs) -> Callable: params = dict( - type=click.Choice(AVAILABLE_JOB_TYPES), + type=click.Choice(tuple(map(operator.attrgetter('value'), JobType))), help='Specify type of jobs to retrieve.', required=False ) @@ -555,7 +512,16 @@ def build_rule_source_id_option(**kwargs) -> Callable: return click.option('--rule_source_id', '-rsid', **params) -customer_option = build_customer_option() +def build_limit_option(**kwargs) -> Callable: + params = dict( + type=click.IntRange(min=1, max=50), + default=10, show_default=True, + help='Number of records to show' + ) + params.update(kwargs) + return click.option('--limit', '-l', **params) + + tenant_option = build_tenant_option() tenant_display_name_option = build_tenant_display_name_option() account_option = build_account_option() @@ -573,8 +539,6 @@ def build_rule_source_id_option(**kwargs) -> Callable: help='Generate report TILL date.' ) -limit_option = click.option('--limit', '-l', type=click.IntRange(min=1), - default=10, show_default=True, - help='Number of records to show') +limit_option = build_limit_option() next_option = click.option('--next_token', '-nt', type=str, required=False, - help=f'Token to start record-pagination from') + help='Token to start record-pagination from') diff --git a/c7n/c7ncli/group/application.py b/c7n/c7ncli/group/application.py deleted file mode 100644 index 25ab189fe..000000000 --- a/c7n/c7ncli/group/application.py +++ /dev/null @@ -1,110 +0,0 @@ -from typing import Optional - -import click - -from c7ncli.group import cli_response, ViewCommand, response, ContextObj, \ - customer_option -from c7ncli.group.application_access import access -from c7ncli.group.application_dojo import dojo -from c7ncli.service.constants import RULE_CLOUDS - - -@click.group(name='application') -def application(): - """Manages Applications Entity""" - - -@application.command(cls=ViewCommand, name='add') -@customer_option -@click.option('--description', '-d', type=str, - help='Application description') -@click.option('--cloud', '-c', required=False, - type=click.Choice(RULE_CLOUDS), - help='Cloud to activate the application for') -@click.option('--cloud_application_id', '-caid', required=False, type=str, - help='Application id containing creds to access the cloud') -@click.option('--tenant_license_key', '-tlk', required=False, - type=str, - help='Tenant license key with license for the specified cloud') -@cli_response() -def add(ctx: ContextObj, customer_id: Optional[str], **kwargs): - """ - Creates Custodian Application with specified license - """ - - _cloud_modifier = (bool(kwargs['cloud_application_id']) or - bool(kwargs['tenant_license_key'])) - _cloud = bool(kwargs['cloud']) - kwargs.update(customer=customer_id) - - if _cloud ^ _cloud_modifier: - return response('Both --cloud and --cloud_application_id ' - 'or --tenant_license_key must be specified or omitted') - kwargs['access_application_id'] = kwargs.pop('cloud_application_id') - return ctx['api_client'].application_post(**kwargs) - - -@application.command(cls=ViewCommand, name='update') -@click.option('--application_id', '-aid', required=True, type=str, - help='Id of the application') -@click.option('--description', '-d', type=str, - help='Application description') -@click.option('--cloud', '-c', - type=click.Choice(RULE_CLOUDS), - help='Cloud to activate the application for') -@click.option('--cloud_application_id', '-caid', - type=str, help='Application id containing creds to ' - 'access the cloud') -@click.option('--tenant_license_key', '-tlk', - type=str, - help='Tenant license key with license for the specified cloud') -@customer_option -@cli_response() -def update(ctx: ContextObj, application_id, **kwargs): - """ - Update Custodian Application - """ - - _cloud_modifier = (bool(kwargs['cloud_application_id']) or - bool(kwargs['tenant_license_key'])) - _cloud = bool(kwargs['cloud']) - - if _cloud ^ _cloud_modifier: - return response('Both --cloud and --cloud_application_id ' - 'or --tenant_license_key must be specified or omitted') - kwargs['access_application_id'] = kwargs.pop('cloud_application_id') - kwargs['customer'] = kwargs.pop('customer_id', None) - return ctx['api_client'].application_patch(application_id, **kwargs) - - -@application.command(cls=ViewCommand, name='describe') -@click.option('--application_id', '-aid', required=False, type=str, - help='Id of the application') -@customer_option -@cli_response() -def describe(ctx: ContextObj, application_id: str, - customer_id: Optional[str]): - """ - Describe Custodian Application - """ - if application_id: - return ctx['api_client'].application_get(application_id) - return ctx['api_client'].application_list( - customer=customer_id - ) - - -@application.command(cls=ViewCommand, name='delete') -@click.option('--application_id', '-aid', required=True, type=str, - help='Id of the application') -@customer_option -@cli_response() -def delete(ctx: ContextObj, application_id: str, customer_id): - """ - Delete Custodian Application - """ - return ctx['api_client'].application_delete(application_id, customer_id) - - -application.add_command(access) -application.add_command(dojo) diff --git a/c7n/c7ncli/group/application_access.py b/c7n/c7ncli/group/application_access.py deleted file mode 100644 index 9c36cec80..000000000 --- a/c7n/c7ncli/group/application_access.py +++ /dev/null @@ -1,102 +0,0 @@ -from typing import Optional - -import click - -from c7ncli.group import cli_response, ViewCommand, response, ContextObj, \ - customer_option - - -@click.group(name='access') -def access(): - """Manages Applications Entity""" - - -@access.command(cls=ViewCommand, name='add') -@customer_option -@click.option('--description', '-d', type=str, - help='Application description') -@click.option('--username', '-u', type=str, - help='Username to set to the application') -@click.option('--password', '-p', type=str, - help='Password to set to application') -@click.option('--url', '-U', type=str, - help='Url to Custodian installation') -@click.option('--auto_resolve_access', '-ara', is_flag=True, - help='If specified, Custodian will try to ' - 'resolve access automatically. ' - 'Otherwise you must specify url') -@click.option('--results_storage', '-rs', type=str, - help='S3 bucket name were to store EC2 recommendations') -@cli_response() -def add(ctx: ContextObj, customer_id: Optional[str], **kwargs): - """ - Creates Custodian Application with specified access data. Only one - Custodian application with access data within a customer can be created - """ - kwargs.update(customer=customer_id) - if bool(kwargs['username']) ^ bool(kwargs['password']): - return response('Both --username and --password must be given') - if not kwargs['auto_resolve_access'] and not kwargs['url']: - return response('--url must be given in case ' - '--auto_resolve_access is not specified') - return ctx['api_client'].access_application_post(**kwargs) - - -@access.command(cls=ViewCommand, name='update') -@click.option('--application_id', '-aid', required=True, type=str, - help='Id of the application') -@click.option('--description', '-d', type=str, - help='Application description') -@click.option('--username', '-u', type=str, - help='Username to set to the application') -@click.option('--password', '-p', type=str, - help='Password to set to application') -@click.option('--url', '-U', type=str, - help='Url to Custodian installation') -@click.option('--auto_resolve_access', '-ara', is_flag=True, - help='If specified, Custodian will try to ' - 'resolve access automatically. ' - 'Otherwise you must specify url') -@click.option('--results_storage', '-rs', type=str, - help='S3 bucket name were to store EC2 recommendations') -@customer_option -@cli_response() -def update(ctx: ContextObj, application_id, **kwargs): - """ - Update Custodian Application - """ - if bool(kwargs['username']) ^ bool(kwargs['password']): - return response('Both --username and --password must be given') - kwargs['customer'] = kwargs.pop('customer_id', None) - return ctx['api_client'].access_application_patch(application_id, **kwargs) - - -@access.command(cls=ViewCommand, name='describe') -@click.option('--application_id', '-aid', required=False, type=str, - help='Id of the application') -@customer_option -@cli_response() -def describe(ctx: ContextObj, application_id: str, - customer_id: Optional[str]): - """ - Describe Custodian Application - """ - if application_id: - return ctx['api_client'].access_application_get(application_id) - return ctx['api_client'].access_application_list( - customer=customer_id - ) - - -@access.command(cls=ViewCommand, name='delete') -@click.option('--application_id', '-aid', required=True, type=str, - help='Id of the application') -@customer_option -@cli_response() -def delete(ctx: ContextObj, application_id: str, customer_id): - """ - Delete Custodian Application - """ - return ctx['api_client'].access_application_delete( - application_id, customer_id - ) diff --git a/c7n/c7ncli/group/application_dojo.py b/c7n/c7ncli/group/application_dojo.py deleted file mode 100644 index 2a8207def..000000000 --- a/c7n/c7ncli/group/application_dojo.py +++ /dev/null @@ -1,78 +0,0 @@ -from typing import Optional - -import click - -from c7ncli.group import cli_response, ViewCommand, response, ContextObj, \ - customer_option - - -@click.group(name='dojo') -def dojo(): - """Manages Applications Entity""" - - -@dojo.command(cls=ViewCommand, name='add') -@customer_option -@click.option('--description', '-d', type=str, - help='Application description') -@click.option('--url', '-U', type=str, required=True, - help='Url to Custodian installation') -@click.option('--api_key', '-key', type=str, required=True, - help='Defect Dojo api key') -@cli_response() -def add(ctx: ContextObj, customer_id: Optional[str], **kwargs): - """ - Creates an application which holds access to Defect Dojo - """ - kwargs.update(customer=customer_id) - return ctx['api_client'].dojo_application_post(**kwargs) - - -@dojo.command(cls=ViewCommand, name='update') -@click.option('--application_id', '-aid', required=True, type=str, - help='Id of the application') -@click.option('--description', '-d', type=str, - help='Application description') -@click.option('--url', '-U', type=str, - help='Url to Custodian installation') -@click.option('--api_key', '-key', type=str, - help='Defect Dojo api key') -@customer_option -@cli_response() -def update(ctx: ContextObj, application_id, **kwargs): - """ - Update Custodian Application - """ - kwargs['customer'] = kwargs.pop('customer_id', None) - return ctx['api_client'].dojo_application_patch(application_id, **kwargs) - - -@dojo.command(cls=ViewCommand, name='describe') -@click.option('--application_id', '-aid', required=False, type=str, - help='Id of the application') -@customer_option -@cli_response() -def describe(ctx: ContextObj, application_id: str, - customer_id: Optional[str]): - """ - Describe Custodian Application - """ - if application_id: - return ctx['api_client'].dojo_application_get(application_id) - return ctx['api_client'].dojo_application_list( - customer=customer_id - ) - - -@dojo.command(cls=ViewCommand, name='delete') -@click.option('--application_id', '-aid', required=True, type=str, - help='Id of the application') -@customer_option -@cli_response() -def delete(ctx: ContextObj, application_id: str, customer_id): - """ - Delete Custodian Application - """ - return ctx['api_client'].dojo_application_delete( - application_id, customer_id - ) diff --git a/c7n/c7ncli/group/c7n.py b/c7n/c7ncli/group/c7n.py index a7f9d9d96..b2fc749fa 100644 --- a/c7n/c7ncli/group/c7n.py +++ b/c7n/c7ncli/group/c7n.py @@ -1,13 +1,13 @@ import click -from c7ncli.group import cli_response, ViewCommand, response, ContextObj -from c7ncli.group.application import application +from c7ncli.group import ContextObj, ViewCommand, cli_response, response from c7ncli.group.customer import customer +from c7ncli.group.integrations import integrations from c7ncli.group.job import job from c7ncli.group.license import license from c7ncli.group.meta import meta from c7ncli.group.metrics import metrics -from c7ncli.group.parent import parent +from c7ncli.group.platform import platform from c7ncli.group.policy import policy from c7ncli.group.report import report from c7ncli.group.results import results @@ -17,11 +17,11 @@ from c7ncli.group.rulesource import rulesource from c7ncli.group.setting import setting from c7ncli.group.tenant import tenant -from c7ncli.group.platform import platform -from c7ncli.group.user import user +from c7ncli.group.users import users from c7ncli.service.helpers import validate_api_link from c7ncli.service.logger import get_logger -from c7ncli.version import __version__ +from c7ncli.version import __version__, check_version_compatibility + SYSTEM_LOG = get_logger(__name__) @@ -39,7 +39,7 @@ def c7n(): help='Specify how many items per table. ' 'Set `0` to disable the limitation') @cli_response(check_api_link=False, check_access_token=False, ) -def configure(ctx: ContextObj, api_link, items_per_column): +def configure(ctx: ContextObj, api_link, items_per_column, **kwargs): """ Configures c7n tool to work with Custodian as a Service. """ @@ -67,23 +67,35 @@ def configure(ctx: ContextObj, api_link, items_per_column): @click.option('--password', '-p', type=str, required=True, hide_input=True, prompt=True, help='Custodian Service user password.') -@cli_response(check_access_token=False, secured_params=['password']) -def login(ctx: ContextObj, username: str, password: str): +@cli_response(check_access_token=False) +def login(ctx: ContextObj, username: str, password: str, **kwargs): """ Authenticates user to work with Custodian as a Service. """ adapter = ctx['api_client'] - _response = adapter.login(username=username, password=password) - - if isinstance(_response, dict): - return _response - ctx['config'].access_token = _response + resp = adapter.login(username=username, password=password) + if resp.exc or not resp.ok: + return resp + check_version_compatibility(resp.api_version) + + ctx['config'].access_token = resp.data['access_token'] + if rt := resp.data.get('refresh_token'): + ctx['config'].refresh_token = rt return response('Great! The c7n tool access token has been saved.') +@c7n.command(cls=ViewCommand, name='whoami') +@cli_response(attributes_order=('username', 'customer', 'role', 'latest_login')) +def whoami(ctx: ContextObj, customer_id: str): + """ + Returns information about the current user + """ + return ctx['api_client'].whoami() + + @c7n.command(cls=ViewCommand, name='cleanup') @cli_response(check_access_token=False, check_api_link=False) -def cleanup(ctx: ContextObj): +def cleanup(ctx: ContextObj, **kwargs): """ Removes all the configuration data related to the tool. """ @@ -95,11 +107,11 @@ def cleanup(ctx: ContextObj): @click.option('--identifier', '-id', type=str, help='Concrete check id to retrieve') @click.option('--status', '-st', - type=click.Choice(['OK', 'UNKNOWN', 'NOT_OK']), + type=click.Choice(('OK', 'UNKNOWN', 'NOT_OK')), help='Filter checks by status') -@cli_response(attributes_order=['id', 'status', 'details', 'impact', - 'remediation']) -def health_check(ctx: ContextObj, identifier, status): +@cli_response(attributes_order=('id', 'status', 'details', 'impact', + 'remediation')) +def health_check(ctx: ContextObj, identifier, status, **kwargs): """ Checks Custodian Service components availability """ @@ -110,7 +122,7 @@ def health_check(ctx: ContextObj, identifier, status): @c7n.command(cls=ViewCommand, name='show_config') @cli_response() -def show_config(ctx: ContextObj): +def show_config(ctx: ContextObj, **kwargs): """ Returns the cli configuration """ @@ -127,12 +139,10 @@ def show_config(ctx: ContextObj): c7n.add_command(ruleset) c7n.add_command(rulesource) c7n.add_command(license) -# c7n.add_command(trigger) -c7n.add_command(user) c7n.add_command(setting) c7n.add_command(results) -c7n.add_command(application) -c7n.add_command(parent) c7n.add_command(metrics) c7n.add_command(meta) -c7n.add_command(platform) \ No newline at end of file +c7n.add_command(platform) +c7n.add_command(integrations) +c7n.add_command(users) diff --git a/c7n/c7ncli/group/customer.py b/c7n/c7ncli/group/customer.py index c1601c54d..51139f6ae 100644 --- a/c7n/c7ncli/group/customer.py +++ b/c7n/c7ncli/group/customer.py @@ -1,9 +1,9 @@ import click -from c7ncli.group import cli_response, ViewCommand, ContextObj, customer_option +from c7ncli.group import ContextObj, ViewCommand, cli_response, response from c7ncli.group.customer_rabbitmq import rabbitmq -from c7ncli.service.constants import PARAM_DISPLAY_NAME, \ - PARAM_NAME + +attributes_order = 'name', 'display_name', 'admins' @click.group(name='customer') @@ -12,17 +12,46 @@ def customer(): @customer.command(cls=ViewCommand, name='describe') -@customer_option -@click.option('--full', '-f', is_flag=True, - help='Show full command output', show_default=True) -@cli_response(attributes_order=[PARAM_NAME, PARAM_DISPLAY_NAME]) -def describe(ctx: ContextObj, customer_id: str, full: bool): +@cli_response(attributes_order=attributes_order) +def describe(ctx: ContextObj, customer_id: str): """ Describes your user's customer """ return ctx['api_client'].customer_get( name=customer_id, - complete=full + ) + + +@customer.command(cls=ViewCommand, name='set_excluded_rules') +@click.option('--rules', '-r', type=str, multiple=True, + help='Rules that you want to exclude for a customer. ' + 'They will be excluded for each tenant') +@click.option('--empty', is_flag=True, help='Whether to reset the ' + 'list of excluded rules') +@cli_response() +def set_excluded_rules(ctx: ContextObj, customer_id: str | None, + rules: tuple[str, ...], empty: bool): + """ + Excludes rules for a customer + """ + if not rules and not empty: + return response('Specify either --rules '' or --empty') + if empty: + rules = () + return ctx['api_client'].customer_set_excluded_rules( + customer_id=customer_id, + rules=rules + ) + + +@customer.command(cls=ViewCommand, name='get_excluded_rules') +@cli_response() +def get_excluded_rules(ctx: ContextObj, customer_id): + """ + Returns excluded rules for a customer + """ + return ctx['api_client'].customer_get_excluded_rules( + customer_id=customer_id ) diff --git a/c7n/c7ncli/group/customer_rabbitmq.py b/c7n/c7ncli/group/customer_rabbitmq.py index 99ceac88e..3b4ac80fc 100644 --- a/c7n/c7ncli/group/customer_rabbitmq.py +++ b/c7n/c7ncli/group/customer_rabbitmq.py @@ -1,12 +1,12 @@ import click -from c7ncli.group import ViewCommand, cli_response, ContextObj, customer_option +from c7ncli.group import ContextObj, ViewCommand, cli_response @click.group(name='rabbitmq') def rabbitmq(): """ - Manages Job submit action + Manages RabbitMQ configuration for a customer """ @@ -25,33 +25,27 @@ def rabbitmq(): help='Rabbit connection url') @click.option('--sdk_secret_key', '-ssk', type=str, required=True, help='SDK Secret key') -@customer_option -@cli_response(secured_params=['sdk_secret_key']) +@cli_response() def add(ctx: ContextObj, **kwargs): """ Creates rabbitMQ configuration for your customer """ - kwargs['customer'] = kwargs.pop('customer_id', None) return ctx['api_client'].rabbitmq_post(**kwargs) @rabbitmq.command(cls=ViewCommand, name='describe') -@customer_option @cli_response() def describe(ctx: ContextObj, **kwargs): """ Describes rabbitMQ configuration for your customer """ - kwargs['customer'] = kwargs.pop('customer_id', None) return ctx['api_client'].rabbitmq_get(**kwargs) @rabbitmq.command(cls=ViewCommand, name='delete') -@customer_option @cli_response() def delete(ctx: ContextObj, **kwargs): """ Removes rabbitMQ configuration for your customer """ - kwargs['customer'] = kwargs.pop('customer_id', None) return ctx['api_client'].rabbitmq_delete(**kwargs) diff --git a/c7n/c7ncli/group/integrations.py b/c7n/c7ncli/group/integrations.py new file mode 100644 index 000000000..70e800c9a --- /dev/null +++ b/c7n/c7ncli/group/integrations.py @@ -0,0 +1,13 @@ +import click + +from c7ncli.group.integrations_dojo import dojo +from c7ncli.group.integrations_sre import sre + + +@click.group(name='integrations') +def integrations(): + """Manages Custodian Service Integrations""" + + +integrations.add_command(dojo) +integrations.add_command(sre) diff --git a/c7n/c7ncli/group/integrations_dojo.py b/c7n/c7ncli/group/integrations_dojo.py new file mode 100644 index 000000000..9e75de5a0 --- /dev/null +++ b/c7n/c7ncli/group/integrations_dojo.py @@ -0,0 +1,172 @@ +import click + +from c7ncli.group import ( + ContextObj, + ViewCommand, + build_tenant_option, + cli_response, + response, +) +from c7ncli.service.constants import AWS, AZURE, GOOGLE, KUBERNETES + + +@click.group(name='dojo') +def dojo(): + """ + Manages defect dojo integrations + :return: + """ + + +@dojo.command(cls=ViewCommand, name='add') +@click.option('--url', '-u', type=str, required=True, + help='Url to defect dojo installation. With API prefix ' + '(http://127.0.0.1:8080/api/v2') +@click.option('--api_key', '-ak', type=str, required=True, + help='Defect dojo api key') +@click.option('--description', '-d', type=str, required=True, + help='Human-readable description of this installation') +@cli_response() +def add(ctx: ContextObj, url: str, api_key: str, description: str, + customer_id: str | None): + """ + Adds dojo integration + """ + return ctx['api_client'].dojo_add( + api_key=api_key, + url=url, + description=description, + customer_id=customer_id + ) + + +@dojo.command(cls=ViewCommand, name='describe') +@click.option('--integration_id', '-id', type=str, required=False, + help='Id of dojo integration') +@cli_response() +def describe(ctx: ContextObj, integration_id: str | None, + customer_id: str | None): + """ + Describes Dojo integration + """ + if integration_id: + return ctx['api_client'].dojo_get(integration_id, customer_id=customer_id) + return ctx['api_client'].dojo_query(customer_id=customer_id) + + +@dojo.command(cls=ViewCommand, name='delete') +@click.option('--integration_id', '-id', type=str, required=True, + help='Id of dojo integration') +@cli_response() +def delete(ctx: ContextObj, integration_id: str, customer_id): + """ + Deletes Dojo integration + """ + return ctx['api_client'].dojo_delete(integration_id, + customer_id=customer_id) + + +@dojo.command(cls=ViewCommand, name='activate') +@click.option('--integration_id', '-id', type=str, required=True, + help='Id of dojo integration') +@build_tenant_option(multiple=True) +@click.option('--all_tenants', is_flag=True, + help='Whether to activate integration for all tenants') +@click.option('--clouds', '-cl', + type=click.Choice((AWS, AZURE, GOOGLE, KUBERNETES)), + multiple=True, + help='Tenant clouds to activate this dojo for. ' + 'Can be specific together with --all_tenants flag') +@click.option('--exclude_tenant', '-et', type=str, multiple=True, + help='Tenants to exclude for this integration. ' + 'Can be specified together with --all_tenants flag') +@click.option('--scan_type', '-st', default='Generic Findings Import', + show_default=True, + type=click.Choice( + ('Generic Findings Import', 'Cloud Custodian Scan')), + help='Defect Dojo scan type. Generic Findings Import can be ' + 'used with open source DefectDojo whereas Cloud ' + 'Custodian Scan - with EPAM`s fork') +@click.option('--send_after_job', '-saj', is_flag=True, + help='Specify this flag to send results to dojo after each job ' + 'automatically') +@click.option('--product_type', type=str, default='Rule Engine', + help='Product name to create in Dojo, ' + '"Rule Engine" is used by default. "tenant_name", ' + '"customer_name" and "job_id" can be used as generic ' + 'placeholders inside curly brackets') +@click.option('--product', type=str, default='{tenant_name}', + help='Product name to create in Dojo, ' + '"{tenant_name}" is used by default') +@click.option('--engagement', type=str, default='Rule-Engine Main', + help='Engagement name to create in Dojo. "Rule-Engine Main" ' + 'is used by default') +@click.option('--test', type=str, default='{job_id}', + help='Test name to create in Dojo, ' + '"{job_id}" is used by default') +@click.option('--attachment', type=click.Choice(('json', 'xlsx', 'csv')), + required=False, + help='What type of file with resources to attach to each ' + 'finding. If not provided, no files will be attached, ' + 'resources will be displayed in description') +@cli_response() +def activate(ctx: ContextObj, integration_id: str, + tenant_name: tuple[str, ...], + all_tenants: bool, clouds: tuple[str], + exclude_tenant: tuple[str, ...], scan_type: str, + send_after_job: bool, product_type: str | None, + product: str | None, engagement: str | None, test: str | None, + attachment: str | None, customer_id): + """ + Activates a concrete dojo integration for a specific set of tenants. + Each activation overrides the existing one + """ + if tenant_name and any((all_tenants, clouds, exclude_tenant)): + return response('Do not provide --all_tenants, --clouds or ' + '--exclude_tenants if --tenant_name given') + if not all_tenants and not tenant_name: + return response('Either --all_tenants or --tenant_name must be given') + if (clouds or exclude_tenant) and not all_tenants: + return response('set --all_tenants if you provide --clouds or ' + '--exclude_tenants') + return ctx['api_client'].dojo_activate( + id=integration_id, + tenant_names=tenant_name, + all_tenants=all_tenants, + clouds=clouds, + exclude_tenants=exclude_tenant, + scan_type=scan_type, + send_after_job=send_after_job, + product_type=product_type, + product=product, + engagement=engagement, + test=test, + attachment=attachment, + customer_id=customer_id + ) + + +@dojo.command(cls=ViewCommand, name='deactivate') +@click.option('--integration_id', '-id', type=str, required=True, + help='Id of dojo integration') +@cli_response() +def deactivate(ctx: ContextObj, integration_id: str, customer_id): + """ + Deactivates a concrete dojo integration for a specific set of tenants + """ + return ctx['api_client'].dojo_deactivate(id=integration_id, + customer_id=customer_id) + + +@dojo.command(cls=ViewCommand, name='get_activation') +@click.option('--integration_id', '-id', type=str, required=True, + help='Id of dojo integration') +@cli_response() +def get_activation(ctx: ContextObj, integration_id: str, customer_id): + """ + Returns a dojo activation + """ + return ctx['api_client'].dojo_get_activation( + id=integration_id, + customer_id=customer_id + ) diff --git a/c7n/c7ncli/group/integrations_sre.py b/c7n/c7ncli/group/integrations_sre.py new file mode 100644 index 000000000..dbc3fb81e --- /dev/null +++ b/c7n/c7ncli/group/integrations_sre.py @@ -0,0 +1,113 @@ +import click + +from c7ncli.group import ( + ContextObj, + ViewCommand, + build_tenant_option, + cli_response, + response, +) +from c7ncli.service.constants import AWS, AZURE, GOOGLE, KUBERNETES + + +@click.group(name='sre') +def sre(): + """ + Manages Rule engine integration (self integration for Maestro) + :return: + """ + + +@sre.command(cls=ViewCommand, name='add') +@build_tenant_option(multiple=True) +@click.option('--all_tenants', is_flag=True, + help='Whether to activate integration for all tenants') +@click.option('--clouds', '-cl', + type=click.Choice((AWS, AZURE, GOOGLE, KUBERNETES)), + multiple=True, + help='Tenant clouds to activate this dojo for. ' + 'Can be specific together with --all_tenants flag') +@click.option('--exclude_tenant', '-et', type=str, multiple=True, + help='Tenants to exclude for this integration. ' + 'Can be specified together with --all_tenants flag') +@click.option('--description', '-d', type=str, required=True, + help='Human-readable description of this installation') +@click.option('--username', '-u', type=str, required=True, + help='Username to set to the application') +@click.option('--password', '-p', type=str, required=True, + help='Password to set to application') +@click.option('--url', '-U', type=str, + help='Url to Custodian installation') +@click.option('--auto_resolve_access', '-ara', is_flag=True, + help='If specified, Custodian will try to ' + 'resolve access automatically. ' + 'Otherwise you must specify url') +@click.option('--results_storage', '-rs', type=str, + help='S3 bucket name were to store EC2 recommendations') +@cli_response() +def add(ctx: ContextObj, tenant_name: tuple[str, ...], all_tenants: bool, + clouds: tuple[str], exclude_tenant: tuple[str, ...], + description: str, username: str, password: str, url: str | None, + auto_resolve_access: bool, + results_storage: str | None, customer_id: str | None): + """ + Adds self integration + """ + if tenant_name and any((all_tenants, clouds, exclude_tenant)): + return response('Do not provide --all_tenants, --clouds or ' + '--exclude_tenants if --tenant_name given') + if not all_tenants and not tenant_name: + return response('Either --all_tenants or --tenant_name must be given') + if (clouds or exclude_tenant) and not all_tenants: + return response('set --all_tenants if you provide --clouds or ' + '--exclude_tenants') + if auto_resolve_access and url: + return response('Do not provide --url if --auto_resolve_access is set') + return ctx['api_client'].sre_add( + description=description, + username=username, + password=password, + auto_resolve_access=auto_resolve_access, + url=url, + results_storage=results_storage, + tenant_names=tenant_name, + all_tenants=all_tenants, + clouds=clouds, + exclude_tenant=exclude_tenant, + customer_id=customer_id + ) + + +@sre.command(cls=ViewCommand, name='describe') +@cli_response() +def describe(ctx: ContextObj, customer_id): + """ + Describes self integration + """ + return ctx['api_client'].sre_describe(customer_id=customer_id) + + +@sre.command(cls=ViewCommand, name='delete') +@cli_response() +def delete(ctx: ContextObj, customer_id): + """ + Deletes self integration + """ + return ctx['api_client'].sre_delete(customer_id=customer_id) + + +@sre.command(cls=ViewCommand, name='update') +@click.option('--add_tenant', '-at', type=str, multiple=True, + help='Tenants to activate') +@click.option('--exclude_tenant', '-et', type=str, multiple=True, + help='Tenants to deactivate') +@cli_response() +def update(ctx: ContextObj, customer_id, add_tenant, exclude_tenant): + """ + Allows to add and remove specific tenants from the activation + """ + return ctx['api_client'].sre_update( + add_tenants=add_tenant, + remove_tenants=exclude_tenant, + customer_id=customer_id + ) diff --git a/c7n/c7ncli/group/job.py b/c7n/c7ncli/group/job.py index 0699292fb..4b1ffff52 100644 --- a/c7n/c7ncli/group/job.py +++ b/c7n/c7ncli/group/job.py @@ -1,26 +1,24 @@ import json import os from pathlib import Path -from typing import Tuple, Optional import click -from c7ncli.service.constants import C7NCLI_DEVELOPER_MODE_ENV_NAME -from c7ncli.service.constants import PARAM_JOB_ID, PARAM_STARTED_AT, \ - PARAM_JOB_OWNER, PARAM_STATUS, \ - PARAM_STOPPED_AT, PARAM_SCAN_REGIONS, PARAM_SUBMITTED_AT, \ - PARAM_SCAN_RULESETS, PARAM_CREATED_AT, AWS, AZURE, GOOGLE -from c7ncli.service.logger import get_user_logger -from c7ncli.group import cli_response, ViewCommand, response, ContextObj, \ - customer_option -from c7ncli.group import tenant_option, limit_option, next_option +from c7ncli.group import ContextObj, ViewCommand, cli_response, response +from c7ncli.group import limit_option, next_option, tenant_option from c7ncli.group.job_scheduled import scheduled +from c7ncli.service.constants import AWS, AZURE, GOOGLE, JobState, \ + ENV_AWS_ACCESS_KEY_ID, ENV_AWS_SESSION_TOKEN, ENV_AWS_SECRET_ACCESS_KEY, \ + ENV_AZURE_TENANT_ID, ENV_AZURE_CLIENT_ID, ENV_AZURE_CLIENT_SECRET, \ + ENV_AZURE_SUBSCRIPTION_ID +from c7ncli.service.constants import C7NCLI_DEVELOPER_MODE_ENV_NAME from c7ncli.service.credentials import EnvCredentialsResolver from c7ncli.service.helpers import Color +from c7ncli.service.logger import get_user_logger USER_LOG = get_user_logger(__name__) -AVAILABLE_CLOUDS = [AWS, AZURE, GOOGLE] +attributes_order = 'id', 'tenant_name', 'status', 'submitted_at', @click.group(name='job') @@ -32,15 +30,20 @@ def job(): @click.option('--job_id', '-id', type=str, required=False, help='Job id to describe') @tenant_option -@customer_option +@click.option('--status', '-s', type=click.Choice(tuple(JobState.iter())), + required=False, help='Status to query jobs by') +@click.option('--from_date', '-from', type=str, + help='Query jobs from this date. Accepts date ISO string. ' + 'Example: 2023-10-20') +@click.option('--to_date', '-to', type=str, + help='Query jobs till this date. Accepts date ISO string. ' + 'Example: 2023-10-20') @limit_option @next_option -@cli_response(attributes_order=[PARAM_JOB_ID, PARAM_JOB_OWNER, PARAM_STATUS, - PARAM_SCAN_REGIONS, PARAM_SCAN_RULESETS, - PARAM_SUBMITTED_AT, PARAM_STARTED_AT, - PARAM_STOPPED_AT]) +@cli_response(attributes_order=attributes_order) def describe(ctx: ContextObj, job_id: str, tenant_name: str, customer_id: str, - limit: int, next_token: str): + limit: int, next_token: str, status: str, from_date: str, + to_date: str): """ Describes Custodian Service Scans """ @@ -49,13 +52,17 @@ def describe(ctx: ContextObj, job_id: str, tenant_name: str, customer_id: str, return response('You do not have to specify account of tenant ' 'name if job id is specified.') if job_id: - return ctx['api_client'].job_get(job_id) - return ctx['api_client'].job_list( - tenant_name=tenant_name, - customer=customer_id, - limit=limit, - next_token=next_token - ) + return ctx['api_client'].job_get(job_id, customer_id=customer_id) + dct = { + 'tenant_name': tenant_name, + 'customer_id': customer_id, + 'limit': limit, + 'next_token': next_token, + 'status': status, + 'from': from_date, + 'to': to_date + } + return ctx['api_client'].job_list(**dct) @job.command(cls=ViewCommand, name='submit') @@ -68,29 +75,29 @@ def describe(ctx: ContextObj, job_id: str, tenant_name: str, customer_id: str, multiple=True, help='Regions to scan. If not specified, ' 'all active regions will be used') -@click.option('--cloud', '-c', type=click.Choice(AVAILABLE_CLOUDS), +@click.option('--cloud', '-c', type=click.Choice((AWS, AZURE, GOOGLE)), required=False, help='Cloud to scan. Required, if ' '`--credentials_from_env` flag is set.') @click.option('--credentials_from_env', '-cenv', is_flag=True, default=False, help='Specify to get credentials for scan from environment variables. ' 'Requires `--cloud` to be set.') @click.option('--rules_to_scan', required=False, multiple=True, type=str, - help='Concrete rule ids to scan. These rules will be filtered ' - 'from the ruleset and scanned. ' - 'Can be json string with list of rules or path to a ' - 'json file with list or rules') -@customer_option -@cli_response( - attributes_order=[PARAM_JOB_ID, PARAM_JOB_OWNER, PARAM_STATUS, - PARAM_SUBMITTED_AT, PARAM_CREATED_AT, - PARAM_STARTED_AT, PARAM_STOPPED_AT] -) + help='Rules that must be scanned. Ruleset must contain them. ' + 'You can specify some subpart of rule names. Custodian ' + 'will try to resolve the full names: aws-002 -> ' + 'ecc-aws-002-encryption... Also you can specify some part ' + 'that is common to multiple rules. All the them will be ' + 'resolved: postgresql -> [ecc-aws-001-postgresql..., ' + 'ecc-aws-002-postgresql...]. This CLI param can accept ' + 'both raw rule names and path to file with JSON list ' + 'of rules') +@cli_response(attributes_order=attributes_order) def submit(ctx: ContextObj, cloud: str, tenant_name: str, - ruleset: Tuple[str, ...], region: Tuple[str, ...], + ruleset: tuple[str, ...], region: tuple[str, ...], credentials_from_env: bool, - customer_id: Optional[str], rules_to_scan: Optional[Tuple[str]]): + customer_id: str | None, rules_to_scan: tuple[str]): """ - Submits a job to scan an infrastructure + Submits a job to scan either AWS, AZURE or GOOGLE account """ credentials = None if not cloud and credentials_from_env: @@ -109,7 +116,171 @@ def submit(ctx: ContextObj, cloud: str, tenant_name: str, target_rulesets=ruleset, target_regions=region, credentials=credentials, - customer=customer_id, + customer_id=customer_id, + rules_to_scan=load_rules_to_scan(rules_to_scan) + ) + + +@job.command(cls=ViewCommand, name='submit_aws') +@tenant_option +@click.option('--ruleset', '-rs', type=str, required=False, + multiple=True, + help='Rulesets to scan. If not specified, all available by ' + 'license rulesets will be used') +@click.option('--region', '-r', type=str, required=False, + multiple=True, + help='Regions to scan. If not specified, ' + 'all active regions will be used') +@click.option('--rules_to_scan', required=False, multiple=True, type=str, + help='Rules that must be scanned. Ruleset must contain them. ' + 'You can specify some subpart of rule names. Custodian ' + 'will try to resolve the full names: aws-002 -> ' + 'ecc-aws-002-encryption... Also you can specify some part ' + 'that is common to multiple rules. All the them will be ' + 'resolved: postgresql -> [ecc-aws-001-postgresql..., ' + 'ecc-aws-002-postgresql...]. This CLI param can accept ' + 'both raw rule names and path to file with JSON list ' + 'of rules') +@click.option('--access_key', '-ak', type=str, help='AWS access key') +@click.option('--secret_key', '-sk', type=str, help='AWS secret key') +@click.option('--session_token', '-st', type=str, help='AWS session token') +@cli_response(attributes_order=attributes_order) +def submit_aws(ctx: ContextObj, tenant_name: str, + ruleset: tuple[str, ...], region: tuple[str, ...], + customer_id: str | None, rules_to_scan: tuple[str], + access_key, secret_key, session_token): + """ + Submits a job to scan an AWS account + """ + if access_key and secret_key and session_token: + creds = { + ENV_AWS_ACCESS_KEY_ID: access_key, + ENV_AWS_SECRET_ACCESS_KEY: secret_key, + ENV_AWS_SESSION_TOKEN: session_token + } + elif access_key and secret_key: + creds = { + ENV_AWS_ACCESS_KEY_ID: access_key, + ENV_AWS_SECRET_ACCESS_KEY: secret_key, + } + elif not any((access_key, secret_key, session_token)): + creds = None + else: + return response('either provide --access_key and --secret_key and ' + 'optionally --session_token or do not provide anything') + return ctx['api_client'].job_post( + tenant_name=tenant_name, + target_rulesets=ruleset, + target_regions=region, + credentials=creds, + customer_id=customer_id, + rules_to_scan=load_rules_to_scan(rules_to_scan) + ) + + +@job.command(cls=ViewCommand, name='submit_azure') +@tenant_option +@click.option('--ruleset', '-rs', type=str, required=False, + multiple=True, + help='Rulesets to scan. If not specified, all available by ' + 'license rulesets will be used') +@click.option('--rules_to_scan', required=False, multiple=True, type=str, + help='Rules that must be scanned. Ruleset must contain them. ' + 'You can specify some subpart of rule names. Custodian ' + 'will try to resolve the full names: aws-002 -> ' + 'ecc-aws-002-encryption... Also you can specify some part ' + 'that is common to multiple rules. All the them will be ' + 'resolved: postgresql -> [ecc-aws-001-postgresql..., ' + 'ecc-aws-002-postgresql...]. This CLI param can accept ' + 'both raw rule names and path to file with JSON list ' + 'of rules') +@click.option('--tenant_id', '-ti', type=str, help='Azure tenant id') +@click.option('--client_id', '-ci', type=str, help='Azure client id') +@click.option('--client_secret', '-cs', type=str, help='Azure client secret') +@click.option('--subscription_id', '-si', type=str, + help='Azure subscription id') +@cli_response(attributes_order=attributes_order) +def submit_azure(ctx: ContextObj, tenant_name: str, + ruleset: tuple[str, ...], region: tuple[str, ...], + customer_id: str | None, rules_to_scan: tuple[str], + tenant_id, client_id, client_secret, subscription_id): + """ + Submits a job to scan an Azure subscription + """ + if tenant_id and client_id and client_secret and subscription_id: + creds = { + ENV_AZURE_CLIENT_ID: client_id, + ENV_AZURE_TENANT_ID: tenant_id, + ENV_AZURE_CLIENT_SECRET: client_secret, + ENV_AZURE_SUBSCRIPTION_ID: subscription_id + } + elif tenant_id and client_id and client_secret: + creds = { + ENV_AZURE_CLIENT_ID: client_id, + ENV_AZURE_TENANT_ID: tenant_id, + ENV_AZURE_CLIENT_SECRET: client_secret, + } + elif not any((tenant_id, client_id, client_secret, subscription_id)): + creds = None + else: + return response( + 'Provide --tenant_id, --client_id, --client_secret ' + 'and optionally --subscription_id or do not provide anything' + ) + return ctx['api_client'].job_post( + tenant_name=tenant_name, + target_rulesets=ruleset, + target_regions=region, + credentials=creds, + customer_id=customer_id, + rules_to_scan=load_rules_to_scan(rules_to_scan) + ) + + +@job.command(cls=ViewCommand, name='submit_google') +@tenant_option +@click.option('--ruleset', '-rs', type=str, required=False, + multiple=True, + help='Rulesets to scan. If not specified, all available by ' + 'license rulesets will be used') +@click.option('--rules_to_scan', required=False, multiple=True, type=str, + help='Rules that must be scanned. Ruleset must contain them. ' + 'You can specify some subpart of rule names. Custodian ' + 'will try to resolve the full names: aws-002 -> ' + 'ecc-aws-002-encryption... Also you can specify some part ' + 'that is common to multiple rules. All the them will be ' + 'resolved: postgresql -> [ecc-aws-001-postgresql..., ' + 'ecc-aws-002-postgresql...]. This CLI param can accept ' + 'both raw rule names and path to file with JSON list ' + 'of rules') +@click.option('--application_credentials_path', '-acp', type=str, + help='Path to file with google credentials') +@cli_response(attributes_order=attributes_order) +def submit_google(ctx: ContextObj, tenant_name: str, + ruleset: tuple[str, ...], region: tuple[str, ...], + customer_id: str | None, rules_to_scan: tuple[str], + application_credentials_path: str): + """ + Submits a job to scan a Google project + """ + if application_credentials_path: + path = Path(application_credentials_path) + if not path.exists() or not path.is_file(): + return response('provided path must point to existing file') + with open(path, 'r') as file: + try: + creds = json.load(file) + except json.JSONDecodeError: + return response('cannot load json') + else: + creds = None + + return ctx['api_client'].job_post( + tenant_name=tenant_name, + target_rulesets=ruleset, + target_regions=region, + credentials=creds, + customer_id=customer_id, rules_to_scan=load_rules_to_scan(rules_to_scan) ) @@ -122,14 +293,16 @@ def submit(ctx: ContextObj, cloud: str, tenant_name: str, 'license rulesets will be used') @click.option('--token', '-t', type=str, required=False, help='Short-lived token to perform k8s scan with') -@customer_option @cli_response() def submit_k8s(ctx: ContextObj, platform_id: str, ruleset: tuple, - customer_id: Optional[str], token: Optional[str]): + customer_id: str | None, token: str | None): + """ + Submits a job for kubernetes cluster + """ return ctx['api_client'].k8s_job_post( platform_id=platform_id, target_rulesets=ruleset, - customer=customer_id, + customer_id=customer_id, token=token ) @@ -138,17 +311,17 @@ def submit_k8s(ctx: ContextObj, platform_id: str, ruleset: tuple, @click.option('--job_id', '-id', type=str, required=True, help='Job id to terminate') @cli_response() -def terminate(ctx: ContextObj, job_id: str): +def terminate(ctx: ContextObj, job_id: str, customer_id): """ Terminates Custodian Service Scan """ - return ctx['api_client'].job_delete(job_id=job_id) + return ctx['api_client'].job_delete(job_id=job_id, customer_id=customer_id) job.add_command(scheduled) -def load_rules_to_scan(rules_to_scan: Tuple[str, ...]) -> list: +def load_rules_to_scan(rules_to_scan: tuple[str, ...]) -> list: """ Each item of the tuple can be either a raw rule id, or path to a file containing json with ids or just a JSON string. This method resolves it diff --git a/c7n/c7ncli/group/job_event.py b/c7n/c7ncli/group/job_event.py index 35dba1998..4257157f7 100644 --- a/c7n/c7ncli/group/job_event.py +++ b/c7n/c7ncli/group/job_event.py @@ -1,9 +1,12 @@ import click -from c7ncli.group import cli_response, ViewCommand, response, ContextObj -from c7ncli.service.helpers import build_cloudtrail_records, \ - build_eventbridge_record, build_maestro_record, \ - normalize_lists +from c7ncli.group import ContextObj, ViewCommand, cli_response, response +from c7ncli.service.helpers import ( + build_cloudtrail_records, + build_eventbridge_record, + build_maestro_record, + normalize_lists, +) @click.group(name='event') @@ -29,7 +32,7 @@ def event(): @cli_response() def cloudtrail(ctx: ContextObj, cloud_identifier: tuple, region: tuple, event_source: tuple, event_name: tuple, - wrap_in_eventbridge: bool): + wrap_in_eventbridge: bool, customer_id): """ Command to simulate event-driven request from CloudTrail-based event-listener. Use it just to check whether @@ -50,33 +53,31 @@ def cloudtrail(ctx: ContextObj, cloud_identifier: tuple, region: tuple, region=region[0], detail=rec )) - return ctx['api_client'].event_action('AWS', events) + return ctx['api_client'].event_action( + version='1.0.0', + vendor='AWS', + events=events, + customer_id=customer_id + ) @event.command(cls=ViewCommand, name='maestro') @click.option('--event_action', '-ea', type=click.Choice( - ['COMMAND', 'CREATE', 'DELETE', 'DISABLE', 'UPDATE']), + ('COMMAND', 'CREATE', 'DELETE', 'DISABLE', 'UPDATE')), required=True, multiple=True) -@click.option('--group', type=click.Choice(['MANAGEMENT']), required=True, +@click.option('--group', type=click.Choice(('MANAGEMENT', )), required=True, default='MANAGEMENT', show_default=True) -@click.option('--sub_group', type=click.Choice(['INSTANCE']), required=True, +@click.option('--sub_group', type=click.Choice(('INSTANCE', )), required=True, default='INSTANCE', show_default=True) @click.option('--tenant_name', '-tn', type=str, required=True, multiple=True) -@click.option('--cloud', '-c', type=click.Choice(['AZURE', 'GOOGLE']), +@click.option('--cloud', '-c', type=click.Choice(('AZURE', 'GOOGLE')), required=True) @cli_response() def maestro(ctx: ContextObj, event_action: tuple, group: str, sub_group: str, - tenant_name: tuple, cloud: str): + tenant_name: tuple, cloud: str, customer_id): """ Builds maestro audit event - :param ctx: - :param event_action: - :param group: - :param sub_group: - :param tenant_name: - :param cloud: - :return: """ lists = [list(event_action), [group, ], [sub_group, ], list(tenant_name), [cloud, ]] @@ -90,7 +91,12 @@ def maestro(ctx: ContextObj, event_action: tuple, group: str, sub_group: str, tenant_name=lists[3][i], cloud=lists[4][i] )) - return ctx['api_client'].event_action('MAESTRO', events) + return ctx['api_client'].event_action( + version='1.0.0', + vendor='MAESTRO', + events=events, + customer_id=customer_id + ) @event.command(cls=ViewCommand, name='eventbridge') @@ -108,7 +114,7 @@ def maestro(ctx: ContextObj, event_action: tuple, group: str, sub_group: str, help='CloudTrail event name to simulate', multiple=True) @cli_response() def eventbridge(ctx: ContextObj, account: tuple, region: tuple, source: tuple, - detail_type: tuple): + detail_type: tuple, customer_id): """ Command to simulate event-driven request from EventBridge-based event-listener. Use it just to check whether diff --git a/c7n/c7ncli/group/job_scheduled.py b/c7n/c7ncli/group/job_scheduled.py index 6a31b73c5..18c94e028 100644 --- a/c7n/c7ncli/group/job_scheduled.py +++ b/c7n/c7ncli/group/job_scheduled.py @@ -1,8 +1,8 @@ import click -from c7ncli.group import ViewCommand, tenant_option, customer_option, \ - cli_response, ContextObj, response -from c7ncli.service.constants import PARAM_NAME, PARAM_CUSTOMER_NAME, \ - PARAM_TENANT, PARAM_ACCOUNT + +from c7ncli.group import ContextObj, ViewCommand, cli_response, response, tenant_option + +attributes_order = 'name', 'tenant_name', 'enabled', 'schedule' @click.group(name='scheduled') @@ -26,21 +26,19 @@ def scheduled(): @click.option('--name', '-n', type=str, required=False, help='Name for the scheduled job. Must be unique. If not ' 'given, will be generated automatically') -@customer_option -@cli_response(attributes_order=[PARAM_NAME, PARAM_CUSTOMER_NAME, - PARAM_TENANT, PARAM_ACCOUNT]) +@cli_response(attributes_order=attributes_order) def add(ctx: ContextObj, tenant_name, schedule, ruleset, region, name, customer_id): """ Registers a scheduled job """ return ctx['api_client'].scheduled_job_post( - tenant=tenant_name, + tenant_name=tenant_name, schedule=schedule, - target_ruleset=ruleset, - target_region=region, + target_rulesets=ruleset, + target_regions=region, name=name, - customer=customer_id + customer_id=customer_id ) @@ -48,9 +46,7 @@ def add(ctx: ContextObj, tenant_name, schedule, @click.option('--name', '-n', type=str, required=False, help='Scheduled job name to remove') @tenant_option -@customer_option -@cli_response(attributes_order=[PARAM_NAME, PARAM_CUSTOMER_NAME, - PARAM_TENANT, PARAM_ACCOUNT]) +@cli_response(attributes_order=attributes_order) def describe(ctx: ContextObj, name, tenant_name, customer_id): """ Describes registered scheduled jobs @@ -59,23 +55,23 @@ def describe(ctx: ContextObj, name, tenant_name, customer_id): return response('You don`t have to specify other attributes if' ' \'--name\' is specified') if name: - return ctx['api_client'].scheduled_job_get(name) + return ctx['api_client'].scheduled_job_get(name, customer_id=customer_id) return ctx['api_client'].scheduled_job_query( tenant_name=tenant_name, - customer=customer_id + customer_id=customer_id ) @scheduled.command(cls=ViewCommand, name='delete') @click.option('--name', '-n', type=str, required=True, help='Scheduled job name to remove') -@customer_option @cli_response() def delete(ctx: ContextObj, name, customer_id): """ Removes a scheduled job """ - return ctx['api_client'].scheduled_job_delete(name=name) + return ctx['api_client'].scheduled_job_delete(name=name, + customer_id=customer_id) @scheduled.command(cls=ViewCommand, name='update') @@ -86,9 +82,7 @@ def delete(ctx: ContextObj, name, customer_id): 'rate(2 minutes)') @click.option('--enabled', '-e', type=bool, required=False, help='Param to enable or disable the job temporarily') -@customer_option -@cli_response(attributes_order=[PARAM_NAME, PARAM_CUSTOMER_NAME, - PARAM_TENANT, PARAM_ACCOUNT]) +@cli_response(attributes_order=attributes_order) def update(ctx: ContextObj, name, schedule, enabled, customer_id): """ Updates an existing scheduled job @@ -96,5 +90,8 @@ def update(ctx: ContextObj, name, schedule, enabled, customer_id): if all(param is None for param in (enabled, schedule)): return response('You must specify at least one parameter to update.') return ctx['api_client'].scheduled_job_update( - name=name, schedule=schedule, enabled=enabled, customer=customer_id + name=name, + schedule=schedule, + enabled=enabled, + customer_id=customer_id ) diff --git a/c7n/c7ncli/group/license.py b/c7n/c7ncli/group/license.py index cc75097bf..ab508e8ae 100644 --- a/c7n/c7ncli/group/license.py +++ b/c7n/c7ncli/group/license.py @@ -1,7 +1,15 @@ import click -from c7ncli.group import cli_response, ViewCommand, ContextObj, customer_option -from c7ncli.service.constants import PARAM_CUSTOMERS, PARAM_LICENSE_HASH_KEY, \ - PARAM_RULESET_IDS, PARAM_EXPIRATION, PARAM_LATEST_SYNC + +from c7ncli.group import ( + ContextObj, + ViewCommand, + build_tenant_option, + cli_response, + response, +) +from c7ncli.service.constants import AWS, AZURE, GOOGLE, KUBERNETES + +attributes_order = 'license_key', 'ruleset_ids', 'expiration', 'latest_sync' @click.group(name='license') @@ -12,53 +20,139 @@ def license(): @license.command(cls=ViewCommand, name='describe') @click.option('--license_key', '-lk', type=str, required=False, help='License key to describe') -@customer_option @cli_response() def describe(ctx: ContextObj, license_key, customer_id): """ Describes a Custodian Service Licenses """ - return ctx['api_client'].license_get( - license_key=license_key, customer=customer_id - ) + if license_key: + return ctx['api_client'].license_get(license_key, + customer_id=customer_id) + return ctx['api_client'].license_query(customer_id=customer_id) -# currently obsolete. Don't try to uncomment, the endpoint is disabled -# @license.command(cls=ViewCommand, name='add') +@license.command(cls=ViewCommand, name='add') @click.option('--tenant_license_key', '-tlk', type=str, required=True, help='License key to create') -@click.option('--tenant_name', '-tn', type=str, - help='Tenant name to attach the license to', required=True) +@click.option('--description', '-d', type=str, required=False, + help='Your description for this license') @cli_response() -def add(ctx: ContextObj, tenant_license_key, tenant_name): +def add(ctx: ContextObj, tenant_license_key: str, description: str | None, + customer_id: str | None): """ - Adds the licensed rulesets to a specific tenant if allowed. + Adds a license from License Manager to the system. After performing this + action licensed rulesets will appear and will be ready to use """ - return ctx['api_client'].license_post(tenant_name, tenant_license_key) + return ctx['api_client'].license_post( + tenant_license_key=tenant_license_key, + customer_id=customer_id, + description=description + ) @license.command(cls=ViewCommand, name='delete') @click.option('--license_key', '-lk', type=str, required=True, help='License key to delete') -@customer_option @cli_response() def delete(ctx: ContextObj, license_key, customer_id): """ Deletes Custodian Service Licenses """ - return ctx['api_client'].license_delete( - customer_name=customer_id, - license_key=license_key) + return ctx['api_client'].license_delete(license_key=license_key, + customer_id=customer_id) @license.command(cls=ViewCommand, name='sync') @click.option('--license_key', '-lk', type=str, required=True, help='License key to synchronize') -@cli_response(attributes_order=[ - PARAM_LICENSE_HASH_KEY, PARAM_CUSTOMERS, PARAM_RULESET_IDS, - PARAM_EXPIRATION, PARAM_LATEST_SYNC]) -def sync(ctx: ContextObj, license_key=None): +@cli_response(attributes_order=attributes_order) +def sync(ctx: ContextObj, license_key, customer_id): """ Synchronizes Custodian Service Licenses """ - return ctx['api_client'].license_sync(license_key=license_key) + return ctx['api_client'].license_sync(license_key, customer_id=customer_id) + + +@license.command(cls=ViewCommand, name='activate') +@click.option('--license_key', '-lk', type=str, required=True, + help='License key') +@build_tenant_option(multiple=True) +@click.option('--all_tenants', is_flag=True, + help='Whether to activate integration for all tenants') +@click.option('--clouds', '-cl', + type=click.Choice((AWS, AZURE, GOOGLE, KUBERNETES)), + multiple=True, + help='Tenant clouds to activate this dojo for. ' + 'Can be specific together with --all_tenants flag') +@click.option('--exclude_tenant', '-et', type=str, multiple=True, + help='Tenants to exclude for this integration. ' + 'Can be specified together with --all_tenants flag') +@cli_response() +def activate(ctx: ContextObj, license_key: str, tenant_name: tuple[str, ...], + all_tenants: bool, clouds: tuple[str], + exclude_tenant: tuple[str, ...], customer_id: str | None): + """ + Activates a concrete license for a specific set of tenants. + Each activation overrides the existing one + """ + if tenant_name and any((all_tenants, clouds, exclude_tenant)): + return response('Do not provide --all_tenants, --clouds or ' + '--exclude_tenants if --tenant_name given') + if not all_tenants and not tenant_name: + return response('Either --all_tenants or --tenant_name must be given') + if (clouds or exclude_tenant) and not all_tenants: + return response('set --all_tenants if you provide --clouds or ' + '--exclude_tenants') + return ctx['api_client'].license_activate( + license_key=license_key, + tenant_names=tenant_name, + all_tenants=all_tenants, + clouds=clouds, + exclude_tenants=exclude_tenant, + customer_id=customer_id + ) + + +@license.command(cls=ViewCommand, name='deactivate') +@click.option('--license_key', '-lk', type=str, required=True, + help='License key') +@cli_response() +def deactivate(ctx: ContextObj, license_key: str, customer_id): + """ + Deactivates a concrete license for a specific set of tenants + """ + return ctx['api_client'].license_deactivate(license_key=license_key, + customer_id=customer_id) + + +@license.command(cls=ViewCommand, name='get_activation') +@click.option('--license_key', '-lk', type=str, required=True, + help='License key') +@cli_response() +def get_activation(ctx: ContextObj, license_key: str, customer_id): + """ + Returns license activation + """ + return ctx['api_client'].license_get_activation(license_key=license_key, + customer_id=customer_id) + + +@license.command(cls=ViewCommand, name='update_activation') +@click.option('--license_key', '-lk', type=str, required=True, + help='License key') +@click.option('--add_tenant', '-at', type=str, multiple=True, + help='Tenants to activate') +@click.option('--exclude_tenant', '-et', type=str, multiple=True, + help='Tenants to deactivate') +@cli_response() +def update_activation(ctx: ContextObj, license_key, customer_id, add_tenant, + exclude_tenant): + """ + Allows to add and remove specific tenants from the activation + """ + return ctx['api_client'].license_update_activation( + license_key=license_key, + add_tenants=add_tenant, + remove_tenants=exclude_tenant, + customer_id=customer_id + ) diff --git a/c7n/c7ncli/group/meta.py b/c7n/c7ncli/group/meta.py index 2374374e2..6c942e167 100644 --- a/c7n/c7ncli/group/meta.py +++ b/c7n/c7ncli/group/meta.py @@ -1,5 +1,6 @@ import click -from c7ncli.group import cli_response, ViewCommand, ContextObj + +from c7ncli.group import ContextObj, ViewCommand, cli_response @click.group(name='meta') @@ -9,7 +10,7 @@ def meta(): @meta.command(cls=ViewCommand, name='update_standards') @cli_response() -def update_standards(ctx: ContextObj): +def update_standards(ctx: ContextObj, customer_id): """ Triggers a metrics update for Custodian Service reports. Report data will contain data up to the time when the trigger was executed @@ -19,7 +20,7 @@ def update_standards(ctx: ContextObj): @meta.command(cls=ViewCommand, name='update_mappings') @cli_response() -def update_mappings(ctx: ContextObj): +def update_mappings(ctx: ContextObj, customer_id): """ Execution status of the last metrics update """ @@ -28,8 +29,8 @@ def update_mappings(ctx: ContextObj): @meta.command(cls=ViewCommand, name='update_meta') @cli_response() -def update_meta(ctx: ContextObj): +def update_meta(ctx: ContextObj, customer_id): """ Execution status of the last metrics update """ - return ctx['api_client'].update_meta() \ No newline at end of file + return ctx['api_client'].update_meta() diff --git a/c7n/c7ncli/group/metrics.py b/c7n/c7ncli/group/metrics.py index 015f4b1a1..2b8b0373b 100644 --- a/c7n/c7ncli/group/metrics.py +++ b/c7n/c7ncli/group/metrics.py @@ -1,5 +1,6 @@ import click -from c7ncli.group import cli_response, ViewCommand, ContextObj + +from c7ncli.group import ContextObj, ViewCommand, cli_response @click.group(name='metrics') @@ -9,7 +10,7 @@ def metrics(): @metrics.command(cls=ViewCommand, name='update') @cli_response() -def update(ctx: ContextObj): +def update(ctx: ContextObj, customer_id): """ Triggers a metrics update for Custodian Service reports. Report data will contain data up to the time when the trigger was executed @@ -18,9 +19,19 @@ def update(ctx: ContextObj): @metrics.command(cls=ViewCommand, name='status') +@click.option('--from_date', '-from', type=str, + help='Query metrics statuses from this date. Accepts date ISO ' + 'string. Example: 2023-10-20') +@click.option('--to_date', '-to', type=str, + help='Query metrics statuses till this date. Accepts date ISO ' + 'string. Example: 2023-12-29') @cli_response() -def status(ctx: ContextObj): +def status(ctx: ContextObj, from_date: str, to_date: str, customer_id): """ Execution status of the last metrics update """ - return ctx['api_client'].metrics_status() + params = { + 'from': from_date, + 'to': to_date + } + return ctx['api_client'].metrics_status(**params) diff --git a/c7n/c7ncli/group/parent.py b/c7n/c7ncli/group/parent.py deleted file mode 100644 index 7ffaaef48..000000000 --- a/c7n/c7ncli/group/parent.py +++ /dev/null @@ -1,96 +0,0 @@ -from typing import Optional, Tuple - -import click - -from c7ncli.group import cli_response, ViewCommand, ContextObj, \ - customer_option, tenant_option -from c7ncli.service.constants import AWS, AZURE, GOOGLE, ParentScope - -parent_type_option = click.option( - '--type', '-t', - type=click.Choice(['CUSTODIAN', 'CUSTODIAN_LICENSES', 'SIEM_DEFECT_DOJO', - 'CUSTODIAN_ACCESS']), - required=True, help='Parent type' -) - -ORDER = ['parent_id', 'application_id', 'customer_id'] - - -@click.group(name='parent') -def parent(): - """Manages Custodian Service Parent Entities""" - - -@parent.command(cls=ViewCommand, name='add') -@click.option('--application_id', '-aid', type=str, required=False, - help='Id of an application to connect to the parent') -@parent_type_option -@click.option('--description', '-d', type=str, required=False, - help='Description for the parent') -@click.option('--cloud', '-c', - type=click.Choice([AWS, AZURE, GOOGLE]), - help='Cloud to connect the parent to') -@click.option('--scope', '-sc', - type=click.Choice(ParentScope.iter()), - help='Tenants scope for the parent') -@click.option('--rules_to_exclude', '-rte', multiple=True, - help='Rules to exclude for the scope of tenants') -@tenant_option -@customer_option -@cli_response(ORDER) -def add(ctx: ContextObj, customer_id: Optional[str], - cloud: Optional[str], **kwargs): - """ - Creates parent within the customer - """ - kwargs.update(customer=customer_id) - kwargs.update(cloud=cloud) - return ctx['api_client'].parent_post(**kwargs) - - -@parent.command(cls=ViewCommand, name='update') -@click.option('--parent_id', '-pid', type=str, required=True, - help='Parent id to update') -@click.option('--application_id', '-aid', type=str, - help='Id of an application to connect to the parent') -@click.option('--description', '-d', type=str, - help='Description for the parent') -@click.option('--rules_to_exclude', '-rte', type=str, multiple=True, - help='Rules to exclude for tenant') -@click.option('--rules_to_include', '-rti', type=str, multiple=True, - help='Rules to include for tenant') -@customer_option -@cli_response(ORDER) -def update(ctx: ContextObj, customer_id: Optional[str], **kwargs): - """ - Updates parent within the customer - """ - kwargs.update(customer=customer_id) - return ctx['api_client'].parent_patch(**kwargs) - - -@parent.command(cls=ViewCommand, name='describe') -@click.option('--parent_id', '-pid', type=str, - help='Parent id to describe a concrete parent') -@customer_option -@cli_response(ORDER) -def describe(ctx: ContextObj, parent_id, customer_id): - """ - Describes customer's parents - """ - if parent_id: - return ctx['api_client'].parent_get(parent_id) - return ctx['api_client'].parent_list( - customer=customer_id - ) - - -@parent.command(cls=ViewCommand, name='delete') -@click.option('--parent_id', '-pid', type=str, required=True, - help='Parent id to describe a concrete parent') -@cli_response() -def delete(ctx: ContextObj, parent_id): - """ - Deletes customer's parent by id - """ - return ctx['api_client'].parent_delete(parent_id) diff --git a/c7n/c7ncli/group/platform_k8s.py b/c7n/c7ncli/group/platform_k8s.py index f77c25687..b8ea4fe44 100644 --- a/c7n/c7ncli/group/platform_k8s.py +++ b/c7n/c7ncli/group/platform_k8s.py @@ -2,82 +2,63 @@ import click -from c7ncli.group import cli_response, ViewCommand, build_tenant_option, \ - ContextObj +from c7ncli.group import ContextObj, ViewCommand, build_tenant_option, cli_response @click.group(name='k8s') def k8s(): - """Manages Kubernetes Platform configuration """ + """Manages kubernetes platforms""" -@k8s.command(cls=ViewCommand, name='create_native') +@k8s.command(cls=ViewCommand, name='create') @build_tenant_option(required=True) @click.option('-n', '--name', type=str, required=True, help='Cluster name') -@click.option('-e', '--endpoint', type=str, required=True, help='K8s endpoint') -@click.option('-ca', '--certificate_authority', type=str, required=True, +@click.option('-t', '--type', required=True, + type=click.Choice(('SELF_MANAGED', 'EKS', 'AKS', 'GKS')), + help='Cluster type') +@click.option('-r', '--region', type=str, required=False, + help='Cluster region in case the cluster is bound to a cloud') +@click.option('-d', '--description', type=str, required=False, + help='Eks platform description') +@click.option('-e', '--endpoint', type=str, required=False, + help='K8s endpoint') +@click.option('-ca', '--certificate_authority', type=str, required=False, help='Certificate authority base64 encoded string') -@click.option('-t', '--token', type=str, required=False, +@click.option('-T', '--token', type=str, required=False, help='Long lived token. Short-lived tokens will ' 'be generated base on this one') -@click.option('-d', '--description', type=str, required=False, - help='Eks platform description') @cli_response() -def create_native(ctx: ContextObj, tenant_name: str, name: str, endpoint: str, - certificate_authority: str, token: Optional[str], - description: Optional[str]): - return ctx['api_client'].platform_k8s_create_native( +def create(ctx: ContextObj, tenant_name: str, name: str, region: Optional[str], + description: Optional[str], type: str, endpoint: Optional[str], + certificate_authority: Optional[str], token: Optional[str], + customer_id): + return ctx['api_client'].platform_k8s_create( tenant_name=tenant_name, name=name, + region=region, + type=type, + description=description, endpoint=endpoint, certificate_authority=certificate_authority, token=token, - description=description - ) - - -@k8s.command(cls=ViewCommand, name='create_eks') -@build_tenant_option(required=True) -@click.option('-n', '--name', type=str, required=True, help='Cluster name') -@click.option('-r', '--region', type=str, required=True, - help='AWS region where eks cluster is situated') -@click.option('-aid', '--application_id', type=str, required=True, - help='ID of application with AWS credentials that have ' - 'access to the cluster') -@click.option('-d', '--description', type=str, required=False, - help='Eks platform description') -@cli_response() -def create_eks(ctx: ContextObj, tenant_name: str, name: str, region: str, - application_id: str, description: Optional[str]): - return ctx['api_client'].platform_eks_create_native( - tenant_name=tenant_name, - name=name, - region=region, - application_id=application_id, - description=description, + customer_id=customer_id ) @k8s.command(cls=ViewCommand, name='describe') @build_tenant_option() @cli_response() -def describe(ctx: ContextObj, tenant_name: Optional[str]): +def describe(ctx: ContextObj, tenant_name: Optional[str], customer_id): return ctx['api_client'].platform_k8s_list( tenant_name=tenant_name, + customer_id=customer_id ) -@k8s.command(cls=ViewCommand, name='delete_native') -@click.option('-pid', '--platform_id', type=str, required=True, - help='Platform id') -@cli_response() -def delete_native(ctx: ContextObj, platform_id: str): - return ctx['api_client'].platform_k8s_delete_native(platform_id) - - -@k8s.command(cls=ViewCommand, name='delete_eks') +@k8s.command(cls=ViewCommand, name='delete') @click.option('-pid', '--platform_id', type=str, required=True, help='Platform id') @cli_response() -def delete_eks(ctx: ContextObj, platform_id: str): - return ctx['api_client'].platform_k8s_delete_eks(platform_id) +def delete(ctx: ContextObj, platform_id: str, customer_id): + return ctx['api_client'].platform_k8s_delete(platform_id, + customer_id=customer_id) diff --git a/c7n/c7ncli/group/policy.py b/c7n/c7ncli/group/policy.py index 93451b858..aae68fa0c 100644 --- a/c7n/c7ncli/group/policy.py +++ b/c7n/c7ncli/group/policy.py @@ -3,10 +3,9 @@ import click -from c7ncli.group import cli_response, ViewCommand, response, \ - ContextObj, customer_option -from c7ncli.service.constants import PARAM_CUSTOMER, PARAM_NAME, \ - PARAM_PERMISSIONS +from c7ncli.group import ContextObj, ViewCommand, cli_response, response + +attributes_order = 'name', 'permissions', 'customer' @click.group(name='policy') @@ -15,38 +14,51 @@ def policy(): @policy.command(cls=ViewCommand, name='describe') -@click.option('--policy_name', '-name', type=str, +@click.option('--name', '-n', type=str, help='Policy name to describe.') -@customer_option -@cli_response(attributes_order=[PARAM_CUSTOMER, PARAM_NAME, PARAM_PERMISSIONS]) -def describe(ctx: ContextObj, customer_id, policy_name=None): +@cli_response(attributes_order=attributes_order) +def describe(ctx: ContextObj, customer_id, name): """ Describes Custodian Service policies of a customer """ - return ctx['api_client'].policy_get( - customer_display_name=customer_id, - policy_name=policy_name + if name: + return ctx['api_client'].policy_get( + name=name, + customer_id=customer_id, + ) + return ctx['api_client'].policy_query( + customer_id=customer_id, ) @policy.command(cls=ViewCommand, name='add') -@click.option('--policy_name', '-name', type=str, required=True, +@click.option('--name', '-n', type=str, required=True, help='Policy name to create') @click.option('--permission', '-p', multiple=True, help='List of permissions to attach to the policy') @click.option('--path_to_permissions', '-path', required=False, help='Local path to .json file that contains list of ' 'permissions to attach to the policy') -@customer_option -@cli_response(attributes_order=[PARAM_CUSTOMER, PARAM_NAME, PARAM_PERMISSIONS]) -def add(ctx: ContextObj, customer_id, policy_name, permission, - path_to_permissions): +@click.option('--permissions_admin', '-admin', is_flag=True, + help='Whether to add all permissions to this policy') +@click.option('--effect', '-ef', type=click.Choice(('allow', 'deny')), + required=True, help='That this policy will do') +@click.option('--tenant', '-t', type=str, multiple=True, + help='Permission will be allowed or denied for these tenants. ' + 'Specify tenant name') +@click.option('--description', '-d', type=str, required=True, + help='Description for this policy') +@cli_response(attributes_order=attributes_order) +def add(ctx: ContextObj, customer_id, name, permission, + path_to_permissions, permissions_admin, effect, tenant, description): """ Creates a Custodian Service policy for a customer """ - if not permission and not path_to_permissions: - return response('--permission or --path_to_permissions ' - 'must be provided') + if not permission and not path_to_permissions and not permissions_admin: + return response( + '--permission or --path_to_permissions or --permissions_admin ' + 'must be provided' + ) permissions = list(permission) if path_to_permissions: path = Path(path_to_permissions) @@ -60,67 +72,63 @@ def add(ctx: ContextObj, customer_id, policy_name, permission, permissions.extend(data) return ctx['api_client'].policy_post( - name=policy_name, + name=name, permissions=permissions, - customer=customer_id, + customer_id=customer_id, + permissions_admin=permissions_admin, + effect=effect, + tenants=tenant, + description=description ) @policy.command(cls=ViewCommand, name='update') -@click.option('--policy_name', '-name', type=str, required=True) +@click.option('--name', '-n', type=str, required=True) @click.option('--attach_permission', '-ap', multiple=True, required=False, help='Names of permissions to attach to the policy') @click.option('--detach_permission', '-dp', multiple=True, required=False, help='Names of permissions to detach from the policy') -@customer_option -@cli_response(attributes_order=[PARAM_CUSTOMER, PARAM_NAME, PARAM_PERMISSIONS]) -def update(ctx: ContextObj, customer_id, policy_name, attach_permission, - detach_permission): +@click.option('--effect', '-ef', type=click.Choice(('allow', 'deny')), + help='That this policy will do') +@click.option('--add_tenant', '-at', type=str, multiple=True, + help='Add these tenants. Specify tenant name') +@click.option('--remove_tenant', '-rt', type=str, multiple=True, + help='Remove these tenants. Specify tenant name') +@click.option('--description', '-d', type=str, + help='Description for this policy') +@cli_response(attributes_order=attributes_order) +def update(ctx: ContextObj, customer_id, name, attach_permission, + detach_permission, effect, add_tenant, remove_tenant, description): """ Updates permission-list within a Custodian Service policy """ - if not attach_permission and not detach_permission: - return response('At least one of the following arguments must be ' - 'provided: attach_permission, detach_permission') + if not attach_permission and not detach_permission and not effect and not add_tenant and not remove_tenant and not description: + return response('At least one parameter to update must be provided') return ctx['api_client'].policy_patch( - customer=customer_id, - name=policy_name, + name=name, + customer_id=customer_id, permissions_to_attach=attach_permission, - permissions_to_detach=detach_permission + permissions_to_detach=detach_permission, + effect=effect, + tenants_to_add=add_tenant, + tenants_to_remove=remove_tenant, + description=description ) @policy.command(cls=ViewCommand, name='delete') -@click.option('--policy_name', '-name', type=str, required=True, +@click.option('--name', '-n', type=str, required=True, help='Policy name to delete') -@customer_option @cli_response() -def delete(ctx: ContextObj, customer_id, policy_name): +def delete(ctx: ContextObj, customer_id, name): """ Deletes a Custodian Service policy of a customer """ - if policy_name: - policy_name = policy_name.lower() return ctx['api_client'].policy_delete( - customer_display_name=customer_id, - policy_name=policy_name.lower()) - - -@policy.command(cls=ViewCommand, name='clean_cache') -@click.option('--policy_name', '-name', type=str, - help='Policy name to clean from cache. If not specified, ' - 'all policies cache within the customer is cleaned') -@customer_option -@cli_response() -def clean_cache(ctx: ContextObj, policy_name, customer_id): - """ - Clears out cached Custodian Service policies within Lambda - """ - return ctx['api_client'].policy_clean_cache( - customer=customer_id, - name=policy_name + name=name, + customer_id=customer_id, ) diff --git a/c7n/c7ncli/group/report.py b/c7n/c7ncli/group/report.py index a1ca2d1cd..4dec3e5f2 100644 --- a/c7n/c7ncli/group/report.py +++ b/c7n/c7ncli/group/report.py @@ -1,16 +1,15 @@ import click -from c7ncli.group import cli_response, ViewCommand, ContextObj, \ - build_tenant_option -from c7ncli.group import customer_option -from c7ncli.group import tenant_display_name_option +from c7ncli.group import ContextObj, ViewCommand, build_tenant_option, cli_response, tenant_display_name_option, response from c7ncli.group.report_compliance import compliance from c7ncli.group.report_details import details from c7ncli.group.report_digests import digests from c7ncli.group.report_errors import errors +from c7ncli.group.report_findings import findings from c7ncli.group.report_push import push -from c7ncli.group.report_rules import rules from c7ncli.group.report_resource import resource +from c7ncli.group.report_rules import rules +from c7ncli.group.report_raw import raw START_END_DATES_MISSING_MESSAGE = 'At least either --start_date and' \ ' --end_date should be specified' @@ -22,73 +21,140 @@ def report(): """Manages Custodian Service reports""" + @report.command(cls=ViewCommand, name='operational') -@build_tenant_option(required=True) +@build_tenant_option(required=True, multiple=True) @click.option('--report_types', '-rt', multiple=True, type=click.Choice( - ['OVERVIEW', 'RESOURCES', 'COMPLIANCE', 'RULE', 'ATTACK_VECTOR', - 'FINOPS']), + ('OVERVIEW', 'RESOURCES', 'COMPLIANCE', 'RULE', 'ATTACK_VECTOR', + 'FINOPS', 'KUBERNETES')), required=False, help='Report type') -@customer_option -@cli_response(attributes_order=[]) -def operational(ctx: ContextObj, tenant_name, report_types, customer_id): +@click.option('--receiver', '-r', multiple=True, type=str, + help='Emails that will receive this notification') +@cli_response() +def operational(ctx: ContextObj, tenant_name, report_types, customer_id, + receiver): """ Retrieves operational-level reports """ - return ctx['api_client'].operational_report_get( - tenant_name=tenant_name, - report_types=', '.join(report_types) if report_types else None, - customer=customer_id + res = ctx['api_client'].operational_report_post( + tenant_names=tenant_name, + types=report_types, + receivers=receiver, + customer_id=customer_id + ) + if not res.ok: + return res + report_id = res.data.get('data', {}).get('report_id') + if not report_id: + return res + return response( + f'To see job status, call the `c7n report status -id {report_id}`' ) @report.command(cls=ViewCommand, name='project') @tenant_display_name_option @click.option('--report_types', '-rt', multiple=True, type=click.Choice( - ['OVERVIEW', 'RESOURCES', 'COMPLIANCE', 'ATTACK_VECTOR', 'FINOPS']), + ('OVERVIEW', 'RESOURCES', 'COMPLIANCE', 'ATTACK_VECTOR', 'FINOPS')), required=False, help='Report type') -@customer_option -@cli_response(attributes_order=[]) -def project(ctx: ContextObj, report_types, tenant_display_name, customer_id): +@click.option('--receiver', '-r', multiple=True, type=str, + help='Emails that will receive this notification') +@cli_response() +def project(ctx: ContextObj, report_types, tenant_display_name, customer_id, + receiver): """ Retrieves project-level reports for a tenant group """ - return ctx['api_client'].project_report_get( - tenant_display_name=tenant_display_name, - report_types=', '.join(report_types) if report_types else None, - customer=customer_id + res = ctx['api_client'].project_report_post( + tenant_display_names=[tenant_display_name], + types=report_types, + receivers=receiver, + customer_id=customer_id + ) + if not res.ok: + return res + report_id = res.data.get('data', {}).get('report_id') + if not report_id: + return res + return response( + f'To see job status, call the `c7n report status -id {report_id}`' ) @report.command(cls=ViewCommand, name='department') @click.option('--report_types', '-rt', multiple=True, type=click.Choice( - ['TOP_RESOURCES_BY_CLOUD', 'TOP_TENANTS_RESOURCES', + ('TOP_RESOURCES_BY_CLOUD', 'TOP_TENANTS_RESOURCES', 'TOP_TENANTS_COMPLIANCE', 'TOP_COMPLIANCE_BY_CLOUD', - 'TOP_TENANTS_ATTACKS', 'TOP_ATTACK_BY_CLOUD']), required=False, + 'TOP_TENANTS_ATTACKS', 'TOP_ATTACK_BY_CLOUD')), required=False, help='Report type') -@customer_option -@cli_response(attributes_order=[]) +@cli_response() def department(ctx: ContextObj, report_types, customer_id): """ Retrieves department-level reports """ - return ctx['api_client'].department_report_get( - report_types=', '.join(report_types) if report_types else None, - customer=customer_id) + res = ctx['api_client'].department_report_post( + types=report_types, + customer_id=customer_id + ) + if not res.ok: + return res + report_id = res.data.get('data', {}).get('report_id') + if not report_id: + return res + return response( + f'To see job status, call the `c7n report status -id {report_id}`' + ) @report.command(cls=ViewCommand, name='clevel') @click.option('--report_types', '-rt', multiple=True, type=click.Choice( - ['OVERVIEW', 'COMPLIANCE', 'ATTACK_VECTOR']), required=False, + ('OVERVIEW', 'COMPLIANCE', 'ATTACK_VECTOR')), required=False, help='Report type') -@customer_option -@cli_response(attributes_order=[]) +@cli_response() def clevel(ctx: ContextObj, report_types, customer_id): """ Retrieves c-level reports """ - return ctx['api_client'].c_level_report_get( - report_types=', '.join(report_types) if report_types else None, - customer=customer_id) + res = ctx['api_client'].c_level_report_post( + types=report_types, + customer_id=customer_id + ) + if not res.ok: + return res + report_id = res.data.get('data', {}).get('report_id') + if not report_id: + return res + return response( + f'To see job status, call the `c7n report status -id {report_id}`' + ) + + +@report.command(cls=ViewCommand, name='diagnostic') +@cli_response() +def diagnostic(ctx: ContextObj, customer_id): + """ + Retrieves diagnostic reports + """ + return ctx['api_client'].diagnostic_report_get( + customer_id=customer_id + ) + + +@report.command(cls=ViewCommand, name='status') +@click.option('--job_id', '-id', type=str, required=True, + help='Report job type') +@click.option('--full', '-f', is_flag=True, + help='Flag to list all attempts related to specified ID') +@cli_response() +def status(ctx: ContextObj, job_id, full, customer_id): + """ + Retrieves report status by its ID + """ + return ctx['api_client'].report_status_get( + job_id=job_id, + complete=full, + customer_id=customer_id + ) report.add_command(digests) @@ -98,3 +164,5 @@ def clevel(ctx: ContextObj, report_types, customer_id): report.add_command(rules) report.add_command(push) report.add_command(resource) +report.add_command(findings) +report.add_command(raw) diff --git a/c7n/c7ncli/group/report_compliance.py b/c7n/c7ncli/group/report_compliance.py index 43bc22c44..48655f6aa 100644 --- a/c7n/c7ncli/group/report_compliance.py +++ b/c7n/c7ncli/group/report_compliance.py @@ -1,7 +1,13 @@ import click -from c7ncli.group import cli_response, ViewCommand, ContextObj, \ - build_tenant_option, build_job_id_option, optional_job_type_option +from c7ncli.group import ( + ContextObj, + ViewCommand, + build_job_id_option, + build_tenant_option, + cli_response, + optional_job_type_option, +) @click.group(name='compliance') @@ -13,27 +19,40 @@ def compliance(): @build_job_id_option(required=True) @optional_job_type_option @click.option('--href', '-hf', is_flag=True, help='Return hypertext reference') +@click.option('--format', '-ft', type=click.Choice(('json', 'xlsx')), + default='json', show_default=True, + help='Format of the file within the hypertext reference') @cli_response() -def jobs(ctx: ContextObj, job_id: str, job_type: str, href: bool): +def jobs(ctx: ContextObj, job_id: str, job_type: str, href: bool, format: str, + customer_id): """ Describes job compliance reports """ - return ctx['api_client'].report_compliance_get( - job_id=job_id, job_type=job_type, href=href, jobs=True + return ctx['api_client'].report_compliance_jobs( + job_id=job_id, + job_type=job_type, + href=href, + format=format, + customer_id=customer_id ) @compliance.command(cls=ViewCommand, name='accumulated') @build_tenant_option(required=True) @click.option('--href', '-hf', is_flag=True, help='Return hypertext reference') -@cli_response(attributes_order=[]) -def accumulated(ctx: ContextObj, tenant_name: str, href: bool): +@click.option('--format', '-ft', type=click.Choice(('json', 'xlsx')), + default='json', show_default=True, + help='Format of the file within the hypertext reference') +@cli_response() +def accumulated(ctx: ContextObj, tenant_name: str, href: bool, format: str, + customer_id): """ Describes tenant-specific compliance report """ - return ctx['api_client'].report_compliance_get( + return ctx['api_client'].report_compliance_tenants( tenant_name=tenant_name, href=href, - jobs=False + format=format, + customer_id=customer_id ) diff --git a/c7n/c7ncli/group/report_details.py b/c7n/c7ncli/group/report_details.py index e41e3379d..6a0077078 100644 --- a/c7n/c7ncli/group/report_details.py +++ b/c7n/c7ncli/group/report_details.py @@ -3,11 +3,15 @@ import click -from c7ncli.group import build_job_id_option, \ - to_date_report_option, from_date_report_option,\ - optional_job_type_option -from c7ncli.group import cli_response, ViewCommand, ContextObj -from c7ncli.group import tenant_option, customer_option +from c7ncli.group import ( + build_job_id_option, + from_date_report_option, + optional_job_type_option, + response, + to_date_report_option, +) +from c7ncli.group import ContextObj, ViewCommand, cli_response +from c7ncli.group import tenant_option @click.group(name='details') @@ -19,62 +23,38 @@ def details(): @build_job_id_option(required=False) @optional_job_type_option @tenant_option -@customer_option @from_date_report_option @to_date_report_option @click.option('--href', '-hf', is_flag=True, help='Return hypertext reference') -@cli_response(attributes_order=[]) -def jobs(ctx: ContextObj, - job_id: Optional[str], tenant_name: Optional[str], - customer_id: Optional[str], from_date: Optional[datetime], - to_date: Optional[datetime], job_type: str, href: bool): +@click.option('--obfuscated', is_flag=True, + help='Whether to obfuscate the data and return also a dictionary') +@cli_response() +def jobs(ctx: ContextObj, job_id: Optional[str], tenant_name: Optional[str], + from_date: Optional[datetime], to_date: Optional[datetime], + job_type: str, href: bool, obfuscated, customer_id): """ Describes detailed reports of jobs """ + if sum(map(bool, (job_id, tenant_name))) != 1: + return response('Either --job_id or --tenant_name must be given') dates = from_date, to_date i_iso = map(lambda d: d.isoformat() if d else None, dates) from_date, to_date = tuple(i_iso) - kwargs = dict( - start_date=from_date, end_date=to_date, - job_type=job_type, href=href, jobs=True - ) - if tenant_name or job_id: - return ctx['api_client'].report_details_get( - job_id=job_id, tenant_name=tenant_name, - **kwargs - ) - else: - return ctx['api_client'].report_details_query( - customer=customer_id, **kwargs - ) - -@details.command(cls=ViewCommand, name='accumulated') -@optional_job_type_option -@tenant_option -@customer_option -@from_date_report_option -@to_date_report_option -@click.option('--href', '-hf', is_flag=True, help='Return hypertext reference') -@cli_response(attributes_order=[]) -def accumulated(ctx: ContextObj, tenant_name: Optional[str], - customer_id: Optional[str], from_date: Optional[datetime], - to_date: Optional[datetime], job_type: str, href: bool): - """ - Describes tenant-specific detailed reports, based on relevant jobs - """ - dates = from_date, to_date - i_iso = map(lambda d: d.isoformat() if d else None, dates) - from_date, to_date = tuple(i_iso) - kwargs = dict( - start_date=from_date, end_date=to_date, - job_type=job_type, href=href, jobs=False - ) - if tenant_name: - return ctx['api_client'].report_details_get( - tenant_name=tenant_name, **kwargs - ) - else: - return ctx['api_client'].report_details_query( - customer=customer_id, **kwargs + if job_id: + return ctx['api_client'].report_details_jobs( + job_id=job_id, + job_type=job_type, + href=href, + customer_id=customer_id, + obfuscated=obfuscated ) + return ctx['api_client'].report_details_tenants( + tenant_name=tenant_name, + job_type=job_type, + href=href, + start_iso=from_date, + end_iso=to_date, + customer_id=customer_id, + obfuscated=obfuscated + ) diff --git a/c7n/c7ncli/group/report_digests.py b/c7n/c7ncli/group/report_digests.py index 7b46a0f81..8f1b1429a 100644 --- a/c7n/c7ncli/group/report_digests.py +++ b/c7n/c7ncli/group/report_digests.py @@ -3,11 +3,15 @@ import click -from c7ncli.group import cli_response, ViewCommand, ContextObj -from c7ncli.group import tenant_option, customer_option -from c7ncli.group import build_job_id_option, \ - from_date_report_option, to_date_report_option,\ - optional_job_type_option +from c7ncli.group import ( + build_job_id_option, + from_date_report_option, + optional_job_type_option, + response, + to_date_report_option, +) +from c7ncli.group import ContextObj, ViewCommand, cli_response +from c7ncli.group import tenant_option @click.group(name='digests') @@ -19,64 +23,31 @@ def digests(): @build_job_id_option(required=False) @optional_job_type_option @tenant_option -@customer_option @from_date_report_option @to_date_report_option -@click.option('--href', '-hf', is_flag=True, help='Return hypertext reference') -@cli_response(attributes_order=[]) -def jobs(ctx: ContextObj, - job_id: Optional[str], - tenant_name: Optional[str], customer_id: Optional[str], +@cli_response() +def jobs(ctx: ContextObj, job_id: Optional[str], tenant_name: Optional[str], from_date: Optional[datetime], to_date: Optional[datetime], - job_type: str, href: bool): + job_type: Optional[str], customer_id): """ Describes summary reports of jobs """ + if sum(map(bool, (job_id, tenant_name))) != 1: + return response('Either --job_id or --tenant_name must be given') dates = from_date, to_date i_iso = map(lambda d: d.isoformat() if d else None, dates) from_date, to_date = tuple(i_iso) - kwargs = dict( - start_date=from_date, end_date=to_date, - job_type=job_type, href=href, jobs=True - ) - if tenant_name or job_id: - return ctx['api_client'].report_digests_get( - job_id=job_id, tenant_name=tenant_name, **kwargs - ) - else: - return ctx['api_client'].report_digests_query( - customer=customer_id, **kwargs + if job_id: + return ctx['api_client'].report_digest_jobs( + job_id=job_id, + job_type=job_type, + customer_id=customer_id ) - - -@digests.command(cls=ViewCommand, name='accumulated') -@tenant_option -@optional_job_type_option -@customer_option -@from_date_report_option -@to_date_report_option -@click.option('--href', '-hf', is_flag=True, help='Return hypertext reference') -@cli_response(attributes_order=[]) -def accumulated(ctx: ContextObj, tenant_name: Optional[str], - customer_id: Optional[str], from_date: Optional[datetime], - to_date: Optional[datetime], job_type: str, href: bool): - """ - Describes tenant-specific summary reports, based on relevant jobs - """ - dates = from_date, to_date - i_iso = map(lambda d: d.isoformat() if d else None, dates) - from_date, to_date = tuple(i_iso) - - kwargs = dict( - start_date=from_date, end_date=to_date, - job_type=job_type, href=href, jobs=False + return ctx['api_client'].report_digest_tenants( + tenant_name=tenant_name, + job_type=job_type, + start_iso=from_date, + end_iso=to_date, + customer_id=customer_id ) - if tenant_name: - return ctx['api_client'].report_digests_get( - tenant_name=tenant_name, **kwargs - ) - else: - return ctx['api_client'].report_digests_query( - customer=customer_id, **kwargs - ) diff --git a/c7n/c7ncli/group/report_errors.py b/c7n/c7ncli/group/report_errors.py index a9c9a2c37..c6e3655c3 100644 --- a/c7n/c7ncli/group/report_errors.py +++ b/c7n/c7ncli/group/report_errors.py @@ -1,7 +1,15 @@ +from typing import Optional + import click -from c7ncli.group.report_errors_accumulated import accumulated -from c7ncli.group.report_errors_jobs import jobs +from c7ncli.group import ( + ContextObj, + ViewCommand, + build_job_id_option, + build_job_type_option, + cli_response, +) +from c7ncli.service.constants import PolicyErrorType @click.group(name='errors') @@ -9,5 +17,26 @@ def errors(): """Describes error reports""" -errors.add_command(jobs) -errors.add_command(accumulated) +@errors.command(cls=ViewCommand, name='jobs') +@build_job_id_option(required=True) +@build_job_type_option() +@click.option('--href', '-hf', is_flag=True, + help='Return hypertext reference') +@click.option('--format', '-ft', type=click.Choice(('json', 'xlsx')), + default='json', show_default=True, + help='Format of the file within the hypertext reference') +@click.option('--error_type', '-et', type=click.Choice(tuple(PolicyErrorType.iter()))) +@cli_response() +def jobs(ctx: ContextObj, job_id: str, job_type: str, href: bool, format: str, + error_type: Optional[str], customer_id): + """ + Errors of specific job + """ + return ctx['api_client'].report_errors_job( + job_id=job_id, + job_type=job_type, + href=href, + format=format, + error_type=error_type, + customer_id=customer_id + ) diff --git a/c7n/c7ncli/group/report_errors_accumulated.py b/c7n/c7ncli/group/report_errors_accumulated.py deleted file mode 100644 index 8f6ccad68..000000000 --- a/c7n/c7ncli/group/report_errors_accumulated.py +++ /dev/null @@ -1,99 +0,0 @@ -from datetime import datetime -from typing import Optional - -import click - -from c7ncli.group import cli_response, ViewCommand, ContextObj -from c7ncli.group import tenant_option, customer_option -from c7ncli.group import from_date_report_option, \ - to_date_report_option, optional_job_type_option - -AVAILABLE_ERROR_FORMATS = ['json', 'xlsx'] - - -@click.group(name='accumulated') -def accumulated(): - """Describes tenant-specific error reports, based on relevant jobs""" - - -@accumulated.command(cls=ViewCommand, name='total') -@optional_job_type_option -@tenant_option -@customer_option -@to_date_report_option -@from_date_report_option -@click.option('--href', '-hf', is_flag=True, help='Return hypertext reference') -@click.option('--format', '-ft', type=click.Choice(AVAILABLE_ERROR_FORMATS), - help='Format of the file within the hypertext reference') -@cli_response(attributes_order=[]) -def total(ctx: ContextObj, tenant_name: Optional[str], customer_id: Optional[str], - from_date: Optional[datetime], to_date: Optional[datetime], job_type: str, - href: bool, format: str): - """ - Describes job error reports - """ - dates = from_date, to_date - i_iso = map(lambda d: d.isoformat() if d else None, dates) - from_date, to_date = tuple(i_iso) - - return ctx['api_client'].report_errors_query( - end_date=to_date, start_date=from_date, - tenant_name=tenant_name, - job_type=job_type, href=href, frmt=format, subtype=None, - customer=customer_id - ) - - -@accumulated.command(cls=ViewCommand, name='access') -@optional_job_type_option -@tenant_option -@customer_option -@to_date_report_option -@from_date_report_option -@click.option('--href', '-hf', is_flag=True, help='Return hypertext reference') -@click.option('--format', '-ft', type=click.Choice(AVAILABLE_ERROR_FORMATS), - help='Format of the file within the hypertext reference') -@cli_response(attributes_order=[]) -def access(ctx: ContextObj, tenant_name: Optional[str], customer_id: Optional[str], - from_date: Optional[datetime], to_date: Optional[datetime], - job_type: str, href: bool, format: str): - """ - Describes access-related job error reports - """ - dates = from_date, to_date - i_iso = map(lambda d: d.isoformat() if d else None, dates) - from_date, to_date = tuple(i_iso) - - return ctx['api_client'].report_errors_query( - end_date=to_date, start_date=from_date, - tenant_name=tenant_name, - job_type=job_type, href=href, frmt=format, subtype='access', - customer=customer_id - ) - - -@accumulated.command(cls=ViewCommand, name='core') -@optional_job_type_option -@tenant_option -@customer_option -@to_date_report_option -@from_date_report_option -@click.option('--href', '-hf', is_flag=True, help='Return hypertext reference') -@click.option('--format', '-ft', type=click.Choice(AVAILABLE_ERROR_FORMATS), - help='Format of the file within the hypertext reference') -@cli_response(attributes_order=[]) -def core(ctx: ContextObj, tenant_name: Optional[str], customer_id: Optional[str], - from_date: Optional[datetime], to_date: Optional[datetime], job_type: str, - href: bool, format: str): - """ - Describes core-related error reports - """ - dates = from_date, to_date - i_iso = map(lambda d: d.isoformat() if d else None, dates) - from_date, to_date = tuple(i_iso) - - return ctx['api_client'].report_errors_query( - end_date=to_date, start_date=from_date, - tenant_name=tenant_name, job_type=job_type, href=href, frmt=format, - subtype='core', customer=customer_id - ) diff --git a/c7n/c7ncli/group/report_errors_jobs.py b/c7n/c7ncli/group/report_errors_jobs.py deleted file mode 100644 index 164052817..000000000 --- a/c7n/c7ncli/group/report_errors_jobs.py +++ /dev/null @@ -1,73 +0,0 @@ -import click - -from c7ncli.group import cli_response, ViewCommand, ContextObj -from c7ncli.group import build_job_id_option, build_job_type_option -from c7ncli.service.constants import MANUAL_JOB_TYPE - -required_job_id_option = build_job_id_option(required=True) -default_job_type_option = build_job_type_option(default=MANUAL_JOB_TYPE, show_default=True) - -AVAILABLE_ERROR_FORMATS = ['json', 'xlsx'] - - -@click.group(name='jobs') -def jobs(): - """Describes error reports of jobs""" - - -@jobs.command(cls=ViewCommand, name='total') -@required_job_id_option -@default_job_type_option -@click.option('--href', '-hf', is_flag=True, help='Return hypertext reference') -@click.option('--format', '-ft', type=click.Choice(AVAILABLE_ERROR_FORMATS), - help='Format of the file within the hypertext reference') -@cli_response(attributes_order=[]) -def total(ctx: ContextObj, job_id: str, job_type: str, href: bool, format: str): - """ - Describes all job error reports - """ - return ctx['api_client'].report_errors_get( - job_id=job_id, job_type=job_type, href=href, frmt=format - ) - - -@jobs.command(cls=ViewCommand, name='access') -@required_job_id_option -@default_job_type_option -@click.option('--href', '-hf', is_flag=True, help='Return hypertext reference') -@click.option('--format', '-ft', type=click.Choice(AVAILABLE_ERROR_FORMATS), - help='Format of the file within the hypertext reference') -@cli_response(attributes_order=[]) -def access( - ctx: ContextObj, job_id: str, - job_type: str, href: bool, format: str -): - """ - Describes access-related job error reports - """ - - return ctx['api_client'].report_errors_get( - job_id=job_id, job_type=job_type, href=href, frmt=format, - subtype='access' - ) - - -@jobs.command(cls=ViewCommand, name='core') -@required_job_id_option -@default_job_type_option -@click.option('--href', '-hf', is_flag=True, help='Return hypertext reference') -@click.option('--format', '-ft', type=click.Choice(AVAILABLE_ERROR_FORMATS), - help='Format of the file within the hypertext reference') -@cli_response(attributes_order=[]) -def core( - ctx: ContextObj, job_id: str, job_type: str, - href: bool, format: str -): - """ - Describes core-related job error reports - """ - - return ctx['api_client'].report_errors_get( - job_id=job_id, job_type=job_type, href=href, frmt=format, - subtype='core' - ) diff --git a/c7n/c7ncli/group/report_findings.py b/c7n/c7ncli/group/report_findings.py new file mode 100644 index 000000000..ef22974fd --- /dev/null +++ b/c7n/c7ncli/group/report_findings.py @@ -0,0 +1,60 @@ +from datetime import datetime +from typing import Optional + +import click + +from c7ncli.group import ( + build_job_id_option, + from_date_report_option, + optional_job_type_option, + response, + to_date_report_option, +) +from c7ncli.group import ContextObj, ViewCommand, cli_response +from c7ncli.group import tenant_option + + +@click.group(name='findings') +def findings(): + """Describes findings reports""" + + +@findings.command(cls=ViewCommand, name='jobs') +@build_job_id_option(required=False) +@optional_job_type_option +@tenant_option +@from_date_report_option +@to_date_report_option +@click.option('--href', '-hf', is_flag=True, help='Return hypertext reference') +@click.option('--obfuscated', is_flag=True, + help='Whether to obfuscate the data and return also a dictionary') +@cli_response() +def jobs(ctx: ContextObj, job_id: Optional[str], tenant_name: Optional[str], + from_date: Optional[datetime], to_date: Optional[datetime], + job_type: str, href: bool, customer_id, obfuscated): + """ + Describes detailed reports of jobs + """ + if sum(map(bool, (job_id, tenant_name))) != 1: + return response('Either --job_id or --tenant_name must be given') + dates = from_date, to_date + i_iso = map(lambda d: d.isoformat() if d else None, dates) + from_date, to_date = tuple(i_iso) + + if job_id: + return ctx['api_client'].report_findings_jobs( + job_id=job_id, + job_type=job_type, + href=href, + customer_id=customer_id, + obfuscated=obfuscated + ) + return ctx['api_client'].report_findings_tenants( + tenant_name=tenant_name, + job_type=job_type, + href=href, + start_iso=from_date, + end_iso=to_date, + customer_id=customer_id, + obfuscated=obfuscated + ) diff --git a/c7n/c7ncli/group/report_push.py b/c7n/c7ncli/group/report_push.py index 1eec0f2cd..b63f6a7a3 100644 --- a/c7n/c7ncli/group/report_push.py +++ b/c7n/c7ncli/group/report_push.py @@ -4,9 +4,14 @@ import click from c7ncli.group import build_job_id_option, optional_job_type_option -from c7ncli.group import cli_response, ViewCommand, \ - tenant_option, customer_option, ContextObj, \ - from_date_report_option, to_date_report_option +from c7ncli.group import ( + ContextObj, + ViewCommand, + cli_response, + from_date_report_option, + tenant_option, + to_date_report_option, +) optional_job_id_option = build_job_id_option( required=False, @@ -25,65 +30,23 @@ def push(): @optional_job_type_option @from_date_report_option @to_date_report_option -@customer_option @tenant_option @cli_response() -def dojo( - ctx: ContextObj, job_id: Optional[str], job_type: Optional[str], - from_date: Optional[datetime], to_date: Optional[datetime], - customer_id: Optional[str], tenant_name: Optional[str] -): +def dojo(ctx: ContextObj, job_id: Optional[str], job_type: Optional[str], + from_date: Optional[datetime], to_date: Optional[datetime], + customer_id: Optional[str], tenant_name: Optional[str]): """ Pushes job detailed report(s) to the Dojo SIEM """ if job_id: - return ctx['api_client'].push_dojo_by_job_id(job_id=job_id) + return ctx['api_client'].push_dojo_by_job_id(job_id=job_id, + customer_id=customer_id) if not tenant_name: raise click.UsageError("Missing option '--tenant_name' / '-tn'.") return ctx['api_client'].push_dojo_multiple( start_date=from_date.isoformat() if from_date else None, end_date=to_date.isoformat() if to_date else None, - customer=customer_id, + customer_id=customer_id, tenant_name=tenant_name, job_type=job_type ) - - -@push.command(cls=ViewCommand, name='security_hub') -@optional_job_id_option -@optional_job_type_option -@from_date_report_option -@to_date_report_option -@customer_option -@tenant_option -@click.option('--aws_access_key', '-ak', type=str, - help='AWS Account access key') -@click.option('--aws_secret_access_key', '-sk', type=str, - help='AWS Account secret access key') -@click.option('--aws_session_token', '-st', type=str, - help='AWS Account session token') -@click.option('--aws_default_region', '-df', type=str, default='eu-central-1', - show_default=True, - help='AWS Account default region to init a client ') -@cli_response(attributes_order=[]) -def security_hub( - ctx: ContextObj, job_id: Optional[str], job_type: Optional[str], - from_date: Optional[datetime], to_date: Optional[datetime], - customer_id: Optional[str], tenant_name: Optional[str], - aws_access_key, aws_secret_access_key, aws_session_token, - aws_default_region -): - """ - Pushes job detailed report(s) to the AWS Security Hub SIEM - """ - if job_id: - return ctx['api_client'].push_security_hub_by_job_id(job_id) - if not tenant_name: - raise click.UsageError("Missing option '--tenant_name' / '-tn'.") - return ctx['api_client'].push_security_hub_multiple( - start_date=from_date.isoformat() if from_date else None, - end_date=to_date.isoformat() if to_date else None, - customer=customer_id, - job_type=job_type, - tenant_name=tenant_name, - ) diff --git a/c7n/c7ncli/group/report_raw.py b/c7n/c7ncli/group/report_raw.py new file mode 100644 index 000000000..0b66aabcd --- /dev/null +++ b/c7n/c7ncli/group/report_raw.py @@ -0,0 +1,28 @@ +import click + +from c7ncli.group import ContextObj, ViewCommand, cli_response +from c7ncli.group import build_tenant_option + + +@click.group(name='raw') +def raw(): + """Fetches raw report""" + + +@raw.command(cls=ViewCommand, name='latest') +@build_tenant_option(required=True) +@click.option('--obfuscated', is_flag=True, + help='Whether to obfuscate the data and return also a dictionary') +@click.option('--meta', is_flag=True, + help='Whether to return rules meta as well') +@cli_response() +def latest(ctx: ContextObj, tenant_name, obfuscated, meta, customer_id): + """ + Returns latest raw report + """ + return ctx['api_client'].report_raw_tenant( + tenant_name=tenant_name, + obfuscated=obfuscated, + meta=meta, + customer_id=customer_id + ) diff --git a/c7n/c7ncli/group/report_resource.py b/c7n/c7ncli/group/report_resource.py index 1e962c509..d830f28c6 100644 --- a/c7n/c7ncli/group/report_resource.py +++ b/c7n/c7ncli/group/report_resource.py @@ -3,50 +3,119 @@ import click -from c7ncli.group import cli_response, ViewCommand, ContextObj, \ - build_tenant_option, from_date_report_option, to_date_report_option, \ - optional_job_type_option, build_job_id_option +from c7ncli.group import ( + ContextObj, + ViewCommand, + build_job_id_option, + build_tenant_option, + cli_response, + from_date_report_option, + optional_job_type_option, + to_date_report_option, +) @click.group(name='resource') def resource(): - """Describes compliance reports""" + """Generate resource reports""" @resource.command(cls=ViewCommand, name='latest') @build_tenant_option(required=True) -@click.option('--resource_id', '-rid', required=True, type=str, - help='Resource identifier (arn or name or id)') +@click.option('--resource_type', '-rt', type=str, + help='Resource type to filter the result (lambda, s3, ...)') +@click.option('--region', '-r', type=str, + help='Region to filter the result by. Specify "global" ' + 'for region-independent resources') +@click.option('--full', '-f', is_flag=True, + help='Whether to return full resource data') @click.option('--exact_match', '-em', type=bool, default=True, show_default=True, help='Whether to match the identifier exactly or allow ' 'partial match') -@click.option('--search_by', '-sb', type=str, multiple=True, - help='Attributes to search the identifier by ' - '(arn, name, id, etc..). If not specified, report ' - 'fields from rule meta will be used') @click.option('--search_by_all', '-sba', is_flag=True, help='If specified, all the fields will be checked') -@click.option('--resource_type', '-rt', type=str, - help='Resource type to filter the result (lambda, s3, ...)') -@click.option('--region', '-r', type=str, - help='Region to filter the result by') +@click.option('--format', '-ft', type=click.Choice(('json', 'xlsx')), + help='Report format') +@click.option('--href', '-hf', is_flag=True, + help='Whether to return raw data of url to the data') +@click.option('--obfuscated', is_flag=True, + help='Whether to obfuscate the data and return also a dictionary') +@click.option('--id', required=False, type=str, + help='Resource identifier') +@click.option('--name', required=False, type=str, + help='Resource name') +@click.option('--arn', required=False, type=str, + help='Resource arn, only for aws') @cli_response() -def latest(ctx: ContextObj, tenant_name: str, resource_id: str, - exact_match: bool, - search_by: tuple, search_by_all: bool, - resource_type: Optional[str], region: Optional[str]): +def latest(ctx: ContextObj, tenant_name: str, resource_type: Optional[str], + region: Optional[str], full: bool, exact_match: bool, + search_by_all: bool, format: str, href: bool, id: Optional[str], + name: Optional[str], arn: Optional[str], customer_id, obfuscated): """ - Describes job compliance reports + Resource report for tenant """ return ctx['api_client'].report_resource_latest( tenant_name=tenant_name, - identifier=resource_id, + resource_type=resource_type, + region=region, + full=full, exact_match=exact_match, - search_by=','.join(search_by) if search_by else None, search_by_all=search_by_all, + format=format, + href=href, + obfuscated=obfuscated, + id=id, + name=name, + arn=arn, + customer_id=customer_id + ) + + +@resource.command(cls=ViewCommand, name='platform_latest') +@click.option('--platform_id', '-pid', type=str, required=True, + help='Platform id') +@click.option('--resource_type', '-rt', type=str, + help='Resource type to filter the result (lambda, s3, ...)') +@click.option('--full', '-f', is_flag=True, + help='Whether to return full resource data') +@click.option('--exact_match', '-em', type=bool, default=True, + show_default=True, + help='Whether to match the identifier exactly or allow ' + 'partial match') +@click.option('--search_by_all', '-sba', is_flag=True, + help='If specified, all the fields will be checked') +@click.option('--format', '-ft', type=click.Choice(('json', 'xlsx')), + help='Report format') +@click.option('--href', '-hf', is_flag=True, + help='Whether to return raw data of url to the data') +@click.option('--obfuscated', is_flag=True, + help='Whether to obfuscate the data and return also a dictionary') +@click.option('--id', required=False, type=str, + help='Resource identifier') +@click.option('--name', required=False, type=str, + help='Resource name') +@cli_response() +def platform_latest(ctx: ContextObj, platform_id: str, + resource_type: Optional[str], full: bool, + exact_match: bool, search_by_all: bool, + format: str, href: bool, id: Optional[str], + name: Optional[str], customer_id, obfuscated): + """ + Resource report for platform + """ + return ctx['api_client'].platform_report_resource_latest( + platform_id=platform_id, resource_type=resource_type, - region=region, + full=full, + exact_match=exact_match, + search_by_all=search_by_all, + format=format, + href=href, + id=id, + name=name, + customer_id=customer_id, + obfuscated=obfuscated ) @@ -55,81 +124,101 @@ def latest(ctx: ContextObj, tenant_name: str, resource_id: str, @optional_job_type_option @from_date_report_option @to_date_report_option -@click.option('--resource_id', '-rid', required=True, type=str, - help='Resource identifier (arn or name or id)') +@click.option('--resource_type', '-rt', type=str, + help='Resource type to filter the result (lambda, s3, ...)') +@click.option('--region', '-r', type=str, + help='Region to filter the result by. Specify "global" ' + 'for region-independent resources') +@click.option('--full', '-f', is_flag=True, + help='Whether to return full resource data') @click.option('--exact_match', '-em', type=bool, default=True, show_default=True, help='Whether to match the identifier exactly or allow ' 'partial match') -@click.option('--search_by', '-sb', type=str, multiple=True, - help='Attributes to search the identifier by ' - '(arn, name, id, etc..). If not specified, report ' - 'fields from rule meta will be used') @click.option('--search_by_all', '-sba', is_flag=True, help='If specified, all the fields will be checked') -@click.option('--resource_type', '-rt', type=str, - help='Resource type to filter the result (lambda, s3, ...)') -@click.option('--region', '-r', type=str, - help='Region to filter the result by') +@click.option('--id', required=False, type=str, + help='Resource identifier') +@click.option('--name', required=False, type=str, + help='Resource name') +@click.option('--arn', required=False, type=str, + help='Resource arn, only for aws') @cli_response() def jobs(ctx: ContextObj, tenant_name: str, job_type: str, - from_date: datetime, to_date: datetime, resource_id: str, - exact_match: bool, - search_by: tuple, search_by_all: bool, - resource_type: Optional[str], region: Optional[str], ): + from_date: datetime, to_date: datetime, resource_type: Optional[str], + region: Optional[str], full: bool, exact_match: bool, + search_by_all: bool, id: Optional[str], name: Optional[str], + arn: Optional[str], customer_id): """ - Describes job compliance reports + Resource report for tenant jobs """ dates = from_date, to_date i_iso = map(lambda d: d.isoformat() if d else None, dates) from_date, to_date = tuple(i_iso) return ctx['api_client'].report_resource_jobs( tenant_name=tenant_name, - type=job_type, + job_type=job_type, start_iso=from_date, end_iso=to_date, - identifier=resource_id, - exact_match=exact_match, - search_by=','.join(search_by) if search_by else None, - search_by_all=search_by_all, resource_type=resource_type, region=region, + full=full, + exact_match=exact_match, + search_by_all=search_by_all, + id=id, + name=name, + arn=arn, + customer_id=customer_id ) @resource.command(cls=ViewCommand, name='job') @build_job_id_option(required=True) @optional_job_type_option -@click.option('--resource_id', '-rid', required=True, type=str, - help='Resource identifier (arn or name or id)') +@click.option('--resource_type', '-rt', type=str, + help='Resource type to filter the result (lambda, s3, ...)') +@click.option('--region', '-r', type=str, + help='Region to filter the result by. Specify "global" ' + 'for region-independent resources') +@click.option('--full', '-f', is_flag=True, + help='Whether to return full resource data') @click.option('--exact_match', '-em', type=bool, default=True, show_default=True, help='Whether to match the identifier exactly or allow ' 'partial match') -@click.option('--search_by', '-sb', type=str, multiple=True, - help='Attributes to search the identifier by ' - '(arn, name, id, etc..). If not specified, report ' - 'fields from rule meta will be used') @click.option('--search_by_all', '-sba', is_flag=True, help='If specified, all the fields will be checked') -@click.option('--resource_type', '-rt', type=str, - help='Resource type to filter the result (lambda, s3, ...)') -@click.option('--region', '-r', type=str, - help='Region to filter the result by') +@click.option('--href', '-hf', is_flag=True, + help='Whether to return raw data of url to the data') +@click.option('--obfuscated', is_flag=True, + help='Whether to obfuscate the data and return also a dictionary') +@click.option('--id', required=False, type=str, + help='Resource identifier') +@click.option('--name', required=False, type=str, + help='Resource name') +@click.option('--arn', required=False, type=str, + help='Resource arn, only for aws') @cli_response() -def job(ctx: ContextObj, job_id: str, job_type: str, resource_id: str, - exact_match: bool, search_by: tuple, search_by_all: bool, - resource_type: Optional[str], region: Optional[str], ): +def job(ctx: ContextObj, job_id: str, job_type: str, + resource_type: Optional[str], region: Optional[str], full: bool, + exact_match: bool, search_by_all: bool, id: Optional[str], + name: Optional[str], arn: Optional[str], href: bool, customer_id, + obfuscated): """ - Describes job compliance reports + Resource report for concrete job """ return ctx['api_client'].report_resource_job( - id=job_id, + job_id=job_id, type=job_type, - identifier=resource_id, - exact_match=exact_match, - search_by=','.join(search_by) if search_by else None, - search_by_all=search_by_all, resource_type=resource_type, region=region, + full=full, + exact_match=exact_match, + search_by_all=search_by_all, + id=id, + name=name, + arn=arn, + customer_id=customer_id, + href=href, + obfuscated=obfuscated ) diff --git a/c7n/c7ncli/group/report_rules.py b/c7n/c7ncli/group/report_rules.py index f34562486..0fb4dfb64 100644 --- a/c7n/c7ncli/group/report_rules.py +++ b/c7n/c7ncli/group/report_rules.py @@ -3,15 +3,20 @@ import click -from c7ncli.group import build_job_id_option, build_job_type_option, optional_job_type_option -from c7ncli.group import cli_response, ViewCommand, \ - tenant_option, customer_option, ContextObj, \ - from_date_report_option, to_date_report_option -from c7ncli.service.constants import MANUAL_JOB_TYPE - -required_job_id_option = build_job_id_option(required=True) -default_job_type_option = build_job_type_option(default=MANUAL_JOB_TYPE, show_default=True) -AVAILABLE_ERROR_FORMATS = ['json', 'xlsx'] +from c7ncli.group import ( + build_job_id_option, + build_job_type_option, + optional_job_type_option, +) +from c7ncli.group import ( + ContextObj, + ViewCommand, + build_tenant_option, + cli_response, + from_date_report_option, + to_date_report_option, +) +from c7ncli.service.constants import JobType @click.group(name='rules') @@ -20,40 +25,39 @@ def rules(): @rules.command(cls=ViewCommand, name='jobs') -@required_job_id_option -@default_job_type_option -@click.option('--rule', '-rl', type=str, help='Denotes rule to target') -@click.option('--href', '-hf', is_flag=True, help='Return hypertext reference') -@click.option('--format', '-ft', type=click.Choice(AVAILABLE_ERROR_FORMATS), +@build_job_id_option(required=True) +@build_job_type_option(default=JobType.MANUAL.value, show_default=True) +@click.option('--href', '-hf', is_flag=True, + help='Return hypertext reference') +@click.option('--format', '-ft', type=click.Choice(('json', 'xlsx')), + default='json', show_default=True, help='Format of the file within the hypertext reference') -@cli_response(attributes_order=[]) +@cli_response() def jobs(ctx: ContextObj, job_id: str, job_type: str, - rule: str, href: bool, format: str): + href: bool, format: str, customer_id): """ Describes job-specific rule statistic reports """ return ctx['api_client'].report_rules_get( - job_id=job_id, job_type=job_type, href=href, - frmt=format, jobs=True, target_rule=rule + job_id=job_id, + job_type=job_type, + href=href, + format=format, + customer_id=customer_id ) @rules.command(cls=ViewCommand, name='accumulated') -@tenant_option -@customer_option +@build_tenant_option(required=True) @from_date_report_option @to_date_report_option @optional_job_type_option -@click.option('--rule', '-rl', type=str, help='Denotes rule to target') -@click.option('--href', '-hf', is_flag=True, help='Return hypertext reference') -@click.option('--format', '-ft', type=click.Choice(AVAILABLE_ERROR_FORMATS), - help='Format of the file within the hypertext reference') -@cli_response(attributes_order=[]) -def accumulated(ctx: ContextObj, tenant_name: Optional[str], - customer_id: Optional[str], from_date: Optional[datetime], - to_date: datetime, job_type: str, - rule: str, href: bool, format: str): +@cli_response() +def accumulated(ctx: ContextObj, tenant_name: str, + from_date: Optional[datetime], + to_date: Optional[datetime], + job_type: Optional[str], customer_id): """ Describes tenant-specific rule statistic reports, based on relevant jobs """ @@ -61,17 +65,10 @@ def accumulated(ctx: ContextObj, tenant_name: Optional[str], dates = from_date, to_date i_iso = map(lambda d: d.isoformat() if d else None, dates) from_date, to_date = tuple(i_iso) - kwargs = dict( - start_date=from_date, end_date=to_date, - job_type=job_type, href=href, jobs=False, - target_rule=rule, frmt=format + return ctx['api_client'].report_rules_query( + start_iso=from_date, + end_iso=to_date, + tenant_name=tenant_name, + job_type=job_type, + customer_id=customer_id ) - - if tenant_name: - return ctx['api_client'].report_rules_get( - tenant_name=tenant_name, **kwargs - ) - else: - return ctx['api_client'].report_rules_query( - customer=customer_id, **kwargs - ) diff --git a/c7n/c7ncli/group/results.py b/c7n/c7ncli/group/results.py index 47c97b4c8..52c645ef8 100644 --- a/c7n/c7ncli/group/results.py +++ b/c7n/c7ncli/group/results.py @@ -1,9 +1,18 @@ from datetime import datetime + import click -from c7ncli.group import tenant_option, limit_option, next_option, \ - ContextObj, cli_response, ViewCommand, customer_option, build_iso_date_option, \ - from_date_iso_args, to_date_iso_args +from c7ncli.group import ( + ContextObj, + ViewCommand, + build_iso_date_option, + cli_response, + from_date_iso_args, + limit_option, + next_option, + tenant_option, + to_date_iso_args, +) from_date_results_option = build_iso_date_option( *from_date_iso_args, required=False, @@ -24,12 +33,11 @@ def results(): @click.option('--batch_result_id', '-id', type=str, required=False, help='Batch Result identifier to describe by') @tenant_option -@customer_option @from_date_results_option @to_date_results_option @limit_option @next_option -@cli_response(attributes_order=[]) +@cli_response() def describe(ctx: ContextObj, batch_result_id: str, tenant_name: str, customer_id: str, from_date: datetime, to_date: datetime, limit: int, next_token: str): @@ -37,7 +45,8 @@ def describe(ctx: ContextObj, batch_result_id: str, tenant_name: str, Describes results of Custodian Service reactive, batched scans """ if batch_result_id: - return ctx['api_client'].batch_results_get(br_id=batch_result_id) + return ctx['api_client'].batch_results_get(br_id=batch_result_id, + customer_id=customer_id) if from_date: from_date = from_date.isoformat() @@ -46,7 +55,10 @@ def describe(ctx: ContextObj, batch_result_id: str, tenant_name: str, to_date = to_date.isoformat() return ctx['api_client'].batch_results_query( - tenant=tenant_name, customer=customer_id, - start_date=from_date, end_date=to_date, - next_token=next_token, limit=limit + customer_id=customer_id, + tenant_name=tenant_name, + start=from_date, + end=to_date, + limit=limit, + next_token=next_token ) diff --git a/c7n/c7ncli/group/role.py b/c7n/c7ncli/group/role.py index ae2d7722c..f48672d38 100644 --- a/c7n/c7ncli/group/role.py +++ b/c7n/c7ncli/group/role.py @@ -2,10 +2,9 @@ import click -from c7ncli.group import cli_response, ViewCommand, response, ContextObj, \ - customer_option -from c7ncli.service.constants import PARAM_NAME, PARAM_CUSTOMER, \ - PARAM_POLICIES, PARAM_EXPIRATION +from c7ncli.group import ContextObj, ViewCommand, cli_response, response + +attributes_order = 'name', 'expiration', 'policies' @click.group(name='role') @@ -15,16 +14,18 @@ def role(): @role.command(cls=ViewCommand, name='describe') @click.option('--name', '-n', type=str, help='Role name to describe.') -@customer_option -@cli_response(attributes_order=[PARAM_NAME, PARAM_CUSTOMER, PARAM_POLICIES, - PARAM_EXPIRATION]) +@cli_response(attributes_order=attributes_order) def describe(ctx: ContextObj, customer_id, name): """ Describes a Custodian Service roles for the given customer. """ - return ctx['api_client'].role_get( - customer=customer_id, - name=name + if name: + return ctx['api_client'].role_get( + name=name, + customer_id=customer_id, + ) + return ctx['api_client'].role_query( + customer_id=customer_id, ) @@ -35,10 +36,10 @@ def describe(ctx: ContextObj, customer_id, name): help='List of policies to attach to the role') @click.option('--expiration', '-e', type=str, help='Expiration date, ISO 8601. Example: 2021-08-01T15:30:00') -@customer_option -@cli_response(attributes_order=[PARAM_NAME, PARAM_CUSTOMER, PARAM_POLICIES, - PARAM_EXPIRATION]) -def add(ctx: ContextObj, customer_id, name, policies, expiration): +@click.option('--description', '-d', type=str, required=True, + help='Description for the created role') +@cli_response(attributes_order=attributes_order) +def add(ctx: ContextObj, customer_id, name, policies, description, expiration): """ Creates the Role entity with the given name from Customer with the given id """ @@ -49,10 +50,11 @@ def add(ctx: ContextObj, customer_id, name, policies, expiration): return response(f'Invalid value for the \'expiration\' ' f'parameter: {expiration}') return ctx['api_client'].role_post( - customer=customer_id, + customer_id=customer_id, name=name, policies=policies, - expiration=expiration + expiration=expiration, + description=description ) @@ -65,10 +67,10 @@ def add(ctx: ContextObj, customer_id, name, policies, expiration): help='List of policies to detach from role') @click.option('--expiration', '-e', type=str, required=False, help='Expiration date, ISO 8601. Example: 2021-08-01T15:30:00') -@customer_option -@cli_response(attributes_order=[PARAM_NAME, PARAM_CUSTOMER, PARAM_POLICIES, - PARAM_EXPIRATION]) -def update(ctx: ContextObj, customer_id, name, attach_policy, detach_policy, expiration): +@click.option('--description', '-d', type=str, + help='Description for the created role') +@cli_response(attributes_order=attributes_order) +def update(ctx: ContextObj, customer_id, name, attach_policy, detach_policy, expiration, description): """ Updates role configuration. """ @@ -88,35 +90,20 @@ def update(ctx: ContextObj, customer_id, name, attach_policy, detach_policy, exp policies_to_attach=attach_policy, policies_to_detach=detach_policy, expiration=expiration, - customer=customer_id + customer_id=customer_id, + description=description ) @role.command(cls=ViewCommand, name='delete') @click.option('--name', '-n', type=str, required=True, help='Role name to delete') -@customer_option @cli_response() def delete(ctx: ContextObj, customer_id, name): """ Deletes customers role. """ return ctx['api_client'].role_delete( - customer=customer_id, - name=name - ) - - -@role.command(cls=ViewCommand, name='clean_cache') -@click.option('--name', '-n', type=str, - help='Role name to clean from cache') -@customer_option -@cli_response() -def clean_cache(ctx: ContextObj, customer_id, name): - """ - Cleans cached role from lambda. - """ - return ctx['api_client'].role_clean_cache( - customer=customer_id, - name=name + name=name, + customer_id=customer_id, ) diff --git a/c7n/c7ncli/group/rule.py b/c7n/c7ncli/group/rule.py index 5f7af4f17..f2575d259 100644 --- a/c7n/c7ncli/group/rule.py +++ b/c7n/c7ncli/group/rule.py @@ -1,11 +1,17 @@ import click -from c7ncli.group import cli_response, ViewCommand, response, ContextObj, \ - customer_option, build_rule_source_id_option +from c7ncli.group import ( + ContextObj, + ViewCommand, + build_rule_source_id_option, + cli_response, + response, +) from c7ncli.group import limit_option, next_option -from c7ncli.service.constants import PARAM_NAME, PARAM_VERSION, PARAM_ID, \ - PARAM_CLOUD, PARAM_DESCRIPTION, PARAM_UPDATED_DATE, \ - PARAM_SERVICE_SECTION, RULE_CLOUDS +from c7ncli.service.constants import RULE_CLOUDS + +attributes_order = ('name', 'cloud', 'resource', 'description', 'branch', + 'project') @click.group(name='rule') @@ -25,11 +31,7 @@ def rule(): help='Branch of git repo to build a ruleset') @limit_option @next_option -@customer_option -@cli_response(attributes_order=[PARAM_ID, PARAM_DESCRIPTION, - PARAM_SERVICE_SECTION, - PARAM_NAME, PARAM_VERSION, - PARAM_CLOUD, PARAM_UPDATED_DATE]) +@cli_response(attributes_order=attributes_order) def describe(ctx: ContextObj, customer_id, rule_name, cloud, git_project_id, git_ref, limit, next_token): @@ -40,7 +42,7 @@ def describe(ctx: ContextObj, customer_id, return response('--git_project_id must be provided with --git_ref') return ctx['api_client'].rule_get( rule=rule_name, - customer=customer_id, + customer_id=customer_id, cloud=cloud, git_project_id=git_project_id, git_ref=git_ref, @@ -51,7 +53,6 @@ def describe(ctx: ContextObj, customer_id, @rule.command(cls=ViewCommand, name='update') @build_rule_source_id_option(required=False) -@customer_option @cli_response() def update(ctx: ContextObj, rule_source_id, customer_id): """ @@ -59,7 +60,7 @@ def update(ctx: ContextObj, rule_source_id, customer_id): """ return ctx['api_client'].trigger_rule_meta_updater( rule_source_id=rule_source_id, - customer=customer_id, + customer_id=customer_id, ) @@ -73,7 +74,6 @@ def update(ctx: ContextObj, rule_source_id, customer_id): help='Project id of git repo to delete rules') @click.option('--git_ref', '-gr', required=False, type=str, help='Branch of git repo to delete rules') -@customer_option @cli_response() def delete(ctx: ContextObj, customer_id, rule_name, cloud, git_project_id, git_ref): @@ -81,7 +81,7 @@ def delete(ctx: ContextObj, customer_id, rule_name, cloud, Deletes rules within your customer """ return ctx['api_client'].rule_delete( - customer=customer_id, + customer_id=customer_id, rule=rule_name, cloud=cloud, git_project_id=git_project_id, diff --git a/c7n/c7ncli/group/ruleset.py b/c7n/c7ncli/group/ruleset.py index a4728c65f..5f4c8ae88 100644 --- a/c7n/c7ncli/group/ruleset.py +++ b/c7n/c7ncli/group/ruleset.py @@ -1,18 +1,10 @@ import click -from c7ncli.group import cli_response, \ - ViewCommand, customer_option, response, ContextObj +from c7ncli.group import ContextObj, ViewCommand, cli_response, response from c7ncli.group.ruleset_eventdriven import eventdriven -from c7ncli.service.constants import PARAM_NAME, \ - PARAM_VERSION, PARAM_CLOUD, PARAM_STATUS_CODE, PARAM_ACTIVATION_DATE, \ - PARAM_STATUS_REASON, PARAM_RULES_NUMBER, PARAM_EVENT_DRIVEN, PARAM_ID, \ - PARAM_CUSTOMER, RULE_CLOUDS +from c7ncli.service.constants import RULE_CLOUDS -response_attributes_order = [ - PARAM_ID, PARAM_CUSTOMER, PARAM_NAME, PARAM_VERSION, PARAM_CLOUD, - PARAM_RULES_NUMBER, PARAM_STATUS_CODE, PARAM_STATUS_REASON, - PARAM_ACTIVATION_DATE, PARAM_EVENT_DRIVEN -] +attributes_order = 'name', 'version', 'cloud', 'licensed', 'license_manager_id' @click.group(name='ruleset') @@ -30,11 +22,10 @@ def ruleset(): @click.option('--get_rules', '-r', is_flag=True, help='If specified, ruleset\'s rules ids will be returned. ' 'MAKE SURE to use \'--json\' flag to get a clear output ') -@customer_option @click.option('--licensed', '-ls', type=bool, help='If True, only licensed rule-sets are returned. ' 'If False, only standard rule-sets') -@cli_response(attributes_order=response_attributes_order) +@cli_response(attributes_order=attributes_order) def describe(ctx: ContextObj, name, version, active, cloud, get_rules, customer_id, licensed): """ @@ -47,7 +38,7 @@ def describe(ctx: ContextObj, name, version, active, cloud, get_rules, name=name, version=version, cloud=cloud, - customer=customer_id, + customer_id=customer_id, get_rules=get_rules, active=active, licensed=licensed @@ -56,9 +47,10 @@ def describe(ctx: ContextObj, name, version, active, cloud, get_rules, @ruleset.command(cls=ViewCommand, name='add') @click.option('--name', '-n', type=str, required=True, help='Ruleset name') -@click.option('--version', '-v', type=float, default=1.0) +@click.option('--version', '-v', type=float, default=1.0, + help='Ruleset version') @click.option('--cloud', '-c', type=click.Choice(RULE_CLOUDS), - required=True) + required=True, help='Ruleset cloud') @click.option('--rule', '-r', multiple=True, required=False, help='Rule ids to attach to the ruleset. ' 'Multiple ids can be specified') @@ -76,15 +68,11 @@ def describe(ctx: ContextObj, name, version, active, cloud, get_rules, help='Filter rules by severity') @click.option('--mitre', '-m', type=str, multiple=True, help='Filter rules by mitre') -@click.option('--allow_tenant', '-at', type=str, multiple=True, - help='Allow ruleset for tenant. ' - 'Your user must have access to tenant') -@customer_option -@cli_response(attributes_order=response_attributes_order) +@cli_response(attributes_order=attributes_order) def add(ctx: ContextObj, name: str, version: float, cloud: str, rule: tuple, git_project_id: str, git_ref: str, active: bool, standard: tuple, service_section: tuple, - severity: tuple, mitre: tuple, allow_tenant: tuple, customer_id: str): + severity: tuple, mitre: tuple, customer_id: str): """ Creates Customers ruleset. """ @@ -102,8 +90,7 @@ def add(ctx: ContextObj, name: str, version: float, cloud: str, rule: tuple, service_section=service_section, severity=severity, mitre=mitre, - tenant_allowance=allow_tenant, - customer=customer_id + customer_id=customer_id ) @@ -119,38 +106,25 @@ def add(ctx: ContextObj, name: str, version: float, cloud: str, rule: tuple, 'Multiple values allowed') @click.option('--active', '-act', type=bool, required=False, help='Force set/unset ruleset version as active') -@click.option('--allow_tenant', '-at', type=str, multiple=True, - help='Allow ruleset for tenant. ' - 'Your user must have access to tenant') -@click.option('--restrict_tenant', '-rt', type=str, multiple=True, - help='Restrict ruleset for tenant. ' - 'Your user must have access to tenant') -@customer_option -@cli_response(attributes_order=response_attributes_order) +@cli_response(attributes_order=attributes_order) def update(ctx: ContextObj, customer_id, name, version, attach_rules, - detach_rules, active, - allow_tenant, restrict_tenant): + detach_rules, active): """ Updates Customers ruleset. """ - if not (attach_rules or detach_rules or isinstance(active, bool) or - allow_tenant or restrict_tenant): + if not (attach_rules or detach_rules or isinstance(active, bool)): return response( 'At least one of the following arguments must be ' 'provided: \'--attach_rules\', \'--detach_rules\',' - ' \'--active\', ' - '\'--allow_tenant\' or ' - '\'--restrict_tenant\'.') + ' \'--active\'') return ctx['api_client'].ruleset_update( - customer=customer_id, - ruleset_name=name, + name=name, version=version, rules_to_attach=attach_rules, rules_to_detach=detach_rules, active=active, - tenant_allowance=allow_tenant, - tenant_restriction=restrict_tenant + customer_id=customer_id, ) @@ -158,7 +132,6 @@ def update(ctx: ContextObj, customer_id, name, version, attach_rules, @click.option('--name', '-n', type=str, required=True, help='Ruleset name') @click.option('--version', '-v', type=float, required=True, help='Ruleset version') -@customer_option @cli_response() def delete(ctx: ContextObj, customer_id, name, version): """ @@ -166,8 +139,8 @@ def delete(ctx: ContextObj, customer_id, name, version): inactive """ return ctx['api_client'].ruleset_delete( - customer=customer_id, - ruleset_name=name, + customer_id=customer_id, + name=name, version=version ) diff --git a/c7n/c7ncli/group/ruleset_eventdriven.py b/c7n/c7ncli/group/ruleset_eventdriven.py index 6fff1cf3f..11e610e27 100644 --- a/c7n/c7ncli/group/ruleset_eventdriven.py +++ b/c7n/c7ncli/group/ruleset_eventdriven.py @@ -1,6 +1,6 @@ import click -from c7ncli.group import cli_response, ViewCommand, ContextObj +from c7ncli.group import ContextObj, ViewCommand, cli_response from c7ncli.service.constants import RULE_CLOUDS @@ -24,7 +24,7 @@ def eventdriven(): '--full_cloud or --standard flags') @cli_response() def add(ctx: ContextObj, version: float, cloud: str, - rule_id: tuple, rule_version: str): + rule_id: tuple, rule_version: str, customer_id): """ Creates Event-driven ruleset with all the rules """ @@ -44,11 +44,14 @@ def add(ctx: ContextObj, version: float, cloud: str, help='If specified, ruleset\'s rules ids will be returned. ' 'MAKE SURE to use \'--json\' flag to get a clear output') @cli_response() -def describe(ctx: ContextObj, cloud: str, get_rules: bool): +def describe(ctx: ContextObj, cloud: str, get_rules: bool, customer_id): """ Describes Event-driven ruleset """ - return ctx['api_client'].ed_ruleset_get(cloud=cloud, get_rules=get_rules) + return ctx['api_client'].ed_ruleset_get( + cloud=cloud, + get_rules=get_rules + ) @eventdriven.command(cls=ViewCommand, name='delete') @@ -58,7 +61,7 @@ def describe(ctx: ContextObj, cloud: str, get_rules: bool): @click.option('--version', '-v', type=float, default=1.0, help='Event-driven ruleset version to delete') @cli_response() -def delete(ctx: ContextObj, cloud: str, version: float, ): +def delete(ctx: ContextObj, cloud: str, version: float, customer_id): """ Deletes Event-driven ruleset """ diff --git a/c7n/c7ncli/group/rulesource.py b/c7n/c7ncli/group/rulesource.py index e48bf0b20..a38b362d5 100644 --- a/c7n/c7ncli/group/rulesource.py +++ b/c7n/c7ncli/group/rulesource.py @@ -3,19 +3,12 @@ import click from c7ncli.group import cli_response, ViewCommand, \ - customer_option, response, ContextObj, \ + response, ContextObj, \ build_rule_source_id_option -from c7ncli.service.constants import GIT_ACCESS_TOKEN, \ - PARAM_GIT_URL, PARAM_GIT_REF, PARAM_GIT_ACCESS_TYPE, \ - PARAM_GIT_RULES_PREFIX, PARAM_ID, PARAM_CUSTOMER, \ - PARAM_LAST_SYNC_CURRENT_STATUS, PARAM_GIT_PROJECT_ID +from c7ncli.service.constants import GIT_ACCESS_TOKEN -response_attributes_order = [ - PARAM_ID, PARAM_CUSTOMER, PARAM_GIT_PROJECT_ID, - PARAM_GIT_URL, PARAM_GIT_REF, - PARAM_GIT_RULES_PREFIX, PARAM_GIT_ACCESS_TYPE, - PARAM_LAST_SYNC_CURRENT_STATUS, -] +attributes_order = ('id', 'type', 'description', 'git_project_id', 'git_url', + 'git_ref', 'git_rules_prefix') @click.group(name='rulesource') @@ -27,16 +20,15 @@ def rulesource(): @build_rule_source_id_option(required=False) @click.option('--git_project_id', '-gpid', type=str, required=False, help='Git project id to describe rule source') -@customer_option -@cli_response(attributes_order=response_attributes_order) +@cli_response(attributes_order=attributes_order) def describe(ctx: ContextObj, rule_source_id: Optional[str] = None, customer_id=None, git_project_id=None): """ Describes rule source """ return ctx['api_client'].rule_source_get( - rule_source_id=rule_source_id, - customer=customer_id, + id=rule_source_id, + customer_id=customer_id, git_project_id=git_project_id ) @@ -51,19 +43,13 @@ def describe(ctx: ContextObj, rule_source_id: Optional[str] = None, show_default=True, help='Name of the branch to grab rules from') @click.option('--git_rules_prefix', '-gprefix', type=str, default='/', help='Rules path prefix', show_default=True) -# todo uncomment when other access types are available @click.option('--git_access_secret', '-gsecret', type=str, help='Secret token to be able to access the repository') -@click.option('--allow_tenant', '-at', type=str, multiple=True, - help='Allow ruleset for tenant. ' - 'Your user must have access to tenant') @click.option('--description', '-d', type=str, required=True, help='Human-readable description or the repo') -@customer_option -@cli_response(attributes_order=response_attributes_order, - secured_params=['git_access_secret']) +@cli_response(attributes_order=attributes_order) def add(ctx: ContextObj, git_project_id, git_url, git_ref, git_rules_prefix, - git_access_secret, allow_tenant, description, customer_id): + git_access_secret, description, customer_id): """ Creates rule source """ @@ -75,57 +61,42 @@ def add(ctx: ContextObj, git_project_id, git_url, git_ref, git_rules_prefix, git_rules_prefix=git_rules_prefix, git_access_type=GIT_ACCESS_TOKEN, git_access_secret=git_access_secret, - tenant_allowance=allow_tenant, description=description, - customer=customer_id + customer_id=customer_id ) @rulesource.command(cls=ViewCommand, name='update') @build_rule_source_id_option(required=True) @click.option('--git_access_secret', '-gsecret', type=str, required=False) -@click.option('--allow_tenant', '-at', type=str, multiple=True, - help='Allow ruleset for tenant. ' - 'Your user must have access to tenant') -@click.option('--restrict_tenant', '-rt', type=str, multiple=True, - help='Restrict ruleset for tenant. ' - 'Your user must have access to tenant') @click.option('--description', '-d', type=str, help='Human-readable description or the repo') -@customer_option -@cli_response(attributes_order=response_attributes_order) +@cli_response(attributes_order=attributes_order) def update(ctx: ContextObj, rule_source_id, - git_access_secret, allow_tenant, - restrict_tenant, description, customer_id): + git_access_secret, description, customer_id): """Updates rule source""" - if not (git_access_secret or allow_tenant or restrict_tenant or - description): + if not (git_access_secret or description): return response( 'At least one of these parameters must be given' - ': \'--git_access_secret\', ' - '\'--allow_tenant\', \'--description\' or ' - '\'--restrict_tenant\'.') + ': \'--git_access_secret\' or \'--description\'') return ctx['api_client'].rule_source_patch( id=rule_source_id, git_access_type=GIT_ACCESS_TOKEN, git_access_secret=git_access_secret, - tenant_allowance=list(allow_tenant), - tenant_restriction=list(restrict_tenant), - customer=customer_id, + customer_id=customer_id, description=description ) @rulesource.command(cls=ViewCommand, name='delete') @build_rule_source_id_option(required=True) -@customer_option @cli_response() def delete(ctx: ContextObj, rule_source_id, customer_id): """ Deletes rule source """ return ctx['api_client'].rule_source_delete( - rule_source_id=rule_source_id, - customer=customer_id + id=rule_source_id, + customer_id=customer_id ) diff --git a/c7n/c7ncli/group/setting.py b/c7n/c7ncli/group/setting.py index f4e267eda..be4c74f27 100644 --- a/c7n/c7ncli/group/setting.py +++ b/c7n/c7ncli/group/setting.py @@ -2,6 +2,7 @@ from c7ncli.group.setting_lm import lm from c7ncli.group.setting_mail import mail +from c7ncli.group.setting_report import report @click.group(name='setting') @@ -11,3 +12,4 @@ def setting(): setting.add_command(mail) setting.add_command(lm) +setting.add_command(report) diff --git a/c7n/c7ncli/group/setting_lm.py b/c7n/c7ncli/group/setting_lm.py index 51d1d2124..b304fef6f 100644 --- a/c7n/c7ncli/group/setting_lm.py +++ b/c7n/c7ncli/group/setting_lm.py @@ -1,6 +1,7 @@ import click -from c7ncli.group.setting_lm_config import config + from c7ncli.group.setting_lm_client import client +from c7ncli.group.setting_lm_config import config @click.group(name='lm') diff --git a/c7n/c7ncli/group/setting_lm_client.py b/c7n/c7ncli/group/setting_lm_client.py index 0a61d0634..8a2c9c960 100644 --- a/c7n/c7ncli/group/setting_lm_client.py +++ b/c7n/c7ncli/group/setting_lm_client.py @@ -1,7 +1,6 @@ import click -from c7ncli.group import cli_response, ViewCommand, ContextObj -PARAM_PEM = 'PEM' +from c7ncli.group import ContextObj, ViewCommand, cli_response @click.group(name='client') @@ -10,38 +9,40 @@ def client(): @client.command(cls=ViewCommand, name='describe') -@click.option('--format', '-f', type=click.Choice([PARAM_PEM]), - default=PARAM_PEM, show_default=PARAM_PEM, - help='Format of the private-key.') @cli_response() -def describe(ctx: ContextObj, **kwargs): +def describe(ctx: ContextObj, customer_id): """ Describe current License Manager client-key data """ - kwargs['frmt'] = kwargs.pop('format') - return ctx['api_client'].lm_client_setting_get(**kwargs) + return ctx['api_client'].lm_client_setting_get( + customer_id=customer_id + ) @client.command(cls=ViewCommand, name='add') @click.option('--key_id', '-kid', type=str, required=True, help='Key-id granted by the License Manager.') -@click.option('--algorithm', '-alg', type=str, +@click.option('--algorithm', '-alg', type=str, default='ECC:p521_DSS_SHA:256', + show_default=True, help='Algorithm granted by the License Manager.', required=True) @click.option('--private_key', '-prk', type=str, required=True, help='Private-key granted by the License Manager.') -@click.option('--format', '-f', type=click.Choice([PARAM_PEM]), - default=PARAM_PEM, show_default=PARAM_PEM, - help='Format of the private-key.') @click.option('--b64encoded', '-b64', is_flag=True, default=False, help='Specify whether the private is b64encoded.') -@cli_response(secured_params=['private_key', 'key_id']) -def add(ctx: ContextObj, **kwargs): +@cli_response() +def add(ctx: ContextObj, key_id, algorithm, private_key, b64encoded, + customer_id): """ Adds License Manager provided client-key data """ - kwargs['frmt'] = kwargs.pop('format') - return ctx['api_client'].lm_client_setting_post(**kwargs) + return ctx['api_client'].lm_client_setting_post( + key_id=key_id, + algorithm=algorithm, + private_key=private_key, + b64_encoded=b64encoded, + customer_id=customer_id + ) @client.command(cls=ViewCommand, name='delete') @@ -49,12 +50,13 @@ def add(ctx: ContextObj, **kwargs): type=str, required=True, help='Key-id granted by the License Manager.') @cli_response() -def delete(ctx: ContextObj, key_id: str): +def delete(ctx: ContextObj, key_id: str, customer_id): """ Removes current License Manager client-key data """ return ctx['api_client'].lm_client_setting_delete( - key_id=key_id + key_id=key_id, + customer_id=customer_id ) diff --git a/c7n/c7ncli/group/setting_lm_config.py b/c7n/c7ncli/group/setting_lm_config.py index 0e369f2f9..8050d178f 100644 --- a/c7n/c7ncli/group/setting_lm_config.py +++ b/c7n/c7ncli/group/setting_lm_config.py @@ -1,5 +1,6 @@ import click -from c7ncli.group import cli_response, ViewCommand, ContextObj + +from c7ncli.group import ContextObj, ViewCommand, cli_response @click.group(name='config') @@ -9,7 +10,7 @@ def config(): @config.command(cls=ViewCommand, name='describe') @cli_response() -def describe(ctx: ContextObj): +def describe(ctx: ContextObj, customer_id): """ Describes current License Manager access configuration data """ @@ -23,7 +24,7 @@ def describe(ctx: ContextObj): help='License Manager host. You can specify the full url here') @click.option('--port', '-p', type=int, help='License Manager port.', required=False) -@click.option('--protocol', '-pr', type=click.Choice(['HTTP', 'HTTPS']), +@click.option('--protocol', '-pr', type=click.Choice(('HTTP', 'HTTPS')), help='License manager protocol') @click.option('--stage', '-st', type=str, help='Path prefix') @@ -39,7 +40,7 @@ def add(ctx: ContextObj, **kwargs): @config.command(cls=ViewCommand, name='delete') @click.option('--confirm', is_flag=True, help='Confirms the action.') @cli_response() -def delete(ctx: ContextObj, confirm: bool): +def delete(ctx: ContextObj, confirm: bool, customer_id): """ Removes current License Manager access configuration data """ diff --git a/c7n/c7ncli/group/setting_mail.py b/c7n/c7ncli/group/setting_mail.py index 8d56daae6..51121b1b7 100644 --- a/c7n/c7ncli/group/setting_mail.py +++ b/c7n/c7ncli/group/setting_mail.py @@ -1,6 +1,6 @@ import click -from c7ncli.group import cli_response, ViewCommand, response, ContextObj +from c7ncli.group import ContextObj, ViewCommand, cli_response, response @click.group(name='mail') @@ -11,8 +11,8 @@ def mail(): @mail.command(cls=ViewCommand, name='describe') @click.option('--display_password', '-dp', is_flag=True, default=False, help='Specify to whether display a configured password.') -@cli_response(attributes_order=[]) -def describe(ctx: ContextObj, display_password: bool): +@cli_response() +def describe(ctx: ContextObj, display_password: bool, customer_id): """ Describes Custodian Service Mail configuration """ @@ -37,10 +37,10 @@ def describe(ctx: ContextObj, display_password: bool): 'Defaults to \'--username\'.') @click.option('--emails_per_session', '-eps', type=int, default=1, required=False, help='Amount of emails to send per session.') -@cli_response(secured_params=['password', 'password_label']) +@cli_response() def add(ctx: ContextObj, username, password, password_label, host, port, - use_tls, sender_name, emails_per_session): + use_tls, sender_name, emails_per_session, customer_id): """ Creates Custodian Service Mail configuration """ @@ -51,7 +51,8 @@ def add(ctx: ContextObj, username=username, password=password, password_alias=password_label, - host=host, port=port, + host=host, + port=port, use_tls=use_tls, default_sender=sender_name or username, max_emails=emails_per_session @@ -61,7 +62,7 @@ def add(ctx: ContextObj, @mail.command(cls=ViewCommand, name='delete') @click.option('--confirm', is_flag=True, help='Confirms the action.') @cli_response() -def delete(ctx: ContextObj, confirm: bool): +def delete(ctx: ContextObj, confirm: bool, customer_id): """ Deletes Custodian Service Mail configuration """ diff --git a/c7n/c7ncli/group/setting_report.py b/c7n/c7ncli/group/setting_report.py new file mode 100644 index 000000000..f496167b1 --- /dev/null +++ b/c7n/c7ncli/group/setting_report.py @@ -0,0 +1,32 @@ +import click + +from c7ncli.group import ContextObj, ViewCommand, cli_response + + +@click.group(name='report') +def report(): + """Manages Custodian Service Mail configuration""" + + +@report.command(cls=ViewCommand, name='enable_sending') +@click.option('--confirm', is_flag=True, help='Confirms the action.') +@cli_response() +def enable_sending(ctx: ContextObj, confirm: bool, customer_id): + """ + Enables Custodian Service report sending mechanism + """ + if not confirm: + raise click.UsageError('Please, specify `--confirm` flag') + return ctx['api_client'].reports_sending_setting_enable() + + +@report.command(cls=ViewCommand, name='disable_sending') +@click.option('--confirm', is_flag=True, help='Confirms the action.') +@cli_response() +def disable_sending(ctx: ContextObj, confirm: bool, customer_id): + """ + Disables Custodian Service report sending mechanism + """ + if not confirm: + raise click.UsageError('Please, specify `--confirm` flag') + return ctx['api_client'].reports_sending_setting_disable() diff --git a/c7n/c7ncli/group/siem.py b/c7n/c7ncli/group/siem.py deleted file mode 100644 index 6248d0594..000000000 --- a/c7n/c7ncli/group/siem.py +++ /dev/null @@ -1,17 +0,0 @@ -import click - -from c7ncli.group.siem_add import add -from c7ncli.group.siem_delete import delete -from c7ncli.group.siem_describe import describe -from c7ncli.group.siem_update import update - - -@click.group(name='siem') -def siem(): - """Manages SIEM configuration""" - - -siem.add_command(add) -siem.add_command(update) -siem.add_command(describe) -siem.add_command(delete) diff --git a/c7n/c7ncli/group/siem_add.py b/c7n/c7ncli/group/siem_add.py deleted file mode 100644 index c5ce10dbd..000000000 --- a/c7n/c7ncli/group/siem_add.py +++ /dev/null @@ -1,100 +0,0 @@ -import click - -from c7ncli.group import cli_response, ViewCommand, tenant_option, ContextObj -from c7ncli.service.constants import \ - PARAM_PRODUCT_TYPE_NAME, PARAM_PRODUCT_NAME, PARAM_ENGAGEMENT_NAME, \ - PARAM_TEST_TITLE, PARAM_DOJO_APIKEY, PARAM_DOJO_HOST, PARAM_DOJO_USER, \ - PARAM_DOJO_UPLOAD_FILES, PARAM_DOJO_DISPLAY_ALL_FIELDS, \ - PARAM_DOJO_RESOURCE_PER_FINDING - - -@click.group(name='add') -def add(): - """Manages SIEM configuration create action""" - - -@add.command(cls=ViewCommand, name='dojo') -@tenant_option -@click.option('--host', '-h', type=str, required=True, - help='DefectDojo host:port') -@click.option('--api_key', '-key', type=str, required=True, - help='DefectDojo API key') -@click.option('--user', '-u', type=str, required=True, default='admin', - help='DefectDojo user name') -@click.option('--display_all_fields', '-ALL', is_flag=True, default=False, - help='Flag for displaying all fields') -@click.option('--upload_files', '-U', is_flag=True, default=False, - help='Flag for displaying a file for each resource with its ' - 'full description in the \"file\" field') -@click.option('--product_type_name', type=str, - help='DefectDojo\'s product type name. Customer\'s name will ' - 'be used by default: \'{customer}\'') -@click.option('--product_name', type=str, - help='DefectDojo\'s product name. ' - 'Tenant and account names will be used by ' - 'default: \'{tenant} - {account}\'') -@click.option('--engagement_name', type=str, - help='DefectDojo\'s engagement name. Account name and day\'s ' - 'date scope will be used by default: ' - '\'{account}: {day_scope}\'') -@click.option('--test_title', type=str, - help='Tests\' title name in DefectDojo. Job\'s date scope ' - 'and job id will be used by default: ' - '\'{job_scope}: {job_id}\'') -@click.option('--resource_per_finding', is_flag=True, - help='Specify if you want each finding to represent a separate ' - 'violated resource') -@cli_response(secured_params=['api_key']) -def dojo(ctx: ContextObj, tenant_name, host, api_key, user, display_all_fields, - upload_files, product_type_name, product_name, engagement_name, - test_title, resource_per_finding): - """ - Adds dojo configuration. When you specify '--product_type_name', - '--product_name', '--engagement_name', '--test_title', you can use these - special key-words: 'customer', 'tenant', 'job_id', 'day_scope', - 'job_scope' inside curly braces to map the entities. - Example: 'c7n siem add dojo ... --product_name - "Product {tenant}: {day_scope}"' - """ - entities_mapping = { - PARAM_PRODUCT_TYPE_NAME: product_type_name, - PARAM_PRODUCT_NAME: product_name, - PARAM_ENGAGEMENT_NAME: engagement_name, - PARAM_TEST_TITLE: test_title - } - entities_mapping = {k: v for k, v in entities_mapping.items() if v} - return ctx['api_client'].siem_dojo_post( - tenant_name=tenant_name, - configuration={ - PARAM_DOJO_HOST: host, - PARAM_DOJO_APIKEY: api_key, - PARAM_DOJO_USER: user, - PARAM_DOJO_DISPLAY_ALL_FIELDS: display_all_fields, - PARAM_DOJO_UPLOAD_FILES: upload_files, - PARAM_DOJO_RESOURCE_PER_FINDING: resource_per_finding - }, entities_mapping=entities_mapping) - - -@add.command(cls=ViewCommand, name='security_hub') -@tenant_option -@click.option('--region', '-r', type=str, required=True, - help='AWS region name') -@click.option('--product_arn', '-p', type=str, required=True, - help='ARN of security product') -@click.option('--trusted_role_arn', '-tra', type=str, required=False, - help='Role that will be assumed to upload findings') -@cli_response() -def security_hub(ctx: ContextObj, tenant_name, region, product_arn, - trusted_role_arn): - """ - Adds security hub configuration. - """ - configuration = { - 'aws_region': region, - 'product_arn': product_arn, - } - if trusted_role_arn: - configuration['trusted_role_arn'] = trusted_role_arn - return ctx['api_client'].siem_security_hub_post( - tenant_name=tenant_name, - configuration=configuration) diff --git a/c7n/c7ncli/group/siem_delete.py b/c7n/c7ncli/group/siem_delete.py deleted file mode 100644 index a13105f3b..000000000 --- a/c7n/c7ncli/group/siem_delete.py +++ /dev/null @@ -1,32 +0,0 @@ -import click -from c7ncli.group import ViewCommand, cli_response, tenant_option, ContextObj -from c7ncli.service.constants import PARAM_DOJO, SECURITY_HUB_COMMAND_NAME - - -@click.group(name='delete') -def delete(): - """Manages SIEM configuration delete action""" - - -@delete.command(cls=ViewCommand, name='dojo') -@tenant_option -@cli_response() -def dojo(ctx: ContextObj, tenant_name): - """ - Deletes DefectDojo SIEM configuration. - """ - return ctx['api_client'].siem_delete(tenant_name=tenant_name, - configuration_type=PARAM_DOJO) - - -@delete.command(cls=ViewCommand, name='security_hub') -@tenant_option -@cli_response() -def security_hub(ctx: ContextObj, tenant_name): - """ - Deletes Security Hub SIEM configuration. - """ - return ctx['api_client'].siem_delete( - tenant_name=tenant_name, - configuration_type=SECURITY_HUB_COMMAND_NAME - ) diff --git a/c7n/c7ncli/group/siem_describe.py b/c7n/c7ncli/group/siem_describe.py deleted file mode 100644 index 477985a93..000000000 --- a/c7n/c7ncli/group/siem_describe.py +++ /dev/null @@ -1,32 +0,0 @@ -import click - -from c7ncli.group import ViewCommand, cli_response, tenant_option, ContextObj -from c7ncli.service.constants import PARAM_DOJO, SECURITY_HUB_COMMAND_NAME - - -@click.group(name='describe') -def describe(): - """Manages SIEM configuration describe action""" - - -@describe.command(cls=ViewCommand, name='dojo') -@tenant_option -@cli_response() -def dojo(ctx: ContextObj, tenant_name): - """ - Describes DefectDojo SIEM configuration of a customer. - """ - return ctx['api_client'].siem_get(tenant_name=tenant_name, - configuration_type=PARAM_DOJO) - - -@describe.command(cls=ViewCommand, name='security_hub') -@tenant_option -@cli_response() -def security_hub(ctx: ContextObj, tenant_name): - """ - Describes Security Hub SIEM configuration of a customer. - """ - return ctx['api_client'].siem_get( - tenant_name=tenant_name, - configuration_type=SECURITY_HUB_COMMAND_NAME) diff --git a/c7n/c7ncli/group/siem_update.py b/c7n/c7ncli/group/siem_update.py deleted file mode 100644 index 4d231c62d..000000000 --- a/c7n/c7ncli/group/siem_update.py +++ /dev/null @@ -1,104 +0,0 @@ -import click - -from c7ncli.group import cli_response, ViewCommand, tenant_option, ContextObj -from c7ncli.service.constants import \ - PARAM_PRODUCT_TYPE_NAME, PARAM_PRODUCT_NAME, PARAM_ENGAGEMENT_NAME, \ - PARAM_TEST_TITLE, PARAM_DOJO_APIKEY, PARAM_DOJO_HOST, PARAM_DOJO_USER, \ - PARAM_DOJO_UPLOAD_FILES, PARAM_DOJO_DISPLAY_ALL_FIELDS, \ - PARAM_DOJO_RESOURCE_PER_FINDING - - -@click.group(name='update') -def update(): - """Manages SIEM configuration update action""" - - -@update.command(cls=ViewCommand, name='dojo') -@tenant_option -@click.option('--host', '-h', type=str, required=False, - help='DefectDojo host:port') -@click.option('--api_key', '-key', type=str, required=False, - help='DefectDojo API key') -@click.option('--user', '-u', type=str, required=False, default='admin', - help='DefectDojo user name') -@click.option('--display_all_fields', '-ALL', type=bool, required=False, - help='Flag for displaying all fields') -@click.option('--upload_files', '-U', type=bool, required=False, - help='Flag for displaying a file for each resource with its ' - 'full description in the \"file\" field') -@click.option('--product_type_name', type=str, - help='DefectDojo\'s product type name. Customer\'s name will ' - 'be used by default: \'{customer}\'') -@click.option('--product_name', type=str, - help='DefectDojo\'s product name. ' - 'Tenant and account names will be used by ' - 'default: \'{tenant} - {account}\'') -@click.option('--engagement_name', type=str, - help='DefectDojo\'s engagement name. Account name and day\'s ' - 'date scope will be used by default: ' - '\'{account}: {day_scope}\'') -@click.option('--test_title', type=str, - help='Tests\' title name in DefectDojo. Job\'s date scope ' - 'and job id will be used by default: ' - '\'{job_scope}: {job_id}\'') -@click.option('--clear_existing_mapping', is_flag=True, - help='Clear the existing entities mapping configuration so ' - 'that you can use the default one') -@click.option('--resource_per_finding', type=bool, - help='Specify if you want each finding to represent a separate ' - 'violated resource') -@cli_response(secured_params=['api_key']) -def dojo(ctx: ContextObj, tenant_name, host, api_key, user, display_all_fields, - upload_files, product_type_name, product_name, engagement_name, - test_title, clear_existing_mapping, resource_per_finding): - """ - Updates dojo configuration. When you specify '--product_type_name', - '--product_name', '--engagement_name', '--test_title', you can use these - special key-words: 'customer', 'tenant', 'account', 'job_id', 'day_scope', - 'job_scope' inside curly braces to map the entities. - Example: 'c7n siem add dojo ... --product_name - "Product {account}: {day_scope}"' - """ - entities_mapping = { - PARAM_PRODUCT_TYPE_NAME: product_type_name, - PARAM_PRODUCT_NAME: product_name, - PARAM_ENGAGEMENT_NAME: engagement_name, - PARAM_TEST_TITLE: test_title - } - entities_mapping = {k: v for k, v in entities_mapping.items() if v} - return ctx['api_client'].siem_dojo_patch( - tenant_name=tenant_name, configuration={ - PARAM_DOJO_HOST: host, - PARAM_DOJO_APIKEY: api_key, - PARAM_DOJO_USER: user, - PARAM_DOJO_DISPLAY_ALL_FIELDS: display_all_fields, - PARAM_DOJO_UPLOAD_FILES: upload_files, - PARAM_DOJO_RESOURCE_PER_FINDING: resource_per_finding - }, entities_mapping=entities_mapping, - clear_existing_mapping=clear_existing_mapping) - - -@update.command(cls=ViewCommand, name='security_hub') -@tenant_option -@click.option('--region', '-r', type=str, required=False, - help='AWS region name') -@click.option('--product_arn', '-p', type=str, required=False, - help='ARN of security product') -@click.option('--trusted_role_arn', '-tra', type=str, required=False, - help='Role that will be assumed to upload findings') -@cli_response() -def security_hub(ctx: ContextObj, tenant_name, region, product_arn, - trusted_role_arn): - """ - Updates security hub configuration. - """ - - configuration = { - 'aws_region': region, - 'product_arn': product_arn, - 'trusted_role_arn': trusted_role_arn - } - configuration = {k: v for k, v in configuration.items() if v} - return ctx['api_client'].siem_security_hub_patch( - tenant_name=tenant_name, - configuration=configuration) diff --git a/c7n/c7ncli/group/tenant.py b/c7n/c7ncli/group/tenant.py index d7d4291c0..bb3e8a419 100644 --- a/c7n/c7ncli/group/tenant.py +++ b/c7n/c7ncli/group/tenant.py @@ -1,14 +1,24 @@ -import click from typing import Optional -from c7ncli.group import cli_response, ViewCommand, limit_option, \ - next_option, ContextObj, tenant_option, build_tenant_option, \ - account_option, build_account_option, customer_option -from c7ncli.group.tenant_findings import findings -from c7ncli.group.tenant_region import region_group +import click + +from c7ncli.group import ( + ContextObj, + ViewCommand, + account_option, + build_limit_option, + build_tenant_option, + cli_response, + limit_option, + next_option, + response, + tenant_option, +) +from c7ncli.service.constants import ModularCloud from c7ncli.group.tenant_credentials import credentials -from c7ncli.service.constants import PARAM_CUSTOMER_DISPLAY_NAME, \ - PARAM_ACTIVATION_DATE, PARAM_INHERIT, PARAM_NAME, AWS, GOOGLE, AZURE + + +attributes_order = 'name', 'account_id', 'id_active', 'regions' @click.group(name='tenant') @@ -19,86 +29,85 @@ def tenant(): @tenant.command(cls=ViewCommand, name='describe') @tenant_option @account_option -@customer_option -@click.option('--full', '-f', is_flag=True, - help='Show full command output.') +@click.option('--active', '-act', type=bool, required=False, + help='Type of tenants to return') +@click.option('--cloud', '-c', type=click.Choice(tuple(ModularCloud.iter())), + required=False, help='Specific cloud to describe tenants') @limit_option @next_option -@cli_response(attributes_order=[PARAM_NAME, PARAM_CUSTOMER_DISPLAY_NAME, - PARAM_ACTIVATION_DATE, PARAM_INHERIT]) -def describe(ctx: ContextObj, tenant_name, customer_id, account_number, - full, limit, next_token): +@cli_response(attributes_order=attributes_order) +def describe(ctx: ContextObj, tenant_name, account_number, active, cloud, + limit, next_token, customer_id): """ - Describes your user's tenant(s) + Describes tenants within your customer """ - return ctx['api_client'].tenant_get( - tenant_name=tenant_name, - customer_name=customer_id, - cloud_identifier=account_number, - complete=full, + if tenant_name and account_number: + return response( + 'Either --tenant_name or --account_number can be given' + ) + if tenant_name: + return ctx['api_client'].tenant_get(tenant_name or account_number, + customer_id=customer_id) + return ctx['api_client'].tenant_query( + active=active, + cloud=cloud, limit=limit, - next_token=next_token + next_token=next_token, + customer_id=customer_id ) -@tenant.command(cls=ViewCommand, name='create') -@build_tenant_option(required=True, help='Name of the tenant') -@click.option('--display_name', '-dn', type=str, - help='Tenant display name. If not specified, the ' - 'value from --name is used') -@click.option('--cloud', '-c', type=click.Choice([AWS, AZURE, GOOGLE]), - required=True, help='Cloud of the tenant') -@build_account_option(required=True) -@click.option('--primary_contacts', '-pc', type=str, multiple=True, - help='Primary emails') -@click.option('--secondary_contacts', '-sc', type=str, multiple=True, - help='Secondary emails') -@click.option('--tenant_manager_contacts', '-tmc', type=str, multiple=True, - help='Tenant manager emails') -@click.option('--default_owner', '-do', type=str, - help='Owner email') +@tenant.command(cls=ViewCommand, name='active_licenses') +@build_tenant_option(required=True) +@build_limit_option(default=1, show_default=True) @cli_response() -def create(ctx: ContextObj, tenant_name: str, display_name: Optional[str], cloud: str, - account_number: str, primary_contacts: tuple, - secondary_contacts: tuple, tenant_manager_contacts: tuple, - default_owner: Optional[str]): +def active_licenses(ctx: ContextObj, tenant_name: str, limit: Optional[int], + customer_id): """ - Activates a tenant, if the environment does not restrict it. + Get tenant active licenses """ - return ctx['api_client'].tenant_post( - name=tenant_name, - display_name=display_name, - cloud=cloud, - cloud_identifier=account_number, - primary_contacts=list(primary_contacts), - secondary_contacts=list(secondary_contacts), - tenant_manager_contacts=list(tenant_manager_contacts), - default_owner=default_owner + return ctx['api_client'].tenant_get_active_licenses( + tenant_name, + limit=limit, + customer_id=customer_id ) -@tenant.command(cls=ViewCommand, name='update') -@tenant_option -@click.option('--rules_to_exclude', '-rte', type=str, multiple=True, - help='Rules to exclude for tenant') -@click.option('--rules_to_include', '-rti', type=str, multiple=True, - help='Rules to include for tenant') -# @click.option('--send_scan_result', '-ssr', type=bool, -# help='Specify whether to send scan results. Obsolete') +@tenant.command(cls=ViewCommand, name='set_excluded_rules') +@build_tenant_option(required=True) +@click.option('--rules', '-r', type=str, multiple=True, + help='Rules that you want to exclude for a tenant') +@click.option('--empty', is_flag=True, help='Whether to reset the ' + 'list of excluded rules') +@cli_response() +def set_excluded_rules(ctx: ContextObj, tenant_name: str, + rules: tuple[str, ...], empty: bool, + customer_id): + """ + Excludes rules for a tenant + """ + if not rules and not empty: + return response('Specify either --rules '' or --empty') + if empty: + rules = () + return ctx['api_client'].tenant_set_excluded_rules( + tenant_name=tenant_name, + rules=rules, + customer_id=customer_id + ) + + +@tenant.command(cls=ViewCommand, name='get_excluded_rules') +@build_tenant_option(required=True) @cli_response() -def update(ctx: ContextObj, tenant_name: Optional[str], - rules_to_exclude: tuple, - rules_to_include: tuple): +def get_excluded_rules(ctx: ContextObj, tenant_name: str, customer_id): """ - Updates settings of your user's tenant + Returns excluded rules for a tenant """ - return ctx['api_client'].tenant_patch( + return ctx['api_client'].tenant_get_excluded_rules( tenant_name=tenant_name, - rules_to_exclude=list(rules_to_exclude), - rules_to_include=list(rules_to_include), + customer_id=customer_id ) -tenant.add_command(findings) -tenant.add_command(region_group) -tenant.add_command(credentials) +tenant.add_command(credentials) \ No newline at end of file diff --git a/c7n/c7ncli/group/tenant_credentials.py b/c7n/c7ncli/group/tenant_credentials.py index ecfa6770d..01da4156d 100644 --- a/c7n/c7ncli/group/tenant_credentials.py +++ b/c7n/c7ncli/group/tenant_credentials.py @@ -1,95 +1,108 @@ import click -from c7ncli.group import cli_response, ViewCommand, ContextObj, \ - account_option, build_account_option -from c7ncli.service.constants import PARAM_CLOUD, PARAM_CLOUD_IDENTIFIER, \ - PARAM_ENABLED, PARAM_TRUSTED_ROLE_ARN +from c7ncli.group import ViewCommand, cli_response, ContextObj, limit_option, \ + next_option, build_tenant_option +from c7ncli.service.constants import AWS, AZURE, GOOGLE +from c7ncli.service.adapter_client import CustodianResponse @click.group(name='credentials') def credentials(): - """Manages Custodian Service Credentials Manager configs""" + """ + Allows to bind existing credentials applications to tenants + :return: + """ @credentials.command(cls=ViewCommand, name='describe') -@click.option('--cloud', '-c', type=str, - help='The cloud to which the credentials configuration belongs.') -@account_option -@cli_response(attributes_order=[PARAM_CLOUD_IDENTIFIER, - PARAM_CLOUD, - PARAM_ENABLED, - PARAM_TRUSTED_ROLE_ARN]) -def describe(ctx: ContextObj, cloud, account_number): +@click.option('--cloud', '-cl', type=click.Choice((AWS, AZURE, GOOGLE)), + help='Cloud to describe credentials by') +@click.option('--application_id', '-aid', type=str, + help='Application id to describe a concrete credentials item') +@limit_option +@next_option +@cli_response() +def describe(ctx: ContextObj, application_id, limit, next_token, cloud, + customer_id): """ - Describes Custodian Service Credentials Manager configurations. + Lists all available applications with credentials """ - return ctx['api_client'].credentials_manager_get( + if not any((cloud, application_id)): + return CustodianResponse.build( + 'Provide either --application_id or --cloud' + ) + if application_id: + return ctx['api_client'].get_credentials(application_id) + return ctx['api_client'].query_credentials( cloud=cloud, - cloud_identifier=account_number + limit=limit, + next_token=next_token, + customer_id=customer_id, ) -@credentials.command(cls=ViewCommand, name='add') -@click.option('--cloud', '-c', type=click.Choice(['AWS', 'AZURE', 'GCP']), - required=True, - help='The cloud to which the credentials configuration belongs.') -@build_account_option(required=True) -@click.option('--trusted_role_arn', '-tra', type=str, required=False, - help='Account role to assume') -@click.option('--enabled', '-e', type=bool, required=False, - default=False, show_default=True, - help="Enable or disable credentials, if not specified: disabled") -@cli_response(attributes_order=[PARAM_CLOUD_IDENTIFIER, - PARAM_CLOUD, - PARAM_ENABLED, - PARAM_TRUSTED_ROLE_ARN]) -def add(ctx: ContextObj, cloud, account_number, trusted_role_arn, enabled): +@credentials.command(cls=ViewCommand, name='link') +@click.option('--application_id', '-aid', type=str, required=True, + help='Application id to describe a concrete credentials item') +@build_tenant_option(multiple=True) +@click.option('--all_tenants', is_flag=True, + help='Whether to activate integration for all tenants') +@click.option('--exclude_tenant', '-et', type=str, multiple=True, + help='Tenants to exclude for this integration. ' + 'Can be specified together with --all_tenants flag') +@cli_response() +def link(ctx: ContextObj, application_id: str, tenant_name: tuple[str, ...], + all_tenants: bool, exclude_tenant: tuple[str, ...], + customer_id: str | None): """ - Creates Custodian Service Credentials Manager configuration. + Links credentials to a specific set of tenants. + Each activation overrides the existing one """ - - return ctx['api_client'].credentials_manager_post( - cloud=cloud, - cloud_identifier=account_number, - trusted_role_arn=trusted_role_arn, - enabled=enabled + if tenant_name and any((all_tenants, exclude_tenant)): + return CustodianResponse.build( + 'Do not provide --all_tenants or ' + '--exclude_tenants if --tenant_name given' + ) + if not all_tenants and not tenant_name: + return CustodianResponse.build( + 'Either --all_tenants or --tenant_name must be given' + ) + if exclude_tenant and not all_tenants: + return CustodianResponse.build( + 'set --all_tenants if you provide --clouds or --exclude_tenants' + ) + return ctx['api_client'].credentials_bind( + application_id=application_id, + tenant_names=tenant_name, + all_tenants=all_tenants, + exclude_tenants=exclude_tenant, + customer_id=customer_id ) -@credentials.command(cls=ViewCommand, name='update') -@click.option('--cloud', '-c', type=str, required=True, - help='The cloud to which the credentials configuration belongs.') -@build_account_option(required=True) -@click.option('--trusted_role_arn', '-tra', type=str, required=False, - help=f'Account role to assume') -@click.option('--enabled', '-e', type=bool, default=False, - help='Enable or disable credentials actuality') -@cli_response(attributes_order=[PARAM_CLOUD_IDENTIFIER, - PARAM_CLOUD, - PARAM_ENABLED, - PARAM_TRUSTED_ROLE_ARN]) -def update(ctx: ContextObj, cloud, account_number, - trusted_role_arn, enabled): +@credentials.command(cls=ViewCommand, name='unlink') +@click.option('--application_id', '-aid', type=str, required=True, + help='Application id to describe a concrete credentials item') +@cli_response() +def unlink(ctx: ContextObj, application_id: str, customer_id): """ - Updates Custodian Service Credentials Manager configuration. + Unlinks credentials from tenants """ - return ctx['api_client'].credentials_manager_patch( - cloud=cloud.lower(), - cloud_identifier=account_number, - trusted_role_arn=trusted_role_arn, - enabled=enabled) + return ctx['api_client'].credentials_unbind( + application_id=application_id, + customer_id=customer_id + ) -@credentials.command(cls=ViewCommand, name='delete') -@click.option('--cloud', '-c', type=str, required=True, - help='The cloud to which the credentials configuration belongs.') -@build_account_option(required=True) +@credentials.command(cls=ViewCommand, name='get_linked_tenants') +@click.option('--application_id', '-aid', type=str, required=True, + help='Application id to describe a concrete credentials item') @cli_response() -def delete(ctx: ContextObj, cloud, account_number): +def get_linked_tenants(ctx: ContextObj, application_id: str, customer_id): """ - Deletes Custodian Service Credentials Manager configuration. + Returns all the tenants the credentials is linked to """ - return ctx['api_client'].credentials_manager_delete( - cloud=cloud, - cloud_identifier=account_number + return ctx['api_client'].credentials_get_binding( + application_id=application_id, + customer_id=customer_id ) diff --git a/c7n/c7ncli/group/tenant_findings.py b/c7n/c7ncli/group/tenant_findings.py deleted file mode 100644 index 80f484721..000000000 --- a/c7n/c7ncli/group/tenant_findings.py +++ /dev/null @@ -1,86 +0,0 @@ -import click -from click import Choice - -from c7ncli.group import cli_response, ViewCommand, response, ContextObj -from c7ncli.group import tenant_option -from c7ncli.service.constants import ( - PARAM_RULES_TO_INCLUDE, PARAM_REGIONS_TO_INCLUDE, - PARAM_RESOURCE_TYPES_TO_INCLUDE, PARAM_SEVERITIES_TO_INCLUDE, - PARAM_DATA_TYPE, PARAM_MAP_KEY -) - -PARAM_RESOURCES = 'resources' -EXPANSION_PARAMETERS = [PARAM_RESOURCES] -SEVERITY_PARAMETERS = ['High', 'Medium', 'Low', 'Info'] -LIST_TYPE = 'list_type' -MAP_TYPE = 'map_type' - - -@click.group(name='findings') -def findings(): - """Manages Tenant Findings state""" - - -@findings.command(cls=ViewCommand, name='describe') -@tenant_option -@click.option('--rule', '-rl', type=str, required=False, - help='Rule to include in a Findings state.') -@click.option('--region', '-r', type=str, required=False, - help='Region to include in a Findings state.') -@click.option('--resource_type', '-rt', type=str, required=False, - help='Resource type to include in a Findings state.') -@click.option('--severity', '-s', type=Choice(SEVERITY_PARAMETERS), - required=False, - help='Severity values to include in a Findings state.') -@click.option('--subset_targets', '-st', is_flag=True, required=False, - help='Applies dependent subset inclusion.') -@click.option('--expand', '-exp', type=Choice(EXPANSION_PARAMETERS), - default=PARAM_RESOURCES, - help='Expansion parameter to invert Findings collection on.') -@click.option('--mapped', '-map', type=str, required=False, - help='Applies mapping format of an expanded Findings collection,' - ' by a given key, rather than a listed one.') -@click.option('--get_url', '-url', is_flag=True, required=False, - help='Returns a presigned URL rather than a raw Findings ' - 'collection.') -@click.option('--raw', is_flag=True, default=False, required=False, - help='Specify if you want to receive raw findings content') -@cli_response() -def describe(ctx: ContextObj, tenant_name, rule, region, resource_type, - severity, subset_targets, expand, mapped, get_url, raw): - """ - Describes Findings state of a tenant. - """ - _filters_relation = zip( - (PARAM_RULES_TO_INCLUDE, PARAM_REGIONS_TO_INCLUDE, - PARAM_RESOURCE_TYPES_TO_INCLUDE, PARAM_SEVERITIES_TO_INCLUDE), - (rule, region, resource_type, severity) - ) - - _filters = {key: value for key, value in _filters_relation if value} - - if not _filters and subset_targets: - return response('One may apply \'--subset_targets\', given ' - 'either of the following parameters has been' - ' supplied: \'--rule\', \'--region\', ' - '\'--resource_type\' or \'--severity\'.') - - if mapped: - _format = {PARAM_DATA_TYPE: MAP_TYPE, PARAM_MAP_KEY: mapped} - else: - _format = {PARAM_DATA_TYPE: LIST_TYPE} - return ctx['api_client'].findings_get( - tenant_name=tenant_name, - filter_dict=_filters, expansion=expand, - dependent=subset_targets, format_dict=_format, get_url=get_url, raw=raw - ) - - -@findings.command(cls=ViewCommand, name='delete') -@tenant_option -@cli_response() -def delete(ctx: ContextObj, tenant_name): - """ - Clears Findings state of a tenant. - """ - return ctx['api_client'].findings_delete(tenant_name=tenant_name) diff --git a/c7n/c7ncli/group/tenant_region.py b/c7n/c7ncli/group/tenant_region.py deleted file mode 100644 index da281318f..000000000 --- a/c7n/c7ncli/group/tenant_region.py +++ /dev/null @@ -1,27 +0,0 @@ -from typing import Optional - -import click - -from c7ncli.group import cli_response, ViewCommand, ContextObj -from c7ncli.group import tenant_option - - -@click.group(name='region') -def region_group(): - """Manages tenant regions""" - - -@region_group.command(cls=ViewCommand, name='activate') -@tenant_option -@click.option('--region', '-r', type=str, - required=True, - help='Region native name to activate') -@cli_response() -def activate(ctx: ContextObj, tenant_name: Optional[str], region: str): - """ - Activates region in tenant - """ - return ctx['api_client'].tenant_region_post( - tenant_name=tenant_name, - region=region - ) diff --git a/c7n/c7ncli/group/trigger.py b/c7n/c7ncli/group/trigger.py deleted file mode 100644 index e6f9397e7..000000000 --- a/c7n/c7ncli/group/trigger.py +++ /dev/null @@ -1,16 +0,0 @@ -import click -from c7ncli.group import cli_response, ViewCommand, ContextObj - - -@click.group(name='trigger') -def trigger(): - """Manages Lambda triggering""" - - -@trigger.command(cls=ViewCommand, name='configuration_backup') -@cli_response() -def configuration_backup(ctx: ContextObj): - """ - Creates backup of Custodian Service - """ - return ctx['api_client'].trigger_backup() diff --git a/c7n/c7ncli/group/user.py b/c7n/c7ncli/group/user.py deleted file mode 100644 index 4b3b443ca..000000000 --- a/c7n/c7ncli/group/user.py +++ /dev/null @@ -1,68 +0,0 @@ -import os - -import click - -from c7ncli.group import ViewCommand, cli_response, ContextObj, \ - build_customer_option -from c7ncli.group.user_tenants import tenants -from c7ncli.service.constants import C7NCLI_DEVELOPER_MODE_ENV_NAME -from c7ncli.service.helpers import gen_password -from c7ncli.service.logger import get_logger - -_LOG = get_logger(__name__) - - -@click.group(name='user') -def user(): - """ - Manages User Entity - """ - - -@click.option('--username', '-u', required=False, type=str, - help='User`s name to delete. Only for SYSTEM. ALl other users ' - 'can delete only themselves') -@cli_response() -def delete(ctx: ContextObj, username): - """ - Delete the current user. You do not want to use the action! - """ - return ctx['api_client'].user_delete(username=username) - -# TODO add command for password-reset - - -@user.command(cls=ViewCommand, name='signup') -@click.option('--username', '-u', required=True, type=str, - help='User`s username. Must be unique') -@click.option('--password', '-p', required=False, type=str, - help='User`s password', show_default=True) -@build_customer_option(required=True) -@click.option('--role', '-r', required=True, type=str, - help='User`s role') -@click.option('--tenant', '-t', required=False, multiple=True, - type=str, - help='Tenants within the customer the user need access to. ' - 'Multiple names can be specified') -@cli_response(secured_params=['password']) -def signup(ctx: ContextObj, username, password, customer_id, role, tenant): - """ - Signs up a new Cognito user - """ - if not password: - _LOG.debug('Password was not given, generating') - password = gen_password() - click.echo(f'Password: {password}') - return ctx['api_client'].signup( - username=username, - password=password, - customer=customer_id, - role=role, - tenants=tenant - ) - - -if str(os.getenv(C7NCLI_DEVELOPER_MODE_ENV_NAME)).lower() == 'true': - user.command(cls=ViewCommand, name='delete')(delete) - -user.add_command(tenants) diff --git a/c7n/c7ncli/group/user_tenants.py b/c7n/c7ncli/group/user_tenants.py deleted file mode 100644 index 0fb7a72da..000000000 --- a/c7n/c7ncli/group/user_tenants.py +++ /dev/null @@ -1,59 +0,0 @@ -import click - -from c7ncli.group import cli_response, ViewCommand, response, ContextObj -from c7ncli.service.helpers import cast_to_list - - -@click.group(name='tenants') -def tenants(): - """Manages User-Tenant relations""" - - -@tenants.command(cls=ViewCommand, name='assign') -@click.option('--username', '-u', required=True, type=str, - help='User to update') -@click.option('--tenant', '-t', type=str, multiple=True, required=True, - help='Tenant name to assign to user. ' - 'Multiple names can be specified') -@cli_response() -def assign(ctx: ContextObj, username, tenant): - """ - Assigns tenants to a user - """ - return ctx['api_client'].user_assign_tenants(username=username, - tenants=tenant) - - -@tenants.command(cls=ViewCommand, name='unassign') -@click.option('--username', '-u', required=True, type=str, - help='User to update') -@click.option('--tenant', '-t', type=str, multiple=True, - help='Tenant names to unassign from user. ' - 'Multiple names can be specified') -@click.option('--all_tenants', '-ALL', is_flag=True, required=False, - help='Remove all tenants from user. This will allow the user to ' - 'interact with all tenants within the customer.') -@cli_response() -def unassign(ctx: ContextObj, username, tenant, all_tenants=False): - """ - Detaches tenants from a user - """ - if not (bool(all_tenants) ^ bool(tenant)): - return response( - 'You must specify either \'--tenant\' or ' - '\'--all_tenants\' parameter') - - return ctx['api_client'].user_unassign_tenants( - username=username, tenants=tenant, all_tenants=all_tenants) - - -@tenants.command(cls=ViewCommand, name='describe') -@click.option('--username', '-u', required=True, type=str, - help='The name of the user for whom the available ' - 'tenants information is to be provided') -@cli_response() -def describe(ctx: ContextObj, username): - """ - Describes user-accessible tenants - """ - return ctx['api_client'].user_describe_tenants(username=username) diff --git a/c7n/c7ncli/group/users.py b/c7n/c7ncli/group/users.py new file mode 100644 index 000000000..91833a8a2 --- /dev/null +++ b/c7n/c7ncli/group/users.py @@ -0,0 +1,64 @@ +import click + +from c7ncli.group import ContextObj, ViewCommand, cli_response, limit_option, \ + next_option + + +@click.group(name='users') +def users(): + """Manage Rule Engine users. Only for system customer""" + + +@users.command(cls=ViewCommand, name='describe') +@click.option('--username', required=False, type=str, + help='Username to describe a specific user') +@limit_option +@next_option +@cli_response() +def describe(ctx: ContextObj, username, limit, next_token, customer_id): + """ + Describe users + """ + if username: + return ctx['api_client'].get_user(username) + return ctx['api_client'].query_user( + customer_id=customer_id, + limit=limit, + next_token=next_token + ) + + +@users.command(cls=ViewCommand, name='create') +@click.option('--username', required=True, type=str, + help='Username to create user') +@click.option('--password', '-p', type=str, + required=True, hide_input=True, prompt=True, + help='New user password') +@click.option('--role_name', '-rn', type=str, + required=True, help='Role to assign to this user. ' + 'It should exist inside the customer') +@cli_response() +def create(ctx: ContextObj, username, password, role_name, customer_id): + """ + Creates a new user + """ + return ctx['api_client'].create_user( + username=username, + password=password, + customer_id=customer_id, + role_name=role_name + ) + + +@users.command(cls=ViewCommand, name='delete') +@click.option('--username', required=True, type=str, + help='Username to create user') +@cli_response() +def delete(ctx: ContextObj, username, customer_id): + """ + Removes an existing user + """ + return ctx['api_client'].delete_user( + username=username, + customer_id=customer_id + ) diff --git a/c7n/c7ncli/service/adapter_client.py b/c7n/c7ncli/service/adapter_client.py index 059b9e7db..c04a75d38 100644 --- a/c7n/c7ncli/service/adapter_client.py +++ b/c7n/c7ncli/service/adapter_client.py @@ -1,1518 +1,1177 @@ +from functools import partial +from http import HTTPStatus +from http.client import HTTPResponse import json import os -from typing import Optional, Dict, List - -import requests +from typing import Iterable +import urllib +import urllib.error +from urllib.parse import quote, urlencode +import urllib.request from c7ncli.service.config import AbstractCustodianConfig -from c7ncli.service.constants import * +from c7ncli.service.constants import ( + CONF_ACCESS_TOKEN, + CONF_REFRESH_TOKEN, + CustodianEndpoint, + HTTPMethod, + ITEMS_ATTR, + LAMBDA_INVOCATION_TRACE_ID_HEADER, + MESSAGE_ATTR, + SERVER_VERSION_HEADER, +) +from c7ncli.service.helpers import JWTToken, catch, sifted, urljoin from c7ncli.service.logger import get_logger, get_user_logger -from c7ncli.version import check_version_compatibility -HTTP_GET = 'get' -HTTP_POST = 'post' -HTTP_PATCH = 'patch' -HTTP_DELETE = 'delete' +_LOG = get_logger(__name__) +USER_LOG = get_user_logger(__name__) -ALLOWED_METHODS = {HTTP_GET, HTTP_POST, HTTP_PATCH, HTTP_DELETE} -SYSTEM_LOG = get_logger(__name__) -USER_LOG = get_user_logger(__name__) +class ApiClient: + """ + Simple JSON API client which is enough to cover our needs. It does not own + custodian-bound logic, i.e, access/refresh tokens and other. It knows + how to build urls and make requests without handling custodian-specific + exceptions + """ + __slots__ = ('_api_link',) + + def __init__(self, api_link: str): + """ + :param api_link: pre-built link, can contain some prefix + """ + self._api_link = api_link + + def build_url(self, path: str, params: dict | None = None, + query: dict | None = None) -> str: + """ + The methods return full built url which can be used to make request + :param path: some custodian resource. One variable from + CustodianEndpoints class + :param params: path params + :param query: dict with query params + :return: + """ + url = path.format(**(params or {})) + url = quote(urljoin(url)) # to remove / + if query: + url += f'?{urlencode(sifted(query))}' + return urljoin(self._api_link, url) + @staticmethod + def prepare_request(url: str, method: HTTPMethod, data: dict | None = None + ) -> urllib.request.Request: + """ + Prepares request instance. Url must be built beforehand + :param url: + :param method: + :param data: + :return: + """ + if isinstance(data, dict): + return urllib.request.Request( + url=url, + method=method.value, + data=json.dumps(data, separators=(',', ':')).encode(), + headers={'Content-Type': 'application/json'} + ) + return urllib.request.Request(url=url, method=method.value) + + def open_request(self, *args, **kwargs) -> HTTPResponse: + return urllib.request.urlopen(*args, **kwargs) -class AdapterClient: # why not ApiClient? - def __init__(self, config: Optional[AbstractCustodianConfig] = None): - self._config = config - SYSTEM_LOG.info('API Client object has has been created') + +class CustodianResponse: + __slots__ = ('method', 'path', 'code', 'data', 'trace_id', 'api_version', + 'exc') + + def __init__(self, method: HTTPMethod | None = None, + path: CustodianEndpoint | None = None, + code: HTTPStatus | None = None, data: dict | None = None, + trace_id: str | None = None, api_version: str | None = None, + exc: Exception | None = None): + self.method = method + self.path = path + self.code = code + self.data = data + self.trace_id = trace_id + self.api_version = api_version + + # JsonDecodeError | urllib.error.URLError - don't know how to handle + # properly + self.exc = exc @property - def config(self) -> Optional[AbstractCustodianConfig]: - return self._config - - @config.setter - def config(self, value: AbstractCustodianConfig): - assert isinstance(value, AbstractCustodianConfig) - self._config = value - - def __make_request(self, resource: str, method: str, payload: dict = None): - assert method in ALLOWED_METHODS, 'Not supported method' - parameters = dict( - method=method.upper(), - url=f'{self.config.api_link}/{resource}', - headers={'authorization': self.config.access_token} - ) - # config.api_link existence is validated in cli_response decorator - if method == HTTP_GET: - parameters.update(params=payload) - else: - parameters.update(json=payload) - SYSTEM_LOG.debug(f'API request info: {parameters}; Method: ' - f'{method.upper()}') + def was_sent(self) -> bool: + """ + Tells whether the request was sent + :return: + """ + return self.code is not None + + @classmethod + def build(cls, content: str | list | dict | Iterable + ) -> 'CustodianResponse': + body = {} + if isinstance(content, str): + body.update({MESSAGE_ATTR: content}) + elif isinstance(content, dict) and content: + body.update(content) + elif isinstance(content, list): + body.update({ITEMS_ATTR: content}) + elif isinstance(content, Iterable): + body.update(({ITEMS_ATTR: list(content)})) + return cls(data=body, code=HTTPStatus.OK) + + @property + def ok(self) -> bool: + return self.code is not None and 200 <= self.code <= 206 + + +class CustodianApiClient: + """ + This api client contains custodian-specific logic. It uses the ApiClient + from above for making requests + """ + __slots__ = '_config', '_client', '_auto_refresh' + + def __init__(self, config: AbstractCustodianConfig): + # api_link and access_token presence is validated before + self._config = config + self._client = ApiClient(api_link=config.api_link) + + self._auto_refresh = True + + def add_token(self, rec: urllib.request.Request, + header: str = 'Authorization'): + """ + Adds token to the given request instance. Refreshes the token if needed + :param header: + :param rec: + :return: + """ + # access token should definitely exist here because we check its + # presence before creating this class + + at = self._config.access_token + rt = self._config.refresh_token + if JWTToken(at).is_expired() and rt and self._auto_refresh: + _LOG.info('Trying to auto-refresh token') + resp = self.refresh(rt) + if resp.ok: + _LOG.info('Token was refreshed successfully. Updating config') + at = resp.data.get('access_token') + rt = resp.data.get('refresh_token') + dct = {CONF_ACCESS_TOKEN: at} + if rt: + # if new one. This probably won't happen because Cognito + # does not return a new refresh token. But just in case + dct[CONF_REFRESH_TOKEN] = rt + self._config.update(dct) + + rec.add_header(header, at) + + def _custodian_open(self, request: urllib.request.Request, + response: CustodianResponse) -> None: + """ + Sends the given request instance. Fills the response instance with data + :param request: + :param response: will be filled with some response data + """ try: - response = requests.request(**parameters) - except requests.exceptions.ConnectionError: - response = { - 'message': 'Provided Custodian api_link is invalid ' - 'or outdated. Please contact the tool support team.' - } - SYSTEM_LOG.exception(response) - return response - SYSTEM_LOG.debug(f'API response info: status {response.status_code}; ' - f'text {response.text}') + resp = self._client.open_request(request) + except urllib.error.HTTPError as e: + resp = e + except urllib.error.URLError as e: + _LOG.exception('Cannot make a request') + response.exc = e + return + response.code = HTTPStatus(resp.getcode()) + if response.code != HTTPStatus.NO_CONTENT: + data, exc = catch(partial(json.load, resp), json.JSONDecodeError) + response.data = data + response.exc = exc + + response.trace_id = resp.headers.get(LAMBDA_INVOCATION_TRACE_ID_HEADER) + response.api_version = resp.headers.get(SERVER_VERSION_HEADER) + resp.close() + return + + def make_request(self, path: CustodianEndpoint, + method: HTTPMethod | None = None, + path_params: dict | None = None, + query: dict | None = None, + data: dict | None = None) -> CustodianResponse: + """ + High-level request method. Adds token. + :param path: + :param method: + :param path_params: + :param query: + :param data: + :return: + """ + if not method: + method = HTTPMethod.POST if data else HTTPMethod.GET + req = self._client.prepare_request( + url=self._client.build_url(path.value, path_params, query), + method=method, + data=data + ) + self.add_token(req) + response = CustodianResponse(method=method, path=path) + self._custodian_open(req, response) return response - @staticmethod - def sifted(request: dict) -> dict: - return {k: v for k, v in request.items() if isinstance( - v, (bool, int)) or v} - - # application entity management - def application_post(self, **kwargs): - return self.__make_request( - resource=API_APPLICATION, - method=HTTP_POST, - payload=self.sifted(kwargs) + def refresh(self, token: str): + req = self._client.prepare_request( + url=self._client.build_url(CustodianEndpoint.REFRESH.value), + method=HTTPMethod.POST, + data={'refresh_token': token} ) + response = CustodianResponse(HTTPMethod.POST, + CustodianEndpoint.REFRESH) + self._custodian_open(req, response) + return response - def application_patch(self, application_id: str, **kwargs): - return self.__make_request( - resource=API_APPLICATION + f'/{application_id}', - method=HTTP_PATCH, - payload=self.sifted(kwargs) + def login(self, username: str, password: str): + req = self._client.prepare_request( + url=self._client.build_url(CustodianEndpoint.SIGNIN.value), + method=HTTPMethod.POST, + data={'username': username, 'password': password} ) + response = CustodianResponse(HTTPMethod.POST, CustodianEndpoint.SIGNIN) + self._custodian_open(req, response) + return response - def application_list(self, **kwargs): - return self.__make_request( - resource=API_APPLICATION, - method=HTTP_GET, - payload=self.sifted(kwargs) + def whoami(self): + return self.make_request( + path=CustodianEndpoint.USERS_WHOAMI, + method=HTTPMethod.GET ) - def application_get(self, application_id: str): - return self.__make_request( - resource=API_APPLICATION + f'/{application_id}', - method=HTTP_GET, - payload={} + def customer_get(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.CUSTOMERS, + method=HTTPMethod.GET, + query=sifted(kwargs) ) - def application_delete(self, application_id: str, - customer_id: Optional[str]): - return self.__make_request( - resource=API_APPLICATION + f'/{application_id}', - method=HTTP_DELETE, - payload={PARAM_CUSTOMER: customer_id} + def customer_get_excluded_rules(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.CUSTOMERS_EXCLUDED_RULES, + method=HTTPMethod.GET, + query=sifted(kwargs) ) - def access_application_post(self, **kwargs): - return self.__make_request( - resource=API_ACCESS_APPLICATION, - method=HTTP_POST, - payload=self.sifted(kwargs) + def customer_set_excluded_rules(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.CUSTOMERS_EXCLUDED_RULES, + method=HTTPMethod.PUT, + data=sifted(kwargs) ) - def access_application_patch(self, application_id: str, **kwargs): - return self.__make_request( - resource=API_ACCESS_APPLICATION + f'/{application_id}', - method=HTTP_PATCH, - payload=self.sifted(kwargs) + def tenant_get(self, tenant_name: str, **kwargs): + return self.make_request( + path=CustodianEndpoint.TENANTS_TENANT_NAME, + path_params={'tenant_name': tenant_name}, + method=HTTPMethod.GET, + query=sifted(kwargs) ) - def access_application_list(self, **kwargs): - return self.__make_request( - resource=API_ACCESS_APPLICATION, - method=HTTP_GET, - payload=self.sifted(kwargs) + def tenant_query(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.TENANTS, + method=HTTPMethod.GET, + query=sifted(kwargs) ) - def access_application_get(self, application_id: str): - return self.__make_request( - resource=API_ACCESS_APPLICATION + f'/{application_id}', - method=HTTP_GET, - payload={} + def tenant_get_excluded_rules(self, tenant_name: str, **kwargs): + return self.make_request( + path=CustodianEndpoint.TENANTS_TENANT_NAME_EXCLUDED_RULES, + method=HTTPMethod.GET, + path_params={'tenant_name': tenant_name}, + query=sifted(kwargs) ) - def access_application_delete(self, application_id: str, - customer_id: Optional[str]): - return self.__make_request( - resource=API_ACCESS_APPLICATION + f'/{application_id}', - method=HTTP_DELETE, - payload={PARAM_CUSTOMER: customer_id} + def tenant_set_excluded_rules(self, tenant_name: str, **kwargs): + return self.make_request( + path=CustodianEndpoint.TENANTS_TENANT_NAME_EXCLUDED_RULES, + method=HTTPMethod.PUT, + path_params={'tenant_name': tenant_name}, + data=sifted(kwargs) ) - def dojo_application_post(self, **kwargs): - return self.__make_request( - resource=API_DOJO_APPLICATION, - method=HTTP_POST, - payload=self.sifted(kwargs) + def tenant_get_active_licenses(self, tenant_name: str, **kwargs): + return self.make_request( + path=CustodianEndpoint.TENANTS_TENANT_NAME_ACTIVE_LICENSES, + path_params={'tenant_name': tenant_name}, + method=HTTPMethod.GET, + query=sifted(kwargs) ) - def dojo_application_patch(self, application_id: str, **kwargs): - return self.__make_request( - resource=API_DOJO_APPLICATION + f'/{application_id}', - method=HTTP_PATCH, - payload=self.sifted(kwargs) + def ruleset_get(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.RULESETS, + method=HTTPMethod.GET, + query=sifted(kwargs) ) - def dojo_application_list(self, **kwargs): - return self.__make_request( - resource=API_DOJO_APPLICATION, - method=HTTP_GET, - payload=self.sifted(kwargs) + def ruleset_post(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.RULESETS, + method=HTTPMethod.POST, + data=sifted(kwargs) ) - def dojo_application_get(self, application_id: str): - return self.__make_request( - resource=API_DOJO_APPLICATION + f'/{application_id}', - method=HTTP_GET, - payload={} + def ruleset_update(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.RULESETS, + method=HTTPMethod.PATCH, + data=sifted(kwargs) ) - def dojo_application_delete(self, application_id: str, - customer_id: Optional[str]): - return self.__make_request( - resource=API_DOJO_APPLICATION + f'/{application_id}', - method=HTTP_DELETE, - payload={PARAM_CUSTOMER: customer_id} + def ruleset_delete(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.RULESETS, + method=HTTPMethod.DELETE, + data=sifted(kwargs) ) - # parent entity management - def parent_post(self, **kwargs): - return self.__make_request( - resource=API_PARENT, - method=HTTP_POST, - payload=self.sifted(kwargs) + def ed_ruleset_add(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.ED_RULESETS, + method=HTTPMethod.POST, + data=sifted(kwargs) ) - def parent_patch(self, parent_id, **kwargs): - return self.__make_request( - resource=API_PARENT + f'/{parent_id}', - method=HTTP_PATCH, - payload=self.sifted(kwargs) + def ed_ruleset_get(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.ED_RULESETS, + method=HTTPMethod.GET, + query=sifted(kwargs) ) - def parent_get(self, parent_id: str): - return self.__make_request( - resource=API_PARENT + f'/{parent_id}', - method=HTTP_GET, payload={} - ) - - def parent_delete(self, parent_id: str): - return self.__make_request( - resource=API_PARENT + f'/{parent_id}', - method=HTTP_DELETE, payload={} + def ed_ruleset_delete(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.ED_RULESETS, + method=HTTPMethod.DELETE, + data=sifted(kwargs) ) - def parent_list(self, **kwargs): - return self.__make_request( - resource=API_PARENT, - method=HTTP_GET, - payload=self.sifted(kwargs) - ) - - def parent_link_tenant(self, **kwargs): - return self.__make_request( - resource=API_PARENT_TENANT_LINK, - method=HTTP_POST, - payload=self.sifted(kwargs) - ) - - def parent_unlink_tenant(self, **kwargs): - return self.__make_request( - resource=API_PARENT_TENANT_LINK, - method=HTTP_DELETE, - payload=self.sifted(kwargs) - ) - - # Customer entity management - def customer_get(self, name: str, complete: bool): - query = self.sifted({PARAM_NAME: name, PARAM_COMPLETE: complete}) - return self.__make_request( - resource=API_CUSTOMER, method=HTTP_GET, payload=query - ) - - # Tenant entity management - def tenant_get(self, tenant_name: str, customer_name: str, - cloud_identifier: str, complete: bool, - limit: int, next_token: str): - return self.__make_request( - resource=API_TENANT, method=HTTP_GET, payload=self.sifted({ - PARAM_TENANT_NAME: tenant_name, - PARAM_CUSTOMER: customer_name, - PARAM_CLOUD_IDENTIFIER: cloud_identifier, - PARAM_COMPLETE: complete, - PARAM_LIMIT: limit, - PARAM_NEXT_TOKEN: next_token - }) - ) - - def tenant_post(self, **kwargs): - return self.__make_request( - resource=API_TENANT, - method=HTTP_POST, - payload=self.sifted(kwargs) - ) - - def tenant_patch(self, **kwargs): - return self.__make_request( - resource=API_TENANT, - method=HTTP_PATCH, - payload=self.sifted(kwargs) - ) - - def tenant_region_post(self, **kwargs): - return self.__make_request( - resource=API_TENANT_REGIONS, - method=HTTP_POST, - payload=self.sifted(kwargs) - ) - - # Tenant License Priority management - - def tenant_license_priority_get( - self, tenant_name: str, customer_name: str, - governance_entity_type: str, governance_entity_id: str, - management_id: str - ): - query = { - PARAM_TENANT_NAME: tenant_name, - PARAM_CUSTOMER: customer_name, - PARAM_GOVERNANCE_ENTITY_TYPE: governance_entity_type, - PARAM_GOVERNANCE_ENTITY_ID: governance_entity_id, - PARAM_MANAGEMENT_ID: management_id - } - return self.__make_request( - resource=API_TENANT_LICENSE_PRIORITIES, method=HTTP_GET, - payload=self.sifted(query) - ) - - def tenant_license_priority_post( - self, tenant_name: str, license_key_list: list, - governance_entity_type: str, governance_entity_id: str, - management_id: str = None - ): - payload = { - PARAM_TENANT_NAME: tenant_name, - PARAM_GOVERNANCE_ENTITY_TYPE: governance_entity_type, - PARAM_GOVERNANCE_ENTITY_ID: governance_entity_id, - PARAM_LICENSE_KEYS: license_key_list, - PARAM_MANAGEMENT_ID: management_id - } - return self.__make_request( - resource=API_TENANT_LICENSE_PRIORITIES, method=HTTP_POST, - payload=self.sifted(payload) - ) - - def tenant_license_priority_patch( - self, tenant_name: str, license_keys_to_prepend: list, - license_keys_to_append: list, license_keys_to_detach: list, - governance_entity_type: str, governance_entity_id: str, - management_id: str - ): - payload = { - PARAM_TENANT_NAME: tenant_name, - PARAM_GOVERNANCE_ENTITY_TYPE: governance_entity_type, - PARAM_GOVERNANCE_ENTITY_ID: governance_entity_id, - PARAM_LICENSE_KEYS_TO_PREPEND: license_keys_to_prepend, - PARAM_LICENSE_KEYS_TO_APPEND: license_keys_to_append, - PARAM_LICENSE_KEYS_TO_DETACH: license_keys_to_detach, - PARAM_MANAGEMENT_ID: management_id - } - return self.__make_request( - resource=API_TENANT_LICENSE_PRIORITIES, method=HTTP_PATCH, - payload=self.sifted(payload) - ) - - def tenant_license_priority_delete( - self, tenant_name: str, governance_entity_type: str, - governance_entity_id: str, management_id: str - ): - payload = { - PARAM_TENANT_NAME: tenant_name, - PARAM_GOVERNANCE_ENTITY_TYPE: governance_entity_type, - PARAM_GOVERNANCE_ENTITY_ID: governance_entity_id, - PARAM_MANAGEMENT_ID: management_id - } - return self.__make_request( - resource=API_TENANT_LICENSE_PRIORITIES, method=HTTP_DELETE, - payload=self.sifted(payload) - ) - - def credentials_manager_get(self, cloud, cloud_identifier): - request = { - PARAM_CLOUD: cloud, - PARAM_CLOUD_IDENTIFIER: cloud_identifier - } - - return self.__make_request(resource=API_CREDENTIALS_MANAGER, - method=HTTP_GET, - payload=request) - - def credentials_manager_post(self, - cloud: str, - cloud_identifier: str, - trusted_role_arn: str, - enabled: bool): - request = { - PARAM_CLOUD: cloud, - PARAM_CLOUD_IDENTIFIER: cloud_identifier, - PARAM_TRUSTED_ROLE_ARN: trusted_role_arn, - PARAM_ENABLED: enabled, - } - - return self.__make_request(resource=API_CREDENTIALS_MANAGER, - method=HTTP_POST, - payload=request) - - def credentials_manager_patch(self, - cloud: str, - cloud_identifier: str, - trusted_role_arn: str, - enabled: bool): - request = { - PARAM_CLOUD: cloud, - PARAM_CLOUD_IDENTIFIER: cloud_identifier, - PARAM_TRUSTED_ROLE_ARN: trusted_role_arn, - PARAM_ENABLED: enabled, - } - request = {k: v for k, v in request.items() if v is not None} - - return self.__make_request(resource=API_CREDENTIALS_MANAGER, - method=HTTP_PATCH, - payload=request) - - def credentials_manager_delete(self, cloud, cloud_identifier): - request = { - PARAM_CLOUD: cloud, - PARAM_CLOUD_IDENTIFIER: cloud_identifier - } - - return self.__make_request(resource=API_CREDENTIALS_MANAGER, - method=HTTP_DELETE, - payload=request) - - # ruleset entity management - def ruleset_get(self, **kwargs): - return self.__make_request( - resource=API_RULESET, - method=HTTP_GET, - payload=self.sifted(kwargs) + def rule_source_get(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.RULE_SOURCES, + method=HTTPMethod.GET, + query=sifted(kwargs) ) - def ruleset_post(self, **kwargs): - return self.__make_request(resource=API_RULESET, - method=HTTP_POST, - payload=self.sifted(kwargs)) - - def ruleset_update(self, customer: str, ruleset_name: str, - version: float, rules_to_attach: list, - rules_to_detach: list, active: bool, - tenant_allowance: list, - tenant_restriction: list): - request = { - PARAM_CUSTOMER: customer, - PARAM_NAME: ruleset_name, - PARAM_VERSION: version, - RULES_TO_ATTACH: rules_to_attach, - RULES_TO_DETACH: rules_to_detach, - PARAM_TENANT_ALLOWANCE: tenant_allowance, - PARAM_TENANT_RESTRICTION: tenant_restriction - } - if isinstance(active, bool): - request[PARAM_ACTIVE] = active - return self.__make_request(resource=API_RULESET, - method=HTTP_PATCH, - payload=request) - - def ruleset_delete(self, customer: str, ruleset_name: str, - version: float): - request = { - PARAM_CUSTOMER: customer, - PARAM_NAME: ruleset_name, - PARAM_VERSION: version, - } - return self.__make_request(resource=API_RULESET, - method=HTTP_DELETE, - payload=request) - - def ed_ruleset_add(self, **kwargs): - return self.__make_request( - resource=API_ED_RULESET, - method=HTTP_POST, - payload=self.sifted(kwargs) + def rule_source_post(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.RULE_SOURCES, + method=HTTPMethod.POST, + data=sifted(kwargs) ) - def ed_ruleset_get(self, cloud: Optional[str], get_rules: Optional[bool]): - return self.__make_request( - resource=API_ED_RULESET, - method=HTTP_GET, - payload=self.sifted({ - 'cloud': cloud, - 'get_rules': get_rules - }) + def rule_source_patch(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.RULE_SOURCES, + method=HTTPMethod.PATCH, + data=sifted(kwargs) ) - def ed_ruleset_delete(self, **kwargs): - return self.__make_request( - resource=API_ED_RULESET, - method=HTTP_DELETE, - payload=self.sifted(kwargs) - ) - - # Rule Source management - def rule_source_get(self, rule_source_id: str, customer: str, - git_project_id: str): - if rule_source_id: - query = {PARAM_ID: rule_source_id} - else: - query = { - PARAM_CUSTOMER: customer, - PARAM_GIT_PROJECT_ID: git_project_id - } - query = self.sifted(request=query) - return self.__make_request(resource=API_RULE_SOURCE, - method=HTTP_GET, - payload=query) - - def rule_source_post(self, **kwargs): - return self.__make_request( - resource=API_RULE_SOURCE, method=HTTP_POST, - payload=self.sifted(kwargs) + def rule_source_delete(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.RULE_SOURCES, + method=HTTPMethod.DELETE, + data=sifted(kwargs) ) - def rule_source_patch(self, **kwargs): - return self.__make_request(resource=API_RULE_SOURCE, method=HTTP_PATCH, - payload=self.sifted(kwargs)) + def rule_get(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.RULES, + method=HTTPMethod.GET, + query=sifted(kwargs) + ) - def rule_source_delete(self, rule_source_id: str, customer: str): - request = { - PARAM_ID: rule_source_id, - PARAM_CUSTOMER: customer - } - return self.__make_request( - resource=API_RULE_SOURCE, method=HTTP_DELETE, - payload=self.sifted(request)) + def rule_delete(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.RULES, + method=HTTPMethod.DELETE, + data=sifted(kwargs) + ) - # Role entity management + def role_get(self, name, **kwargs): + return self.make_request( + path=CustodianEndpoint.ROLES_NAME, + method=HTTPMethod.GET, + path_params={'name': name}, + query=sifted(kwargs) + ) - def role_get(self, **kwargs): - return self.__make_request( - resource=API_ROLE, method=HTTP_GET, - payload=self.sifted(kwargs) + def role_query(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.ROLES, + method=HTTPMethod.GET, + query=sifted(kwargs) ) def role_post(self, **kwargs): - return self.__make_request( - resource=API_ROLE, - method=HTTP_POST, - payload=self.sifted(kwargs) + return self.make_request( + path=CustodianEndpoint.ROLES, + method=HTTPMethod.POST, + data=sifted(kwargs) ) - def role_patch(self, **kwargs): - return self.__make_request( - resource=API_ROLE, - method=HTTP_PATCH, - payload=self.sifted(kwargs) + def role_patch(self, name, **kwargs): + return self.make_request( + path=CustodianEndpoint.ROLES_NAME, + method=HTTPMethod.PATCH, + path_params={'name': name}, + data=sifted(kwargs) ) - def role_delete(self, **kwargs): - return self.__make_request( - resource=API_ROLE, - method=HTTP_DELETE, - payload=self.sifted(kwargs) + def role_delete(self, name, **kwargs): + return self.make_request( + path=CustodianEndpoint.ROLES_NAME, + method=HTTPMethod.DELETE, + path_params={'name': name}, + data=sifted(kwargs) ) - def role_clean_cache(self, **kwargs): - return self.__make_request( - resource=API_ROLE_CACHE, - method=HTTP_DELETE, - payload=self.sifted(kwargs) + def policy_get(self, name, **kwargs): + return self.make_request( + path=CustodianEndpoint.POLICIES_NAME, + method=HTTPMethod.GET, + path_params={'name': name}, + query=sifted(kwargs) ) - # Policy entity management - - def policy_get(self, customer_display_name, policy_name): - request = {PARAM_CUSTOMER: customer_display_name} - if policy_name: - request[PARAM_NAME] = policy_name - return self.__make_request(resource=API_POLICY, method=HTTP_GET, - payload=request) + def policy_query(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.POLICIES, + method=HTTPMethod.GET, + query=sifted(kwargs) + ) def policy_post(self, **kwargs): - return self.__make_request( - resource=API_POLICY, method=HTTP_POST, - payload=self.sifted(kwargs) + return self.make_request( + path=CustodianEndpoint.POLICIES, + method=HTTPMethod.POST, + data=sifted(kwargs) ) - @staticmethod - def __get_permissions_from_file(path_to_permissions): - try: - with open(path_to_permissions, 'r') as file: - content = json.loads(file.read()) - except json.decoder.JSONDecodeError: - return {'message': 'Invalid file content'} - if not isinstance(content, list): - return {'message': 'Invalid file content'} - return content - - def policy_patch(self, **kwargs): - return self.__make_request( - resource=API_POLICY, - method=HTTP_PATCH, - payload=self.sifted(kwargs) + def policy_patch(self, name, **kwargs): + return self.make_request( + path=CustodianEndpoint.POLICIES_NAME, + method=HTTPMethod.PATCH, + path_params={'name': name}, + data=sifted(kwargs) ) - def policy_delete(self, customer_display_name, policy_name): - request = {PARAM_CUSTOMER: customer_display_name, - PARAM_NAME: policy_name} - return self.__make_request(resource=API_POLICY, method=HTTP_DELETE, - payload=request) - - def policy_clean_cache(self, **kwargs): - return self.__make_request( - resource=API_POLICY_CACHE, - method=HTTP_DELETE, - payload=self.sifted(kwargs) + def policy_delete(self, name, **kwargs): + return self.make_request( + path=CustodianEndpoint.POLICIES_NAME, + method=HTTPMethod.DELETE, + path_params={'name': name}, + data=sifted(kwargs) ) - # Rule entity management - - def rule_get(self, **kwargs): - return self.__make_request(resource=API_RULE, - method=HTTP_GET, - payload=self.sifted(kwargs)) - - def rule_delete(self, **kwargs): - return self.__make_request( - resource=API_RULE, - method=HTTP_DELETE, - payload=self.sifted(kwargs) - ) - - # Account Region entity management - def region_get(self, display_name: str, tenant_name: str, - region_name: str): - request = { - PARAM_DISPLAY_NAME: display_name, - PARAM_TENANT_NAME: tenant_name, - PARAM_NAME: region_name, - } - return self.__make_request(resource=API_ACCOUNT_REGION, - method=HTTP_GET, - payload=self.sifted(request)) - - def region_post(self, display_name: str, tenant_name: str, - region_name: str, state: str, - all_regions: bool): - request = { - PARAM_DISPLAY_NAME: display_name, - PARAM_TENANT_NAME: tenant_name, - PARAM_NAME: region_name, - PARAM_REGION_STATE: state, - PARAM_ALL_REGIONS: all_regions - } - return self.__make_request(resource=API_ACCOUNT_REGION, - method=HTTP_POST, - payload=request) - - def region_patch(self, display_name: str, tenant_name: str, - region_name: str, state: str): - request = { - PARAM_DISPLAY_NAME: display_name, - PARAM_TENANT_NAME: tenant_name, - PARAM_NAME: region_name, - PARAM_REGION_STATE: state - } - return self.__make_request(resource=API_ACCOUNT_REGION, - method=HTTP_PATCH, - payload=self.sifted(request)) - - def region_delete(self, display_name: str, tenant_name: str, - regions_names: list): - request = { - PARAM_DISPLAY_NAME: display_name, - PARAM_TENANT_NAME: tenant_name, - PARAM_NAME: regions_names - } - return self.__make_request(resource=API_ACCOUNT_REGION, - method=HTTP_DELETE, - payload=self.sifted(request)) - - def trigger_backup(self): - return self.__make_request(resource=API_BACKUPPER, method=HTTP_POST, - payload={}) - - def metrics_status(self): - return self.__make_request(resource=API_METRICS_STATUS, - method=HTTP_GET, - payload={}) + def metrics_status(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.METRICS_STATUS, + method=HTTPMethod.GET, + query=sifted(kwargs) + ) def trigger_metrics_update(self): - return self.__make_request(resource=API_METRICS_UPDATER, - method=HTTP_POST, payload={}) + return self.make_request( + path=CustodianEndpoint.METRICS_UPDATE, + method=HTTPMethod.POST, + data={} + ) def update_standards(self): - return self.__make_request(resource=API_META_STANDARDS, - method=HTTP_POST, payload={}) + return self.make_request( + path=CustodianEndpoint.META_STANDARDS, + method=HTTPMethod.POST, + data={} + ) def update_mappings(self): - return self.__make_request(resource=API_META_MAPPINGS, - method=HTTP_POST, payload={}) + return self.make_request( + path=CustodianEndpoint.META_MAPPINGS, + method=HTTPMethod.POST, + data={} + ) def update_meta(self): - return self.__make_request(resource=API_META_META, - method=HTTP_POST, payload={}) + return self.make_request( + path=CustodianEndpoint.META_META, + method=HTTPMethod.POST, + data={} + ) def trigger_rule_meta_updater(self, **kwargs): - return self.__make_request( - resource=API_RULE_META_UPDATER, method=HTTP_POST, - payload=self.sifted(kwargs) + return self.make_request( + path=CustodianEndpoint.RULE_META_UPDATER, + method=HTTPMethod.POST, + data=sifted(kwargs) ) def job_list(self, **kwargs): - return self.__make_request( - resource=API_JOB, - method=HTTP_GET, - payload=self.sifted(kwargs) + return self.make_request( + path=CustodianEndpoint.JOBS, + method=HTTPMethod.GET, + query=sifted(kwargs) ) - def job_get(self, job_id: str): - return self.__make_request( - resource=API_JOB + f'/{job_id}', - method=HTTP_GET, payload={} + def job_get(self, job_id: str, **kwargs): + return self.make_request( + path=CustodianEndpoint.JOBS_JOB, + path_params={'job_id': job_id}, + method=HTTPMethod.GET, + query=sifted(kwargs) ) def job_post(self, **kwargs): - api = API_JOB + api = CustodianEndpoint.JOBS if os.environ.get('C7N_STANDARD_JOBS'): - kwargs.pop('check_permission', None) - api += '/standard' - return self.__make_request( - resource=api, - method=HTTP_POST, - payload=self.sifted(kwargs) - ) - - def job_delete(self, job_id: str): - return self.__make_request( - resource=API_JOB + f'/{job_id}', - method=HTTP_DELETE, - payload={} - ) - - def scheduled_job_post(self, schedule: str, - target_ruleset: list, target_region: str, - tenant: str = None, name: str = None, - customer: str = None): - return self.__make_request( - resource=API_SCHEDULED_JOBS, - method=HTTP_POST, - payload=self.sifted({ - PARAM_TENANT_NAME: tenant, - PARAM_TARGET_RULESETS: target_ruleset, - PARAM_TARGET_REGIONS: target_region, - PARAM_SCHEDULE_EXPRESSION: schedule, - PARAM_NAME: name, - PARAM_CUSTOMER: customer - }) - ) - - def scheduled_job_get(self, name: str = None): - return self.__make_request( - resource=API_SCHEDULED_JOBS + f'/{name}', - method=HTTP_GET, - payload={} + api = CustodianEndpoint.JOBS_STANDARD + return self.make_request( + path=api, + method=HTTPMethod.POST, + data=sifted(kwargs) + ) + + def job_delete(self, job_id: str, **kwargs): + return self.make_request( + path=CustodianEndpoint.JOBS_JOB, + path_params={'job_id': job_id}, + method=HTTPMethod.DELETE, + data=sifted(kwargs) + ) + + def scheduled_job_post(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.SCHEDULED_JOB, + method=HTTPMethod.POST, + data=sifted(kwargs) + ) + + def scheduled_job_get(self, name: str, **kwargs): + return self.make_request( + path=CustodianEndpoint.SCHEDULED_JOB_NAME, + path_params={'name': name}, + method=HTTPMethod.GET, + query=sifted(kwargs) ) def scheduled_job_query(self, **kwargs): - return self.__make_request( - resource=API_SCHEDULED_JOBS, - method=HTTP_GET, - payload=self.sifted(kwargs) - ) - - def scheduled_job_delete(self, name: str): - return self.__make_request( - resource=API_SCHEDULED_JOBS + f'/{name}', - method=HTTP_DELETE, - payload={} - ) - - def scheduled_job_update(self, name: str, schedule: str = None, - enabled: bool = None, customer: str = None): - return self.__make_request( - resource=API_SCHEDULED_JOBS + f'/{name}', - method=HTTP_PATCH, - payload=self.sifted({ - PARAM_SCHEDULE_EXPRESSION: schedule, - PARAM_ENABLED: enabled, - PARAM_CUSTOMER: customer - }) - ) - - def report_get(self, job_id: str, account_display_name: str, - tenant_name: str, - detailed: bool, get_url: bool): - request = { - PARAM_JOB_ID: job_id, - PARAM_ACCOUNT: account_display_name, - PARAM_TENANT_NAME: tenant_name, - PARAM_DETAILED: detailed, - PARAM_GET_URL: get_url - } - return self.__make_request( - resource=API_REPORT, - method=HTTP_GET, - payload=self.sifted(request) - ) - - def operational_report_get(self, tenant_name: str, report_types: list, - customer: str): - request = { - PARAM_TENANT_NAMES: tenant_name, - PARAM_TYPES: report_types, - PARAM_CUSTOMER: customer - } - return self.__make_request( - resource=API_OPERATIONAL_REPORT, - method=HTTP_GET, - payload=self.sifted(request) - ) - - def project_report_get(self, tenant_display_name: str, report_types: list, - customer: str): - request = { - PARAM_TENANT_DISPLAY_NAMES: tenant_display_name, - PARAM_TYPES: report_types, - PARAM_CUSTOMER: customer - } - return self.__make_request( - resource=API_PROJECT_REPORT, - method=HTTP_GET, - payload=self.sifted(request) - ) - - def department_report_get(self, report_types: list, customer: str): - request = { - PARAM_TYPES: report_types, - PARAM_CUSTOMER: customer - } - return self.__make_request( - resource=API_DEPARTMENT_REPORT, - method=HTTP_GET, - payload=self.sifted(request) - ) - - def c_level_report_get(self, report_types: list, customer: str): - request = { - PARAM_TYPES: report_types, - PARAM_CUSTOMER: customer - } - return self.__make_request( - resource=API_C_LEVEL_REPORT, - method=HTTP_GET, - payload=self.sifted(request) - ) - - def push_dojo_by_job_id(self, job_id: str): - return self.__make_request( - resource=API_REPORTS_PUSH_DOJO + '/' + job_id, - method=HTTP_POST, - payload={} - ) - - def push_security_hub_by_job_id(self, job_id: str): - return self.__make_request( - resource=API_REPORTS_PUSH_SECURITY_HUB + '/' + job_id, - method=HTTP_POST, - payload={} + return self.make_request( + path=CustodianEndpoint.SCHEDULED_JOB, + method=HTTPMethod.GET, + query=sifted(kwargs) ) - def push_dojo_multiple(self, **kwargs): - return self.__make_request( - resource=API_REPORTS_PUSH_DOJO, - method=HTTP_POST, - payload=self.sifted(kwargs) - ) - - def push_security_hub_multiple(self, **kwargs): - return self.__make_request( - resource=API_REPORTS_PUSH_SECURITY_HUB, - method=HTTP_POST, - payload=self.sifted(kwargs) - ) - - def login(self, username, password): - request = { - PARAM_USERNAME: username, - PARAM_PASSWORD: password - } - response = self.__make_request( - resource=API_SIGNIN, - method=HTTP_POST, - payload=request - ) - if isinstance(response, dict): - return response - if response.status_code != 200: - if 'Incorrect username and/or' in response.text: - message = 'Provided credentials are invalid.' - SYSTEM_LOG.warning(message) - return {'message': message} - else: - SYSTEM_LOG.error(f'Error: {response.text}') - return {'message': 'Malformed response obtained. ' - 'Please contact support team ' - 'for assistance.'} - response = response.json() - SYSTEM_LOG.debug(f'Response: {response}') - check_version_compatibility( - response.get('items')[0].pop(PARAM_API_VERSION, None)) - return response.get('items')[0].get('id_token') - - def signup(self, username: str, password: str, customer: str, role: str, - tenants: tuple) -> dict: - return self.__make_request( - resource=API_SIGNUP, - method=HTTP_POST, - payload=self.sifted({ - PARAM_USERNAME: username, - PARAM_PASSWORD: password, - PARAM_CUSTOMER: customer, - PARAM_ROLE: role, - PARAM_TENANTS: tenants - }) - ) - - def user_delete(self, username: Optional[str] = None) -> dict: - return self.__make_request( - resource=API_USERS, - method=HTTP_DELETE, - payload=self.sifted({PARAM_USERNAME: username}) + def scheduled_job_delete(self, name: str, **kwargs): + return self.make_request( + path=CustodianEndpoint.SCHEDULED_JOB_NAME, + path_params={'name': name}, + method=HTTPMethod.DELETE, + data=sifted(kwargs) + ) + + def scheduled_job_update(self, name, **kwargs): + return self.make_request( + path=CustodianEndpoint.SCHEDULED_JOB_NAME, + path_params={'name': name}, + method=HTTPMethod.PATCH, + data=sifted(kwargs) + ) + + def operational_report_post(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.REPORTS_OPERATIONAL, + method=HTTPMethod.POST, + data=sifted(kwargs) + ) + + def project_report_post(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.REPORTS_PROJECT, + method=HTTPMethod.POST, + data=sifted(kwargs) + ) + + def department_report_post(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.REPORTS_DEPARTMENT, + method=HTTPMethod.POST, + data=sifted(kwargs) + ) + + def c_level_report_post(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.REPORTS_CLEVEL, + method=HTTPMethod.POST, + data=sifted(kwargs) + ) + + def diagnostic_report_get(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.REPORTS_DIAGNOSTIC, + method=HTTPMethod.GET, + query=sifted(kwargs) + ) + + def report_status_get(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.REPORTS_STATUS, + method=HTTPMethod.GET, + query=sifted(kwargs) ) + def push_dojo_by_job_id(self, job_id: str, **kwargs): + return self.make_request( + path=CustodianEndpoint.REPORTS_PUSH_DOJO_JOB_ID, + path_params={'job_id': job_id}, + method=HTTPMethod.POST, + data=sifted(kwargs) + ) + + def push_dojo_multiple(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.REPORTS_PUSH_DOJO, + method=HTTPMethod.POST, + data=sifted(kwargs) + ) + + # def signup(self, username: str, password: str, customer: str, role: str, + # tenants: tuple): + # return self.make_request( + # path=CustodianEndpoint.SIGNUP, + # method=HTTPMethod.POST, + # data=sifted({ + # PARAM_USERNAME: username, + # PARAM_PASSWORD: password, + # PARAM_CUSTOMER: customer, + # PARAM_ROLE: role, + # PARAM_TENANTS: tenants + # }) + # ) + def health_check_list(self, **kwargs): - return self.__make_request( - resource=API_HEALTH_CHECK, - method=HTTP_GET, - payload=self.sifted(kwargs) + return self.make_request( + path=CustodianEndpoint.HEALTH, + method=HTTPMethod.GET, + query=sifted(kwargs) ) def health_check_get(self, _id: str): - return self.__make_request( - resource=API_HEALTH_CHECK + f'/{_id}', - method=HTTP_GET, - payload={} - ) - - def siem_get(self, tenant_name, configuration_type): - request = { - PARAM_TENANT_NAME: tenant_name - } - if configuration_type == PARAM_DOJO: - return self.__make_request( - resource=API_SIEM_DOJO, - method=HTTP_GET, - payload=request - ) - else: - return self.__make_request( - resource=API_SIEM_SECURITY_HUB, - method=HTTP_GET, - payload=request - ) + return self.make_request( + path=CustodianEndpoint.HEALTH_ID, + path_params={'id': _id}, + method=HTTPMethod.GET, + ) - def siem_delete(self, tenant_name, configuration_type): - request = { - PARAM_TENANT_NAME: tenant_name - } - if configuration_type == PARAM_DOJO: - return self.__make_request( - resource=API_SIEM_DOJO, - method=HTTP_DELETE, - payload=request - ) - else: - return self.__make_request( - resource=API_SIEM_SECURITY_HUB, - method=HTTP_DELETE, - payload=request - ) + def license_get(self, license_key, **kwargs): + return self.make_request( + path=CustodianEndpoint.LICENSES_LICENSE_KEY, + path_params={'license_key': license_key}, + method=HTTPMethod.GET, + query=sifted(kwargs) + ) + + def license_query(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.LICENSES, + method=HTTPMethod.GET, + query=sifted(kwargs) + ) + + def license_post(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.LICENSES, + method=HTTPMethod.POST, + data=sifted(kwargs) + ) + + def license_delete(self, license_key: str, **kwargs): + return self.make_request( + path=CustodianEndpoint.LICENSES_LICENSE_KEY, + path_params={'license_key': license_key}, + method=HTTPMethod.DELETE, + data=sifted(kwargs) + ) + + def license_sync(self, license_key: str, **kwargs): + return self.make_request( + path=CustodianEndpoint.LICENSES_LICENSE_KEY_SYNC, + path_params={'license_key': license_key}, + method=HTTPMethod.POST, + data=sifted(kwargs) + ) - def siem_dojo_post(self, tenant_name, - configuration, entities_mapping=None): - request = { - PARAM_TENANT_NAME: tenant_name, - PARAM_CONFIGURATION: configuration, - PARAM_ENTITIES_MAPPING: entities_mapping - } - return self.__make_request( - resource=API_SIEM_DOJO, - method=HTTP_POST, - payload=self.sifted(request) - ) - - def siem_security_hub_post(self, tenant_name, configuration): - request = { - PARAM_TENANT_NAME: tenant_name, - PARAM_CONFIGURATION: configuration - } - return self.__make_request( - resource=API_SIEM_SECURITY_HUB, - method=HTTP_POST, - payload=request - ) - - def siem_dojo_patch(self, tenant_name, configuration, - entities_mapping=None, clear_existing_mapping=None): - request = { - PARAM_TENANT_NAME: tenant_name, - PARAM_CONFIGURATION: configuration - } - if entities_mapping: - request[PARAM_ENTITIES_MAPPING] = entities_mapping - if clear_existing_mapping is not None: - request[PARAM_CLEAR_EXISTING_MAPPING] = clear_existing_mapping - return self.__make_request( - resource=API_SIEM_DOJO, - method=HTTP_PATCH, - payload=request - ) - - def siem_security_hub_patch(self, tenant_name, configuration): - request = { - PARAM_TENANT_NAME: tenant_name, - PARAM_CONFIGURATION: configuration - } - return self.__make_request( - resource=API_SIEM_SECURITY_HUB, - method=HTTP_PATCH, - payload=request - ) - - def license_get(self, license_key, customer): - request = { - PARAM_LICENSE_HASH_KEY: license_key, - PARAM_CUSTOMER: customer - } - return self.__make_request( - resource=API_LICENSE, - method=HTTP_GET, - payload=self.sifted(request) - ) - - def license_post(self, tenant: str, tenant_license_key: str): - return self.__make_request( - resource=API_LICENSE, - method=HTTP_POST, - payload=self.sifted({ - PARAM_TENANT_NAME: tenant, - PARAM_TENANT_LICENSE_KEY: tenant_license_key, - }) - ) - - def license_delete(self, - customer_name, - license_key): - request = { - PARAM_CUSTOMER: customer_name, - PARAM_LICENSE_HASH_KEY: license_key, - } - return self.__make_request( - resource=API_LICENSE, - method=HTTP_DELETE, - payload=request - ) - - def license_sync(self, license_key): - request = { - PARAM_LICENSE_HASH_KEY: license_key, - } - return self.__make_request( - resource=API_LICENSE_SYNC, - method=HTTP_POST, - payload=request - ) - - def findings_get(self, tenant_name: str, - filter_dict: dict, dependent: bool, expansion: str, - format_dict: dict, get_url: bool, raw: bool): - _query = {PARAM_EXPAND_ON: expansion, PARAM_RAW: raw} - _query.update(filter_dict) - _query.update(format_dict) - if tenant_name: - _query.update({PARAM_TENANT_NAME: tenant_name}) - if dependent: - _query.update({PARAM_DEPENDENT_INCLUSION: dependent}) - if get_url: - _query.update({PARAM_GET_URL: get_url}) - return self.__make_request( - resource=API_FINDINGS, - method=HTTP_GET, - payload=self.sifted(_query) - ) - - def findings_delete(self, tenant_name: str): - _payload = {PARAM_TENANT_NAME: tenant_name} - return self.__make_request( - resource=API_FINDINGS, - method=HTTP_DELETE, - payload=self.sifted(_payload) - ) - - def user_assign_tenants(self, username: str, tenants: tuple): - request = { - PARAM_TARGET_USER: username, - PARAM_TENANTS: tenants - } - return self.__make_request(resource=API_USER_TENANTS, - method=HTTP_PATCH, payload=request) - - def user_unassign_tenants(self, username: str, tenants: tuple, - all_tenants: bool): - request = { - PARAM_TARGET_USER: username, - PARAM_TENANTS: tenants, - PARAM_ALL: all_tenants - } - return self.__make_request(resource=API_USER_TENANTS, - method=HTTP_DELETE, payload=request) - - def user_describe_tenants(self, username: str): - request = { - PARAM_TARGET_USER: username - } - return self.__make_request(resource=API_USER_TENANTS, - method=HTTP_GET, payload=request) - - def mail_setting_get(self, disclose: bool): - query = {PARAM_DISCLOSE: disclose} - return self.__make_request( - resource=API_MAIL_SETTING, method=HTTP_GET, - payload=self.sifted(query) - ) - - def mail_setting_post( - self, username: str, password: str, host: str, port: int, - password_alias: str, use_tls: bool, default_sender: str, - max_emails: int - ): - payload = { - PARAM_USERNAME: username, - PARAM_PASSWORD: password, - PARAM_HOST: host, - PARAM_PORT: port, - PARAM_PASSWORD_ALIAS: password_alias, - PARAM_USE_TLS: use_tls, - PARAM_DEFAULT_SENDER: default_sender, - PARAM_MAX_EMAILS: max_emails - } - return self.__make_request( - resource=API_MAIL_SETTING, method=HTTP_POST, payload=payload + def mail_setting_get(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.SETTINGS_MAIL, + method=HTTPMethod.GET, + query=sifted(kwargs) + ) + + def mail_setting_post(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.SETTINGS_MAIL, + method=HTTPMethod.POST, + data=sifted(kwargs) ) def mail_setting_delete(self): - return self.__make_request( - resource=API_MAIL_SETTING, method=HTTP_DELETE, payload={} + return self.make_request( + path=CustodianEndpoint.SETTINGS_MAIL, + method=HTTPMethod.DELETE, + ) + + def reports_sending_setting_enable(self): + return self.make_request( + path=CustodianEndpoint.SETTINGS_SEND_REPORTS, + method=HTTPMethod.POST, + data={'enable': True} + ) + + def reports_sending_setting_disable(self): + return self.make_request( + path=CustodianEndpoint.SETTINGS_SEND_REPORTS, + method=HTTPMethod.POST, + data={'enable': False} ) def lm_config_setting_get(self): - return self.__make_request( - resource=API_LM_CONFIG_SETTING, method=HTTP_GET, payload={} + return self.make_request( + path=CustodianEndpoint.SETTINGS_LICENSE_MANAGER_CONFIG, + method=HTTPMethod.GET, ) def lm_config_setting_post(self, **kwargs): - return self.__make_request( - resource=API_LM_CONFIG_SETTING, - method=HTTP_POST, - payload=self.sifted(kwargs) + return self.make_request( + path=CustodianEndpoint.SETTINGS_LICENSE_MANAGER_CONFIG, + method=HTTPMethod.POST, + data=sifted(kwargs) ) def lm_config_setting_delete(self): - return self.__make_request( - resource=API_LM_CONFIG_SETTING, method=HTTP_DELETE, payload={} - ) - - def lm_client_setting_get(self, frmt: str): - query = { - PARAM_FORMAT: frmt - } - return self.__make_request( - resource=API_LM_CLIENT_SETTING, - method=HTTP_GET, payload=query - ) - - def lm_client_setting_post( - self, key_id: str, algorithm: str, private_key: str, frmt: str, - b64encoded: bool - ): - payload = { - PARAM_KEY_ID: key_id, - PARAM_ALGORITHM: algorithm, - PARAM_PRIVATE_KEY: private_key, - PARAM_FORMAT: frmt, - PARAM_B64ENCODED: b64encoded - } - return self.__make_request( - resource=API_LM_CLIENT_SETTING, method=HTTP_POST, payload=payload - ) - - def lm_client_setting_delete(self, key_id: str): - return self.__make_request( - resource=API_LM_CLIENT_SETTING, method=HTTP_DELETE, payload={ - PARAM_KEY_ID: key_id - } - ) - - def event_action(self, vendor: Optional[str], events: List[Dict]): - return self.__make_request( - resource=EVENT_RESOURCE, method=HTTP_POST, payload=self.sifted({ - PARAM_VENDOR: vendor, - PARAM_VERSION: '1.0.0', - PARAM_EVENTS: events - }) - ) - - def batch_results_get(self, br_id: str): - return self.__make_request( - resource=API_BATCH_RESULTS + f'/{br_id}', - method=HTTP_GET, payload={} - ) - - def batch_results_query( - self, customer: Optional[str], - tenant: Optional[str], start_date: Optional[str], - end_date: Optional[str], next_token: Optional[str], - limit: Optional[int] - ): - payload = { - PARAM_TENANT_NAME: tenant, - PARAM_CUSTOMER: customer, - PARAM_START: start_date, - PARAM_END: end_date, - PARAM_LIMIT: limit, - PARAM_NEXT_TOKEN: next_token - } - return self.__make_request( - resource=API_BATCH_RESULTS, - method=HTTP_GET, payload=self.sifted(request=payload) - ) - - def report_digests_get( - self, tenant_name: str, start_date: str, - end_date: str, job_type: str, href: bool, - jobs: bool = False, job_id: Optional[str] = None - ): - resource = API_DIGESTS_REPORTS - jobs_resource = f'/{PARAM_JOBS}' - - payload = { - PARAM_HREF: href, - PARAM_TYPE: job_type - } - - if job_id: - jobs_resource += f'/{job_id}' - jobs = True - else: - payload[PARAM_START_ISO] = start_date - payload[PARAM_END_ISO] = end_date - resource += f'/{PARAM_TENANTS}/{tenant_name}' - - if jobs: - resource += jobs_resource - - return self.__make_request( - resource=resource, method=HTTP_GET, payload=self.sifted(payload) - ) - - def report_digests_query( - self, customer: str, start_date: str, end_date: str, - job_type: str, href: bool, jobs: bool = False - ): - payload = { - PARAM_START_ISO: start_date, - PARAM_END_ISO: end_date, - PARAM_HREF: href, - PARAM_TYPE: job_type, - PARAM_CUSTOMER: customer - } - resource = API_DIGESTS_REPORTS + f'/{PARAM_TENANTS}' - if jobs: - resource += f'/{PARAM_JOBS}' - - return self.__make_request( - resource=resource, method=HTTP_GET, payload=self.sifted(payload) - ) - - def report_details_get( - self, tenant_name: str, start_date: str, - end_date: str, job_type: str, href: bool, - jobs: bool = False, job_id: Optional[str] = None - ): - resource = API_DETAILS_REPORTS - jobs_resource = f'/{PARAM_JOBS}' - - payload = { - PARAM_HREF: href, - PARAM_TYPE: job_type - } - - if job_id: - jobs_resource += f'/{job_id}' - jobs = True - else: - payload[PARAM_START_ISO] = start_date - payload[PARAM_END_ISO] = end_date - resource += f'/{PARAM_TENANTS}/{tenant_name}' - - if jobs: - resource += jobs_resource - - return self.__make_request( - resource=resource, method=HTTP_GET, payload=self.sifted(payload) - ) - - def report_details_query( - self, customer: str, start_date: str, end_date: str, - job_type: str, href: bool, jobs: bool = False - ): - payload = { - PARAM_START_ISO: start_date, - PARAM_END_ISO: end_date, - PARAM_HREF: href, - PARAM_TYPE: job_type, - PARAM_CUSTOMER: customer - } - resource = API_DETAILS_REPORTS + f'/{PARAM_TENANTS}' - if jobs: - resource += f'/{PARAM_JOBS}' - - return self.__make_request( - resource=resource, method=HTTP_GET, payload=self.sifted(payload) - ) - - def report_compliance_get(self, tenant_name: str = None, - href: bool = None, job_type: str = None, - jobs: bool = False, - job_id: Optional[str] = None): - resource = API_COMPLIANCE_REPORTS - jobs_resource = f'/{PARAM_JOBS}' - - payload = { - PARAM_HREF: href, - PARAM_TYPE: job_type, - } - - if job_id: - jobs_resource += f'/{job_id}' - jobs = True - else: - resource += f'/{PARAM_TENANTS}' - if tenant_name: - resource += f'/{tenant_name}' - - if jobs: - resource += jobs_resource - - return self.__make_request( - resource=resource, method=HTTP_GET, payload=self.sifted(payload) - ) - - def report_errors_get( - self, job_id: str, job_type: str, href: bool, - frmt: Optional[str] = None, subtype: str = None - ): - resource = API_ERROR_REPORTS - jobs_resource = f'/{PARAM_JOBS}' - - payload = { - PARAM_HREF: href, - PARAM_TYPE: job_type, - PARAM_FORMAT: frmt - } - if subtype: - resource += f'/{subtype}' - - jobs_resource += f'/{job_id}' - jobs = True - - if jobs and job_id: - resource += jobs_resource - - return self.__make_request( - resource=resource, method=HTTP_GET, payload=self.sifted(payload) - ) - - def report_errors_query( - self, end_date: str = None, - customer: Optional[str] = None, - tenant_name: Optional[str] = None, - account_name: Optional[str] = None, - start_date: str = None, job_type: str = None, href: bool = None, - frmt: Optional[str] = None, subtype: str = None - ): - resource = API_ERROR_REPORTS - payload = { - PARAM_HREF: href, - PARAM_TYPE: job_type, - PARAM_FORMAT: frmt, - PARAM_START_ISO: start_date, - PARAM_END_ISO: end_date, - PARAM_CUSTOMER: customer - } - if subtype: - resource += f'/{subtype}' - - resource += f'/{PARAM_TENANTS}' - if tenant_name: - resource += f'/{tenant_name}' - if account_name: - resource += f'/{PARAM_ACCOUNTS}/{account_name}' - - return self.__make_request( - resource=resource, method=HTTP_GET, payload=self.sifted(payload) - ) - - def report_rules_get( - self, tenant_name: str = None, job_type: str = None, - href: bool = None, - start_date: str = None, end_date: str = None, - target_rule: Optional[str] = None, - frmt: Optional[str] = None, - jobs: bool = False, job_id: Optional[str] = None - ): - resource = API_RULES_REPORTS - jobs_resource = f'/{PARAM_JOBS}' - - payload = { - PARAM_HREF: href, - PARAM_FORMAT: frmt, - PARAM_TYPE: job_type, - PARAM_RULE: target_rule - } - - if job_id: - jobs_resource += f'/{job_id}' - jobs = True - else: - payload[PARAM_START_ISO] = start_date - payload[PARAM_END_ISO] = end_date - resource += f'/{PARAM_TENANTS}/{tenant_name}' - - if jobs: - resource += jobs_resource - - return self.__make_request( - resource=resource, method=HTTP_GET, payload=self.sifted(payload) - ) - - def report_rules_query( - self, customer: str, start_date: str, end_date: str, - job_type: str, href: bool, jobs: bool = False, - frmt: Optional[str] = None, target_rule: Optional[str] = None - ): - payload = { - PARAM_START_ISO: start_date, - PARAM_END_ISO: end_date, - PARAM_HREF: href, - PARAM_TYPE: job_type, - PARAM_CUSTOMER: customer, - PARAM_FORMAT: frmt, - PARAM_RULE: target_rule - } - resource = API_RULES_REPORTS + f'/{PARAM_TENANTS}' - if jobs: - resource += f'/{PARAM_JOBS}' - - return self.__make_request( - resource=resource, method=HTTP_GET, payload=self.sifted(payload) - ) - - def report_resource_latest(self, **kwargs): - tenant_name = kwargs.pop('tenant_name') - return self.__make_request( - resource=API_REPORTS_RESOURCES_LATEST.format( - tenant_name=tenant_name), - method=HTTP_GET, - payload=self.sifted(kwargs) - ) - - def report_resource_jobs(self, **kwargs): - tenant_name = kwargs.pop('tenant_name') - return self.__make_request( - resource=API_REPORTS_RESOURCES_JOBS.format( - tenant_name=tenant_name), - method=HTTP_GET, - payload=self.sifted(kwargs) - ) - - def report_resource_job(self, **kwargs): - job_id = kwargs.pop('id') - return self.__make_request( - resource=API_REPORTS_RESOURCES_JOB.format(job_id=job_id), - method=HTTP_GET, - payload=self.sifted(kwargs) + return self.make_request( + path=CustodianEndpoint.SETTINGS_LICENSE_MANAGER_CONFIG, + method=HTTPMethod.DELETE, + ) + + def lm_client_setting_get(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.SETTINGS_LICENSE_MANAGER_CLIENT, + method=HTTPMethod.GET, + query=sifted(kwargs) + ) + + def lm_client_setting_post(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.SETTINGS_LICENSE_MANAGER_CLIENT, + method=HTTPMethod.POST, + data=sifted(kwargs) + ) + + def lm_client_setting_delete(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.SETTINGS_LICENSE_MANAGER_CLIENT, + method=HTTPMethod.DELETE, + data=sifted(kwargs) + ) + + def event_action(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.EVENT, + method=HTTPMethod.POST, + data=sifted(kwargs) + ) + + def batch_results_get(self, br_id: str, **kwargs): + return self.make_request( + path=CustodianEndpoint.BATCH_RESULTS_JOB_ID, + path_params={'batch_results_id': br_id}, + method=HTTPMethod.GET, + query=sifted(kwargs) + ) + + def batch_results_query(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.BATCH_RESULTS, + method=HTTPMethod.GET, + query=sifted(kwargs) + ) + + def report_digest_jobs(self, job_id, **kwargs): + return self.make_request( + path=CustodianEndpoint.REPORTS_DIGESTS_JOBS_JOB_ID, + path_params={'job_id': job_id}, + method=HTTPMethod.GET, + query=sifted(kwargs) + ) + + def report_digest_tenants(self, tenant_name, **kwargs): + return self.make_request( + path=CustodianEndpoint.REPORTS_DIGESTS_TENANTS_TENANT_NAME_JOBS, + path_params={'tenant_name': tenant_name}, + method=HTTPMethod.GET, + query=sifted(kwargs) + ) + + def report_details_jobs(self, job_id, **kwargs): + return self.make_request( + path=CustodianEndpoint.REPORTS_DETAILS_JOBS_JOB_ID, + path_params={'job_id': job_id}, + method=HTTPMethod.GET, + query=sifted(kwargs) + ) + + def report_details_tenants(self, tenant_name, **kwargs): + return self.make_request( + path=CustodianEndpoint.REPORTS_DETAILS_TENANTS_TENANT_NAME_JOBS, + path_params={'tenant_name': tenant_name}, + method=HTTPMethod.GET, + query=sifted(kwargs) + ) + + def report_findings_jobs(self, job_id, **kwargs): + return self.make_request( + path=CustodianEndpoint.REPORTS_FINDINGS_JOBS_JOB_ID, + path_params={'job_id': job_id}, + method=HTTPMethod.GET, + query=sifted(kwargs) + ) + + def report_findings_tenants(self, tenant_name, **kwargs): + return self.make_request( + path=CustodianEndpoint.REPORTS_FINDINGS_TENANTS_TENANT_NAME_JOBS, + path_params={'tenant_name': tenant_name}, + method=HTTPMethod.GET, + query=sifted(kwargs) + ) + + def report_compliance_jobs(self, job_id, **kwargs): + return self.make_request( + path=CustodianEndpoint.REPORTS_COMPLIANCE_JOBS_JOB_ID, + path_params={'job_id': job_id}, + method=HTTPMethod.GET, + query=sifted(kwargs) + ) + + def report_compliance_tenants(self, tenant_name, **kwargs): + return self.make_request( + path=CustodianEndpoint.REPORTS_COMPLIANCE_TENANTS_TENANT_NAME, + path_params={'tenant_name': tenant_name}, + method=HTTPMethod.GET, + query=sifted(kwargs) + ) + + def report_errors_job(self, job_id, **kwargs): + return self.make_request( + path=CustodianEndpoint.REPORTS_ERRORS_JOBS_JOB_ID, + path_params={'job_id': job_id}, + method=HTTPMethod.GET, + query=sifted(kwargs) + ) + + def report_rules_get(self, job_id, **kwargs): + return self.make_request( + path=CustodianEndpoint.REPORTS_RULES_JOBS_JOB_ID, + path_params={'job_id': job_id}, + method=HTTPMethod.GET, + query=sifted(kwargs) + ) + + def report_rules_query(self, tenant_name, **kwargs): + return self.make_request( + path=CustodianEndpoint.REPORTS_RULES_TENANTS_TENANT_NAME, + path_params={'tenant_name': tenant_name}, + method=HTTPMethod.GET, + query=sifted(kwargs) + ) + + def report_resource_latest(self, tenant_name, **kwargs): + return self.make_request( + path=CustodianEndpoint.REPORTS_RESOURCES_TENANTS_TENANT_NAME_LATEST, + path_params={'tenant_name': tenant_name}, + method=HTTPMethod.GET, + query=sifted(kwargs) + ) + + def platform_report_resource_latest(self, platform_id, **kwargs): + return self.make_request( + path=CustodianEndpoint.REPORTS_RESOURCES_PLATFORMS_K8S_PLATFORM_ID_LATEST, + path_params={'platform_id': platform_id}, + method=HTTPMethod.GET, + query=sifted(kwargs) + ) + + def report_resource_jobs(self, tenant_name, **kwargs): + return self.make_request( + path=CustodianEndpoint.REPORTS_RESOURCES_TENANTS_TENANT_NAME_JOBS, + path_params={'tenant_name': tenant_name}, + method=HTTPMethod.GET, + query=sifted(kwargs) + ) + + def report_resource_job(self, job_id, **kwargs): + return self.make_request( + path=CustodianEndpoint.REPORTS_RESOURCES_JOBS_JOB_ID, + path_params={'job_id': job_id}, + method=HTTPMethod.GET, + query=sifted(kwargs) + ) + + def report_raw_tenant(self, tenant_name, **kwargs): + return self.make_request( + path=CustodianEndpoint.REPORTS_RAW_TENANTS_TENANT_NAME_STATE_LATEST, + path_params={'tenant_name': tenant_name}, + method=HTTPMethod.GET, + query=sifted(kwargs) ) def rabbitmq_get(self, **kwargs): - return self.__make_request( - resource=API_RABBITMQ, - method=HTTP_GET, - payload=self.sifted(kwargs) + return self.make_request( + path=CustodianEndpoint.CUSTOMERS_RABBITMQ, + method=HTTPMethod.GET, + query=sifted(kwargs) ) def rabbitmq_post(self, **kwargs): - return self.__make_request( - resource=API_RABBITMQ, - method=HTTP_POST, - payload=self.sifted(kwargs) + return self.make_request( + path=CustodianEndpoint.CUSTOMERS_RABBITMQ, + method=HTTPMethod.POST, + data=sifted(kwargs) ) def rabbitmq_delete(self, **kwargs): - return self.__make_request( - resource=API_RABBITMQ, - method=HTTP_DELETE, - payload=self.sifted(kwargs) + return self.make_request( + path=CustodianEndpoint.CUSTOMERS_RABBITMQ, + method=HTTPMethod.DELETE, + data=sifted(kwargs) ) - def platform_k8s_create_native(self, **kwargs): - return self.__make_request( - resource=API_PLATFORM_K8S_NATIVE, - method=HTTP_POST, - payload=self.sifted(kwargs) + def platform_k8s_create(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.PLATFORMS_K8S, + method=HTTPMethod.POST, + data=sifted(kwargs) ) - def platform_eks_create_native(self, **kwargs): - return self.__make_request( - resource=API_PLATFORM_K8S_EKS, - method=HTTP_POST, - payload=self.sifted(kwargs) + def platform_k8s_delete(self, platform_id: str, **kwargs): + return self.make_request( + path=CustodianEndpoint.PLATFORMS_K8S_ID, + path_params={'id': platform_id}, + method=HTTPMethod.DELETE, + data=sifted(kwargs) ) - def platform_k8s_delete_eks(self, platform_id: str): - return self.__make_request( - resource=API_PLATFORM_K8S_EKS + '/' + platform_id, - method=HTTP_DELETE, - payload={} + def platform_k8s_list(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.PLATFORMS_K8S, + method=HTTPMethod.GET, + query=sifted(kwargs) ) - def platform_k8s_delete_native(self, platform_id: str): - return self.__make_request( - resource=API_PLATFORM_K8S_NATIVE + '/' + platform_id, - method=HTTP_DELETE, - payload={} + def k8s_job_post(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.JOBS_K8S, + method=HTTPMethod.POST, + data=sifted(kwargs) ) - def platform_k8s_list(self, **kwargs): - return self.__make_request( - resource=API_PLATFORM_K8S, - method=HTTP_GET, - payload=self.sifted(kwargs) + def dojo_add(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.INTEGRATIONS_DEFECT_DOJO, + method=HTTPMethod.POST, + data=sifted(kwargs) ) - def k8s_job_post(self, **kwargs): - return self.__make_request( - resource=API_K8S_JOBS, - method=HTTP_POST, - payload=self.sifted(kwargs) + def dojo_delete(self, id: str, **kwargs): + return self.make_request( + path=CustodianEndpoint.INTEGRATIONS_DEFECT_DOJO_ID, + method=HTTPMethod.DELETE, + path_params={'id': id}, + data=sifted(kwargs) + ) + + def dojo_get(self, id: str, **kwargs): + return self.make_request( + path=CustodianEndpoint.INTEGRATIONS_DEFECT_DOJO_ID, + method=HTTPMethod.GET, + path_params={'id': id}, + query=sifted(kwargs) + ) + + def dojo_query(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.INTEGRATIONS_DEFECT_DOJO, + method=HTTPMethod.GET, + query=sifted(kwargs) + ) + + def dojo_activate(self, id: str, **kwargs): + return self.make_request( + path=CustodianEndpoint.INTEGRATIONS_DEFECT_DOJO_ID_ACTIVATION, + method=HTTPMethod.PUT, + path_params={'id': id}, + data=sifted(kwargs) + ) + + def dojo_deactivate(self, id: str, **kwargs): + return self.make_request( + path=CustodianEndpoint.INTEGRATIONS_DEFECT_DOJO_ID_ACTIVATION, + method=HTTPMethod.DELETE, + path_params={'id': id}, + data=sifted(kwargs) + ) + + def dojo_get_activation(self, id: str, **kwargs): + return self.make_request( + path=CustodianEndpoint.INTEGRATIONS_DEFECT_DOJO_ID_ACTIVATION, + method=HTTPMethod.GET, + path_params={'id': id}, + data=sifted(kwargs) + ) + + def sre_add(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.INTEGRATIONS_SELF, + method=HTTPMethod.PUT, + data=sifted(kwargs) + ) + + def sre_update(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.INTEGRATIONS_SELF, + method=HTTPMethod.PATCH, + data=sifted(kwargs) + ) + + def sre_describe(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.INTEGRATIONS_SELF, + method=HTTPMethod.GET, + query=sifted(kwargs) + ) + + def sre_delete(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.INTEGRATIONS_SELF, + method=HTTPMethod.DELETE, + query=sifted(kwargs) + ) + + def license_activate(self, license_key: str, **kwargs): + return self.make_request( + path=CustodianEndpoint.LICENSE_LICENSE_KEY_ACTIVATION, + method=HTTPMethod.PUT, + path_params={'license_key': license_key}, + data=sifted(kwargs) + ) + + def license_deactivate(self, license_key: str, **kwargs): + return self.make_request( + path=CustodianEndpoint.LICENSE_LICENSE_KEY_ACTIVATION, + method=HTTPMethod.DELETE, + path_params={'license_key': license_key}, + data=sifted(kwargs) + ) + + def license_get_activation(self, license_key: str, **kwargs): + return self.make_request( + path=CustodianEndpoint.LICENSE_LICENSE_KEY_ACTIVATION, + method=HTTPMethod.GET, + path_params={'license_key': license_key}, + query=sifted(kwargs) + ) + + def license_update_activation(self, license_key: str, **kwargs): + return self.make_request( + path=CustodianEndpoint.LICENSE_LICENSE_KEY_ACTIVATION, + method=HTTPMethod.PATCH, + path_params={'license_key': license_key}, + data=sifted(kwargs) + ) + + def get_credentials(self, application_id: str, **kwargs): + return self.make_request( + path=CustodianEndpoint.CREDENTIALS_ID, + method=HTTPMethod.GET, + path_params={'id': application_id}, + query=sifted(kwargs) + ) + + def query_credentials(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.CREDENTIALS, + method=HTTPMethod.GET, + query=sifted(kwargs) + ) + + def credentials_bind(self, application_id: str, **kwargs): + return self.make_request( + path=CustodianEndpoint.CREDENTIALS_ID_BINDING, + method=HTTPMethod.PUT, + path_params={'id': application_id}, + data=sifted(kwargs) + ) + + def credentials_unbind(self, application_id: str, **kwargs): + return self.make_request( + path=CustodianEndpoint.CREDENTIALS_ID_BINDING, + method=HTTPMethod.DELETE, + path_params={'id': application_id}, + data=sifted(kwargs) + ) + + def credentials_get_binding(self, application_id: str, **kwargs): + return self.make_request( + path=CustodianEndpoint.CREDENTIALS_ID_BINDING, + method=HTTPMethod.GET, + path_params={'id': application_id}, + query=sifted(kwargs) + ) + + def get_user(self, username: str): + return self.make_request( + path=CustodianEndpoint.USERS_USERNAME, + path_params={'username': username}, + method=HTTPMethod.GET, + ) + + def delete_user(self, username: str, **kwargs): + return self.make_request( + path=CustodianEndpoint.USERS_USERNAME, + path_params={'username': username}, + method=HTTPMethod.DELETE, + query=sifted(kwargs) + ) + + def query_user(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.USERS, + method=HTTPMethod.GET, + query=sifted(kwargs) + ) + + def create_user(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.USERS, + method=HTTPMethod.POST, + data=sifted(kwargs) + ) + + def reset_password(self, **kwargs): + return self.make_request( + path=CustodianEndpoint.USERS_RESET_PASSWORD, + method=HTTPMethod.POST, + data=sifted(kwargs) ) diff --git a/c7n/c7ncli/service/config.py b/c7n/c7ncli/service/config.py index 693afcc32..f1f707676 100644 --- a/c7n/c7ncli/service/config.py +++ b/c7n/c7ncli/service/config.py @@ -2,29 +2,27 @@ from concurrent.futures import ThreadPoolExecutor, as_completed from itertools import chain from pathlib import Path -from typing import Optional, Union, Dict, List, Generator, Tuple +from typing import Generator, TYPE_CHECKING from botocore.credentials import JSONFileCache -try: - # just for typing - from modular_cli_sdk.services.credentials_manager import \ - AbstractCredentialsManager -except ImportError: - pass + from c7ncli.service.constants import CONFIG_FOLDER, CONF_ACCESS_TOKEN, \ - CONF_API_LINK, CONF_ITEMS_PER_COLUMN -from c7ncli.service.logger import get_logger, get_user_logger + CONF_API_LINK, CONF_ITEMS_PER_COLUMN, CONF_REFRESH_TOKEN +from c7ncli.service.logger import get_logger + +if TYPE_CHECKING: + from modular_cli_sdk.services.credentials_manager import ( + AbstractCredentialsManager) SYSTEM_LOG = get_logger(__name__) -USER_LOG = get_user_logger(__name__) -Json = Union[Dict, List, int, str, bool, None] +Json = dict | list | float | int | str | bool class AbstractCustodianConfig(ABC): @property @abstractmethod - def api_link(self) -> Optional[str]: + def api_link(self) -> str | None: ... @api_link.setter @@ -34,7 +32,7 @@ def api_link(self, value): @property @abstractmethod - def access_token(self) -> Optional[str]: + def access_token(self) -> str | None: ... @access_token.setter @@ -44,7 +42,17 @@ def access_token(self, value): @property @abstractmethod - def items_per_column(self) -> Optional[int]: + def refresh_token(self) -> str | None: + ... + + @refresh_token.setter + @abstractmethod + def refresh_token(self, value): + ... + + @property + @abstractmethod + def items_per_column(self) -> int | None: ... @items_per_column.setter @@ -53,13 +61,21 @@ def items_per_column(self, value: int): ... @abstractmethod - def items(self) -> Generator[Tuple[str, Json], None, None]: + def items(self) -> Generator[tuple[str, Json], None, None]: ... @abstractmethod def clear(self): ... + @abstractmethod + def set(self, key: str, value: Json): + ... + + @abstractmethod + def update(self, dct: dict): + ... + class CustodianCLIConfig(JSONFileCache, AbstractCustodianConfig): """ @@ -72,12 +88,19 @@ class CustodianCLIConfig(JSONFileCache, AbstractCustodianConfig): def __init__(self, prefix: str = 'root', working_dir: Path = CACHE_DIR): super().__init__(working_dir=working_dir / prefix) - def get(self, cache_key: str) -> Optional[Json]: + def get(self, cache_key: str) -> Json | None: if cache_key in self: return self[cache_key] + def set(self, key: str, value: Json): + self[key] = value + + def update(self, dct: dict): + for key, value in dct.items(): + self.set(key, value) + @property - def items_per_column(self) -> Optional[int]: + def items_per_column(self) -> int | None: """ If None, all the items is shown. It's the default behaviour :return: @@ -95,7 +118,7 @@ def items_per_column(self): del self[CONF_ITEMS_PER_COLUMN] @property - def api_link(self) -> Optional[str]: + def api_link(self) -> str | None: return self.get(CONF_API_LINK) @api_link.setter @@ -108,7 +131,7 @@ def api_link(self): del self[CONF_API_LINK] @property - def access_token(self) -> Optional[str]: + def access_token(self) -> str | None: return self.get(CONF_ACCESS_TOKEN) @access_token.setter @@ -120,20 +143,29 @@ def access_token(self): if CONF_ACCESS_TOKEN in self: del self[CONF_ACCESS_TOKEN] + @property + def refresh_token(self) -> str | None: + return self.get(CONF_REFRESH_TOKEN) + + @refresh_token.setter + def refresh_token(self, value: str): + self[CONF_REFRESH_TOKEN] = value + @classmethod - def public_config_params(cls) -> List[property]: + def public_config_params(cls) -> list[property]: return [ cls.api_link, cls.items_per_column ] @classmethod - def private_config_params(cls) -> List[property]: + def private_config_params(cls) -> list[property]: return [ - cls.access_token + cls.access_token, + cls.refresh_token ] - def items(self) -> Generator[Tuple[str, Json], None, None]: + def items(self) -> Generator[tuple[str, Json], None, None]: with ThreadPoolExecutor() as executor: # it reads a lot of files futures = { executor.submit(prop.fget, self): prop.fget.__name__ @@ -160,7 +192,8 @@ def __init__(self, credentials_manager: 'AbstractCredentialsManager'): @property def config_dict(self) -> dict: - from modular_cli_sdk.commons.exception import ModularCliSdkBaseException + from modular_cli_sdk.commons.exception import \ + ModularCliSdkBaseException # in order to be able to use other classes from this module # without cli_sdk installed if not self._config_dict: @@ -176,8 +209,13 @@ def set(self, key: str, value: Json): config_dict[key] = value self._credentials_manager.store(config_dict) + def update(self, dct: dict): + config_dict = self.config_dict + config_dict.update(dct) + self._credentials_manager.store(config_dict) + @property - def api_link(self) -> Optional[str]: + def api_link(self) -> str | None: return self.config_dict.get(CONF_API_LINK) @api_link.setter @@ -185,7 +223,7 @@ def api_link(self, value: str): self.set(CONF_API_LINK, value) @property - def access_token(self) -> Optional[str]: + def access_token(self) -> str | None: return self.config_dict.get(CONF_ACCESS_TOKEN) @access_token.setter @@ -193,7 +231,15 @@ def access_token(self, value: str): self.set(CONF_ACCESS_TOKEN, value) @property - def items_per_column(self) -> Optional[int]: + def refresh_token(self) -> str | None: + return self.config_dict.get(CONF_REFRESH_TOKEN) + + @refresh_token.setter + def refresh_token(self, value: str): + self.set(CONF_REFRESH_TOKEN, value) + + @property + def items_per_column(self) -> int | None: return self.config_dict.get(CONF_ITEMS_PER_COLUMN) @items_per_column.setter @@ -201,7 +247,7 @@ def items_per_column(self, value: int): assert isinstance(value, (int, type(None))) self.set(CONF_ITEMS_PER_COLUMN, value) - def items(self) -> Generator[Tuple[str, Json], None, None]: + def items(self) -> Generator[tuple[str, Json], None, None]: yield CONF_API_LINK, self.api_link yield CONF_ITEMS_PER_COLUMN, self.items_per_column diff --git a/c7n/c7ncli/service/constants.py b/c7n/c7ncli/service/constants.py index cbf5fd273..e62abd9c2 100644 --- a/c7n/c7ncli/service/constants.py +++ b/c7n/c7ncli/service/constants.py @@ -1,233 +1,116 @@ +import operator from enum import Enum -PARAM_CUSTOMER_ID = 'customer_id' -PARAM_TENANT_ID = 'tenant_id' -PARAM_NAME = 'name' -PARAM_PERMISSIONS = 'permissions' -PARAM_PERMISSIONS_ADMIN = 'permissions_admin' -PARAM_EXPIRATION = 'expiration' -PARAM_POLICIES = 'policies' -PARAM_DISPLAY_NAME = 'display_name' -PARAM_CREDENTIALS = 'credentials' -PARAM_ACCESS_TOKEN = 'access_token' # google -PARAM_PROJECT_ID = 'project_id' # google -PARAM_OWNER = 'owner' -PARAM_INHERIT = 'inherit' -PARAM_CLOUD_IDENTIFIER = 'cloud_identifier' -PARAM_RULES_TO_EXCLUDE = 'rules_to_exclude' -PARAM_RULES_TO_INCLUDE = 'rules_to_include' -PARAM_ENABLED = 'enabled' -PARAM_TRUSTED_ROLE_ARN = 'trusted_role_arn' -PARAM_VERSION = 'version' -PARAM_CLOUD = 'cloud' -PARAM_RULES = 'rules' -PARAM_GET_RULES = 'get_rules' -PARAM_STANDARD = 'standard' -PARAM_RULES_NUMBER = 'rules_number' -PARAM_REGION_STATE = 'state' -PARAM_ALL_REGIONS = 'all_regions' -PARAM_CUSTOMER = 'customer' -PARAM_TENANT = 'tenant' -PARAM_TENANTS = 'tenants' -PARAM_ACCOUNT = 'account' -PARAM_ACCOUNTS = 'accounts' -PARAM_JOB_ID = 'job_id' -PARAM_TARGET_RULESETS = 'target_rulesets' -PARAM_TARGET_REGIONS = 'target_regions' -PARAM_DETAILED = 'detailed' -PARAM_GET_URL = 'get_url' -PARAM_ACTIVATION_DATE = 'activation_date' -PARAM_REGIONS = 'regions' -PARAM_REGION = 'region' -PARAM_GIT_ACCESS_SECRET = 'git_access_secret' -PARAM_GIT_ACCESS_TYPE = 'git_access_type' -PARAM_GIT_PROJECT_ID = 'git_project_id' -PARAM_GIT_REF = 'git_ref' -PARAM_GIT_RULES_PREFIX = 'git_rules_prefix' -PARAM_GIT_URL = 'git_url' -PARAM_LAST_SYNC_CURRENT_STATUS = 'latest_sync_current_status' -PARAM_RULE_ID = 'rule_id' -PARAM_CUSTOMER_DISPLAY_NAME = 'customer_display_name' -PARAM_TENANT_DISPLAY_NAME = 'tenant_display_name' -PARAM_TENANT_NAME = 'tenant_name' -PARAM_STATUS_CODE = 'status_code' -PARAM_STATUS_REASON = 'status_reason' -PARAM_DESCRIPTION = 'description' -PARAM_SERVICE_SECTION = 'service_section' -PARAM_UPDATED_DATE = 'updated_date' -PARAM_ACCOUNT_DISPLAY_NAME = 'account_display_name' -PARAM_JOB_OWNER = 'job_owner' -PARAM_SCAN_REGIONS = 'scan_regions' -PARAM_SCAN_RULESETS = 'scan_rulesets' -PARAM_STATUS = 'status' -PARAM_SUBMITTED_AT = 'submitted_at' -PARAM_CREATED_AT = 'created_at' -PARAM_STARTED_AT = 'started_at' -PARAM_STOPPED_AT = 'stopped_at' -PARAM_SUCCESSFUL_CHECKS = 'successful_checks' -PARAM_FAILED_CHECKS = 'failed_checks' -PARAM_TOTAL_CHECKS = 'total_checks_performed' -PARAM_TOTAL_RESOURCES_VIOLATED_RULES = 'total_resources_violated_rules' -PARAM_LIMIT = 'limit' -PARAM_OFFSET = 'offset' -PARAM_NEXT_TOKEN = 'next_token' -PARAM_ACTIVE = 'active' -PARAM_FULL_CLOUD = 'full_cloud' -PARAM_START = 'start' -PARAM_END = 'end' -PARAM_DIRECT_OUTPUT = 'direct_output' -PARAM_ACTION = 'action' -PARAM_EVENT_DRIVEN = 'event_driven' -PARAM_RULE_VERSION = 'rule_version' -PARAM_ALL = 'all' -PARAM_LICENSE_HASH_KEY = "license_key" -PARAM_LICENSE_KEYS = 'license_keys' -PARAM_LICENSE_KEYS_TO_PREPEND = 'license_keys_to_prepend' -PARAM_LICENSE_KEYS_TO_APPEND = 'license_keys_to_append' -PARAM_LICENSE_KEYS_TO_DETACH = 'license_keys_to_detach' -PARAM_TENANT_LICENSE_KEY = 'tenant_license_key' -PARAM_CUSTOMER_NAME = "customer_name" -PARAM_CUSTOMERS = 'customers' -PARAM_RULESET_IDS = 'ruleset_ids' -PARAM_LATEST_SYNC = 'latest_sync' -PARAM_ADD_CUSTOMERS = 'add_customers' -PARAM_REMOVE_CUSTOMERS = 'remove_customers' -PARAM_COMPLETE = 'complete' -PARAM_SEND_SCAN_RESULT = 'send_scan_result' -PARAM_RULESET_LICENSE_PRIORITY = 'ruleset_license_priority' -PARAM_TENANT_DISPLAY_NAMES = 'tenant_display_names' -PARAM_TENANT_NAMES = 'tenant_names' - -PARAM_GOVERNANCE_ENTITY_TYPE = 'governance_entity_type' -PARAM_GOVERNANCE_ENTITY_ID = 'governance_entity_id' -PARAM_MANAGEMENT_ID = 'management_id' -PARAM_RULESET = 'ruleset' - -ACTION_COMPLIANCE_REPORT = 'compliance_report' -ACTION_RULE_REPORT = 'rule_report' -ACTION_ERROR_REPORT = 'error_report' -ACTION_PUSH_REPORT = 'push_report_to_siem' - -PARAM_USERNAME = 'username' -PARAM_PASSWORD = 'password' -PARAM_ROLE = 'role' - -PARAM_DOJO = 'dojo' -PARAM_DOJO_HOST = 'host' -PARAM_DOJO_APIKEY = 'api_key' -PARAM_DOJO_USER = 'user' -PARAM_DOJO_DISPLAY_ALL_FIELDS = 'display_all_fields' -PARAM_DOJO_UPLOAD_FILES = 'upload_files' -PARAM_DOJO_RESOURCE_PER_FINDING = 'resource_per_finding' -PARAM_DOJO_SEVERITY = 'severity' -PARAM_DOJO_LIMIT = 'limit' -PARAM_DOJO_OFFSET = 'offset' - -PARAM_LM_HOST = 'host' -PARAM_LM_PORT = 'port' -PARAM_LM_VERSION = 'version' - -PARAM_KEY_ID = 'key_id' -PARAM_PRIVATE_KEY = 'private_key' -PARAM_ALGORITHM = 'algorithm' -PARAM_FORMAT = 'format' -PARAM_B64ENCODED = 'b64_encoded' - -PARAM_CHECK_PERMISSION = 'check_permission' - -API_CUSTOMER = 'customers' -API_APPLICATION = 'applications' -API_ACCESS_APPLICATION = 'applications/access' -API_DOJO_APPLICATION = 'applications/dojo' -API_PARENT = 'parents' -API_PARENT_TENANT_LINK = 'parents/tenant-link' -API_CUSTOMER_RULESET = 'customers/ruleset' -API_RULESET = 'rulesets' -API_ED_RULESET = 'rulesets/event-driven' -API_RULE_SOURCE = 'rule-sources' -API_CUSTOMER_RULE_SOURCE = 'customers/rule/source' -API_TENANT = 'tenants' -API_TENANT_REGIONS = 'tenants/regions' -API_TENANT_LICENSE_PRIORITIES = 'tenants/license-priorities' -API_TENANT_RULE_SOURCE = 'tenants/rule/source' -API_ACCOUNT = 'accounts' -API_CREDENTIALS_MANAGER = 'accounts/credential_manager' -API_ACCOUNT_RULESET = 'accounts/ruleset' -API_ACCOUNT_RULE_SOURCE = 'accounts/rule/source' -API_ACCOUNT_REGION = 'accounts/regions' -API_POLICY = 'policies' -API_POLICY_CACHE = 'policies/cache' -API_ROLE = 'roles' -API_ROLE_CACHE = 'roles/cache' -API_RULE = 'rules' -API_RULE_META_UPDATER = 'rules/update-meta' -API_BACKUPPER = 'backup' -API_METRICS_UPDATER = 'metrics/update' -API_METRICS_STATUS = 'metrics/status' -API_META_STANDARDS = 'rule-meta/standards' -API_META_MAPPINGS = 'rule-meta/mappings' -API_META_META = 'rule-meta/meta' -API_JOB = 'jobs' -API_SIGNIN = 'signin' -API_SIGNUP = 'signup' -API_LICENSE = 'license' -API_LICENSE_SYNC = "license/sync" -API_FINDINGS = 'findings' -API_SCHEDULED_JOBS = 'scheduled-job' -API_USERS = 'users' -API_USER_TENANTS = 'users/tenants' -API_MAIL_SETTING = 'settings/mail' -API_LM_CONFIG_SETTING = 'settings/license-manager/config' -API_LM_CLIENT_SETTING = 'settings/license-manager/client' -API_BATCH_RESULTS = 'batch_results' -EVENT_RESOURCE = 'event' - -API_HEALTH_CHECK = 'health' -API_SIEM_DOJO = 'siem/defect-dojo' -API_SIEM_SECURITY_HUB = 'siem/security-hub' - -API_REPORT = 'report' -API_EVENT_DRIVEN_REPORT = 'reports/event-driven' -API_PUSH_REPORTS = 'reports/push' -API_REPORTS_PUSH_DOJO = 'reports/push/dojo' -API_REPORTS_PUSH_SECURITY_HUB = 'reports/push/security-hub' -API_DIGESTS_REPORTS = 'reports/digests' -API_DETAILS_REPORTS = 'reports/details' -API_COMPLIANCE_REPORTS = 'reports/compliance' -API_ERROR_REPORTS = 'reports/errors' -API_RULES_REPORTS = 'reports/rules' -API_REPORTS_RESOURCES_LATEST = 'reports/resources/tenants/{tenant_name}/state/latest' -API_REPORTS_RESOURCES_JOBS = 'reports/resources/tenants/{tenant_name}/jobs' -API_REPORTS_RESOURCES_JOB = 'reports/resources/jobs/{job_id}' - -API_OPERATIONAL_REPORT = 'reports/operational' -API_DEPARTMENT_REPORT = 'reports/department' -API_PROJECT_REPORT = 'reports/project' -API_C_LEVEL_REPORT = 'reports/clevel' - -API_RABBITMQ = 'customers/rabbitmq' -API_PLATFORM_K8S = 'platforms/k8s' -API_PLATFORM_K8S_NATIVE = 'platforms/k8s/native' -API_PLATFORM_K8S_EKS = 'platforms/k8s/eks' -API_K8S_JOBS = 'jobs/k8s' - -PARAM_ID = 'id' -PARAM_RULE_SOURCE_ID = 'rule_source_id' - -MODEL_CUSTOMER = 'CUSTOMER' -MODEL_TENANT = 'TENANT' -MODEL_ACCOUNT = 'ACCOUNT' - -POLICIES_TO_ATTACH = 'policies_to_attach' -POLICIES_TO_DETACH = 'policies_to_detach' - -PERMISSIONS_TO_ATTACH = 'permissions_to_attach' -PERMISSIONS_TO_DETACH = 'permissions_to_detach' - -RULES_TO_ATTACH = 'rules_to_attach' -RULES_TO_DETACH = 'rules_to_detach' + + +# from http import HTTPMethod # python3.11+ + + +class HTTPMethod(str, Enum): + HEAD = 'HEAD' + GET = 'GET' + POST = 'POST' + PATCH = 'PATCH' + DELETE = 'DELETE' + PUT = 'PUT' + + +class CustodianEndpoint(str, Enum): + """ + Should correspond to Api gateway models + """ + DOC = '/doc' + JOBS = '/jobs' + ROLES = '/roles' + RULES = '/rules' + USERS = '/users' + EVENT = '/event' + SIGNIN = '/signin' + SIGNUP = '/signup' + HEALTH = '/health' + REFRESH = '/refresh' + TENANTS = '/tenants' + RULESETS = '/rulesets' + LICENSES = '/licenses' + POLICIES = '/policies' + JOBS_K8S = '/jobs/k8s' + CUSTOMERS = '/customers' + HEALTH_ID = '/health/{id}' + JOBS_JOB = '/jobs/{job_id}' + DOC_PROXY = '/doc/{proxy+}' + ROLES_NAME = '/roles/{name}' + CREDENTIALS = '/credentials' + META_META = '/rule-meta/meta' + RULE_SOURCES = '/rule-sources' + USERS_WHOAMI = '/users/whoami' + SCHEDULED_JOB = '/scheduled-job' + PLATFORMS_K8S = '/platforms/k8s' + SETTINGS_MAIL = '/settings/mail' + JOBS_STANDARD = '/jobs/standard' + BATCH_RESULTS = '/batch-results' + REPORTS_RETRY = '/reports/retry' + POLICIES_NAME = '/policies/{name}' + METRICS_STATUS = '/metrics/status' + REPORTS_CLEVEL = '/reports/clevel' + METRICS_UPDATE = '/metrics/update' + REPORTS_STATUS = '/reports/status' + REPORTS_PROJECT = '/reports/project' + USERS_USERNAME = '/users/{username}' + CREDENTIALS_ID = '/credentials/{id}' + META_MAPPINGS = '/rule-meta/mappings' + RULESETS_CONTENT = '/rulesets/content' + ED_RULESETS = '/rulesets/event-driven' + DOC_SWAGGER_JSON = '/doc/swagger.json' + META_STANDARDS = '/rule-meta/standards' + RULE_META_UPDATER = '/rules/update-meta' + REPORTS_PUSH_DOJO = '/reports/push/dojo' + CUSTOMERS_RABBITMQ = '/customers/rabbitmq' + REPORTS_DIAGNOSTIC = '/reports/diagnostic' + REPORTS_DEPARTMENT = '/reports/department' + INTEGRATIONS_SELF = '/integrations/temp/sre' + SCHEDULED_JOB_NAME = '/scheduled-job/{name}' + REPORTS_OPERATIONAL = '/reports/operational' + TENANTS_TENANT_NAME = '/tenants/{tenant_name}' + USERS_RESET_PASSWORD = '/users/reset-password' + REPORTS_EVENT_DRIVEN = '/reports/event_driven' + LICENSES_LICENSE_KEY = '/licenses/{license_key}' + SETTINGS_SEND_REPORTS = '/settings/send_reports' + PLATFORMS_K8S_ID = '/platforms/k8s/{platform_id}' + CREDENTIALS_ID_BINDING = '/credentials/{id}/binding' + CUSTOMERS_EXCLUDED_RULES = '/customers/excluded-rules' + INTEGRATIONS_DEFECT_DOJO = '/integrations/defect-dojo' + REPORTS_PUSH_DOJO_JOB_ID = '/reports/push/dojo/{job_id}' + REPORTS_RULES_JOBS_JOB_ID = '/reports/rules/jobs/{job_id}' + BATCH_RESULTS_JOB_ID = '/batch-results/{batch_results_id}' + LICENSES_LICENSE_KEY_SYNC = '/licenses/{license_key}/sync' + REPORTS_ERRORS_JOBS_JOB_ID = '/reports/errors/jobs/{job_id}' + INTEGRATIONS_DEFECT_DOJO_ID = '/integrations/defect-dojo/{id}' + REPORTS_DIGESTS_JOBS_JOB_ID = '/reports/digests/jobs/{job_id}' + REPORTS_DETAILS_JOBS_JOB_ID = '/reports/details/jobs/{job_id}' + TENANTS_TENANT_NAME_REGIONS = '/tenants/{tenant_name}/regions' + REPORTS_FINDINGS_JOBS_JOB_ID = '/reports/findings/jobs/{job_id}' + REPORTS_RESOURCES_JOBS_JOB_ID = '/reports/resources/jobs/{job_id}' + REPORTS_COMPLIANCE_JOBS_JOB_ID = '/reports/compliance/jobs/{job_id}' + SETTINGS_LICENSE_MANAGER_CLIENT = '/settings/license-manager/client' + SETTINGS_LICENSE_MANAGER_CONFIG = '/settings/license-manager/config' + LICENSE_LICENSE_KEY_ACTIVATION = '/licenses/{license_key}/activation' + REPORTS_RULES_TENANTS_TENANT_NAME = '/reports/rules/tenants/{tenant_name}' + TENANTS_TENANT_NAME_EXCLUDED_RULES = '/tenants/{tenant_name}/excluded-rules' + TENANTS_TENANT_NAME_ACTIVE_LICENSES = '/tenants/{tenant_name}/active-licenses' + REPORTS_COMPLIANCE_TENANTS_TENANT_NAME = '/reports/compliance/tenants/{tenant_name}' + INTEGRATIONS_DEFECT_DOJO_ID_ACTIVATION = '/integrations/defect-dojo/{id}/activation' + REPORTS_DETAILS_TENANTS_TENANT_NAME_JOBS = '/reports/details/tenants/{tenant_name}/jobs' + REPORTS_DIGESTS_TENANTS_TENANT_NAME_JOBS = '/reports/digests/tenants/{tenant_name}/jobs' + REPORTS_FINDINGS_TENANTS_TENANT_NAME_JOBS = '/reports/findings/tenants/{tenant_name}/jobs' + REPORTS_RESOURCES_TENANTS_TENANT_NAME_JOBS = '/reports/resources/tenants/{tenant_name}/jobs' + REPORTS_RAW_TENANTS_TENANT_NAME_STATE_LATEST = '/reports/raw/tenants/{tenant_name}/state/latest' + REPORTS_RESOURCES_TENANTS_TENANT_NAME_LATEST = '/reports/resources/tenants/{tenant_name}/state/latest' + REPORTS_RESOURCES_PLATFORMS_K8S_PLATFORM_ID_LATEST = '/reports/resources/platforms/k8s/{platform_id}/state/latest' + + +LAMBDA_INVOCATION_TRACE_ID_HEADER = 'Lambda-Invocation-Trace-Id' +SERVER_VERSION_HEADER = 'Accept-Version' GIT_ACCESS_TOKEN = 'TOKEN' -AVAILABLE_GIT_ACCESS_TYPES = (GIT_ACCESS_TOKEN,) class ParentScope(str, Enum): @@ -237,90 +120,28 @@ class ParentScope(str, Enum): @classmethod def iter(cls): - """ - Iterates over values, not enum items - """ - return map(lambda x: x.value, cls) + return map(operator.attrgetter('value'), cls) AWS, AZURE, GCP, GOOGLE = 'AWS', 'AZURE', 'GCP', 'GOOGLE' KUBERNETES = 'KUBERNETES' # This tuple represent clouds types of rules/rulesets, not tenants or jobs RULE_CLOUDS = (AWS, AZURE, GCP, KUBERNETES) -REGION_STATE_ACTIVE = 'ACTIVE' -REGION_STATE_INACTIVE = 'INACTIVE' -AVAILABLE_REGION_STATES = (REGION_STATE_ACTIVE, REGION_STATE_INACTIVE) - -PARAM_CONFIGURATION_TYPE = 'configuration_type' -PARAM_CONFIGURATION = 'configuration' - -PARAM_ENTITIES_MAPPING = 'entities_mapping' -PARAM_CLEAR_EXISTING_MAPPING = 'clear_existing_mapping' -PARAM_PRODUCT_TYPE_NAME = 'product_type_name' -PARAM_PRODUCT_NAME = 'product_name' -PARAM_ENGAGEMENT_NAME = 'engagement_name' -PARAM_TEST_TITLE = 'test_title' - -PARAM_API_VERSION = 'api_version' - -PARAM_TENANT_ALLOWANCE = 'tenant_allowance' -PARAM_TENANT_RESTRICTION = 'tenant_restriction' -PARAM_TENANT_ACCOUNT_EXCLUSION = 'tenant_account_exclusion' - -HEALTH_CHECK_COMMAND_NAME = 'health_check' -CLEAN_CACHE_COMMAND_NAME = 'clean_cache' -UPDATE_RULES_COMMAND_NAME = 'update_rules' -RULE_SOURCE_GROUP_NAME = 'rule_source' -CREDENTIALS_MANAGER_GROUP_NAME = 'credentials_manager' -SECURITY_HUB_COMMAND_NAME = 'security_hub' +DATA_ATTR = 'data' ITEMS_ATTR = 'items' +ERRORS_ATTR = 'errors' MESSAGE_ATTR = 'message' -TRACE_ID_ATTR = 'trace_id' NEXT_TOKEN_ATTR = 'next_token' -PARAM_RAW = 'raw' -PARAM_EXPAND_ON = 'expand_on' -PARAM_REGIONS_TO_INCLUDE = 'regions_to_include' -PARAM_RESOURCE_TYPES_TO_INCLUDE = 'resource_types_to_include' -PARAM_SEVERITIES_TO_INCLUDE = 'severities_to_include' -PARAM_DATA_TYPE = 'data_type' -PARAM_MAP_KEY = 'map_key' -PARAM_DEPENDENT_INCLUSION = 'dependent_inclusion' -PARAM_SCHEDULE_EXPRESSION = 'schedule' -PARAM_TARGET_USER = 'target_user' - -PARAM_DISCLOSE = 'disclose' -PARAM_PASSWORD_ALIAS = 'password_alias' -PARAM_HOST = 'host' -PARAM_PORT = 'port' -PARAM_MAX_EMAILS = 'max_emails' -PARAM_DEFAULT_SENDER = 'default_sender' -PARAM_USE_TLS = 'use_tls' - -PARAM_EVENT_TYPE = 'event_type' -PARAM_EVENT_BODY = 'event_body' -PARAM_EVENTS = 'events' -PARAM_VENDOR = 'vendor' - C7NCLI_LOG_LEVEL_ENV_NAME = 'C7NCLI_LOG_LEVEL' C7NCLI_DEVELOPER_MODE_ENV_NAME = 'C7N_CLI_DEVELOPER_MODE' -CLOUDTRAIL_EVENT_TYPE = 'CloudTrail' -EVENTBRIDGE_EVENT_TYPE = 'EventBridge' - -PARAM_START_ISO = 'start_iso' -PARAM_END_ISO = 'end_iso' -PARAM_HREF = 'href' -PARAM_TYPE = 'type' -PARAM_TYPES = 'types' -PARAM_JOBS = 'jobs' -PARAM_RULE = 'rule' -MANUAL_JOB_TYPE = 'manual' -REACTIVE_JOB_TYPE = 'reactive' -AVAILABLE_JOB_TYPES = ( - MANUAL_JOB_TYPE, REACTIVE_JOB_TYPE -) + +class JobType(str, Enum): + MANUAL = 'manual' + REACTIVE = 'reactive' + # Credentials ENV_AWS_ACCESS_KEY_ID = 'AWS_ACCESS_KEY_ID' @@ -338,39 +159,70 @@ def iter(cls): DEFAULT_AWS_REGION = 'us-east-1' -PARAM_AWS_ACCESS_KEY = ENV_AWS_ACCESS_KEY_ID -PARAM_AWS_SECRET_ACCESS_KEY = ENV_AWS_SECRET_ACCESS_KEY -PARAM_AWS_DEFAULT_REGION = ENV_AWS_DEFAULT_REGION -PARAM_AWS_SESSION_TOKEN = ENV_AWS_SESSION_TOKEN - -PARAM_AZURE_TENANT_ID = ENV_AZURE_TENANT_ID -PARAM_AZURE_SUBSCRIPTION_ID = ENV_AZURE_SUBSCRIPTION_ID -PARAM_AZURE_CLIENT_ID = ENV_AZURE_CLIENT_ID -PARAM_AZURE_CLIENT_SECRET = ENV_AZURE_CLIENT_SECRET - - # responses -ADAPTER_NOT_CONFIGURED_MESSAGE = \ - 'Custodian Service API link is not configured. ' \ - 'Run \'c7n configure\' and try again.' -MALFORMED_RESPONSE_MESSAGE = \ - 'Malformed response obtained. Please contact ' \ - 'support team for assistance.' NO_ITEMS_TO_DISPLAY_RESPONSE_MESSAGE = 'No items to display' NO_CONTENT_RESPONSE_MESSAGE = 'Request is successful. No content returned' # 204 -# codes -RESPONSE_NO_CONTENT = 204 - CONFIG_FOLDER = '.c7n' -CONTEXT_API_CLIENT = 'api_client' -CONTEXT_CONFIG = 'config' CONTEXT_MODULAR_ADMIN_USERNAME = 'modular_admin_username' - CONF_ACCESS_TOKEN = 'access_token' +CONF_REFRESH_TOKEN = 'refresh_token' CONF_API_LINK = 'api_link' CONF_ITEMS_PER_COLUMN = 'items_per_column' MODULE_NAME = 'c7n' # for modular admin + + +class JobState(str, Enum): + """ + https://docs.aws.amazon.com/batch/latest/userguide/job_states.html + """ + SUBMITTED = 'SUBMITTED' + PENDING = 'PENDING' + RUNNABLE = 'RUNNABLE' + STARTING = 'STARTING' + RUNNING = 'RUNNING' + FAILED = 'FAILED' + SUCCEEDED = 'SUCCEEDED' + + @classmethod + def iter(cls): + return map(operator.attrgetter('value'), cls) + + +class PolicyErrorType(str, Enum): + """ + For statistics + """ + SKIPPED = 'SKIPPED' + ACCESS = 'ACCESS' # not enough permissions + CREDENTIALS = 'CREDENTIALS' # invalid credentials + CLIENT = 'CLIENT' # some other client error + INTERNAL = 'INTERNAL' # unexpected error + + @classmethod + def iter(cls): + return map(operator.attrgetter('value'), cls) + + +class ModularCloud(str, Enum): + AZURE = 'AZURE' + YANDEX = 'YANDEX' + GOOGLE = 'GOOGLE' + AWS = 'AWS' + OPENSTACK = 'OPEN_STACK' + CSA = 'CSA' + HWU = 'HARDWARE' + ENTERPRISE = 'ENTERPRISE' + EXOSCALE = 'EXOSCALE' + WORKSPACE = 'WORKSPACE' + AOS = 'AOS' + VSPHERE = 'VSPHERE' + VMWARE = 'VMWARE' # VCloudDirector group + NUTANIX = 'NUTANIX' + + @classmethod + def iter(cls): + return map(operator.attrgetter('value'), cls) diff --git a/c7n/c7ncli/service/helpers.py b/c7n/c7ncli/service/helpers.py index a87c66ab9..8a475bcc4 100644 --- a/c7n/c7ncli/service/helpers.py +++ b/c7n/c7ncli/service/helpers.py @@ -1,14 +1,76 @@ +import base64 +import json import secrets import string -from copy import deepcopy +import time +import uuid +import urllib.error +import urllib.request from datetime import datetime, timezone -from re import compile -from typing import Callable, Dict, Union, List, Iterable -from typing import Optional -from uuid import uuid4 -import requests +from typing import Callable, TypeVar from dateutil.parser import isoparse +from urllib3.exceptions import LocationParseError +from urllib3.util import parse_url + + +def urljoin(*args: str) -> str: + """ + This method somehow differs from urllib.parse.urljoin. See: + >>> urljoin('one', 'two', 'three') + 'one/two/three' + >>> urljoin('one/', '/two/', '/three/') + 'one/two/three' + >>> urljoin('https://example.com/', '/prefix', 'path/to/service') + 'https://example.com/prefix/path/to/service' + :param args: list of string + :return: + """ + return '/'.join(map(lambda x: str(x).strip('/'), args)) + + +def sifted(data: dict) -> dict: + """ + >>> sifted({'k': 'value', 'k1': None, 'k2': '', 'k3': 0, 'k4': False}) + {'k': 'value', 'k3': 0, 'k4': False} + :param data: + :return: + """ + return {k: v for k, v in data.items() if isinstance(v, (bool, int)) or v} + + +class JWTToken: + """ + A simple wrapper over jwt token + """ + EXP_THRESHOLD = 300 # in seconds + __slots__ = '_token', '_exp_threshold' + + def __init__(self, token: str, exp_threshold: int = EXP_THRESHOLD): + self._token = token + self._exp_threshold = exp_threshold + + @property + def raw(self) -> str: + return self._token + + @property + def payload(self) -> dict | None: + try: + return json.loads( + base64.b64decode(self._token.split('.')[1] + '==').decode() + ) + except Exception: + return + + def is_expired(self) -> bool: + p = self.payload + if not p: + return True + exp = p.get('exp') + if not exp: + return False + return exp < time.time() + self._exp_threshold def gen_password(digits: int = 20) -> str: @@ -23,72 +85,7 @@ def gen_password(digits: int = 20) -> str: return password -def cast_to_list(input): - if type(input) == tuple: - list_item = list(input) - elif type(input) == str: - list_item = [input] - else: - list_item = input - return list_item - - -def cast_to_dict(payload: str, null_key: str = None, deep: bool = False) -> \ - Dict[str, Union[Dict, List[str]]]: - pattern, delimiter = compile(r'([^:]*):([^\s]*)(?=,|$)'), ',' - casted: dict = dict() - for match in pattern.finditer(payload): - key, value = match.groups() - key = key or null_key - if deep and value: - value = cast_to_dict(payload=value, deep=deep) or value - value = value.split(delimiter) if isinstance(value, str) else value - - value = [*filter(bool, value)] if isinstance(value, list) else value - - default = dict() if deep else list() - reference: Union[List, Dict] = casted.setdefault(key, default) - - if isinstance(value, dict): - reference.update(value) - elif not deep and value: - reference.extend(value) - - return casted - - -def merge_dict(resolver: Callable, default: Callable, *subjects): - merged = dict() - for each in subjects: - copied: dict = deepcopy(each) - for key, value in copied.items(): - installed = merged.setdefault(key, default()) - conflict = isinstance(value, dict) & isinstance(installed, dict) - - if conflict and installed is not value: - merged[key] = merge_dict(installed, value) - else: - resolver(installed, value) - - return merged - - -def invert_dict( - subject: dict, default: Callable = None, resolver: Callable = None -): - _output = {} - for key, value in subject.items(): - values = value if isinstance(value, Iterable) else (value,) - for each in values: - if default and resolver: - store = _output.setdefault(each, default()) - resolver(store, key) - else: - _output[each] = key - return _output - - -def utc_datetime(_from: Optional[str] = None) -> datetime: +def utc_datetime(_from: str | None = None) -> datetime: """ Returns time-zone aware datetime object in UTC. You can optionally pass an existing ISO string. The function will parse it to object and make @@ -100,7 +97,7 @@ def utc_datetime(_from: Optional[str] = None) -> datetime: return obj.astimezone(timezone.utc) -def utc_iso(_from: Optional[datetime] = None) -> str: +def utc_iso(_from: datetime | None = None) -> str: """ Returns time-zone aware datetime ISO string in UTC with military suffix. You can optionally pass datetime object. The function will make it @@ -125,7 +122,7 @@ def build_cloudtrail_record(cloud_identifier: str, region: str, } -def normalize_lists(lists: List[List[str]]): +def normalize_lists(lists: list[list[str]]): """ Changes the given lists in place making them all equal length by repeating the last attr the necessary number of times. @@ -165,7 +162,7 @@ def build_eventbridge_record(detail_type: str, source: str, account: str, region: str, detail: dict) -> dict: return { "version": "0", - "id": str(uuid4()), + "id": str(uuid.uuid4()), "detail-type": detail_type, "source": source, "account": account, @@ -188,13 +185,14 @@ def build_maestro_record(event_action: str, group: str, sub_group: str, :return: """ return { - "_id": str(uuid4()), + "_id": str(uuid.uuid4()), "eventAction": event_action, "group": group, "subGroup": sub_group, "tenantName": tenant_name, "eventMetadata": { - "request": {'cloud': cloud}, # todo native maestro events have string here + "request": {'cloud': cloud}, + # todo native maestro events have string here "cloud": cloud } } @@ -256,16 +254,42 @@ def _(cls, st: str) -> str: return f'{cls._current}{st}{cls.ENDC}' -def validate_api_link(api_link: str) -> Optional[str]: - message = None +def validate_api_link(url: str) -> str | None: + url = url.lstrip() + + if "://" in url and not url.lower().startswith("http"): + return 'Invalid API link: not supported scheme' + try: + scheme, auth, host, port, path, query, fragment = parse_url(url) + except LocationParseError as e: + return 'Invalid API link' + if not scheme: + return 'Invalid API link: missing scheme' + if not host: + return 'Invalid API link: missing host' + try: + req = urllib.request.Request(url) + urllib.request.urlopen(req) + except urllib.error.HTTPError as e: + pass + except urllib.error.URLError as e: + return 'Invalid API link: cannot make a request' + + +RT = TypeVar('RT') # return type +ET = TypeVar('ET', bound=Exception) # exception type + + +def catch(func: Callable[[], RT], exception: type[ET] = Exception + ) -> tuple[RT | None, ET | None]: + """ + Calls the provided function and catches the desired exception. + Seems useful to me :) ? + :param func: + :param exception: + :return: + """ try: - requests.get(api_link) - except (requests.exceptions.MissingSchema, - requests.exceptions.ConnectionError): - message = f'Invalid API link: {api_link}' - except requests.exceptions.InvalidURL: - message = f'Invalid URL \'{api_link}\': No host specified.' - except requests.exceptions.InvalidSchema: - message = f'Invalid URL \'{api_link}\': No network protocol specified ' \ - f'(http/https).' - return message + return func(), None + except exception as e: + return None, e diff --git a/c7n/c7ncli/service/logger.py b/c7n/c7ncli/service/logger.py index 9b2a73b97..681ec89cf 100644 --- a/c7n/c7ncli/service/logger.py +++ b/c7n/c7ncli/service/logger.py @@ -1,19 +1,23 @@ -import os -import re -import traceback from datetime import date from getpass import getuser -from logging import DEBUG, getLogger, Formatter, StreamHandler, INFO, \ - FileHandler, NullHandler +from logging import ( + DEBUG, + FileHandler, + Formatter, + INFO, + NullHandler, + StreamHandler, + getLogger, +) +import os from pathlib import Path +import re from c7ncli.service.constants import C7NCLI_LOG_LEVEL_ENV_NAME from c7ncli.version import __version__ -LOGS_FOLDER = 'c7ncli-logs' +LOGS_FOLDER = Path('logs/c7ncli') LOGS_FILE_NAME = date.today().strftime('%Y-%m-%d-c7n.log') -# LOGS_FOLDER_PATH = Path(__file__).parent.parent.parent / LOGS_FOLDER -LOGS_FOLDER_PATH = Path(os.getcwd(), LOGS_FOLDER) SYSTEM_LOG_FORMAT = f'%(asctime)s [USER: {getuser()}] %(message)s' @@ -23,26 +27,28 @@ class SensitiveFormatter(Formatter): - """Formatter that removes sensitive information.""" - SECURED_PARAMS = { + """ + Formatter that removes sensitive information. + """ + _inner = '|'.join(( 'refresh_token', 'id_token', 'password', 'authorization', 'secret', 'AWS_SECRET_ACCESS_KEY', 'AWS_SESSION_TOKEN', 'git_access_secret', 'api_key', 'AZURE_CLIENT_ID', 'AZURE_CLIENT_SECRET', 'GOOGLE_APPLICATION_CREDENTIALS', 'private_key', 'private_key_id', - 'Authorization', 'Authentication', 'sdk_secret_key', 'key_id' - } - - @staticmethod - def _filter(string): - for param in SensitiveFormatter.SECURED_PARAMS: - # [\'"] - single or double quote; [ ]? - zero or more spaces - string = re.sub(f'[\'"]{param}[\'"]:[ ]*[\'"](.*?)[\'"]', - f'\'{param}\': \'****\'', string) - return string + 'Authorization', 'Authentication', 'sdk_secret_key', 'key_id', + 'certificate', 'access_token', 'refresh_token' + )) + # assuming that only raw python dicts will be written. This regex won't + # catch exposed secured params inside JSON strings. In looks only for + # single quotes + regex = re.compile(rf"'({_inner})':\s*?'(.*?)'") def format(self, record): - original = Formatter.format(self, record) - return self._filter(original) + return re.sub( + self.regex, + r"'\1': '****'", + super().format(record) + ) # SYSTEM logger @@ -81,14 +87,9 @@ def get_user_logger(log_name, level=INFO): return module_logger -def exception_handler_formatter(exception_type, exception, exc_traceback): - c7n_logger.error('%s: %s', exception_type.__name__, exception) - traceback.print_tb(tb=exc_traceback, limit=15) - - def write_verbose_logs(): - os.makedirs(LOGS_FOLDER_PATH, exist_ok=True) - file_handler = FileHandler(LOGS_FOLDER_PATH / LOGS_FILE_NAME) + os.makedirs(LOGS_FOLDER, exist_ok=True) + file_handler = FileHandler(LOGS_FOLDER / LOGS_FILE_NAME) file_handler.setLevel(DEBUG) formatter = SensitiveFormatter(VERBOSE_MODE_LOG_FORMAT) file_handler.setFormatter(formatter) diff --git a/c7n/c7ncli/version.py b/c7n/c7ncli/version.py index 9ec2ec4ae..1e9869ffb 100644 --- a/c7n/c7ncli/version.py +++ b/c7n/c7ncli/version.py @@ -1,9 +1,9 @@ -__version__ = '4.15.0' +__version__ = '5.1.0' import sys from distutils.version import LooseVersion - +# todo rewrite def check_version_compatibility(api_version): if not api_version: print('Custodian API did not return the version number!') diff --git a/c7n/docs/README.md b/c7n/docs/README.md deleted file mode 100644 index dd2032d47..000000000 --- a/c7n/docs/README.md +++ /dev/null @@ -1,87 +0,0 @@ -# Custodian as a service CLI (4.1.4) documentation - - - -## c7n - -### Description - -Custodian as a service cli s a simple thin layer between you and Custodian as a service API. It provides command line interface to the API. - - -### Installation - -**Standalone:** - -```bash -pip install ./c7n -``` - -**m3-modular-admin:** - -```bash -m3modular install --module_path ./c7n -``` - -Check whether CLI is installed: - -```bash -$ c7n --version -c7n, version 4.1.4 -``` - - -### Synopsis - -```bash -c7n [parameters] -``` - -You can use `c7n --help` to get information about specific command or group. - - -### Global Options - -`--json` (flag) - -Returns the output of API in json format directly to console - instead of table view which is by default. - -`--verbose` (flag) - -Writes verbose cli logs to `./c7ncli-logs` folder and prints trace id from AWS to console above the table. - -`--help` (flag) - -Outputs the information about command or group. - -`--customer_id` (string) - -Performs the action on behalf of the specified customer. Can be used only if you are a SYSTEM user - -### Available command - -- [application](./application/index.md) -- [cleanup](./cleanup.md) -- [configure](./configure.md) -- [customer](./customer/index.md) -- [health_check](./health_check.md) -- [job](./job/index.md) -- [lm](./lm/index.md) -- [login](./login.md) -- [parent](./parent/index.md) -- [policy](./policy/index.md) -- [report](./report/index.md) -- [results](./results/index.md) -- [role](./role/index.md) -- [rule](./rule/index.md) -- [ruleset](./ruleset/index.md) -- [rulesource](./rulesource/index.md) -- [setting](./setting/index.md) -- [show_config](./show_config.md) -- [siem](./siem/index.md) -- [tenant](./tenant/index.md) -- [trigger](./trigger/index.md) -- [user](./user/index.md) - - -_In case you have any questions, contact the support team_ \ No newline at end of file diff --git a/c7n/docs/application/add.md b/c7n/docs/application/add.md deleted file mode 100644 index e83dead6a..000000000 --- a/c7n/docs/application/add.md +++ /dev/null @@ -1,46 +0,0 @@ -# add - -## Description - -Creates Custodian Application with specified license - -## Synopsis - -```bash -c7n application add - [--cloud ] - [--cloud_application_id ] - [--description ] - [--password ] - [--tenant_license_key ] - [--username ] -``` - -## Options - -`--cloud` (AWS, AZURE, GOOGLE) - -Cloud to activate the application for - -`--cloud_application_id` (text) - -Application id containing creds to access the cloud - -`--description` (text) - -Application description - -`--password` (text) - -Password to set to application - -`--tenant_license_key` (text) - -Tenant license key with license for the specified cloud - -`--username` (text) - -Username to set to the application - - -[← application](./index.md) \ No newline at end of file diff --git a/c7n/docs/application/delete.md b/c7n/docs/application/delete.md deleted file mode 100644 index 56d73527f..000000000 --- a/c7n/docs/application/delete.md +++ /dev/null @@ -1,21 +0,0 @@ -# delete - -## Description - -Delete Custodian Application - -## Synopsis - -```bash -c7n application delete - --application_id -``` - -## Options - -`--application_id` (text) - -Id of the application - - -[← application](./index.md) \ No newline at end of file diff --git a/c7n/docs/application/describe.md b/c7n/docs/application/describe.md deleted file mode 100644 index 57c887cb9..000000000 --- a/c7n/docs/application/describe.md +++ /dev/null @@ -1,18 +0,0 @@ -# describe - -## Description - -Describe Custodian Application - -## Synopsis - -```bash -c7n application describe -``` - -## Options - - - - -[← application](./index.md) \ No newline at end of file diff --git a/c7n/docs/application/index.md b/c7n/docs/application/index.md deleted file mode 100644 index 68e9648af..000000000 --- a/c7n/docs/application/index.md +++ /dev/null @@ -1,15 +0,0 @@ -# application - -## Description - -Manages Applications Entity - -## Available commands - -- [add](./add.md) -- [delete](./delete.md) -- [describe](./describe.md) -- [update](./update.md) - - -[← c7n](../README.md) \ No newline at end of file diff --git a/c7n/docs/application/update.md b/c7n/docs/application/update.md deleted file mode 100644 index 0c6341255..000000000 --- a/c7n/docs/application/update.md +++ /dev/null @@ -1,51 +0,0 @@ -# update - -## Description - -Update Custodian Application - -## Synopsis - -```bash -c7n application update - --application_id - [--cloud ] - [--cloud_application_id ] - [--description ] - [--password ] - [--tenant_license_key ] - [--username ] -``` - -## Options - -`--application_id` (text) - -Id of the application - -`--cloud` (AWS, AZURE, GOOGLE) - -Cloud to activate the application for - -`--cloud_application_id` (text) - -Application id containing creds to access the cloud - -`--description` (text) - -Application description - -`--password` (text) - -Password to set to application - -`--tenant_license_key` (text) - -Tenant license key with license for the specified cloud - -`--username` (text) - -Username to set to the application - - -[← application](./index.md) \ No newline at end of file diff --git a/c7n/docs/cleanup.md b/c7n/docs/cleanup.md deleted file mode 100644 index 5dd47cfe0..000000000 --- a/c7n/docs/cleanup.md +++ /dev/null @@ -1,18 +0,0 @@ -# cleanup - -## Description - -Removes all the configuration data related to the tool. - -## Synopsis - -```bash -c7n cleanup -``` - -## Options - - - - -[← c7n](./README.md) \ No newline at end of file diff --git a/c7n/docs/configure.md b/c7n/docs/configure.md deleted file mode 100644 index e43660376..000000000 --- a/c7n/docs/configure.md +++ /dev/null @@ -1,26 +0,0 @@ -# configure - -## Description - -Configures c7n tool to work with Custodian as a Service. - -## Synopsis - -```bash -c7n configure - [--api_link ] - [--items_per_column ] -``` - -## Options - -`--api_link` (text) - -Link to the Custodian as a Service host. - -`--items_per_column` (integer range) - -Specify how many items per table. Set `0` to disable the limitation - - -[← c7n](./README.md) \ No newline at end of file diff --git a/c7n/docs/customer/describe.md b/c7n/docs/customer/describe.md deleted file mode 100644 index 8c427691d..000000000 --- a/c7n/docs/customer/describe.md +++ /dev/null @@ -1,21 +0,0 @@ -# describe - -## Description - -Describes your user's customer - -## Synopsis - -```bash -c7n customer describe - [--full ] -``` - -## Options - -`--full` (boolean) - -Show full command output - - -[← customer](./index.md) \ No newline at end of file diff --git a/c7n/docs/customer/index.md b/c7n/docs/customer/index.md deleted file mode 100644 index 4e785d98c..000000000 --- a/c7n/docs/customer/index.md +++ /dev/null @@ -1,12 +0,0 @@ -# customer - -## Description - -Manages Custodian Service Customer Entities - -## Available commands - -- [describe](./describe.md) - - -[← c7n](../README.md) \ No newline at end of file diff --git a/c7n/docs/health_check.md b/c7n/docs/health_check.md deleted file mode 100644 index 037b9ce74..000000000 --- a/c7n/docs/health_check.md +++ /dev/null @@ -1,26 +0,0 @@ -# health_check - -## Description - -Checks Custodian Service components availability - -## Synopsis - -```bash -c7n health_check - [--identifier ] - [--status ] -``` - -## Options - -`--identifier` (text) - -Concrete check id to retrieve - -`--status` (OK, UNKNOWN, NOT_OK) - -Filter checks by status - - -[← c7n](./README.md) \ No newline at end of file diff --git a/c7n/docs/job/describe.md b/c7n/docs/job/describe.md deleted file mode 100644 index c9559fe1e..000000000 --- a/c7n/docs/job/describe.md +++ /dev/null @@ -1,36 +0,0 @@ -# describe - -## Description - -Describes Custodian Service Scans - -## Synopsis - -```bash -c7n job describe - [--job_id ] - [--limit ] - [--next_token ] - [--tenant_name ] -``` - -## Options - -`--job_id` (text) - -Job id to describe - -`--limit` (integer range) [default: 10] - -Number of records to show - -`--next_token` (text) - -Token to start record-pagination from - -`--tenant_name` (text) - -Name of related tenant - - -[← job](./index.md) \ No newline at end of file diff --git a/c7n/docs/job/index.md b/c7n/docs/job/index.md deleted file mode 100644 index b7010a84e..000000000 --- a/c7n/docs/job/index.md +++ /dev/null @@ -1,15 +0,0 @@ -# job - -## Description - -Manages Custodian Service jobs - -## Available commands - -- [describe](./describe.md) -- [scheduled](./scheduled/index.md) -- [submit](./submit.md) -- [terminate](./terminate.md) - - -[← c7n](../README.md) \ No newline at end of file diff --git a/c7n/docs/job/scheduled/add.md b/c7n/docs/job/scheduled/add.md deleted file mode 100644 index 13a6163f8..000000000 --- a/c7n/docs/job/scheduled/add.md +++ /dev/null @@ -1,41 +0,0 @@ -# add - -## Description - -Registers a scheduled job - -## Synopsis - -```bash -c7n job scheduled add - --schedule - [--name ] - [--region ] - [--ruleset ] - [--tenant_name ] -``` - -## Options - -`--schedule` (text) - -Cron or Rate expression: cron(0 20 * * *), rate(2 minutes) - -`--name` (text) - -Name for the scheduled job. Must be unique. If not given, will be generated automatically - -`--region` (text) - -Regions to scan. If not specified, all active regions will be used - -`--ruleset` (text) - -Rulesets to scan. If not specified, all available rulesets will be used - -`--tenant_name` (text) - -Name of related tenant - - -[← scheduled](./index.md) \ No newline at end of file diff --git a/c7n/docs/job/scheduled/delete.md b/c7n/docs/job/scheduled/delete.md deleted file mode 100644 index f4ad67ee1..000000000 --- a/c7n/docs/job/scheduled/delete.md +++ /dev/null @@ -1,21 +0,0 @@ -# delete - -## Description - -Removes a scheduled job - -## Synopsis - -```bash -c7n job scheduled delete - --name -``` - -## Options - -`--name` (text) - -Scheduled job name to remove - - -[← scheduled](./index.md) \ No newline at end of file diff --git a/c7n/docs/job/scheduled/describe.md b/c7n/docs/job/scheduled/describe.md deleted file mode 100644 index 0702895a9..000000000 --- a/c7n/docs/job/scheduled/describe.md +++ /dev/null @@ -1,26 +0,0 @@ -# describe - -## Description - -Describes registered scheduled jobs - -## Synopsis - -```bash -c7n job scheduled describe - [--name ] - [--tenant_name ] -``` - -## Options - -`--name` (text) - -Scheduled job name to remove - -`--tenant_name` (text) - -Name of related tenant - - -[← scheduled](./index.md) \ No newline at end of file diff --git a/c7n/docs/job/scheduled/index.md b/c7n/docs/job/scheduled/index.md deleted file mode 100644 index 07e4f23f4..000000000 --- a/c7n/docs/job/scheduled/index.md +++ /dev/null @@ -1,15 +0,0 @@ -# scheduled - -## Description - -Manages Job submit action - -## Available commands - -- [add](./add.md) -- [delete](./delete.md) -- [describe](./describe.md) -- [update](./update.md) - - -[← job](../index.md) \ No newline at end of file diff --git a/c7n/docs/job/scheduled/update.md b/c7n/docs/job/scheduled/update.md deleted file mode 100644 index de577b0c8..000000000 --- a/c7n/docs/job/scheduled/update.md +++ /dev/null @@ -1,31 +0,0 @@ -# update - -## Description - -Updates an existing scheduled job - -## Synopsis - -```bash -c7n job scheduled update - --name - [--enabled ] - [--schedule ] -``` - -## Options - -`--name` (text) - -Scheduled job name to update - -`--enabled` (boolean) - -Param to enable or disable the job temporarily - -`--schedule` (text) - -Cron or Rate expression: cron(0 20 * * *), rate(2 minutes) - - -[← scheduled](./index.md) \ No newline at end of file diff --git a/c7n/docs/job/submit.md b/c7n/docs/job/submit.md deleted file mode 100644 index 3af566ab5..000000000 --- a/c7n/docs/job/submit.md +++ /dev/null @@ -1,46 +0,0 @@ -# submit - -## Description - -Submits a job to scan an infrastructure - -## Synopsis - -```bash -c7n job submit - [--cloud ] - [--credentials_from_env ] - [--not_check_permission ] - [--region ] - [--ruleset ] - [--tenant_name ] -``` - -## Options - -`--cloud` (AWS, AZURE, GOOGLE) - -Cloud to scan. Required, if `--credentials_from_env` flag is set. - -`--credentials_from_env` (boolean) - -Specify to get credentials for scan from environment variables. Requires `--cloud` to be set. - -`--not_check_permission` (boolean) - -Force the server not to check execution permissions. Job that is not permitted but has started will eventually fail - -`--region` (text) - -Regions to scan. If not specified, all active regions will be used - -`--ruleset` (text) - -Rulesets to scan. If not specified, all available by license rulesets will be used - -`--tenant_name` (text) - -Name of related tenant - - -[← job](./index.md) \ No newline at end of file diff --git a/c7n/docs/job/terminate.md b/c7n/docs/job/terminate.md deleted file mode 100644 index 71f578e45..000000000 --- a/c7n/docs/job/terminate.md +++ /dev/null @@ -1,21 +0,0 @@ -# terminate - -## Description - -Terminates Custodian Service Scan - -## Synopsis - -```bash -c7n job terminate - --job_id -``` - -## Options - -`--job_id` (text) - -Job id to terminate - - -[← job](./index.md) \ No newline at end of file diff --git a/c7n/docs/lm/delete.md b/c7n/docs/lm/delete.md deleted file mode 100644 index 1388564f1..000000000 --- a/c7n/docs/lm/delete.md +++ /dev/null @@ -1,21 +0,0 @@ -# delete - -## Description - -Deletes Custodian Service Licenses - -## Synopsis - -```bash -c7n lm delete - --license_key -``` - -## Options - -`--license_key` (text) - -License key to delete - - -[← lm](./index.md) \ No newline at end of file diff --git a/c7n/docs/lm/describe.md b/c7n/docs/lm/describe.md deleted file mode 100644 index 19c27196f..000000000 --- a/c7n/docs/lm/describe.md +++ /dev/null @@ -1,21 +0,0 @@ -# describe - -## Description - -Describes a Custodian Service Licenses - -## Synopsis - -```bash -c7n lm describe - [--license_key ] -``` - -## Options - -`--license_key` (text) - -License key to describe - - -[← lm](./index.md) \ No newline at end of file diff --git a/c7n/docs/lm/index.md b/c7n/docs/lm/index.md deleted file mode 100644 index 74593faba..000000000 --- a/c7n/docs/lm/index.md +++ /dev/null @@ -1,14 +0,0 @@ -# lm - -## Description - -Manages Custodian Service License Entities - -## Available commands - -- [delete](./delete.md) -- [describe](./describe.md) -- [sync](./sync.md) - - -[← c7n](../README.md) \ No newline at end of file diff --git a/c7n/docs/lm/sync.md b/c7n/docs/lm/sync.md deleted file mode 100644 index ea5d2b1ef..000000000 --- a/c7n/docs/lm/sync.md +++ /dev/null @@ -1,21 +0,0 @@ -# sync - -## Description - -Synchronizes Custodian Service Licenses - -## Synopsis - -```bash -c7n lm sync - --license_key -``` - -## Options - -`--license_key` (text) - -License key to synchronize - - -[← lm](./index.md) \ No newline at end of file diff --git a/c7n/docs/login.md b/c7n/docs/login.md deleted file mode 100644 index 6f106cd21..000000000 --- a/c7n/docs/login.md +++ /dev/null @@ -1,26 +0,0 @@ -# login - -## Description - -Authenticates user to work with Custodian as a Service. - -## Synopsis - -```bash -c7n login - --password - --username -``` - -## Options - -`--password` (text) - -Custodian Service user password. - -`--username` (text) - -Custodian Service username. - - -[← c7n](./README.md) \ No newline at end of file diff --git a/c7n/docs/parent/add.md b/c7n/docs/parent/add.md deleted file mode 100644 index efcda382e..000000000 --- a/c7n/docs/parent/add.md +++ /dev/null @@ -1,41 +0,0 @@ -# add - -## Description - -Creates a CUSTODIAN parent of a customer - -## Synopsis - -```bash -c7n parent add - --cloud - --scope - [--application_id ] - [--description ] - [--rules_to_exclude ] -``` - -## Options - -`--cloud` (AWS, AZURE, GOOGLE) - -Cloud to connect the parent to - -`--scope` (ALL, SPECIFIC_TENANT) - -Tenants scope for the parent - -`--application_id` (text) - -Id of an application with type CUSTODIAN within your customer. To connect a parent to - -`--description` (text) - -Description for the parent - -`--rules_to_exclude` (text) - -Rules to exclude for the scope of tenants - - -[← parent](./index.md) \ No newline at end of file diff --git a/c7n/docs/parent/delete.md b/c7n/docs/parent/delete.md deleted file mode 100644 index 017450d68..000000000 --- a/c7n/docs/parent/delete.md +++ /dev/null @@ -1,21 +0,0 @@ -# delete - -## Description - -Deletes customer's CUSTODIAN parent - -## Synopsis - -```bash -c7n parent delete - --parent_id -``` - -## Options - -`--parent_id` (text) - -Parent id to describe a concrete parent - - -[← parent](./index.md) \ No newline at end of file diff --git a/c7n/docs/parent/describe.md b/c7n/docs/parent/describe.md deleted file mode 100644 index 245bf96d0..000000000 --- a/c7n/docs/parent/describe.md +++ /dev/null @@ -1,21 +0,0 @@ -# describe - -## Description - -Describes customer's CUSTODIAN parent - -## Synopsis - -```bash -c7n parent describe - [--parent_id ] -``` - -## Options - -`--parent_id` (text) - -Parent id to describe a concrete parent - - -[← parent](./index.md) \ No newline at end of file diff --git a/c7n/docs/parent/index.md b/c7n/docs/parent/index.md deleted file mode 100644 index 216f2c8c6..000000000 --- a/c7n/docs/parent/index.md +++ /dev/null @@ -1,17 +0,0 @@ -# parent - -## Description - -Manages Custodian Service Parent Entities - -## Available commands - -- [add](./add.md) -- [delete](./delete.md) -- [describe](./describe.md) -- [link_tenant](./link_tenant.md) -- [unlink_tenant](./unlink_tenant.md) -- [update](./update.md) - - -[← c7n](../README.md) \ No newline at end of file diff --git a/c7n/docs/parent/link_tenant.md b/c7n/docs/parent/link_tenant.md deleted file mode 100644 index e03bd5b9c..000000000 --- a/c7n/docs/parent/link_tenant.md +++ /dev/null @@ -1,26 +0,0 @@ -# link_tenant - -## Description - -Links Maestro tenant to CUSTODIAN parent - -## Synopsis - -```bash -c7n parent link_tenant - --parent_id - [--tenant_name ] -``` - -## Options - -`--parent_id` (text) - -Maestro Parent id to link to tenant. - -`--tenant_name` (text) - -Name of related tenant - - -[← parent](./index.md) \ No newline at end of file diff --git a/c7n/docs/parent/unlink_tenant.md b/c7n/docs/parent/unlink_tenant.md deleted file mode 100644 index e67dd86fc..000000000 --- a/c7n/docs/parent/unlink_tenant.md +++ /dev/null @@ -1,21 +0,0 @@ -# unlink_tenant - -## Description - -Unlinks CUSTODIAN parent from tenant - -## Synopsis - -```bash -c7n parent unlink_tenant - [--tenant_name ] -``` - -## Options - -`--tenant_name` (text) - -Name of related tenant - - -[← parent](./index.md) \ No newline at end of file diff --git a/c7n/docs/parent/update.md b/c7n/docs/parent/update.md deleted file mode 100644 index c781bd7ca..000000000 --- a/c7n/docs/parent/update.md +++ /dev/null @@ -1,46 +0,0 @@ -# update - -## Description - -Updates CUSTODIAN parent of a customer - -## Synopsis - -```bash -c7n parent update - --parent_id - [--cloud ] - [--description ] - [--rules_to_exclude ] - [--rules_to_include ] - [--scope ] -``` - -## Options - -`--parent_id` (text) - -Parent id to update - -`--cloud` (AWS, AZURE, GOOGLE) - -Cloud to connect the parent to - -`--description` (text) - -Description for the parent - -`--rules_to_exclude` (text) - -Rules to exclude for tenant - -`--rules_to_include` (text) - -Rules to include for tenant - -`--scope` (ALL, SPECIFIC_TENANT) - -Tenants scope for the parent - - -[← parent](./index.md) \ No newline at end of file diff --git a/c7n/docs/policy/add.md b/c7n/docs/policy/add.md deleted file mode 100644 index 1c793649c..000000000 --- a/c7n/docs/policy/add.md +++ /dev/null @@ -1,36 +0,0 @@ -# add - -## Description - -Creates a Custodian Service policy for a customer - -## Synopsis - -```bash -c7n policy add - --policy_name - [--admin_permissions ] - [--path_to_permissions ] - [--permission ] -``` - -## Options - -`--policy_name` (text) - -Policy name to create - -`--admin_permissions` (boolean) - -Adds all admin permissions - -`--path_to_permissions` (text) - -Local path to .json file that contains list of permissions to attach to the policy - -`--permission` (text) - -List of permissions to attach to the policy - - -[← policy](./index.md) \ No newline at end of file diff --git a/c7n/docs/policy/clean_cache.md b/c7n/docs/policy/clean_cache.md deleted file mode 100644 index 9ae0f75d7..000000000 --- a/c7n/docs/policy/clean_cache.md +++ /dev/null @@ -1,21 +0,0 @@ -# clean_cache - -## Description - -Clears out cached Custodian Service policies within Lambda - -## Synopsis - -```bash -c7n policy clean_cache - [--policy_name ] -``` - -## Options - -`--policy_name` (text) - -Policy name to clean from cache. If not specified, all policies cache within the customer is cleaned - - -[← policy](./index.md) \ No newline at end of file diff --git a/c7n/docs/policy/delete.md b/c7n/docs/policy/delete.md deleted file mode 100644 index de5faf6fa..000000000 --- a/c7n/docs/policy/delete.md +++ /dev/null @@ -1,21 +0,0 @@ -# delete - -## Description - -Deletes a Custodian Service policy of a customer - -## Synopsis - -```bash -c7n policy delete - --policy_name -``` - -## Options - -`--policy_name` (text) - -Policy name to delete - - -[← policy](./index.md) \ No newline at end of file diff --git a/c7n/docs/policy/describe.md b/c7n/docs/policy/describe.md deleted file mode 100644 index cf726984e..000000000 --- a/c7n/docs/policy/describe.md +++ /dev/null @@ -1,21 +0,0 @@ -# describe - -## Description - -Describes Custodian Service policies of a customer - -## Synopsis - -```bash -c7n policy describe - [--policy_name ] -``` - -## Options - -`--policy_name` (text) - -Policy name to describe. - - -[← policy](./index.md) \ No newline at end of file diff --git a/c7n/docs/policy/index.md b/c7n/docs/policy/index.md deleted file mode 100644 index 628450983..000000000 --- a/c7n/docs/policy/index.md +++ /dev/null @@ -1,16 +0,0 @@ -# policy - -## Description - -Manages Custodian Service Policy Entities - -## Available commands - -- [add](./add.md) -- [clean_cache](./clean_cache.md) -- [delete](./delete.md) -- [describe](./describe.md) -- [update](./update.md) - - -[← c7n](../README.md) \ No newline at end of file diff --git a/c7n/docs/policy/update.md b/c7n/docs/policy/update.md deleted file mode 100644 index 89b8928e5..000000000 --- a/c7n/docs/policy/update.md +++ /dev/null @@ -1,31 +0,0 @@ -# update - -## Description - -Updates permission-list within a Custodian Service policy - -## Synopsis - -```bash -c7n policy update - --policy_name - [--attach_permission ] - [--detach_permission ] -``` - -## Options - -`--policy_name` (text) - -None - -`--attach_permission` (text) - -Names of permissions to attach to the policy - -`--detach_permission` (text) - -Names of permissions to detach from the policy - - -[← policy](./index.md) \ No newline at end of file diff --git a/c7n/docs/report/clevel.md b/c7n/docs/report/clevel.md deleted file mode 100644 index 28a603b5c..000000000 --- a/c7n/docs/report/clevel.md +++ /dev/null @@ -1,18 +0,0 @@ -# clevel - -## Description - -Retrieves c-level reports - -## Synopsis - -```bash -c7n report clevel -``` - -## Options - - - - -[← report](./index.md) \ No newline at end of file diff --git a/c7n/docs/report/compliance/accumulated.md b/c7n/docs/report/compliance/accumulated.md deleted file mode 100644 index 00f92ded9..000000000 --- a/c7n/docs/report/compliance/accumulated.md +++ /dev/null @@ -1,26 +0,0 @@ -# accumulated - -## Description - -Describes tenant-specific compliance report - -## Synopsis - -```bash -c7n report compliance accumulated - --tenant_name - [--href ] -``` - -## Options - -`--tenant_name` (text) - -Name of related tenant - -`--href` (boolean) - -Return hypertext reference - - -[← compliance](./index.md) \ No newline at end of file diff --git a/c7n/docs/report/compliance/index.md b/c7n/docs/report/compliance/index.md deleted file mode 100644 index c83fa4d90..000000000 --- a/c7n/docs/report/compliance/index.md +++ /dev/null @@ -1,13 +0,0 @@ -# compliance - -## Description - -Describes compliance reports - -## Available commands - -- [accumulated](./accumulated.md) -- [jobs](./jobs.md) - - -[← report](../index.md) \ No newline at end of file diff --git a/c7n/docs/report/compliance/jobs.md b/c7n/docs/report/compliance/jobs.md deleted file mode 100644 index e5dfdca9e..000000000 --- a/c7n/docs/report/compliance/jobs.md +++ /dev/null @@ -1,31 +0,0 @@ -# jobs - -## Description - -Describes job compliance reports - -## Synopsis - -```bash -c7n report compliance jobs - --job_id - [--href ] - [--job_type ] -``` - -## Options - -`--job_id` (text) - -Unique job identifier - -`--href` (boolean) - -Return hypertext reference - -`--job_type` (manual, reactive) - -Specify type of jobs to retrieve. - - -[← compliance](./index.md) \ No newline at end of file diff --git a/c7n/docs/report/department.md b/c7n/docs/report/department.md deleted file mode 100644 index b6b64c145..000000000 --- a/c7n/docs/report/department.md +++ /dev/null @@ -1,18 +0,0 @@ -# department - -## Description - -Retrieves department-level reports - -## Synopsis - -```bash -c7n report department -``` - -## Options - - - - -[← report](./index.md) \ No newline at end of file diff --git a/c7n/docs/report/details/accumulated.md b/c7n/docs/report/details/accumulated.md deleted file mode 100644 index eaa448a66..000000000 --- a/c7n/docs/report/details/accumulated.md +++ /dev/null @@ -1,41 +0,0 @@ -# accumulated - -## Description - -Describes tenant-specific detailed reports, based on relevant jobs - -## Synopsis - -```bash -c7n report details accumulated - [--from_date ] - [--href ] - [--job_type ] - [--tenant_name ] - [--to_date ] -``` - -## Options - -`--from_date` (isoparse) - -Generate report FROM date. ISO 8601 format. Example: 2021-09-22T00:00:00.000000 - -`--href` (boolean) - -Return hypertext reference - -`--job_type` (manual, reactive) - -Specify type of jobs to retrieve. - -`--tenant_name` (text) - -Name of related tenant - -`--to_date` (isoparse) - -Generate report TILL date. ISO 8601 format. Example: 2021-09-22T00:00:00.000000 - - -[← details](./index.md) \ No newline at end of file diff --git a/c7n/docs/report/details/index.md b/c7n/docs/report/details/index.md deleted file mode 100644 index f5b7a5ba6..000000000 --- a/c7n/docs/report/details/index.md +++ /dev/null @@ -1,13 +0,0 @@ -# details - -## Description - -Describes detailed undigested reports - -## Available commands - -- [accumulated](./accumulated.md) -- [jobs](./jobs.md) - - -[← report](../index.md) \ No newline at end of file diff --git a/c7n/docs/report/details/jobs.md b/c7n/docs/report/details/jobs.md deleted file mode 100644 index ba47a5a5d..000000000 --- a/c7n/docs/report/details/jobs.md +++ /dev/null @@ -1,46 +0,0 @@ -# jobs - -## Description - -Describes detailed reports of jobs - -## Synopsis - -```bash -c7n report details jobs - [--from_date ] - [--href ] - [--job_id ] - [--job_type ] - [--tenant_name ] - [--to_date ] -``` - -## Options - -`--from_date` (isoparse) - -Generate report FROM date. ISO 8601 format. Example: 2021-09-22T00:00:00.000000 - -`--href` (boolean) - -Return hypertext reference - -`--job_id` (text) - -Unique job identifier - -`--job_type` (manual, reactive) - -Specify type of jobs to retrieve. - -`--tenant_name` (text) - -Name of related tenant - -`--to_date` (isoparse) - -Generate report TILL date. ISO 8601 format. Example: 2021-09-22T00:00:00.000000 - - -[← details](./index.md) \ No newline at end of file diff --git a/c7n/docs/report/digests/accumulated.md b/c7n/docs/report/digests/accumulated.md deleted file mode 100644 index d1e961360..000000000 --- a/c7n/docs/report/digests/accumulated.md +++ /dev/null @@ -1,41 +0,0 @@ -# accumulated - -## Description - -Describes tenant-specific summary reports, based on relevant jobs - -## Synopsis - -```bash -c7n report digests accumulated - [--from_date ] - [--href ] - [--job_type ] - [--tenant_name ] - [--to_date ] -``` - -## Options - -`--from_date` (isoparse) - -Generate report FROM date. ISO 8601 format. Example: 2021-09-22T00:00:00.000000 - -`--href` (boolean) - -Return hypertext reference - -`--job_type` (manual, reactive) - -Specify type of jobs to retrieve. - -`--tenant_name` (text) - -Name of related tenant - -`--to_date` (isoparse) - -Generate report TILL date. ISO 8601 format. Example: 2021-09-22T00:00:00.000000 - - -[← digests](./index.md) \ No newline at end of file diff --git a/c7n/docs/report/digests/index.md b/c7n/docs/report/digests/index.md deleted file mode 100644 index 95e0e3bac..000000000 --- a/c7n/docs/report/digests/index.md +++ /dev/null @@ -1,13 +0,0 @@ -# digests - -## Description - -Describes summaries of job reports - -## Available commands - -- [accumulated](./accumulated.md) -- [jobs](./jobs.md) - - -[← report](../index.md) \ No newline at end of file diff --git a/c7n/docs/report/digests/jobs.md b/c7n/docs/report/digests/jobs.md deleted file mode 100644 index eee165696..000000000 --- a/c7n/docs/report/digests/jobs.md +++ /dev/null @@ -1,46 +0,0 @@ -# jobs - -## Description - -Describes summary reports of jobs - -## Synopsis - -```bash -c7n report digests jobs - [--from_date ] - [--href ] - [--job_id ] - [--job_type ] - [--tenant_name ] - [--to_date ] -``` - -## Options - -`--from_date` (isoparse) - -Generate report FROM date. ISO 8601 format. Example: 2021-09-22T00:00:00.000000 - -`--href` (boolean) - -Return hypertext reference - -`--job_id` (text) - -Unique job identifier - -`--job_type` (manual, reactive) - -Specify type of jobs to retrieve. - -`--tenant_name` (text) - -Name of related tenant - -`--to_date` (isoparse) - -Generate report TILL date. ISO 8601 format. Example: 2021-09-22T00:00:00.000000 - - -[← digests](./index.md) \ No newline at end of file diff --git a/c7n/docs/report/errors/accumulated/access.md b/c7n/docs/report/errors/accumulated/access.md deleted file mode 100644 index a04a72b66..000000000 --- a/c7n/docs/report/errors/accumulated/access.md +++ /dev/null @@ -1,46 +0,0 @@ -# access - -## Description - -Describes access-related job error reports - -## Synopsis - -```bash -c7n report errors accumulated access - [--format ] - [--from_date ] - [--href ] - [--job_type ] - [--tenant_name ] - [--to_date ] -``` - -## Options - -`--format` (json, xlsx) - -Format of the file within the hypertext reference - -`--from_date` (isoparse) - -Generate report FROM date. ISO 8601 format. Example: 2021-09-22T00:00:00.000000 - -`--href` (boolean) - -Return hypertext reference - -`--job_type` (manual, reactive) - -Specify type of jobs to retrieve. - -`--tenant_name` (text) - -Name of related tenant - -`--to_date` (isoparse) - -Generate report TILL date. ISO 8601 format. Example: 2021-09-22T00:00:00.000000 - - -[← accumulated](./index.md) \ No newline at end of file diff --git a/c7n/docs/report/errors/accumulated/core.md b/c7n/docs/report/errors/accumulated/core.md deleted file mode 100644 index c9fecf068..000000000 --- a/c7n/docs/report/errors/accumulated/core.md +++ /dev/null @@ -1,46 +0,0 @@ -# core - -## Description - -Describes core-related error reports - -## Synopsis - -```bash -c7n report errors accumulated core - [--format ] - [--from_date ] - [--href ] - [--job_type ] - [--tenant_name ] - [--to_date ] -``` - -## Options - -`--format` (json, xlsx) - -Format of the file within the hypertext reference - -`--from_date` (isoparse) - -Generate report FROM date. ISO 8601 format. Example: 2021-09-22T00:00:00.000000 - -`--href` (boolean) - -Return hypertext reference - -`--job_type` (manual, reactive) - -Specify type of jobs to retrieve. - -`--tenant_name` (text) - -Name of related tenant - -`--to_date` (isoparse) - -Generate report TILL date. ISO 8601 format. Example: 2021-09-22T00:00:00.000000 - - -[← accumulated](./index.md) \ No newline at end of file diff --git a/c7n/docs/report/errors/accumulated/index.md b/c7n/docs/report/errors/accumulated/index.md deleted file mode 100644 index 9bc7d70f4..000000000 --- a/c7n/docs/report/errors/accumulated/index.md +++ /dev/null @@ -1,14 +0,0 @@ -# accumulated - -## Description - -Describes tenant-specific error reports, based on relevant jobs - -## Available commands - -- [access](./access.md) -- [core](./core.md) -- [total](./total.md) - - -[← errors](../index.md) \ No newline at end of file diff --git a/c7n/docs/report/errors/accumulated/total.md b/c7n/docs/report/errors/accumulated/total.md deleted file mode 100644 index 872e83931..000000000 --- a/c7n/docs/report/errors/accumulated/total.md +++ /dev/null @@ -1,46 +0,0 @@ -# total - -## Description - -Describes job error reports - -## Synopsis - -```bash -c7n report errors accumulated total - [--format ] - [--from_date ] - [--href ] - [--job_type ] - [--tenant_name ] - [--to_date ] -``` - -## Options - -`--format` (json, xlsx) - -Format of the file within the hypertext reference - -`--from_date` (isoparse) - -Generate report FROM date. ISO 8601 format. Example: 2021-09-22T00:00:00.000000 - -`--href` (boolean) - -Return hypertext reference - -`--job_type` (manual, reactive) - -Specify type of jobs to retrieve. - -`--tenant_name` (text) - -Name of related tenant - -`--to_date` (isoparse) - -Generate report TILL date. ISO 8601 format. Example: 2021-09-22T00:00:00.000000 - - -[← accumulated](./index.md) \ No newline at end of file diff --git a/c7n/docs/report/errors/index.md b/c7n/docs/report/errors/index.md deleted file mode 100644 index 786aa94ce..000000000 --- a/c7n/docs/report/errors/index.md +++ /dev/null @@ -1,13 +0,0 @@ -# errors - -## Description - -Describes error reports - -## Available commands - -- [accumulated](./accumulated/index.md) -- [jobs](./jobs/index.md) - - -[← report](../index.md) \ No newline at end of file diff --git a/c7n/docs/report/errors/jobs/access.md b/c7n/docs/report/errors/jobs/access.md deleted file mode 100644 index b267ad13b..000000000 --- a/c7n/docs/report/errors/jobs/access.md +++ /dev/null @@ -1,36 +0,0 @@ -# access - -## Description - -Describes access-related job error reports - -## Synopsis - -```bash -c7n report errors jobs access - --job_id - [--format ] - [--href ] - [--job_type ] -``` - -## Options - -`--job_id` (text) - -Unique job identifier - -`--format` (json, xlsx) - -Format of the file within the hypertext reference - -`--href` (boolean) - -Return hypertext reference - -`--job_type` (manual, reactive) [default: manual] - -Specify type of jobs to retrieve. - - -[← jobs](./index.md) \ No newline at end of file diff --git a/c7n/docs/report/errors/jobs/core.md b/c7n/docs/report/errors/jobs/core.md deleted file mode 100644 index 7a37df90a..000000000 --- a/c7n/docs/report/errors/jobs/core.md +++ /dev/null @@ -1,36 +0,0 @@ -# core - -## Description - -Describes core-related job error reports - -## Synopsis - -```bash -c7n report errors jobs core - --job_id - [--format ] - [--href ] - [--job_type ] -``` - -## Options - -`--job_id` (text) - -Unique job identifier - -`--format` (json, xlsx) - -Format of the file within the hypertext reference - -`--href` (boolean) - -Return hypertext reference - -`--job_type` (manual, reactive) [default: manual] - -Specify type of jobs to retrieve. - - -[← jobs](./index.md) \ No newline at end of file diff --git a/c7n/docs/report/errors/jobs/index.md b/c7n/docs/report/errors/jobs/index.md deleted file mode 100644 index de98a4e82..000000000 --- a/c7n/docs/report/errors/jobs/index.md +++ /dev/null @@ -1,14 +0,0 @@ -# jobs - -## Description - -Describes error reports of jobs - -## Available commands - -- [access](./access.md) -- [core](./core.md) -- [total](./total.md) - - -[← errors](../index.md) \ No newline at end of file diff --git a/c7n/docs/report/errors/jobs/total.md b/c7n/docs/report/errors/jobs/total.md deleted file mode 100644 index c8a24bec5..000000000 --- a/c7n/docs/report/errors/jobs/total.md +++ /dev/null @@ -1,36 +0,0 @@ -# total - -## Description - -Describes all job error reports - -## Synopsis - -```bash -c7n report errors jobs total - --job_id - [--format ] - [--href ] - [--job_type ] -``` - -## Options - -`--job_id` (text) - -Unique job identifier - -`--format` (json, xlsx) - -Format of the file within the hypertext reference - -`--href` (boolean) - -Return hypertext reference - -`--job_type` (manual, reactive) [default: manual] - -Specify type of jobs to retrieve. - - -[← jobs](./index.md) \ No newline at end of file diff --git a/c7n/docs/report/index.md b/c7n/docs/report/index.md deleted file mode 100644 index 892714f62..000000000 --- a/c7n/docs/report/index.md +++ /dev/null @@ -1,21 +0,0 @@ -# report - -## Description - -Manages Custodian Service reports - -## Available commands - -- [clevel](./clevel.md) -- [compliance](./compliance/index.md) -- [department](./department.md) -- [details](./details/index.md) -- [digests](./digests/index.md) -- [errors](./errors/index.md) -- [operational](./operational.md) -- [project](./project.md) -- [push](./push/index.md) -- [rules](./rules/index.md) - - -[← c7n](../README.md) \ No newline at end of file diff --git a/c7n/docs/report/operational.md b/c7n/docs/report/operational.md deleted file mode 100644 index edcff9a1f..000000000 --- a/c7n/docs/report/operational.md +++ /dev/null @@ -1,21 +0,0 @@ -# operational - -## Description - -Retrieves operational-level reports - -## Synopsis - -```bash -c7n report operational - --tenant_name -``` - -## Options - -`--tenant_name` (text) - -Name of related tenant - - -[← report](./index.md) \ No newline at end of file diff --git a/c7n/docs/report/project.md b/c7n/docs/report/project.md deleted file mode 100644 index 1e00a1f6e..000000000 --- a/c7n/docs/report/project.md +++ /dev/null @@ -1,21 +0,0 @@ -# project - -## Description - -Retrieves project-level reports for a tenant group - -## Synopsis - -```bash -c7n report project - --tenant_display_name -``` - -## Options - -`--tenant_display_name` (text) - -The name of the target tenant group - - -[← report](./index.md) \ No newline at end of file diff --git a/c7n/docs/report/push/dojo.md b/c7n/docs/report/push/dojo.md deleted file mode 100644 index 2f90d7a00..000000000 --- a/c7n/docs/report/push/dojo.md +++ /dev/null @@ -1,41 +0,0 @@ -# dojo - -## Description - -Pushes job detailed report(s) to the Dojo SIEM - -## Synopsis - -```bash -c7n report push dojo - [--from_date ] - [--job_id ] - [--job_type ] - [--tenant_name ] - [--to_date ] -``` - -## Options - -`--from_date` (isoparse) - -Generate report FROM date. ISO 8601 format. Example: 2021-09-22T00:00:00.000000 - -`--job_id` (text) - -Unique job identifier. Required if neither `--to_date` or `--from_date` are set. - -`--job_type` (manual, reactive) - -Specify type of jobs to retrieve. - -`--tenant_name` (text) - -Name of related tenant - -`--to_date` (isoparse) - -Generate report TILL date. ISO 8601 format. Example: 2021-09-22T00:00:00.000000 - - -[← push](./index.md) \ No newline at end of file diff --git a/c7n/docs/report/push/index.md b/c7n/docs/report/push/index.md deleted file mode 100644 index 54e26222b..000000000 --- a/c7n/docs/report/push/index.md +++ /dev/null @@ -1,13 +0,0 @@ -# push - -## Description - -Pushes Custodian Service job reports to SIEMs - -## Available commands - -- [dojo](./dojo.md) -- [security_hub](./security_hub.md) - - -[← report](../index.md) \ No newline at end of file diff --git a/c7n/docs/report/push/security_hub.md b/c7n/docs/report/push/security_hub.md deleted file mode 100644 index 26fc12dd2..000000000 --- a/c7n/docs/report/push/security_hub.md +++ /dev/null @@ -1,61 +0,0 @@ -# security_hub - -## Description - -Pushes job detailed report(s) to the AWS Security Hub SIEM - -## Synopsis - -```bash -c7n report push security_hub - [--aws_access_key ] - [--aws_default_region ] - [--aws_secret_access_key ] - [--aws_session_token ] - [--from_date ] - [--job_id ] - [--job_type ] - [--tenant_name ] - [--to_date ] -``` - -## Options - -`--aws_access_key` (text) - -AWS Account access key - -`--aws_default_region` (text) [default: eu-central-1] - -AWS Account default region to init a client - -`--aws_secret_access_key` (text) - -AWS Account secret access key - -`--aws_session_token` (text) - -AWS Account session token - -`--from_date` (isoparse) - -Generate report FROM date. ISO 8601 format. Example: 2021-09-22T00:00:00.000000 - -`--job_id` (text) - -Unique job identifier. Required if neither `--to_date` or `--from_date` are set. - -`--job_type` (manual, reactive) - -Specify type of jobs to retrieve. - -`--tenant_name` (text) - -Name of related tenant - -`--to_date` (isoparse) - -Generate report TILL date. ISO 8601 format. Example: 2021-09-22T00:00:00.000000 - - -[← push](./index.md) \ No newline at end of file diff --git a/c7n/docs/report/rules/accumulated.md b/c7n/docs/report/rules/accumulated.md deleted file mode 100644 index 5b977ee65..000000000 --- a/c7n/docs/report/rules/accumulated.md +++ /dev/null @@ -1,51 +0,0 @@ -# accumulated - -## Description - -Describes tenant-specific rule statistic reports, based on relevant jobs - -## Synopsis - -```bash -c7n report rules accumulated - [--format ] - [--from_date ] - [--href ] - [--job_type ] - [--rule ] - [--tenant_name ] - [--to_date ] -``` - -## Options - -`--format` (json, xlsx) - -Format of the file within the hypertext reference - -`--from_date` (isoparse) - -Generate report FROM date. ISO 8601 format. Example: 2021-09-22T00:00:00.000000 - -`--href` (boolean) - -Return hypertext reference - -`--job_type` (manual, reactive) - -Specify type of jobs to retrieve. - -`--rule` (text) - -Denotes rule to target - -`--tenant_name` (text) - -Name of related tenant - -`--to_date` (isoparse) - -Generate report TILL date. ISO 8601 format. Example: 2021-09-22T00:00:00.000000 - - -[← rules](./index.md) \ No newline at end of file diff --git a/c7n/docs/report/rules/index.md b/c7n/docs/report/rules/index.md deleted file mode 100644 index 06f0e8375..000000000 --- a/c7n/docs/report/rules/index.md +++ /dev/null @@ -1,13 +0,0 @@ -# rules - -## Description - -Describes rule reports of jobs - -## Available commands - -- [accumulated](./accumulated.md) -- [jobs](./jobs.md) - - -[← report](../index.md) \ No newline at end of file diff --git a/c7n/docs/report/rules/jobs.md b/c7n/docs/report/rules/jobs.md deleted file mode 100644 index 232b04a23..000000000 --- a/c7n/docs/report/rules/jobs.md +++ /dev/null @@ -1,41 +0,0 @@ -# jobs - -## Description - -Describes job-specific rule statistic reports - -## Synopsis - -```bash -c7n report rules jobs - --job_id - [--format ] - [--href ] - [--job_type ] - [--rule ] -``` - -## Options - -`--job_id` (text) - -Unique job identifier - -`--format` (json, xlsx) - -Format of the file within the hypertext reference - -`--href` (boolean) - -Return hypertext reference - -`--job_type` (manual, reactive) [default: manual] - -Specify type of jobs to retrieve. - -`--rule` (text) - -Denotes rule to target - - -[← rules](./index.md) \ No newline at end of file diff --git a/c7n/docs/results/describe.md b/c7n/docs/results/describe.md deleted file mode 100644 index 6c274a3a4..000000000 --- a/c7n/docs/results/describe.md +++ /dev/null @@ -1,46 +0,0 @@ -# describe - -## Description - -Describes results of Custodian Service reactive, batched scans - -## Synopsis - -```bash -c7n results describe - [--batch_result_id ] - [--from_date ] - [--limit ] - [--next_token ] - [--tenant_name ] - [--to_date ] -``` - -## Options - -`--batch_result_id` (text) - -Batch Result identifier to describe by - -`--from_date` (isoparse) - -Obtain batched-results FROM date. ISO 8601 format. Example: 2021-09-22T00:00:00.000000 - -`--limit` (integer range) [default: 10] - -Number of records to show - -`--next_token` (text) - -Token to start record-pagination from - -`--tenant_name` (text) - -Name of related tenant - -`--to_date` (isoparse) - -Obtain batched-results TILL date. ISO 8601 format. Example: 2021-09-22T00:00:00.000000 - - -[← results](./index.md) \ No newline at end of file diff --git a/c7n/docs/results/index.md b/c7n/docs/results/index.md deleted file mode 100644 index 493e366dc..000000000 --- a/c7n/docs/results/index.md +++ /dev/null @@ -1,12 +0,0 @@ -# results - -## Description - -Manages Custodian Service Results of Batched Scan Entities - -## Available commands - -- [describe](./describe.md) - - -[← c7n](../README.md) \ No newline at end of file diff --git a/c7n/docs/role/add.md b/c7n/docs/role/add.md deleted file mode 100644 index cbeca52d3..000000000 --- a/c7n/docs/role/add.md +++ /dev/null @@ -1,31 +0,0 @@ -# add - -## Description - -Creates the Role entity with the given name from Customer with the given id - -## Synopsis - -```bash -c7n role add - --name - --policies - [--expiration ] -``` - -## Options - -`--name` (text) - -Role name - -`--policies` (text) - -List of policies to attach to the role - -`--expiration` (text) - -Expiration date, ISO 8601. Example: 2021-08-01T15:30:00 - - -[← role](./index.md) \ No newline at end of file diff --git a/c7n/docs/role/clean_cache.md b/c7n/docs/role/clean_cache.md deleted file mode 100644 index be597453f..000000000 --- a/c7n/docs/role/clean_cache.md +++ /dev/null @@ -1,21 +0,0 @@ -# clean_cache - -## Description - -Cleans cached role from lambda. - -## Synopsis - -```bash -c7n role clean_cache - [--name ] -``` - -## Options - -`--name` (text) - -Role name to clean from cache - - -[← role](./index.md) \ No newline at end of file diff --git a/c7n/docs/role/delete.md b/c7n/docs/role/delete.md deleted file mode 100644 index 3fe82213e..000000000 --- a/c7n/docs/role/delete.md +++ /dev/null @@ -1,21 +0,0 @@ -# delete - -## Description - -Deletes customers role. - -## Synopsis - -```bash -c7n role delete - --name -``` - -## Options - -`--name` (text) - -Role name to delete - - -[← role](./index.md) \ No newline at end of file diff --git a/c7n/docs/role/describe.md b/c7n/docs/role/describe.md deleted file mode 100644 index 18236883d..000000000 --- a/c7n/docs/role/describe.md +++ /dev/null @@ -1,21 +0,0 @@ -# describe - -## Description - -Describes a Custodian Service roles for the given customer. - -## Synopsis - -```bash -c7n role describe - [--name ] -``` - -## Options - -`--name` (text) - -Role name to describe. - - -[← role](./index.md) \ No newline at end of file diff --git a/c7n/docs/role/index.md b/c7n/docs/role/index.md deleted file mode 100644 index 65a9d9490..000000000 --- a/c7n/docs/role/index.md +++ /dev/null @@ -1,16 +0,0 @@ -# role - -## Description - -Manages Role Entity - -## Available commands - -- [add](./add.md) -- [clean_cache](./clean_cache.md) -- [delete](./delete.md) -- [describe](./describe.md) -- [update](./update.md) - - -[← c7n](../README.md) \ No newline at end of file diff --git a/c7n/docs/role/update.md b/c7n/docs/role/update.md deleted file mode 100644 index f2907a578..000000000 --- a/c7n/docs/role/update.md +++ /dev/null @@ -1,36 +0,0 @@ -# update - -## Description - -Updates role configuration. - -## Synopsis - -```bash -c7n role update - --name - [--attach_policy ] - [--detach_policy ] - [--expiration ] -``` - -## Options - -`--name` (text) - -Role name to modify - -`--attach_policy` (text) - -List of policies to attach to the role - -`--detach_policy` (text) - -List of policies to detach from role - -`--expiration` (text) - -Expiration date, ISO 8601. Example: 2021-08-01T15:30:00 - - -[← role](./index.md) \ No newline at end of file diff --git a/c7n/docs/rule/delete.md b/c7n/docs/rule/delete.md deleted file mode 100644 index 092c8cd65..000000000 --- a/c7n/docs/rule/delete.md +++ /dev/null @@ -1,21 +0,0 @@ -# delete - -## Description - -Deletes rules within your customer - -## Synopsis - -```bash -c7n rule delete - [--rule_id ] -``` - -## Options - -`--rule_id` (text) - -Rule id to delete - - -[← rule](./index.md) \ No newline at end of file diff --git a/c7n/docs/rule/describe.md b/c7n/docs/rule/describe.md deleted file mode 100644 index 076eb66a6..000000000 --- a/c7n/docs/rule/describe.md +++ /dev/null @@ -1,46 +0,0 @@ -# describe - -## Description - -Describes rules within your customer - -## Synopsis - -```bash -c7n rule describe - [--cloud ] - [--full ] - [--git_project_id ] - [--limit ] - [--next_token ] - [--rule_id ] -``` - -## Options - -`--cloud` (AWS, AZURE, GCP) - -Display only rules of specific cloud. - -`--full` (boolean) - -Show full command output - -`--git_project_id` (text) - -Filter rules by git project id - -`--limit` (integer range) [default: 10] - -Number of records to show - -`--next_token` (text) - -Token to start record-pagination from - -`--rule_id` (text) - -Rule id to describe - - -[← rule](./index.md) \ No newline at end of file diff --git a/c7n/docs/rule/index.md b/c7n/docs/rule/index.md deleted file mode 100644 index 985a96f4c..000000000 --- a/c7n/docs/rule/index.md +++ /dev/null @@ -1,14 +0,0 @@ -# rule - -## Description - -Manages Rule Entity - -## Available commands - -- [delete](./delete.md) -- [describe](./describe.md) -- [update](./update.md) - - -[← c7n](../README.md) \ No newline at end of file diff --git a/c7n/docs/rule/update.md b/c7n/docs/rule/update.md deleted file mode 100644 index 525593a3a..000000000 --- a/c7n/docs/rule/update.md +++ /dev/null @@ -1,26 +0,0 @@ -# update - -## Description - -Pulls the latest versions of rules within your customer - -## Synopsis - -```bash -c7n rule update - [--all ] - [--git_project_id ] -``` - -## Options - -`--all` (boolean) - -Set to update rules in all the customers. Only for the SYSTEM customer - -`--git_project_id` (text) - -Git repo project id to pull update from - - -[← rule](./index.md) \ No newline at end of file diff --git a/c7n/docs/ruleset/add.md b/c7n/docs/ruleset/add.md deleted file mode 100644 index b45187e2e..000000000 --- a/c7n/docs/ruleset/add.md +++ /dev/null @@ -1,71 +0,0 @@ -# add - -## Description - -Creates Customers ruleset. - -## Synopsis - -```bash -c7n ruleset add - --cloud - --name - [--active ] - [--all_rules ] - [--allow_tenant ] - [--git_project_id ] - [--rule_id ] - [--rule_version ] - [--service_section ] - [--standard ] - [--version ] -``` - -## Options - -`--cloud` (AWS, AZURE, GCP) - -None - -`--name` (text) - -Ruleset name - -`--active` (boolean) - -Force set ruleset version as active - -`--all_rules` (boolean) - -Assemble all available rules for specific cloud provider - -`--allow_tenant` (text) - -Allow ruleset for tenant. Your user must have access to tenant - -`--git_project_id` (text) - -Git project id to build the ruleset - -`--rule_id` (text) - -Rule ids to attach to the ruleset. Multiple ids can be specified - -`--rule_version` (text) - -Rule version to choose in case of duplication (the highest version by default). Used with --full_cloud or --standard flags - -`--service_section` (text) - -Filter rules by the service section - -`--standard` (text) - -Filter rules by the security standard name - -`--version` (float) [default: 1.0] - -None - - -[← ruleset](./index.md) \ No newline at end of file diff --git a/c7n/docs/ruleset/delete.md b/c7n/docs/ruleset/delete.md deleted file mode 100644 index 2dd52e35e..000000000 --- a/c7n/docs/ruleset/delete.md +++ /dev/null @@ -1,27 +0,0 @@ -# delete - -## Description - -Deletes Customer ruleset. For successful deletion, the ruleset must be - inactive - -## Synopsis - -```bash -c7n ruleset delete - --name - --version -``` - -## Options - -`--name` (text) - -Ruleset name - -`--version` (float) - -Ruleset version - - -[← ruleset](./index.md) \ No newline at end of file diff --git a/c7n/docs/ruleset/describe.md b/c7n/docs/ruleset/describe.md deleted file mode 100644 index a2fe4afcb..000000000 --- a/c7n/docs/ruleset/describe.md +++ /dev/null @@ -1,46 +0,0 @@ -# describe - -## Description - -Describes Customer rulesets - -## Synopsis - -```bash -c7n ruleset describe - [--active ] - [--cloud ] - [--get_rules ] - [--licensed ] - [--name ] - [--version ] -``` - -## Options - -`--active` (text) - -Filter only active rulesets - -`--cloud` (AWS, AZURE, GCP) - -Cloud name to filter rulesets - -`--get_rules` (boolean) - -If specified, ruleset's rules ids will be returned. MAKE SURE to use '--json' flag to get a clear output - -`--licensed` (boolean) - -If True, only licensed rule-sets are returned. If False, only standard rule-sets - -`--name` (text) - -Ruleset name - -`--version` (float) - -Ruleset version - - -[← ruleset](./index.md) \ No newline at end of file diff --git a/c7n/docs/ruleset/eventdriven/add.md b/c7n/docs/ruleset/eventdriven/add.md deleted file mode 100644 index e54267883..000000000 --- a/c7n/docs/ruleset/eventdriven/add.md +++ /dev/null @@ -1,41 +0,0 @@ -# add - -## Description - -Creates Event-driven ruleset with all the rules - -## Synopsis - -```bash -c7n ruleset eventdriven add - --cloud - --name - [--rule_id ] - [--rule_version ] - [--version ] -``` - -## Options - -`--cloud` (AWS, AZURE, GCP) - -Ruleset cloud - -`--name` (text) - -Ruleset name - -`--rule_id` (text) - -Rule ids to attach to the ruleset - -`--rule_version` (text) - -Rule version to choose in case of duplication (the highest version by default). Used with --full_cloud or --standard flags - -`--version` (float) [default: 1.0] - -Ruleset version - - -[← eventdriven](./index.md) \ No newline at end of file diff --git a/c7n/docs/ruleset/eventdriven/delete.md b/c7n/docs/ruleset/eventdriven/delete.md deleted file mode 100644 index b1784da79..000000000 --- a/c7n/docs/ruleset/eventdriven/delete.md +++ /dev/null @@ -1,26 +0,0 @@ -# delete - -## Description - -Deletes Event-driven ruleset - -## Synopsis - -```bash -c7n ruleset eventdriven delete - --name - [--version ] -``` - -## Options - -`--name` (text) - -Ruleset name - -`--version` (float) [default: 1.0] - -Event-driven ruleset version to delete - - -[← eventdriven](./index.md) \ No newline at end of file diff --git a/c7n/docs/ruleset/eventdriven/describe.md b/c7n/docs/ruleset/eventdriven/describe.md deleted file mode 100644 index 75d22c0be..000000000 --- a/c7n/docs/ruleset/eventdriven/describe.md +++ /dev/null @@ -1,26 +0,0 @@ -# describe - -## Description - -Describes Event-driven ruleset - -## Synopsis - -```bash -c7n ruleset eventdriven describe - [--cloud ] - [--get_rules ] -``` - -## Options - -`--cloud` (AWS, AZURE, GCP) - -Event-driven ruleset cloud to describe - -`--get_rules` (boolean) - -If specified, ruleset's rules ids will be returned. MAKE SURE to use '--json' flag to get a clear output - - -[← eventdriven](./index.md) \ No newline at end of file diff --git a/c7n/docs/ruleset/eventdriven/index.md b/c7n/docs/ruleset/eventdriven/index.md deleted file mode 100644 index 377a5a002..000000000 --- a/c7n/docs/ruleset/eventdriven/index.md +++ /dev/null @@ -1,16 +0,0 @@ -# eventdriven - -## Description - - - Manages event-driven rule-sets - - -## Available commands - -- [add](./add.md) -- [delete](./delete.md) -- [describe](./describe.md) - - -[← ruleset](../index.md) \ No newline at end of file diff --git a/c7n/docs/ruleset/index.md b/c7n/docs/ruleset/index.md deleted file mode 100644 index f112baab3..000000000 --- a/c7n/docs/ruleset/index.md +++ /dev/null @@ -1,16 +0,0 @@ -# ruleset - -## Description - -Manages Customer rulesets - -## Available commands - -- [add](./add.md) -- [delete](./delete.md) -- [describe](./describe.md) -- [eventdriven](./eventdriven/index.md) -- [update](./update.md) - - -[← c7n](../README.md) \ No newline at end of file diff --git a/c7n/docs/ruleset/update.md b/c7n/docs/ruleset/update.md deleted file mode 100644 index 3ee386a92..000000000 --- a/c7n/docs/ruleset/update.md +++ /dev/null @@ -1,51 +0,0 @@ -# update - -## Description - -Updates Customers ruleset. - -## Synopsis - -```bash -c7n ruleset update - --name - --version - [--active ] - [--allow_tenant ] - [--attach_rules ] - [--detach_rules ] - [--restrict_tenant ] -``` - -## Options - -`--name` (text) - -Ruleset name - -`--version` (float) - -Ruleset version - -`--active` (boolean) - -Force set/unset ruleset version as active - -`--allow_tenant` (text) - -Allow ruleset for tenant. Your user must have access to tenant - -`--attach_rules` (text) - -Rule ids to attach to the ruleset. Multiple values allowed - -`--detach_rules` (text) - -Rule ids to detach from the ruleset. Multiple values allowed - -`--restrict_tenant` (text) - -Restrict ruleset for tenant. Your user must have access to tenant - - -[← ruleset](./index.md) \ No newline at end of file diff --git a/c7n/docs/rulesource/add.md b/c7n/docs/rulesource/add.md deleted file mode 100644 index b4ef1f9a9..000000000 --- a/c7n/docs/rulesource/add.md +++ /dev/null @@ -1,46 +0,0 @@ -# add - -## Description - -Creates rule source - -## Synopsis - -```bash -c7n rulesource add - --git_access_secret - --git_project_id - [--allow_tenant ] - [--git_ref ] - [--git_rules_prefix ] - [--git_url ] -``` - -## Options - -`--git_access_secret` (text) - -Secret token to be able to access the repository - -`--git_project_id` (text) - -GitLab Project id - -`--allow_tenant` (text) - -Allow ruleset for tenant. Your user must have access to tenant - -`--git_ref` (text) [default: master] - -Name of the branch to grab rules from - -`--git_rules_prefix` (text) [default: /] - -Rules path prefix - -`--git_url` (text) [default: https://git.epam.com] - -Link to GitLab repository with c7n rules - - -[← rulesource](./index.md) \ No newline at end of file diff --git a/c7n/docs/rulesource/delete.md b/c7n/docs/rulesource/delete.md deleted file mode 100644 index e41e4f3ea..000000000 --- a/c7n/docs/rulesource/delete.md +++ /dev/null @@ -1,21 +0,0 @@ -# delete - -## Description - -Deletes rule source - -## Synopsis - -```bash -c7n rulesource delete - --git_project_id -``` - -## Options - -`--git_project_id` (text) - -Git project id to delete rule source - - -[← rulesource](./index.md) \ No newline at end of file diff --git a/c7n/docs/rulesource/describe.md b/c7n/docs/rulesource/describe.md deleted file mode 100644 index cff94dba8..000000000 --- a/c7n/docs/rulesource/describe.md +++ /dev/null @@ -1,21 +0,0 @@ -# describe - -## Description - -Describes rule source - -## Synopsis - -```bash -c7n rulesource describe - [--git_project_id ] -``` - -## Options - -`--git_project_id` (text) - -Git project id to describe rule source - - -[← rulesource](./index.md) \ No newline at end of file diff --git a/c7n/docs/rulesource/index.md b/c7n/docs/rulesource/index.md deleted file mode 100644 index 7026376c1..000000000 --- a/c7n/docs/rulesource/index.md +++ /dev/null @@ -1,15 +0,0 @@ -# rulesource - -## Description - -Manages Rule Source entity - -## Available commands - -- [add](./add.md) -- [delete](./delete.md) -- [describe](./describe.md) -- [update](./update.md) - - -[← c7n](../README.md) \ No newline at end of file diff --git a/c7n/docs/rulesource/update.md b/c7n/docs/rulesource/update.md deleted file mode 100644 index aa5e05209..000000000 --- a/c7n/docs/rulesource/update.md +++ /dev/null @@ -1,51 +0,0 @@ -# update - -## Description - -Updates rule source - -## Synopsis - -```bash -c7n rulesource update - --git_project_id - [--allow_tenant ] - [--git_access_secret ] - [--git_ref ] - [--git_rules_prefix ] - [--git_url ] - [--restrict_tenant ] -``` - -## Options - -`--git_project_id` (text) - -GitLab Project id - -`--allow_tenant` (text) - -Allow ruleset for tenant. Your user must have access to tenant - -`--git_access_secret` (text) - -None - -`--git_ref` (text) - -Name of the branch to grab rules from. - -`--git_rules_prefix` (text) - -Rules path prefix. - -`--git_url` (text) - -Link to GitLab repository with c7n rules - -`--restrict_tenant` (text) - -Restrict ruleset for tenant. Your user must have access to tenant - - -[← rulesource](./index.md) \ No newline at end of file diff --git a/c7n/docs/setting/index.md b/c7n/docs/setting/index.md deleted file mode 100644 index 9f268b092..000000000 --- a/c7n/docs/setting/index.md +++ /dev/null @@ -1,13 +0,0 @@ -# setting - -## Description - -Manages Custodian Service Settings - -## Available commands - -- [lm](./lm/index.md) -- [mail](./mail/index.md) - - -[← c7n](../README.md) \ No newline at end of file diff --git a/c7n/docs/setting/lm/client/add.md b/c7n/docs/setting/lm/client/add.md deleted file mode 100644 index f8e290447..000000000 --- a/c7n/docs/setting/lm/client/add.md +++ /dev/null @@ -1,41 +0,0 @@ -# add - -## Description - -Adds License Manager provided client-key data - -## Synopsis - -```bash -c7n setting lm client add - --algorithm - --key_id - --private_key - [--b64encoded ] - [--format ] -``` - -## Options - -`--algorithm` (text) - -Algorithm granted by the License Manager. - -`--key_id` (text) - -Key-id granted by the License Manager. - -`--private_key` (text) - -Private-key granted by the License Manager. - -`--b64encoded` (boolean) - -Specify whether the private is b64encoded. - -`--format` (PEM) [default: PEM] - -Format of the private-key. - - -[← client](./index.md) \ No newline at end of file diff --git a/c7n/docs/setting/lm/client/delete.md b/c7n/docs/setting/lm/client/delete.md deleted file mode 100644 index fffdb08fd..000000000 --- a/c7n/docs/setting/lm/client/delete.md +++ /dev/null @@ -1,21 +0,0 @@ -# delete - -## Description - -Removes current License Manager client-key data - -## Synopsis - -```bash -c7n setting lm client delete - --key_id -``` - -## Options - -`--key_id` (text) - -Key-id granted by the License Manager. - - -[← client](./index.md) \ No newline at end of file diff --git a/c7n/docs/setting/lm/client/describe.md b/c7n/docs/setting/lm/client/describe.md deleted file mode 100644 index 5a071f782..000000000 --- a/c7n/docs/setting/lm/client/describe.md +++ /dev/null @@ -1,21 +0,0 @@ -# describe - -## Description - -Describe current License Manager client-key data - -## Synopsis - -```bash -c7n setting lm client describe - [--format ] -``` - -## Options - -`--format` (PEM) [default: PEM] - -Format of the private-key. - - -[← client](./index.md) \ No newline at end of file diff --git a/c7n/docs/setting/lm/client/index.md b/c7n/docs/setting/lm/client/index.md deleted file mode 100644 index 316c18acf..000000000 --- a/c7n/docs/setting/lm/client/index.md +++ /dev/null @@ -1,14 +0,0 @@ -# client - -## Description - -Manages License Manager Client data - -## Available commands - -- [add](./add.md) -- [delete](./delete.md) -- [describe](./describe.md) - - -[← lm](../index.md) \ No newline at end of file diff --git a/c7n/docs/setting/lm/config/add.md b/c7n/docs/setting/lm/config/add.md deleted file mode 100644 index e8ef933bc..000000000 --- a/c7n/docs/setting/lm/config/add.md +++ /dev/null @@ -1,31 +0,0 @@ -# add - -## Description - -Adds License Manager access configuration data - -## Synopsis - -```bash -c7n setting lm config add - --host - --port - --version -``` - -## Options - -`--host` (text) - -License Manager host. - -`--port` (integer) - -License Manager port. - -`--version` (float) [default: 2.0] - -License Manager version. - - -[← config](./index.md) \ No newline at end of file diff --git a/c7n/docs/setting/lm/config/delete.md b/c7n/docs/setting/lm/config/delete.md deleted file mode 100644 index b4763a755..000000000 --- a/c7n/docs/setting/lm/config/delete.md +++ /dev/null @@ -1,21 +0,0 @@ -# delete - -## Description - -Removes current License Manager access configuration data - -## Synopsis - -```bash -c7n setting lm config delete - --confirm -``` - -## Options - -`--confirm` (boolean) - -Confirms the action. - - -[← config](./index.md) \ No newline at end of file diff --git a/c7n/docs/setting/lm/config/describe.md b/c7n/docs/setting/lm/config/describe.md deleted file mode 100644 index 0338204eb..000000000 --- a/c7n/docs/setting/lm/config/describe.md +++ /dev/null @@ -1,18 +0,0 @@ -# describe - -## Description - -Describes current License Manager access configuration data - -## Synopsis - -```bash -c7n setting lm config describe -``` - -## Options - - - - -[← config](./index.md) \ No newline at end of file diff --git a/c7n/docs/setting/lm/config/index.md b/c7n/docs/setting/lm/config/index.md deleted file mode 100644 index 0784e69c9..000000000 --- a/c7n/docs/setting/lm/config/index.md +++ /dev/null @@ -1,14 +0,0 @@ -# config - -## Description - -Manages License Manager Config data - -## Available commands - -- [add](./add.md) -- [delete](./delete.md) -- [describe](./describe.md) - - -[← lm](../index.md) \ No newline at end of file diff --git a/c7n/docs/setting/lm/index.md b/c7n/docs/setting/lm/index.md deleted file mode 100644 index 5572570db..000000000 --- a/c7n/docs/setting/lm/index.md +++ /dev/null @@ -1,13 +0,0 @@ -# lm - -## Description - -Manages License Manager Setting(s) - -## Available commands - -- [client](./client/index.md) -- [config](./config/index.md) - - -[← setting](../index.md) \ No newline at end of file diff --git a/c7n/docs/setting/mail/add.md b/c7n/docs/setting/mail/add.md deleted file mode 100644 index 2d3bdc779..000000000 --- a/c7n/docs/setting/mail/add.md +++ /dev/null @@ -1,56 +0,0 @@ -# add - -## Description - -Creates Custodian Service Mail configuration - -## Synopsis - -```bash -c7n setting mail add - --host - --password - --password_label - --port - --username - [--emails_per_session ] - [--sender_name ] - [--use_tls ] -``` - -## Options - -`--host` (text) - -Host of a mail server. - -`--password` (text) - -Password of mail account. - -`--password_label` (text) - -Name of the parameter to store password under. - -`--port` (integer) - -Port of a mail server. - -`--username` (text) - -Username of mail account. - -`--emails_per_session` (integer) [default: 1] - -Amount of emails to send per session. - -`--sender_name` (text) - -Name to specify as the sender of email(s). Defaults to '--username'. - -`--use_tls` (boolean) - -Specify to whether utilize TLS. - - -[← mail](./index.md) \ No newline at end of file diff --git a/c7n/docs/setting/mail/delete.md b/c7n/docs/setting/mail/delete.md deleted file mode 100644 index 581e1821c..000000000 --- a/c7n/docs/setting/mail/delete.md +++ /dev/null @@ -1,21 +0,0 @@ -# delete - -## Description - -Deletes Custodian Service Mail configuration - -## Synopsis - -```bash -c7n setting mail delete - --confirm -``` - -## Options - -`--confirm` (boolean) - -Confirms the action. - - -[← mail](./index.md) \ No newline at end of file diff --git a/c7n/docs/setting/mail/describe.md b/c7n/docs/setting/mail/describe.md deleted file mode 100644 index d4527b336..000000000 --- a/c7n/docs/setting/mail/describe.md +++ /dev/null @@ -1,21 +0,0 @@ -# describe - -## Description - -Describes Custodian Service Mail configuration - -## Synopsis - -```bash -c7n setting mail describe - [--display_password ] -``` - -## Options - -`--display_password` (boolean) - -Specify to whether display a configured password. - - -[← mail](./index.md) \ No newline at end of file diff --git a/c7n/docs/setting/mail/index.md b/c7n/docs/setting/mail/index.md deleted file mode 100644 index 949e52525..000000000 --- a/c7n/docs/setting/mail/index.md +++ /dev/null @@ -1,14 +0,0 @@ -# mail - -## Description - -Manages Custodian Service Mail configuration - -## Available commands - -- [add](./add.md) -- [delete](./delete.md) -- [describe](./describe.md) - - -[← setting](../index.md) \ No newline at end of file diff --git a/c7n/docs/show_config.md b/c7n/docs/show_config.md deleted file mode 100644 index f19e3bc98..000000000 --- a/c7n/docs/show_config.md +++ /dev/null @@ -1,18 +0,0 @@ -# show_config - -## Description - -Returns the cli configuration - -## Synopsis - -```bash -c7n show_config -``` - -## Options - - - - -[← c7n](./README.md) \ No newline at end of file diff --git a/c7n/docs/siem/add/dojo.md b/c7n/docs/siem/add/dojo.md deleted file mode 100644 index 6729d9c4b..000000000 --- a/c7n/docs/siem/add/dojo.md +++ /dev/null @@ -1,76 +0,0 @@ -# dojo - -## Description - -Adds dojo configuration. When you specify '--product_type_name', - '--product_name', '--engagement_name', '--test_title', you can use these - special key-words: 'customer', 'tenant', 'job_id', 'day_scope', - 'job_scope' inside curly braces to map the entities. - Example: 'c7n siem add dojo ... --product_name - "Product {tenant}: {day_scope}"' - -## Synopsis - -```bash -c7n siem add dojo - --api_key - --host - --user - [--display_all_fields ] - [--engagement_name ] - [--product_name ] - [--product_type_name ] - [--resource_per_finding ] - [--tenant_name ] - [--test_title ] - [--upload_files ] -``` - -## Options - -`--api_key` (text) - -DefectDojo API key - -`--host` (text) - -DefectDojo host:port - -`--user` (text) [default: admin] - -DefectDojo user name - -`--display_all_fields` (boolean) - -Flag for displaying all fields - -`--engagement_name` (text) - -DefectDojo's engagement name. Account name and day's date scope will be used by default: '{account}: {day_scope}' - -`--product_name` (text) - -DefectDojo's product name. Tenant and account names will be used by default: '{tenant} - {account}' - -`--product_type_name` (text) - -DefectDojo's product type name. Customer's name will be used by default: '{customer}' - -`--resource_per_finding` (boolean) - -Specify if you want each finding to represent a separate violated resource - -`--tenant_name` (text) - -Name of related tenant - -`--test_title` (text) - -Tests' title name in DefectDojo. Job's date scope and job id will be used by default: '{job_scope}: {job_id}' - -`--upload_files` (boolean) - -Flag for displaying a file for each resource with its full description in the "file" field - - -[← add](./index.md) \ No newline at end of file diff --git a/c7n/docs/siem/add/index.md b/c7n/docs/siem/add/index.md deleted file mode 100644 index fcab9d699..000000000 --- a/c7n/docs/siem/add/index.md +++ /dev/null @@ -1,13 +0,0 @@ -# add - -## Description - -Manages SIEM configuration create action - -## Available commands - -- [dojo](./dojo.md) -- [security_hub](./security_hub.md) - - -[← siem](../index.md) \ No newline at end of file diff --git a/c7n/docs/siem/add/security_hub.md b/c7n/docs/siem/add/security_hub.md deleted file mode 100644 index 4a3a38d4a..000000000 --- a/c7n/docs/siem/add/security_hub.md +++ /dev/null @@ -1,36 +0,0 @@ -# security_hub - -## Description - -Adds security hub configuration. - -## Synopsis - -```bash -c7n siem add security_hub - --product_arn - --region - [--tenant_name ] - [--trusted_role_arn ] -``` - -## Options - -`--product_arn` (text) - -ARN of security product - -`--region` (text) - -AWS region name - -`--tenant_name` (text) - -Name of related tenant - -`--trusted_role_arn` (text) - -Role that will be assumed to upload findings - - -[← add](./index.md) \ No newline at end of file diff --git a/c7n/docs/siem/delete/dojo.md b/c7n/docs/siem/delete/dojo.md deleted file mode 100644 index 2a865521a..000000000 --- a/c7n/docs/siem/delete/dojo.md +++ /dev/null @@ -1,21 +0,0 @@ -# dojo - -## Description - -Deletes DefectDojo SIEM configuration. - -## Synopsis - -```bash -c7n siem delete dojo - [--tenant_name ] -``` - -## Options - -`--tenant_name` (text) - -Name of related tenant - - -[← delete](./index.md) \ No newline at end of file diff --git a/c7n/docs/siem/delete/index.md b/c7n/docs/siem/delete/index.md deleted file mode 100644 index 2e0ba9dce..000000000 --- a/c7n/docs/siem/delete/index.md +++ /dev/null @@ -1,13 +0,0 @@ -# delete - -## Description - -Manages SIEM configuration delete action - -## Available commands - -- [dojo](./dojo.md) -- [security_hub](./security_hub.md) - - -[← siem](../index.md) \ No newline at end of file diff --git a/c7n/docs/siem/delete/security_hub.md b/c7n/docs/siem/delete/security_hub.md deleted file mode 100644 index 15606de51..000000000 --- a/c7n/docs/siem/delete/security_hub.md +++ /dev/null @@ -1,21 +0,0 @@ -# security_hub - -## Description - -Deletes Security Hub SIEM configuration. - -## Synopsis - -```bash -c7n siem delete security_hub - [--tenant_name ] -``` - -## Options - -`--tenant_name` (text) - -Name of related tenant - - -[← delete](./index.md) \ No newline at end of file diff --git a/c7n/docs/siem/describe/dojo.md b/c7n/docs/siem/describe/dojo.md deleted file mode 100644 index ac080de86..000000000 --- a/c7n/docs/siem/describe/dojo.md +++ /dev/null @@ -1,21 +0,0 @@ -# dojo - -## Description - -Describes DefectDojo SIEM configuration of a customer. - -## Synopsis - -```bash -c7n siem describe dojo - [--tenant_name ] -``` - -## Options - -`--tenant_name` (text) - -Name of related tenant - - -[← describe](./index.md) \ No newline at end of file diff --git a/c7n/docs/siem/describe/index.md b/c7n/docs/siem/describe/index.md deleted file mode 100644 index 6e1195281..000000000 --- a/c7n/docs/siem/describe/index.md +++ /dev/null @@ -1,13 +0,0 @@ -# describe - -## Description - -Manages SIEM configuration describe action - -## Available commands - -- [dojo](./dojo.md) -- [security_hub](./security_hub.md) - - -[← siem](../index.md) \ No newline at end of file diff --git a/c7n/docs/siem/describe/security_hub.md b/c7n/docs/siem/describe/security_hub.md deleted file mode 100644 index b2eedbcb0..000000000 --- a/c7n/docs/siem/describe/security_hub.md +++ /dev/null @@ -1,21 +0,0 @@ -# security_hub - -## Description - -Describes Security Hub SIEM configuration of a customer. - -## Synopsis - -```bash -c7n siem describe security_hub - [--tenant_name ] -``` - -## Options - -`--tenant_name` (text) - -Name of related tenant - - -[← describe](./index.md) \ No newline at end of file diff --git a/c7n/docs/siem/index.md b/c7n/docs/siem/index.md deleted file mode 100644 index ed477c233..000000000 --- a/c7n/docs/siem/index.md +++ /dev/null @@ -1,15 +0,0 @@ -# siem - -## Description - -Manages SIEM configuration - -## Available commands - -- [add](./add/index.md) -- [delete](./delete/index.md) -- [describe](./describe/index.md) -- [update](./update/index.md) - - -[← c7n](../README.md) \ No newline at end of file diff --git a/c7n/docs/siem/update/dojo.md b/c7n/docs/siem/update/dojo.md deleted file mode 100644 index 852bc98a5..000000000 --- a/c7n/docs/siem/update/dojo.md +++ /dev/null @@ -1,81 +0,0 @@ -# dojo - -## Description - -Updates dojo configuration. When you specify '--product_type_name', - '--product_name', '--engagement_name', '--test_title', you can use these - special key-words: 'customer', 'tenant', 'account', 'job_id', 'day_scope', - 'job_scope' inside curly braces to map the entities. - Example: 'c7n siem add dojo ... --product_name - "Product {account}: {day_scope}"' - -## Synopsis - -```bash -c7n siem update dojo - [--api_key ] - [--clear_existing_mapping ] - [--display_all_fields ] - [--engagement_name ] - [--host ] - [--product_name ] - [--product_type_name ] - [--resource_per_finding ] - [--tenant_name ] - [--test_title ] - [--upload_files ] - [--user ] -``` - -## Options - -`--api_key` (text) - -DefectDojo API key - -`--clear_existing_mapping` (boolean) - -Clear the existing entities mapping configuration so that you can use the default one - -`--display_all_fields` (boolean) - -Flag for displaying all fields - -`--engagement_name` (text) - -DefectDojo's engagement name. Account name and day's date scope will be used by default: '{account}: {day_scope}' - -`--host` (text) - -DefectDojo host:port - -`--product_name` (text) - -DefectDojo's product name. Tenant and account names will be used by default: '{tenant} - {account}' - -`--product_type_name` (text) - -DefectDojo's product type name. Customer's name will be used by default: '{customer}' - -`--resource_per_finding` (boolean) - -Specify if you want each finding to represent a separate violated resource - -`--tenant_name` (text) - -Name of related tenant - -`--test_title` (text) - -Tests' title name in DefectDojo. Job's date scope and job id will be used by default: '{job_scope}: {job_id}' - -`--upload_files` (boolean) - -Flag for displaying a file for each resource with its full description in the "file" field - -`--user` (text) [default: admin] - -DefectDojo user name - - -[← update](./index.md) \ No newline at end of file diff --git a/c7n/docs/siem/update/index.md b/c7n/docs/siem/update/index.md deleted file mode 100644 index cd6e877da..000000000 --- a/c7n/docs/siem/update/index.md +++ /dev/null @@ -1,13 +0,0 @@ -# update - -## Description - -Manages SIEM configuration update action - -## Available commands - -- [dojo](./dojo.md) -- [security_hub](./security_hub.md) - - -[← siem](../index.md) \ No newline at end of file diff --git a/c7n/docs/siem/update/security_hub.md b/c7n/docs/siem/update/security_hub.md deleted file mode 100644 index 42304878a..000000000 --- a/c7n/docs/siem/update/security_hub.md +++ /dev/null @@ -1,36 +0,0 @@ -# security_hub - -## Description - -Updates security hub configuration. - -## Synopsis - -```bash -c7n siem update security_hub - [--product_arn ] - [--region ] - [--tenant_name ] - [--trusted_role_arn ] -``` - -## Options - -`--product_arn` (text) - -ARN of security product - -`--region` (text) - -AWS region name - -`--tenant_name` (text) - -Name of related tenant - -`--trusted_role_arn` (text) - -Role that will be assumed to upload findings - - -[← update](./index.md) \ No newline at end of file diff --git a/c7n/docs/tenant/create.md b/c7n/docs/tenant/create.md deleted file mode 100644 index 1a44ce53d..000000000 --- a/c7n/docs/tenant/create.md +++ /dev/null @@ -1,56 +0,0 @@ -# create - -## Description - -Activates a tenant, if the environment does not restrict it. - -## Synopsis - -```bash -c7n tenant create - --account_number - --cloud - --tenant_name - [--default_owner ] - [--display_name ] - [--primary_contacts ] - [--secondary_contacts ] - [--tenant_manager_contacts ] -``` - -## Options - -`--account_number` (text) - -Cloud native account identifier - -`--cloud` (AWS, AZURE, GOOGLE) - -Cloud of the tenant - -`--tenant_name` (text) - -Name of the tenant - -`--default_owner` (text) - -Owner email - -`--display_name` (text) - -Tenant display name. If not specified, the value from --name is used - -`--primary_contacts` (text) - -Primary emails - -`--secondary_contacts` (text) - -Secondary emails - -`--tenant_manager_contacts` (text) - -Tenant manager emails - - -[← tenant](./index.md) \ No newline at end of file diff --git a/c7n/docs/tenant/credentials/add.md b/c7n/docs/tenant/credentials/add.md deleted file mode 100644 index f76941c68..000000000 --- a/c7n/docs/tenant/credentials/add.md +++ /dev/null @@ -1,36 +0,0 @@ -# add - -## Description - -Creates Custodian Service Credentials Manager configuration. - -## Synopsis - -```bash -c7n tenant credentials add - --account_number - --cloud - [--enabled ] - [--trusted_role_arn ] -``` - -## Options - -`--account_number` (text) - -Cloud native account identifier - -`--cloud` (text) - -The cloud to which the credentials configuration belongs. - -`--enabled` (boolean) - -Enable or disable credentials, if not specified: disabled - -`--trusted_role_arn` (text) - -Account role to assume - - -[← credentials](./index.md) \ No newline at end of file diff --git a/c7n/docs/tenant/credentials/delete.md b/c7n/docs/tenant/credentials/delete.md deleted file mode 100644 index 0e716e242..000000000 --- a/c7n/docs/tenant/credentials/delete.md +++ /dev/null @@ -1,26 +0,0 @@ -# delete - -## Description - -Deletes Custodian Service Credentials Manager configuration. - -## Synopsis - -```bash -c7n tenant credentials delete - --account_number - --cloud -``` - -## Options - -`--account_number` (text) - -Cloud native account identifier - -`--cloud` (text) - -The cloud to which the credentials configuration belongs. - - -[← credentials](./index.md) \ No newline at end of file diff --git a/c7n/docs/tenant/credentials/describe.md b/c7n/docs/tenant/credentials/describe.md deleted file mode 100644 index 2e0824035..000000000 --- a/c7n/docs/tenant/credentials/describe.md +++ /dev/null @@ -1,26 +0,0 @@ -# describe - -## Description - -Describes Custodian Service Credentials Manager configurations. - -## Synopsis - -```bash -c7n tenant credentials describe - [--account_number ] - [--cloud ] -``` - -## Options - -`--account_number` (text) - -Cloud native account identifier - -`--cloud` (text) - -The cloud to which the credentials configuration belongs. - - -[← credentials](./index.md) \ No newline at end of file diff --git a/c7n/docs/tenant/credentials/index.md b/c7n/docs/tenant/credentials/index.md deleted file mode 100644 index bfac3614e..000000000 --- a/c7n/docs/tenant/credentials/index.md +++ /dev/null @@ -1,15 +0,0 @@ -# credentials - -## Description - -Manages Custodian Service Credentials Manager configs - -## Available commands - -- [add](./add.md) -- [delete](./delete.md) -- [describe](./describe.md) -- [update](./update.md) - - -[← tenant](../index.md) \ No newline at end of file diff --git a/c7n/docs/tenant/credentials/update.md b/c7n/docs/tenant/credentials/update.md deleted file mode 100644 index 8a202d9c7..000000000 --- a/c7n/docs/tenant/credentials/update.md +++ /dev/null @@ -1,36 +0,0 @@ -# update - -## Description - -Updates Custodian Service Credentials Manager configuration. - -## Synopsis - -```bash -c7n tenant credentials update - --account_number - --cloud - [--enabled ] - [--trusted_role_arn ] -``` - -## Options - -`--account_number` (text) - -Cloud native account identifier - -`--cloud` (text) - -The cloud to which the credentials configuration belongs. - -`--enabled` (boolean) - -Enable or disable credentials actuality - -`--trusted_role_arn` (text) - -Account role to assume - - -[← credentials](./index.md) \ No newline at end of file diff --git a/c7n/docs/tenant/describe.md b/c7n/docs/tenant/describe.md deleted file mode 100644 index 0c579cbb8..000000000 --- a/c7n/docs/tenant/describe.md +++ /dev/null @@ -1,41 +0,0 @@ -# describe - -## Description - -Describes your user's tenant(s) - -## Synopsis - -```bash -c7n tenant describe - [--account_number ] - [--full ] - [--limit ] - [--next_token ] - [--tenant_name ] -``` - -## Options - -`--account_number` (text) - -Cloud native account identifier - -`--full` (boolean) - -Show full command output. - -`--limit` (integer range) [default: 10] - -Number of records to show - -`--next_token` (text) - -Token to start record-pagination from - -`--tenant_name` (text) - -Name of related tenant - - -[← tenant](./index.md) \ No newline at end of file diff --git a/c7n/docs/tenant/findings/delete.md b/c7n/docs/tenant/findings/delete.md deleted file mode 100644 index 1ff1e5054..000000000 --- a/c7n/docs/tenant/findings/delete.md +++ /dev/null @@ -1,21 +0,0 @@ -# delete - -## Description - -Clears Findings state of a tenant. - -## Synopsis - -```bash -c7n tenant findings delete - [--tenant_name ] -``` - -## Options - -`--tenant_name` (text) - -Name of related tenant - - -[← findings](./index.md) \ No newline at end of file diff --git a/c7n/docs/tenant/findings/describe.md b/c7n/docs/tenant/findings/describe.md deleted file mode 100644 index 154d480e7..000000000 --- a/c7n/docs/tenant/findings/describe.md +++ /dev/null @@ -1,61 +0,0 @@ -# describe - -## Description - -Describes Findings state of a tenant. - -## Synopsis - -```bash -c7n tenant findings describe - [--expand ] - [--get_url ] - [--mapped ] - [--region ] - [--resource_type ] - [--rule ] - [--severity ] - [--subset_targets ] - [--tenant_name ] -``` - -## Options - -`--expand` (resources) [default: resources] - -Expansion parameter to invert Findings collection on. - -`--get_url` (boolean) - -Returns a presigned URL rather than a raw Findings collection. - -`--mapped` (text) - -Applies mapping format of an expanded Findings collection, by a given key, rather than a listed one. - -`--region` (text) - -Region to include in a Findings state. - -`--resource_type` (text) - -Resource type to include in a Findings state. - -`--rule` (text) - -Rule to include in a Findings state. - -`--severity` (High, Medium, Low, Info) - -Severity values to include in a Findings state. - -`--subset_targets` (boolean) - -Applies dependent subset inclusion. - -`--tenant_name` (text) - -Name of related tenant - - -[← findings](./index.md) \ No newline at end of file diff --git a/c7n/docs/tenant/findings/index.md b/c7n/docs/tenant/findings/index.md deleted file mode 100644 index d8c46e9c1..000000000 --- a/c7n/docs/tenant/findings/index.md +++ /dev/null @@ -1,13 +0,0 @@ -# findings - -## Description - -Manages Tenant Findings state - -## Available commands - -- [delete](./delete.md) -- [describe](./describe.md) - - -[← tenant](../index.md) \ No newline at end of file diff --git a/c7n/docs/tenant/index.md b/c7n/docs/tenant/index.md deleted file mode 100644 index 651ef01da..000000000 --- a/c7n/docs/tenant/index.md +++ /dev/null @@ -1,17 +0,0 @@ -# tenant - -## Description - -Manages Tenant Entity - -## Available commands - -- [create](./create.md) -- [credentials](./credentials/index.md) -- [describe](./describe.md) -- [findings](./findings/index.md) -- [region](./region/index.md) -- [update](./update.md) - - -[← c7n](../README.md) \ No newline at end of file diff --git a/c7n/docs/tenant/region/activate.md b/c7n/docs/tenant/region/activate.md deleted file mode 100644 index eb964ea95..000000000 --- a/c7n/docs/tenant/region/activate.md +++ /dev/null @@ -1,26 +0,0 @@ -# activate - -## Description - -Activates region in tenant - -## Synopsis - -```bash -c7n tenant region activate - --region - [--tenant_name ] -``` - -## Options - -`--region` (text) - -Region native name to activate - -`--tenant_name` (text) - -Name of related tenant - - -[← region](./index.md) \ No newline at end of file diff --git a/c7n/docs/tenant/region/index.md b/c7n/docs/tenant/region/index.md deleted file mode 100644 index 8c16243fa..000000000 --- a/c7n/docs/tenant/region/index.md +++ /dev/null @@ -1,12 +0,0 @@ -# region - -## Description - -Manages tenant regions - -## Available commands - -- [activate](./activate.md) - - -[← tenant](../index.md) \ No newline at end of file diff --git a/c7n/docs/tenant/update.md b/c7n/docs/tenant/update.md deleted file mode 100644 index 28e40901c..000000000 --- a/c7n/docs/tenant/update.md +++ /dev/null @@ -1,31 +0,0 @@ -# update - -## Description - -Updates settings of your user's tenant - -## Synopsis - -```bash -c7n tenant update - [--rules_to_exclude ] - [--rules_to_include ] - [--tenant_name ] -``` - -## Options - -`--rules_to_exclude` (text) - -Rules to exclude for tenant - -`--rules_to_include` (text) - -Rules to include for tenant - -`--tenant_name` (text) - -Name of related tenant - - -[← tenant](./index.md) \ No newline at end of file diff --git a/c7n/docs/trigger/configuration_backup.md b/c7n/docs/trigger/configuration_backup.md deleted file mode 100644 index 5781481e3..000000000 --- a/c7n/docs/trigger/configuration_backup.md +++ /dev/null @@ -1,18 +0,0 @@ -# configuration_backup - -## Description - -Creates backup of Custodian Service - -## Synopsis - -```bash -c7n trigger configuration_backup -``` - -## Options - - - - -[← trigger](./index.md) \ No newline at end of file diff --git a/c7n/docs/trigger/index.md b/c7n/docs/trigger/index.md deleted file mode 100644 index cba38e7db..000000000 --- a/c7n/docs/trigger/index.md +++ /dev/null @@ -1,12 +0,0 @@ -# trigger - -## Description - -Manages Lambda triggering - -## Available commands - -- [configuration_backup](./configuration_backup.md) - - -[← c7n](../README.md) \ No newline at end of file diff --git a/c7n/docs/user/index.md b/c7n/docs/user/index.md deleted file mode 100644 index 8c4a4d876..000000000 --- a/c7n/docs/user/index.md +++ /dev/null @@ -1,13 +0,0 @@ -# user - -## Description - -Manages User Entity - -## Available commands - -- [signup](./signup.md) -- [tenants](./tenants/index.md) - - -[← c7n](../README.md) \ No newline at end of file diff --git a/c7n/docs/user/signup.md b/c7n/docs/user/signup.md deleted file mode 100644 index 56c5301ea..000000000 --- a/c7n/docs/user/signup.md +++ /dev/null @@ -1,36 +0,0 @@ -# signup - -## Description - -Signs up a new Cognito user - -## Synopsis - -```bash -c7n user signup - --role - --username - [--password ] - [--tenants ] -``` - -## Options - -`--role` (text) - -User`s role - -`--username` (text) - -User`s username. Must be unique - -`--password` (text) - -User`s password - -`--tenants` (text) - -Tenants within the customer the user need access to - - -[← user](./index.md) \ No newline at end of file diff --git a/c7n/docs/user/tenants/assign.md b/c7n/docs/user/tenants/assign.md deleted file mode 100644 index b4f106408..000000000 --- a/c7n/docs/user/tenants/assign.md +++ /dev/null @@ -1,26 +0,0 @@ -# assign - -## Description - -Assigns tenants to a user - -## Synopsis - -```bash -c7n user tenants assign - --tenant - --username -``` - -## Options - -`--tenant` (text) - -Tenant names to assign to user - -`--username` (text) - -User to update - - -[← tenants](./index.md) \ No newline at end of file diff --git a/c7n/docs/user/tenants/describe.md b/c7n/docs/user/tenants/describe.md deleted file mode 100644 index b3aa76a8e..000000000 --- a/c7n/docs/user/tenants/describe.md +++ /dev/null @@ -1,21 +0,0 @@ -# describe - -## Description - -Describes user-accessible tenants - -## Synopsis - -```bash -c7n user tenants describe - --username -``` - -## Options - -`--username` (text) - -The name of the user for whom the available tenants information is to be provided - - -[← tenants](./index.md) \ No newline at end of file diff --git a/c7n/docs/user/tenants/index.md b/c7n/docs/user/tenants/index.md deleted file mode 100644 index bc07e8af3..000000000 --- a/c7n/docs/user/tenants/index.md +++ /dev/null @@ -1,14 +0,0 @@ -# tenants - -## Description - -Manages User-Tenant relations - -## Available commands - -- [assign](./assign.md) -- [describe](./describe.md) -- [unassign](./unassign.md) - - -[← user](../index.md) \ No newline at end of file diff --git a/c7n/docs/user/tenants/unassign.md b/c7n/docs/user/tenants/unassign.md deleted file mode 100644 index b7fe3d6cd..000000000 --- a/c7n/docs/user/tenants/unassign.md +++ /dev/null @@ -1,31 +0,0 @@ -# unassign - -## Description - -Detaches tenants from a user - -## Synopsis - -```bash -c7n user tenants unassign - --username - [--all_tenants ] - [--tenant ] -``` - -## Options - -`--username` (text) - -User to update - -`--all_tenants` (boolean) - -Remove all tenants from user. This will allow the user to interact with all tenants within the customer. - -`--tenant` (text) - -Tenant names to unassign from user - - -[← tenants](./index.md) \ No newline at end of file diff --git a/c7n/pyproject.toml b/c7n/pyproject.toml new file mode 100644 index 000000000..4bd2137a0 --- /dev/null +++ b/c7n/pyproject.toml @@ -0,0 +1,37 @@ +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + + +[project] +name = "c7ncli" +description = "Rule engine CLI" +requires-python = ">=3.10" +readme = "README.md" +dynamic = ["version"] +dependencies = [ + "click==7.1.2", + "tabulate==0.9.0", + "boto3==1.26.80", + "python-dateutil==2.8.2", + "modular-cli-sdk[hvac]==2.0.0", +] + +[project.scripts] +c7n = "c7ncli.group.c7n:c7n" + +[tool.setuptools.dynamic] +version = {attr = "c7ncli.version.__version__"} + +[tool.setuptools.packages.find] +where = ["."] +include = ["c7ncli*"] +namespaces = false + +[tool.pyright] +include = ["c7ncli"] +exclude = [ + "**/__pycache__", +] +pythonVersion = "3.10" +reportIncompatibleMethodOverride = "warning" diff --git a/c7n/setup.py b/c7n/setup.py deleted file mode 100644 index 8ae9b8c5c..000000000 --- a/c7n/setup.py +++ /dev/null @@ -1,23 +0,0 @@ -from setuptools import find_packages, setup -from c7ncli.version import __version__ - - -setup( - name='c7ncli', - version=__version__, - packages=find_packages(), - include_package_data=True, - install_requires=[ - 'click==7.1.2', - 'tabulate==0.9.0', - 'requests==2.31.0', - 'boto3==1.26.80', - 'python-dateutil==2.8.2', - 'modular-cli-sdk[hvac]' - ], - entry_points=''' - [console_scripts] - c7n=c7ncli.group.c7n:c7n - ''', - python_requires='>=3.10' -) diff --git a/obfuscation_manager/README.md b/obfuscation_manager/README.md new file mode 100644 index 000000000..724f22f7f --- /dev/null +++ b/obfuscation_manager/README.md @@ -0,0 +1,55 @@ +## Custodian Service Obfuscation Manager + +Script allows to obfuscate and de-obfuscate data aggregated by Custodian Service while scanning infrastructure. +## Installation + +### Prerequisites +1. The following software must be installed in order to complete the guide: +* python3.8 or higher +* pip (installation guide: https://pip.pypa.io/en/stable/installation/) +* virtualenv (to install: `pip install virtualenv`) + +2. Create virtualenv using the command: +```bash +virtualenv -p python3 venv +``` +3. Activate newly created virtualenv with the command +* Linux/Mac: +```bash +source venv/bin/activate +``` +* Windows CMD: +```bash +venv/Scripts/activate.bat +``` +4. Install required modules withing the virtualenv: +* Linux/Mac/Windows CDM: +```bash +pip install . +``` + +5. Installation is done. + +## Obfuscation flow +1. Execute the following command: +```bash +c7nobf obfuscate --dump-directory "$custodian_dump_folder" --to "$target_folder" --dictionary-out "$obfuscation_dictionary.json" +``` +Where: +* $custodian_dump_folder - is the full path to the folder where Custodian Service dump is stored +* $target_folder - is the full path to the folder where the obfuscation result will be stored. The folder will be created if does not exist +* $obfuscation_dictionary.json - is the name of the file where the mapping of resource names to synthetic ids will be stored. Please keep it safe - it is impossible to de-obfuscate data without this file + +2. Obfuscation done. + +## Deobfuscation flow +1. Execute the following command: +```bash +c7nobf deobfuscate --dump-directory "$obfuscated_data_folder" --dictionary "$objuscation_dictionary.json" --to "$deobfuscated_data_folder" +``` +Where: +* $obfuscated_data_folder - is the full path to the folder where Custodian Service obfuscated data is stored +* $obfuscation_dictionary.json - is the name of the file where the mapping of resource names to synthetic ids will be stored +* $deobfuscated_data_folder - is the full path to the folder where the de-obfuscation result will be stored. The folder will be created if does not exist +**Note:** A value of parameter `--to` can be omitted. If it's omitted it will become the same as `--dump-directory` +2. Deobfuscation done. \ No newline at end of file diff --git a/obfuscation_manager/obfuscation_manager.py b/obfuscation_manager/obfuscation_manager.py new file mode 100644 index 000000000..a65d946c0 --- /dev/null +++ b/obfuscation_manager/obfuscation_manager.py @@ -0,0 +1,759 @@ +from abc import ABC, abstractmethod +import argparse +import csv +import email +import email.message +import email.policy +import gzip +import importlib +import itertools +import json +import logging +import logging.config +import os +from pathlib import Path +import subprocess +import sys +from typing import Generator, List, Optional, TYPE_CHECKING, Tuple, Union, cast +import uuid + +openpyxl = None +if TYPE_CHECKING: + import openpyxl + from openpyxl.worksheet.worksheet import Worksheet + + +__version__ = '1.0.0' + + +REPORT_FIELDS = {'id', 'name', 'arn', 'namespace'} + + +class TermColor: + ENDC = '\033[0m' + BOLD = '\033[1m' + FAIL = '\033[91m' + DEBUG = '\033[90m' + HEADER = '\033[95m' + OKBLUE = '\033[94m' + OKCYAN = '\033[96m' + OKGREEN = '\033[92m' + WARNING = '\033[93m' + UNDERLINE = '\033[4m' + BOLD_RED = '\x1b[31;1m' + + _pattern = '{color}{string}' + ENDC + + @classmethod + def blue(cls, st: str) -> str: + return cls._pattern.format(color=cls.OKBLUE, string=st) + + @classmethod + def cyan(cls, st: str) -> str: + return cls._pattern.format(color=cls.OKCYAN, string=st) + + @classmethod + def green(cls, st: str) -> str: + return cls._pattern.format(color=cls.OKGREEN, string=st) + + @classmethod + def yellow(cls, st: str) -> str: + return cls._pattern.format(color=cls.WARNING, string=st) + + @classmethod + def red(cls, st: str) -> str: + return cls._pattern.format(color=cls.FAIL, string=st) + + @classmethod + def gray(cls, st: str) -> str: + return cls._pattern.format(color=cls.DEBUG, string=st) + + @classmethod + def bold_red(cls, st: str) -> str: + return cls._pattern.format(color=cls.BOLD_RED, string=st) + + +class ColorFormatter(logging.Formatter): + formats = { + logging.DEBUG: TermColor.gray, + logging.INFO: TermColor.green, + logging.WARNING: TermColor.yellow, + logging.ERROR: TermColor.red, + logging.CRITICAL: TermColor.bold_red + } + + def format(self, record): + res = super().format(record) + return self.formats[record.levelno](res) + + +def get_logger(name: str, level=os.getenv('LOG_LEVEL', logging.DEBUG)): + log = logging.getLogger(name) + log.setLevel(level) + handler = logging.StreamHandler() + handler.setFormatter(ColorFormatter('%(levelname)s - %(message)s')) + log.addHandler(handler) + return log + + +_LOG = get_logger(__name__) + + +NoneType = type(None) +Leaf = Union[str, int, bool, NoneType] +JsonContainer = Union[list, dict] +Json = Union[Leaf, JsonContainer] + + +def iter_values(finding: Json) -> Generator[Leaf, Leaf, Json]: + """ + Yields values from the given finding with an ability to send back + the desired values. I proudly think this is cool, because we can put + keys replacement login outside of this generator + >>> gen = iter_values({'1':'q', '2': ['w', 'e'], '3': {'4': 'r'}}) + >>> next(gen) + q + >>> gen.send('instead of q') + w + >>> gen.send('instead of w') + e + >>> gen.send('instead of e') + r + >>> gen.send('instead of r') + After the last command StopIteration will be raised, and it + will contain the changed finding. The given finding will be changed + in-place + :param finding: + :return: + """ + if isinstance(finding, (str, int, bool, NoneType)): + new = yield finding + return new + if isinstance(finding, dict): + for k, v in finding.items(): + finding[k] = yield from iter_values(v) + return finding + if isinstance(finding, list): + for i, v in enumerate(finding): + finding[i] = yield from iter_values(v) + return finding + + +def flip_dict(d: dict) -> None: + """ + In place + :param d: + :return: + """ + for k in tuple(d.keys()): + d[d.pop(k)] = k + + +def keep_only(d: dict, keys: set) -> None: + if not keys: + return + for k in tuple(d.keys()): + if k not in keys: + d.pop(k) + + +def query_yes_no(question: str, default: str = "yes") -> bool: + """Ask a yes/no question via raw_input() and return their answer. + + "question" is a string that is presented to the user. + "default" is the presumed answer if the user just hits . + It must be "yes" (the default), "no" or None (meaning + an answer is required of the user). + + The "answer" return value is True for "yes" or False for "no". + """ + valid = {"yes": True, "y": True, "ye": True, "no": False, "n": False} + if default is None: + prompt = " [y/n] " + elif default == "yes": + prompt = " [Y/n] " + elif default == "no": + prompt = " [y/N] " + else: + raise ValueError("invalid default answer: '%s'" % default) + + while True: + sys.stdout.write(question + prompt) + choice = input().lower() + if default is not None and choice == "": + return valid[default] + elif choice in valid: + return valid[choice] + else: + sys.stdout.write( + "Please respond with 'yes' or 'no' " "(or 'y' or 'n').\n") + + +def import_openpyxl() -> None: + global openpyxl + if openpyxl: + return + _LOG.info('Going to import openpyxl') + try: + openpyxl = importlib.import_module('openpyxl') + except ImportError: + if not query_yes_no('Required requirement openpyxl is not found. ' + 'Want to install?'): + _LOG.error('Aborting...') + sys.exit(1) + subprocess.run(['pip', 'install', 'openpyxl']) + openpyxl = importlib.import_module('openpyxl') + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + description='Obfuscation script cli enter-point' + ) + # -- top level sub-parser + sub_parsers = parser.add_subparsers(dest='action', required=True, + help='Available actions') + obfuscate_parser = sub_parsers.add_parser( + 'obfuscate', help='Obfuscates an existing dump' + ) + obfuscate_parser.add_argument( + '--dump-directory', default='custodian_dump', type=Path, + help='Path to the folder where custodian dump is sited ' + '(default: %(default)s)' + ) + obfuscate_parser.add_argument( + '--to', required=False, type=Path, + help='Path the the patched data must be placed. If not specified, ' + 'pathed data will override an existing dump' + ) + obfuscate_parser.add_argument( + '--keep-all-fields', action='store_true', + help='If specified, all the fields will be kept. By default, ' + 'only ID fields are kept' + ) + obfuscate_parser.add_argument( + '--dictionary-out', default='dictionary_out.json', type=Path, + help='Path where obfuscated keys and their IDs will be places ' + '(default: %(default)s)' + ) + obfuscate_parser.add_argument( + '--dictionary', dest='dictionary_path', required=False, type=Path, + help='Optional dict, in case you want to give some specific ' + 'aliases for concrete names in resources. ' + 'Path to file which contains JSON with key-value pairs, ' + 'where key is real value of some attribute in resources ' + f'and value is a name you want to replace the real value with. ' + f'(default: %(default)s)' + ) + deobfuscate_parser = sub_parsers.add_parser( + 'deobfuscate', help='De-obfuscates an existing dump' + ) + deobfuscate_parser.add_argument( + '--dump-directory', default='custodian_dump', type=Path, + help='Path to the folder where the obfuscated custodian dump is ' + 'sited. Can be also a path to concrete file ' + '(default: %(default)s)' + ) + deobfuscate_parser.add_argument( + '--to', required=False, type=Path, + help='Path where the de-obfuscated data must be placed. ' + 'If not specified, pathed data will override an existing dump' + ) + deobfuscate_parser.add_argument( + '--dictionary', required=True, nargs='+', type=Path, + help='Path to a file where obfuscated keys and their IDs are' + ) + return parser + + +class ActionHandler(ABC): + @abstractmethod + def __call__(self, **kwargs): + ... + + +class BaseHandler(ActionHandler): + """ + Contains some common methods + """ + + def __call__(self, *args, **kwargs): + raise NotImplementedError('BaseHandler must not be used directly') + + @staticmethod + def is_gzipped_json(name: Path) -> bool: + return name.suffixes == ['.json', '.gz'] + + @staticmethod + def is_json(name: Path) -> bool: + return name.suffixes == ['.json'] + + @staticmethod + def is_xlsx(name: Path) -> bool: + return name.suffixes == ['.xlsx'] + + @staticmethod + def is_csv(name: Path) -> bool: + return name.suffixes == ['.csv'] + + @staticmethod + def load_json(path: Path, gzipped: Optional[bool] = None + ) -> Optional[Json]: + if not isinstance(gzipped, bool): + gzipped = BaseHandler.is_gzipped_json(path) + try: + with open(path, 'rb') as fp: + data = fp.read() + if gzipped: + data = gzip.decompress(data) + return json.loads(data) + except Exception as e: + _LOG.warning( + f'Unexpected error occurred trying to load {path}: {e}. ' + f'Skipping...' + ) + return + + @staticmethod + def dump_json(to: Path, data: Json, gzipped: Optional[bool] = None): + if not isinstance(gzipped, bool): + gzipped = BaseHandler.is_gzipped_json(to) + to.parent.mkdir(parents=True, exist_ok=True) + with open(to, 'wb') as fp: + buf = json.dumps(data, separators=(',', ':')).encode() + if gzipped: + buf = gzip.compress(buf) + fp.write(buf) + + @staticmethod + def yield_files(root: Path) -> Generator[Tuple[Path, Path], None, None]: + """ + Iterates over all the files in root and yield each file full + path and path relative to root + :param root: (full, relative) + :return: + """ + if root.is_file(): + yield root.resolve(), root.relative_to(root.parent) + return + for base, _, files in os.walk(root): + for file in files: + _path = Path(base, file) + yield _path, _path.relative_to(root) + + +class ObfuscateDump(BaseHandler): + + @staticmethod + def is_findings(dct) -> bool: + """ + Is 'findings' format of report + :param dct: + :return: + """ + if not isinstance(dct, dict): + return False + if not list(dct.keys())[0].startswith('ecc-'): + return False + return True + + @staticmethod + def is_list_of_resources(lst) -> bool: + """ + Is just list of resources + :param lst: + :return: + """ + if not isinstance(lst, list): + return False + if len(lst) == 0: + return True + if not isinstance(lst[0], dict): + return False + return True + + @staticmethod + def is_list_of_shard_parts(lst) -> bool: + if not isinstance(lst, list): + return False + if len(lst) == 0: + return True + dct = lst[0] + if not isinstance(dct, dict): + return False + return 'p' in dct and 'l' in dct and 't' in dct and 'r' in dct + + @staticmethod + def obfuscate_finding(finding: dict, dictionary: dict, + dictionary_out: dict) -> None: + """ + Main business logic + :param finding: + :param dictionary: + :param dictionary_out: + :return: + """ + gen = iter_values(finding) + try: + real = next(gen) + gen_id = uuid.uuid4 + while True: + alias = dictionary_out.setdefault( + real, dictionary.get(real) or str(gen_id()) + ) + _LOG.debug(f'"{str(real)[:3]}***" will be ' + f'replaced with {alias}') + real = gen.send(alias) + except StopIteration: + pass + + def patch_findings(self, findings: dict, all_fields: bool, + dictionary: dict, + dictionary_out: dict): + """ + In place + :param findings: + :param all_fields: + :param dictionary: + :param dictionary_out: + :return: + """ + for data in findings.values(): + for resources in data['resources'].values(): + for resource in resources: + if not all_fields: + keep_only(resource, REPORT_FIELDS) + self.obfuscate_finding( + resource, + dictionary, + dictionary_out + ) + + def patch_list_of_resources(self, findings: list, all_fields: bool, + dictionary: dict, dictionary_out: dict): + for resource in findings: + if not all_fields: + keep_only(resource, REPORT_FIELDS) + self.obfuscate_finding(resource, dictionary, dictionary_out) + + def patch_list_of_shard_parts(self, findings: list, all_fields: bool, + dictionary: dict, dictionary_out: dict): + for part in findings: + for resource in part.setdefault('r', []): + if not all_fields: + keep_only(resource, REPORT_FIELDS) + self.obfuscate_finding(resource, dictionary, dictionary_out) + + def yield_jsons(self, root: Path + ) -> Generator[Tuple[Path, Json], None, None]: + """ + Yields tuples where the first element is file path relative to root, + the second element - loaded file content. + Loads only JSON and gzipped JSON. Skips the file if it cannot be + loaded or not json + :param root: + :return: + """ + for full, relative in self.yield_files(root): + gz = self.is_gzipped_json(full) + js = self.is_json(full) + if not (gz or js): + _LOG.info(f'Skipping: {relative} - not json') + continue + data = self.load_json(full) + if not data: + continue + yield relative, data + + def __call__(self, dump_directory: Path, to: Optional[Path], + keep_all_fields: bool, dictionary_out: Path, + dictionary_path: Optional[Path]): + # output dump directory validation + if not to: + if query_yes_no('Parameter --to was not provided. Patched files ' + 'will override the dump'): + to = dump_directory + else: + _LOG.error('Aborting') + sys.exit(1) + _LOG.info(f'Obfuscated dump will be places to: "{to}"') + + # Loading desired values dictionary + if dictionary_path: + _LOG.info('Loading dictionary') + try: + with open(dictionary_path, 'r') as file: + dictionary = json.load(file) + if not isinstance(dictionary, dict): + raise ValueError('The content must be a dict') + except Exception as e: + _LOG.error(f'Could not load {dictionary_path}: {e}') + sys.exit(1) + else: + _LOG.info('Dictionary was not provided. All the ' + 'aliases will be randomly generated') + dictionary = {} + + # Logging whether all the fields will be kept + if keep_all_fields: + _LOG.warning('All the fields will be kept') + else: + _LOG.info('Only id fields will be kept') + + # obfuscating + out = {} # here we will put real names to our randomly generated + for path, content in self.yield_jsons(dump_directory): + if self.is_findings(content): + _LOG.info(f'Findings found by path: {path}. Pathing') + content = cast(dict, content) # is_findings ensures it's dict + self.patch_findings(content, keep_all_fields, dictionary, out) + elif self.is_list_of_shard_parts(content): + _LOG.info(f'List of shard parts found by path: {path}. ' + f'Pathing') + content = cast(list, content) + self.patch_list_of_shard_parts(content, keep_all_fields, + dictionary, out) + elif self.is_list_of_resources(content): + _LOG.info(f'List of resources found by path: {path}. Pathing') + content = cast(list, content) + self.patch_list_of_resources(content, keep_all_fields, + dictionary, out) + else: + _LOG.warning(f'Unknown file format: {path}. Skipping') + self.dump_json(to / path, content) + + # dumping output dict + _LOG.info(f'Output dictionary will be dumped to {dictionary_out}') + dictionary_out.parent.mkdir(parents=True, exist_ok=True) + flip_dict(out) + with open(dictionary_out, 'w') as file: + json.dump(out, file, indent=2) + _LOG.info('Finished!') + + +class Deobfuscator(ABC): + """ + >>> Deobfuscator().deobfuscate(Path('here')).to(Path('there')).using({}) + """ + _what: Union[Path, None] = None + _to: Union[Path, None] = None + + def __init__(self, *args, **kwargs): + pass + + def deobfuscate(self, what: Path) -> 'Deobfuscator': + self._what = what + return self + + def to(self, to: Path) -> 'Deobfuscator': + self._to = to + return self + + def using(self, dictionary: dict) -> None: + assert self._what and self._to, 'Invalid calls chain' + self._make_it(self._what, self._to, dictionary) + + @staticmethod + def _deobfuscate_str(item: str, dictionary: dict) -> str: + for k, v in dictionary.items(): + # todo think another more efficient way + item = item.replace(k, str(v)) + return item + + @staticmethod + def _deobfuscate_finding(finding: Json, dictionary: dict) -> None: + """ + Deobfuscates one json item. + :param finding: + :param dictionary: + :return: + """ + gen = iter_values(finding) + try: + alias = next(gen) + while True: + if alias not in dictionary: + _LOG.warning(f'{alias} will not be replaced because ' + f'there is not corresponding value in ' + f'the dictionary') + alias = gen.send(alias) + continue + + real = dictionary[alias] + _LOG.debug(f'{alias} will be ' + f'replaced with "{str(real)[:3]}***"') + alias = gen.send(real) + except StopIteration: + pass + + def _deobfuscate_maybe_json(self, item: str, dictionary: dict) -> str: + try: + data = json.loads(item) + self._deobfuscate_finding(data, dictionary) + return json.dumps(data, separators=(',', ':')) + except json.JSONDecodeError: + return self._deobfuscate_str(item, dictionary) + + def _deobfuscate_line(self, ln: List[str], dictionary: dict) -> list: + """ + Returns the same object as received + """ + for i, item in enumerate(ln): + ln[i] = self._deobfuscate_maybe_json(item, dictionary) + return ln + + @abstractmethod + def _make_it(self, what: Path, to: Path, dictionary: dict): + """ + Should create a deobfuscated file + :param what: + :param to: + :param dictionary: + :return: + """ + + +class JsonDeobfuscator(Deobfuscator): + def _make_it(self, what: Path, to: Path, dictionary: dict): + data = BaseHandler.load_json(what) + self._deobfuscate_finding(data, dictionary) + BaseHandler.dump_json(to, data) + + +class XlsxDeobfuscator(Deobfuscator): + def _deobfuscate_worksheet(self, wsh: 'Worksheet', dictionary: dict): + for row in wsh.rows: + for cell in row: + if cell.value is None: # isinstance(cell, MergedCell) + continue # MergedCell + if not isinstance(cell.value, str): + continue + cell.value = self._deobfuscate_maybe_json(cell.value, + dictionary) + + def _make_it(self, what: Path, to: Path, dictionary: dict): + wb = openpyxl.load_workbook(what) + for wsh in wb: + _LOG.debug(f'Deobfuscating {wsh.title} worksheet') + self._deobfuscate_worksheet(wsh, dictionary) + wb.save(to) + + +class CsvDeobfuscator(Deobfuscator): + def _make_it(self, what: Path, to: Path, dictionary: dict): + f1 = open(what, 'r', newline='') + f2 = open(to, 'w', newline='') + reader = csv.reader(f1) + writer = csv.writer(f2, dialect=reader.dialect) + writer.writerows( + map(self._deobfuscate_line, reader, itertools.repeat(dictionary)) + ) + f1.close() + f2.close() + + +class EmlDeobfuscator(Deobfuscator): + def _deobfuscate_text_part(self, part: email.message.EmailMessage, + dictionary: dict): + charset = part.get_content_charset() + content = self._deobfuscate_maybe_json(part.get_content(), dictionary) + part.set_content( + content.encode(), + maintype=part.get_content_maintype(), + subtype=part.get_content_subtype(), + cte=part.get('Content-Transfer-Encoding'), + disposition=part.get_content_disposition(), + filename=part.get_filename(), + cid=part.get('Content-ID'), + ) + part.set_charset(charset) + + def _make_it(self, what: Path, to: Path, dictionary: dict): + with open(what, 'rb') as file: + msg = cast( + email.message.EmailMessage, + email.message_from_binary_file(file, + policy=email.policy.default) + ) + for part in msg.walk(): + # multipart/* are just containers + if part.get_content_maintype() == 'multipart': + continue + ct = part.get_content_type() + if ct in ('text/plain', 'text/html', 'text/csv', 'application/json'): + self._deobfuscate_text_part(part, dictionary) + + with open(to, 'wb') as fp: + fp.write(msg.as_bytes()) + + +class DeobfuscatorFactory: + def __init__(self, path: Path): + self._path = path + + @classmethod + def path(cls, path: Path): + return cls(path) + + def build(self, *args, **kwargs) -> Union[Deobfuscator, None]: + suffixes = self._path.suffixes + if suffixes == ['.xlsx']: + import_openpyxl() + return XlsxDeobfuscator(*args, **kwargs) + elif suffixes == ['.csv']: + return CsvDeobfuscator(*args, **kwargs) + elif suffixes == ['.json', '.gz'] or suffixes == ['.json']: + return JsonDeobfuscator(*args, **kwargs) + elif suffixes == ['.eml'] or suffixes == ['.emltpl']: + return EmlDeobfuscator(*args, **kwargs) + return + + +class DeObfuscateDump(BaseHandler): + + def __call__(self, dump_directory: Path, to: Optional[Path], + dictionary: List[Path]): + if not to: + if query_yes_no('Parameter --to was not provided. ' + 'De-obfuscated files will override the dump'): + to = dump_directory + else: + _LOG.error('Aborting') + sys.exit(1) + if dump_directory.is_dir() and to.is_file(): + _LOG.error('If --dump-directory is a directory --to must ' + 'be a directory as well') + sys.exit(1) + if dump_directory.is_file(): + to.parent.mkdir(parents=True, exist_ok=True) + to.touch() + _LOG.info('Loading dictionary') + dct = {} + for i in dictionary: + try: + with open(i, 'r') as file: + dct.update(json.load(file)) + except Exception as e: + _LOG.error(f'Could not load {i}: {e}') + + for full, relative in self.yield_files(dump_directory): + _LOG.info(f'De-obfuscating {full}') + _path = to if to.is_file() else to / relative + _path.parent.mkdir(parents=True, exist_ok=True) + deobfuscator = DeobfuscatorFactory(relative).build() + if not deobfuscator: + _LOG.warning(f'Not supported file type: {relative}') + continue + deobfuscator.deobfuscate(full).to(_path).using(dct) + _LOG.info('Done!') + + +def main(): + arguments = build_parser().parse_args() + mapping = {'obfuscate': ObfuscateDump(), 'deobfuscate': DeObfuscateDump()} + func = mapping[arguments.action] + delattr(arguments, 'action') + func(**vars(arguments)) + + +if __name__ == '__main__': + main() diff --git a/obfuscation_manager/pyproject.toml b/obfuscation_manager/pyproject.toml new file mode 100644 index 000000000..23751ade1 --- /dev/null +++ b/obfuscation_manager/pyproject.toml @@ -0,0 +1,36 @@ +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + + +[project] +name = "rule-engine-obfuscation-manager" +description = "Cli to deobfuscate data" +dynamic = ["version"] +requires-python = ">=3.8" +readme = "README.md" +dependencies = [] + +[project.optional-dependencies] +xlsx = ["openpyxl==3.0.10"] + + +[project.scripts] +sre-obfuscator = "obfuscation_manager:main" + + +[tool.setuptools.dynamic] +version = {attr = "obfuscation_manager.__version__"} + + +[tool.setuptools] +py-modules = ["obfuscation_manager"] + + +[tool.pyright] +include = ["obfuscation_manager.py"] +exclude = [ + "**/__pycache__", +] +pythonVersion = "3.8" +reportIncompatibleMethodOverride = "warning" diff --git a/src/.env.example b/src/.env.example index 6ec4de319..a0f880e2d 100644 --- a/src/.env.example +++ b/src/.env.example @@ -1,67 +1,40 @@ -# Custodian Service exclusively on-prem envs -# name=value -# [syndicate alias name] -# if alias name is not specified, it's the same as env name. +CAAS_SERVICE_MODE=docker +CAAS_TESTING=false -_service_mode=docker # saas | docker -_db_name=custodian_as_a_service +CAAS_RULESETS_BUCKET_NAME=custodian-rulesets +CAAS_REPORTS_BUCKET_NAME=custodian-reports +CAAS_METRICS_BUCKET_NAME=custodian-metrics +CAAS_STATISTICS_BUCKET_NAME=custodian-statistics +CAAS_RECOMMENDATIONS_BUCKET_NAME=custodian-recommendations -SERVICE_MODE=${_service_mode} +CAAS_BATCH_JOB_LOG_LEVEL=DEBUG -# for saas these bucket names must be the same as ones -# from lambda envs -reports_bucket_name=custodian-reports -caas_rulesets_bucket=custodian-rulesets -caas_ssm_backup_bucket=custodian-ssm-backup -stats_s3_bucket_name=custodian-statistics -templates_s3_bucket_name=custodian-templates -caas_metrics_bucket_name=custodian-metrics -# +CAAS_SKIP_CLOUD_IDENTIFIER_VALIDATION=false +CAAS_ALLOW_SIMULTANEOUS_JOBS_FOR_ONE_TENANT=false -# -last_scan_threshold=0 -feature_skip_cloud_identifier_validation=false -batch_job_log_level=DEBUG -feature_allow_only_temp_aws_credentials=false -# +CAAS_INNER_CACHE_TTL_SECONDS=0 # -MONGO_DATABASE=${_db_name} -MONGO_USER= -MONGO_PASSWORD= -MONGO_URL= # host:port -VAULT_URL= # host -VAULT_SERVICE_SERVICE_PORT= # port -VAULT_TOKEN= -MINIO_HOST= # host -MINIO_PORT= # port -MINIO_ACCESS_KEY= -MINIO_SECRET_ACCESS_KEY= -INNER_CACHE_TTL_SECONDS=0 -# +CAAS_MINIO_ENDPOINT= +CAAS_MINIO_ACCESS_KEY_ID= +CAAS_MINIO_SECRET_ACCESS_KEY= + +CAAS_VAULT_ENDPOINT= +CAAS_VAULT_TOKEN= -# -# EXECUTOR_PATH: /custodian-as-a-service/executor/executor.py -# VENV_PATH: /custodian-as-a-service/executor/.executor_venv/bin/python -# +CAAS_MONGO_URI= +CAAS_MONGO_DATABASE= # -modular_service_mode=${_service_mode} -modular_mongo_db_name=${_db_name} +modular_service_mode=docker +modular_mongo_db_name=custodian-as-a-service modular_mongo_user= modular_mongo_password= modular_mongo_url= # host:port -application_name=caas -component_name=custodian_service -# -#------------------------------------------------------------------------------- -# The following commented envs are necessary only for saas -# batch_job_def_name=caas-job-definition # reports-submit-job-definition -# batch_job_queue_name=caas-job-queue # reports-submit-job-queue -# event_bridge_service_role_to_invoke_batch= # event-bridge-service-role-to-invoke-batch -# lambdas_alias_name=dev - -# modular_assume_role_arn= -# MODULAR_AWS_REGION= +VAULT_TOKEN= +VAULT_URL= +VAULT_SERVICE_SERVICE_PORT= +component_name=custodian_service +application_name=caas \ No newline at end of file diff --git a/src/connections/auth_extension/base_auth_client.py b/src/connections/auth_extension/base_auth_client.py deleted file mode 100644 index 73b605546..000000000 --- a/src/connections/auth_extension/base_auth_client.py +++ /dev/null @@ -1,84 +0,0 @@ -from abc import ABC, abstractmethod -from typing import Union - - -class BaseAuthClient(ABC): - @abstractmethod - def admin_initiate_auth(self, username: str, password: str) -> dict: - ... - - @abstractmethod - def admin_delete_user(self, username: str): - ... - - @abstractmethod - def respond_to_auth_challenge(self, challenge_name: str): - ... - - @abstractmethod - def sign_up(self, username, password, customer, role, tenants=None): - ... - - @abstractmethod - def set_password(self, username, password, permanent=True): - ... - - @abstractmethod - def is_user_exists(self, username: str) -> bool: - ... - - @abstractmethod - def get_user_role(self, username: str): - ... - - @abstractmethod - def get_user_customer(self, username: str): - ... - - @abstractmethod - def get_user_tenants(self, username: str): - ... - - @abstractmethod - def update_role(self, username: str, role: str): - ... - - @abstractmethod - def get_user_latest_login(self, username: str): - ... - - @abstractmethod - def update_latest_login(self, username: str): - ... - - @abstractmethod - def update_customer(self, username: str, customer: str): - ... - - @abstractmethod - def update_tenants(self, username: str, tenants: Union[str, list]): - ... - - @abstractmethod - def delete_role(self, username: str): - ... - - @abstractmethod - def delete_customer(self, username: str): - ... - - @abstractmethod - def delete_tenants(self, username: str): - ... - - @abstractmethod - def is_system_user_exists(self) -> bool: - ... - - @abstractmethod - def get_system_user(self): - ... - - @abstractmethod - def get_customers_latest_logins(self, customers=None): - ... diff --git a/src/connections/auth_extension/cognito_to_jwt_adapter.py b/src/connections/auth_extension/cognito_to_jwt_adapter.py deleted file mode 100644 index f35c28477..000000000 --- a/src/connections/auth_extension/cognito_to_jwt_adapter.py +++ /dev/null @@ -1,244 +0,0 @@ -import json -import time -from http import HTTPStatus -from typing import Union, Optional, Any, TypedDict - -import bcrypt -import jwt - -from connections.auth_extension.base_auth_client import BaseAuthClient -from helpers import CustodianException -from helpers.constants import EXP_ATTR, COGNITO_USERNAME, CUSTOM_ROLE_ATTR, \ - CUSTOM_TENANTS_ATTR, CUSTOM_CUSTOMER_ATTR, CUSTOM_LATEST_LOGIN_ATTR -from helpers.log_helper import get_logger -from helpers.system_customer import SYSTEM_CUSTOMER -from helpers.time_helper import utc_iso, utc_datetime -from models.user import User -from services.ssm_service import SSMService - -_LOG = get_logger(__name__) - -AUTH_TOKEN_NAME = 'token' -EXPIRATION_IN_MINUTES = 60 - -USER_NOT_FOUND_MESSAGE = 'No user with username {username} was found' -WRONG_USER_CREDENTIALS_MESSAGE = 'Incorrect username and/or password' -TOKEN_EXPIRED_MESSAGE = 'The incoming token has expired' -UNAUTHORIZED_MESSAGE = 'Unauthorized' - - -class MongoAndSSMAuthClient(BaseAuthClient): - class JwtSecret(TypedDict): - phrase: str - - def __init__(self, ssm_service: SSMService): - self._ssm = ssm_service - - def _get_jwt_secret(self) -> JwtSecret: - jwt_secret = self._ssm.get_secret_value(AUTH_TOKEN_NAME) - - if not jwt_secret: - _LOG.error('Can not find jwt-secret') - raise CustodianException( - code=HTTPStatus.UNAUTHORIZED, - content=WRONG_USER_CREDENTIALS_MESSAGE) - if isinstance(jwt_secret, dict): - return jwt_secret - # isinstance(jwt_secret, str): - try: - return json.loads(jwt_secret) - except json.JSONDecodeError: - _LOG.error('Invalid jwt-secret format') - raise CustodianException( - code=HTTPStatus.UNAUTHORIZED, - content=WRONG_USER_CREDENTIALS_MESSAGE) - - def decode_token(self, token: str) -> dict: - jwt_secret = self._get_jwt_secret() - try: - return jwt.decode( - jwt=token, - key=jwt_secret['phrase'], - algorithms=['HS256'] - ) - except jwt.exceptions.ExpiredSignatureError: - raise CustodianException( - code=HTTPStatus.UNAUTHORIZED, - content=TOKEN_EXPIRED_MESSAGE, - ) - except jwt.exceptions.PyJWTError: - raise CustodianException( - code=HTTPStatus.UNAUTHORIZED, - content=UNAUTHORIZED_MESSAGE - ) - - def admin_initiate_auth(self, username: str, password: str) -> dict: - # todo implement refresh token - - user_item = User.get_nullable(hash_key=username) - if not user_item or bcrypt.hashpw( - password.encode(), user_item.password) != user_item.password: - _LOG.error(WRONG_USER_CREDENTIALS_MESSAGE) - raise CustodianException( - code=HTTPStatus.UNAUTHORIZED, - content=WRONG_USER_CREDENTIALS_MESSAGE) - - jwt_secret = self._get_jwt_secret() - self.update_latest_login(username) - encoded_jwt = jwt.encode( - payload={ - COGNITO_USERNAME: username, - CUSTOM_CUSTOMER_ATTR: user_item.customer, - CUSTOM_TENANTS_ATTR: user_item.tenants or '', - CUSTOM_ROLE_ATTR: user_item.role, - CUSTOM_LATEST_LOGIN_ATTR: user_item.latest_login, - EXP_ATTR: round(time.time()) + EXPIRATION_IN_MINUTES * 60 - }, - key=jwt_secret['phrase'], - algorithm='HS256' - ) - return { - 'AuthenticationResult': { - 'IdToken': encoded_jwt, 'RefreshToken': [] - } - } - - @staticmethod - def _set_password(user: User, password: str): - user.password = bcrypt.hashpw(password.encode(), bcrypt.gensalt()) - - def admin_delete_user(self, username: str): - User(hash_key=username).delete() - - def respond_to_auth_challenge(self, challenge_name: str): - pass - - def sign_up(self, username, password, customer, role, tenants=None): - user = User() - user.user_id = username - self._set_password(user, password) - user.customer = customer - user.role = role - user.tenants = tenants - User.save(user) - - @staticmethod - def _get_user(username: str) -> Optional[User]: - return User.get_nullable(hash_key=username) - - def _get_user_attr(self, username: str, attr: str) -> Any: - return getattr(self._get_user(username), attr, None) - - def is_user_exists(self, username: str) -> bool: - return bool(self._get_user(username)) - - def get_user_role(self, username: str): - return self._get_user_attr(username, 'role') - - def get_user_customer(self, username: str): - return self._get_user_attr(username, 'customer') - - def get_user_latest_login(self, username: str): - return self._get_user_attr(username, 'latest_login') - - def update_role(self, username: str, role: str): - user = self._get_user(username) - if not user: - _LOG.warning(USER_NOT_FOUND_MESSAGE.format(username=username)) - return - user.role = role - user.save() - - def update_customer(self, username: str, customer: str): - user = self._get_user(username) - if not user: - _LOG.warning(USER_NOT_FOUND_MESSAGE.format(username=username)) - return - user.customer = customer - user.save() - - def update_latest_login(self, username: str): - user = self._get_user(username) - if not user: - _LOG.warning(USER_NOT_FOUND_MESSAGE.format(username=username)) - return - user.latest_login = utc_iso() - user.save() - - def delete_role(self, username: str): - user = self._get_user(username) - if not user: - _LOG.warning(USER_NOT_FOUND_MESSAGE.format(username=username)) - return - user.role = None - user.save() - - def delete_customer(self, username: str): - user = self._get_user(username) - if not user: - _LOG.warning(USER_NOT_FOUND_MESSAGE.format(username=username)) - return - user.customer = None - user.save() - - def set_password(self, username: str, password: str, - permanent: bool = True): - user = User.get_nullable(hash_key=username) - self._set_password(user, password) - user.save() - - def is_system_user_exists(self) -> bool: - """Checks whether user with customer=SYSTEM_CUSTOMER already exists""" - scan_results = list(User.scan(User.customer == SYSTEM_CUSTOMER)) - if not scan_results: - return False - number_of_system_users = len(scan_results) - if number_of_system_users == 0: - return False - elif number_of_system_users == 1: - return True - elif number_of_system_users > 1: - raise AssertionError("Only one SYSTEM user must be!") - - def get_system_user(self): - users = User.scan(User.customer == SYSTEM_CUSTOMER) - try: - return next(users).user_id - except StopIteration: - _LOG.info("No SYSTEM user was found") - return None - - def get_customers_latest_logins(self, customers=None): - result = {} - users = list(User.scan()) - for user in users: - _customer, _ll = user.customer, user.latest_login - if not result.get(_customer): - result[_customer] = user.latest_login - elif _ll and utc_datetime(_from=result[_customer]) < \ - utc_datetime(_from=_ll): - result[_customer] = _ll - if customers: - result = {k: v for k, v in result.items() if k in customers} - return result - - def get_user_tenants(self, username: str): - return self._get_user_attr(username, 'tenants') or '' - - def update_tenants(self, username: str, tenants: Union[str, list]): - if isinstance(tenants, list): - tenants = ','.join(tenants) - user = self._get_user(username) - if not user: - _LOG.warning(USER_NOT_FOUND_MESSAGE.format(username=username)) - return - user.tenants = tenants - user.save() - - def delete_tenants(self, username: str): - user = self._get_user(username) - if not user: - _LOG.warning(USER_NOT_FOUND_MESSAGE.format(username=username)) - return - user.tenants = None - user.save() diff --git a/src/connections/batch_extension/base_job_client.py b/src/connections/batch_extension/base_job_client.py deleted file mode 100644 index a7689288d..000000000 --- a/src/connections/batch_extension/base_job_client.py +++ /dev/null @@ -1,76 +0,0 @@ -import os - -from services.clients.batch import BatchClient -from services.clients.sts import StsClient -from services.environment_service import EnvironmentService -from helpers.constants import ENV_SERVICE_MODE, DOCKER_SERVICE_MODE, \ - SAAS_SERVICE_MODE, ENV_MAX_NUMBER_OF_JOBS_ON_PREM -from helpers.log_helper import get_logger - -_LOG = get_logger(__name__) - -SERVICE_MODE = os.getenv(ENV_SERVICE_MODE) or SAAS_SERVICE_MODE -MAX_NUMBER_OF_JOBS = int(os.environ.get(ENV_MAX_NUMBER_OF_JOBS_ON_PREM, 4)) - - -def subprocess_handler_builder(): - handler = None - - def init_handler(): - assert SERVICE_MODE == DOCKER_SERVICE_MODE, \ - "You can init subprocess handler only if SERVICE_MODE=docker" - nonlocal handler - if handler: - return handler - from connections.batch_extension.batch_to_subprocess_adapter import \ - BatchToSubprocessAdapter - handler = BatchToSubprocessAdapter( - max_number_of_jobs=MAX_NUMBER_OF_JOBS) - _LOG.info('Subprocess connection was successfully initialized') - return handler - return init_handler - - -SUBPROCESS_HANDLER = subprocess_handler_builder() - - -class BaseBatchClient(BatchClient): - - def __init__(self, environment_service: EnvironmentService, - sts_client: StsClient): - super().__init__(environment_service=environment_service, - sts_client=sts_client) - - def submit_job(self, job_name: str, job_queue: str, job_definition: str, - command: str, size: int = None, depends_on: list = None, - parameters=None, retry_strategy: int = None, - timeout: int = None, environment_variables: dict = None): - if self._environment.is_docker(): - return SUBPROCESS_HANDLER().submit_job( - job_name=job_name, - command=command, - environment_variables=environment_variables - ) - return super().submit_job( - job_name=job_name, - job_queue=job_queue, - job_definition=job_definition, - command=command, - size=size, - depends_on=depends_on, - parameters=parameters, - retry_strategy=retry_strategy, - timeout=timeout, - environment_variables=environment_variables - ) - - def terminate_job(self, job_id: str, reason: str = 'Terminating job.'): - if self._environment.is_docker(): - return SUBPROCESS_HANDLER().terminate_job(job_id=job_id, - reason=reason) - return super().terminate_job(job_id=job_id, reason=reason) - - def describe_jobs(self, jobs): - if self._environment.is_docker(): - return SUBPROCESS_HANDLER().describe_jobs(jobs) - return super().describe_jobs(jobs) diff --git a/src/connections/batch_extension/batch_to_subprocess_adapter.py b/src/connections/batch_extension/batch_to_subprocess_adapter.py deleted file mode 100644 index c7cbf58ca..000000000 --- a/src/connections/batch_extension/batch_to_subprocess_adapter.py +++ /dev/null @@ -1,125 +0,0 @@ -import os -import pathlib -import subprocess -import sys - -import psutil - -from helpers import generate_id, build_response -from helpers.constants import BATCH_ENV_SUBMITTED_AT, BATCH_ENV_JOB_ID -from helpers.log_helper import get_logger -from helpers.time_helper import utc_iso -from models.job import Job - -_LOG = get_logger(__name__) -file_path = pathlib.Path(__file__).parent.resolve() - -src = file_path.parent.parent -root = src.parent -docker_path = root / 'executor' -executor_path = docker_path / 'executor.py' - -EXECUTABLE_VENV_FOLDER_PRIORITY = ['.executor_venv', 'venv'] - - -class BatchToSubprocessAdapter: - """ - Before using this class, please set up next env vars: - VENV_PATH - path to python.exe in venv folder - EXECUTOR_PATH - path to executor.py to run jobs - set max_number_of_jobs in aliases - job_id - will be unique id of the job - pid - id of process that is now running job - """ - - def __init__(self, max_number_of_jobs): - self._processes = {} - self.max_jobs_count = max_number_of_jobs - - @property - def processes(self): - _temp = self._processes.copy() - for job_id, pid in _temp.items(): - if not self.check_if_process_is_running(pid): - self._processes.pop(job_id) - return self._processes - - @staticmethod - def resolve_executor_path() -> str: - """ - Priorities: - 1. EXECUTOR_PATH env - 2. custodian-as-a-service/executor/executor.py - """ - return os.environ.get('EXECUTOR_PATH') or str(executor_path) - - @staticmethod - def resolve_executor_venv() -> str: - """ - Priorities - 1. VENV_PATH env - 2. custodian-as-a-service/executor/.executor_venv/bin/python - 3. custodian-as-a-service/executor/venv/bin/python - 4. sys.executable - """ - from_env = os.environ.get('VENV_PATH') - if from_env: - return from_env - for folder in EXECUTABLE_VENV_FOLDER_PRIORITY: - venv_path = docker_path / folder - if venv_path.exists(): - return str(venv_path / 'bin/python') - return sys.executable - - def submit_job(self, job_name: str = None, command: str = None, - environment_variables: dict = None): - environment_variables = environment_variables or {} - self.check_ability_to_start_job() - - job_id = generate_id() - path_to_venv = self.resolve_executor_venv() - path_to_executor = self.resolve_executor_path() - - # Popen raises TypeError in case there is an env where value is None - env = {k: v for k, v in { - **os.environ, - **environment_variables, - BATCH_ENV_JOB_ID: job_id, - BATCH_ENV_SUBMITTED_AT: utc_iso() # for scheduled jobs - }.items() if v} - - _LOG.info('Executing sub-process') - process = subprocess.Popen([ - path_to_venv, path_to_executor - ], env=env, shell=False) - - self.processes[job_id] = process.pid - return {'jobId': job_id} - - def terminate_job(self, job_id, reason="Terminating job."): - if job_id in self.processes: - pid = self.processes[job_id] - p = psutil.Process(pid) - p.terminate() - - self.processes.pop(job_id) - - @staticmethod - def check_if_process_is_running(pid) -> bool: - return psutil.pid_exists(pid) - - def check_ability_to_start_job(self): - if len(self.processes) >= int(self.max_jobs_count): - return build_response( - content="The maximum number of jobs has already been started", - code=400 - ) - - @staticmethod - def describe_jobs(jobs: list): - result = [] - for job in jobs: - job_item = Job.get_nullable(hash_key=job) - if job_item: - result.append(job_item.attribute_values) - return result diff --git a/src/connections/logs_extension/base_logs_client.py b/src/connections/logs_extension/base_logs_client.py deleted file mode 100644 index f88e66eb8..000000000 --- a/src/connections/logs_extension/base_logs_client.py +++ /dev/null @@ -1,46 +0,0 @@ -import os - -from helpers.log_helper import get_logger -from services.clients.cloudwatch import CloudWatchClient -from helpers.constants import ENV_SERVICE_MODE, DOCKER_SERVICE_MODE, \ - SAAS_SERVICE_MODE - -_LOG = get_logger(__name__) - -SERVICE_MODE = os.getenv(ENV_SERVICE_MODE) or SAAS_SERVICE_MODE - - -def log_file_handler_builder(): - handler = None - - def init_handler(): - assert SERVICE_MODE == DOCKER_SERVICE_MODE, \ - "You can init log file handler only if SERVICE_MODE=docker" - nonlocal handler - if handler: - return handler - from connections.logs_extension.cw_to_log_file_adapter import \ - CWToLogFileAdapter - handler = CWToLogFileAdapter() - _LOG.info('Log file connection was successfully initialized') - return handler - return init_handler - - -LOGS_HANDLER = log_file_handler_builder() - - -class BaseLogsClient(CloudWatchClient): - is_docker = SERVICE_MODE == DOCKER_SERVICE_MODE - - def __init__(self, region=None): - self.region = region - if not self.is_docker: - super().__init__(region) - - def get_log_events(self, log_group_name, start, end, log_stream_name=None, - job_id=None): - if self.is_docker: - return LOGS_HANDLER().get_log_events(job_id) - return super().get_log_events(log_group_name, log_stream_name, start, - end) diff --git a/src/connections/logs_extension/cw_to_log_file_adapter.py b/src/connections/logs_extension/cw_to_log_file_adapter.py deleted file mode 100644 index 6c5d889d6..000000000 --- a/src/connections/logs_extension/cw_to_log_file_adapter.py +++ /dev/null @@ -1,20 +0,0 @@ -import os -import pathlib - - -class CWToLogFileAdapter: - def __init__(self): - file_path = pathlib.Path(__file__).parent.resolve() - - src = file_path.parent.parent - root = src.parent - self.logs_path = f'{root}/executor/logs' - - def get_log_events(self, job_id): - logs_path = f'{self.logs_path}/{job_id}/error.log' - if not os.path.exists(logs_path): - return [] - with open(logs_path, 'r') as file: - full_content = file.read() - result = [{'message': line} for line in full_content.split(' ;\n')] - return result diff --git a/src/deployment_resources.json b/src/deployment_resources.json index fed00bc72..4c65a91c6 100644 --- a/src/deployment_resources.json +++ b/src/deployment_resources.json @@ -1,23 +1,23 @@ { "CaaSJobs": { "resource_type": "dynamodb_table", - "hash_key_name": "job_id", + "hash_key_name": "i", "hash_key_type": "S", - "read_capacity": 1, + "read_capacity": 2, "write_capacity": 1, "global_indexes": [ { - "name": "customer-display-name", - "index_key_name": "customer_display_name", + "name": "t-sa-index", + "index_key_name": "t", "index_key_type": "S", - "index_sort_key_name": "submitted_at", + "index_sort_key_name": "sa", "index_sort_key_type": "S" }, { - "name": "tenant_display_name-submitted_at-index", - "index_key_name": "tenant_display_name", + "name": "c-sa-index", + "index_key_name": "c", "index_key_type": "S", - "index_sort_key_name": "submitted_at", + "index_sort_key_name": "sa", "index_sort_key_type": "S" } ] @@ -40,12 +40,35 @@ } ] }, - "CaaSRules": { + "CaaSReportStatistics": { "resource_type": "dynamodb_table", "hash_key_name": "id", "hash_key_type": "S", + "sort_key_name": "triggered_at", + "sort_key_type": "S", "read_capacity": 1, "write_capacity": 1, + "global_indexes": [ + { + "name": "customer_name-triggered_at-index", + "index_key_name": "customer_name", + "index_key_type": "S", + "index_sort_key_name": "triggered_at", + "index_sort_key_type": "S" + }, + { + "name": "status-index", + "index_key_name": "status", + "index_key_type": "S" + } + ] + }, + "CaaSRules": { + "resource_type": "dynamodb_table", + "hash_key_name": "id", + "hash_key_type": "S", + "read_capacity": 1, + "write_capacity": 2, "global_indexes": [ { "name": "c-id-index", @@ -63,16 +86,6 @@ } ] }, - "CaaSRulesMeta": { - "resource_type": "dynamodb_table", - "hash_key_name": "n", - "hash_key_type": "S", - "sort_key_name": "v", - "sort_key_type": "S", - "read_capacity": 10, - "write_capacity": 1, - "global_indexes": [] - }, "CaaSRulesets": { "resource_type": "dynamodb_table", "hash_key_name": "id", @@ -117,31 +130,6 @@ "read_capacity": 1, "write_capacity": 1 }, - "CaaSCredentialsManager": { - "resource_type": "dynamodb_table", - "hash_key_name": "cid", - "hash_key_type": "S", - "sort_key_name": "c", - "sort_key_type": "S", - "read_capacity": 1, - "write_capacity": 1, - "global_indexes": [ - { - "name": "cn-c-index", - "index_key_name": "cn", - "index_key_type": "S", - "index_sort_key_name": "c", - "index_sort_key_type": "S" - }, - { - "name": "tn-c-index", - "index_key_name": "tn", - "index_key_type": "S", - "index_sort_key_name": "c", - "index_sort_key_type": "S" - } - ] - }, "CaaSPolicies": { "resource_type": "dynamodb_table", "hash_key_name": "customer", @@ -160,14 +148,6 @@ "read_capacity": 1, "write_capacity": 1 }, - "CaaSLicenses": { - "resource_type": "dynamodb_table", - "hash_key_name": "license_key", - "hash_key_type": "S", - "read_capacity": 1, - "write_capacity": 1, - "global_indexes": [] - }, "CaaSEvents": { "resource_type": "dynamodb_table", "hash_key_name": "p", @@ -239,36 +219,17 @@ "write_capacity": 1, "global_indexes": [ { - "name": "jid-index", - "index_key_name": "jid", - "index_key_type": "S" - }, - { - "name": "cid-ers-index", - "index_key_name": "cid", - "index_key_type": "S", - "index_sort_key_name": "ers", - "index_sort_key_type": "S" - }, - { - "name": "t-ers-index", - "index_key_name": "t", - "index_key_type": "S", - "index_sort_key_name": "ers", - "index_sort_key_type": "S" - }, - { - "name": "c-ers-index", + "name": "c-jsa-index", "index_key_name": "c", "index_key_type": "S", - "index_sort_key_name": "ers", + "index_sort_key_name": "jsa", "index_sort_key_type": "S" }, { - "name": "c-jsta-index", - "index_key_name": "c", + "name": "t-jsa-index", + "index_key_name": "t", "index_key_type": "S", - "index_sort_key_name": "jsta", + "index_sort_key_name": "jsa", "index_sort_key_type": "S" } ] @@ -345,6 +306,62 @@ }, "resource_type": "iam_policy" }, + "step-function-lambda-execution": { + "policy_content": { + "Statement": [ + { + "Action": [ + "events:PutTargets", + "events:PutRule", + "events:DescribeRule", + "lambda:InvokeFunction", + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + "dynamodb:PutItem", + "dynamodb:Scan", + "sts:AssumeRole" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "resource_type": "iam_policy" + }, + "execute-step-function": { + "policy_content": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Resource": "*", + "Action": [ + "sts:AssumeRole", + "states:StartExecution" + ] + } + ] + }, + "resource_type": "iam_policy" + }, + "step-function-role": { + "predefined_policies": [], + "principal_service": "states", + "custom_policies": [ + "step-function-lambda-execution" + ], + "resource_type": "iam_role" + }, + "execute-step-function-role": { + "predefined_policies": [], + "principal_service": "apigateway", + "custom_policies": [ + "execute-step-function" + ], + "resource_type": "iam_role" + }, "batch-job-def-role": { "predefined_policies": [], "principal_service": "ecs-tasks", @@ -411,7 +428,7 @@ "memory": 2048, "command": [ "python", - "/executor/executor.py" + "/src/run.py" ], "environment": [ { @@ -446,18 +463,10 @@ "resource_type": "s3_bucket", "cors": [] }, - "${caas_ssm_backup_bucket}": { - "resource_type": "s3_bucket", - "cors": [] - }, "${stats_s3_bucket_name}": { "resource_type": "s3_bucket", "cors": [] }, - "${templates_s3_bucket_name}": { - "resource_type": "s3_bucket", - "cors": [] - }, "custodian_as_a_service": { "resource_type": "cognito_idp", "region": "${region}", @@ -476,10 +485,6 @@ { "name": "latest_login", "type": "String" - }, - { - "name": "tenants", - "type": "String" } ], "client": { @@ -493,6 +498,89 @@ ] } }, + "send_reports": { + "definition": { + "Comment": "Send reports, retry if something went wrong, save failed sending attempt", + "StartAt": "Report", + "States": { + "Report": { + "Type": "Task", + "Resource": "arn:aws:states:::lambda:invoke", + "OutputPath": "$.Payload", + "Parameters": { + "FunctionName": "arn:aws:lambda:${region}:${account_id}:function:caas-report-generation-handler:${lambdas_alias_name}", + "Payload": { + "requestContext.$": "$.requestContext", + "attempt.$": "$$.State.RetryCount", + "headers.$": "$.headers", + "httpMethod.$": "$.httpMethod", + "execution_job_id.$": "$$.Execution.Id", + "body.$": "$.body" + } + }, + "Retry": [ + { + "ErrorEquals": [ + "ReportNotSendException" + ], + "BackoffRate": 2, + "MaxAttempts": 4, + "IntervalSeconds": 900 + } + ], + "Next": "Pass", + "Catch": [ + { + "ErrorEquals": [ + "ReportNotSendException" + ], + "Next": "Pass", + "ResultPath": "$.errorInfo" + } + ] + }, + "Pass": { + "Type": "Pass", + "End": true + } + } + }, + "iam_role": "step-function-role", + "resource_type": "step_functions" + }, + "retry_send_reports": { + "definition": { + "Comment": "A description of my state machine", + "StartAt": "Lambda Invoke", + "States": { + "Lambda Invoke": { + "Type": "Task", + "Resource": "arn:aws:states:::lambda:invoke", + "OutputPath": "$.Payload", + "Parameters": { + "Payload.$": "$", + "FunctionName": "arn:aws:lambda:${region}:${account_id}:function:caas-report-generation-handler:${lambdas_alias_name}" + }, + "Next": "Pass", + "Catch": [ + { + "ErrorEquals": [ + "States.Timeout", + "TimeoutError" + ], + "Next": "Lambda Invoke" + } + ] + }, + "Pass": { + "Type": "Pass", + "End": true + } + } + }, + "iam_role": "step-function-role", + "resource_type": "step_functions" + }, "custodian-as-a-service-api": { "dependencies": [], "resource_type": "api_gateway", @@ -516,36 +604,61 @@ "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", - "authorization_type": "authorizer", - "method_request_parameters": {}, - "request_validator": { - "validate_request_body": false, - "validate_request_parameters": false - }, + "authorization_type": "NONE", + "lambda_name": "caas-api-handler", "method_request_models": { - "application/json": "SignUpPostModel" + "application/json": "SignUpModel" }, "responses": [ { "status_code": "201", "response_models": { - "application/json": "UserDTO" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "400", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "ErrorsModel" } }, { - "status_code": "400", + "status_code": "401", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "409", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-api-handler" + ] } }, "/signin": { @@ -556,11 +669,11 @@ "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "NONE", - "method_request_parameters": {}, "request_validator": { "validate_request_body": false, "validate_request_parameters": false }, + "lambda_name": "caas-api-handler", "method_request_models": { "application/json": "SignInPostModel" }, @@ -568,105 +681,111 @@ { "status_code": "200", "response_models": { - "application/json": "UserDTO" + "application/json": "SignInModel" } }, { "status_code": "400", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "ErrorsModel" + } + }, + { + "status_code": "401", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-api-handler" + ] } }, - "/event": { + "/refresh": { "policy_statement_singleton": true, "enable_cors": true, "POST": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", - "authorization_type": "authorizer", - "method_request_parameters": {}, + "authorization_type": "NONE", "request_validator": { "validate_request_body": false, "validate_request_parameters": false }, + "lambda_name": "caas-api-handler", "method_request_models": { - "application/json": "EventPostModel" + "application/json": "RefreshPostModel" }, "responses": [ { - "status_code": "202", + "status_code": "200", + "response_models": { + "application/json": "SignInModel" + } + }, + { + "status_code": "400", "response_models": { - "application/json": "JobDTO" + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "409", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } - } - ], - "lambda_name": "caas-api-handler" - } - }, - "/health": { - "policy_statement_singleton": true, - "enable_cors": true, - "GET": { - "integration_type": "lambda", - "enable_proxy": true, - "lambda_alias": "${lambdas_alias_name}", - "authorization_type": "authorizer", - "method_request_parameters": {}, - "responses": [ + }, { - "status_code": "200", + "status_code": "503", "response_models": { - "application/json": "HealthCheckDTO" + "application/json": "MessageModel" } - } - ], - "lambda_name": "caas-api-handler" - } - }, - "/health/{id}": { - "policy_statement_singleton": true, - "enable_cors": true, - "GET": { - "integration_type": "lambda", - "enable_proxy": true, - "lambda_alias": "${lambdas_alias_name}", - "authorization_type": "NONE", - "method_request_parameters": {}, - "responses": [ + }, { - "status_code": "200", + "status_code": "504", "response_models": { - "application/json": "HealthCheckDTO" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-api-handler" + ] } }, - "/jobs/standard": { + "/event": { "policy_statement_singleton": true, "enable_cors": true, "POST": { @@ -674,86 +793,120 @@ "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { "validate_request_body": false, "validate_request_parameters": false }, + "lambda_name": "caas-api-handler", "method_request_models": { - "application/json": "StandardJobPostModel" + "application/json": "EventPostModel" }, "responses": [ { "status_code": "202", "response_models": { - "application/json": "JobDTO" + "application/json": "EventModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "409", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-api-handler" + ] } }, - "/jobs/k8s": { + "/health": { "policy_statement_singleton": true, "enable_cors": true, - "POST": { + "GET": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, - "request_validator": { - "validate_request_body": false, - "validate_request_parameters": false + "lambda_name": "caas-api-handler", + "method_request_parameters": { + "method.request.querystring.customer_id": false, + "method.request.querystring.status": false }, "responses": [ { - "status_code": "202", + "status_code": "200", "response_models": { - "application/json": "JobDTO" + "application/json": "MultipleHealthChecksModel" } }, { - "status_code": "401", + "status_code": "400", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "ErrorsModel" } }, { - "status_code": "409", + "status_code": "401", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "403", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-api-handler" + ] } }, - "/jobs": { + "/health/{id}": { "policy_statement_singleton": true, "enable_cors": true, "GET": { @@ -761,144 +914,187 @@ "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", + "lambda_name": "caas-api-handler", "method_request_parameters": { - "method.request.querystring.customer": false, - "method.request.querystring.tenant_name": false, - "method.request.querystring.limit": false, - "method.request.querystring.next_token": false - }, - "request_validator": { - "validate_request_parameters": false + "method.request.querystring.customer_id": false }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "JobDTO" + "application/json": "SingleHealthCheckModel" } }, { "status_code": "400", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "ErrorsModel" + } + }, + { + "status_code": "401", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "404", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-api-handler" - }, + ] + } + }, + "/jobs/standard": { + "policy_statement_singleton": true, + "enable_cors": true, "POST": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { "validate_request_body": false, "validate_request_parameters": false }, + "lambda_name": "caas-api-handler", "method_request_models": { - "application/json": "JobPostModel" + "application/json": "StandardJobPostModel" }, "responses": [ { "status_code": "202", "response_models": { - "application/json": "JobDTO" + "application/json": "SingleJobModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "409", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } - } - ], - "lambda_name": "caas-api-handler" - } - }, - "/jobs/{job_id}": { - "policy_statement_singleton": true, - "enable_cors": true, - "GET": { - "integration_type": "lambda", - "enable_proxy": true, - "lambda_alias": "${lambdas_alias_name}", - "authorization_type": "authorizer", - "method_request_parameters": {}, - "request_validator": { - "validate_request_parameters": false - }, - "responses": [ + }, { - "status_code": "200", + "status_code": "503", "response_models": { - "application/json": "JobDTO" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-api-handler" - }, - "DELETE": { + ] + } + }, + "/jobs/k8s": { + "policy_statement_singleton": true, + "enable_cors": true, + "POST": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { "validate_request_body": false, "validate_request_parameters": false }, + "lambda_name": "caas-api-handler", "method_request_models": { - "application/json": "JobDeleteModel" + "application/json": "K8sJobPostModel" }, "responses": [ { "status_code": "202", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "SingleJobModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-api-handler" + ] } }, - "/scheduled-job": { + "/jobs": { "policy_statement_singleton": true, "enable_cors": true, "GET": { @@ -906,257 +1102,251 @@ "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": { - "method.request.querystring.name": false, - "method.request.querystring.customer": false, - "method.request.querystring.account": false, - "method.request.querystring.tenant_name": false - }, "request_validator": { "validate_request_parameters": false }, + "lambda_name": "caas-api-handler", + "method_request_parameters": { + "method.request.querystring.customer_id": false, + "method.request.querystring.limit": false, + "method.request.querystring.next_token": false, + "method.request.querystring.start_iso": false, + "method.request.querystring.end_iso": false, + "method.request.querystring.tenant_name": false, + "method.request.querystring.status": false + }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "ScheduledJobDTO" + "application/json": "MultipleJobsModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-api-handler" + ] }, "POST": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { "validate_request_body": false, "validate_request_parameters": false }, + "lambda_name": "caas-api-handler", "method_request_models": { - "application/json": "ScheduledJobPostModel" + "application/json": "JobPostModel" }, "responses": [ { - "status_code": "201", + "status_code": "202", + "response_models": { + "application/json": "SingleJobModel" + } + }, + { + "status_code": "400", "response_models": { - "application/json": "ScheduledJobDTO" + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "409", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-api-handler" + ] } - }, - "/scheduled-job/{name}": { + "/jobs/{job_id}": { + "policy_statement_singleton": true, + "enable_cors": true, "GET": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": { - "method.request.querystring.name": false, - "method.request.querystring.customer": false, - "method.request.querystring.account": false, - "method.request.querystring.tenant_name": false - }, "request_validator": { "validate_request_parameters": false }, + "lambda_name": "caas-api-handler", + "method_request_parameters": { + "method.request.querystring.customer_id": false + }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "ScheduledJobDTO" + "application/json": "SingleJobModel" } }, { - "status_code": "401", + "status_code": "400", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "ErrorsModel" } }, { - "status_code": "404", + "status_code": "401", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } - } - ], - "lambda_name": "caas-api-handler" - }, - "PATCH": { - "integration_type": "lambda", - "enable_proxy": true, - "lambda_alias": "${lambdas_alias_name}", - "authorization_type": "authorizer", - "method_request_parameters": {}, - "request_validator": { - "validate_request_body": false, - "validate_request_parameters": false - }, - "method_request_models": { - "application/json": "ScheduledJobPatchModel" - }, - "responses": [ + }, { - "status_code": "200", + "status_code": "404", "response_models": { - "application/json": "ScheduledJobDTO" + "application/json": "MessageModel" } }, { - "status_code": "401", + "status_code": "500", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "503", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-api-handler" + ] }, "DELETE": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, - "request_validator": { - "validate_request_body": false, - "validate_request_parameters": false - }, + "lambda_name": "caas-api-handler", "method_request_models": { - "application/json": "ScheduledJobDeleteModel" + "application/json": "BaseModel" }, "responses": [ { - "status_code": "200", + "status_code": "202", "response_models": { - "application/json": "ScheduledJobDTO" + "application/json": "MessageModel" } }, { - "status_code": "401", + "status_code": "400", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "ErrorsModel" } }, { - "status_code": "404", + "status_code": "401", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } - } - ], - "lambda_name": "caas-api-handler" - } - }, - "/customers": { - "policy_statement_singleton": true, - "enable_cors": true, - "GET": { - "integration_type": "lambda", - "enable_proxy": true, - "lambda_alias": "${lambdas_alias_name}", - "authorization_type": "authorizer", - "method_request_parameters": { - "method.request.querystring.complete": false, - "method.request.querystring.name": false - }, - "request_validator": { - "validate_request_parameters": false - }, - "responses": [ + }, { - "status_code": "200", + "status_code": "404", "response_models": { - "application/json": "CustomerDTO" + "application/json": "MessageModel" } }, { - "status_code": "401", + "status_code": "500", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "503", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] } }, - "/customers/rabbitmq": { + "/scheduled-job": { "policy_statement_singleton": true, "enable_cors": true, "GET": { @@ -1164,128 +1354,119 @@ "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": { - }, "request_validator": { "validate_request_parameters": false }, + "lambda_name": "caas-api-handler", + "method_request_parameters": { + "method.request.querystring.customer_id": false, + "method.request.querystring.tenant_name": false + }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "RabbitMQDTO" + "application/json": "MultipleScheduledJobsModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } - } - ], - "lambda_name": "caas-configuration-api-handler" + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" + } + } + ] }, "POST": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { "validate_request_body": false, "validate_request_parameters": false }, + "lambda_name": "caas-api-handler", "method_request_models": { - "application/json": "RabbitMQPostModel" + "application/json": "ScheduledJobPostModel" }, "responses": [ { - "status_code": "200", - "response_models": { - "application/json": "RabbitMQDTO" - } - }, - { - "status_code": "401", - "response_models": { - "application/json": "UnauthorizedResponseModel" - } - }, - { - "status_code": "404", + "status_code": "201", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "SingleScheduledJobModel" } }, { "status_code": "400", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "ErrorsModel" } }, { - "status_code": "409", + "status_code": "401", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } - } - ], - "lambda_name": "caas-configuration-api-handler" - }, - "DELETE": { - "integration_type": "lambda", - "enable_proxy": true, - "lambda_alias": "${lambdas_alias_name}", - "authorization_type": "authorizer", - "method_request_parameters": {}, - "method_request_models": { - "application/json": "RabbitMQDeleteModel" - }, - "request_validator": { - "validate_request_body": false - }, - "responses": [ + }, { - "status_code": "204", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "401", + "status_code": "500", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "503", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] } }, - "/tenants": { + "/scheduled-job/{name}": { "policy_statement_singleton": true, "enable_cors": true, "GET": { @@ -1293,219 +1474,246 @@ "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", + "lambda_name": "caas-api-handler", "method_request_parameters": { - "method.request.querystring.tenant_name": false, - "method.request.querystring.name": false, - "method.request.querystring.limit": false, - "method.request.querystring.next_token": false - }, - "request_validator": { - "validate_request_parameters": false + "method.request.querystring.customer_id": false }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "TenantDTO" + "application/json": "SingleScheduledJobModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" } }, { "status_code": "404", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] }, - "POST": { + "PATCH": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { "validate_request_body": false, "validate_request_parameters": false }, + "lambda_name": "caas-api-handler", "method_request_models": { - "application/json": "TenantPostModel" + "application/json": "ScheduledJobPatchModel" }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "TenantDTO" + "application/json": "SingleScheduledJobModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" } }, { "status_code": "404", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "409", + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] }, - "PATCH": { + "DELETE": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, - "request_validator": { - "validate_request_body": false, - "validate_request_parameters": false - }, + "lambda_name": "caas-api-handler", "method_request_models": { - "application/json": "TenantPatchModel" + "application/json": "BaseModel" }, "responses": [ { - "status_code": "200", + "status_code": "204" + }, + { + "status_code": "400", "response_models": { - "application/json": "TenantDTO" + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "404", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } - } - ], - "lambda_name": "caas-configuration-api-handler" - }, - "DELETE": { - "integration_type": "lambda", - "enable_proxy": true, - "lambda_alias": "${lambdas_alias_name}", - "authorization_type": "authorizer", - "method_request_parameters": {}, - "request_validator": { - "validate_request_body": false, - "validate_request_parameters": false - }, - "method_request_models": { - "application/json": "TenantDeleteModel" - }, - "responses": [ + }, { - "status_code": "200", + "status_code": "500", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "401", + "status_code": "503", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] } }, - "/tenants/regions": { + "/customers": { "policy_statement_singleton": true, "enable_cors": true, - "POST": { + "GET": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { - "validate_request_body": false, "validate_request_parameters": false }, - "method_request_models": { - "application/json": "TenantRegionPostModel" + "lambda_name": "caas-configuration-api-handler", + "method_request_parameters": { + "method.request.querystring.customer_id": false, + "method.request.querystring.name": false }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "TenantDTO" + "application/json": "MultipleCustomersModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "409", + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] } }, - "/accounts/credential_manager": { + "/customers/rabbitmq": { "policy_statement_singleton": true, "enable_cors": true, "GET": { @@ -1513,172 +1721,237 @@ "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": { - "method.request.querystring.cloud_identifier": false, - "method.request.querystring.cloud": false - }, "request_validator": { "validate_request_parameters": false }, + "lambda_name": "caas-configuration-api-handler", + "method_request_parameters": { + "method.request.querystring.customer_id": false + }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "CredentialsManagerDTO" + "application/json": "SingleRabbitMQModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] }, "POST": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { "validate_request_body": false, "validate_request_parameters": false }, + "lambda_name": "caas-configuration-api-handler", "method_request_models": { - "application/json": "CredentialsManagerPostModel" + "application/json": "RabbitMQPostModel" }, "responses": [ { - "status_code": "201", + "status_code": "200", "response_models": { - "application/json": "CredentialsManagerDTO" + "application/json": "SingleRabbitMQModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "409", + "status_code": "500", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] }, - "PATCH": { + "DELETE": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { - "validate_request_body": false, - "validate_request_parameters": false + "validate_request_body": false }, + "lambda_name": "caas-configuration-api-handler", "method_request_models": { - "application/json": "CredentialsManagerPatchModel" + "application/json": "RabbitMQDeleteModel" }, "responses": [ { - "status_code": "200", + "status_code": "204" + }, + { + "status_code": "400", "response_models": { - "application/json": "CredentialsManagerDTO" + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" - }, - "DELETE": { + ] + } + }, + "/tenants": { + "policy_statement_singleton": true, + "enable_cors": true, + "GET": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { - "validate_request_body": false, "validate_request_parameters": false }, - "method_request_models": { - "application/json": "CredentialsManagerDeleteModel" + "lambda_name": "caas-configuration-api-handler", + "method_request_parameters": { + "method.request.querystring.customer_id": false, + "method.request.querystring.limit": false, + "method.request.querystring.next_token": false, + "method.request.querystring.active": false, + "method.request.querystring.cloud": false }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MultipleTenantsModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] } }, - "/rules": { + "/tenants/{tenant_name}": { "policy_statement_singleton": true, "enable_cors": true, "GET": { @@ -1686,134 +1959,250 @@ "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", + "lambda_name": "caas-configuration-api-handler", "method_request_parameters": { - "method.request.querystring.rule_id": false, - "method.request.querystring.cloud": false, - "method.request.querystring.customer": false, - "method.request.querystring.limit": false, - "method.request.querystring.next_token": false, - "method.request.querystring.complete": false, - "method.request.querystring.rule_source_id": false - }, - "request_validator": { - "validate_request_parameters": false + "method.request.querystring.customer_id": false }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "RuleDTO" + "application/json": "SingleTenantsModel" } }, { - "status_code": "401", + "status_code": "400", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "ErrorsModel" } }, { - "status_code": "404", + "status_code": "401", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" + } + }, + { + "status_code": "404", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" - }, - "DELETE": { + ] + } + }, + "/tenants/{tenant_name}/active-licenses": { + "policy_statement_singleton": true, + "enable_cors": true, + "GET": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, - "request_validator": { - "validate_request_body": false, - "validate_request_parameters": false - }, - "method_request_models": { - "application/json": "RuleDeleteModel" + "lambda_name": "caas-configuration-api-handler", + "method_request_parameters": { + "method.request.querystring.customer_id": false, + "method.request.querystring.limit": false }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MultipleLicensesModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" } }, { "status_code": "404", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] } }, - "/rules/update-meta": { + "/rules": { "policy_statement_singleton": true, "enable_cors": true, - "POST": { + "GET": { + "integration_type": "lambda", + "enable_proxy": true, + "lambda_alias": "${lambdas_alias_name}", + "authorization_type": "authorizer", + "request_validator": { + "validate_request_parameters": false + }, + "lambda_name": "caas-configuration-api-handler", + "method_request_parameters": { + "method.request.querystring.customer_id": false, + "method.request.querystring.limit": false, + "method.request.querystring.next_token": false, + "method.request.querystring.rule": false, + "method.request.querystring.cloud": false, + "method.request.querystring.git_project_id": false, + "method.request.querystring.git_ref": false + }, + "responses": [ + { + "status_code": "200", + "response_models": { + "application/json": "MultipleRulesModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" + } + }, + { + "status_code": "401", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" + } + } + ] + }, + "DELETE": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { "validate_request_body": false, "validate_request_parameters": false }, + "lambda_name": "caas-configuration-api-handler", "method_request_models": { - "application/json": "RuleUpdateMetaPostModel" + "application/json": "RuleDeleteModel" }, "responses": [ { - "status_code": "202", + "status_code": "204" + }, + { + "status_code": "400", "response_models": { - "application/json": "RuleMetaUpdateDTO" + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] } }, - "/backup": { + "/rules/update-meta": { "policy_statement_singleton": true, "enable_cors": true, "POST": { @@ -1821,28 +2210,58 @@ "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, + "request_validator": { + "validate_request_body": false, + "validate_request_parameters": false + }, + "lambda_name": "caas-configuration-api-handler", + "method_request_models": { + "application/json": "RuleUpdateMetaPostModel" + }, "responses": [ { - "status_code": "200", + "status_code": "202", "response_models": { - "application/json": "BackUpResponseDTO" + "application/json": "MultipleRuleMetaUpdateModel" } }, { "status_code": "400", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] } }, "/metrics/update": { @@ -1853,28 +2272,54 @@ "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, + "lambda_name": "caas-configuration-api-handler", + "method_request_models": { + "application/json": "BaseModel" + }, "responses": [ { - "status_code": "200", + "status_code": "202", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { "status_code": "400", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] } }, "/metrics/status": { @@ -1885,28 +2330,56 @@ "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, + "lambda_name": "caas-api-handler", + "method_request_parameters": { + "method.request.querystring.customer_id": false, + "method.request.querystring.start_iso": false, + "method.request.querystring.end_iso": false + }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MultipleMetricsStatusesModel" } }, { "status_code": "400", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-api-handler" + ] } }, "/rulesets": { @@ -1917,55 +2390,74 @@ "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", + "request_validator": { + "validate_request_parameters": false + }, + "lambda_name": "caas-configuration-api-handler", "method_request_parameters": { + "method.request.querystring.customer_id": false, "method.request.querystring.name": false, "method.request.querystring.version": false, "method.request.querystring.cloud": false, - "method.request.querystring.customer": false, "method.request.querystring.get_rules": false, - "method.request.querystring.active": false - }, - "request_validator": { - "validate_request_parameters": false + "method.request.querystring.active": false, + "method.request.querystring.licensed": false }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "RulesetDTO" + "application/json": "MultipleRulesetsModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] }, "POST": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { "validate_request_body": false, "validate_request_parameters": false }, + "lambda_name": "caas-configuration-api-handler", "method_request_models": { "application/json": "RulesetPostModel" }, @@ -1973,40 +2465,57 @@ { "status_code": "201", "response_models": { - "application/json": "RulesetDTO" + "application/json": "SingleRulesetModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "409", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] }, "PATCH": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { "validate_request_body": false, "validate_request_parameters": false }, + "lambda_name": "caas-configuration-api-handler", "method_request_models": { "application/json": "RulesetPatchModel" }, @@ -2014,70 +2523,101 @@ { "status_code": "200", "response_models": { - "application/json": "RulesetDTO" + "application/json": "SingleRulesetModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] }, "DELETE": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { "validate_request_body": false, "validate_request_parameters": false }, + "lambda_name": "caas-configuration-api-handler", "method_request_models": { "application/json": "RulesetDeleteModel" }, "responses": [ { - "status_code": "200", + "status_code": "204" + }, + { + "status_code": "400", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } - } - ], - "lambda_name": "caas-configuration-api-handler" + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" + } + } + ] } }, "/rulesets/event-driven": { @@ -2088,51 +2628,70 @@ "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", + "request_validator": { + "validate_request_parameters": false + }, + "lambda_name": "caas-configuration-api-handler", "method_request_parameters": { + "method.request.querystring.customer_id": false, "method.request.querystring.cloud": false, "method.request.querystring.get_rules": false }, - "request_validator": { - "validate_request_parameters": false - }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "RulesetDTO" + "application/json": "MultipleRulesetsModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] }, "POST": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { "validate_request_body": false, "validate_request_parameters": false }, + "lambda_name": "caas-configuration-api-handler", "method_request_models": { "application/json": "EventDrivenRulesetPostModel" }, @@ -2140,70 +2699,101 @@ { "status_code": "201", "response_models": { - "application/json": "RulesetDTO" + "application/json": "SingleRulesetModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "409", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] }, "DELETE": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { "validate_request_body": false, "validate_request_parameters": false }, + "lambda_name": "caas-configuration-api-handler", "method_request_models": { "application/json": "EventDrivenRulesetDeleteModel" }, "responses": [ { - "status_code": "200", + "status_code": "204" + }, + { + "status_code": "400", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] } }, "/rulesets/content": { @@ -2214,41 +2804,59 @@ "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": { - "method.request.querystring.name": true, - "method.request.querystring.version": true, - "method.request.querystring.cloud": false - }, "request_validator": { "validate_request_parameters": false }, + "lambda_name": "caas-configuration-api-handler", + "method_request_parameters": { + "method.request.querystring.customer_id": false, + "method.request.querystring.name": true, + "method.request.querystring.version": true + }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] } }, "/rule-sources": { @@ -2259,51 +2867,70 @@ "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": { - "method.request.querystring.id": false, - "method.request.querystring.customer": false - }, "request_validator": { "validate_request_parameters": false }, + "lambda_name": "caas-configuration-api-handler", + "method_request_parameters": { + "method.request.querystring.customer_id": false, + "method.request.querystring.id": false, + "method.request.querystring.git_project_id": false + }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "RuleSourceDTO" + "application/json": "MultipleRuleSourceModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] }, "POST": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { "validate_request_body": false, "validate_request_parameters": false }, + "lambda_name": "caas-configuration-api-handler", "method_request_models": { "application/json": "RuleSourcePostModel" }, @@ -2311,34 +2938,57 @@ { "status_code": "201", "response_models": { - "application/json": "RuleSourceDTO" + "application/json": "SingleRuleSourceModel" } }, { "status_code": "400", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] }, "PATCH": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { "validate_request_body": false, "validate_request_parameters": false }, + "lambda_name": "caas-configuration-api-handler", "method_request_models": { "application/json": "RuleSourcePatchModel" }, @@ -2346,279 +2996,410 @@ { "status_code": "200", "response_models": { - "application/json": "RuleSourceDTO" + "application/json": "SingleRuleSourceModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] }, "DELETE": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { "validate_request_body": false, "validate_request_parameters": false }, + "lambda_name": "caas-configuration-api-handler", "method_request_models": { "application/json": "RuleSourceDeleteModel" }, "responses": [ { - "status_code": "200", + "status_code": "204" + }, + { + "status_code": "400", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] } }, - "/policies": { + "/licenses": { "policy_statement_singleton": true, "enable_cors": true, - "GET": { + "POST": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": { - "method.request.querystring.name": false, - "method.request.querystring.customer": false - }, "request_validator": { "validate_request_parameters": false }, + "lambda_name": "caas-configuration-api-handler", + "method_request_models": { + "application/json": "LicensePostModel" + }, "responses": [ { - "status_code": "200", + "status_code": "202" + }, + { + "status_code": "400", "response_models": { - "application/json": "PolicyDTO" + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] }, - "POST": { + "GET": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { - "validate_request_body": false, "validate_request_parameters": false }, - "method_request_models": { - "application/json": "PolicyPostModel" + "lambda_name": "caas-configuration-api-handler", + "method_request_parameters": { + "method.request.querystring.customer_id": false }, "responses": [ { - "status_code": "201", + "status_code": "200", + "response_models": { + "application/json": "MultipleLicensesModel" + } + }, + { + "status_code": "400", "response_models": { - "application/json": "PolicyDTO" + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "409", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" - }, - "PATCH": { + ] + } + }, + "/licenses/{license_key}": { + "policy_statement_singleton": true, + "enable_cors": true, + "GET": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { - "validate_request_body": false, "validate_request_parameters": false }, - "method_request_models": { - "application/json": "PolicyPatchModel" + "lambda_name": "caas-configuration-api-handler", + "method_request_parameters": { + "method.request.querystring.customer_id": false }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "PolicyDTO" + "application/json": "SingleLicenseModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" } }, { "status_code": "404", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] }, "DELETE": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { - "validate_request_body": false, "validate_request_parameters": false }, + "lambda_name": "caas-configuration-api-handler", "method_request_models": { - "application/json": "PolicyDeleteModel" + "application/json": "BaseModel" }, "responses": [ { - "status_code": "200", + "status_code": "204" + }, + { + "status_code": "400", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" } }, { "status_code": "404", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] } }, - "/policies/cache": { + "/licenses/{license_key}/sync": { "policy_statement_singleton": true, "enable_cors": true, - "DELETE": { + "POST": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, - "request_validator": { - "validate_request_body": false, - "validate_request_parameters": false - }, + "lambda_name": "caas-configuration-api-handler", "method_request_models": { - "application/json": "PolicyCacheDeleteModel" + "application/json": "BaseModel" }, "responses": [ { - "status_code": "200", + "status_code": "202", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "400", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "403", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "404", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] } }, - "/roles": { + "/licenses/{license_key}/activation": { "policy_statement_singleton": true, "enable_cors": true, "GET": { @@ -2626,288 +3407,249 @@ "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": { - "method.request.querystring.name": true, - "method.request.querystring.customer": false - }, "request_validator": { - "validate_request_parameters": false + "validate_request_body": false + }, + "lambda_name": "caas-configuration-api-handler", + "method_request_parameters": { + "method.request.querystring.customer_id": false }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "RoleDTO" + "application/json": "SingleLicenseActivationModel" } }, { - "status_code": "401", + "status_code": "400", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "ErrorsModel" } }, { - "status_code": "404", + "status_code": "401", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } - } - ], - "lambda_name": "caas-configuration-api-handler" - }, - "POST": { - "integration_type": "lambda", - "enable_proxy": true, - "lambda_alias": "${lambdas_alias_name}", - "authorization_type": "authorizer", - "method_request_parameters": {}, - "request_validator": { - "validate_request_body": false, - "validate_request_parameters": false - }, - "method_request_models": { - "application/json": "RolePostModel" - }, - "responses": [ + }, { - "status_code": "201", + "status_code": "404", "response_models": { - "application/json": "RoleDTO" + "application/json": "MessageModel" } }, { - "status_code": "401", + "status_code": "500", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "409", + "status_code": "503", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] }, - "PATCH": { + "DELETE": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { - "validate_request_body": false, - "validate_request_parameters": false + "validate_request_body": false }, + "lambda_name": "caas-configuration-api-handler", "method_request_models": { - "application/json": "RolePatchModel" + "application/json": "BaseModel" }, "responses": [ { - "status_code": "200", + "status_code": "204" + }, + { + "status_code": "400", "response_models": { - "application/json": "RoleDTO" + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" } }, { "status_code": "404", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] }, - "DELETE": { + "PUT": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { - "validate_request_body": false, - "validate_request_parameters": false + "validate_request_body": false }, + "lambda_name": "caas-configuration-api-handler", "method_request_models": { - "application/json": "RoleDeleteModel" + "application/json": "LicenseActivationPutModel" }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "SingleLicenseActivationModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "404", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } - } - ], - "lambda_name": "caas-configuration-api-handler" - } - }, - "/roles/cache": { - "policy_statement_singleton": true, - "enable_cors": true, - "DELETE": { - "integration_type": "lambda", - "enable_proxy": true, - "lambda_alias": "${lambdas_alias_name}", - "authorization_type": "authorizer", - "method_request_parameters": {}, - "request_validator": { - "validate_request_body": false, - "validate_request_parameters": false - }, - "method_request_models": { - "application/json": "RoleCacheDeleteModel" - }, - "responses": [ + }, { - "status_code": "200", + "status_code": "500", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "401", + "status_code": "503", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" - } - }, - "/users": { - "policy_statement_singleton": true, - "enable_cors": true, - "DELETE": { + ] + }, + "PATCH": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, + "lambda_name": "caas-configuration-api-handler", + "method_request_models": { + "application/json": "LicenseActivationPatchModel" + }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "SingleLicenseActivationModel" } }, { - "status_code": "401", + "status_code": "400", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "ErrorsModel" } }, { - "status_code": "404", + "status_code": "401", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } - } - ], - "lambda_name": "caas-api-handler" - } - }, - "/users/password-reset": { - "policy_statement_singleton": true, - "enable_cors": true, - "POST": { - "integration_type": "lambda", - "enable_proxy": true, - "lambda_alias": "${lambdas_alias_name}", - "authorization_type": "authorizer", - "method_request_parameters": {}, - "request_validator": { - "validate_request_body": false, - "validate_request_parameters": false - }, - "method_request_models": { - "application/json": "UserPasswordResetPostModel" - }, - "responses": [ + }, { - "status_code": "200", + "status_code": "404", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "401", + "status_code": "500", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "503", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-api-handler" + ] } }, - "/users/customer": { + "/settings/mail": { "policy_statement_singleton": true, "enable_cors": true, "GET": { @@ -2915,331 +3657,402 @@ "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": { - "method.request.querystring.target_user": true - }, "request_validator": { "validate_request_parameters": false }, + "lambda_name": "caas-configuration-api-handler", + "method_request_parameters": { + "method.request.querystring.customer_id": false, + "method.request.querystring.disclose": false + }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "UserCustomerDTO" + "application/json": "SingleMailSettingModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] }, "POST": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { - "validate_request_body": false, - "validate_request_parameters": false + "validate_request_body": false }, + "lambda_name": "caas-configuration-api-handler", "method_request_models": { - "application/json": "UserCustomerPostModel" + "application/json": "MailSettingPostModel" }, "responses": [ { "status_code": "201", "response_models": { - "application/json": "UserCustomerDTO" + "application/json": "SingleMailSettingModel" } }, { - "status_code": "401", + "status_code": "400", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "ErrorsModel" } }, { - "status_code": "409", + "status_code": "401", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", - "response_models": { - "application/json": "BaseMessageResponseModel" - } - } - ], - "lambda_name": "caas-configuration-api-handler" - }, - "PATCH": { - "integration_type": "lambda", - "enable_proxy": true, - "lambda_alias": "${lambdas_alias_name}", - "authorization_type": "authorizer", - "method_request_parameters": {}, - "request_validator": { - "validate_request_body": false, - "validate_request_parameters": false - }, - "method_request_models": { - "application/json": "UserCustomerPatchModel" - }, - "responses": [ - { - "status_code": "200", + "status_code": "403", "response_models": { - "application/json": "UserRoleDTO" + "application/json": "MessageModel" } }, { - "status_code": "401", + "status_code": "500", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "503", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] }, "DELETE": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, - "request_validator": { - "validate_request_body": false, - "validate_request_parameters": false - }, + "lambda_name": "caas-configuration-api-handler", "method_request_models": { - "application/json": "UserCustomerDeleteModel" + "application/json": "BaseModel" }, "responses": [ { - "status_code": "200", + "status_code": "204" + }, + { + "status_code": "400", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] } }, - "/users/role": { + "/settings/send_reports": { "policy_statement_singleton": true, "enable_cors": true, - "GET": { + "POST": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": { - "method.request.querystring.target_user": true - }, "request_validator": { - "validate_request_parameters": false + "validate_request_body": false + }, + "lambda_name": "caas-configuration-api-handler", + "method_request_models": { + "application/json": "ReportsSendingSettingPostModel" }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "UserRoleDTO" + "application/json": "MessageModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" - }, - "POST": { + ] + } + }, + "/settings/license-manager/config": { + "policy_statement_singleton": true, + "enable_cors": true, + "GET": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { - "validate_request_body": false, "validate_request_parameters": false }, - "method_request_models": { - "application/json": "UserRolePostModel" + "lambda_name": "caas-configuration-api-handler", + "method_request_parameters": { + "method.request.querystring.customer_id": false }, "responses": [ { - "status_code": "201", + "status_code": "200", + "response_models": { + "application/json": "SingleLMConfigModel" + } + }, + { + "status_code": "400", "response_models": { - "application/json": "UserRoleDTO" + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "409", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] }, - "PATCH": { + "POST": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { - "validate_request_body": false, - "validate_request_parameters": false + "validate_request_body": false }, + "lambda_name": "caas-configuration-api-handler", "method_request_models": { - "application/json": "UserRolePatchModel" + "application/json": "LicenseManagerConfigSettingPostModel" }, "responses": [ { - "status_code": "200", + "status_code": "201", + "response_models": { + "application/json": "SingleLMConfigModel" + } + }, + { + "status_code": "400", "response_models": { - "application/json": "UserRoleDTO" + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] }, "DELETE": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { - "validate_request_body": false, - "validate_request_parameters": false + "validate_request_body": false }, + "lambda_name": "caas-configuration-api-handler", "method_request_models": { - "application/json": "UserRoleDeleteModel" + "application/json": "BaseModel" }, "responses": [ { - "status_code": "200", + "status_code": "204" + }, + { + "status_code": "400", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] } }, - "/users/tenants": { + "/settings/license-manager/client": { "policy_statement_singleton": true, "enable_cors": true, "GET": { @@ -3247,124 +4060,168 @@ "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": { - "method.request.querystring.target_user": true - }, "request_validator": { "validate_request_parameters": false }, + "lambda_name": "caas-configuration-api-handler", + "method_request_parameters": { + "method.request.querystring.customer_id": false + }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "UserTenantsDTO" + "application/json": "SingleLMClientModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] }, - "PATCH": { + "POST": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { - "validate_request_body": false, - "validate_request_parameters": false + "validate_request_body": false }, + "lambda_name": "caas-configuration-api-handler", "method_request_models": { - "application/json": "UserTenantsPatchModel" + "application/json": "LicenseManagerClientSettingPostModel" }, "responses": [ { - "status_code": "200", + "status_code": "201", + "response_models": { + "application/json": "SingleLMClientModel" + } + }, + { + "status_code": "400", "response_models": { - "application/json": "UserTenantsDTO" + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] }, "DELETE": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, - "request_validator": { - "validate_request_body": false, - "validate_request_parameters": false - }, + "lambda_name": "caas-configuration-api-handler", "method_request_models": { - "application/json": "UserTenantsDeleteModel" + "application/json": "LicenseManagerClientSettingDeleteModel" }, "responses": [ { - "status_code": "200", + "status_code": "204" + }, + { + "status_code": "400", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] } }, - "/license": { + "/batch-results": { "policy_statement_singleton": true, "enable_cors": true, "GET": { @@ -3372,257 +4229,267 @@ "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": { - "method.request.querystring.license_key": false, - "method.request.querystring.customer": false - }, "request_validator": { "validate_request_parameters": false }, + "lambda_name": "caas-api-handler", + "method_request_parameters": { + "method.request.querystring.customer_id": false, + "method.request.querystring.limit": false, + "method.request.querystring.next_token": false, + "method.request.querystring.tenant_name": false, + "method.request.querystring.start": false, + "method.request.querystring.end": false + }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MultipleBatchResultsModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" - }, - "POST": { + ] + } + }, + "/batch-results/{batch_results_id}": { + "policy_statement_singleton": true, + "enable_cors": true, + "GET": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, - "request_validator": { - "validate_request_body": false, - "validate_request_parameters": false - }, - "method_request_models": { - "application/json": "LicensePostModel" + "lambda_name": "caas-api-handler", + "method_request_parameters": { + "method.request.querystring.customer_id": false }, "responses": [ { - "status_code": "202", + "status_code": "200", + "response_models": { + "application/json": "SingleBatchResultModel" + } + }, + { + "status_code": "400", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { "status_code": "403", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { "status_code": "404", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" - }, - "DELETE": { + ] + } + }, + "/reports/digests/jobs/{job_id}": { + "policy_statement_singleton": true, + "enable_cors": true, + "GET": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { - "validate_request_body": false, "validate_request_parameters": false }, - "method_request_models": { - "application/json": "LicenseDeleteModel" + "lambda_name": "caas-report-generator", + "method_request_parameters": { + "method.request.querystring.customer_id": false, + "method.request.querystring.job_type": false }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "SingleJobReportModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" } }, { "status_code": "404", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] } }, - "/license/sync": { + "/reports/digests/tenants/{tenant_name}/jobs": { "policy_statement_singleton": true, "enable_cors": true, - "POST": { + "GET": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { - "validate_request_body": false, "validate_request_parameters": false }, - "method_request_models": { - "application/json": "LicenseSyncPostModel" + "lambda_name": "caas-report-generator", + "method_request_parameters": { + "method.request.querystring.customer_id": false, + "method.request.querystring.start_iso": false, + "method.request.querystring.end_iso": false, + "method.request.querystring.job_type": false }, "responses": [ { - "status_code": "202", + "status_code": "200", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MultipleJobReportModel" } }, { "status_code": "400", "response_models": { - "application/json": "BaseMessageResponseModel" - } - } - ], - "lambda_name": "caas-configuration-api-handler" - } - }, - "/findings": { - "policy_statement_singleton": true, - "enable_cors": true, - "GET": { - "integration_type": "lambda", - "enable_proxy": true, - "lambda_alias": "${lambdas_alias_name}", - "authorization_type": "authorizer", - "method_request_parameters": { - "method.request.querystring.tenant_name": false, - "method.request.querystring.expand_on": false, - "method.request.querystring.data_type": false, - "method.request.querystring.get_url": false, - "method.request.querystring.map_key": false, - "method.request.querystring.dependent_inclusion": false, - "method.request.querystring.rules_to_include": false, - "method.request.querystring.resource_types_to_include": false, - "method.request.querystring.regions_to_include": false, - "method.request.querystring.severities_to_include": false - }, - "request_validator": { - "validate_request_parameters": false - }, - "responses": [ - { - "status_code": "200", - "response_models": { - "application/json": "FindingsDTO" + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", - "response_models": { - "application/json": "BaseMessageResponseModel" - } - } - ], - "lambda_name": "caas-configuration-api-handler" - }, - "DELETE": { - "integration_type": "lambda", - "enable_proxy": true, - "lambda_alias": "${lambdas_alias_name}", - "authorization_type": "authorizer", - "method_request_parameters": {}, - "request_validator": { - "validate_request_body": false, - "validate_request_parameters": false - }, - "method_request_models": { - "application/json": "FindingsDeleteModel" - }, - "responses": [ - { - "status_code": "200", + "status_code": "404", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "401", + "status_code": "500", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "503", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] } }, - "/settings/mail": { + "/reports/details/jobs/{job_id}": { "policy_statement_singleton": true, "enable_cors": true, "GET": { @@ -3630,116 +4497,139 @@ "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": { - "method.request.querystring.disclose": false - }, "request_validator": { "validate_request_parameters": false }, + "lambda_name": "caas-report-generator", + "method_request_parameters": { + "method.request.querystring.customer_id": false, + "method.request.querystring.job_type": false, + "method.request.querystring.href": false + }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "MailSettingDTO" + "application/json": "SingleJobReportModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" } }, { "status_code": "404", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" - }, - "POST": { + ] + } + }, + "/reports/details/tenants/{tenant_name}/jobs": { + "policy_statement_singleton": true, + "enable_cors": true, + "GET": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, - "method_request_models": { - "application/json": "MailSettingPostModel" - }, "request_validator": { - "validate_request_body": false + "validate_request_parameters": false + }, + "lambda_name": "caas-report-generator", + "method_request_parameters": { + "method.request.querystring.customer_id": false, + "method.request.querystring.start_iso": false, + "method.request.querystring.end_iso": false, + "method.request.querystring.job_type": false, + "method.request.querystring.href": false }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "MailSettingDTO" + "application/json": "MultipleJobReportModel" } }, { - "status_code": "401", + "status_code": "400", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "ErrorsModel" } }, { - "status_code": "404", + "status_code": "401", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } - } - ], - "lambda_name": "caas-configuration-api-handler" - }, - "DELETE": { - "integration_type": "lambda", - "enable_proxy": true, - "lambda_alias": "${lambdas_alias_name}", - "authorization_type": "authorizer", - "method_request_parameters": {}, - "responses": [ + }, { - "status_code": "200", + "status_code": "404", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "401", + "status_code": "500", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "503", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] } }, - "/settings/license-manager/config": { + "/reports/findings/jobs/{job_id}": { "policy_statement_singleton": true, "enable_cors": true, "GET": { @@ -3747,236 +4637,209 @@ "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { "validate_request_parameters": false }, + "lambda_name": "caas-report-generator", + "method_request_parameters": { + "method.request.querystring.customer_id": false, + "method.request.querystring.job_type": false, + "method.request.querystring.href": false + }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "LicenseManagerConfigDTO" + "application/json": "SingleJobReportModel" } }, { - "status_code": "401", + "status_code": "400", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "ErrorsModel" } }, { - "status_code": "404", + "status_code": "401", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } - } - ], - "lambda_name": "caas-configuration-api-handler" - }, - "POST": { - "integration_type": "lambda", - "enable_proxy": true, - "lambda_alias": "${lambdas_alias_name}", - "authorization_type": "authorizer", - "method_request_parameters": {}, - "method_request_models": { - "application/json": "LicenseManagerClientSettingPostModel" - }, - "request_validator": { - "validate_request_body": false - }, - "responses": [ + }, { - "status_code": "200", + "status_code": "404", "response_models": { - "application/json": "LicenseManagerConfigDTO" + "application/json": "MessageModel" } }, { - "status_code": "401", + "status_code": "500", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "503", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" - }, - "DELETE": { + ] + } + }, + "/reports/findings/tenants/{tenant_name}/jobs": { + "policy_statement_singleton": true, + "enable_cors": true, + "GET": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_models": { - "application/json": "LicenseManagerClientSettingDeleteModel" - }, "request_validator": { - "validate_request_body": false + "validate_request_parameters": false + }, + "lambda_name": "caas-report-generator", + "method_request_parameters": { + "method.request.querystring.customer_id": false, + "method.request.querystring.start_iso": false, + "method.request.querystring.end_iso": false, + "method.request.querystring.job_type": false, + "method.request.querystring.href": false }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MultipleJobReportModel" } }, { - "status_code": "401", + "status_code": "400", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "ErrorsModel" } }, { - "status_code": "404", + "status_code": "401", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } - } - ], - "lambda_name": "caas-configuration-api-handler" - } - }, - "/settings/license-manager/client": { - "policy_statement_singleton": true, - "enable_cors": true, - "GET": { - "integration_type": "lambda", - "enable_proxy": true, - "lambda_alias": "${lambdas_alias_name}", - "authorization_type": "authorizer", - "method_request_parameters": { - "method.request.querystring.format": true - }, - "request_validator": { - "validate_request_parameters": false - }, - "responses": [ + }, { - "status_code": "200", + "status_code": "404", "response_models": { - "application/json": "LicenseManagerClientDTO" + "application/json": "MessageModel" } }, { - "status_code": "401", + "status_code": "500", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "503", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" - }, - "POST": { + ] + } + }, + "/reports/compliance/jobs/{job_id}": { + "policy_statement_singleton": true, + "enable_cors": true, + "GET": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, - "method_request_models": { - "application/json": "LicenseManagerClientClientPostModel" - }, "request_validator": { - "validate_request_body": false + "validate_request_parameters": false + }, + "lambda_name": "caas-report-generator", + "method_request_parameters": { + "method.request.querystring.customer_id": false, + "method.request.querystring.job_type": false, + "method.request.querystring.format": false, + "method.request.querystring.href": false }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "LicenseManagerClientDTO" + "application/json": "SingleJobReportModel" } }, { - "status_code": "401", + "status_code": "400", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "ErrorsModel" } }, { - "status_code": "404", + "status_code": "401", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } - } - ], - "lambda_name": "caas-configuration-api-handler" - }, - "DELETE": { - "integration_type": "lambda", - "enable_proxy": true, - "lambda_alias": "${lambdas_alias_name}", - "authorization_type": "authorizer", - "method_request_parameters": {}, - "responses": [ + }, { - "status_code": "200", + "status_code": "404", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "401", + "status_code": "500", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "503", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] } }, - "/batch_results": { + "/reports/compliance/tenants/{tenant_name}": { "policy_statement_singleton": true, "enable_cors": true, "GET": { @@ -3984,40 +4847,68 @@ "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { "validate_request_parameters": false }, + "lambda_name": "caas-report-generator", + "method_request_parameters": { + "method.request.querystring.customer_id": false, + "method.request.querystring.format": false, + "method.request.querystring.href": false + }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "BatchResultDTO" + "application/json": "SingleEntityReportModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" } }, { "status_code": "404", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-api-handler" + ] } }, - "/batch_results/{batch_results_id}": { + "/reports/errors/jobs/{job_id}": { "policy_statement_singleton": true, "enable_cors": true, "GET": { @@ -4025,81 +4916,70 @@ "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { "validate_request_parameters": false }, + "lambda_name": "caas-report-generator", + "method_request_parameters": { + "method.request.querystring.customer_id": false, + "method.request.querystring.job_type": false, + "method.request.querystring.href": false, + "method.request.querystring.format": false, + "method.request.querystring.error_type": false + }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "BatchResultDTO" + "application/json": "ErrorsReportModel" } }, { - "status_code": "401", + "status_code": "400", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "ErrorsModel" } }, { - "status_code": "404", + "status_code": "401", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } - } - ], - "lambda_name": "caas-api-handler" - } - }, - "/reports/digests/jobs/{id}": { - "policy_statement_singleton": true, - "enable_cors": true, - "GET": { - "integration_type": "lambda", - "enable_proxy": true, - "lambda_alias": "${lambdas_alias_name}", - "authorization_type": "authorizer", - "method_request_parameters": {}, - "request_validator": { - "validate_request_parameters": false - }, - "responses": [ + }, { - "status_code": "200", + "status_code": "404", "response_models": { - "application/json": "GenericReportDTO" + "application/json": "MessageModel" } }, { - "status_code": "401", + "status_code": "500", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "503", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-report-generator" + ] } }, - "/reports/digests/tenants/jobs": { + "/reports/rules/jobs/{job_id}": { "policy_statement_singleton": true, "enable_cors": true, "GET": { @@ -4107,81 +4987,69 @@ "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { "validate_request_parameters": false }, + "lambda_name": "caas-report-generator", + "method_request_parameters": { + "method.request.querystring.customer_id": false, + "method.request.querystring.job_type": false, + "method.request.querystring.href": false, + "method.request.querystring.format": false + }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "GenericReportDTO" + "application/json": "RulesReportModel" } }, { - "status_code": "401", + "status_code": "400", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "ErrorsModel" } }, { - "status_code": "404", + "status_code": "401", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } - } - ], - "lambda_name": "caas-report-generator" - } - }, - "/reports/digests/tenants/{tenant_name}/jobs": { - "policy_statement_singleton": true, - "enable_cors": true, - "GET": { - "integration_type": "lambda", - "enable_proxy": true, - "lambda_alias": "${lambdas_alias_name}", - "authorization_type": "authorizer", - "method_request_parameters": {}, - "request_validator": { - "validate_request_parameters": false - }, - "responses": [ + }, { - "status_code": "200", + "status_code": "404", "response_models": { - "application/json": "GenericReportDTO" + "application/json": "MessageModel" } }, { - "status_code": "401", + "status_code": "500", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "503", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-report-generator" + ] } }, - "/reports/digests/tenants": { + "/reports/rules/tenants/{tenant_name}": { "policy_statement_singleton": true, "enable_cors": true, "GET": { @@ -4189,450 +5057,495 @@ "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { "validate_request_parameters": false }, + "lambda_name": "caas-report-generator", + "method_request_parameters": { + "method.request.querystring.customer_id": false, + "method.request.querystring.start_iso": false, + "method.request.querystring.end_iso": false, + "method.request.querystring.job_type": false + }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "GenericReportDTO" + "application/json": "EntityRulesReportModel" } }, { - "status_code": "401", + "status_code": "400", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "ErrorsModel" } }, { - "status_code": "404", + "status_code": "401", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } - } - ], - "lambda_name": "caas-report-generator" - } - }, - "/reports/digests/tenants/{tenant_name}": { - "policy_statement_singleton": true, - "enable_cors": true, - "GET": { - "integration_type": "lambda", - "enable_proxy": true, - "lambda_alias": "${lambdas_alias_name}", - "authorization_type": "authorizer", - "method_request_parameters": {}, - "request_validator": { - "validate_request_parameters": false - }, - "responses": [ + }, { - "status_code": "200", + "status_code": "404", "response_models": { - "application/json": "GenericReportDTO" + "application/json": "MessageModel" } }, { - "status_code": "401", + "status_code": "500", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "503", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-report-generator" + ] } }, - "/reports/details/jobs/{id}": { + "/reports/push/dojo/{job_id}": { "policy_statement_singleton": true, "enable_cors": true, - "GET": { + "POST": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { + "validate_request_body": false, "validate_request_parameters": false }, + "lambda_name": "caas-report-generator", + "method_request_models": { + "application/json": "ReportPushByJobIdModel" + }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "GenericReportDTO" + "application/json": "SingleDefectDojoPushResult" } }, { - "status_code": "401", + "status_code": "400", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "ErrorsModel" } }, { - "status_code": "404", + "status_code": "401", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } - } - ], - "lambda_name": "caas-report-generator" - } - }, - "/reports/details/tenants/jobs": { - "policy_statement_singleton": true, - "enable_cors": true, - "GET": { - "integration_type": "lambda", - "enable_proxy": true, - "lambda_alias": "${lambdas_alias_name}", - "authorization_type": "authorizer", - "method_request_parameters": {}, - "request_validator": { - "validate_request_parameters": false - }, - "responses": [ + }, { - "status_code": "200", + "status_code": "404", "response_models": { - "application/json": "GenericReportDTO" + "application/json": "MessageModel" } }, { - "status_code": "401", + "status_code": "500", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "503", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-report-generator" + ] } }, - "/reports/details/tenants/{tenant_name}/jobs": { + "/reports/push/dojo": { "policy_statement_singleton": true, "enable_cors": true, - "GET": { + "POST": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { + "validate_request_body": false, "validate_request_parameters": false }, + "lambda_name": "caas-report-generator", + "method_request_models": { + "application/json": "ReportPushMultipleModel" + }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "GenericReportDTO" + "application/json": "MultipleDefectDojoPushResult" } }, { - "status_code": "401", + "status_code": "400", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "ErrorsModel" } }, { - "status_code": "404", + "status_code": "401", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", - "response_models": { - "application/json": "BaseMessageResponseModel" - } - } - ], - "lambda_name": "caas-report-generator" - } - }, - "/reports/details/tenants": { - "policy_statement_singleton": true, - "enable_cors": true, - "GET": { - "integration_type": "lambda", - "enable_proxy": true, - "lambda_alias": "${lambdas_alias_name}", - "authorization_type": "authorizer", - "method_request_parameters": {}, - "request_validator": { - "validate_request_parameters": false - }, - "responses": [ - { - "status_code": "200", + "status_code": "403", "response_models": { - "application/json": "GenericReportDTO" + "application/json": "MessageModel" } }, { - "status_code": "401", + "status_code": "500", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "503", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-report-generator" + ] } }, - "/reports/details/tenants/{tenant_name}": { + "/reports/operational": { "policy_statement_singleton": true, "enable_cors": true, - "GET": { - "integration_type": "lambda", - "enable_proxy": true, - "lambda_alias": "${lambdas_alias_name}", + "POST": { + "integration_type": "service", + "integration_method": "POST", + "uri": "${region}:states:action/StartExecution", + "role": "execute-step-function-role", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { + "validate_request_body": false, "validate_request_parameters": false }, - "responses": [ + "integration_request_body_template": { + "application/json": "#set($input = '{\"requestContext\":{\"authorizer\":{\"claims\":{\"cognito:username\":\"' + $context.authorizer.claims['cognito:username'] + '\", \"custom:role\":\"' + $context.authorizer.claims['custom:role'] + '\",\"custom:customer\":\"' +$context.authorizer.claims['custom:customer'] + '\"}},\"resourcePath\":\"' + $context.resourcePath + '\", \"path\":\"' + $context.path + '\"},\"headers\":{\"Host\":\"' + $context.domainName + '\"},\"httpMethod\":\"' + $context.httpMethod +'\"'+ ',\"body\":' + \"$input.body\" +'}'){\"input\":\"$util.escapeJavaScript($input)\",\"stateMachineArn\":\"arn:aws:states:${region}:${account_id}:stateMachine:send_reports\"}" + }, + "integration_responses": [ { "status_code": "200", - "response_models": { - "application/json": "GenericReportDTO" + "response_templates": { + "application/json": "#set($inputRoot = $input.path('$'))\n{\"data\":{\"report_id\":\"$inputRoot.executionArn.split(\":\")[-1]\"}}" } - }, + } + ], + "lambda_name": "caas-report-generation-handler", + "method_request_models": { + "application/json": "OperationalGetReportModel" + }, + "responses": [ { - "status_code": "401", + "status_code": "200", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "400", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "ErrorsModel" } }, { - "status_code": "400", + "status_code": "401", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } - } - ], - "lambda_name": "caas-report-generator" - } - }, - "/reports/compliance/jobs/{id}": { - "policy_statement_singleton": true, - "enable_cors": true, - "GET": { - "integration_type": "lambda", - "enable_proxy": true, - "lambda_alias": "${lambdas_alias_name}", - "authorization_type": "authorizer", - "method_request_parameters": {}, - "request_validator": { - "validate_request_parameters": false - }, - "responses": [ + }, { - "status_code": "200", + "status_code": "403", "response_models": { - "application/json": "GenericReportDTO" + "application/json": "MessageModel" } }, { - "status_code": "401", + "status_code": "500", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "503", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-report-generator" + ] } }, - "/reports/compliance/tenants/{tenant_name}": { + "/reports/project": { "policy_statement_singleton": true, "enable_cors": true, - "GET": { - "integration_type": "lambda", - "enable_proxy": true, - "lambda_alias": "${lambdas_alias_name}", + "POST": { + "integration_type": "service", + "integration_method": "POST", + "uri": "${region}:states:action/StartExecution", + "role": "execute-step-function-role", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { + "validate_request_body": false, "validate_request_parameters": false }, + "integration_request_body_template": { + "application/json": "#set($input = '{\"requestContext\":{\"authorizer\":{\"claims\":{\"cognito:username\":\"' + $context.authorizer.claims['cognito:username'] + '\", \"custom:role\":\"' + $context.authorizer.claims['custom:role'] + '\",\"custom:customer\":\"' +$context.authorizer.claims['custom:customer'] + '\"}},\"resourcePath\":\"' + $context.resourcePath + '\", \"path\":\"' + $context.path + '\"},\"headers\":{\"Host\":\"' + $context.domainName + '\"},\"httpMethod\":\"' + $context.httpMethod +'\"'+ ',\"body\":' + \"$input.body\" +'}'){\"input\":\"$util.escapeJavaScript($input)\",\"stateMachineArn\":\"arn:aws:states:${region}:${account_id}:stateMachine:send_reports\"}" + }, + "integration_responses": [ + { + "status_code": "200", + "response_templates": { + "application/json": "#set($inputRoot = $input.path('$'))\n{\"data\":{\"report_id\":\"$inputRoot.executionArn.split(\":\")[-1]\"}}" + } + } + ], + "lambda_name": "caas-report-generation-handler", + "method_request_models": { + "application/json": "ProjectGetReportModel" + }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "GenericReportDTO" + "application/json": "MessageModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-report-generator" + ] } }, - "/reports/errors/jobs/{id}": { + "/reports/department": { "policy_statement_singleton": true, "enable_cors": true, - "GET": { - "integration_type": "lambda", - "enable_proxy": true, - "lambda_alias": "${lambdas_alias_name}", + "POST": { + "integration_type": "service", + "integration_method": "POST", + "uri": "${region}:states:action/StartExecution", + "role": "execute-step-function-role", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { + "validate_request_body": false, "validate_request_parameters": false }, + "integration_request_body_template": { + "application/json": "#set($input = '{\"requestContext\":{\"authorizer\":{\"claims\":{\"cognito:username\":\"' + $context.authorizer.claims['cognito:username'] + '\", \"custom:role\":\"' + $context.authorizer.claims['custom:role'] + '\",\"custom:customer\":\"' +$context.authorizer.claims['custom:customer'] + '\"}},\"resourcePath\":\"' + $context.resourcePath + '\", \"path\":\"' + $context.path + '\"},\"headers\":{\"Host\":\"' + $context.domainName + '\"},\"httpMethod\":\"' + $context.httpMethod +'\"'+ ',\"body\":' + \"$input.body\" +'}'){\"input\":\"$util.escapeJavaScript($input)\",\"stateMachineArn\":\"arn:aws:states:${region}:${account_id}:stateMachine:send_reports\"}" + }, + "integration_responses": [ + { + "status_code": "200", + "response_templates": { + "application/json": "#set($inputRoot = $input.path('$'))\n{\"data\":{\"report_id\":\"$inputRoot.executionArn.split(\":\")[-1]\"}}" + } + } + ], + "lambda_name": "caas-report-generation-handler", + "method_request_models": { + "application/json": "DepartmentGetReportModel" + }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "GenericReportDTO" + "application/json": "MessageModel" } }, { - "status_code": "401", + "status_code": "400", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "ErrorsModel" } }, { - "status_code": "404", + "status_code": "401", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" + } + }, + { + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-report-generator" + ] } }, - "/reports/errors/access/jobs/{id}": { + "/reports/clevel": { "policy_statement_singleton": true, "enable_cors": true, - "GET": { - "integration_type": "lambda", - "enable_proxy": true, - "lambda_alias": "${lambdas_alias_name}", + "POST": { + "integration_type": "service", + "integration_method": "POST", + "uri": "${region}:states:action/StartExecution", + "role": "execute-step-function-role", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { + "validate_request_body": false, "validate_request_parameters": false }, + "integration_request_body_template": { + "application/json": "#set($input = '{\"requestContext\":{\"authorizer\":{\"claims\":{\"cognito:username\":\"' + $context.authorizer.claims['cognito:username'] + '\", \"custom:role\":\"' + $context.authorizer.claims['custom:role'] + '\",\"custom:customer\":\"' +$context.authorizer.claims['custom:customer'] + '\"}},\"resourcePath\":\"' + $context.resourcePath + '\", \"path\":\"' + $context.path + '\"},\"headers\":{\"Host\":\"' + $context.domainName + '\"},\"httpMethod\":\"' + $context.httpMethod +'\"'+ ',\"body\":' + \"$input.body\" +'}'){\"input\":\"$util.escapeJavaScript($input)\",\"stateMachineArn\":\"arn:aws:states:${region}:${account_id}:stateMachine:send_reports\"}" + }, + "integration_responses": [ + { + "status_code": "200", + "response_templates": { + "application/json": "#set($inputRoot = $input.path('$'))\n{\"data\":{\"report_id\":\"$inputRoot.executionArn.split(\":\")[-1]\"}}" + } + } + ], + "lambda_name": "caas-report-generation-handler", + "method_request_models": { + "application/json": "CLevelGetReportModel" + }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "GenericReportDTO" + "application/json": "MessageModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-report-generator" + ] } }, - "/reports/errors/core/jobs/{id}": { + "/reports/diagnostic": { "policy_statement_singleton": true, "enable_cors": true, "GET": { @@ -4640,40 +5553,61 @@ "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { + "validate_request_body": false, "validate_request_parameters": false }, + "lambda_name": "caas-report-generation-handler", + "method_request_parameters": { + "method.request.querystring.customer_id": false + }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "GenericReportDTO" + "application/json": "MessageModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-report-generator" + ] } }, - "/reports/errors/tenants": { + "/reports/status": { "policy_statement_singleton": true, "enable_cors": true, "GET": { @@ -4681,163 +5615,237 @@ "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { + "validate_request_body": false, "validate_request_parameters": false }, + "lambda_name": "caas-configuration-api-handler", + "method_request_parameters": { + "method.request.querystring.customer_id": false, + "method.request.querystring.job_id": true, + "method.request.querystring.complete": false + }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "GenericReportDTO" + "application/json": "MultipleReportStatusModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-report-generator" + ] } }, - "/reports/errors/access/tenants": { + "/rule-meta/standards": { "policy_statement_singleton": true, "enable_cors": true, - "GET": { + "POST": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, - "request_validator": { - "validate_request_parameters": false + "lambda_name": "caas-configuration-api-handler", + "method_request_models": { + "application/json": "BaseModel" }, "responses": [ { - "status_code": "200", + "status_code": "202", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "400", "response_models": { - "application/json": "GenericReportDTO" + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-report-generator" + ] } }, - "/reports/errors/core/tenants": { + "/rule-meta/mappings": { "policy_statement_singleton": true, "enable_cors": true, - "GET": { + "POST": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, - "request_validator": { - "validate_request_parameters": false + "lambda_name": "caas-configuration-api-handler", + "method_request_models": { + "application/json": "BaseModel" }, "responses": [ { - "status_code": "200", + "status_code": "202", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "400", "response_models": { - "application/json": "GenericReportDTO" + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-report-generator" + ] } }, - "/reports/errors/tenants/{tenant_name}": { + "/rule-meta/meta": { "policy_statement_singleton": true, "enable_cors": true, - "GET": { + "POST": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, - "request_validator": { - "validate_request_parameters": false + "lambda_name": "caas-configuration-api-handler", + "method_request_models": { + "application/json": "BaseModel" }, "responses": [ { - "status_code": "200", + "status_code": "202", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "400", "response_models": { - "application/json": "GenericReportDTO" + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-report-generator" + ] } }, - "/reports/errors/access/tenants/{tenant_name}": { + "/reports/resources/platforms/k8s/{platform_id}/state/latest": { "policy_statement_singleton": true, "enable_cors": true, "GET": { @@ -4845,81 +5853,72 @@ "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { "validate_request_parameters": false }, + "lambda_name": "caas-report-generator", + "method_request_parameters": { + "method.request.querystring.customer_id": false, + "method.request.querystring.resource_type": false, + "method.request.querystring.full": false, + "method.request.querystring.exact_match": false, + "method.request.querystring.search_by_all": false, + "method.request.querystring.format": false, + "method.request.querystring.href": false + }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "GenericReportDTO" + "application/json": "EntityResourcesReportModel" } }, { - "status_code": "401", + "status_code": "400", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "ErrorsModel" } }, { - "status_code": "404", + "status_code": "401", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } - } - ], - "lambda_name": "caas-report-generator" - } - }, - "/reports/errors/core/tenants/{tenant_name}": { - "policy_statement_singleton": true, - "enable_cors": true, - "GET": { - "integration_type": "lambda", - "enable_proxy": true, - "lambda_alias": "${lambdas_alias_name}", - "authorization_type": "authorizer", - "method_request_parameters": {}, - "request_validator": { - "validate_request_parameters": false - }, - "responses": [ + }, { - "status_code": "200", + "status_code": "404", "response_models": { - "application/json": "GenericReportDTO" + "application/json": "MessageModel" } }, { - "status_code": "401", + "status_code": "500", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "503", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-report-generator" + ] } }, - "/reports/rules/jobs/{id}": { + "/reports/resources/tenants/{tenant_name}/state/latest": { "policy_statement_singleton": true, "enable_cors": true, "GET": { @@ -4927,81 +5926,73 @@ "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { "validate_request_parameters": false }, + "lambda_name": "caas-report-generator", + "method_request_parameters": { + "method.request.querystring.customer_id": false, + "method.request.querystring.resource_type": false, + "method.request.querystring.region": false, + "method.request.querystring.full": false, + "method.request.querystring.exact_match": false, + "method.request.querystring.search_by_all": false, + "method.request.querystring.format": false, + "method.request.querystring.href": false + }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "RuleReportDTO" + "application/json": "EntityResourcesReportModel" } }, { - "status_code": "401", + "status_code": "400", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "ErrorsModel" } }, { - "status_code": "404", + "status_code": "401", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } - } - ], - "lambda_name": "caas-report-generator" - } - }, - "/reports/rules/tenants": { - "policy_statement_singleton": true, - "enable_cors": true, - "GET": { - "integration_type": "lambda", - "enable_proxy": true, - "lambda_alias": "${lambdas_alias_name}", - "authorization_type": "authorizer", - "method_request_parameters": {}, - "request_validator": { - "validate_request_parameters": false - }, - "responses": [ + }, { - "status_code": "200", + "status_code": "404", "response_models": { - "application/json": "RuleReportDTO" + "application/json": "MessageModel" } }, { - "status_code": "401", + "status_code": "500", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "503", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-report-generator" + ] } }, - "/reports/rules/tenants/{tenant_name}": { + "/reports/resources/tenants/{tenant_name}/jobs": { "policy_statement_singleton": true, "enable_cors": true, "GET": { @@ -5009,259 +6000,267 @@ "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { "validate_request_parameters": false }, + "lambda_name": "caas-report-generator", + "method_request_parameters": { + "method.request.querystring.customer_id": false, + "method.request.querystring.start_iso": false, + "method.request.querystring.end_iso": false, + "method.request.querystring.job_type": false, + "method.request.querystring.resource_type": false, + "method.request.querystring.region": false, + "method.request.querystring.full": false, + "method.request.querystring.exact_match": false, + "method.request.querystring.search_by_all": false + }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "RuleReportDTO" + "application/json": "JobResourcesReportModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" } }, { "status_code": "404", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-report-generator" + ] } }, - "/reports/push/dojo/{job_id}": { + "/reports/resources/jobs/{job_id}": { "policy_statement_singleton": true, "enable_cors": true, - "POST": { + "GET": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { - "validate_request_body": false, "validate_request_parameters": false }, - "method_request_models": { - "application/json": "ReportPushByJobIdModel" + "lambda_name": "caas-report-generator", + "method_request_parameters": { + "method.request.querystring.customer_id": false, + "method.request.querystring.job_type": false, + "method.request.querystring.resource_type": false, + "method.request.querystring.region": false, + "method.request.querystring.full": false, + "method.request.querystring.exact_match": false, + "method.request.querystring.search_by_all": false, + "method.request.querystring.href": false }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "SiemPushDTO" + "application/json": "JobResourcesReportModel" } }, { "status_code": "400", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } - } - ], - "lambda_name": "caas-report-generator" - } - }, - "/reports/push/security-hub/{job_id}": { - "policy_statement_singleton": true, - "enable_cors": true, - "POST": { - "integration_type": "lambda", - "enable_proxy": true, - "lambda_alias": "${lambdas_alias_name}", - "authorization_type": "authorizer", - "method_request_parameters": {}, - "request_validator": { - "validate_request_body": false, - "validate_request_parameters": false - }, - "method_request_models": { - "application/json": "ReportPushByJobIdModel" - }, - "responses": [ + }, { - "status_code": "200", + "status_code": "404", "response_models": { - "application/json": "SiemPushDTO" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "401", + "status_code": "503", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-report-generator" + ] } }, - "/reports/push/dojo": { + "/platforms/k8s": { "policy_statement_singleton": true, "enable_cors": true, - "POST": { + "GET": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { - "validate_request_body": false, "validate_request_parameters": false }, - "method_request_models": { - "application/json": "ReportPushMultipleModel" + "lambda_name": "caas-configuration-api-handler", + "method_request_parameters": { + "method.request.querystring.customer_id": false, + "method.request.querystring.tenant_name": false }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "SiemPushDTO" + "application/json": "MultipleK8SPlatformsModel" } }, { "status_code": "400", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-report-generator" - } - }, - "/reports/push/security-hub": { - "policy_statement_singleton": true, - "enable_cors": true, + ] + }, "POST": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { - "validate_request_body": false, "validate_request_parameters": false }, + "lambda_name": "caas-configuration-api-handler", "method_request_models": { - "application/json": "ReportPushMultipleModel" + "application/json": "PlatformK8SPostModel" }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "SiemPushDTO" + "application/json": "SingleK8SPlatformModel" } }, { "status_code": "400", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } - } - ], - "lambda_name": "caas-report-generator" - } - }, - "/reports/operational": { - "policy_statement_singleton": true, - "enable_cors": true, - "GET": { - "integration_type": "lambda", - "enable_proxy": true, - "lambda_alias": "${lambdas_alias_name}", - "authorization_type": "authorizer", - "method_request_parameters": {}, - "request_validator": { - "validate_request_body": false, - "validate_request_parameters": false - }, - "method_request_models": { - "application/json": "OperationalGetReport" - }, - "responses": [ + }, { - "status_code": "200", + "status_code": "500", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "503", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "401", + "status_code": "504", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-report-generation-handler" + ] } }, - "/reports/project": { + "/platforms/k8s/{platform_id}": { "policy_statement_singleton": true, "enable_cors": true, "GET": { @@ -5269,77 +6268,126 @@ "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { - "validate_request_body": false, "validate_request_parameters": false }, - "method_request_models": { - "application/json": "ProjectGetReport" + "lambda_name": "caas-configuration-api-handler", + "method_request_parameters": { + "method.request.querystring.customer_id": false }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "SingleK8SPlatformModel" } }, { "status_code": "400", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "404", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-report-generation-handler" - } - }, - "/reports/department": { - "policy_statement_singleton": true, - "enable_cors": true, - "GET": { + ] + }, + "DELETE": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { - "validate_request_body": false, "validate_request_parameters": false }, + "lambda_name": "caas-configuration-api-handler", "method_request_models": { - "application/json": "DepartmentGetReport" + "application/json": "BaseModel" }, "responses": [ { - "status_code": "200", - "response_models": { - "application/json": "BaseMessageResponseModel" - } + "status_code": "204" }, { "status_code": "400", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "404", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-report-generation-handler" + ] } }, - "/reports/clevel": { + "/integrations/defect-dojo": { "policy_statement_singleton": true, "enable_cors": true, "GET": { @@ -5347,979 +6395,2027 @@ "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, "request_validator": { - "validate_request_body": false, "validate_request_parameters": false }, - "method_request_models": { - "application/json": "ChiefGetReport" + "lambda_name": "caas-configuration-api-handler", + "method_request_parameters": { + "method.request.querystring.customer_id": false }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MultipleDefectDojoModel" } }, { "status_code": "400", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } - } - ], - "lambda_name": "caas-report-generation-handler" - } - }, - "/applications/dojo": { - "policy_statement_singleton": true, - "enable_cors": true, - "POST": { - "integration_type": "lambda", - "enable_proxy": true, - "lambda_alias": "${lambdas_alias_name}", - "authorization_type": "authorizer", - "method_request_parameters": {}, - "method_request_models": { - "application/json": "DojoApplicationPostModel" - }, - "request_validator": { - "validate_request_body": false - }, - "responses": [ + }, { - "status_code": "200", + "status_code": "403", "response_models": { - "application/json": "AccessApplicationDTO" + "application/json": "MessageModel" } }, { - "status_code": "401", + "status_code": "500", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "503", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] }, - "GET": { + "POST": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": { - "method.request.querystring.customer": false - }, "request_validator": { "validate_request_parameters": false }, + "lambda_name": "caas-configuration-api-handler", + "method_request_models": { + "application/json": "DefectDojoPostModel" + }, "responses": [ { - "status_code": "200", + "status_code": "201", + "response_models": { + "application/json": "SingleDefeDojoModel" + } + }, + { + "status_code": "400", "response_models": { - "application/json": "DojoApplicationListModel" + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] } }, - "/applications/dojo/{application_id}": { - "DELETE": { + "/integrations/defect-dojo/{id}": { + "policy_statement_singleton": true, + "enable_cors": true, + "GET": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, - "method_request_models": { - "application/json": "DojoApplicationDeleteModel" - }, "request_validator": { - "validate_request_body": false + "validate_request_parameters": false + }, + "lambda_name": "caas-configuration-api-handler", + "method_request_parameters": { + "method.request.querystring.customer_id": false }, "responses": [ { - "status_code": "204", + "status_code": "200", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "SingleDefeDojoModel" } }, { - "status_code": "401", + "status_code": "400", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "ErrorsModel" } }, { - "status_code": "404", + "status_code": "401", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } - } - ], - "lambda_name": "caas-configuration-api-handler" - }, - "PATCH": { - "integration_type": "lambda", - "enable_proxy": true, - "lambda_alias": "${lambdas_alias_name}", - "authorization_type": "authorizer", - "method_request_parameters": {}, - "method_request_models": { - "application/json": "DojoApplicationPatchModel" - }, - "request_validator": { - "validate_request_body": false - }, - "responses": [ + }, { - "status_code": "200", + "status_code": "404", "response_models": { - "application/json": "AccessApplicationDTO" + "application/json": "MessageModel" } }, { - "status_code": "401", + "status_code": "500", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "503", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] }, - "GET": { + "DELETE": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": { - "method.request.querystring.customer": false - }, "request_validator": { "validate_request_parameters": false }, + "lambda_name": "caas-configuration-api-handler", + "method_request_models": { + "application/json": "BaseModel" + }, "responses": [ { - "status_code": "200", + "status_code": "204" + }, + { + "status_code": "400", "response_models": { - "application/json": "AccessApplicationDTO" + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" } }, { "status_code": "404", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } - } - ], - "lambda_name": "caas-configuration-api-handler" - } - }, - "/applications/access": { - "policy_statement_singleton": true, - "enable_cors": true, - "POST": { + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" + } + } + ] + } + }, + "/integrations/defect-dojo/{id}/activation": { + "policy_statement_singleton": true, + "enable_cors": true, + "PUT": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, + "lambda_name": "caas-configuration-api-handler", "method_request_models": { - "application/json": "AccessApplicationPostModel" - }, - "request_validator": { - "validate_request_body": false + "application/json": "DefectDojoActivationPutModel" }, "responses": [ { - "status_code": "200", + "status_code": "201", + "response_models": { + "application/json": "SingleDefectDojoActivation" + } + }, + { + "status_code": "400", "response_models": { - "application/json": "AccessApplicationDTO" + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" } }, { "status_code": "404", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] }, "GET": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", + "lambda_name": "caas-configuration-api-handler", "method_request_parameters": { - "method.request.querystring.customer": false - }, - "request_validator": { - "validate_request_parameters": false + "method.request.querystring.customer_id": false }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "AccessApplicationDTO" + "application/json": "SingleDefectDojoActivation" } }, { - "status_code": "401", + "status_code": "400", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "ErrorsModel" } }, { - "status_code": "404", + "status_code": "401", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } - } - ], - "lambda_name": "caas-configuration-api-handler" - } - }, - "/applications/access/{application_id}": { - "DELETE": { - "integration_type": "lambda", - "enable_proxy": true, - "lambda_alias": "${lambdas_alias_name}", - "authorization_type": "authorizer", - "method_request_parameters": {}, - "method_request_models": { - "application/json": "AccessApplicationDeleteModel" - }, - "request_validator": { - "validate_request_body": false - }, - "responses": [ + }, { - "status_code": "204", + "status_code": "404", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "401", + "status_code": "500", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "503", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] }, - "PATCH": { + "DELETE": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, + "lambda_name": "caas-configuration-api-handler", "method_request_models": { - "application/json": "AccessApplicationPatchModel" - }, - "request_validator": { - "validate_request_body": false + "application/json": "BaseModel" }, "responses": [ { - "status_code": "200", - "response_models": { - "application/json": "AccessApplicationDTO" - } + "status_code": "204" }, { - "status_code": "401", + "status_code": "400", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "ErrorsModel" } }, { - "status_code": "404", + "status_code": "401", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } - } - ], - "lambda_name": "caas-configuration-api-handler" - }, - "GET": { - "integration_type": "lambda", - "enable_proxy": true, - "lambda_alias": "${lambdas_alias_name}", - "authorization_type": "authorizer", - "method_request_parameters": { - "method.request.querystring.customer": false - }, - "request_validator": { - "validate_request_parameters": false - }, - "responses": [ + }, { - "status_code": "200", + "status_code": "404", "response_models": { - "application/json": "AccessApplicationDTO" + "application/json": "MessageModel" } }, { - "status_code": "401", + "status_code": "500", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "503", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] } }, - "/applications": { + "/integrations/temp/sre": { "policy_statement_singleton": true, "enable_cors": true, - "POST": { + "PUT": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, + "lambda_name": "caas-configuration-api-handler", "method_request_models": { - "application/json": "ApplicationPostModel" - }, - "request_validator": { - "validate_request_body": false + "application/json": "SelfIntegrationPutModel" }, "responses": [ { - "status_code": "200", + "status_code": "201", + "response_models": { + "application/json": "SingleSelfIntegration" + } + }, + { + "status_code": "400", "response_models": { - "application/json": "ApplicationDTO" + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] }, "GET": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", + "lambda_name": "caas-configuration-api-handler", "method_request_parameters": { - "method.request.querystring.customer": false - }, - "request_validator": { - "validate_request_parameters": false + "method.request.querystring.customer_id": false }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "ApplicationDTO" + "application/json": "SingleSelfIntegration" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" - } - }, - "/applications/{application_id}": { + ] + }, "DELETE": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, + "lambda_name": "caas-configuration-api-handler", "method_request_models": { - "application/json": "ApplicationDeleteModel" - }, - "request_validator": { - "validate_request_body": false + "application/json": "BaseModel" }, "responses": [ { - "status_code": "204", + "status_code": "204" + }, + { + "status_code": "400", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] }, "PATCH": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, + "lambda_name": "caas-configuration-api-handler", "method_request_models": { - "application/json": "ApplicationPatchModel" - }, - "request_validator": { - "validate_request_body": false + "application/json": "SelfIntegrationPatchModel" }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "ApplicationDTO" + "application/json": "SingleSelfIntegration" } }, { - "status_code": "401", + "status_code": "400", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "ErrorsModel" } }, { - "status_code": "404", + "status_code": "401", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", - "response_models": { - "application/json": "BaseMessageResponseModel" - } - } - ], - "lambda_name": "caas-configuration-api-handler" - }, - "GET": { - "integration_type": "lambda", - "enable_proxy": true, - "lambda_alias": "${lambdas_alias_name}", - "authorization_type": "authorizer", - "method_request_parameters": { - "method.request.querystring.customer": false - }, - "request_validator": { - "validate_request_parameters": false - }, - "responses": [ - { - "status_code": "200", + "status_code": "403", "response_models": { - "application/json": "ApplicationDTO" + "application/json": "MessageModel" } }, { - "status_code": "401", + "status_code": "500", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "503", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] } }, - "/parents": { + "/tenants/{tenant_name}/excluded-rules": { "policy_statement_singleton": true, "enable_cors": true, - "POST": { + "PUT": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, + "lambda_name": "caas-configuration-api-handler", "method_request_models": { - "application/json": "ParentPostModel" - }, - "request_validator": { - "validate_request_body": false + "application/json": "TenantExcludedRulesPutModel" }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "ParentDTO" + "application/json": "SingleTenantExcludedRules" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" } }, { "status_code": "404", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] }, "GET": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", + "lambda_name": "caas-configuration-api-handler", "method_request_parameters": { - "method.request.querystring.customer": false - }, - "request_validator": { - "validate_request_parameters": false + "method.request.querystring.customer_id": false }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "ParentDTO" + "application/json": "SingleTenantExcludedRules" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" } }, { "status_code": "404", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] } }, - "/parents/{parent_id}": { + "/customers/excluded-rules": { "policy_statement_singleton": true, "enable_cors": true, - "GET": { + "PUT": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, - "request_validator": { - "validate_request_parameters": false + "lambda_name": "caas-configuration-api-handler", + "method_request_models": { + "application/json": "CustomerExcludedRulesPutModel" }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "ParentDTO" + "application/json": "SingleCustomerExcludedRules" } }, { "status_code": "400", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "ErrorsModel" } - } - ], - "lambda_name": "caas-configuration-api-handler" - }, - "DELETE": { - "integration_type": "lambda", - "enable_proxy": true, + }, + { + "status_code": "401", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" + } + } + ] + }, + "GET": { + "integration_type": "lambda", + "enable_proxy": true, + "lambda_alias": "${lambdas_alias_name}", + "authorization_type": "authorizer", + "lambda_name": "caas-configuration-api-handler", + "method_request_parameters": { + "method.request.querystring.customer_id": false + }, + "responses": [ + { + "status_code": "200", + "response_models": { + "application/json": "SingleCustomerExcludedRules" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" + } + }, + { + "status_code": "401", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" + } + } + ] + } + }, + "/doc": { + "enable_cors": true, + "policy_statement_singleton": true, + "GET": { + "authorization_type": "NONE", + "integration_type": "lambda", + "lambda_name": "caas-api-handler", + "enable_proxy": true + } + }, + "/doc/swagger.json": { + "enable_cors": true, + "policy_statement_singleton": true, + "GET": { + "authorization_type": "NONE", + "integration_type": "lambda", + "lambda_name": "caas-api-handler", + "enable_proxy": true + } + }, + "/credentials": { + "policy_statement_singleton": true, + "enable_cors": true, + "GET": { + "integration_type": "lambda", + "enable_proxy": true, + "lambda_alias": "${lambdas_alias_name}", + "authorization_type": "authorizer", + "lambda_name": "caas-configuration-api-handler", + "method_request_parameters": { + "method.request.querystring.customer_id": false, + "method.request.querystring.limit": false, + "method.request.querystring.next_token": false, + "method.request.querystring.cloud": true + }, + "responses": [ + { + "status_code": "200", + "response_models": { + "application/json": "MultipleCredentialsModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" + } + }, + { + "status_code": "401", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" + } + } + ] + } + }, + "/credentials/{id}": { + "policy_statement_singleton": true, + "enable_cors": true, + "GET": { + "integration_type": "lambda", + "enable_proxy": true, + "lambda_alias": "${lambdas_alias_name}", + "authorization_type": "authorizer", + "lambda_name": "caas-configuration-api-handler", + "method_request_parameters": { + "method.request.querystring.customer_id": false + }, + "responses": [ + { + "status_code": "200", + "response_models": { + "application/json": "SingleCredentialsModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" + } + }, + { + "status_code": "401", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "404", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" + } + } + ] + } + }, + "/credentials/{id}/binding": { + "policy_statement_singleton": true, + "enable_cors": true, + "GET": { + "integration_type": "lambda", + "enable_proxy": true, + "lambda_alias": "${lambdas_alias_name}", + "authorization_type": "authorizer", + "lambda_name": "caas-configuration-api-handler", + "method_request_parameters": { + "method.request.querystring.customer_id": false + }, + "responses": [ + { + "status_code": "200", + "response_models": { + "application/json": "CredentialsActivationModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" + } + }, + { + "status_code": "401", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "404", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" + } + } + ] + }, + "PUT": { + "integration_type": "lambda", + "enable_proxy": true, + "lambda_alias": "${lambdas_alias_name}", + "authorization_type": "authorizer", + "lambda_name": "caas-configuration-api-handler", + "method_request_models": { + "application/json": "CredentialsBindModel" + }, + "responses": [ + { + "status_code": "200", + "response_models": { + "application/json": "CredentialsActivationModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" + } + }, + { + "status_code": "401", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "404", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" + } + } + ] + }, + "DELETE": { + "integration_type": "lambda", + "enable_proxy": true, + "lambda_alias": "${lambdas_alias_name}", + "authorization_type": "authorizer", + "lambda_name": "caas-configuration-api-handler", + "method_request_models": { + "application/json": "BaseModel" + }, + "responses": [ + { + "status_code": "204" + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" + } + }, + { + "status_code": "401", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "404", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" + } + } + ] + } + }, + "/users/whoami": { + "policy_statement_singleton": true, + "enable_cors": true, + "GET": { + "integration_type": "lambda", + "enable_proxy": true, + "lambda_alias": "${lambdas_alias_name}", + "authorization_type": "authorizer", + "lambda_name": "caas-api-handler", + "method_request_parameters": { + "method.request.querystring.customer_id": false + }, + "responses": [ + { + "status_code": "200", + "response_models": { + "application/json": "SingleUserModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" + } + }, + { + "status_code": "401", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" + } + } + ] + } + }, + "/users/{username}": { + "policy_statement_singleton": true, + "enable_cors": true, + "GET": { + "integration_type": "lambda", + "enable_proxy": true, + "lambda_alias": "${lambdas_alias_name}", + "authorization_type": "authorizer", + "lambda_name": "caas-api-handler", + "method_request_parameters": { + "method.request.querystring.customer_id": false + }, + "responses": [ + { + "status_code": "200", + "response_models": { + "application/json": "SingleUserModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" + } + }, + { + "status_code": "401", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "404", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" + } + } + ] + }, + "PATCH": { + "integration_type": "lambda", + "enable_proxy": true, + "lambda_alias": "${lambdas_alias_name}", + "authorization_type": "authorizer", + "lambda_name": "caas-api-handler", + "method_request_models": { + "application/json": "UserPatchModel" + }, + "responses": [ + { + "status_code": "200", + "response_models": { + "application/json": "SingleUserModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" + } + }, + { + "status_code": "401", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "404", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" + } + } + ] + }, + "DELETE": { + "integration_type": "lambda", + "enable_proxy": true, + "lambda_alias": "${lambdas_alias_name}", + "authorization_type": "authorizer", + "lambda_name": "caas-api-handler", + "method_request_models": { + "application/json": "BaseModel" + }, + "responses": [ + { + "status_code": "204" + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" + } + }, + { + "status_code": "401", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "404", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" + } + } + ] + } + }, + "/users": { + "policy_statement_singleton": true, + "enable_cors": true, + "GET": { + "integration_type": "lambda", + "enable_proxy": true, + "lambda_alias": "${lambdas_alias_name}", + "authorization_type": "authorizer", + "lambda_name": "caas-api-handler", + "method_request_parameters": { + "method.request.querystring.customer_id": false, + "method.request.querystring.limit": false, + "method.request.querystring.next_token": false + }, + "responses": [ + { + "status_code": "200", + "response_models": { + "application/json": "MultipleUsersModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" + } + }, + { + "status_code": "401", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" + } + } + ] + }, + "POST": { + "integration_type": "lambda", + "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, - "request_validator": { - "validate_request_body": false, - "validate_request_parameters": false + "lambda_name": "caas-api-handler", + "method_request_models": { + "application/json": "UserPostModel" + }, + "responses": [ + { + "status_code": "200", + "response_models": { + "application/json": "SingleUserModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" + } + }, + { + "status_code": "401", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "409", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" + } + } + ] + } + }, + "/users/reset-password": { + "policy_statement_singleton": true, + "enable_cors": true, + "POST": { + "integration_type": "lambda", + "enable_proxy": true, + "lambda_alias": "${lambdas_alias_name}", + "authorization_type": "authorizer", + "lambda_name": "caas-api-handler", + "method_request_models": { + "application/json": "UserResetPasswordModel" + }, + "responses": [ + { + "status_code": "204" + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" + } + }, + { + "status_code": "401", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" + } + } + ] + } + }, + "/policies": { + "policy_statement_singleton": true, + "enable_cors": true, + "GET": { + "integration_type": "lambda", + "enable_proxy": true, + "lambda_alias": "${lambdas_alias_name}", + "authorization_type": "authorizer", + "lambda_name": "caas-configuration-api-handler", + "method_request_parameters": { + "method.request.querystring.customer_id": false, + "method.request.querystring.limit": false, + "method.request.querystring.next_token": false + }, + "responses": [ + { + "status_code": "200", + "response_models": { + "application/json": "MultiplePoliciesModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" + } + }, + { + "status_code": "401", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" + } + } + ] + }, + "POST": { + "integration_type": "lambda", + "enable_proxy": true, + "lambda_alias": "${lambdas_alias_name}", + "authorization_type": "authorizer", + "lambda_name": "caas-configuration-api-handler", + "method_request_models": { + "application/json": "PolicyPostModel" + }, + "responses": [ + { + "status_code": "201", + "response_models": { + "application/json": "SinglePolicyModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" + } + }, + { + "status_code": "401", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" + } + } + ] + } + }, + "/policies/{name}": { + "policy_statement_singleton": true, + "enable_cors": true, + "GET": { + "integration_type": "lambda", + "enable_proxy": true, + "lambda_alias": "${lambdas_alias_name}", + "authorization_type": "authorizer", + "lambda_name": "caas-configuration-api-handler", + "method_request_parameters": { + "method.request.querystring.customer_id": false }, + "responses": [ + { + "status_code": "200", + "response_models": { + "application/json": "SinglePolicyModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" + } + }, + { + "status_code": "401", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "404", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" + } + } + ] + }, + "PATCH": { + "integration_type": "lambda", + "enable_proxy": true, + "lambda_alias": "${lambdas_alias_name}", + "authorization_type": "authorizer", + "lambda_name": "caas-configuration-api-handler", "method_request_models": { - "application/json": "ParentDeleteModel" + "application/json": "PolicyPatchModel" }, "responses": [ { - "status_code": "204", + "status_code": "200", + "response_models": { + "application/json": "SinglePolicyModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" + } + }, + { + "status_code": "401", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "401", + "status_code": "404", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "500", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] }, - "PATCH": { + "DELETE": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, - "request_validator": { - "validate_request_body": false, - "validate_request_parameters": false - }, + "lambda_name": "caas-configuration-api-handler", "method_request_models": { - "application/json": "ParentPatchModel" + "application/json": "BaseModel" }, "responses": [ { - "status_code": "200", + "status_code": "204" + }, + { + "status_code": "400", "response_models": { - "application/json": "ParentDTO" + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "404", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } - } - ], - "lambda_name": "caas-configuration-api-handler" - } - }, - "/rule-meta/standards": { - "POST": { - "integration_type": "lambda", - "enable_proxy": true, - "lambda_alias": "${lambdas_alias_name}", - "authorization_type": "authorizer", - "method_request_parameters": {}, - "responses": [ + }, { - "status_code": "202", + "status_code": "500", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } - } - ], - "lambda_name": "caas-configuration-api-handler" - } - }, - "/rule-meta/mappings": { - "POST": { - "integration_type": "lambda", - "enable_proxy": true, - "lambda_alias": "${lambdas_alias_name}", - "authorization_type": "authorizer", - "method_request_parameters": {}, - "responses": [ + }, { - "status_code": "202", + "status_code": "503", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } - } - ], - "lambda_name": "caas-configuration-api-handler" - } - }, - "/rule-meta/meta": { - "POST": { - "integration_type": "lambda", - "enable_proxy": true, - "lambda_alias": "${lambdas_alias_name}", - "authorization_type": "authorizer", - "method_request_parameters": {}, - "responses": [ + }, { - "status_code": "202", + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] } }, - "/reports/resources/tenants/{tenant_name}/state/latest": { + "/roles": { + "policy_statement_singleton": true, + "enable_cors": true, "GET": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, - "request_validator": { - "validate_request_parameters": false + "lambda_name": "caas-configuration-api-handler", + "method_request_parameters": { + "method.request.querystring.customer_id": false, + "method.request.querystring.limit": false, + "method.request.querystring.next_token": false }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "ResourceReportDTO" + "application/json": "MultipleRoleModel" } }, { "status_code": "400", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "ErrorsModel" } - } - ], - "lambda_name": "caas-report-generator" - } - }, - "/reports/resources/tenants/{tenant_name}/jobs": { - "GET": { - "integration_type": "lambda", - "enable_proxy": true, - "lambda_alias": "${lambdas_alias_name}", - "authorization_type": "authorizer", - "method_request_parameters": {}, - "request_validator": { - "validate_request_parameters": false - }, - "responses": [ + }, { - "status_code": "200", + "status_code": "401", "response_models": { - "application/json": "ResourceReportJobsDTO" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "403", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "500", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-report-generator" - } - }, - "/reports/resources/jobs/{id}": { - "GET": { + ] + }, + "POST": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, - "request_validator": { - "validate_request_parameters": false + "lambda_name": "caas-configuration-api-handler", + "method_request_models": { + "application/json": "RolePostModel" }, "responses": [ { - "status_code": "200", + "status_code": "201", "response_models": { - "application/json": "ResourceReportJobsDTO" + "application/json": "SingleRoleModel" } }, { "status_code": "400", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "ErrorsModel" + } + }, + { + "status_code": "401", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-report-generator" + ] } }, - "/platforms/k8s": { + "/roles/{name}": { "policy_statement_singleton": true, "enable_cors": true, "GET": { @@ -6327,183 +8423,243 @@ "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, - "request_validator": { - "validate_request_parameters": false + "lambda_name": "caas-configuration-api-handler", + "method_request_parameters": { + "method.request.querystring.customer_id": false }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "PlatformK8SDTO" + "application/json": "SingleRoleModel" } }, { "status_code": "400", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "ErrorsModel" + } + }, + { + "status_code": "401", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "404", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", + "response_models": { + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" - } - }, - "/platforms/k8s/eks": { - "policy_statement_singleton": true, - "enable_cors": true, - "POST": { + ] + }, + "PATCH": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, - "request_validator": { - "validate_request_body": false + "lambda_name": "caas-configuration-api-handler", + "method_request_models": { + "application/json": "RolePatchModel" }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "PlatformK8SDTO" + "application/json": "SingleRoleModel" + } + }, + { + "status_code": "400", + "response_models": { + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" } }, { "status_code": "404", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" - } - }, - "/platforms/k8s/eks/{id}": { - "policy_statement_singleton": true, - "enable_cors": true, + ] + }, "DELETE": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, + "lambda_name": "caas-configuration-api-handler", + "method_request_models": { + "application/json": "BaseModel" + }, "responses": [ { - "status_code": "204", + "status_code": "204" + }, + { + "status_code": "400", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "ErrorsModel" } }, { "status_code": "401", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" + } + }, + { + "status_code": "403", + "response_models": { + "application/json": "MessageModel" } }, { "status_code": "404", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "500", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "503", + "response_models": { + "application/json": "MessageModel" + } + }, + { + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] } }, - "/platforms/k8s/native": { + "/reports/raw/tenants/{tenant_name}/state/latest": { "policy_statement_singleton": true, "enable_cors": true, - "POST": { + "GET": { "integration_type": "lambda", "enable_proxy": true, "lambda_alias": "${lambdas_alias_name}", "authorization_type": "authorizer", - "method_request_parameters": {}, - "request_validator": { - "validate_request_body": false + "lambda_name": "caas-report-generator", + "method_request_parameters": { + "method.request.querystring.customer_id": false, + "method.request.querystring.obfuscated": false, + "method.request.querystring.meta": false }, "responses": [ { "status_code": "200", "response_models": { - "application/json": "PlatformK8SDTO" + "application/json": "RawReportModel" } }, { - "status_code": "401", + "status_code": "400", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "ErrorsModel" } }, { - "status_code": "404", + "status_code": "401", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "403", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } - } - ], - "lambda_name": "caas-configuration-api-handler" - } - }, - "/platforms/k8s/native/{id}": { - "policy_statement_singleton": true, - "enable_cors": true, - "DELETE": { - "integration_type": "lambda", - "enable_proxy": true, - "lambda_alias": "${lambdas_alias_name}", - "authorization_type": "authorizer", - "method_request_parameters": {}, - "responses": [ + }, { - "status_code": "204", + "status_code": "404", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "401", + "status_code": "500", "response_models": { - "application/json": "UnauthorizedResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "404", + "status_code": "503", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } }, { - "status_code": "400", + "status_code": "504", "response_models": { - "application/json": "BaseMessageResponseModel" + "application/json": "MessageModel" } } - ], - "lambda_name": "caas-configuration-api-handler" + ] } } } diff --git a/src/executor/Dockerfile b/src/executor/Dockerfile new file mode 100644 index 000000000..666bdbd3e --- /dev/null +++ b/src/executor/Dockerfile @@ -0,0 +1,69 @@ +FROM public.ecr.aws/docker/library/python:3.10 as executor-compile-image + +# default values expect that user has cloned core to the Custodian's +# root and execute build with build context pointing to Custodian's root: +# pwd +# ../custodian-as-a-service +# docker build -f src/executor/Dockerfile . +ARG CUSTODIAN_SERVICE_PATH=. +ARG CLOUD_CUSTODIAN_PATH=custodian-custom-core +# it's not enough to just add a provider here, you must copy its files below +ARG PROVIDERS="gcp azure kube" + +ARG POETRY_VERSION="1.4.0" +# to import c7n_azure.resources.insights.DiagnosticSettings +ENV AZURE_SUBSCRIPTION_ID=" " + +RUN pip install "poetry==$POETRY_VERSION" && python -m venv /root/.local + +WORKDIR /build/custodian-custom-core + +# Core's root +COPY $CLOUD_CUSTODIAN_PATH/pyproject.toml $CLOUD_CUSTODIAN_PATH/poetry.lock $CLOUD_CUSTODIAN_PATH/README.md ./ +RUN . /root/.local/bin/activate && poetry install --no-interaction --without dev --no-root + +# Core's providers +COPY $CLOUD_CUSTODIAN_PATH/tools/c7n_gcp/pyproject.toml $CLOUD_CUSTODIAN_PATH/tools/c7n_gcp/poetry.lock tools/c7n_gcp/ +COPY $CLOUD_CUSTODIAN_PATH/tools/c7n_azure/pyproject.toml $CLOUD_CUSTODIAN_PATH/tools/c7n_azure/poetry.lock tools/c7n_azure/ +COPY $CLOUD_CUSTODIAN_PATH/tools/c7n_kube/pyproject.toml $CLOUD_CUSTODIAN_PATH/tools/c7n_kube/poetry.lock tools/c7n_kube/ + +RUN for pkg in $PROVIDERS; do . /root/.local/bin/activate && cd tools/c7n_$pkg && poetry install --no-interaction --without dev --no-root && cd ../..; done + +# executor requirements +COPY $CUSTODIAN_SERVICE_PATH/src/executor/requirements.txt ../src/executor/requirements.txt +RUN . /root/.local/bin/activate && pip install -r ../src/executor/requirements.txt + + +COPY $CLOUD_CUSTODIAN_PATH/c7n c7n/ +# "poetry install --only-root" installs in editable mode +# (like pip install -e .). Due to multi-stage build we do not need this. +# To install in non-editable mode with Poetry you have to build a dist and +# then install which is quite a work so I use "pip install --no-deps ." +# https://github.com/python-poetry/poetry/issues/1382 +RUN . /root/.local/bin/activate && pip install --no-deps . + +COPY $CLOUD_CUSTODIAN_PATH/tools/c7n_gcp tools/c7n_gcp/ +COPY $CLOUD_CUSTODIAN_PATH/tools/c7n_azure tools/c7n_azure/ +COPY $CLOUD_CUSTODIAN_PATH/tools/c7n_kube tools/c7n_kube/ +RUN for pkg in $PROVIDERS; do . /root/.local/bin/activate && cd tools/c7n_$pkg && pip install --no-deps . && cd ../..; done + + +COPY $CUSTODIAN_SERVICE_PATH/src ../src +# by here we have executor built in /root/.local + +# is it ok? +RUN rm -rf $(find /root/.local/lib -name "*.dist-info") && rm -rf $(find /root/.local/lib/ -name "__pycache__") + +# 3.10-alpine 3.10-slim both work +FROM public.ecr.aws/docker/library/python:3.10-slim AS build-image + +COPY --from=executor-compile-image /root/.local /root/.local +COPY --from=executor-compile-image /build/src/executor/helpers /src/executor/helpers +COPY --from=executor-compile-image /build/src/executor/services /src/executor/services +COPY --from=executor-compile-image /build/src/helpers /src/helpers +COPY --from=executor-compile-image /build/src/models /src/models +COPY --from=executor-compile-image /build/src/services /src/services +COPY --from=executor-compile-image /build/src/run.py /src/ +ENV PATH=/root/.local/bin:$PATH + +WORKDIR /src diff --git a/src/executor/Dockerfile-opensource b/src/executor/Dockerfile-opensource new file mode 100644 index 000000000..78c0280c8 --- /dev/null +++ b/src/executor/Dockerfile-opensource @@ -0,0 +1,30 @@ +FROM public.ecr.aws/docker/library/python:3.10-slim as compile-image + +ARG CUSTODIAN_SERVICE_PATH=. +ARG CLOUD_CUSTODIAN_PROVIDERS="gcp azure kube" + +# some dependency error occurred when installing all with one pip install command. So +RUN pip install --user c7n && for pkg in $CLOUD_CUSTODIAN_PROVIDERS; do pip install --user c7n-$pkg; done + +COPY $CUSTODIAN_SERVICE_PATH/src/executor/requirements.txt /src/executor/requirements.txt +RUN pip install --user -r /src/executor/requirements.txt + +COPY $CUSTODIAN_SERVICE_PATH/src/executor/helpers /src/executor/helpers +COPY $CUSTODIAN_SERVICE_PATH/src/executor/services /src/executor/services +COPY $CUSTODIAN_SERVICE_PATH/src/helpers /src/helpers +COPY $CUSTODIAN_SERVICE_PATH/src/models /src/models +COPY $CUSTODIAN_SERVICE_PATH/src/services /src/services +COPY $CUSTODIAN_SERVICE_PATH/src/run.py /src/ + +# Some additional actions to reduce size, don't know whether those are used practices and whether they do no harm, but +# seems to work +RUN rm -rf $(find /root/.local/lib -name "*.dist-info") && rm -rf $(find /root/.local/lib/ -name "__pycache__") + +#FROM public.ecr.aws/docker/library/python:3.10-alpine AS build-image +FROM public.ecr.aws/docker/library/python:3.10-slim AS build-image + +COPY --from=compile-image /root/.local /root/.local +COPY --from=compile-image /src /src + +ENV PATH=/root/.local/bin:$PATH +WORKDIR /src \ No newline at end of file diff --git a/src/executor/README.md b/src/executor/README.md new file mode 100644 index 000000000..0565876c2 --- /dev/null +++ b/src/executor/README.md @@ -0,0 +1,34 @@ +![Custodian Service logo](../../docs/pics/cs_logo.png) + +### Custodian Service executor + +The application provides ability to +perform [custodian](https://cloudcustodian.io) +scans for AWS, Azure and GCP accounts. + +[Custodian](https://cloudcustodian.io) is a tool for automation cloud security +compliance. It is based on open-source tool Cloud Custodian enhanced by EPAM +Team. The tool allows users to check their infrastructure resources for +compliance to the security policies and standards. Custodian applies the defined +sets of rules against the infrastructure and provides information on the +resources that break the policies. The rulesets are designed specifically for +each of the clouds – AWS, Azure, and GCP. + +### Notice + +All the technical details described below are actual for the particular version, +or a range of versions of the software. + +### Actual for versions: 1.0.0 + +## Custodian Service diagram + +Build docker image (directory custodian-as-a-service/docker) and put it to + Elastic Container Registry field set to `true`. + +## Exit codes +Container can end its execution with the following status codes: +- `0` if code execution completed successfully; +- `2` if it is not allowed to start the job due to the license manager restrictions; +- `126` if retry needed (example - any problems with credentials); +- `1` all other execeptions and errors that do not require a retry. \ No newline at end of file diff --git a/src/connections/__init__.py b/src/executor/__init__.py similarity index 100% rename from src/connections/__init__.py rename to src/executor/__init__.py diff --git a/src/connections/auth_extension/__init__.py b/src/executor/helpers/__init__.py similarity index 100% rename from src/connections/auth_extension/__init__.py rename to src/executor/helpers/__init__.py diff --git a/src/executor/helpers/constants.py b/src/executor/helpers/constants.py new file mode 100644 index 000000000..393cf1d6b --- /dev/null +++ b/src/executor/helpers/constants.py @@ -0,0 +1,63 @@ +""" +Executor specific constants +""" +from enum import Enum + +from helpers.constants import CAASEnv, Cloud + + +class ExecutorMode(str, Enum): + CONSISTENT = 'consistent' + CONCURRENT = 'concurrent' + + +ENV_AWS_ACCESS_KEY_ID = 'AWS_ACCESS_KEY_ID' +ENV_AWS_SECRET_ACCESS_KEY = 'AWS_SECRET_ACCESS_KEY' +ENV_AWS_SESSION_TOKEN = 'AWS_SESSION_TOKEN' +ENV_AWS_DEFAULT_REGION = 'AWS_DEFAULT_REGION' + +ENV_AZURE_CLIENT_SECRET = 'AZURE_CLIENT_SECRET' + +ENV_GOOGLE_APPLICATION_CREDENTIALS = 'GOOGLE_APPLICATION_CREDENTIALS' +ENV_CLOUDSDK_CORE_PROJECT = 'CLOUDSDK_CORE_PROJECT' + +AWS_DEFAULT_REGION = 'us-east-1' + +DEFAULT_JOB_LIFETIME_MIN = 55 + +ENVS_TO_HIDE = { + 'PS1', 'PS2', 'PS3', 'PS4', + CAASEnv.MINIO_ACCESS_KEY_ID, CAASEnv.MINIO_SECRET_ACCESS_KEY, + CAASEnv.VAULT_TOKEN, CAASEnv.MONGO_URI, + ENV_AWS_SECRET_ACCESS_KEY, ENV_AWS_SESSION_TOKEN, ENV_AZURE_CLIENT_SECRET +} +HIDDEN_ENV_PLACEHOLDER = '****' + +INVALID_CREDENTIALS_ERROR_CODES = { + Cloud.AWS: {'AuthFailure', 'InvalidToken', + 'UnrecognizedClientException', 'ExpiredToken', + 'ExpiredTokenException'}, # add 'InvalidClientTokenId' + Cloud.AZURE: {'InvalidAuthenticationTokenTenant', + 'AuthorizationFailed', 'ClientAuthenticationError', + 'Azure Error: AuthorizationFailed'}, + Cloud.GOOGLE: set() +} +ACCESS_DENIED_ERROR_CODE = { + Cloud.AWS: { + 'AccessDenied', 'AccessDeniedException', 'UnauthorizedOperation', + 'AuthorizationError', 'AccessDeniedException, AccessDeniedException' + }, + Cloud.AZURE: set(), + Cloud.GOOGLE: set() +} + +CACHE_FILE = 'cloud-custodian.cache' + + +class ExecutorError(str, Enum): + """ + Explanation for user why the job failed + """ + LM_DID_NOT_ALLOW = 'License manager does did not allow this job' # exit code 2 + NO_CREDENTIALS = 'Could not resolve any credentials' + INTERNAL = 'Internal executor error' diff --git a/src/executor/helpers/profiling.py b/src/executor/helpers/profiling.py new file mode 100644 index 000000000..64f5ec6a0 --- /dev/null +++ b/src/executor/helpers/profiling.py @@ -0,0 +1,63 @@ +import logging +from abc import ABC, abstractmethod +from typing import BinaryIO + +import msgspec +from aws_xray_sdk.core import xray_recorder, patch +from aws_xray_sdk.core.models.entity import Entity +from aws_xray_sdk.core.sampling.local.sampler import LocalSampler + +from helpers.log_helper import get_logger + +_LOG = get_logger(__name__) + +patch(('requests', 'pymongo', 'botocore')) + + +class Emitter(ABC): + @abstractmethod + def send_entity(self, entity: Entity): + """ + Must write this entity data somewhere. Entity is a segment + or subsegment + :param entity: + :return: + """ + + +class BytesEmitter(Emitter): + """ + Writes JSONs in bytes line by line to the given buffer + """ + __slots__ = '_buffer', '_encoder' + + def __init__(self, buffer: BinaryIO): + self._buffer = buffer + self._encoder = msgspec.json.Encoder(enc_hook=str) + + def send_entity(self, entity: Entity): + try: + self._buffer.write(self._encoder.encode(entity.to_dict())) + self._buffer.write(b'\n') + except Exception: # noqa + _LOG.exception('Failed to write entity to bytes buffer') + + +SAMPLING_RULES = { + "version": 2, + "default": { + "fixed_target": 0, + "rate": .5 # 50% of jobs are sampled + }, + "rules": [ + ] +} + +logging.getLogger('aws_xray_sdk').setLevel(logging.ERROR) +xray_recorder.configure( + context_missing='IGNORE_ERROR', + sampling=True, + sampler=LocalSampler(SAMPLING_RULES), + service='custodian-executor', + streaming_threshold=1000 +) diff --git a/src/executor/requirements.txt b/src/executor/requirements.txt new file mode 100644 index 000000000..5baa1cea8 --- /dev/null +++ b/src/executor/requirements.txt @@ -0,0 +1,15 @@ +boto3==1.26.80 +botocore==1.29.80 +pynamodb==5.3.2 +dynamodb-json==1.3 +pymongo==4.5.0 +hvac==1.2.1 +requests==2.31.0 +python-dateutil==2.8.2 +jinja2==3.1.2 +modular-sdk==5.1.1 +aws-xray-sdk==2.12.0 +msgspec==0.18.6 +cryptography==42.0.2 +XlsxWriter==3.1.9 +tabulate==0.9.0 \ No newline at end of file diff --git a/src/executor/services/__init__.py b/src/executor/services/__init__.py new file mode 100644 index 000000000..adee55296 --- /dev/null +++ b/src/executor/services/__init__.py @@ -0,0 +1,76 @@ +""" +This module contains executor specific code that should not be used inside +lambdas +""" + +from functools import cached_property +from typing import TYPE_CHECKING + +from helpers import SingletonMeta +from helpers.log_helper import get_logger +from services import SP + +if TYPE_CHECKING: + from executor.services.credentials_service import CredentialsService + from executor.services.environment_service import BatchEnvironmentService + from executor.services.license_manager_service import LicenseManagerService + from executor.services.notification_service import NotificationService + from executor.services.policy_service import PoliciesService + +_LOG = get_logger(__name__) + + +class BatchServiceProvider(metaclass=SingletonMeta): + """ + Services that are specific to executor + """ + + @cached_property + def credentials_service(self) -> 'CredentialsService': + from executor.services.credentials_service import CredentialsService + _LOG.debug('Creating CredentialsService') + return CredentialsService( + ssm_client=SP.ssm, + environment_service=self.environment_service, + ) + + @cached_property + def environment_service(self) -> 'BatchEnvironmentService': + from executor.services.environment_service import \ + BatchEnvironmentService + _LOG.debug('Creating EnvironmentService') + return BatchEnvironmentService() + + @property + def env(self) -> 'BatchEnvironmentService': # alias + return self.environment_service + + @cached_property + def license_manager_service(self) -> 'LicenseManagerService': + from executor.services.license_manager_service import \ + LicenseManagerService + _LOG.debug('Creating LicenseManagerService') + return LicenseManagerService( + settings_service=SP.settings_service, + ssm_client=SP.ssm, + environment_service=self.environment_service + ) + + @cached_property + def notification_service(self) -> 'NotificationService': + _LOG.debug('Creating NotificationService') + return NotificationService( + setting_service=SP.settings_service, + ssm_client=SP.ssm, + s3_client=SP.s3 + ) + + @cached_property + def policies_service(self) -> 'PoliciesService': + from executor.services.policy_service import PoliciesService + _LOG.debug('Creating PoliciesService') + return PoliciesService(ruleset_service=SP.ruleset_service, + environment_service=self.environment_service) + + +BSP = BatchServiceProvider() # stands for Batch service provider diff --git a/src/executor/services/credentials_service.py b/src/executor/services/credentials_service.py new file mode 100644 index 000000000..6f1983bb9 --- /dev/null +++ b/src/executor/services/credentials_service.py @@ -0,0 +1,51 @@ +import json +import tempfile + +from executor.helpers.constants import ENV_CLOUDSDK_CORE_PROJECT, \ + ENV_GOOGLE_APPLICATION_CREDENTIALS +from executor.services.environment_service import BatchEnvironmentService +from helpers.log_helper import get_logger +from services.clients.ssm import AbstractSSMClient + +_LOG = get_logger(__name__) + + +class CredentialsService: + def __init__(self, ssm_client: AbstractSSMClient, + environment_service: BatchEnvironmentService): + self.ssm = ssm_client + self.environment_service = environment_service + + def get_credentials_from_ssm(self, credentials_key: bool = None, + remove: bool = True) -> str | dict: + """ + Get our (not maestro) credentials from ssm. For AWS and AZURE + these are already valid credentials envs. Must be just exported. + For GOOGLE a file must be created additionally. + :param credentials_key: + :param remove: + :return: + """ + if not credentials_key: + _LOG.info('Credentials key not provided to ' + 'get_credentials_from_ssm function. Using key from env') + credentials_key = self.environment_service.credentials_key() + if not credentials_key: + return {} + try: + value = self.ssm.get_secret_value(credentials_key) or {} + if remove: + _LOG.info(f'Removing secret {credentials_key}') + self.ssm.delete_parameter(credentials_key) + return value + except json.JSONDecodeError: + return {} + + @staticmethod + def google_credentials_to_file(credentials: dict) -> dict: + with tempfile.NamedTemporaryFile('w', delete=False) as fp: + json.dump(credentials, fp) + return { + ENV_GOOGLE_APPLICATION_CREDENTIALS: fp.name, + ENV_CLOUDSDK_CORE_PROJECT: credentials.get('project_id') + } diff --git a/src/executor/services/environment_service.py b/src/executor/services/environment_service.py new file mode 100644 index 000000000..c4843562c --- /dev/null +++ b/src/executor/services/environment_service.py @@ -0,0 +1,161 @@ +from typing_extensions import override + +from executor.helpers.constants import (ExecutorMode, AWS_DEFAULT_REGION, + DEFAULT_JOB_LIFETIME_MIN, ENVS_TO_HIDE, + HIDDEN_ENV_PLACEHOLDER) +from helpers.constants import (BatchJobEnv, BatchJobType, ENV_TRUE) +from services.environment_service import EnvironmentService + + +class BatchEnvironmentService(EnvironmentService): + """ + Extends base environment with batch specific-ones + """ + + @override + def aws_region(self) -> str: + return super().aws_region() or self.aws_default_region() + + def job_id(self) -> str | None: + return self._environment.get(BatchJobEnv.CUSTODIAN_JOB_ID) + + def batch_job_id(self) -> str: + return self._environment[BatchJobEnv.JOB_ID] + + def batch_results_ids(self) -> set[str]: + env = self._environment.get(BatchJobEnv.BATCH_RESULTS_IDS) + if not env: + return set() + return set(env.split(',')) + + def target_regions(self) -> set[str]: + """ + Definitely important for AWS scans. Returns regions that must be + scanned by not global policies. Global one anyway will scan all the + resources + :return: + """ + regions = self._environment.get(BatchJobEnv.TARGET_REGIONS) + if regions: + return set(map(str.strip, regions.split(','))) + return set() + + def target_rulesets(self) -> set[str]: + env = self._environment.get(BatchJobEnv.TARGET_RULESETS) + if env: + return set(map(str.strip, env.split(','))) + return set() + + def licensed_ruleset_map(self, license_key_list: list[str] + ) -> dict[str, list[str]]: + reference_map = {} + rulesets = self._environment.get(BatchJobEnv.LICENSED_RULESETS) + if rulesets: + for each in rulesets.split(','): + index, *ruleset_id = each.split(':', 1) + try: + index = int(index) + except ValueError: + continue + if ruleset_id and 0 <= index < len(license_key_list): + key = license_key_list[index] + referenced = reference_map.setdefault(key, []) + referenced.append(ruleset_id[0]) + return reference_map + + def affected_licenses(self) -> list[str]: + license_keys = self._environment.get(BatchJobEnv.AFFECTED_LICENSES) + if license_keys: + return [each.strip() for each in license_keys.split(',')] + return [] + + def is_licensed_job(self) -> bool: + """ + Returns true in case the job is licensed. A licensed job is the + one which involves at least one licensed ruleset. + """ + return not not self.affected_licenses() + + def aws_default_region(self): + return self._environment.get( + BatchJobEnv.AWS_DEFAULT_REGION) or AWS_DEFAULT_REGION + + def credentials_key(self) -> str | None: + return self._environment.get(BatchJobEnv.CREDENTIALS_KEY) + + def job_lifetime_min(self) -> int: + """ + Must return job lifetime in minutes + :return: + """ + return int(self._environment.get( + BatchJobEnv.BATCH_JOB_LIFETIME_MINUTES, DEFAULT_JOB_LIFETIME_MIN + )) + + def job_type(self) -> BatchJobType: + """ + Default job type is `standard` + """ + env = self._environment.get(BatchJobEnv.JOB_TYPE) + if env: + return BatchJobType(env) + return BatchJobType.STANDARD + + def is_standard(self) -> bool: + return self.job_type() == BatchJobType.STANDARD + + def is_multi_account_event_driven(self) -> bool: + return self.job_type() == BatchJobType.EVENT_DRIVEN + + def is_scheduled(self) -> bool: + return self.job_type() == BatchJobType.SCHEDULED + + def submitted_at(self): + return self._environment.get(BatchJobEnv.SUBMITTED_AT) + + def executor_mode(self) -> ExecutorMode: + _default = ExecutorMode.CONSISTENT + env = self._environment.get(BatchJobEnv.EXECUTOR_MODE) + if not env: + return _default + try: + return ExecutorMode(env) + except ValueError: + return _default + + def is_concurrent(self) -> bool: + return self.executor_mode() == ExecutorMode.CONCURRENT + + def scheduled_job_name(self) -> str | None: + return self._environment.get(BatchJobEnv.SCHEDULED_JOB_NAME) or None + + def tenant_name(self) -> str | None: + """ + Standard (not event-driven) scans involve one tenant per job. + This env contains this tenant's name + """ + return self._environment.get(BatchJobEnv.TENANT_NAME) + + def platform_id(self) -> str | None: + """ + We can scan platforms within tenants. In case platform id is + provided, this specific platform must be scanned + :return: + """ + return self._environment.get(BatchJobEnv.PLATFORM_ID) + + def is_management_creds_allowed(self) -> bool: + """ + Specifies whether it's allowed to use Maestro's management + credentials to scan a tenant. Default if False because it's not safe. + Those creds have not only read access + """ + return str( + self._environment.get(BatchJobEnv.ALLOW_MANAGEMENT_CREDS) + ).lower() in ENV_TRUE + + def __repr__(self): + return ', '.join([ + f'{k}={v if k not in ENVS_TO_HIDE else HIDDEN_ENV_PLACEHOLDER}' + for k, v in self._environment.items() + ]) diff --git a/src/executor/services/license_manager_service.py b/src/executor/services/license_manager_service.py new file mode 100644 index 000000000..8dd5739e5 --- /dev/null +++ b/src/executor/services/license_manager_service.py @@ -0,0 +1,286 @@ +import re +import time +from datetime import datetime +from functools import cached_property +from http import HTTPStatus +from typing import List, Dict, Optional + +from executor.services.environment_service import BatchEnvironmentService +from helpers.constants import KID_ATTR, ALG_ATTR, JobState, TOKEN_ATTR, \ + EXPIRATION_ATTR +from helpers.log_helper import get_logger +from services.clients.license_manager import LicenseManagerClientFactory +from services.clients.license_manager import LicenseManagerClientInterface +from services.clients.ssm import AbstractSSMClient +from services.setting_service import SettingsService + +_LOG = get_logger(__name__) + +SSM_LM_TOKEN_KEY = 'caas_lm_auth_token_{customer}' +DEFAULT_CUSTOMER = 'default' + + +class BalanceExhaustion(Exception): + def __init__(self, message: str): + self.message = message + + def __str__(self): + return self.message + + +class InaccessibleAssets(Exception): + def __init__( + self, message: str, assets: Dict[str, List[str]], + hr_sep: str, ei_sep: str, i_sep: str, i_wrap: Optional[str] = None + ): + self._assets = self._dissect( + message=message, assets=assets, hr_sep=hr_sep, ei_sep=ei_sep, + i_sep=i_sep, i_wrap=i_wrap + ) + + @staticmethod + def _dissect( + message: str, assets: Dict[str, List[str]], + hr_sep: str, ei_sep: str, i_sep: str, i_wrap: Optional[str] = None + ): + """ + Dissects License Manager response of entity(ies)-not-found message. + Such as: TenantLicense or Ruleset(s):$id(s) - $reason. + param message: str - maintains the raw response message + param assets: Dict[str, List[str]] - source of assets to + param hr_sep: str - head-reason separator, within the response message + param ei_sep: str - entity type - id(s) separator, within the head of + the message + param i_sep: str - separator of entity-identifier(s), within the raw + id(s). + param i_wrap: Optional[str] - quote-type wrapper of each identifier. + """ + each_template = 'Each of {} license-subscription' + head, *_ = message.rsplit(hr_sep, maxsplit=1) + head = head.strip(' ') + if not head: + _LOG.error(f'Response message is not separated by a \'{hr_sep}\'.') + return + + entity, *ids = head.split(ei_sep, maxsplit=1) + ids = ids[0] if len(ids) == 1 else '' + if 's' in entity and entity.index('s') == len(entity) - 1: + ids = ids.split(i_sep) + + ids = [each.strip(i_wrap or '') for each in ids.split(i_sep)] + + if 'TenantLicense' in entity: + ids = [ + asset + for tlk in ids + if tlk in assets + for asset in assets[tlk] or [each_template.format(tlk)] + ] + + return ids + + def __str__(self): + head = 'Ruleset' + + if len(self._assets) > 1: + head += 's' + scope = ', '.join(f'"{each}"' for each in self._assets) + reason = 'are' if len(self._assets) > 1 else 'is' + reason += ' no longer accessible' + return f'{head}:{scope} - {reason}.' + + def __iter__(self): + return iter(self._assets) + + +class LicenseManagerService: + + def __init__(self, settings_service: SettingsService, + ssm_client: AbstractSSMClient, + environment_service: BatchEnvironmentService): + self.settings_service = settings_service + self.ssm_client = ssm_client + self.environment_service = environment_service + + @cached_property + def client(self) -> LicenseManagerClientInterface: + _LOG.debug('Creating license manager client inside LM service') + return LicenseManagerClientFactory(self.settings_service).create() + + def update_job_in_license_manager(self, job_id: str, + created_at: str = None, + started_at: str = None, + stopped_at: str = None, + status: JobState = None): + + auth = self._get_client_token() + if not auth: + _LOG.warning('Client authorization token could be established.') + return None + + response = self.client.update_job( + job_id=job_id, created_at=created_at, started_at=started_at, + stopped_at=stopped_at, status=status, auth=auth + ) + + if response and response.status_code == HTTPStatus.OK.value: + return self.client.retrieve_json(response) + return + + def instantiate_licensed_job_dto(self, job_id: str, customer: str, + tenant: str, + ruleset_map: dict[str, list[str]] + ) -> dict | None: + """ + Mandates licensed Job data transfer object retrieval, + by successfully interacting with LicenseManager providing the + following parameters. + + :parameter job_id: str + :parameter customer: str + :parameter tenant: str + :parameter ruleset_map: Union[Type[None], List[str]] + + :raises: InaccessibleAssets, given the requested content is not + accessible + :raises: BalanceExhaustion, given the job-balance has been exhausted + :return: Optional[Dict] + """ + auth = self._get_client_token(customer=customer) + if not auth: + _LOG.warning('Client authorization token could be established.') + return None + response = self.client.post_job( + job_id=job_id, customer=customer, tenant=tenant, + ruleset_map=ruleset_map, auth=auth + ) + if response is None: + return + + decoded = self.client.retrieve_json(response) or {} + if response.status_code == HTTPStatus.OK.value: + items = decoded.get('items', []) + if len(items) != 1: + _LOG.warning(f'Unexpected License Manager response: {items}.') + item = None + else: + item = items.pop() + return item + + else: + message = decoded.get('message') + if response.status_code == HTTPStatus.NOT_FOUND.value: + raise InaccessibleAssets( + message=message, assets=ruleset_map, + hr_sep='-', ei_sep=':', i_sep=', ', i_wrap='\'' + ) + elif response.status_code == HTTPStatus.FORBIDDEN.value: + raise BalanceExhaustion(message) + + def instantiate_job_sourced_ruleset_list(self, licensed_job_dto: dict): + """ + Mandates production of ruleset dto list, items of which have been + attached to a licensed job. Aforementioned data is retrieved from + a response object of a `Job` instantiation request, denoted + `license_job_dto`. + :parameter licensed_job_dto: dict + :return: List[Dict] + """ + _default = self._default_instance + licensed_job_dto = _default(licensed_job_dto, dict) + content = _default(licensed_job_dto.get('ruleset_content'), dict) + + return [ + self._instantiate_licensed_ruleset_data(ruleset_id=ruleset_id, + source=source) + for ruleset_id, source in content.items() + ] + + def _get_client_token(self, customer: str = None): + secret_name = self.get_ssm_auth_token_name(customer=customer) + cached_auth = self.ssm_client.get_secret_value( + secret_name=secret_name) or {} + cached_token = cached_auth.get(TOKEN_ATTR) + cached_token_expiration = cached_auth.get(EXPIRATION_ATTR) + + if (cached_token and cached_token_expiration and + not self.is_expired(expiration=cached_token_expiration)): + _LOG.debug(f'Using cached lm auth token.') + return cached_token + _LOG.debug(f'Cached lm auth token are not found or expired. ' + f'Generating new token.') + lifetime_minutes = self.environment_service.lm_token_lifetime_minutes() + token = self._generate_client_token( + lifetime=lifetime_minutes, + customer=customer + ) + + _LOG.debug(f'Updating lm auth token in SSM.') + secret_data = { + EXPIRATION_ATTR: int(time.time()) + lifetime_minutes * 60, + TOKEN_ATTR: token + } + self.ssm_client.create_secret( + secret_name=secret_name, + secret_value=secret_data + ) + return token + + @staticmethod + def is_expired(expiration: int): + now = int(datetime.utcnow().timestamp()) + return now >= expiration + + def _generate_client_token(self, lifetime: int, customer: str): + """ + Delegated to derive a custodian-service-token, encoding any given + payload key-value pairs into the claims. + :parameter lifetime: token lifetime in minutes + :parameter customer: str + :return: Union[str, Type[None]] + """ + # not to bring cryptography to global + from services.license_manager_token import LicenseManagerToken + key_data = self.client.client_key_data + kid, alg = key_data.get(KID_ATTR), key_data.get(ALG_ATTR) + if not (kid and alg): + _LOG.warning('LicenseManager Client-Key data is missing.') + return + pem = self.ssm_client.get_secret_value( + secret_name=self.derive_client_private_key_id(kid) + ).get('value') + token = LicenseManagerToken( + customer=customer, + lifetime=lifetime, + kid=kid, + private_pem=pem.encode() + ) + return token.produce() + + @staticmethod + def derive_client_private_key_id(kid: str): + return f'cs_lm_client_{kid}_prk' + + @staticmethod + def _instantiate_licensed_ruleset_data(ruleset_id: str, source: str): + """ + Designated to produce an ambiguously licensed ruleset data, including + a given `ruleset_id` and URI `source`. + :parameter ruleset_id: str + :parameter source: str + :return: Dict + """ + return dict(id=ruleset_id, licensed=True, s3_path=source, + active=True, status=dict(code='READY_TO_SCAN')) + + @staticmethod + def _default_instance(value, _type: type, *args, **kwargs): + return value if isinstance(value, _type) else _type(*args, **kwargs) + + @staticmethod + def get_ssm_auth_token_name(customer: str = None): + if customer: + customer = re.sub(r"[\s-]", '_', customer.lower()) + else: + customer = DEFAULT_CUSTOMER + return SSM_LM_TOKEN_KEY.format(customer=customer) diff --git a/src/services/notification_service.py b/src/executor/services/notification_service.py similarity index 58% rename from src/services/notification_service.py rename to src/executor/services/notification_service.py index 855e7b357..5ede06976 100644 --- a/src/services/notification_service.py +++ b/src/executor/services/notification_service.py @@ -1,27 +1,21 @@ -import base64 -import io import smtplib from email import encoders from email.mime.base import MIMEBase from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText -from typing import List, Union +from typing import Optional from jinja2 import Environment, BaseLoader - -# todo import matplotlib only when necessary 'cause it's huge -# and requires numpy. Importing time can actually be noticeable -import matplotlib.pyplot as plt +from modular_sdk.models.tenant import Tenant from helpers.log_helper import get_logger -from helpers.constants import DEFAULT_SENDER_ATTR, MAX_EMAILS_ATTR, \ - USE_TLS_ATTR -from services.setting_service import SettingsService -from services.ssm_service import SSMService from services.clients.s3 import S3Client +from services.clients.ssm import AbstractSSMClient +from services.setting_service import SettingsService -RED_COLOR_HASH = '#D10000' -GREEN_COLOR_HASH = '#00E400' +DEFAULT_SENDER_ATTR = 'default_sender' +USE_TLS_ATTR = 'use_tls' +MAX_EMAILS_ATTR = 'max_emails' CUSTODIAN_FOLDER_NAME = 'custodian' @@ -30,10 +24,10 @@ class NotificationService: def __init__(self, setting_service: SettingsService, - ssm_service: SSMService, s3_service: S3Client): + ssm_client: AbstractSSMClient, s3_client: S3Client): self.setting_service = setting_service - self.ssm_service = ssm_service - self.s3_service = s3_service + self.ssm = ssm_client + self.s3_client = s3_client self._settings = None self._password = None @@ -62,8 +56,9 @@ def username(self): @property def password(self): if not self._password: - self._password = self.ssm_service.get_secret_value( - self.setting.get('password')) + self._password = self.ssm.get_secret_value( + self.setting.get('password') + ) return self._password @property @@ -81,60 +76,6 @@ def max_emails(self): def __del__(self): self._close_session() - @staticmethod - def build_donut_chart(succeeded: int, failed: int): - plt.clf() - label = f'{succeeded}/{succeeded + failed}' - size_of_groups = [failed, succeeded] - plt.pie(size_of_groups, startangle=60, - colors=[RED_COLOR_HASH, GREEN_COLOR_HASH]) - - # circle to create donut chart from pie chart - circle = plt.Circle((0, 0), 0.8, color='white') - plt.gca().add_artist(circle) - - # label at the center of chart - plt.rcParams.update({'font.size': 24}) - plt.text(0, 0, label, ha='center', va='center') - - buf = io.BytesIO() - plt.savefig(buf, format='png', transparent=True, - bbox_inches='tight', pad_inches=0) - return base64.b64encode(buf.getvalue()).decode('utf-8') - - @staticmethod - def build_pie_chart(labels: List[str], values: List[Union[float, int]], - colors: list = None): - plt.clf() # clean plot from previous data - if len(labels) != len(values): - _LOG.warning( - 'The length of labels does not math the length of values') - return None - for v in values.copy(): - if float(v) == 0.0: - if colors: - colors.remove(colors[values.index(v)]) - labels.remove(labels[values.index(v)]) - values.remove(v) - - if len(values) > 5: - mapping = dict(zip(labels, values)) - sorted_by_values = dict(sorted( - mapping.items(), key=lambda item: item[1], reverse=True)) - other = 0 - for v in list(sorted_by_values.values())[5:]: - other += v - labels = list(sorted_by_values.keys())[:5] + ['other'] - values = list(sorted_by_values.values())[:5] + [other] - - plt.pie(values, labels=labels, autopct='%.1f%%', - textprops={'fontsize': 9}, colors=colors) - - buf = io.BytesIO() - plt.savefig(buf, format='png', transparent=True, - bbox_inches='tight', pad_inches=0) - return base64.b64encode(buf.getvalue()).decode('utf-8') - @staticmethod def build_message(sender_email: str, recipients: list, subject: str, text: str = None, html: str = None, attachment=None, @@ -162,12 +103,25 @@ def build_message(sender_email: str, recipients: list, subject: str, msg.attach(part) return msg.as_string() - def _get_event_driven_template(self): - content = self.s3_service.get_file_content( - bucket_name=self.setting_service.get_template_bucket(), - full_file_name=f'{CUSTODIAN_FOLDER_NAME}/' - f'event_driven_vulnerabilities.html') - return content.decode('utf-8') + def _get_template(self, filename: str) -> Optional[str]: + bucket_name = self.setting_service.get_template_bucket() # move todo + if not bucket_name: + _LOG.warning('Template bucket name is not set in CaaSSettings') + return + content = self.s3_client.get_object( + bucket=bucket_name, + key=f'{CUSTODIAN_FOLDER_NAME}/{filename}', + ) + if not content: + _LOG.warning('Application Service is not configured properly: ' + f'cannot get `{filename}` from `{bucket_name}`') + return content.read().decode() + + def _get_findings_template(self) -> Optional[str]: + return self._get_template('findings.html') + + def _get_schedule_deactivate_template(self): + return self._get_template('schedule_deactivate.html') def _init_session(self): self._num_emails = 0 @@ -192,18 +146,22 @@ def _close_session(self): self._client = None self._num_emails = 0 - def send_event_driven_notification( - self, recipients: list, subject: str, data: dict, - sender_email: str = None - ): + def send_rescheduling_notice_notification( + self, recipients: list, subject: str, tenant: Tenant, + scheduled_job_name: str, + ruleset_list: list[str], customer: str, sender_email: str = None): sender_email = sender_email or self.default_sender or self.username - _LOG.info(f'Going to push event-driven notification from' + _LOG.info(f'Going to push rescheduling-notice notification from' f' {sender_email} to {recipients}. Subject: \'{subject}\'') - template_content = self._get_event_driven_template() + template_content = self._get_schedule_deactivate_template() if template_content: _body = self._render_template( template_content=template_content, - data=data) + data={'scheduled_job_name': scheduled_job_name, + 'account_name': tenant.name, + 'account_id': tenant.project, + 'customer': customer, + 'ruleset_list': ruleset_list}) return self.send_email( sender_email=sender_email, recipients=recipients, message=self.build_message( @@ -211,9 +169,6 @@ def send_event_driven_notification( subject=subject, html=_body ) ) - _LOG.warning('Cannot get event-driven vulnerabilities template. ' - 'Notification was not sent') - return None def send_email(self, sender_email, recipients, message): if not self._client: diff --git a/src/executor/services/policy_service.py b/src/executor/services/policy_service.py new file mode 100644 index 000000000..0b79d7a43 --- /dev/null +++ b/src/executor/services/policy_service.py @@ -0,0 +1,134 @@ +import hashlib +import tempfile +from concurrent.futures import ThreadPoolExecutor +from itertools import chain +from pathlib import Path +from typing import Iterable, TypedDict, Generator + +import msgspec + +from executor.services.environment_service import BatchEnvironmentService +from helpers import download_url +from helpers.constants import Cloud +from helpers.log_helper import get_logger +from models.ruleset import Ruleset +from services.ruleset_service import RulesetService + +_LOG = get_logger(__name__) + + +class PolicyDict(TypedDict): + name: str + resource: str + filters: list + + +class PoliciesService: + __slots__ = '_ruleset_service', '_environment_service' + + def __init__(self, ruleset_service: RulesetService, + environment_service: BatchEnvironmentService): + self._ruleset_service = ruleset_service + self._environment_service = environment_service + + def get_standard_rulesets(self) -> Generator[Ruleset, None, None]: + yield from self._ruleset_service.iter_by_id( + self._environment_service.target_rulesets() + ) + + @staticmethod + def iter_excluding(policies: Iterable[PolicyDict], exclude: set[str] + ) -> Iterable[PolicyDict]: + def check(p: PolicyDict): + return p['name'] not in exclude + + return filter(check, policies) + + @staticmethod + def iter_keeping(policies: Iterable[PolicyDict], keep: set[str] + ) -> Iterable[PolicyDict]: + def check(p: PolicyDict): + if not keep: + return True + return p['name'] in keep + + return filter(check, policies) + + @staticmethod + def without_duplicates(policies: Iterable[PolicyDict] + ) -> Iterable[PolicyDict]: + duplicated = set() + for p in policies: + name = p['name'] + if name in duplicated: + _LOG.debug(f'Duplicated policy found {name}. Skipping') + continue + duplicated.add(name) + yield p + + def get_policies(self, urls: Iterable[str], keep: set[str] | None = None, + exclude: set[str] | None = None) -> list[PolicyDict]: + """ + Downloads multiple files with policies and merges them into one tuple + of policies. + :param urls: + :param keep: + :param exclude: + :return: + """ + lists = [] + decoder = msgspec.json.Decoder(type=dict) + with ThreadPoolExecutor() as ex: + for fp in ex.map(download_url, urls): + lists.append(decoder.decode(fp.getvalue()).get('policies') or ()) + policies = chain.from_iterable(lists) + if exclude: + policies = self.iter_excluding(policies, exclude) + if keep: + policies = self.iter_keeping(policies, keep) + policies = self.without_duplicates(policies) + return list(policies) + + def ensure_event_driven_ruleset(self, cloud: Cloud) -> Path: + """ + For event-driven scans we use full system event-driven rulesets + created beforehand. But only rules that are allowed for tenant + are will be loaded. + Returns local path to event-driven ruleset loading it in case + it has not been loaded yet + """ + fn = hashlib.sha1(cloud.value.encode()).hexdigest() + path = Path(tempfile.gettempdir(), f'{fn}.json') + if path.exists(): + _LOG.info(f'Event-driven ruleset for cloud {cloud} has already ' + f'been downloaded. Returning path to it.') + return path + + ruleset = self._ruleset_service.get_ed_ruleset(cloud) + if ruleset: + with open(path, 'wb') as file: + self._ruleset_service.download(ruleset, file) + else: + _LOG.warning(f'Event-driven ruleset item for cloud {cloud} not ' + f'found in DB. Creating an empty one') + with open(path, 'wb') as file: + file.write(b'{"policies": []}') + return path + + def separate_ruleset(self, from_: Path, + keep: set[str] | None = None, + exclude: set[str] | None = None) -> list[PolicyDict]: + """ + Creates new ruleset file in work_dir filtering the ruleset + in `from_` variable (keeping and excluding specific rules). + This is done in order to reduce the size of rule-sets for event-driven + scans before they are loaded by Custom-Core. + """ + with open(from_, 'rb') as file: + policies = msgspec.json.decode(file.read()).get('policies') or () + if exclude: + policies = self.iter_excluding(policies, exclude) + if keep: + policies = self.iter_keeping(policies, keep) + policies = self.without_duplicates(policies) + return list(policies) diff --git a/src/executor/services/report_service.py b/src/executor/services/report_service.py new file mode 100644 index 000000000..6a9a7b134 --- /dev/null +++ b/src/executor/services/report_service.py @@ -0,0 +1,379 @@ +from pathlib import Path +from typing import Generator, TypedDict, Iterable, cast + +import msgspec +from c7n.provider import get_resource_class +from c7n.resources import load_resources +from modular_sdk.models.tenant import Tenant + +from helpers import json_path_get +from helpers.constants import (Cloud, GLOBAL_REGION, PolicyErrorType) +from helpers.log_helper import get_logger +from services.sharding import LazyPickleShardPart + +_LOG = get_logger(__name__) + + +class StatisticsItem(TypedDict): + policy: str + region: str + tenant_name: str + customer_name: str + start_time: float + end_time: float + api_calls: dict + + +class StatisticsItemFail(StatisticsItem): + reason: str | None + traceback: list[str] + error_type: PolicyErrorType + + +class StatisticsItemSuccess(StatisticsItem): + scanned_resources: int + failed_resources: int + + +class ReportFieldsLoader: + """ + What you need to understand is that each resource type has its unique + json representation of its instances. We cannot know what field inside + that json is considered to be a logical ID, name (or arn in case AWS) of + that resource. Fortunately, this information is present inside Cloud + Custodian and we get get it. + For k8s name is always inside "metadata.name", id - "metadata.uid", + namespace - "metadata.namespace". + For azure they are also always the same due to consistent api. + For AWS, GOOGLE we must retrieve these values for each resource type + """ + class Fields(TypedDict, total=False): + id: str + name: str + arn: str | None + namespace: str | None + + _mapping: dict[str, Fields] = {} + + @classmethod + def _load_for_resource_type(cls, rt: str) -> Fields | None: + """ + Updates mapping for the given resource type. + It must be loaded beforehand + :param rt: + :return: + """ + _LOG.debug(f'Loading meta for resource type: {rt}') + try: + factory = get_resource_class(rt) + except (KeyError, AssertionError): + _LOG.warning(f'Could not load resource type: {rt}') + return + resource_type = getattr(factory, 'resource_type') + _id = getattr(resource_type, 'id', None) + _name = getattr(resource_type, 'name', None) + if not _id: + _LOG.warning('Resource type has no id') + if not _name: + _LOG.warning('Resource type has no name') + + return { + 'id': cast(str, _id), + 'name': cast(str, _name), + 'arn': getattr(resource_type, 'arn', None), + 'namespace': 'metadata.namespace' # for k8s always this way + } + + @classmethod + def get(cls, rt: str) -> Fields: + if rt not in cls._mapping: + fields = cls._load_for_resource_type(rt) + if not isinstance(fields, dict): + return {} + cls._mapping[rt] = fields + return cls._mapping[rt] + + @classmethod + def load(cls, resource_types: tuple = ('*',)): + """ + Loads all the modules. In theory, we must use this class after + performing scan. Till that moment all the necessary resources must be + already loaded + :param resource_types: + :return: + """ + load_resources(set(resource_types)) + + +class RuleRawMetadata: + """ + Simple wrapper over metadata.json that is returned by Cloud Custodian. + Allows to extract some data + """ + __slots__ = ('data',) + + class MetricItem(TypedDict): + MetricName: str + Timestamp: str + Value: float | int + Unit: str # Count | Seconds + + def __init__(self, data: dict): + self.data = data + + @property + def policy(self) -> dict: + return self.data['policy'] + + @property + def name(self) -> str: + return self.policy['name'] + + @property + def resource_type(self) -> str: + return self.policy['resource'] + + @property + def description(self) -> str | None: + return self.policy.get('description') + + @property + def start_time(self) -> float: + return self.data['execution']['start'] + + @property + def end_time(self) -> float: + return self.data['execution']['end_time'] + + @property + def api_calls(self) -> dict: + return self.data.get('api-stats') or {} + + def metric_by_name(self, name: str) -> MetricItem | None: + it = iter(self.data.get('metrics') or []) + it = filter(lambda m: m['MetricName'] == name, it) + return next(it, None) + + @property + def all_resources_count(self) -> int | None: + metric = self.metric_by_name('AllScannedResourcesCount') + if not metric: + return + return metric['Value'] + + @property + def failed_resources_count(self) -> int | None: + metric = self.metric_by_name('ResourceCount') + if not metric: + return + return metric['Value'] + + +class RuleRawOutput: + __slots__ = ('metadata', 'resources') + + def __init__(self, metadata: RuleRawMetadata, + resources: list[dict] | None): + self.metadata = metadata + self.resources = resources # if None, the rule wasn't executed at all + # custodian_run: str + + @property + def was_executed(self) -> bool: + """ + Tells whether this policy was executed at all. Policy was not + executed in case some internal exception or ClientError or + something else. Either way resources.json is not created for such + policies, PolicyException metric is present + :return: + """ + return self.resources is not None + + +class JobResult: + class FormattedItem(TypedDict): # our detailed report item + policy: dict + resources: list[dict] + + RegionRuleOutput = tuple[str, str, RuleRawOutput] + + def __init__(self, work_dir: Path, cloud: Cloud): + self._work_dir = work_dir + self._cloud = cloud + + self._res_decoded = msgspec.json.Decoder(type=list[dict]) + + @staticmethod + def cloud_to_resource_type_prefix() -> dict[Cloud, str]: + return { + Cloud.AWS: 'aws', + Cloud.AZURE: 'azure', + Cloud.GOOGLE: 'gcp', + Cloud.KUBERNETES: 'k8s' + } + + def adjust_resource_type(self, rt: str) -> str: + rt = rt.split('.', maxsplit=1)[-1] + return '.'.join(( + self.cloud_to_resource_type_prefix()[self._cloud], rt + )) + + def _load_raw_rule_output(self, root: Path) -> RuleRawOutput | None: + """ + Folder with rule output contains three files: + 'custodian-run.log' -> logs in text + 'metadata.json' -> dict + 'resources.json' -> list or resources + In case resources.json files does not exist this execution did + not happen due to some exception + :param root: + :return: + """ + # logs = root / 'custodian-run.log' + metadata = root / 'metadata.json' + resources = root / 'resources.json' + + with open(metadata, 'r') as file: + metadata_data = msgspec.json.decode(file.read(), type=dict) + resources_data = None + if resources.exists(): + with open(resources, 'r') as file: + resources_data = self._res_decoded.decode(file.read()) + return RuleRawOutput( + metadata=RuleRawMetadata(metadata_data), + resources=resources_data + ) + + def _extend_resources(self, output: RuleRawOutput): + """ + Adds some report fields (id, name, arn, namespace) to each resource + """ + assert output.was_executed, 'You must provide this method only with policies that was executed without exceptions' # noqa + rt = self.adjust_resource_type(output.metadata.resource_type) + ReportFieldsLoader.load((rt,)) # should be loaded before + fields = ReportFieldsLoader.get(rt) + for res in output.resources: + for field, path in fields.items(): + if not path: + continue + val = json_path_get(res, path) + if not val: + continue + res[field] = val + + def iter_raw(self) -> Generator[RegionRuleOutput, None, None]: + dirs = filter(Path.is_dir, self._work_dir.iterdir()) + for region in dirs: + for rule in filter(Path.is_dir, region.iterdir()): + loaded = self._load_raw_rule_output(rule) + if not loaded: + continue + yield region.name, rule.name, loaded + + @staticmethod + def resolve_azure_locations(it: Iterable[RegionRuleOutput] + ) -> Generator[RegionRuleOutput, None, None]: + """ + The thing is: Custodian Custom Core cannot scan Azure + region-dependently. A rule covers the whole subscription + (or whatever, I don't know) and then each found resource has + 'location' field with its real location. + In order to adhere to AWS logic, when a user wants to receive + reports only for regions he activated, we need to filter out only + appropriate resources. + Also note that Custom Core has such a thing as `AzureCloud`. From + my point of view it's like a mock for every region (because, + I believe, in the beginning Core was designed for AWS and therefore + there are regions). With the current scanner implementation + (3.3.1) incoming `detailed_report` will always have one key: + `AzureCloud` with a list of all the scanned rules. We must remap it. + All the resources that does not contain + 'location' will be congested to 'multiregion' region. + :return: + """ + for _, rule, item in it: + if not item.was_executed: + yield GLOBAL_REGION, rule, item + continue + # was executed + if not item.resources: # we cannot know + yield GLOBAL_REGION, rule, item + continue + # resources exist + _loc_res = {} + for res in item.resources: + loc = res.get('location') or GLOBAL_REGION + _loc_res.setdefault(loc, []).append(res) + for location, resources in _loc_res.items(): + yield location, rule, RuleRawOutput( + metadata=item.metadata, + resources=resources + ) + + def build_default_iterator(self) -> Iterable[RegionRuleOutput]: + it = self.iter_raw() + if self._cloud == Cloud.AZURE: + it = self.resolve_azure_locations(it) + return it + + def statistics(self, tenant: Tenant, failed: dict) -> list[dict]: + """ + :param tenant: + :param failed: + :return: Ready statistics dict + :rtype: StatisticsItemFail | StatisticsItemSuccess + """ + failed = failed or {} + res = [] + for region, rule, output in self.iter_raw(): + metadata = output.metadata + item = { + 'policy': rule, + 'region': region, + 'tenant_name': tenant.name, + 'customer_name': tenant.customer_name, + 'start_time': metadata.start_time, + 'end_time': metadata.end_time, + 'api_calls': metadata.api_calls, + } + if output.was_executed: + item['scanned_resources'] = metadata.all_resources_count + item['failed_resources'] = metadata.failed_resources_count + elif _failed := failed.get((region, rule)): + item['error_type'] = _failed[0] + item['reason'] = _failed[1] + item['traceback'] = _failed[2] + else: + _LOG.warning(f'Rule {rule}:{region} has was not executed but ' + f'failed map does not contain its info') + item['error_type'] = PolicyErrorType.INTERNAL + res.append(item) + return res + + def iter_shard_parts(self) -> Generator[LazyPickleShardPart, None, None]: + for region, rule, output in self.build_default_iterator(): + if not output.was_executed: + continue + self._extend_resources(output) # todo use ijson + yield LazyPickleShardPart.from_resources( + resources=output.resources, + policy=rule, + location=region + ) + + def rules_meta(self) -> dict[str, dict]: + """ + Collect some meta for each policy, currently it's everything that + policy has except filters + :return: + """ + result = {} + for _, rule, output in self.iter_raw(): + meta = { + k: v for k, v in output.metadata.policy.items() + if k not in ('filters', 'name') + } + if 'resource' in meta: + meta['resource'] = self.adjust_resource_type(meta['resource']) + result.setdefault(rule, {}).update(meta) + return result diff --git a/src/exported_module/api/__init__.py b/src/exported_module/api/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/exported_module/api/app.py b/src/exported_module/api/app.py deleted file mode 100644 index 3a1712bea..000000000 --- a/src/exported_module/api/app.py +++ /dev/null @@ -1,171 +0,0 @@ -import importlib -from functools import cached_property -from http import HTTPStatus -from typing import Tuple, Optional - -from bottle import Bottle, request, Route, HTTPResponse - -from connections.auth_extension.cognito_to_jwt_adapter import \ - UNAUTHORIZED_MESSAGE -from exported_module.api.deployment_resources_parser import \ - DeploymentResourcesParser -from helpers import CustodianException, RequestContext -from helpers.constants import CUSTOM_CUSTOMER_ATTR, CUSTOM_ROLE_ATTR, \ - CUSTOM_TENANTS_ATTR, COGNITO_USERNAME -from helpers.constants import PARAM_HTTP_METHOD, \ - PARAM_RESOURCE_PATH -from helpers.log_helper import get_logger -from services import SERVICE_PROVIDER - -_LOG = get_logger(__name__) - -RESPONSE_HEADERS = {'Content-Type': 'application/json'} - - -class DynamicAPI: - def __init__(self, dr_parser: DeploymentResourcesParser): - self.app = Bottle(__name__) - - self.dr_parser = dr_parser - self.api_config = self.dr_parser.generate_api_config() - self.lambda_module_mapping = self.import_api_lambdas() - self.generate_api() - - def generate_api(self): - for request_path, endpoint_meta in self.api_config.items(): - endpoint_methods = endpoint_meta.get('allowed_methods') - for http_method in endpoint_methods: - route = Route(app=self.app, rule=request_path, - method=http_method, - callback=self.api) - self.app.add_route(route=route) - - @cached_property - def paths_without_jwt(self) -> set: - return { - '/caas/signin' - } - - @staticmethod - def get_token_from_header(header: str) -> Optional[str]: - if not header or not isinstance(header, str): - return - parts = header.split() - if len(parts) == 1: - return parts[0] - if len(parts) == 2 and parts[0].lower() == 'bearer': - return parts[1] - - def authorize(self) -> dict: - """ - May raise CustodianException. - Returns a decoded token - :return: dict - """ - header = (request.headers.get('Authorization') or - request.headers.get('authorization')) - token = self.get_token_from_header(header) - if not token: - raise CustodianException( - code=HTTPStatus.UNAUTHORIZED, - content=UNAUTHORIZED_MESSAGE - ) - return SERVICE_PROVIDER.cognito().decode_token(token) - - def api(self, **path_kwargs): - try: - if str(request.path) in self.paths_without_jwt: - token_decoded = {} - else: - token_decoded = self.authorize() - except CustodianException as e: - response = e.response() - return HTTPResponse( - body=response.get('body'), - status=response.get('statusCode'), - headers=response.get('headers') - ) - - config_path = str(request.path) - stage, resource_path = self._split_stage_and_resource(path=config_path) - if request.url_args: - # Builds up a proxy-based path. - for key, value in request.url_args.items(): - # TODO, bug: what if value (which is a value of url args) - # is equal, for instance, to api stage???, or to some - # part of it. Think about it - if value in config_path: - # Bottle-Routing compatible config path. - start = config_path.index(value) - prefix = config_path[:start] - suffix = config_path[start + len(value):] - config_path = prefix + f'<{key}>' + suffix - - # ApiGateway-2-Lambda compatible request path. - start = resource_path.index(value) - prefix = resource_path[:start] - suffix = resource_path[start + len(value):] - resource_path = prefix + '{' + key + '}' + suffix - - endpoint_meta = self.api_config.get(config_path) - # API-GATEWAY lambda proxy integration event - event = { - PARAM_HTTP_METHOD: request.method, - 'headers': dict(request.headers), - 'requestContext': { - 'stage': stage.strip('/'), - 'path': '/' + stage.strip('/') + '/' + resource_path.strip( - '/'), - PARAM_RESOURCE_PATH: resource_path, - 'authorizer': { - 'claims': { - COGNITO_USERNAME: token_decoded.get(COGNITO_USERNAME), - CUSTOM_CUSTOMER_ATTR: token_decoded.get( - CUSTOM_CUSTOMER_ATTR), - CUSTOM_ROLE_ATTR: token_decoded.get(CUSTOM_ROLE_ATTR), - CUSTOM_TENANTS_ATTR: token_decoded.get( - CUSTOM_TENANTS_ATTR) or '' - } - } - }, - 'pathParameters': path_kwargs - } - if request.method == 'GET': - event['queryStringParameters'] = dict(request.query) - else: - event['body'] = request.body.read().decode() - - lambda_module = self.lambda_module_mapping.get( - endpoint_meta.get('lambda_name')) - - response = lambda_module.lambda_handler(event=event, - context=RequestContext()) - return HTTPResponse( - body=response.get('body'), - status=response.get('statusCode'), - headers=response.get('headers') - ) - - @staticmethod - def import_api_lambdas(): - # TODO add Notification handler lambda? - # TODO, merge report-generator and report-generator-handler - _import = importlib.import_module - return { - 'caas-api-handler': - _import('lambdas.custodian_api_handler.handler'), - 'caas-configuration-api-handler': - _import('lambdas.custodian_configuration_api_handler.handler'), - 'caas-report-generator': - _import('lambdas.custodian_report_generator.handler'), - 'caas-report-generation-handler': - _import('lambdas.custodian_report_generation_handler.handler') - } - - @staticmethod - def _split_stage_and_resource(path: str) -> Tuple[str, str]: - """/caas/account/region -> ("/caas", "/account/region")""" - path = path.rstrip('/') - path = path.lstrip('/') - first_slash = path.index('/') - return f'/{path[:first_slash]}', path[first_slash:] diff --git a/src/exported_module/api/deployment_resources_parser.py b/src/exported_module/api/deployment_resources_parser.py deleted file mode 100644 index cfc99fa1f..000000000 --- a/src/exported_module/api/deployment_resources_parser.py +++ /dev/null @@ -1,84 +0,0 @@ -import json -from pathlib import Path -from re import finditer -from typing import Union, Optional, List, Dict - -from helpers.constants import HTTPMethod - -API_GATEWAY_RESOURCE_TYPE = 'api_gateway' -S3_BUCKET_RESOURCE_TYPE = 's3_bucket' - - -class DeploymentResourcesParser: - def __init__(self, file_path: Union[str, Path]): - self._filename = file_path - self._content: Optional[dict] = None - - @property - def content(self) -> dict: - if not self._content: - with open(self._filename, 'r') as file: - self._content = json.load(file) - return self._content - - def get_api_gateway(self) -> List[Dict]: - api_gateway_dr = [] - for name, meta in self.content.items(): - if meta.get('resource_type') == API_GATEWAY_RESOURCE_TYPE: - api_gateway_dr.append(self.content[name]) - return api_gateway_dr - - def generate_api_config(self) -> Dict[str, Dict]: - config = {} - ig_meta = self.get_api_gateway()[0] - stage = ig_meta.get('deploy_stage') - - for url, resource_meta in ig_meta.get('resources', {}).items(): - endpoint_methods = self.get_endpoint_methods(resource_meta) - lambda_name = self.get_endpoint_lambda_name(resource_meta, - endpoint_methods[0]) - - request_path = '/' + stage + self.get_proxied_resource(url) - config[request_path] = { - 'allowed_methods': endpoint_methods, - 'lambda_name': lambda_name - } - return config - - @staticmethod - def get_endpoint_methods(ig_resource_meta: dict) -> List: - methods = [key for key in ig_resource_meta.keys() if key.isupper()] - if 'ANY' in methods: - return list(HTTPMethod()) - return methods - - @staticmethod - def get_endpoint_lambda_name(ig_resource_meta: dict, - http_method: str) -> str: - if http_method not in ig_resource_meta.keys(): - http_method = 'ANY' - return ig_resource_meta.get(http_method, {}).get('lambda_name') - - def get_s3(self): - s3_dr = {} - for name, meta in self.content.items(): - if meta.get('resource_type') == S3_BUCKET_RESOURCE_TYPE: - s3_dr[name] = meta - return s3_dr - - @staticmethod - def get_proxied_resource(resource: str) -> str: - """ - Returns a proxied resource path, compatible with Bottle. - :param resource: str, given '/path/{child_1}/{child_2+}' - - returns `/path//` - :return: str - """ - pattern = '([^{\/]+)(?=})' - for match in finditer(pattern=pattern, string=resource): - suffix = resource[match.end() + 1:] - resource = resource[:match.start() - 1] - path_input = match.group() - path_input = path_input.strip('{+') - resource += f'<{path_input}>' + suffix - return resource diff --git a/src/exported_module/api/license_sync.py b/src/exported_module/api/license_sync.py deleted file mode 100644 index 6988a04c8..000000000 --- a/src/exported_module/api/license_sync.py +++ /dev/null @@ -1,52 +0,0 @@ -import importlib - -from apscheduler.schedulers.background import BackgroundScheduler -from apscheduler.triggers.interval import IntervalTrigger - -from helpers import RequestContext -from helpers.log_helper import get_logger -from services import SERVICE_PROVIDER - -LICENSE_SYNC_SCHEDULED_JOB_NAME = 'custodian-system-license-sync-job' - -_LOG = get_logger(__name__) - - -def sync_license(): - """ - This is a scheduled job itself. It must be in a separate module - from __main__ in case we have multiple possible __main__ (s). And we do - """ - license_module = importlib.import_module( - 'lambdas.custodian_license_updater.handler') - license_module.lambda_handler(event={}, context=RequestContext()) - - -def ensure_license_sync_job(schedule_hours: int = 3): - """ - Make sure you've started the scheduler and set envs before invoking - this function - """ - from helpers.system_customer import SYSTEM_CUSTOMER - - scheduler: BackgroundScheduler = SERVICE_PROVIDER.ap_job_scheduler(). \ - scheduler - _job = scheduler.get_job(LICENSE_SYNC_SCHEDULED_JOB_NAME) - if not _job: - _LOG.info('License sync scheduled job not found, registering one') - scheduler.add_job(sync_license, id=LICENSE_SYNC_SCHEDULED_JOB_NAME, - trigger=IntervalTrigger(hours=schedule_hours)) - else: # exists - _LOG.info('License sync scheduled job has already been registered. ' - 'Schedule hours cli param won`t be used to reschedule the ' - 'job. Remove the job and restart the server if you want ' - 'to reschedule it.') - from models.scheduled_job import ScheduledJob - - _item = ScheduledJob.get(LICENSE_SYNC_SCHEDULED_JOB_NAME) - _item.update_with( - customer=SYSTEM_CUSTOMER, - schedule=f'rate({schedule_hours} ' - f'{"hour" if schedule_hours == 1 else "hours"})' - ) - _item.save() diff --git a/src/exported_module/requirements.txt b/src/exported_module/requirements.txt deleted file mode 100644 index 95ca99a6a..000000000 --- a/src/exported_module/requirements.txt +++ /dev/null @@ -1,41 +0,0 @@ -attrs==21.2.0 -pynamodb==5.3.2 -boto3==1.26.80 -botocore==1.29.80 -certifi==2021.5.30 -chardet==4.0.0 -PyJWT==2.8.0 -colorama==0.4.1 -configobj==5.0.6 -idna==2.10 -jsonpickle==1.3 -jsonschema==3.2.0 -pyrsistent==0.17.3 -python-dateutil==2.8.2 -regex==2021.3.17 -requests==2.31.0 -six==1.16.0 -tqdm==4.19.5 -zipp==3.4.1 -dynamodb-json==1.3 -pymongo==4.5.0 -bcrypt==4.0.1 -boltons==23.0.0 -bottle==0.12.19 -hvac==1.2.1 -psutil==5.8.0 -XlsxWriter==3.0.3 -typing_extensions==4.8.0 -pytablewriter==0.64.2 # currently not used -ruamel.yaml==0.17.16 -gunicorn==20.1.0 -pycryptodome==3.19.0 -APScheduler==3.10.4 -pydantic==1.10.2 -cachetools==5.3.1 -python-dotenv==1.0.0 -dacite==1.8.1 -et-xmlfile==1.1.0 -openpyxl==3.0.10 -cryptography==3.4.7 -modular-sdk==3.3.2 \ No newline at end of file diff --git a/src/exported_module/scripts/__init__.py b/src/exported_module/scripts/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/exported_module/scripts/init_minio.py b/src/exported_module/scripts/init_minio.py deleted file mode 100644 index 4894ed9e4..000000000 --- a/src/exported_module/scripts/init_minio.py +++ /dev/null @@ -1,36 +0,0 @@ -from typing import Iterable - -from services import SERVICE_PROVIDER - -environment = SERVICE_PROVIDER.environment_service() -BUCKETS = [ - environment.get_statistics_bucket_name(), - environment.get_ssm_backup_bucket(), - environment.get_rulesets_bucket_name(), - environment.default_reports_bucket_name(), - environment.get_templates_bucket_name(), - environment.get_metrics_bucket_name() -] - - -def create_buckets(buckets: Iterable[str]) -> None: - client = SERVICE_PROVIDER.s3() - env = SERVICE_PROVIDER.environment_service() - for buckets_name in buckets: - if client.is_bucket_exists(bucket_name=buckets_name): - print(f'Bucket with name \'{buckets_name}\' already exists. ' - f'Skipping..') - continue - client.create_bucket( - bucket_name=buckets_name, - region=env.aws_region() or 'eu-central-1' - ) - print(f'Bucket {buckets_name} created.') - - -def init_minio(): - create_buckets(BUCKETS) - - -if __name__ == '__main__': - init_minio() diff --git a/src/exported_module/scripts/init_mongo.py b/src/exported_module/scripts/init_mongo.py deleted file mode 100644 index 2a08baae0..000000000 --- a/src/exported_module/scripts/init_mongo.py +++ /dev/null @@ -1,111 +0,0 @@ -from typing import List, Tuple, Union - -import pymongo -from modular_sdk.models.region import RegionModel - -from helpers.log_helper import get_logger -from models.credentials_manager import CredentialsManager -from models.job import Job -from models.licenses import License -from models.modular import BaseModel -from models.modular.application import Application -from modular_sdk.models.customer import Customer -from models.modular.parents import Parent -from modular_sdk.models.tenant_settings import TenantSettings -from modular_sdk.models.tenant import Tenant -from modular_sdk.models.job import Job as ModularJob -from models.policy import Policy -from models.role import Role -from models.rule import Rule -from models.batch_results import BatchResults -from models.event import Event -from models.rule_source import RuleSource -from models.ruleset import Ruleset -from models.scheduled_job import ScheduledJob -from models.setting import Setting -from models.user import User -from models.customer_metrics import CustomerMetrics -from models.job_statistics import JobStatistics -from models.tenant_metrics import TenantMetrics -from models.rule_meta import RuleMeta - - -_LOG = get_logger(__name__) - -MAIN_DYNAMODB_INDEX_KEY = 'main' -GLOBAL_SECONDARY_INDEXES = 'global_secondary_indexes' -LOCAL_SECONDARY_INDEXES = 'local_secondary_indexes' -INDEX_NAME_ATTR, KEY_SCHEMA_ATTR = 'index_name', 'key_schema' - -ATTRIBUTE_NAME_ATTR, KEY_TYPE_ATTR = 'AttributeName', 'KeyType' -HASH_KEY_TYPE, RANGE_KEY_TYPE = 'HASH', 'RANGE' - - -def convert_index(key_schema: dict) -> Union[str, List[Tuple]]: - if len(key_schema) == 1: - _LOG.info('Only hash key found for the index') - return key_schema[0][ATTRIBUTE_NAME_ATTR] - elif len(key_schema) == 2: - _LOG.info('Both hash and range keys found for the index') - result = [None, None] - for key in key_schema: - if key[KEY_TYPE_ATTR] == HASH_KEY_TYPE: - _i = 0 - elif key[KEY_TYPE_ATTR] == RANGE_KEY_TYPE: - _i = 1 - else: - raise ValueError(f'Unknown key type: {key[KEY_TYPE_ATTR]}') - result[_i] = (key[ATTRIBUTE_NAME_ATTR], pymongo.DESCENDING) - return result - else: - raise ValueError(f'Unknown key schema: {key_schema}') - - -def create_indexes_for_model(model: BaseModel): - table_name = model.Meta.table_name - collection = model.mongodb_handler().mongodb.collection(table_name) - collection.drop_indexes() - - hash_key = getattr(model._hash_key_attribute(), 'attr_name', None) - range_key = getattr(model._range_key_attribute(), 'attr_name', None) - _LOG.info(f'Creating main indexes for \'{table_name}\'') - if hash_key and range_key: - collection.create_index([(hash_key, pymongo.ASCENDING), - (range_key, pymongo.ASCENDING)], - name=MAIN_DYNAMODB_INDEX_KEY) - elif hash_key: - collection.create_index(hash_key, name=MAIN_DYNAMODB_INDEX_KEY) - else: - _LOG.error(f'Table \'{table_name}\' has no hash_key and range_key') - - indexes = model._get_schema() # GSIs & LSIs, # only PynamoDB 5.2.1+ - gsi = indexes.get(GLOBAL_SECONDARY_INDEXES) - lsi = indexes.get(LOCAL_SECONDARY_INDEXES) - if gsi: - _LOG.info(f'Creating global indexes for \'{table_name}\'') - for i in gsi: - index_name = i[INDEX_NAME_ATTR] - _LOG.info(f'Processing index \'{index_name}\'') - collection.create_index( - convert_index(i[KEY_SCHEMA_ATTR]), name=index_name) - _LOG.info(f'Index \'{index_name}\' was created') - _LOG.info(f'Global indexes for \'{table_name}\' were created!') - if lsi: - pass # write this part if at least one LSI is used - - -def init_mongo(): - models = [Job, ScheduledJob, CredentialsManager, - License, Policy, Role, Rule, RuleSource, Ruleset, Setting, - User, Event, BatchResults, TenantMetrics, JobStatistics, - CustomerMetrics, RuleMeta] - modular_models = [ - Customer, Tenant, Parent, RegionModel, TenantSettings, Application, - ModularJob - ] - for model in models + modular_models: - create_indexes_for_model(model) - - -if __name__ == '__main__': - init_mongo() diff --git a/src/exported_module/scripts/init_vault.py b/src/exported_module/scripts/init_vault.py deleted file mode 100644 index c4c8086d2..000000000 --- a/src/exported_module/scripts/init_vault.py +++ /dev/null @@ -1,30 +0,0 @@ -import base64 -import os - -from services import SERVICE_PROVIDER - - -def generate_256_bit() -> str: - return base64.b64encode(os.urandom(24)).decode('utf-8') - - -def init_vault(): - ssm = SERVICE_PROVIDER.ssm_service() - if ssm.enable_secrets_engine(): - print('Vault engine was enabled') - else: - print('Vault engine has been already enabled') - if ssm.get_secret_value('token'): - print('Token inside Vault already exists. Skipping...') - return - ssm.create_secret_value( - secret_name='token', - secret_value={ - "phrase": generate_256_bit() - } - ) - print('Token was set to Vault') - - -if __name__ == '__main__': - init_vault() diff --git a/src/exported_module/scripts/run.sh b/src/exported_module/scripts/run.sh deleted file mode 100644 index 35bce99e9..000000000 --- a/src/exported_module/scripts/run.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/bash - -DEFAULT_CUSTOMER_NAME=ADMIN_CUSTOMER -DEFAULT_CUSTOMER_OWNER=admin@gmail.com -DEFAULT_USERNAME=admin - -echo "Creating the necessary buckets in Minio" -python main.py create_buckets - -echo "Creating the necessary engine and token in Vault" -python main.py init_vault - -echo "Creating indexes in MongoDB" -python main.py create_indexes - -echo "Creating the SYSTEM customer" -lm_api_link_param="" -if [ ! -z "$LM_API_LINK" ]; then - lm_api_link_param+="--lm_api_link $LM_API_LINK" -fi -# TODO think about using private ip addresses instead of MINIKUBE_IP and expose the API via kubectl port-forward -python main.py env update_settings $lm_api_link_param - -echo "Creating the system user" -python main.py env create_system_user --username admin --api_link http://${MINIKUBE_IP}:30300/caas -echo "Creating the standard customer" -python main.py env create_customer --customer_name "${CUSTOMER_NAME:-$DEFAULT_CUSTOMER_NAME}" --admins "${CUSTOMER_OWNER:-$DEFAULT_CUSTOMER_OWNER}" - -echo "Creating a user within the customer" -python main.py env create_user --username "${USERNAME:-$DEFAULT_USERNAME}" --customer_name "${CUSTOMER_NAME:-$DEFAULT_CUSTOMER_NAME}" - -echo "Starting the server" - -python main.py run --gunicorn \ No newline at end of file diff --git a/src/handlers/__init__.py b/src/handlers/__init__.py index e69de29bb..dc78ee6ec 100644 --- a/src/handlers/__init__.py +++ b/src/handlers/__init__.py @@ -0,0 +1,24 @@ +from abc import ABC, abstractmethod +from functools import cached_property +from typing import Callable +from helpers.constants import CustodianEndpoint + +Mapping = dict[CustodianEndpoint, dict[str, Callable]] + + +class AbstractHandler(ABC): + @classmethod + @abstractmethod + def build(cls) -> 'AbstractHandler': + """ + Builds the instance of the class + """ + + @cached_property + @abstractmethod + def mapping(self) -> Mapping: + """ + { + CustodianEndpoint.JOBS: {HTTPMethod.GET: self.get_jobs} + } + """ diff --git a/src/handlers/abstracts/__init__.py b/src/handlers/abstracts/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/handlers/abstracts/abstract_credentials_manager_handler.py b/src/handlers/abstracts/abstract_credentials_manager_handler.py deleted file mode 100644 index a55903169..000000000 --- a/src/handlers/abstracts/abstract_credentials_manager_handler.py +++ /dev/null @@ -1,438 +0,0 @@ -from abc import abstractmethod -from http import HTTPStatus -from typing import Optional, List - -from botocore.exceptions import ClientError - -from handlers.abstracts.abstract_handler import AbstractHandler -from helpers import build_response -from helpers.constants import CLOUD_IDENTIFIER_ATTR, CLOUD_ATTR, \ - TRUSTED_ROLE_ARN, TENANTS_ATTR, CUSTOMER_ATTR, TENANT_ATTR -from helpers.log_helper import get_logger -from services import SERVICE_PROVIDER -from services.credentials_manager_service import CredentialsManagerService -from services.modular_service import ModularService -from services.user_service import CognitoUserService - -CREDENTIALS = 'Credentials' - -_LOG = get_logger(__name__) - - -class AbstractCredentialsManagerHandler(AbstractHandler): - - def __init__(self, credential_manager_service: CredentialsManagerService, - user_service: CognitoUserService, - modular_service: ModularService): - self.credential_manager_service = credential_manager_service - self.user_service = user_service - self.modular_service = modular_service - self.sts_client = SERVICE_PROVIDER.sts_client() - - @abstractmethod - def get_configuration_object( - self, cloud: str, cloud_identifier: str, - customer: Optional[str] = None, tenants: Optional[List[str]] = None - ): - """ - Restricts access to a derived CredentialsManager entity, - adhering either to the customer or tenant scope. - :param cloud: str, cloud value of the configuration - :param cloud_identifier: str, cid valid of the configuration - :param customer: Optional[str] - :param tenants: Optional[List[str]] - :return: Optional[CredentialsManager] - """ - raise NotImplementedError - - @staticmethod - @abstractmethod - def is_accessible( - granted_customer: str, granted_tenants: List[str], - target_customer: str, target_tenant: str - ): - """ - Predicates whether a subject customer-tenant scope is accessible, - based on the granted customer and tenants. - :param granted_customer: str - :param granted_tenants: List[str] - :param target_tenant: str - :param target_customer: str - :return: bool - """ - - - @abstractmethod - def configuration_object_exist(self, *args, **kwargs): - raise NotImplementedError() - - @abstractmethod - def create_configuration_object(self, *args, **kwargs): - raise NotImplementedError() - - @abstractmethod - def save_configuration_object(self, *args, **kwargs): - raise NotImplementedError() - - @abstractmethod - def delete_configuration_object(self, *args, **kwargs): - raise NotImplementedError() - - @property - @abstractmethod - def entity_name(self): - raise NotImplementedError() - - @property - @abstractmethod - def hash_key_attr_name(self): - raise NotImplementedError() - - @property - @abstractmethod - def range_key_attr_name(self): - raise NotImplementedError() - - @abstractmethod - def get_configuration_object_dto(self, *args, **kwargs): - raise NotImplementedError() - - @property - @abstractmethod - def default_params_mapping(self): - raise NotImplementedError() - - @property - @abstractmethod - def required_params_list(self): - raise NotImplementedError() - - @property - @abstractmethod - def full_params_list(self): - raise NotImplementedError() - - @property - @abstractmethod - def update_params_list(self): - raise NotImplementedError - - @property - @abstractmethod - def param_type_mapping(self): - raise NotImplementedError() - - def _basic_get_handler(self, event: dict): - _LOG.debug(f'Event: {event}') - - _LOG.debug(f'Get {self.entity_name} action') - - cloud_identifier = event.get(self.hash_key_attr_name) - cloud = event.get(self.range_key_attr_name) - - # AbstractHandler input. - customer = event.get(CUSTOMER_ATTR) - - # RestrictionService input. - tenants = event.get(TENANTS_ATTR) - - entities = [] - _log = f'Fetching entities by fields: cloud={cloud}, '\ - f'cloud_identifier={cloud_identifier}' - - if customer: - _log += f'. Obscuring the output view by \'{customer}\' customer' - if tenants: - _tenants = ','.join(map("'{}'".format, tenants)) - _log += f'. Restricting by {_tenants} tenant(s) scope' - - _LOG.debug(f'{_log}.') - - if cloud and cloud_identifier: - entity = self.get_configuration_object( - cloud=cloud, cloud_identifier=cloud_identifier, - customer=customer, tenants=tenants - ) - if entity: - entities.append(entity) - - else: - entities = self.credential_manager_service.inquire( - cloud=cloud, - cloud_identifier=cloud_identifier, - customer=customer, tenants=tenants - ) - - return build_response( - code=HTTPStatus.OK, - content=( - self.get_configuration_object_dto(entity) - for entity in entities - ) - ) - - def _basic_create_handler(self, event: dict): - """ - When create credentials manager need to specify next required fields: - :cloud_identifier - :trusted_role_arn - :cloud - (required_params_list) - - Status of 'enabled' will be set to True automatically (check - default_params_mapping) - - :tenant - :customer - Are obtained from a derived account entity. - """ - - _LOG.debug(f'Creating {self.entity_name}: {event}') - - event = self.validations(event, # todo refactor this validations - check_on_existence=True, - reverse_existence=False, - if_cloud_aws_validate_trusted_role=True, - validate_account_with_specified_id=True, - assume_role=True) - - ready_for_save_data = self._replace_dict_params( - event=event, - full_params_list=self.full_params_list, - default_params_mapping=self.default_params_mapping - ) - - _LOG.debug(f'{self.entity_name} data: {ready_for_save_data}') - - result = self._save_entity( - entity_data=ready_for_save_data, - entity_name=self.entity_name, - create_func=self.create_configuration_object, - save_func=self.save_configuration_object, - get_dto_func=self.get_configuration_object_dto - ) - - return build_response(code=HTTPStatus.CREATED, content=result) - - def _basic_update_handler(self, event: dict): - """ - 1. Checks user (permissions) - 2. Validate data from event - 3. Get element from DB to update - 4. Change field to new in _replace_obj_params() - """ - - _LOG.debug(f'Update {self.entity_name} action') - - # AbstractHandler input. - customer = event.get(CUSTOMER_ATTR) - - # RestrictionService input. - tenants = event.get(TENANTS_ATTR) - - _LOG.debug('Validate event param types') - - cloud_identifier = event.get(self.hash_key_attr_name) - cloud = event.get(self.range_key_attr_name) - role_arn = event.get(TRUSTED_ROLE_ARN) - if role_arn: - self.try_to_assume_role(role_arn) - - _LOG.debug(f'{self.entity_name} to update: ' - f'{cloud} and {cloud_identifier}') - - entity = self.get_configuration_object( - cloud=cloud.lower(), cloud_identifier=cloud_identifier, - customer=customer, tenants=tenants - ) - - if not entity: - return build_response( - code=HTTPStatus.NOT_FOUND, - content=f'Credentials configuration for {cloud} cloud and ' - f'{cloud_identifier} does not exist.' - ) - - entity = self._replace_obj_params( - event=event, - entity=entity, - full_params_list=self.update_params_list, - entity_name=self.entity_name, - save_func=self.save_configuration_object - ) - - return build_response( - code=HTTPStatus.OK, - content=self.get_configuration_object_dto(entity) - ) - - def _basic_delete_handler(self, event: dict): - """ - 1. Checks user (permissions) - 2. Validate data from event - 3. Get entity from DB - 4. Delete entity - """ - - _LOG.debug(f'Delete {self.entity_name} action') - - # AbstractHandler input. - customer = event.get(CUSTOMER_ATTR) - - # RestrictionService input. - tenants = event.get(TENANTS_ATTR) - - cloud_identifier = event.get(self.hash_key_attr_name) - cloud = event.get(self.range_key_attr_name) - - entity = self.get_configuration_object( - cloud=cloud.lower(), cloud_identifier=cloud_identifier, - customer=customer, tenants=tenants - ) - - if not entity: - return build_response( - code=HTTPStatus.NOT_FOUND, - content=f'Credentials configuration for {cloud} cloud and ' - f'{cloud_identifier} does not exist.' - ) - - self.delete_configuration_object(entity) - - return build_response( - code=HTTPStatus.OK, - content=f'Credentials configuration for {cloud} cloud and ' - f'{cloud_identifier} cloud identifier has been deleted') - - def validations(self, - event, - check_on_existence=False, - reverse_existence=False, - assume_role=False, - validate_account_with_specified_id=False, - if_cloud_aws_validate_trusted_role=False): - """ - This method check validation on next: - 1. Correct cloud attribute in event - 2. req_params_list - check that all required params are in event - 3. check_on_existence - check if object in DB -> if yes -> error - 4. reverse_existence - if not in DB -> error - 5. Check if validate_account_with_specified_id exists in CaasAccount table - * Attaching `tenant`, `customer` attributes to the event variable. - 6. try to assume role - 7. if cloud - aws, validate trusted role in event - """ - - cloud = event.get(CLOUD_ATTR) - if cloud: - event[CLOUD_ATTR] = cloud.lower() - - if check_on_existence: - if self.check_on_existence(event): - debug_message = f"{self.entity_name.capitalize()} with " \ - f"cloud '{event.get(CLOUD_ATTR)}', and " \ - f"cloud identifier" \ - f" '{event.get(CLOUD_IDENTIFIER_ATTR)}' " \ - f"already exists" - - _LOG.warning(debug_message) - return build_response( - code=HTTPStatus.CONFLICT, - content=debug_message) - - if reverse_existence: - if not self.check_on_existence(event): - debug_message = f'{self.entity_name.capitalize()} with next ' \ - f'fields: {event.get(CLOUD_ATTR)}' \ - f'{event.get(CLOUD_IDENTIFIER_ATTR)} ' \ - f'does not exist' - - _LOG.warning(debug_message) - return build_response( - code=HTTPStatus.NOT_FOUND, - content=debug_message) - - if validate_account_with_specified_id: - cloud = event.get(CLOUD_ATTR) - - if event.get(CLOUD_IDENTIFIER_ATTR): - cloud_identifier = event.get(CLOUD_IDENTIFIER_ATTR) - tenant = next( - self.modular_service.i_get_tenants_by_acc(cloud_identifier), - None - ) - - persistence_issue = \ - f"Tenant:\'{cloud_identifier}\' within " \ - f"\'{cloud.upper()}\' " \ - f"cloud does not exist." - - if not tenant: - return build_response( - code=HTTPStatus.NOT_FOUND, - content=persistence_issue - ) - - if cloud and tenant.cloud.upper() != cloud.upper(): - message = f"Account:\'{cloud_identifier}\'" \ - f" does not exist within \'{cloud.upper()}\'" \ - f" cloud." - return build_response( - code=HTTPStatus.NOT_FOUND, - content=message - ) - - if not self.is_accessible( - granted_customer=event.get(CUSTOMER_ATTR), - granted_tenants=event.get(TENANTS_ATTR), - target_customer=tenant.customer_name, - target_tenant=tenant.name - ): - # Generic, non-leaking response. - return build_response( - code=HTTPStatus.NOT_FOUND, - content=persistence_issue - ) - - # Retain customer and tenant attributes, for instantiation. - event[TENANT_ATTR] = tenant.name - event[CUSTOMER_ATTR] = tenant.customer_name - - if if_cloud_aws_validate_trusted_role: - if event.get(CLOUD_ATTR): - cloud = event.get(CLOUD_ATTR).lower() - if cloud == 'aws': - if not event.get(TRUSTED_ROLE_ARN): - message = f"When specified cloud is 'aws', " \ - f"than parameter '{TRUSTED_ROLE_ARN}' should " \ - f"be in request" - return build_response( - code=HTTPStatus.NOT_FOUND, - content=message) - - if assume_role: - self.try_to_assume_role(role_arn=event.get(TRUSTED_ROLE_ARN)) - - return event - - def check_on_existence(self, event): - cloud_identifier = event.get(self.hash_key_attr_name) - cloud = event.get(self.range_key_attr_name) - - if self.configuration_object_exist(cloud, cloud_identifier): - return True - - return False - - def try_to_assume_role(self, role_arn): - try: - self.sts_client.assume_role(role_arn=role_arn).get(CREDENTIALS) - except (ClientError, Exception) as e: - message = f"Can't assume role with specified {TRUSTED_ROLE_ARN} " \ - f"'{role_arn}'" - _LOG.warning(f'{message}, due to - {e}') - return build_response( - code=HTTPStatus.BAD_REQUEST, - content=message - ) - diff --git a/src/handlers/abstracts/abstract_handler.py b/src/handlers/abstracts/abstract_handler.py deleted file mode 100644 index 0b521717b..000000000 --- a/src/handlers/abstracts/abstract_handler.py +++ /dev/null @@ -1,125 +0,0 @@ -import abc -from http import HTTPStatus -from typing import Callable, Dict, Optional, Any - -from helpers import build_response -from helpers.log_helper import get_logger - -_LOG = get_logger(__name__) - -ENTITY_DOES_NOT_EXIST_MESSAGE = 'The requested entity was not found' -ENTITY_ALREADY_EXISTS_MESSAGE = 'The requested entity already exists' - - -class AbstractHandler: - - @abc.abstractmethod - def define_action_mapping(self): - """ - Should return dict of the following format: - { - ACTION_NAME: { - METHOD: self.handler_func - } - } - """ - raise NotImplementedError() - - @staticmethod - def _replace_dict_params(event, - full_params_list: list, - default_params_mapping: dict): - """Replace all parameters that can be default and then get from - event all parameters""" - - entity_data = {} - for param in full_params_list: - if param not in event and param in default_params_mapping: - entity_data[param] = default_params_mapping.get(param) - else: - param_value = event.get(param) - if param_value is not None: - entity_data[param] = param_value - return entity_data - - @staticmethod - def _save_entity(entity_data: dict, - entity_name: str, - create_func: Callable, - save_func: Callable, - get_dto_func: Callable): - try: - entity = create_func(entity_data) - _LOG.debug(f'{entity_name} configuration object json: ' - f'{entity.get_json()}') - save_func(entity) - except (ValueError, TypeError) as e: - return build_response(code=HTTPStatus.BAD_REQUEST, - content=str(e)) - return get_dto_func(entity) - - @staticmethod - def _replace_obj_params(event: dict, - entity: object, - full_params_list: list, - entity_name: str, - save_func: Callable): - """ - For each parameter in DB model: - 1. Get it from event - 2. Check if different from existing - 3. Set attribute to new - 4. Then save this attribute to DB - """ - - try: - for attr in full_params_list: - attr_value = event.get(attr) - if attr_value is not None \ - and attr_value != getattr(entity, attr): - _LOG.debug(f'Setting {attr} attribute to {attr_value} ' - f'for {entity_name}') - setattr(entity, attr, attr_value) - - save_func(entity) - except (TypeError, ValueError) as e: - return build_response(code=HTTPStatus.BAD_REQUEST, - content=str(e)) - return entity - - def _assert_exists(self, entity: Optional[Any] = None, - message: str = None, **kwargs) -> None: - if not entity: - _message = (message or ENTITY_DOES_NOT_EXIST_MESSAGE).format( - **kwargs) - _LOG.info(_message) - return build_response(code=HTTPStatus.NOT_FOUND, - content=_message) - - def _assert_does_not_exist(self, entity: Optional[Any] = None, - message: str = None, **kwargs) -> None: - if entity: - _message = (message or ENTITY_ALREADY_EXISTS_MESSAGE).format( - **kwargs) - _LOG.info(_message) - return build_response(code=HTTPStatus.CONFLICT, - content=_message) - - -class AbstractComposedHandler(AbstractHandler): - - def __init__(self, resource_map: Dict[str, Dict[str, AbstractHandler]]): - self._action_mapping = resource_map - - def define_action_mapping(self): - return { - resource: { - method: handler.define_action_mapping(). - get(resource, dict()).get(method) - for method, handler in method_map.items() - } - for resource, method_map in self._action_mapping.items() - } - - def define_handler_mapping(self) -> Dict[str, Dict[str, AbstractHandler]]: - return self._action_mapping diff --git a/src/handlers/abstracts/abstract_modular_entity_handler.py b/src/handlers/abstracts/abstract_modular_entity_handler.py deleted file mode 100644 index 2af26a500..000000000 --- a/src/handlers/abstracts/abstract_modular_entity_handler.py +++ /dev/null @@ -1,161 +0,0 @@ -from abc import abstractmethod -from http import HTTPStatus -from typing import Iterable, Callable, Dict, Union, Type, List, Optional - -from modular_sdk.models.pynamodb_extension.base_model import LastEvaluatedKey - -from handlers.abstracts.abstract_handler import AbstractHandler -from helpers import build_response -from helpers.constants import NEXT_TOKEN_ATTR -from helpers.log_helper import get_logger -from services.modular_service import ModularService - -_LOG = get_logger(__name__) - -UNRESOLVABLE_ERROR = 'Request has run into an issue, which could not' \ - ' be resolved.' - -BEGUN_TEMPLATE = '{action} has begun: {event}.' -FAILED_TEMPLATE = '{action} has failed.' -SUSPENDED_TEMPLATE = '{action} has suspended the execution.' -RESPONSE_TEMPLATE = '{action} is going to respond with {code}' \ - ' status-code and "{content}" content.' - -ENTITY_TEMPLATE = '{entity}:\'{id}\'' - - -class AbstractModularEntityHandler(AbstractHandler): - """ - Provides abstract behaviour of Maestro Common Domain Model Entities. - """ - - _code: Optional[int] - _content: Optional[str] - _meta: Optional[dict] - - def __init__(self, modular_service: ModularService): - self.modular_service = modular_service - self._reset() - - def _reset(self): - # Denoting response variables. - self._code = None - self._content = None - self._meta = None - - @property - def response(self): - """ - Delegated to dispatch a response of a pending request, based on - a running instance response-mandating variables. - Given absence of assigned values, default internal, unresolvable - errors are to dispatch. - :raises: ApplicationException - :return: Dict - """ - _code = self._code or HTTPStatus.INTERNAL_SERVER_ERROR - _content = UNRESOLVABLE_ERROR if self._content is None else \ - self._content - _meta = self._meta or {} - self._reset() - return build_response(code=_code, content=_content, meta=_meta) - - @property - @abstractmethod - def entity(self) -> str: - raise NotImplementedError - - @property - @abstractmethod - def responsibilities(self) -> Dict[ - str, Iterable[Callable[[Dict], Union[Dict, Type[None]]]] - ]: - """ - Returns a dictionary object, maintaining names of responsibilities - which reference iterable sequences of callable items, each of - which connote concern-segregated step of a flow. - :return: Iterable[Iterable] - """ - raise NotImplementedError - - @abstractmethod - def _produce_response_dto(self, event: Optional[Dict] = None) -> \ - Union[str, Dict, List, Type[None]]: - """ - Mandates derivation of a query-response data transfer object. - :parameter event: Optional[Dict] - :return: Union[Dict, List, Type[None]] - """ - raise NotImplementedError - - @property - @abstractmethod - def attributes_to_log(self) -> Dict[Union[str, Type[None]], Iterable[str]]: - """ - Returns attribute names which are meant to be logged during a - particular concern-flow. - Note: denoting a None key, explicitly stresses a default subject list. - :return: Dict[Union[str, Type[None]], Iterable[str]] - """ - raise NotImplementedError - - def last_evaluated_key(self) -> LastEvaluatedKey: - """Returns last evaluated key instance which contains key from a - request the class is responsible for, - if the next key actually exists""" - return LastEvaluatedKey() # empty - - def _process_action(self, event: Dict, action: str): - """ - Base action processor, which provides sequential execution - of each `step` in respective chain-alike flow. - Given any aforementioned step has failed to produce an event - for the following one to feed on - the execution halts. - :raises: CustodianException - :return: Dict[str, str] - """ - action = f'{action} {self.entity.capitalize()}' - _event_input = ','.join(f'\'{k}\' = {v}' for k, v in event.items()) - - _LOG.info(BEGUN_TEMPLATE.format(action=action, event=_event_input)) - - # Steps through each concreate responsibility chain - for name, flow in self.responsibilities.items(): - - if not event: - break - - for index, step in enumerate(flow, 1): - # Merges all request-bound attributes - _vars = {**self.__dict__, **event} - - # Prepares an attribute-list to log for a step - to_log: dict = self.attributes_to_log - log_key = name if name in self.attributes_to_log else None - attrs: Iterable = to_log.get(log_key, list(_vars)) - - _action = f'{name.capitalize()}:{index})' - - _i_input = (f'\'{k}\' = {_vars.get(k)}' for k in attrs) - _i_step = ', '.join(_i_input) - - _LOG.info(BEGUN_TEMPLATE.format(action=_action, event=_i_step)) - - event = step(event) - if not event: - _LOG.warning(SUSPENDED_TEMPLATE.format(action=_action)) - break - - if event: - dto = self._produce_response_dto(event=event) - - self._code = HTTPStatus.OK if dto is not None else self._code - self._content = dto if dto is not None else self._content - _lek = self.last_evaluated_key() - self._meta = { - NEXT_TOKEN_ATTR: _lek.serialize()} if _lek else None - - _log = dict(action=action, content=self._content, code=self._code) - _LOG.info(RESPONSE_TEMPLATE.format(**_log)) - - return self.response diff --git a/src/handlers/abstracts/abstract_user_handler.py b/src/handlers/abstracts/abstract_user_handler.py deleted file mode 100644 index be73d483a..000000000 --- a/src/handlers/abstracts/abstract_user_handler.py +++ /dev/null @@ -1,173 +0,0 @@ -from abc import abstractmethod -from http import HTTPStatus - -from handlers.abstracts.abstract_handler import AbstractHandler -from helpers import validate_params, build_response -from helpers.log_helper import get_logger - -_LOG = get_logger(__name__) - - -class AbstractUserHandler(AbstractHandler): - @abstractmethod - def get_attribute_value(self, *args, **kwargs): - raise NotImplementedError() - - @abstractmethod - def check_user_exist(self, *args, **kwargs): - raise NotImplementedError() - - @abstractmethod - def update_attribute(self, *args, **kwargs): - raise NotImplementedError() - - @abstractmethod - def validate_value(self, *args, **kwargs): - raise NotImplementedError() - - @abstractmethod - def delete_attribute(self, *args, **kwargs): - raise NotImplementedError() - - @property - @abstractmethod - def attribute_name(self): - raise NotImplementedError() - - def _basic_get_user_attribute_handler(self, event: dict): - _LOG.debug(f'Get {self.attribute_name} attribute for user: {event}') - validate_params(event=event, required_params_list=['user_id']) - - target_user = event.get('target_user') - if not target_user: - target_user = event.get('user_id') - - self._validate_user_existence( - username=target_user) - - _LOG.debug(f'Receiving {self.attribute_name} attribute') - attribute_value = self.get_attribute_value(target_user) - - return build_response( - code=HTTPStatus.OK, - content={self.attribute_name: attribute_value}) - - def _basic_set_user_attribute_handler(self, event: dict): - _LOG.debug(f'Create {self.attribute_name} attribute for user: {event}') - validate_params(event=event, required_params_list=['user_id']) - - target_user = event.get('target_user') - if not target_user: - target_user = event.get('user_id') - - self._validate_user_existence( - username=target_user) - - if self.get_attribute_value(target_user): - _LOG.error(f'Attribute {self.attribute_name} for user ' - f'{target_user} already exists') - return build_response( - code=HTTPStatus.CONFLICT, - content=f'Attribute {self.attribute_name} for user ' - f'{target_user} already exists') - - attribute_value = event.get(self.attribute_name) - if not attribute_value: - _LOG.debug( - f'Attribute value for the {self.attribute_name} attribute ' - f'is not specified') - return build_response( - code=HTTPStatus.BAD_REQUEST, - content=f'Attribute value for the {self.attribute_name} ' - f'attribute is not specified') - # return True if value is valid - self.validate_value(event=event) - - _LOG.debug(f'Creating {self.attribute_name} attribute') - self.update_attribute(target_user, attribute_value) - - return build_response( - code=HTTPStatus.CREATED, - content={self.attribute_name: attribute_value}) - - def _basic_update_user_attribute_handler(self, event: dict): - _LOG.debug(f'Update {self.attribute_name} attribute for user: ' - f'{event}') - validate_params(event=event, required_params_list=['user_id']) - - target_user = event.get('target_user') - if not target_user: - target_user = event.get('user_id') - - self._validate_user_existence( - username=target_user) - - if not self.get_attribute_value(target_user): - _LOG.error( - f'Attribute {self.attribute_name} for user {target_user} ' - f'does not exist') - return build_response( - code=HTTPStatus.NOT_FOUND, - content=f'Attribute {self.attribute_name} for user ' - f'{target_user} does not exist') - - attribute_value = event.get(self.attribute_name) - if not attribute_value: - _LOG.error( - f'Attribute value for the {self.attribute_name} attribute ' - f'is not specified') - return build_response( - code=HTTPStatus.BAD_REQUEST, - content=f'Attribute value for the {self.attribute_name} ' - f'attribute is not specified') - - self.validate_value(event=event) - - _LOG.debug(f'Updating {self.attribute_name} attribute') - self.update_attribute(target_user, attribute_value) - - return build_response( - code=HTTPStatus.OK, - content={self.attribute_name: attribute_value}) - - def _basic_delete_user_attribute_handler(self, event: dict): - _LOG.debug(f'Delete {self.attribute_name} attribute for user: {event}') - validate_params(event=event, required_params_list=['user_id']) - - target_user = event.get('target_user') - if not target_user: - target_user = event.get('user_id') - - self._validate_user_existence( - username=target_user) - - if not self.get_attribute_value(target_user): - _LOG.debug( - f'Attribute {self.attribute_name} for user {target_user} ' - f'does not exist') - return build_response( - code=HTTPStatus.NOT_FOUND, - content=f'Attribute {self.attribute_name} for user ' - f'{target_user} does not exist') - - _LOG.debug(f'Removing {self.attribute_name} attribute') - self.delete_attribute(target_user) - - return build_response( - content=f'Attribute {self.attribute_name} for user ' - f'{target_user} has been deleted.') - - def _validate_user_existence(self, username: str): - if not self.check_user_exist(username): - _LOG.debug(f'{username} does not exist') - return build_response( - code=HTTPStatus.NOT_FOUND, - content=f'{username} does not exist') - - def _validate_target_user_attr_presence(self, target_user): - if not target_user: - _LOG.error('Target user is missing') - return build_response( - code=HTTPStatus.BAD_REQUEST, - content=f'Can not update attribute {self.attribute_name}: ' - f'target user is missing.') diff --git a/src/handlers/applications_handler.py b/src/handlers/applications_handler.py deleted file mode 100644 index e670ab62e..000000000 --- a/src/handlers/applications_handler.py +++ /dev/null @@ -1,653 +0,0 @@ -from http import HTTPStatus -from typing import Optional - -from modular_sdk.services.impl.maestro_credentials_service import \ - CustodianApplicationMeta, DefectDojoApplicationMeta, \ - DefectDojoApplicationSecret -from pydantic import HttpUrl - -from handlers.abstracts.abstract_handler import AbstractHandler -from helpers import build_response -from helpers import setdefault -from helpers.constants import CLOUD_TO_APP_TYPE -from helpers.constants import CUSTOMER_ATTR, USERNAME_ATTR, CLOUD_ATTR, \ - ACCESS_APPLICATION_ID_ATTR, TENANT_LICENSE_KEY_ATTR, LICENSE_KEY_ATTR, \ - DESCRIPTION_ATTR, PASSWORD_ATTR, APPLICATION_ID_ATTR, URL_ATTR, \ - AUTO_RESOLVE_ACCESS_ATTR, RESULTS_STORAGE_ATTR, API_KEY_ATTR, HTTPMethod -from helpers.log_helper import get_logger -from models.modular.application import Application, \ - CustodianLicensesApplicationMeta -from modular_sdk.commons.constants import ApplicationType -from services import SERVICE_PROVIDER -from services.clients.lambda_func import LambdaClient, \ - LICENSE_UPDATER_LAMBDA_NAME -from services.clients.modular import ModularClient -from services.environment_service import EnvironmentService -from services.license_manager_service import LicenseManagerService -from services.license_service import LicenseService -from services.modular_service import ModularService -from services.rbac.iam_cache_service import CachedIamService -from services.user_service import CognitoUserService - -_LOG = get_logger(__name__) - - -class ApplicationsHandler(AbstractHandler): - def __init__(self, modular_service: ModularService, - modular_client: ModularClient, - user_service: CognitoUserService, - cached_iam_service: CachedIamService, - environment_service: EnvironmentService, - license_manager_service: LicenseManagerService, - license_service: LicenseService, - lambda_client: LambdaClient): - self._modular_service = modular_service - self._modular_client = modular_client - self._user_service = user_service - self._iam_service = cached_iam_service - self._environment_service = environment_service - self._license_manager_service = license_manager_service - self._license_service = license_service - self._lambda_client = lambda_client - - @classmethod - def build(cls) -> 'ApplicationsHandler': - return cls( - modular_service=SERVICE_PROVIDER.modular_service(), - modular_client=SERVICE_PROVIDER.modular_client(), - user_service=SERVICE_PROVIDER.user_service(), - cached_iam_service=SERVICE_PROVIDER.iam_cache_service(), - environment_service=SERVICE_PROVIDER.environment_service(), - license_manager_service=SERVICE_PROVIDER.license_manager_service(), - license_service=SERVICE_PROVIDER.license_service(), - lambda_client=SERVICE_PROVIDER.lambda_func() - ) - - def define_action_mapping(self) -> dict: - return { - '/applications/dojo': { - HTTPMethod.POST: self.dojo_post, - HTTPMethod.GET: self.dojo_list, - }, - '/applications/dojo/{application_id}': { - HTTPMethod.PATCH: self.dojo_patch, - HTTPMethod.DELETE: self.dojo_delete, - HTTPMethod.GET: self.dojo_get - }, - '/applications/access': { - HTTPMethod.POST: self.access_post, - HTTPMethod.GET: self.access_list, - }, - '/applications/access/{application_id}': { - HTTPMethod.PATCH: self.access_patch, - HTTPMethod.DELETE: self.access_delete, - HTTPMethod.GET: self.access_get - }, - '/applications': { - HTTPMethod.POST: self.post, - HTTPMethod.GET: self.list, - }, - '/applications/{application_id}': { - HTTPMethod.PATCH: self.patch, - HTTPMethod.DELETE: self.delete, - HTTPMethod.GET: self.get - } - } - - def set_dojo_api_key(self, application: Application, api_key: str): - """ - Modifies the incoming application with secret - :param application: - :param api_key: - """ - assume_role_ssm = self._modular_client.assume_role_ssm_service() - secret_name = application.secret - if not secret_name: - secret_name = assume_role_ssm.safe_name( - name=application.customer_id, - prefix='m3.custodian.dojo', - ) - _LOG.debug('Saving dojo api key to SSM') - secret = assume_role_ssm.put_parameter( - name=secret_name, - value=DefectDojoApplicationSecret(api_key=api_key).dict() - ) - if not secret: - _LOG.warning('Something went wrong trying to save api key ' - 'to ssm. Keeping application.secret empty') - else: - _LOG.debug('Dojo api key was saved to SSM') - application.secret = secret - - def dojo_post(self, event: dict) -> dict: - customer: str = event[CUSTOMER_ATTR] - description = event.get(DESCRIPTION_ATTR) - url: Optional[HttpUrl] = event.get(URL_ATTR) - api_key: str = event.get(API_KEY_ATTR) - - meta = DefectDojoApplicationMeta.from_dict({}) - meta.update_host( - host=url.host, - port=int(url.port) if url.port else None, - protocol=url.scheme, - stage=url.path - ) - application = self._modular_service.create_application( - customer=customer, - description=description, - _type=ApplicationType.DEFECT_DOJO.value, - meta=meta.dict(), - ) - self.set_dojo_api_key(application, api_key) - self._modular_service.save(application) - return build_response( - content=self._modular_service.get_dto(application)) - - def dojo_list(self, event: dict) -> dict: - res = self._modular_service.get_applications( - customer=event.get(CUSTOMER_ATTR), - _type=ApplicationType.DEFECT_DOJO.value - ) - return build_response( - content=(self._modular_service.get_dto(app) for app in res) - ) - - def dojo_get(self, event: dict) -> dict: - customer = event.get(CUSTOMER_ATTR) - application_id = event.get(APPLICATION_ID_ATTR) - item = self.get_application(application_id, customer, - ApplicationType.DEFECT_DOJO.value) - if not item: - return build_response(content=[]) - return build_response(content=[self._modular_service.get_dto(item)]) - - def dojo_patch(self, event: dict) -> dict: - customer: str = event[CUSTOMER_ATTR] - application_id = event.get(APPLICATION_ID_ATTR) - description = event.get(DESCRIPTION_ATTR) - url: Optional[HttpUrl] = event.get(URL_ATTR) - api_key: str = event.get(API_KEY_ATTR) - - application = self.get_application(application_id, customer, - ApplicationType.DEFECT_DOJO) - - if not application: - return build_response( - code=HTTPStatus.NOT_FOUND, - content=f'Defect Dojo application with id {application_id} ' - f'not found in customer {customer}' - ) - if description: - application.description = description - meta = DefectDojoApplicationMeta.from_dict(application.meta.as_dict()) - if url: - meta.update_host( - host=url.host, - port=int(url.port) if url.port else None, - protocol=url.scheme, - stage=url.path - ) - application.meta = meta.dict() - if api_key: - self.set_dojo_api_key(application, api_key) - self._modular_service.save(application) - return build_response( - content=self._modular_service.get_dto(application)) - - def dojo_delete(self, event: dict) -> dict: - customer = event.get(CUSTOMER_ATTR) - application_id = event.get(APPLICATION_ID_ATTR) - application = self.get_application( - application_id, - customer, - ApplicationType.DEFECT_DOJO - ) - if not application: - return build_response( - code=HTTPStatus.NO_CONTENT, - ) - - erased = self._modular_service.delete(application) - if not erased: - return build_response( - code=HTTPStatus.BAD_REQUEST, - content='Could not remove the application. ' - 'Probably it\'s used by some parents.' - ) - # erased - if application.secret: - _LOG.info(f'Removing application secret: {application.secret}') - assume_role_ssm = self._modular_client.assume_role_ssm_service() - if not assume_role_ssm.delete_parameter(application.secret): - _LOG.warning(f'Could not remove secret: {application.secret}') - return build_response(code=HTTPStatus.NO_CONTENT) - - def access_post(self, event: dict) -> dict: - customer: str = event[CUSTOMER_ATTR] - description = event.get(DESCRIPTION_ATTR) - username: str = event.get(USERNAME_ATTR) - password: str = event.get(PASSWORD_ATTR) - url: Optional[HttpUrl] = event.get(URL_ATTR) - auto_resolve_access: bool = event.get(AUTO_RESOLVE_ACCESS_ATTR) - results_storage: str = event.get(RESULTS_STORAGE_ATTR) - - existing = next(self._modular_service.get_applications( - customer=customer, - _type=ApplicationType.CUSTODIAN.value, - limit=1, - deleted=False - ), None) - if existing: - return build_response( - code=HTTPStatus.CONFLICT, - content=f'Access application already ' - f'exists in customer {customer}' - ) - application = self._modular_service.create_application( - customer=customer, - _type=ApplicationType.CUSTODIAN.value, - ) - - if username: - self.validate_username(username, customer) - meta = CustodianApplicationMeta.from_dict({}) - if auto_resolve_access: - meta.update_host( - host=self._environment_service.api_gateway_host(), - stage=self._environment_service.api_gateway_stage() - ) - else: # url - meta.update_host( - host=url.host, - port=int(url.port) if url.port else None, - protocol=url.scheme, - stage=url.path - ) - if username: # means password is given as well - meta.username = username - self.set_user_password(application, password) - - if results_storage: - meta.results_storage = results_storage - application.description = description - - application.meta = meta.dict() - _LOG.info('Saving application item') - self._modular_service.save(application) - - return build_response( - content=self._modular_service.get_dto(application) - ) - - def access_list(self, event: dict) -> dict: - res = self._modular_service.get_applications( - customer=event.get(CUSTOMER_ATTR), - _type=ApplicationType.CUSTODIAN.value - ) - return build_response( - content=(self._modular_service.get_dto(app) for app in res) - ) - - def access_patch(self, event: dict) -> dict: - application_id = event.get(APPLICATION_ID_ATTR) - customer: str = event[CUSTOMER_ATTR] - description = event.get(DESCRIPTION_ATTR) - username: str = event.get(USERNAME_ATTR) - password: str = event.get(PASSWORD_ATTR) - url: Optional[HttpUrl] = event.get(URL_ATTR) - auto_resolve_access: bool = event.get(AUTO_RESOLVE_ACCESS_ATTR) - results_storage: str = event.get(RESULTS_STORAGE_ATTR) - - application = self.get_application(application_id, customer, - ApplicationType.CUSTODIAN.value) - - if not application: - return build_response( - code=HTTPStatus.NOT_FOUND, - content=f'Custodian access application not found in customer ' - f'{customer}' - ) - if username: - self.validate_username(username, customer) - meta = CustodianApplicationMeta.from_dict(application.meta.as_dict()) - if url: - meta.update_host( - host=url.host, - port=int(url.port) if url.port else None, - protocol=url.scheme, - stage=url.path - ) - if auto_resolve_access: - meta.update_host( - host=self._environment_service.api_gateway_host(), - stage=self._environment_service.api_gateway_stage() - ) - if username: - meta.username = username - self.set_user_password(application, password) - if description: - application.description = description - if results_storage: - meta.results_storage = results_storage - - application.meta = meta.dict() - _LOG.info('Saving application item') - self._modular_service.save(application) - - return build_response( - content=self._modular_service.get_dto(application) - ) - - def access_delete(self, event: dict) -> dict: - customer = event.get(CUSTOMER_ATTR) - application_id = event.get(APPLICATION_ID_ATTR) - application = self.get_application( - application_id, - customer, - ApplicationType.CUSTODIAN.value - ) - if not application: - return build_response( - code=HTTPStatus.NO_CONTENT, - ) - - erased = self._modular_service.delete(application) - if not erased: - return build_response( - code=HTTPStatus.BAD_REQUEST, - content='Could not remove the application. ' - 'Probably it\'s used by some parents.' - ) - # erased - if application.secret: - _LOG.info(f'Removing application secret: {application.secret}') - assume_role_ssm = self._modular_client.assume_role_ssm_service() - if not assume_role_ssm.delete_parameter(application.secret): - _LOG.warning(f'Could not remove secret: {application.secret}') - return build_response(code=HTTPStatus.NO_CONTENT) - - def access_get(self, event: dict) -> dict: - customer = event.get(CUSTOMER_ATTR) - application_id = event.get(APPLICATION_ID_ATTR) - item = self.get_application(application_id, customer, - ApplicationType.CUSTODIAN) - if not item: - return build_response(content=[]) - return build_response(content=[self._modular_service.get_dto(item)]) - - def get_application(self, application_id: str, customer: str, _type: str - ) -> Optional[Application]: - application = self._modular_service.get_application(application_id) - if not application or application.is_deleted or \ - application.customer_id != customer or \ - application.type != _type: - return - return application - - def list(self, event: dict) -> dict: - res = self._modular_service.get_applications( - customer=event.get(CUSTOMER_ATTR), - _type=ApplicationType.CUSTODIAN_LICENSES.value - ) - return build_response( - content=(self._modular_service.get_dto(app) for app in res) - ) - - def get(self, event: dict) -> dict: - customer = event.get(CUSTOMER_ATTR) - application_id = event.get(APPLICATION_ID_ATTR) - item = self.get_application(application_id, customer, - ApplicationType.CUSTODIAN_LICENSES.value) - if not item: - return build_response(content=[]) - return build_response(content=[self._modular_service.get_dto(item)]) - - def validate_username(self, username: str, customer: str): - """ - May raise CustodianException - :param customer: - :param username: - :return: - """ - _exists = self._user_service.is_user_exists(username) - if not _exists: - return build_response( - code=HTTPStatus.NOT_FOUND, - content=f'User {username} not found in customer: {customer}' - ) - _customer_match = \ - self._user_service.get_user_customer(username) == customer - - # two identical responses but there will be an error in - # get_user_customer(), if user does not exist - if not _customer_match: - return build_response( - code=HTTPStatus.NOT_FOUND, - content=f'User {username} not found in customer: {customer}' - ) - - def post(self, event: dict) -> dict: - customer: str = event[CUSTOMER_ATTR] - description = event.get(DESCRIPTION_ATTR) - cloud: str = event.get(CLOUD_ATTR) - access_application_id = event.get(ACCESS_APPLICATION_ID_ATTR) - tenant_license_key = event.get(TENANT_LICENSE_KEY_ATTR) - application = self._modular_service.create_application( - customer=customer, - _type=ApplicationType.CUSTODIAN_LICENSES.value, - ) - - meta = CustodianLicensesApplicationMeta() - application.description = description - - if cloud: - # either access_application_id or tenant_license_key or both - if access_application_id: - self.set_access_application_id( - meta=meta, - cloud=cloud, - access_application_id=access_application_id, - customer=customer - ) - if tenant_license_key: - license_key = self.activate_license( - tenant_license_key=tenant_license_key, - customer=customer - ) - # If activate_customer has succeeded, license_sync must - # be successful as well, I hope.. - self._execute_license_sync([license_key]) - # existing_license_key = meta.license_key(cloud) - # if existing_license_key and existing_license_key \ - # not in meta.cloud_to_license_key().values(): - # self._license_service.remove_for_customer( - # existing_license_key, customer - # ) - meta.update_license_key(cloud, license_key) - - application.meta = meta.dict() - _LOG.info('Saving application item') - self._modular_service.save(application) - - return build_response( - content=self._modular_service.get_dto(application) - ) - - def patch(self, event: dict) -> dict: - application_id = event.get(APPLICATION_ID_ATTR) - customer: str = event[CUSTOMER_ATTR] - description = event.get(DESCRIPTION_ATTR) - cloud: str = event.get(CLOUD_ATTR) - access_application_id = event.get(ACCESS_APPLICATION_ID_ATTR) - tenant_license_key = event.get(TENANT_LICENSE_KEY_ATTR) - application = self.get_application( - application_id, - customer, - ApplicationType.CUSTODIAN_LICENSES.value - ) - if not application: - return build_response( - code=HTTPStatus.NOT_FOUND, - content= - f'Custodian application not found in customer {customer}' - ) - meta = CustodianLicensesApplicationMeta(**application.meta.as_dict()) - if description: - application.description = description - if cloud: # either "AWS" or "AZURE" or "GOOGLE", validated by pydentic - # either access_application_id or tenant_license_key or both - if access_application_id: - self.set_access_application_id( - meta=meta, - cloud=cloud, - access_application_id=access_application_id, - customer=customer - ) - if tenant_license_key: - license_key = self.activate_license( - tenant_license_key=tenant_license_key, - customer=customer - ) - # If activate_customer has succeeded, license_sync must - # be successful as well, I hope.. - self._execute_license_sync([license_key]) - existing_license_key = meta.license_key(cloud) - if existing_license_key and existing_license_key \ - not in meta.cloud_to_license_key().values(): - self._license_service.remove_for_customer( - existing_license_key, customer - ) - meta.update_license_key(cloud, license_key) - - application.meta = meta.dict() - _LOG.info('Saving application item') - self._modular_service.save(application) - - return build_response( - content=self._modular_service.get_dto(application) - ) - - def delete(self, event: dict) -> dict: - customer = event.get(CUSTOMER_ATTR) - application_id = event.get(APPLICATION_ID_ATTR) - application = self.get_application( - application_id, customer, ApplicationType.CUSTODIAN_LICENSES.value) - if not application: - return build_response( - code=HTTPStatus.NO_CONTENT, - ) - - erased = self._modular_service.delete(application) - if not erased: - return build_response( - code=HTTPStatus.BAD_REQUEST, - content='Could not remove the application. ' - 'Probably it\'s used by some parents.' - ) - # erased - if application.secret: # should not have, but still - _LOG.info(f'Removing application secret: {application.secret}') - assume_role_ssm = self._modular_client.assume_role_ssm_service() - if not assume_role_ssm.delete_parameter(application.secret): - _LOG.warning(f'Could not remove secret: {application.secret}') - meta = CustodianLicensesApplicationMeta( - **application.meta.as_dict() - ) - for cloud, license_key in meta.cloud_to_license_key().items(): - if not license_key: - continue - self._license_service.remove_for_customer( - license_key, customer - ) - return build_response(code=HTTPStatus.NO_CONTENT) - - def set_user_password(self, application: Application, password: str): - """ - Modifies the incoming application with secret - :param application: - :param password: - """ - assume_role_ssm = self._modular_client.assume_role_ssm_service() - secret_name = assume_role_ssm.safe_name( - name=application.customer_id, - prefix='m3.custodian.application', - date=False - ) - _LOG.debug('Saving password to SSM') - secret = assume_role_ssm.put_parameter( - name=secret_name, - value=password - ) - if not secret: - _LOG.warning('Something went wrong trying to same password ' - 'to ssm. Keeping application.secret empty') - _LOG.debug('Password was saved to SSM') - application.secret = secret - - def set_access_application_id(self, meta: CustodianLicensesApplicationMeta, - cloud: str, - access_application_id: str, - customer: str): - """ - Just keep repeating logic in a separate block - :param meta: - :param cloud: - :param access_application_id: - :param customer: - :return: - """ - _access_app = self._modular_service.get_application( - access_application_id) - if not _access_app or _access_app.customer_id != customer: - return build_response( - code=HTTPStatus.NOT_FOUND, - content=f'Application {access_application_id}' - f' not found within {customer}' - ) - if _access_app.type not in CLOUD_TO_APP_TYPE.get(cloud, set()): - return build_response( - code=HTTPStatus.BAD_REQUEST, - content=f'Application \'{access_application_id}\' ' - f'has type {_access_app.type} that is not ' - f'supported for cloud {cloud}' - ) - _LOG.info(f'Updating access application id for cloud: {cloud}') - meta.update_access_application_id(cloud, access_application_id) - - def activate_license(self, tenant_license_key: str, customer: str) -> str: - _response = self._license_manager_service.activate_customer( - customer, tenant_license_key - ) - if not _response: - _message = f'License manager does not allow to activate ' \ - f'tenant license \'{tenant_license_key}\'' \ - f' for customer \'{customer}\'' - _LOG.warning(_message) - return build_response(code=HTTPStatus.FORBIDDEN, - content=_message) - license_key = _response.get(LICENSE_KEY_ATTR) - license_obj = self._license_service.get_license(license_key) - if not license_obj: - _LOG.info(f'License object with id \'{license_key}\' does ' - f'not exist yet. Creating.') - license_obj = self._license_service.create({ - LICENSE_KEY_ATTR: license_key}) - _d = setdefault(license_obj.customers, customer, {}) - _d[TENANT_LICENSE_KEY_ATTR] = tenant_license_key - _LOG.info('Going to save license object') - license_obj.save() - return license_key - - def _execute_license_sync(self, license_keys: list): - """ - Returns a response from an asynchronously invoked - sync-concerned lambda, `license-updater`. - :return:Dict[code=202] - """ - _LOG.info('Invoking license updater lambda') - response = self._lambda_client.invoke_function_async( - LICENSE_UPDATER_LAMBDA_NAME, event={ - LICENSE_KEY_ATTR: license_keys - } - ) - _LOG.info(f'License updater lambda was invoked: {response}') diff --git a/src/handlers/base_handler.py b/src/handlers/base_handler.py deleted file mode 100644 index 4d3f3c03c..000000000 --- a/src/handlers/base_handler.py +++ /dev/null @@ -1,320 +0,0 @@ -from abc import abstractmethod -from concurrent.futures import ThreadPoolExecutor, as_completed -from http import HTTPStatus -from json import dumps -from sys import getsizeof -from typing import List, Optional, Union, Callable, Dict, Any, Tuple - -from modular_sdk.models.tenant import Tenant - -from handlers.abstracts.abstract_handler import AbstractHandler -from helpers import build_response -from helpers.constants import MANUAL_TYPE_ATTR -from helpers.log_helper import get_logger -from services.ambiguous_job_service import AmbiguousJobService -from services.batch_results_service import BatchResults -from services.job_service import Job, JOB_SUCCEEDED_STATUS -from services.modular_service import ModularService -from services.report_service import \ - ReportService - -RESPONSE_SIZE_LIMIT = 6291456 -ITEM_SIZE_RESPONSE = 'Item size is too large, please use \'href\' parameter.' -DEFAULT_UNRESOLVABLE_RESPONSE = 'Request has run into an unresolvable issue.' - -REACTIVE_TYPE_ATTR = 'reactive' -ID_ATTR = 'id' - -_LOG = get_logger(__name__) - -# Establish an ambiguous report source -Source = Union[BatchResults, Job] -Report = Union[dict, list, str, bytes] -SourceToReport = Dict[Source, Any] -SourceReportDerivation = Callable[[Source, Dict], Any] -SourcedReport = Tuple[Source, Report] -EntityToSourcedReports = Dict[str, List[SourcedReport]] - -EntitySourcedReportDerivation = Callable[[List[SourcedReport], Dict], Any] -EntityToReport = Dict[str, Report] - - -class BaseReportHandler(AbstractHandler): - _code: int - _content: Union[str, dict, list] - - def __init__(self, ambiguous_job_service: AmbiguousJobService, - modular_service: ModularService, - report_service: ReportService): - self._ambiguous_job_service = ambiguous_job_service - self._modular_service = modular_service - self._report_service = report_service - self._reset() - - @property - @abstractmethod - def _source_report_derivation_function(self) -> SourceReportDerivation: - pass - - @property - def response(self): - _code, _content = self._code, self._content - self._reset() - - _response = build_response(code=_code, content=_content) - - if _code == isinstance(_content, (dict, list)): # todo does it work? - _LOG.info('Going to check for the response size constraint.') - size = getsizeof(dumps(_response)) - if size > RESPONSE_SIZE_LIMIT: - _response = None - _LOG.warning(f'Response size of {size} bytes is too large.') - _code = HTTPStatus.BAD_REQUEST - _content = ITEM_SIZE_RESPONSE - - _LOG.info(f'Going to respond with the following ' - f'code={_code}, content={_content}.') - - return _response or build_response(code=_code, content=_content) - - @property - def _entity_sourced_report_derivation_function(self) -> Optional[ - EntitySourcedReportDerivation - ]: - return ... - - def _reset(self): - self._code: Optional[int] = HTTPStatus.INTERNAL_SERVER_ERROR - self._content: Optional[str] = DEFAULT_UNRESOLVABLE_RESPONSE - - def _attain_source(self, uid: str, typ: Optional[str] = None, - customer: Optional[str] = None, - tenants: Optional[List[str]] = None) -> Optional[ - Source]: - """ - Mediates report job source attainment, based on the requested, - previously verified `typ` attribute. - :param uid: str, unique identifier of the requested entity - :param typ: str - :param customer: Optional[str] - :param tenants: Optional[List[str]] - :return: Optional[Source] - """ - ref = { - MANUAL_TYPE_ATTR: self._attain_manual_source, - REACTIVE_TYPE_ATTR: self._attain_reactive_source - } - if typ: - assert typ in ref, f'Invalid type {typ}' - return ref[typ](uid, customer, tenants) - source = filter(lambda x: x, - (get(uid, customer, tenants) for get in ref.values())) - result = next(source, None) - if not result and not typ: - self._content = 'Job does not exist' - return result - - def _attain_reactive_source( - self, brid: str, customer: Optional[str] = None, - tenants: Optional[List[str]] = None - ): - """ - Obtains a Batch Results entity, based on a given `brid` partition key, - verifying access, based on a customer and tenant. - :param brid: str - :param customer: Optional[str] - :param tenants: Optional[List[str]] - :return: Optional[BatchResults] - """ - _head = f'{REACTIVE_TYPE_ATTR.capitalize()} job:\'{brid}\'' - _default_404 = _head + ' does not exist.' - _LOG.info(_head + ' is being obtained.') - - # Todo use a domain entities models rather then persistence models. - entity = self._ambiguous_job_service.get( - uid=brid, typ=REACTIVE_TYPE_ATTR - ) - if not entity: - _LOG.warning(_default_404) - elif customer and entity.customer_name != customer: - _LOG.warning(_head + f' is not bound to \'{customer}\' customer.') - entity = None - elif tenants and entity.tenant_name not in tenants: - _scope = ', '.join(map("'{}'".format, tenants)) + ' tenant(s)' - _LOG.warning(_head + f' is not bound to any of {_scope}.') - entity = None - elif entity.status != JOB_SUCCEEDED_STATUS: - _status = JOB_SUCCEEDED_STATUS - _LOG.warning(_head + f' is not of \'{_status}\' status.') - entity = None - - if not entity: - self._code = HTTPStatus.NOT_FOUND - self._content = _default_404 - - return entity - - def _attain_manual_source( - self, jid: str, customer: Optional[str] = None, - tenants: Optional[List[str]] = None - ): - """ - Obtains a Job entity, based on a given `jid` partition key, - verifying access, based on a customer and tenant. - :param jid: str - :param customer: Optional[str] - :param tenants: Optional[List[str]] - :return: Optional[BatchResults] - """ - _head = f'{MANUAL_TYPE_ATTR.capitalize()} job:\'{jid}\'' - _default_404 = _head + ' does not exist.' - _LOG.info(_head + ' is being obtained.') - # Todo use a domain entities models rather then persistence models. - entity = self._ambiguous_job_service.get(uid=jid, typ=MANUAL_TYPE_ATTR) - - if not entity: - _LOG.warning(_default_404) - elif customer and entity.customer_display_name != customer: - _LOG.warning(_head + f' is not bound to \'{customer}\' customer.') - entity = None - elif tenants and entity.tenant_display_name not in tenants: - _scope = ', '.join(map("'{}'".format, tenants)) + ' tenant(s)' - _LOG.warning(_head + f' is not bound to any of {_scope}.') - entity = None - elif entity.status != JOB_SUCCEEDED_STATUS: - _status = JOB_SUCCEEDED_STATUS - _LOG.warning(_head + f' is not of \'{_status}\' status.') - entity = None - - if not entity: - self._code = HTTPStatus.NOT_FOUND - self._content = _default_404 - - return entity - - def _attain_tenant(self, name: str, customer: Optional[str] = None, - active: bool = None) -> Optional[Tenant]: - """ - Obtains a Tenant entity, based on a given tenant `name` partition key, - verifying access, based on a customer and activity state. - :param name: str - :param customer: Optional[str] - :param active: Optional[bool] - :return: Optional[Tenant] - """ - _head = f'Tenant:\'{name}\'' - _default_404 = _head + ' does not exist.' - _LOG.info(_head + ' is being obtained.') - entity = self._modular_service.get_tenant(tenant=name) - if not entity: - _LOG.warning(_default_404) - elif customer and entity.customer_name != customer: - _LOG.warning(_head + f' is not bound to \'{customer}\' customer.') - entity = None - elif active is not None and entity.is_active != active: - _LOG.warning(_head + f' activity does not equal {active}.') - entity = None - - if not entity: - self._code = HTTPStatus.NOT_FOUND - self._content = _default_404 - - return entity - - def _attain_source_report_map(self, source_list: List[Source], **kwargs): - """ - Returns a map of source-to-report variables, based on the - instance-specific `source_report_derivation_function` - :param source_list: Dict[str, List[Dict]] - :return: SourceToReport - """ - output = {} - - derivation_function = self._source_report_derivation_function - if not derivation_function: - _LOG.error('Report derivation function has not been assigned.') - return output - - with ThreadPoolExecutor() as executor: - futures = { - executor.submit( - derivation_function, source, **kwargs - ): source - for source in source_list - } - for future in as_completed(futures): - _source: Source = futures[future] - _typ = self._ambiguous_job_service.get_type(item=_source) - _uid = self._ambiguous_job_service.get_attribute( - item=_source, attr=ID_ATTR - ) - _head = f'{_typ.capitalize()} Job:\'{_uid}\'' - _head += ' derivation of a report' - try: - _reported = future.result() - if _reported: - output[_source] = _reported - else: - _LOG.warning(_head + ' has been unsuccessful.') - except (Exception, BaseException) as e: - _LOG.warning(_head + f' has run into an issue - {e}.') - - return output - - def _attain_entity_report_map_from_sourced_reports( - self, entity_sourced_reports: EntityToSourcedReports, **kwargs - ): - """ - Returns a map of source-to-report variables, based on the - instance-specific `entity_report_derivation_function` - :param entity_sourced_reports: EntityToSourcedReports - :return: EntityToReport - """ - output = {} - - derivation_function = self._entity_sourced_report_derivation_function - if not derivation_function: - _LOG.error('Report derivation function has not been assigned.') - return output - - with ThreadPoolExecutor() as executor: - futures = { - executor.submit( - derivation_function, - sourced_reports, **kwargs - ): entity - for entity, sourced_reports in entity_sourced_reports.items() - } - for future in as_completed(futures): - _entity: str = futures[future] - _head = f'\'{_entity}\' derivation of an entity-report' - try: - _reported = future.result() - if _reported: - output[_entity] = _reported - else: - _LOG.warning(_head + ' has been unsuccessful.') - except (Exception, BaseException) as e: - _LOG.warning(_head + f' has run into an issue - {e}.') - - return output - - def _attain_entity_sourced_reports( - self, entity_attr: str, source_to_report: SourceToReport, - entity_value: Optional[str] = None - ): - ref: EntityToSourcedReports = {} - ajs = self._ambiguous_job_service - for source, report in source_to_report.items(): - typ = ajs.get_type(item=source) - uid = ajs.get_attribute(item=source, attr=ID_ATTR) - head = f'{typ.capitalize()} Job:\'{uid}\'' - value = ajs.get_attribute(item=source, attr=entity_attr) - value = value or entity_value - if not value: - _LOG.warning(f'{head} could not resolve {entity_attr} attr.') - continue - scope = ref.setdefault(value, []) - scope.append((source, report)) - - return ref diff --git a/src/handlers/compliance_handler.py b/src/handlers/compliance_handler.py index 1286f0ada..1029c8039 100644 --- a/src/handlers/compliance_handler.py +++ b/src/handlers/compliance_handler.py @@ -1,390 +1,170 @@ +import io +from functools import cached_property from http import HTTPStatus -from typing import Optional - -from handlers.base_handler import \ - BaseReportHandler, SourceReportDerivation, \ - Report, ModularService, AmbiguousJobService, Source -from helpers.constants import (HTTPMethod, CUSTOMER_ATTR, TENANTS_ATTR, - TENANT_ATTR, HREF_ATTR, CONTENT_ATTR, ID_ATTR, - AWS_CLOUD_ATTR, - AZURE_CLOUD_ATTR) -from helpers.log_helper import get_logger -from services.coverage_service import CoverageService, RegionPoints -from services.findings_service import FindingsService -from services.modular_service import Tenant -from services.report_service import ReportService, DETAILED_REPORT_FILE - -DEFAULT_UNRESOLVABLE_RESPONSE = 'Request has run into an unresolvable issue.' - -TYPE_ATTR = 'type' -JOB_ID_ATTR = 'job_id' - -ENTITY_ATTR_KEY = 'entity_attr' -ENTITY_VALUE_KEY = 'entity_value' -STANDARDS_COVERAGE_KEY = 'standards_coverage' - -TENANT_NAME_ATTR = 'tenant_name' -TENANT_DISPLAY_NAME_ATTR = 'tenant_display_name' - -REGIONS_TO_INCLUDE_ATTR = 'regions_to_include' -REGIONS_TO_EXCLUDE_ATTR = 'regions_to_exclude' -ACTIVE_ONLY_ATTR = 'active_only' - -_LOG = get_logger(__name__) - -# Done -JOB_ENDPOINT = '/reports/compliance/jobs/{id}' -TENANT_ENDPOINT = '/reports/compliance/tenants/{tenant_name}' - -# Scrapped, due to attribute naming inconsistency - -NO_RESOURCES_FOR_REPORT = ' maintain(s) no resources to derive a report.' - - -class BaseComplianceReportHandler(BaseReportHandler): - """ - Provides base behaviour of compliance-reporting, establishing - report-derivation function. - """ - - def __init__(self, ambiguous_job_service: AmbiguousJobService, - modular_service: ModularService, - report_service: ReportService, - coverage_service: CoverageService): - super().__init__( - ambiguous_job_service=ambiguous_job_service, - modular_service=modular_service, - report_service=report_service, - ) +from itertools import chain + +from modular_sdk.services.tenant_service import TenantService +from xlsxwriter import Workbook +from xlsxwriter.worksheet import Worksheet + +from handlers import AbstractHandler, Mapping +from helpers.constants import CustodianEndpoint, HTTPMethod, ReportFormat +from helpers.lambda_response import build_response +from services import SP +from services import modular_helpers +from services.ambiguous_job_service import AmbiguousJobService +from services.coverage_service import CoverageService +from services.environment_service import EnvironmentService +from services.report_service import ReportResponse, ReportService +from services.xlsx_writer import CellContent, Table, XlsxRowsWriter +from validators.swagger_request_models import ( + JobComplianceReportGetModel, + TenantComplianceReportGetModel, +) +from validators.utils import validate_kwargs + + +class ComplianceReportXlsxWriter: + def __init__(self, coverages: dict[str, dict[str, float]]): + self._coverages = coverages + + def write(self, wsh: Worksheet, wb: Workbook): + standards = sorted(set(chain.from_iterable( + v.keys() for v in self._coverages.values() + ))) + bold = wb.add_format({'bold': True}) + percent = wb.add_format({'num_format': '0.00%'}) + table = Table() + table.new_row() + table.add_cells(CellContent('Regions', bold)) + for st in standards: + table.add_cells(CellContent(st, bold)) + for region, region_data in self._coverages.items(): + table.new_row() + table.add_cells(CellContent(region)) + for st in standards: + table.add_cells(CellContent(region_data.get(st), percent)) + writer = XlsxRowsWriter() + writer.write(wsh, table) + + +class ComplianceReportHandler(AbstractHandler): + def __init__(self, tenant_service: TenantService, + coverage_service: CoverageService, + environment_service: EnvironmentService, + ambiguous_job_service: AmbiguousJobService, + report_service: ReportService): + self._tenant_service = tenant_service self._coverage_service = coverage_service - - def _compliance_per_source_derivation(self, source: Source, **kwargs - ) -> Optional[Report]: - - """ - Obtains compliance report of a sourced job, returning a - respective detailed-report. - :param source: Source - :param kwargs: Dict, maintains: - - `href` attribute, denoting demand for hypertext reference. - - `standards_coverage`, providing cloud-respective standard data. - - :return: Optional[Report] - """ - jid = self._ambiguous_job_service.get_attribute(source, ID_ATTR) - href = kwargs.get(HREF_ATTR) - tenant: Tenant = kwargs.get('tenant') - rs = self._report_service - cs = self._coverage_service - - detailed_path = rs.derive_job_object_path(job_id=jid, - typ=DETAILED_REPORT_FILE) - xlsx_compliance_path = rs.derive_compliance_report_object_path( - job_id=jid, fext='xlsx' + self._environment_service = environment_service + self._ambiguous_job_service = ambiguous_job_service + self._report_service = report_service + + @classmethod + def build(cls) -> 'AbstractHandler': + return cls( + tenant_service=SP.modular_client.tenant_service(), + coverage_service=SP.coverage_service, + environment_service=SP.environment_service, + ambiguous_job_service=SP.ambiguous_job_service, + report_service=SP.report_service ) - _head = f'Job:\'{jid}\'' - - ref = None - if href: - _LOG.info(_head + ' going to obtain hypertext reference' - 'of the compliance report.') - ref = rs.href_concrete_report( - path=xlsx_compliance_path, check=True - ) - if ref: - return ref - - # Maintains raw data, from now on. - message = ' pulling detailed report for compliance derivation.' - _LOG.warning(_head + message) - detailed = self._report_service.pull_job_report(path=detailed_path) - if detailed: - points: RegionPoints = cs.derive_points_from_detailed_report( - detailed_report=detailed - ) - if tenant.cloud == AWS_CLOUD_ATTR: - points = cs.distribute_multiregion(points) - elif tenant.cloud == AZURE_CLOUD_ATTR: - points = cs.congest_to_multiregion(points) - if points: - message = ' deriving compliance coverage of points.' - _LOG.info(_head + message) - ref = cs.calculate_region_coverages( - points=points, cloud=tenant.cloud - ) - if ref and href: - # todo retain .json as well, for faster derivation. - _LOG.warning(_head + ' deriving xlsx compliance report.') - file_name = rs.derive_name_of_report_object_path( - object_path=xlsx_compliance_path - ) - path = rs.derive_compliance_report_excel_path( - file_name=file_name, coverages=ref, - standards_coverage=cs.standards_coverage(tenant.cloud) - ) - if path: - _LOG.warning(_head + ' patching compliance-report.') - if rs.put_path_retained_concrete_report(stream_path=path, - object_path=xlsx_compliance_path) is None: - message = ' compliance-report could not be patched.' - _LOG.warning(_head + message) - # Explicitly denote reference absence. - ref = None - - if href and ref: - # Pull after successful patch. - ref = self._report_service.href_job_report( - path=xlsx_compliance_path, check=False - ) - - return ref - - -class JobsComplianceHandler(BaseComplianceReportHandler): - - def define_action_mapping(self): + @cached_property + def mapping(self) -> Mapping: return { - JOB_ENDPOINT: { + CustodianEndpoint.REPORTS_COMPLIANCE_JOBS_JOB_ID: { HTTPMethod.GET: self.get_by_job - } - } - - @property - def _source_report_derivation_function(self) -> SourceReportDerivation: - return self._compliance_per_source_derivation - - def get_by_job(self, event: dict): - _LOG.info(f'GET Job Details Report(s) - {event}.') - # Note that `job_id` denotes the primary-key's hash-key of entities. - uid: str = event[ID_ATTR] - typ: str = event[TYPE_ATTR] - customer = event.get(CUSTOMER_ATTR) - tenants = event.get(TENANTS_ATTR) - href = event.get(HREF_ATTR) - - head = f'{typ.capitalize()} Job:\'{uid}\'' - - source = self._attain_source( - uid=uid, typ=typ, customer=customer, tenants=tenants - ) - if not source: - return self.response - - source_tenant = self._ambiguous_job_service.get_attribute( - item=source, attr=TENANT_ATTR - ) - tenant_item = self._attain_tenant( - name=source_tenant, customer=customer, active=True - ) - if not tenant_item: - return self.response - _LOG.info(head + ' obtaining source to standards coverage mapping.') - - referenced_reports = self._attain_source_report_map( - source_list=[source, ], tenant=tenant_item, - href=href, - ) - if referenced_reports: - ref_attr = HREF_ATTR if href else CONTENT_ATTR - self._code = HTTPStatus.OK - self._content = [ - self.dto(source=source, report=report, ref_attr=ref_attr) - for source, report in referenced_reports.items() - ] - else: - message = f' - no report could be derived.' - _LOG.warning(head + message) - self._code = HTTPStatus.NOT_FOUND - self._content = head + NO_RESOURCES_FOR_REPORT - - return self.response - - def dto(self, source: Source, report: Report, ref_attr: str): - return { - ID_ATTR: self._ambiguous_job_service.get_attribute( - item=source, attr=ID_ATTR - ), - TYPE_ATTR: self._ambiguous_job_service.get_type(item=source), - ref_attr: report - } - - -class EntityComplianceHandler(BaseComplianceReportHandler): - - def __init__(self, ambiguous_job_service: AmbiguousJobService, - modular_service: ModularService, - report_service: ReportService, - findings_service: FindingsService, - coverage_service: CoverageService): - super().__init__( - ambiguous_job_service=ambiguous_job_service, - modular_service=modular_service, - report_service=report_service, - coverage_service=coverage_service - ) - self._findings_service = findings_service - - def define_action_mapping(self): - return { - TENANT_ENDPOINT: { + }, + CustodianEndpoint.REPORTS_COMPLIANCE_TENANTS_TENANT_NAME: { HTTPMethod.GET: self.get_by_tenant } } - def get_by_tenant(self, event: dict): - _LOG.info(f'GET compliance Report(s) of a Tenant - {event}.') - # `tenant` has been injected via the restriction service. - tenant_name = event[TENANT_ATTR] - - head = f'Tenant:\'{tenant_name}\'' - - href = event.get(HREF_ATTR) - customer = event.get(CUSTOMER_ATTR) - tenant = self._attain_tenant( - name=tenant_name, customer=customer, active=True + @validate_kwargs + def get_by_job(self, event: JobComplianceReportGetModel, job_id: str): + job = self._ambiguous_job_service.get_job( + job_id=job_id, + typ=event.job_type, + customer=event.customer ) + if not job: + return build_response( + content='The request job not found', + code=HTTPStatus.NOT_FOUND + ) + tenant = self._tenant_service.get(job.tenant_name) if not tenant: - return self.response - - # todo, coverage for multiple tenants ? - report = self._compliance_per_tenant_derivation( - tenant=tenant, - href=href, - ) - - if report: - ref_attr = HREF_ATTR if href else CONTENT_ATTR - self._code = HTTPStatus.OK - self._content = [ - self.dto( - entity_attr=TENANT_ATTR, entity_value=tenant_name, - report=report, ref_attr=ref_attr - ) - ] + return build_response( + code=HTTPStatus.NOT_FOUND, + content='Job tenant not found' + ) + # TODO api implement for platform + if not job.is_ed_job: + collection = self._report_service.job_collection(tenant, job.job) else: - message = f' - accumulated report could not be derived.' - _LOG.warning(head + message) - self._code = HTTPStatus.NOT_FOUND - self._content = head + NO_RESOURCES_FOR_REPORT - - return self.response - - def _compliance_per_tenant_derivation(self, tenant: Tenant, - **kwargs: dict) -> Optional[Report]: - - """ - Obtains compliance report of an Account, returning a respective report. - :param account: Account - :param: - :param kwargs: Dict, maintains: - - `href`: bool, denotes demand for hypertext reference. - - `regions_to_include`: List[str], denotes regions to target. - - `regions_to_exclude`: List[str], denotes regions to omit. - - `active_only`: bool = True , denotes to use active regions only - :return: Optional[Report] - """ - - cid = tenant.project - href = kwargs.get(HREF_ATTR) - req_regions = kwargs.get(REGIONS_TO_INCLUDE_ATTR) - omit_regions = kwargs.get(REGIONS_TO_EXCLUDE_ATTR) - active_only = kwargs.get(ACTIVE_ONLY_ATTR, True) - - rs = self._report_service - cs = self._coverage_service - fs = self._findings_service - - head = f'Tenant:\'{cid}\'' - - active_regions = None - - if active_only: - active_regions = self._modular_service.get_tenant_regions(tenant) - - if omit_regions: - message = f' filtering out requested regions - ' - message += ', '.join(omit_regions) - _LOG.info(head + message) - active_regions = list(set(active_regions) - set(omit_regions)) - - if req_regions: - message = f' retaining active requested regions - ' - message += ', '.join(req_regions) - _LOG.info(head + message) - active_regions = list(set(req_regions) & set(active_regions)) - - if not active_regions: - _LOG.warning(head + ' no active regions are available.') - return - - xlsx_compliance_path = rs.derive_compliance_report_object_path( - entity_attr=TENANT_ATTR, entity_value=cid, fext='xlsx', + collection = self._report_service.ed_job_collection(tenant, + job.job) + collection.fetch_all() + coverages = self._coverage_service.coverage_from_collection( + collection, modular_helpers.tenant_cloud(tenant) ) - - # todo given Entity Based Compliance is dynamic, i.e., - # changes with time - ergo "retain-any-new, then-pull" won't work - # Consider `select_object_content` for query-like requests. - - # Dynamically generates compliance. - - message = ' pulling findings content. ' - _LOG.warning(head + message) - ref = {} - findings: Optional[dict] = fs.get_findings_content(identifier=cid) - if findings: - message = ' deriving points based on' - if active_only: - message += f' active region(s): {", ".join(active_regions)}' - message += ' within' - message += ' findings.' - - _LOG.info(head + message) - # Given active_only==False, active_regions=None, ergo ignored. - points: RegionPoints = cs.derive_points_from_findings( - findings=findings, regions=active_regions - ) - if tenant.cloud == AWS_CLOUD_ATTR: - points = cs.distribute_multiregion(points) - elif tenant.cloud == AZURE_CLOUD_ATTR: - points = cs.congest_to_multiregion(points) - if points: - message = ' deriving compliance coverage of points.' - _LOG.info(head + message) - ref = cs.calculate_region_coverages( - points=points, cloud=tenant.cloud - ) - if ref and href: - _LOG.info(head + ' deriving xlsx compliance report.') - file_name = rs.derive_name_of_report_object_path( - object_path=xlsx_compliance_path) - path = rs.derive_compliance_report_excel_path( - file_name=file_name, coverages=ref, - standards_coverage=cs.standards_coverage(tenant.cloud) + response = ReportResponse(job, coverages, event.format) + match event.format: + case ReportFormat.JSON: + if event.href: + url = self._report_service.one_time_url_json( + coverages, f'{tenant.name}-compliance.json' + ) + response.content = url + case ReportFormat.XLSX: + buffer = io.BytesIO() + with Workbook(buffer) as wb: + ComplianceReportXlsxWriter(coverages).write( + wb=wb, + wsh=wb.add_worksheet('Compliance') + ) + buffer.seek(0) + url = self._report_service.one_time_url( + buffer, f'{job.id}-compliance.xlsx' ) - if path: - message = ' generating compliance hypertext reference.' - _LOG.warning(head + message) - if rs.put_path_retained_concrete_report( - stream_path=path, object_path=xlsx_compliance_path - ) is None: - message = ' compliance-report hypertext could not' - message += ' be provided.' - _LOG.warning(head + message) - # Explicitly denote reference absence. - ref = None - - if href and ref: - ref = self._report_service.href_job_report( - path=xlsx_compliance_path, check=False + response.content = url + return build_response(content=response.dict()) + + @validate_kwargs + def get_by_tenant(self, event: TenantComplianceReportGetModel, + tenant_name: str): + tenant = self._tenant_service.get(tenant_name) + modular_helpers.assert_tenant_valid(tenant, event.customer) + cloud = modular_helpers.tenant_cloud(tenant) + if not cloud: + return build_response( + content=f'Not allowed cloud: {cloud.value}', + code=HTTPStatus.BAD_REQUEST ) - - return ref - - @staticmethod - def dto(entity_attr: str, entity_value: str, - report: Report, ref_attr: str): - return { - entity_attr: entity_value, - ref_attr: report - } + collection = self._report_service.tenant_latest_collection(tenant) + collection.fetch_all() + coverages = self._coverage_service.coverage_from_collection( + collection, cloud + ) + response = ReportResponse(tenant, coverages, event.format) + match event.format: + case ReportFormat.JSON: + if event.href: + url = self._report_service.one_time_url_json( + coverages, f'{tenant_name}-compliance.json' + ) + response.content = url + case ReportFormat.XLSX: + buffer = io.BytesIO() + with Workbook(buffer) as wb: + ComplianceReportXlsxWriter(coverages).write( + wb=wb, + wsh=wb.add_worksheet('Compliance') + ) + buffer.seek(0) + url = self._report_service.one_time_url( + buffer, f'{tenant_name}-compliance.xlsx' + ) + response.content = url + return build_response(content=response.dict()) diff --git a/src/handlers/credentials_handler.py b/src/handlers/credentials_handler.py new file mode 100644 index 000000000..ae540ac8f --- /dev/null +++ b/src/handlers/credentials_handler.py @@ -0,0 +1,216 @@ +""" +Maestro applications with credentials and CUSTODIAN_ACCESS parent +""" + +from functools import cached_property +from http import HTTPStatus +from itertools import chain +from typing import Iterable, Literal + +from modular_sdk.commons.constants import ApplicationType, ParentType +from modular_sdk.models.application import Application +from modular_sdk.models.parent import Parent +from modular_sdk.services.application_service import ApplicationService +from modular_sdk.services.parent_service import ParentService + +from handlers import AbstractHandler, Mapping +from helpers import NextToken +from helpers.constants import Cloud, CustodianEndpoint, HTTPMethod +from helpers.lambda_response import ResponseFactory, build_response +from services import SP +from services.abs_lambda import ProcessedEvent +from services.modular_helpers import ( + ResolveParentsPayload, + build_parents, + get_activation_dto, + split_into_to_keep_to_delete, +) +from validators.swagger_request_models import ( + BaseModel, + CredentialsBindModel, + CredentialsQueryModel, +) +from helpers.log_helper import get_logger +from validators.utils import validate_kwargs + +_LOG = get_logger(__name__) + + +class CredentialsHandler(AbstractHandler): + __slots__ = '_aps', '_ps' + + cloud_to_app_types = { + Cloud.AWS: (ApplicationType.AWS_ROLE, + ApplicationType.AWS_CREDENTIALS), + Cloud.AZURE: (ApplicationType.AZURE_CREDENTIALS, + ApplicationType.AZURE_CERTIFICATE), + Cloud.GOOGLE: (ApplicationType.GCP_COMPUTE_ACCOUNT, + ApplicationType.GCP_SERVICE_ACCOUNT) + } + all_types = set(chain.from_iterable(cloud_to_app_types.values())) + + def __init__(self, application_service: ApplicationService, + parent_service: ParentService): + self._aps = application_service + self._ps = parent_service + + @classmethod + def build(cls): + return cls( + application_service=SP.modular_client.application_service(), + parent_service=SP.modular_client.parent_service() + ) + + @cached_property + def mapping(self) -> Mapping: + return { + CustodianEndpoint.CREDENTIALS: { + HTTPMethod.GET: self.query + }, + CustodianEndpoint.CREDENTIALS_ID: { + HTTPMethod.GET: self.get + }, + CustodianEndpoint.CREDENTIALS_ID_BINDING: { + HTTPMethod.PUT: self.bind, + HTTPMethod.DELETE: self.unbind, + HTTPMethod.GET: self.get_binding + }, + } + + def _get_app(self, aid: str, customer_id: str) -> Application | None: + app = self._aps.get_application_by_id(aid) + if (not app or app.is_deleted or app.customer_id != customer_id + or app.type not in self.all_types): + return + return app + + @staticmethod + def _app_cloud(app: Application) -> Literal['AWS', 'AZURE', 'GOOGLE']: + """ + As long as maestro does not change their types, this function + should work as expected + """ + _LOG.info(f'Application type: {app.type}') + cl = app.type.split('_', maxsplit=1)[0] + _LOG.debug(f'Application cloud from type: {cl}') + if cl == 'GCP': + _LOG.debug('Changing cloud to GOOGLE instead of GCP') + cl = 'GOOGLE' + assert cl in ('AWS', 'AZURE', 'GOOGLE'), 'A bug found' + return cl + + @staticmethod + def get_dto(app: Application) -> dict: + return { + 'id': app.application_id, + 'type': app.type, + 'description': app.description, + 'has_secret': not not app.secret, + 'credentials': app.meta.as_dict() + } + + @validate_kwargs + def get(self, event: BaseModel, id: str): + app = self._get_app(id, event.customer_id) + if not app: + raise ResponseFactory(HTTPStatus.NOT_FOUND).message( + 'Credentials item not found' + ).exc() + return build_response(self.get_dto(app)) + + @validate_kwargs + def query(self, event: CredentialsQueryModel): + cloud = event.cloud + if cloud == 'GOOGLE': + # I doubt Maestro will change their application types names. + # so we can do this hack and use prefix as range key condition + cloud = 'GCP' + cursor = self._aps.query_by_customer( + customer=event.customer, + range_key_condition=Application.type.startswith(cloud), + filter_condition=(Application.is_deleted == False), + limit=event.limit, + last_evaluated_key=NextToken.deserialize(event.next_token).value, + ) + items = tuple(cursor) + return ResponseFactory().items( + it=map(self.get_dto, items), + next_token=NextToken(cursor.last_evaluated_key) + ).build() + + def get_all_activations(self, application_id: str, + customer: str | None = None) -> Iterable[Parent]: + it = self._ps.i_list_application_parents( + application_id=application_id, + type_=ParentType.CUSTODIAN_ACCESS, + rate_limit=3 + ) + if customer: + it = filter(lambda p: p.customer_id == customer, it) + return it + + @validate_kwargs + def bind(self, event: CredentialsBindModel, id: str, _pe: ProcessedEvent): + app = self._get_app(id, event.customer_id) + _LOG.info(f'Application received: {app}') + + if not app: + raise ResponseFactory(HTTPStatus.NOT_FOUND).message( + 'Credentials item not found' + ).exc() + if event.all_tenants: + _LOG.info('Resolving application clouds') + clouds = {self._app_cloud(app)} + else: + clouds = set() + payload = ResolveParentsPayload( + parents=list(self.get_all_activations(app.application_id, + event.customer)), + tenant_names=event.tenant_names, + exclude_tenants=event.exclude_tenants, + clouds=clouds, + all_tenants=event.all_tenants + ) + to_keep, to_delete = split_into_to_keep_to_delete(payload) + for parent in to_delete: + self._ps.force_delete(parent) + to_create = build_parents( + payload=payload, + parent_service=self._ps, + application_id=app.application_id, + customer_id=event.customer_id, + type_=ParentType.CUSTODIAN_ACCESS, + created_by=_pe['cognito_user_id'], + ) + for parent in to_create: + self._ps.save(parent) + return build_response( + content=get_activation_dto(chain(to_keep, to_create)) + ) + + @validate_kwargs + def unbind(self, event: BaseModel, id: str): + app = self._get_app(id, event.customer_id) + if not app: + raise ResponseFactory(HTTPStatus.NOT_FOUND).message( + 'Credentials item not found' + ).exc() + activations = self.get_all_activations( + application_id=app.application_id, + customer=event.customer_id + ) + for parent in activations: + self._ps.force_delete(parent) + return build_response(code=HTTPStatus.NO_CONTENT) + + @validate_kwargs + def get_binding(self, event: BaseModel, id: str): + app = self._get_app(id, event.customer_id) + if not app: + raise ResponseFactory(HTTPStatus.NOT_FOUND).message( + 'Credentials item not found' + ).exc() + activations = self.get_all_activations(app.application_id, + event.customer_id) + return build_response(content=get_activation_dto(activations)) + diff --git a/src/handlers/credentials_manager_handler.py b/src/handlers/credentials_manager_handler.py deleted file mode 100644 index ce7c33a07..000000000 --- a/src/handlers/credentials_manager_handler.py +++ /dev/null @@ -1,203 +0,0 @@ -from typing import Optional, List - -from handlers.abstracts.abstract_credentials_manager_handler import \ - AbstractCredentialsManagerHandler -from helpers.constants import HTTPMethod, TRUSTED_ROLE_ARN, \ - CLOUD_IDENTIFIER_ATTR, CLOUD_ATTR, ENABLED, \ - TENANT_ATTR, CUSTOMER_ATTR -from helpers.log_helper import get_logger -from models.credentials_manager import CredentialsManager -from services.credentials_manager_service import CredentialsManagerService -from services.modular_service import ModularService -from services.user_service import CognitoUserService - -_LOG = get_logger(__name__) - - -class CredentialsManagerHandler(AbstractCredentialsManagerHandler): - """ - Manage Credentials Manager API - """ - - def __init__(self, credential_manager_service: CredentialsManagerService, - user_service: CognitoUserService, - modular_service: ModularService): - super().__init__( - credential_manager_service=credential_manager_service, - user_service=user_service, - modular_service=modular_service - ) - - def define_action_mapping(self): - return { - '/accounts/credential_manager': { - HTTPMethod.GET: self.get_credentials_manager, - HTTPMethod.POST: self.add_credentials_manager, - HTTPMethod.PATCH: self.update_credentials_manager, - HTTPMethod.DELETE: self.remove_credentials_manager - } - } - - def get_configuration_object( - self, cloud: str, cloud_identifier: str, - customer: Optional[str] = None, tenants: Optional[List[str]] = None - ): - head = f'CredentialsManager:{cloud}#{cloud_identifier}' - - entity = self.credential_manager_service.get_credentials_configuration( - cloud=cloud, - cloud_identifier=cloud_identifier - ) - - if not entity: - _LOG.warning(head + ' does not exist.') - - elif customer and entity.customer != customer: - _customer = entity.customer - _LOG.warning(head + f' customer restriction - ' - f'\'{customer}\' != \'{_customer}\'') - entity = None - - elif tenants and entity.tenant not in tenants: - _tenant = entity.tenant - _tenants = ', '.join(map("'{}'".format, tenants)) - _LOG.warning(head + f' tenant {_tenant} must be reflected ' - f'within the granted ones - {_tenants}.') - entity = None - - return entity - - def configuration_object_exist(self, cloud, cloud_identifier): - return self.credential_manager_service.credentials_configuration_exists( - cloud=cloud, - cloud_identifier=cloud_identifier) - - def create_configuration_object(self, configuration_data): - return self.credential_manager_service.create_credentials_configuration( - configuration_data=configuration_data) - - def save_configuration_object(self, - credentials_manager: CredentialsManager): - return self.credential_manager_service.save( - credentials_manager=credentials_manager) - - def delete_configuration_object(self, entity: CredentialsManager): - return self.credential_manager_service.remove_entity(entity) - - def get_configuration_object_dto(self, entity): - return self.credential_manager_service.\ - get_credentials_manager_dto(entity) - - @property - def entity_name(self): - return 'credentials-manager' - - @property - def hash_key_attr_name(self): - return CLOUD_IDENTIFIER_ATTR - - @property - def range_key_attr_name(self): - return CLOUD_ATTR - - @property - def default_params_mapping(self): - return { - ENABLED: True - } - - @property - def required_params_list(self): - return [ - CLOUD_IDENTIFIER_ATTR, - TRUSTED_ROLE_ARN, - CLOUD_ATTR, - - # Account-based payload - TENANT_ATTR, - CUSTOMER_ATTR - ] - - @property - def full_params_list(self): - return [ - # Request-based payload - CLOUD_ATTR, - CLOUD_IDENTIFIER_ATTR, - TRUSTED_ROLE_ARN, - ENABLED, - - # Account-based payload - TENANT_ATTR, - CUSTOMER_ATTR - ] - - @property - def update_params_list(self): - return [ - TRUSTED_ROLE_ARN, - ENABLED - ] - - @property - def param_type_mapping(self): - """Validates Request-based payload.""" - # Obsolete, as of 3.2.0 - return { - CLOUD_ATTR: str, - CLOUD_IDENTIFIER_ATTR: str, - TRUSTED_ROLE_ARN: str, - ENABLED: bool - } - - def get_credentials_manager(self, event): - return self._basic_get_handler( - event=event) - - def add_credentials_manager(self, event): - return self._basic_create_handler( - event=event) - - def update_credentials_manager(self, event): - return self._basic_update_handler( - event=event) - - def remove_credentials_manager(self, event): - return self._basic_delete_handler( - event=event) - - @staticmethod - def is_accessible( - granted_customer: str, granted_tenants: List[str], - target_customer: str, target_tenant: str - ): - """ - Predicates whether a subject customer-tenant scope is accessible, - based on the granted customer and tenants. - :param granted_customer: str - :param granted_tenants: List[str] - :param target_tenant: str - :param target_customer: str - :return: bool - """ - - grant_access = True - - if granted_customer and target_customer != granted_customer: - _LOG.warning( - f'Customer restriction - ' - f'granted \'{granted_customer}\' customer must reflect' - f' \'{target_customer}\'.' - ) - grant_access = False - - elif granted_tenants and target_tenant not in granted_tenants: - _tenants = ', '.join(map("'{}'".format, granted_tenants)) - _LOG.warning( - 'Tenant restriction - ' - f'tenant {target_tenant} must be reflected ' - f'within the granted ones - {_tenants}.' - ) - grant_access = False - - return grant_access diff --git a/src/handlers/customer_handler.py b/src/handlers/customer_handler.py index 2993189ae..4f49b1383 100644 --- a/src/handlers/customer_handler.py +++ b/src/handlers/customer_handler.py @@ -1,379 +1,84 @@ -from http import HTTPStatus -from typing import Iterable, Callable, Dict, Union, Type, List, Iterator, \ - Optional - -from handlers.abstracts.abstract_handler import AbstractComposedHandler -from handlers.abstracts.abstract_modular_entity_handler import \ - ModularService, AbstractModularEntityHandler, ENTITY_TEMPLATE -from helpers import retrieve_invalid_parameter_types -from helpers.constants import CUSTOMER_ATTR, NAME_ATTR, CUSTOMER_ACTION, \ - PARAM_COMPLETE, LATEST_LOGIN_ATTR, INHERIT_ATTR, HTTPMethod -from helpers.log_helper import get_logger -from services.modular_service import Customer, Complemented -from services.user_service import CognitoUserService - -_LOG = get_logger(__name__) - -CUSTOMERS_PATH = '/customers' - -FORBIDDEN_ACCESS = 'Access to {} entity is forbidden.' - -PROPER_ACCEPTANCE = ('true', 'yes') - -AUTHORIZATION = 'Authorization' -SPECIFICATION = 'Query-Specification' -VALIDATION = 'Validation' - -ACCESSIBILITY = 'Entity-Accessibility' -AMENDMENT = 'Entity-Amendment' -PERSISTENCE = 'Entity-Persistence' - -PERSISTENCE_ERROR = ' does not exist' -RETAIN_ERROR = ' could not be persisted' - -RETRIEVED = ' has been retrieved' -INSTANTIATED = ' has been instantiated' - -ATTRIBUTE_UPDATED = ' {} has been set to {}' -RETAINED = ' has been persisted' - -I_SOURCE_ATTR = 'i_source' -CUSTOMER_ENTITY_ATTR = 'customer_entity' - - -class BaseCustomerHandler(AbstractModularEntityHandler): - - @property - def entity(self): - return CUSTOMER_ACTION.capitalize() - - -class GetCustomerHandler(BaseCustomerHandler): - i_source: Union[Iterator, Type[None]] - - def __init__(self, modular_service: ModularService, - user_service: CognitoUserService): - self.user_service = user_service - super().__init__(modular_service=modular_service) - - def _reset(self): - super()._reset() - # Declares a subjected query source-iterator - self.i_source = None - - def define_action_mapping(self): - return {CUSTOMERS_PATH: {HTTPMethod.GET: self.get_customer}} - - def get_customer(self, event): - action = HTTPMethod.GET.capitalize() - return self._process_action(event=event, action=action) - - @property - def attributes_to_log(self) -> Dict[Union[str, Type[None]], Iterable[str]]: - return { - AUTHORIZATION: [CUSTOMER_ATTR, NAME_ATTR], - SPECIFICATION: [ - CUSTOMER_ATTR, NAME_ATTR, PARAM_COMPLETE, I_SOURCE_ATTR - ] - } +from functools import cached_property +from typing import TYPE_CHECKING + +from handlers import AbstractHandler, Mapping +from helpers.constants import ( + CustodianEndpoint, + HTTPMethod, + TS_EXCLUDED_RULES_KEY, +) +from helpers.lambda_response import build_response +from services import SP +from validators.swagger_request_models import ( + CustomerExcludedRulesPutModel, + CustomerGetModel, + BaseModel +) +from validators.utils import validate_kwargs + +if TYPE_CHECKING: + from modular_sdk.services.customer_service import CustomerService + from modular_sdk.services.customer_settings_service import CustomerSettingsService + + +class CustomerHandler(AbstractHandler): + def __init__(self, customer_service: 'CustomerService', + customer_settings_service: 'CustomerSettingsService'): + self._cs = customer_service + self._css = customer_settings_service + + @classmethod + def build(cls) -> 'CustomerHandler': + return cls( + customer_service=SP.modular_client.customer_service(), + customer_settings_service=SP.modular_client.customer_settings_service(), + ) - @property - def responsibilities(self) -> Dict[ - str, Iterable[Callable[[Dict], Union[Dict, Type[None]]]] - ]: + @cached_property + def mapping(self) -> Mapping: return { - AUTHORIZATION: self.authorization_responsibility, - SPECIFICATION: self.specification_responsibilities + CustodianEndpoint.CUSTOMERS: { + HTTPMethod.GET: self.query + }, + CustodianEndpoint.CUSTOMERS_EXCLUDED_RULES: { + HTTPMethod.PUT: self.set_excluded_rules, + HTTPMethod.GET: self.get_excluded_rules + } } - @property - def authorization_responsibility(self): - return [self._access_restriction_step] - - @property - def specification_responsibilities(self): - return [ - self._obscure_specification_step, - self._complement_specification_step - ] - - def _access_restriction_step(self, event: Dict) -> \ - Union[Dict, Type[None]]: - """ - Mandates access authorization of SYSTEM and ordinary customers, - allowing action to commence for the primary ones as well as for - the later ones, which have sent pending inquery, targeting themselves. - :parameter event: Dict - :return: Union[Dict, Type[None]] - """ - customer: str = event.get(CUSTOMER_ATTR) - if customer is not None: - name: Union[str, Type[None]] = event.get(NAME_ATTR, None) - if name and name != customer: - event = None - self._code = HTTPStatus.FORBIDDEN - self._content = FORBIDDEN_ACCESS.format(self.entity) - return event - - def _obscure_specification_step(self, event: Dict) -> \ - Union[Dict, Type[None]]: - """ - Mandates concealing specification, based on given query, - either following: - 1. Given a customer `name` has been issued, respectively - amends said specification to retrieve a demanded entity. - 2. Given a non-system customer has issued one, obscures - the view to the said customer. - :parameter event: Dict - :return: Union[Dict, Type[None]] - """ - - customer = event.get(CUSTOMER_ATTR) - name = event.get(NAME_ATTR) - _any = any((customer, name)) - self.i_source = iter([customer or name]) if _any else self.i_source - return event - - def _complement_specification_step(self, event: Dict) \ - -> Union[Dict, Type[None]]: - """ - Mandates parent-complement specification, based on given query. - Given a `complete` parameter has been issued, respectively - amends said specification to retrieve an infused entity. - :parameter event: Dict - :return: Union[Dict, Type[None]] - """ - if event.get(PARAM_COMPLETE): - self.i_source = self.i_query - return event - - @property - def i_query(self) -> Iterator: - """ - Produces a query iterator-like output, based on the pending - specification, resetting it afterwards. - :return: Iterator - """ - i_source = self.i_source - - if i_source: - query = self.modular_service.i_get_customer(i_source) + @validate_kwargs + def query(self, event: CustomerGetModel): + if event.name: + item = self._cs.get(event.name) + customers = [item] if item else [] else: - query = self.modular_service.i_get_customers() - - self.i_source = None - return query - - def _produce_response_dto(self, event: Optional[Dict] = None) -> \ - Union[str, Dict, List, Type[None]]: - """ - Mandates derivation of a query-response data transfer object, - based on a pending source-iterator. Apart from that, - for each entity, a user-respective latest-login is injected - into attached customer dto. - :parameter event: Optional[Dict] - :return: Union[Dict, List, Type[None]] - """ - attr = NAME_ATTR - i_query, dto = self.i_query, [] - get_dto = self.modular_service.get_dto - get_logins = self.user_service.get_customers_latest_logins - - customers: list = [get_dto(each) for each in i_query] - names: list = [each.get(attr) for each in customers if attr in each] - - login_reference: dict = get_logins(names) if names else {} - - for customer in customers: - name = customer.get(attr) - if name in login_reference: - customer[LATEST_LOGIN_ATTR] = login_reference[name] - - return customers - - -class PatchCustomerHandler(BaseCustomerHandler): - customer_entity: Union[Customer, Complemented, Type[None]] - - def __init__(self, modular_service: ModularService): - super().__init__(modular_service=modular_service) - - def _reset(self): - super()._reset() - # Declares request-pending customer entity - self.customer_entity = None - - def define_action_mapping(self): - return {CUSTOMERS_PATH: {HTTPMethod.PATCH: self.patch_customer}} - - def patch_customer(self, event): - action = HTTPMethod.PATCH.capitalize() - return self._process_action(event=event, action=action) - - @property - def attributes_to_log(self) -> Dict[Union[str, Type[None]], Iterable[str]]: - return { - VALIDATION: list(self._patch_required_map), - ACCESSIBILITY: [NAME_ATTR, CUSTOMER_ENTITY_ATTR], - AMENDMENT: [CUSTOMER_ENTITY_ATTR] + self._amend_attribute_list, - PERSISTENCE: [CUSTOMER_ENTITY_ATTR] - } - - @property - def responsibilities(self) -> Dict[ - str, Iterable[Callable[[Dict], Union[Dict, Type[None]]]] - ]: - return { - VALIDATION: self.validation_responsibilities, - ACCESSIBILITY: self.access_responsibilities, - AMENDMENT: self.amendment_responsibilities, - PERSISTENCE: self.persistence_responsibilities - } - - @property - def validation_responsibilities(self): - return [self._validate_payload_types_step] - - @property - def access_responsibilities(self): - return [ - self._access_customer_entity_step - ] - - @property - def amendment_responsibilities(self): - return [self._amend_customer_parent_step] - - @property - def persistence_responsibilities(self): - return [self._persist_customer_parent_step] - - @property - def _patch_required_map(self): - return {CUSTOMER_ATTR: str, INHERIT_ATTR: bool} - - @property - def _amend_attribute_list(self): - return [INHERIT_ATTR] - - def _validate_payload_types_step(self, event: Dict): - """ - Mandates payload parameters for the patch event, adhering - to the respective requirement map. - :parameter event: Dict - :return: Union[Dict, Type[None]] - """ - content = retrieve_invalid_parameter_types( - event=event, required_param_types=self._patch_required_map + customers = self._cs.i_get_customer() + return build_response(map(self._cs.get_dto, customers)) + + @validate_kwargs + def set_excluded_rules(self, event: CustomerExcludedRulesPutModel): + data = {'rules': list(event.rules)} + item = self._css.create( + customer_name=event.customer, + key=TS_EXCLUDED_RULES_KEY, + value=data ) - if content: - event = None - self._code = HTTPStatus.BAD_REQUEST - self._content = content - - return event - - def _access_customer_entity_step(self, event: Dict): - """ - Mandates complemented Customer Parent entity retrieval and lack of - persistence. - Response variables are assigned, under the following predicates: - Given absence: `code` - 404, `content` - persistence reason. - :parameter event: Dict - :return: Union[Dict, Type[None]] - """ - - name = event.get(CUSTOMER_ATTR, '') - _template = ENTITY_TEMPLATE.format(entity=self.entity, id=name) - - parent = self.modular_service.get_customer_bound_parent(name) - if not parent: - event = None - self._code = HTTPStatus.FORBIDDEN - self._content = 'Custodian customer parent does not exist. ' \ - 'Cannot set inherit' - return - customer = self.modular_service.get_customer(name) - self.customer_entity = Complemented( - entity=customer, complement=parent + self._css.save(item) + data['customer_name'] = event.customer + return build_response(data) + + @validate_kwargs + def get_excluded_rules(self, event: BaseModel): + item = self._css.get_nullable( + customer_name=event.customer, + key=TS_EXCLUDED_RULES_KEY ) - - _LOG.info(_template + RETRIEVED) - return event - - def _amend_customer_parent_step(self, event: Dict): - """ - Mandates complemented Customer Parent entity patching.\n - Response variables are assigned, under the following predicates: - \nGiven absence: `code` - 404, `content` - persistence reason. - :parameter event: Dict - :return: Union[Dict, Type[None]] - """ - entity: Complemented = self.customer_entity - if entity: - head = ENTITY_TEMPLATE.format(entity=self.entity, id=entity.name) - for attribute in self._amend_attribute_list: - if attribute in event: - args = attribute, event.get(attribute) - setattr(entity, *args) - _LOG.info(head + ATTRIBUTE_UPDATED.format(*args)) - else: - _LOG.error('Execution step missing \'customer_entity\' variable.') - event = None - - return event - - def _persist_customer_parent_step(self, event: Dict): - """ - Mandates Complemented Customer Parent entity persistence.\n - Response variables are assigned, under the following predicates: - \nGiven absence: `code` - 500, `content` - failed to retain reason. - :parameter event: Dict - :return: Union[Dict, Type[None]] - """ - entity: Complemented = self.customer_entity - if not entity: - _LOG.error('Execution step missing \'customer_entity\' variable.') - return None - - _template = ENTITY_TEMPLATE.format(entity=self.entity, id=entity.name) - modular = self.modular_service - if entity and modular.save(entity=entity): - _LOG.info(_template + RETAINED) - elif entity: - event = None - _LOG.error(_template + RETAIN_ERROR) - - return event - - def _produce_response_dto(self, event: Optional[Dict] = None) -> \ - Union[str, Dict, List, Type[None]]: - """ - Mandates derivation of a patch-response data transfer object, - based on a pending Customer Parent entity. - :parameter event: Dict - :return: Union[Dict, List, Type[None]] - """ - get_dto = self.modular_service.get_dto - entity, self.customer_entity = self.customer_entity, None - return get_dto(entity=entity) or [] - - -class CustomerHandler(AbstractComposedHandler): - ... - - -def instantiate_customer_handler(modular_service: ModularService, - user_service: CognitoUserService): - patch_customer_handler = PatchCustomerHandler( - modular_service=modular_service) - get_customer_handler = GetCustomerHandler( - modular_service=modular_service, user_service=user_service - ) - return CustomerHandler( - resource_map={ - CUSTOMERS_PATH: { - HTTPMethod.GET: get_customer_handler, - } - } - ) + if not item: + return build_response({ + 'rules': [], + 'customer_name': event.customer + }) + return build_response({ + 'rules': item.value.get('rules') or [], + 'customer_name': event.customer + }) diff --git a/src/handlers/defect_dojo_handler.py b/src/handlers/defect_dojo_handler.py new file mode 100644 index 000000000..b430bc34c --- /dev/null +++ b/src/handlers/defect_dojo_handler.py @@ -0,0 +1,192 @@ +from functools import cached_property +from http import HTTPStatus +from typing import Iterable + +from modular_sdk.commons.constants import ApplicationType, ParentType +from modular_sdk.models.parent import Parent +from modular_sdk.services.application_service import ApplicationService +from modular_sdk.services.parent_service import ParentService + +from handlers import AbstractHandler, Mapping +from helpers.constants import CustodianEndpoint, HTTPMethod +from helpers.lambda_response import ResponseFactory, build_response +from services import SP +from services.abs_lambda import ProcessedEvent +from services.defect_dojo_service import DefectDojoParentMeta, DefectDojoService +from services.modular_helpers import ( + ResolveParentsPayload, + build_parents, + get_activation_dto, +) +from validators.swagger_request_models import ( + BaseModel, + DefectDojoActivationPutModel, + DefectDojoPostModel, + DefectDojoQueryModel, +) +from validators.utils import validate_kwargs + + +class DefectDojoHandler(AbstractHandler): + def __init__(self, defect_dojo_service: DefectDojoService, + application_service: ApplicationService, + parent_service: ParentService): + self._dds = defect_dojo_service + self._aps = application_service + self._ps = parent_service + + @classmethod + def build(cls) -> 'DefectDojoHandler': + return cls( + defect_dojo_service=SP.defect_dojo_service, + application_service=SP.modular_client.application_service(), + parent_service=SP.modular_client.parent_service() + ) + + @cached_property + def mapping(self) -> Mapping: + return { + CustodianEndpoint.INTEGRATIONS_DEFECT_DOJO: { + HTTPMethod.POST: self.post, + HTTPMethod.GET: self.query + }, + CustodianEndpoint.INTEGRATIONS_DEFECT_DOJO_ID: { + HTTPMethod.GET: self.get, + HTTPMethod.DELETE: self.delete + }, + CustodianEndpoint.INTEGRATIONS_DEFECT_DOJO_ID_ACTIVATION: { + HTTPMethod.PUT: self.put_activation, + HTTPMethod.DELETE: self.delete_activation, + HTTPMethod.GET: self.get_activation + } + } + + @validate_kwargs + def post(self, event: DefectDojoPostModel, _pe: ProcessedEvent): + dojo = self._dds.create( + customer=event.customer, + description=event.description, + created_by=_pe['cognito_user_id'] + ) + url = event.url + dojo.update_host( + host=url.host, + port=int(url.port) if url.port else None, + protocol=url.scheme, + stage=url.path + ) + self._dds.set_dojo_api_key(dojo, event.api_key) + # todo check connect + self._dds.save(dojo) + return build_response(content=self._dds.dto(dojo), + code=HTTPStatus.CREATED) + + @validate_kwargs + def query(self, event: DefectDojoQueryModel): + cursor = self._aps.i_get_application_by_customer( + customer_id=event.customer, + application_type=ApplicationType.DEFECT_DOJO.value + ) + cursor = self._dds.to_dojos(cursor) + return build_response(content=map(self._dds.dto, cursor)) + + @validate_kwargs + def get(self, event: BaseModel, id: str): + item = self._dds.get_nullable(id) + if not item or event.customer and item.customer != event.customer: + raise ResponseFactory(HTTPStatus.NOT_FOUND).message( + self._dds.not_found_message(id) + ).exc() + return build_response(content=self._dds.dto(item)) + + @validate_kwargs + def delete(self, event: BaseModel, id: str): + item = self._dds.get_nullable(id) + if not item or event.customer and item.customer != event.customer: + return build_response(code=HTTPStatus.NO_CONTENT) + self._dds.delete(item) + for parent in self.get_all_activations(item.id, event.customer): + self._ps.force_delete(parent) + return build_response(code=HTTPStatus.NO_CONTENT) + + def get_all_activations(self, dojo_id: str, + customer: str | None = None) -> Iterable[Parent]: + it = self._ps.i_list_application_parents( + application_id=dojo_id, + type_=ParentType.SIEM_DEFECT_DOJO, + rate_limit=3 + ) + if customer: + it = filter(lambda p: p.customer_id == customer, it) + return it + + @staticmethod + def get_dto(parents: Iterable[Parent], + meta: DefectDojoParentMeta | dict) -> dict: + base = get_activation_dto(parents) + base.update(meta if isinstance(meta, dict) else meta.dto()) + return base + + @validate_kwargs + def put_activation(self, event: DefectDojoActivationPutModel, id: str, + _pe: ProcessedEvent): + item = self._dds.get_nullable(id) + if not item or event.customer and item.customer != event.customer: + raise ResponseFactory(HTTPStatus.NOT_FOUND).message( + self._dds.not_found_message(id) + ).exc() + for parent in self.get_all_activations(item.id, event.customer): + self._ps.force_delete(parent) + + meta = DefectDojoParentMeta( + scan_type=event.scan_type, + product_type=event.product_type, + product=event.product, + engagement=event.engagement, + test=event.test, + send_after_job=event.send_after_job, + attachment=event.attachment + ) + to_create = build_parents( + payload=ResolveParentsPayload( + parents=list(), + tenant_names=event.tenant_names, + exclude_tenants=event.exclude_tenants, + clouds=event.clouds, + all_tenants=event.all_tenants + ), + parent_service=self._ps, + application_id=id, + customer_id=event.customer, + type_=ParentType.SIEM_DEFECT_DOJO, + created_by=_pe['cognito_user_id'], + meta=meta.dict() + ) + for parent in to_create: + self._ps.save(parent) + return build_response(content=self.get_dto(to_create, meta)) + + @validate_kwargs + def delete_activation(self, event: BaseModel, id: str): + item = self._dds.get_nullable(id) + if not item or event.customer and item.customer != event.customer: + raise ResponseFactory(HTTPStatus.NOT_FOUND).message( + self._dds.not_found_message(id) + ).exc() + for parent in self.get_all_activations(item.id, event.customer): + self._ps.force_delete(parent) + return build_response(code=HTTPStatus.NO_CONTENT) + + @validate_kwargs + def get_activation(self, event: BaseModel, id: str): + item = self._dds.get_nullable(id) + if not item or event.customer and item.customer != event.customer: + raise ResponseFactory(HTTPStatus.NOT_FOUND).message( + self._dds.not_found_message(id) + ).exc() + parents = list(self.get_all_activations(item.id, event.customer)) + if not parents: + return build_response(self.get_dto([], {})) + # first because they are all equal + meta = DefectDojoParentMeta.from_dict(parents[0].meta.as_dict()) + return build_response(self.get_dto(parents, meta)) diff --git a/src/handlers/details_handler.py b/src/handlers/details_handler.py index c012b2638..60436799a 100644 --- a/src/handlers/details_handler.py +++ b/src/handlers/details_handler.py @@ -1,613 +1,152 @@ -from datetime import datetime +from functools import cached_property from http import HTTPStatus -from typing import List, Optional, Dict -from handlers.base_handler import \ - BaseReportHandler, \ - SourceReportDerivation, \ - EntitySourcedReportDerivation, SourcedReport, Report, EntityToReport, \ - SourceToReport -from helpers.constants import (CUSTOMER_ATTR, TENANTS_ATTR, - TENANT_ATTR, START_ISO_ATTR, END_ISO_ATTR, - HREF_ATTR, CONTENT_ATTR, - ID_ATTR, HTTPMethod - ) -from helpers.log_helper import get_logger -from services.ambiguous_job_service import Source -from services.report_service import \ - USER_REPORT_FILE, \ - DETAILED_REPORT_FILE, FindingsCollection - -DEFAULT_UNRESOLVABLE_RESPONSE = 'Request has run into an unresolvable issue.' - -TYPE_ATTR = 'type' -JOB_ID_ATTR = 'job_id' -RAW_ATTR = 'raw' - -ENTITY_ATTR_KEY = 'entity_attr' -ENTITY_VALUE_KEY = 'entity_value' - -TENANT_NAME_ATTR = 'tenant_name' -TENANT_DISPLAY_NAME_ATTR = 'tenant_display_name' - -_LOG = get_logger(__name__) - -# Report Details of Jobs resources -JOB_ENDPOINT = '/reports/details/jobs/{id}' -TENANTS_JOBS_ENDPOINT = '/reports/details/tenants/jobs' -TENANT_JOBS_ENDPOINT = '/reports/details/tenants/{tenant_name}/jobs' - -TENANTS_ENDPOINT = '/reports/details/tenants' -TENANT_ENDPOINT = '/reports/details/tenants/{tenant_name}' -ACCOUNT_ENDPOINT = '/reports/details/tenants/{tenant_name}/accounts/{account_display_name}' - -# Scrapped, due to attribute naming inconsistency -ACCOUNTS_ENDPOINT = '/reports/details/tenants/{tenant_name}/accounts' - -NO_RESOURCES_FOR_REPORT = ' maintain(s) no resources to derive a report.' - - -class BaseDetailsReportHandler(BaseReportHandler): - """ - Provides base behaviour of detailed-reporting, establishing - report-derivation function. - """ - - @property - def _source_report_derivation_function(self) -> SourceReportDerivation: - return self._details_report_derivation - - def _details_report_derivation( - self, source: Source, **kwargs: dict - ) -> Optional[Report]: - - """ - Obtains user-accessible details-report of a sourced job, returning a - respective detailed-report. - :param source: Source - :param kwargs: Dict, maintains `href` attribute, denoting demand - for hypertext reference. - :return: Optional[Report] - """ - - jid = self._ambiguous_job_service.get_attribute(source, ID_ATTR) - href = kwargs.get(HREF_ATTR) - rs = self._report_service - - detailed_path = rs.derive_job_object_path(job_id=jid, - typ=DETAILED_REPORT_FILE) - u_detailed_path = rs.derive_job_object_path(job_id=jid, - typ=USER_REPORT_FILE) - - _head = f'Native Job:\'{jid}\'' - - if href: - _LOG.info(_head + ' going to obtain hypertext reference' - 'of the details report.') - ref: Optional[str] = rs.href_job_report( - path=u_detailed_path, check=True - ) - if ref: - return ref - - # Going to generate standalone. - - # Maintains raw data, from now on. - ref: Optional[dict] = None - - if not href: - # Has not been pulled yet. - ref: Optional[dict] = rs.pull_job_report(path=u_detailed_path) - if ref: - ref: FindingsCollection = rs.derive_findings_from_report( - report=ref, user_detailed=True - ) - ref: dict = ref.region_report - - if not ref: - message = ' user-report could not be found, pulling detailed one.' - _LOG.warning(_head + message) - detailed = self._report_service.pull_job_report(path=detailed_path) - if detailed: - ref: FindingsCollection = rs.derive_findings_from_report( - report=detailed, user_detailed=False - ) - ref: dict = ref.region_report - - _LOG.warning(_head + ' patching user-report absence.') - if self._report_service.put_job_report( - path=u_detailed_path, data=ref - ) is None: - message = ' user-report could not be patched.' - _LOG.warning(_head + message) - ref = None if href else ref - - if href and ref: - # Pull after successful patch. - ref: Optional[str] = self._report_service.href_job_report( - path=u_detailed_path, check=False - ) - - return ref - - @property - def _entity_sourced_report_derivation_function( - self) -> EntitySourcedReportDerivation: - return self._entity_report_derivation - - def _entity_report_derivation( - self, sourced_reports: List[SourcedReport], **kwargs: dict - ) -> Optional[Report]: - """ - Accumulates detailed-reports of entity-related sources, returning a - respective entity-based report of details. - :param sourced_reports: List[Tuple[Source, Report=Dict]] - :param kwargs: Dict, maintains `entity_attr` and - optional, target `entity_value`, as well as `href` attribute, - denoting demand for hypertext reference. - :return: List[Report] - """ - entity_attr: str = kwargs.get(ENTITY_ATTR_KEY, '') - entity_value: str = kwargs.get(ENTITY_VALUE_KEY, '') - assert entity_attr, 'Entity attribute is missing' - - dynamic_entity = not bool(entity_value) - - _head = f'{entity_attr.capitalize()}' - href = kwargs.get(HREF_ATTR) - - ref: List[Dict] = [] - ajs = self._ambiguous_job_service - rs = self._report_service - # Given that reports are not sorted, manually establish bounds. - - start, end = None, None - for sourced in sourced_reports: - source, report = sourced - - invariant = 'detailed report(s) of source(s) must be \'dict\'(s)' - assert isinstance(report, dict), f'{_head} {invariant}' - - if dynamic_entity: - _entity = ajs.get_attribute(item=source, attr=entity_attr) - if not entity_value: - entity_value = _entity - - elif entity_value != _entity: - # Precautionary verification. - typ = ajs.get_type(item=source) - uid = ajs.get_attribute(item=source, attr=ID_ATTR) - _e_scope = f'{_head}:\'{entity_value}\'' - _s_scope = f'{_entity} of {typ} \'{uid}\' job' - _LOG.error(f'{_e_scope} mismatches {_s_scope}.') - ref = [] - break - - ref.append(report) - sk = ajs.sort_value_into_datetime(item=source) - if not start or not end: - start = end = sk - - start = sk if sk > start else start - end = sk if sk < end else end - - if not ref: - return None - - _head += f':\'{entity_value}\'' - - # Establish a report-key - path = rs.derive_details_report_object_path( - entity_value=entity_value, entity_attr=entity_attr, - start=start, end=end - ) - - if href: - message = ' obtaining hypertext reference of entity-details report' - _LOG.info(_head + f'{message}.') - url: str = rs.href_concrete_report(path=path, check=True) - if url: - return url - else: - message = ' checking for entity details report presence.' - _LOG.info(_head + message) - if rs.check_concrete_report(path=path): - precomputed: dict = rs.pull_concrete_report(path=path) - if precomputed: - return precomputed - - # Data does not exist or could not be retrieved - generating. - # Going to generate standalone. - message = ' accumulating details of sourced out findings.' - _LOG.info(_head + message) - ref: Optional[FindingsCollection] = rs.accumulate_details( - detailed_report_list=ref, user_detailed=True - ) - if isinstance(ref, FindingsCollection): - ref: dict = ref.region_report - - # todo if expiration rule is assigned, retain per each - # otherwise - retain only per hypertext request. - - if href and ref: - # Pull after successful patch. - message = ' retaining accumulated details report.' - _LOG.info(_head + message) - if rs.put_json_concrete_report(data=ref, path=path): - message = ' obtaining self-retained hypertext reference.' - _LOG.info(_head + message) - ref = rs.href_concrete_report(path=path, check=False) - - return ref - - -class JobsDetailsHandler(BaseDetailsReportHandler): - - def define_action_mapping(self): +from modular_sdk.services.tenant_service import TenantService + +from handlers import AbstractHandler, Mapping +from helpers.constants import CustodianEndpoint, HTTPMethod, JobState, \ + ReportFormat +from helpers.lambda_response import build_response +from helpers import flip_dict +from services import SP +from services import modular_helpers +from services.ambiguous_job_service import AmbiguousJob, AmbiguousJobService +from services.platform_service import PlatformService +from services.report_convertors import ShardsCollectionDetailsConvertor +from services.report_service import ReportResponse, ReportService +from services import obfuscation +from services.sharding import ShardsCollection +from validators.swagger_request_models import ( + JobDetailsReportGetModel, + TenantJobsDetailsReportGetModel, +) +from validators.utils import validate_kwargs + + +class DetailedReportHandler(AbstractHandler): + def __init__(self, ambiguous_job_service: AmbiguousJobService, + report_service: ReportService, + tenant_service: TenantService, + platform_service: PlatformService): + self._ambiguous_job_service = ambiguous_job_service + self._rs = report_service + self._ts = tenant_service + self._platform_service = platform_service + + @classmethod + def build(cls) -> 'AbstractHandler': + return cls( + ambiguous_job_service=SP.ambiguous_job_service, + report_service=SP.report_service, + tenant_service=SP.modular_client.tenant_service(), + platform_service=SP.platform_service + ) + + @cached_property + def mapping(self) -> Mapping: return { - JOB_ENDPOINT: { + CustodianEndpoint.REPORTS_DETAILS_JOBS_JOB_ID: { HTTPMethod.GET: self.get_by_job }, - TENANT_JOBS_ENDPOINT: { - HTTPMethod.GET: self.get_by_tenant - }, - TENANTS_JOBS_ENDPOINT: { - HTTPMethod.GET: self.query_by_tenant - } - } - - def get_by_job(self, event: dict): - _LOG.info(f'GET Job Details Report(s) - {event}.') - # Note that `job_id` denotes the primary-key's hash-key of entities. - uid: str = event[ID_ATTR] - typ: str = event[TYPE_ATTR] - customer = event.get(CUSTOMER_ATTR) - tenants = event.get(TENANTS_ATTR) - href = event.get(HREF_ATTR) - - head = f'{typ.capitalize()} Job:\'{uid}\'' - - source = self._attain_source( - uid=uid, typ=typ, customer=customer, tenants=tenants - ) - if not source: - return self.response - - referenced_reports = self._attain_source_report_map( - source_list=[source], href=href - ) - if referenced_reports: - ref_attr = HREF_ATTR if href else CONTENT_ATTR - self._code = HTTPStatus.OK - self._content = [ - self.dto(source=source, report=report, ref_attr=ref_attr) - for source, report in referenced_reports.items() - ] - else: - message = f' - no report could be derived.' - _LOG.warning(head + message) - self._code = HTTPStatus.NOT_FOUND - self._content = head + NO_RESOURCES_FOR_REPORT - - return self.response - - def get_by_tenant(self, event: dict): - _LOG.info(f'GET Job Details Report(s) of a Tenant - {event}.') - # `tenant` has been injected via the restriction service. - tenant_name = event[TENANT_ATTR] - # Lower bound. - start_iso: datetime = event[START_ISO_ATTR] - # Upper bound. - end_iso: datetime = event[END_ISO_ATTR] - href = event.get(HREF_ATTR) - - customer = event.get(CUSTOMER_ATTR) - tenant = self._attain_tenant( - name=tenant_name, customer=customer, active=True - ) - if not tenant: - return self.response - - referenced_reports = self._attain_referenced_reports( - start_iso=start_iso, end_iso=end_iso, - customer=tenant.customer_name, tenants=[tenant_name], - typ=event.get(TYPE_ATTR), href=event.get(HREF_ATTR) - ) - - if referenced_reports: - ref_attr = HREF_ATTR if href else CONTENT_ATTR - self._code = HTTPStatus.OK - self._content = [ - self.dto(source=source, report=report, ref_attr=ref_attr) - for source, report in referenced_reports.items() - ] - - return self.response - - def query_by_tenant(self, event: dict): - - _LOG.info(f'GET Job Details Report(s) of Tenant(s) - {event}.') - # `tenants` has been injected via the restriction service. - tenant_names = event[TENANTS_ATTR] - start_iso: datetime = event[START_ISO_ATTR] - end_iso: datetime = event[END_ISO_ATTR] - customer = event.get(CUSTOMER_ATTR) - href = event.get(HREF_ATTR) - - referenced_reports = self._attain_referenced_reports( - start_iso=start_iso, end_iso=end_iso, - customer=customer, tenants=tenant_names, - typ=event.get(TYPE_ATTR), href=event.get(HREF_ATTR) - ) - - if referenced_reports: - ref_attr = HREF_ATTR if href else CONTENT_ATTR - self._code = HTTPStatus.OK - self._content = [ - self.dto(source=source, report=report, ref_attr=ref_attr) - for source, report in referenced_reports.items() - ] - - return self.response - - def _attain_referenced_reports( - self, start_iso: datetime, end_iso: datetime, - customer: Optional[str] = None, - tenants: Optional[List[str]] = None, - cloud_ids: Optional[List[str]] = None, - typ: Optional[str] = None, href: bool = False - ) -> Optional[SourceToReport]: - - cloud_ids = cloud_ids or [] - ajs = self._ambiguous_job_service - - head = '' - - # Log-Header. - if tenants: - multiple = len(tenants) > 1 - bind = ', '.join(map("'{}'".format, tenants or [])) - if head: - bind = f', bound to {bind}' - head += f'{bind} tenant' - if multiple: - head += 's' - - if customer: - head = 'Tenants' if not head else head - head += f' of \'{customer}\' customer' - - typ_scope = f'{typ} type' if typ else 'all types' - time_scope = f'from {start_iso.isoformat()} till {end_iso.isoformat()}' - job_scope = f'job(s) of {typ_scope}, {time_scope}' - - # todo Responsibility chain - - _LOG.info(f'Obtaining {job_scope}, for {head or "tenants"}.') - head = head or 'Tenants' - typ_params_map = ajs.derive_typ_param_map( - typ=typ, tenants=tenants, - cloud_ids=cloud_ids - ) - source_list = ajs.batch_list( - typ_params_map=typ_params_map, customer=customer, - start=start_iso, end=end_iso, sort=True - ) - if not source_list: - message = f' - no source-data of {job_scope} could be derived.' - _LOG.warning(head + message) - self._code = HTTPStatus.NOT_FOUND - self._content = head + NO_RESOURCES_FOR_REPORT - return - - _source_scope = ', '.join( - ajs.get_attribute(item=s, attr=ID_ATTR) - for s in source_list - ) - _LOG.info(head + f' retrieving reports of {_source_scope} source(s).') - source_to_report = self._attain_source_report_map( - source_list=source_list, href=href - ) - if not source_to_report: - message = f' - no reports of {job_scope} could be derived.' - _LOG.warning(head + message) - self._code = HTTPStatus.NOT_FOUND - self._content = head + NO_RESOURCES_FOR_REPORT - return - - return source_to_report - - def dto(self, source: Source, report: Report, ref_attr: str): - return { - ID_ATTR: self._ambiguous_job_service.get_attribute( - item=source, attr=ID_ATTR - ), - TYPE_ATTR: self._ambiguous_job_service.get_type(item=source), - ref_attr: report - } - - -class EntityDetailsHandler(BaseDetailsReportHandler): - - def define_action_mapping(self): - return { - TENANT_ENDPOINT: { - HTTPMethod.GET: self.get_by_tenant - }, - TENANTS_ENDPOINT: { - HTTPMethod.GET: self.query_by_tenant + CustodianEndpoint.REPORTS_DETAILS_TENANTS_TENANT_NAME_JOBS: { + HTTPMethod.GET: self.get_by_tenant_jobs } } - def get_by_tenant(self, event: dict): - _LOG.info(f'GET accumulated detailed Report(s) of a Tenant - {event}.') - # `tenant` has been injected via the restriction service. - tenant_name = event[TENANT_ATTR] - start_iso: datetime = event[START_ISO_ATTR] - end_iso: datetime = event[END_ISO_ATTR] - - href = event.get(HREF_ATTR) - customer = event.get(CUSTOMER_ATTR) - tenant = self._attain_tenant( - name=tenant_name, customer=customer, active=True - ) - if not tenant: - return self.response - - referenced_reports = self._attain_referenced_reports( - entity_attr=TENANT_ATTR, entity_value=tenant_name, - start_iso=start_iso, end_iso=end_iso, - customer=tenant.customer_name, tenants=[tenant_name], - typ=event.get(TYPE_ATTR), href=event.get(HREF_ATTR) - ) - if referenced_reports: - ref_attr = HREF_ATTR if href else CONTENT_ATTR - self._code = HTTPStatus.OK - self._content = [ - self.dto( - entity_attr=TENANT_ATTR, entity_value=entity, - report=report, ref_attr=ref_attr - ) - for entity, report in referenced_reports.items() - ] - return self.response - - def query_by_tenant(self, event: dict): - - _LOG.info(f'GET accumulated detailedReport(s) of Tenant(s) - {event}.') - # `tenants` has been injected via the restriction service. - tenant_names = event[TENANTS_ATTR] - start_iso: datetime = event[START_ISO_ATTR] - end_iso: datetime = event[END_ISO_ATTR] - - customer = event.get(CUSTOMER_ATTR) - href = event.get(HREF_ATTR) - - referenced_reports = self._attain_referenced_reports( - entity_attr=TENANT_ATTR, - start_iso=start_iso, end_iso=end_iso, - customer=customer, tenants=tenant_names, - typ=event.get(TYPE_ATTR), href=event.get(HREF_ATTR) - ) + @validate_kwargs + def get_by_job(self, event: JobDetailsReportGetModel, job_id: str): + job = self._ambiguous_job_service.get_job( + job_id=job_id, + typ=event.job_type, + customer=event.customer + ) + if not job: + return build_response( + content='The request job not found', + code=HTTPStatus.NOT_FOUND + ) - if referenced_reports: - ref_attr = HREF_ATTR if href else CONTENT_ATTR - self._code = HTTPStatus.OK - self._content = [ - self.dto( - entity_attr=TENANT_ATTR, entity_value=entity, - report=report, ref_attr=ref_attr + if job.is_platform_job: + platform = self._platform_service.get_nullable(job.platform_id) + if not platform: + return build_response( + content='Job platform not found', + code=HTTPStatus.NOT_FOUND ) - for entity, report in referenced_reports.items() - ] - - return self.response - - def _attain_referenced_reports( - self, entity_attr: str, - start_iso: datetime, end_iso: datetime, - customer: Optional[str] = None, - tenants: Optional[List[str]] = None, - cloud_ids: Optional[List[str]] = None, - typ: Optional[str] = None, href: bool = False, - entity_value: Optional[str] = None - ) -> EntityToReport: - - cloud_ids = cloud_ids or [] - ajs = self._ambiguous_job_service - - head = '' - - # Log-Header. - if tenants: - bind = ', '.join(map("'{}'".format, tenants or [])) - if head: - bind = ', bound to ' - head += f'{bind} tenant(s)' - - if customer: - head += f' of \'{customer}\' customer' - - typ_scope = f'{typ} type' if typ else 'all types' - time_scope = f'from {start_iso.isoformat()} till {end_iso.isoformat()}' - job_scope = f'job(s) of {typ_scope}, {time_scope}' - - entity_scope = f'\'{entity_attr}\' entity' - if entity_value: - entity_scope += f' of the \'{entity_value}\' target value' - - # todo Responsibility chain - - _LOG.info(f'Obtaining {job_scope}, for {head or "tenants"}.') - head = head or 'Tenants' - typ_params_map = ajs.derive_typ_param_map( - typ=typ, tenants=tenants, - cloud_ids=cloud_ids - ) - source_list = ajs.batch_list( - typ_params_map=typ_params_map, customer=customer, - start=start_iso, end=end_iso, sort=False - ) - if not source_list: - message = f' - no source-data of {job_scope} could be derived.' - _LOG.warning(head + message) - self._code = HTTPStatus.NOT_FOUND - self._content = head + NO_RESOURCES_FOR_REPORT - return self.response - - _source_scope = ', '.join( - ajs.get_attribute(item=s, attr=ID_ATTR) for s in source_list - ) - - # Explicitly pulling raw output to compute on. - _LOG.info(head + f' retrieving reports of {_source_scope} source(s).') - source_to_report = self._attain_source_report_map( - source_list=source_list - ) - if not source_to_report: - message = f' - no reports of {job_scope} could be derived.' - _LOG.warning(head + message) - self._code = HTTPStatus.NOT_FOUND - self._content = head + NO_RESOURCES_FOR_REPORT - return self.response - - message = f' mapping sourced reports to {entity_scope}.' - _LOG.info(head + message) - - entity_sourced_reports = self._attain_entity_sourced_reports( - entity_attr=entity_attr, entity_value=entity_value, - source_to_report=source_to_report - ) - if not entity_sourced_reports: - message = f' - no reports of {job_scope} could be mapped ' - message += f' to {entity_scope}.' - _LOG.warning(head + message) - self._code = HTTPStatus.NOT_FOUND - self._content = head + NO_RESOURCES_FOR_REPORT - return self.response - - message = f' deriving detailed reports of {entity_scope}.' - _LOG.info(head + message) + collection = self._rs.platform_job_collection(platform, job.job) + collection.meta = self._rs.fetch_meta(platform) + else: + tenant = self._ts.get(job.tenant_name) + modular_helpers.assert_tenant_valid(tenant, event.customer) + collection = self._rs.ambiguous_job_collection(tenant, job) + collection.meta = self._rs.fetch_meta(tenant) - entity_to_report = self._attain_entity_report_map_from_sourced_reports( - entity_sourced_reports=entity_sourced_reports, - entity_attr=entity_attr, entity_value=entity_value, href=href + return build_response( + content=self._collection_response(job, collection, event.href, + event.obfuscated) ) - if not entity_to_report: - message = f' - no reports of {job_scope} could be derived' - message += f' based on {entity_scope}.' - _LOG.warning(head + message) - self._code = HTTPStatus.NOT_FOUND - self._content = head + NO_RESOURCES_FOR_REPORT - return self.response - return entity_to_report + def _collection_response(self, job: AmbiguousJob, + collection: ShardsCollection, + href: bool = False, + obfuscated: bool = False + ) -> dict: + """ + Builds response for the given collection + :param collection: + :param job: + :param href: + :return: + """ + collection.fetch_all() + + dictionary_url = None + if obfuscated: + dct = obfuscation.get_obfuscation_dictionary(collection) + dictionary_url = self._rs.one_time_url_json(dct, + 'dictionary.json') + report = ShardsCollectionDetailsConvertor().convert(collection) + if href: + return ReportResponse( + job, + self._rs.one_time_url_json(report, f'{job.id}.json'), + dictionary_url, + ReportFormat.JSON + ).dict() - @staticmethod - def dto( - entity_attr: str, entity_value: str, report: Report, ref_attr: str - ): - return { - entity_attr: entity_value, - ref_attr: report - } + else: + return ReportResponse( + job, + report, + dictionary_url, + ReportFormat.JSON + ).dict() + + @validate_kwargs + def get_by_tenant_jobs(self, event: TenantJobsDetailsReportGetModel, + tenant_name: str): + tenant = self._ts.get(tenant_name) + modular_helpers.assert_tenant_valid(tenant, event.customer) + + jobs = self._ambiguous_job_service.get_by_tenant_name( + tenant_name=tenant_name, + job_type=event.job_type, + status=JobState.SUCCEEDED, + start=event.start_iso, + end=event.end_iso + ) + jobs = filter(lambda x: not x.is_platform_job, + self._ambiguous_job_service.to_ambiguous(jobs)) + + meta = self._rs.fetch_meta(tenant) + job_collection = [] + for job in jobs: + col = self._rs.ambiguous_job_collection(tenant, job) + col.meta = meta + job_collection.append((job, col)) + # TODO _collection_response to threads? + return build_response(content=map( + lambda pair: self._collection_response(*pair, href=event.href, + obfuscated=event.obfuscated), + job_collection + )) diff --git a/src/handlers/digest_handler.py b/src/handlers/digest_handler.py index e2e34f566..733c9ff42 100644 --- a/src/handlers/digest_handler.py +++ b/src/handlers/digest_handler.py @@ -1,590 +1,118 @@ -from datetime import datetime +from functools import cached_property from http import HTTPStatus -from typing import List, Optional, Dict -from handlers.base_handler import \ - BaseReportHandler, \ - SourceReportDerivation, \ - EntitySourcedReportDerivation, SourcedReport, Report, EntityToReport, \ - SourceToReport -from helpers.constants import ( - HTTPMethod, CUSTOMER_ATTR, TENANTS_ATTR, TENANT_ATTR, - START_ISO_ATTR, END_ISO_ATTR, HREF_ATTR, CONTENT_ATTR, - ID_ATTR -) -from helpers.log_helper import get_logger -from services.ambiguous_job_service import Source -from services.report_service import \ - DIGEST_REPORT_FILE, \ - DETAILED_REPORT_FILE - -DEFAULT_UNRESOLVABLE_RESPONSE = 'Request has run into an unresolvable issue.' - -TYPE_ATTR = 'type' -JOB_ID_ATTR = 'job_id' -RAW_ATTR = 'raw' - -ENTITY_ATTR_KEY = 'entity_attr' -ENTITY_VALUE_KEY = 'entity_value' - -TENANT_NAME_ATTR = 'tenant_name' -TENANT_DISPLAY_NAME_ATTR = 'tenant_display_name' - -_LOG = get_logger(__name__) - -# Report Digest of Jobs resources -JOB_ENDPOINT = '/reports/digests/jobs/{id}' -TENANTS_JOBS_ENDPOINT = '/reports/digests/tenants/jobs' -TENANT_JOBS_ENDPOINT = '/reports/digests/tenants/{tenant_name}/jobs' - -TENANTS_ENDPOINT = '/reports/digests/tenants' -TENANT_ENDPOINT = '/reports/digests/tenants/{tenant_name}' - -# Scrapped, due to attribute naming inconsistency -ACCOUNTS_ENDPOINT = '/reports/digests/tenants/{tenant_name}/accounts' - -NO_RESOURCES_FOR_REPORT = ' maintain(s) no resources to derive a report.' - - -class BaseDigestReportHandler(BaseReportHandler): - """ - Provides base behaviour of digest-reporting, establishing - report-derivation function. - """ - - @property - def _source_report_derivation_function(self) -> SourceReportDerivation: - return self._digest_report_derivation - - def _digest_report_derivation(self, source: Source, **kwargs: dict): - """ - Thread-safe report-derivation. - """ - jid = self._ambiguous_job_service.get_attribute(source, ID_ATTR) - href = kwargs.get(HREF_ATTR) - detailed_path = self._report_service.derive_job_object_path( - job_id=jid, typ=DETAILED_REPORT_FILE) - digest_path = self._report_service.derive_job_object_path( - job_id=jid, typ=DIGEST_REPORT_FILE) - - _head = f'Native Job:\'{jid}\'' - - if href: - _LOG.info(_head + ' going to obtain hypertext reference' - 'of the digest report.') - ref: Optional[str] = self._report_service.href_job_report( - path=digest_path, check=True - ) - if ref: - return ref - - # Going to generate standalone. - - # Maintains raw data, from now on. - ref: Optional[dict] = None - - if not href: - # Has not been pulled yet. - ref = self._report_service.pull_job_report(path=digest_path) - - if not ref: - _LOG.warning(_head + ' digest-report could not be found, ' - 'pulling detailed one.') - detailed = self._report_service.pull_job_report(path=detailed_path) - if detailed: - ref: dict = self._report_service.generate_digest( - detailed_report=detailed - ) - _LOG.warning(_head + ' patching digest report absence.') - if self._report_service.put_job_report( - path=digest_path, data=ref - ) is None: - message = ' digest report could not be patched.' - _LOG.warning(_head + message) - ref = None if href else ref - - if href and ref: - # Pull after successful patch. - ref: Optional[str] = self._report_service.href_job_report( - path=digest_path, check=False - ) - - return ref - - @property - def _entity_sourced_report_derivation_function( - self) -> EntitySourcedReportDerivation: - return self._entity_report_derivation - - def _entity_report_derivation( - self, sourced_reports: List[SourcedReport], **kwargs: dict - ) -> Optional[Report]: - """ - Accumulates digest reports of entity-related sources, returning a - respective entity-based report of digests. - :param sourced_reports: List[SourcedReport] - :param kwargs: Dict, maintains `entity_attr` and - optional, target `entity_value`, as well as `href` attribute, - denoting demand for hypertext reference. - :return: List[Report] - """ - entity_attr: str = kwargs.get(ENTITY_ATTR_KEY, '') - entity_value: str = kwargs.get(ENTITY_VALUE_KEY, '') - assert entity_attr, 'Entity attribute is missing' - - dynamic_entity = not bool(entity_value) - - _head = f'{entity_attr.capitalize()}' - href = kwargs.get(HREF_ATTR) - - ref: List[dict] = [] - ajs = self._ambiguous_job_service - rs = self._report_service - # Given that reports are not sorted, manually establish bounds. - - start, end = None, None - for sourced in sourced_reports: - source, report = sourced - - if dynamic_entity: - _entity = ajs.get_attribute(item=source, attr=entity_attr) - if not entity_value: - entity_value = _entity - - elif entity_value != _entity: - # Precautionary verification. - typ = ajs.get_type(item=source) - uid = ajs.get_attribute(item=source, attr=ID_ATTR) - _e_scope = f'{_head}:\'{entity_value}\'' - _s_scope = f'{_entity} of {typ} \'{uid}\' job' - _LOG.error(f'{_e_scope} mismatches {_s_scope}.') - ref = [] - break - - ref.append(report) - sk = ajs.sort_value_into_datetime(item=source) - if not start or not end: - start = end = sk - - start = sk if sk > start else start - end = sk if sk < end else end - - if not ref: - return None - - _head += f':\'{entity_value}\'' - - # Establish a report-key - path = rs.derive_digests_report_object_path( - entity_value=entity_value, entity_attr=entity_attr, - start=start, end=end - ) - - if href: - message = ' obtaining hypertext reference of entity-digest report.' - _LOG.info(_head + message) - url: str = rs.href_concrete_report(path=path, check=True) - if url: - return url - else: - message = ' checking for entity-digest report presence.' - _LOG.info(_head + message) - if rs.check_concrete_report(path=path): - precomputed: dict = rs.pull_concrete_report(path=path) - if precomputed: - return precomputed - - # Data does not exist or could not be retrieved - generating. - # Going to generate standalone. - message = ' accumulating points of sourced out digests.' - _LOG.info(_head + message) - ref: Dict[str, int] = rs.accumulate_digest(digest_list=ref) - - # todo if expiration rule is assigned, retain per each - # otherwise - retain only per hypertext request. - - if href and ref: - # Pull after successful patch. - message = ' retaining accumulated digest report.' - _LOG.info(_head + message) - if rs.put_json_concrete_report(data=ref, path=path): - message = ' obtaining self-retained hypertext reference.' - _LOG.info(_head + message) - ref = rs.href_concrete_report(path=path, check=False) - - return ref - - -class JobsDigestHandler(BaseDigestReportHandler): - - def define_action_mapping(self): +from modular_sdk.services.tenant_service import TenantService + +from handlers import AbstractHandler, Mapping +from helpers.constants import HTTPMethod, JobState, ReportFormat, \ + CustodianEndpoint +from helpers.lambda_response import build_response +from services import SP +from services import modular_helpers +from services.ambiguous_job_service import AmbiguousJobService, AmbiguousJob +from services.platform_service import PlatformService +from services.report_convertors import ShardsCollectionDigestConvertor +from services.report_service import ReportService, ReportResponse +from services.sharding import ShardsCollection +from validators.swagger_request_models import JobDigestReportGetModel, \ + TenantJobsDigestsReportGetModel +from validators.utils import validate_kwargs + + +class DigestReportHandler(AbstractHandler): + def __init__(self, ambiguous_job_service: AmbiguousJobService, + report_service: ReportService, + tenant_service: TenantService, + platform_service: PlatformService): + self._ambiguous_job_service = ambiguous_job_service + self._rs = report_service + self._ts = tenant_service + self._platform_service = platform_service + + @classmethod + def build(cls) -> 'DigestReportHandler': + return cls( + ambiguous_job_service=SP.ambiguous_job_service, + report_service=SP.report_service, + tenant_service=SP.modular_client.tenant_service(), + platform_service=SP.platform_service + ) + + @cached_property + def mapping(self) -> Mapping: return { - JOB_ENDPOINT: { + CustodianEndpoint.REPORTS_DIGESTS_JOBS_JOB_ID: { HTTPMethod.GET: self.get_by_job }, - TENANT_JOBS_ENDPOINT: { - HTTPMethod.GET: self.get_by_tenant - }, - TENANTS_JOBS_ENDPOINT: { - HTTPMethod.GET: self.query_by_tenant - }, - } - - def get_by_job(self, event: dict): - _LOG.info(f'GET Job Digest Report(s) - {event}.') - # Note that `job_id` denotes the primary-key's hash-key of entities. - uid: str = event[ID_ATTR] - typ: str = event[TYPE_ATTR] - customer = event.get(CUSTOMER_ATTR) - tenants = event.get(TENANTS_ATTR) - href = event.get(HREF_ATTR) - - head = f'{typ.capitalize()} Job:\'{uid}\'' - - source = self._attain_source( - uid=uid, typ=typ, customer=customer, tenants=tenants - ) - if not source: - return self.response - - referenced_reports = self._attain_source_report_map( - source_list=[source], href=href - ) - if referenced_reports: - ref_attr = HREF_ATTR if href else CONTENT_ATTR - self._code = HTTPStatus.OK - self._content = [ - self.dto(source=source, report=report, ref_attr=ref_attr) - for source, report in referenced_reports.items() - ] - else: - message = f' - no report could be derived.' - _LOG.warning(head + message) - self._code = HTTPStatus.NOT_FOUND - self._content = head + NO_RESOURCES_FOR_REPORT - - return self.response - - def get_by_tenant(self, event: dict): - _LOG.info(f'GET Job Digest Report(s) of a Tenant - {event}.') - # `tenant` has been injected via the restriction service. - tenant_name = event[TENANT_ATTR] - # Lower bound. - start_iso: datetime = event[START_ISO_ATTR] - # Upper bound. - end_iso: datetime = event[END_ISO_ATTR] - href = event.get(HREF_ATTR) - - customer = event.get(CUSTOMER_ATTR) - tenant = self._attain_tenant( - name=tenant_name, customer=customer, active=True - ) - if not tenant: - return self.response - - referenced_reports = self._attain_referenced_reports( - start_iso=start_iso, end_iso=end_iso, - customer=tenant.customer_name, tenants=[tenant_name], - typ=event.get(TYPE_ATTR), href=event.get(HREF_ATTR) - ) - - if referenced_reports: - ref_attr = HREF_ATTR if href else CONTENT_ATTR - self._code = HTTPStatus.OK - self._content = [ - self.dto(source=source, report=report, ref_attr=ref_attr) - for source, report in referenced_reports.items() - ] - - return self.response - - def query_by_tenant(self, event: dict): - - _LOG.info(f'GET Job Digest Report(s) of Tenant(s) - {event}.') - # `tenants` has been injected via the restriction service. - tenant_names = event[TENANTS_ATTR] - start_iso: datetime = event[START_ISO_ATTR] - end_iso: datetime = event[END_ISO_ATTR] - customer = event.get(CUSTOMER_ATTR) - href = event.get(HREF_ATTR) - - referenced_reports = self._attain_referenced_reports( - start_iso=start_iso, end_iso=end_iso, - customer=customer, tenants=tenant_names, - typ=event.get(TYPE_ATTR), href=event.get(HREF_ATTR) - ) - - if referenced_reports: - ref_attr = HREF_ATTR if href else CONTENT_ATTR - self._code = HTTPStatus.OK - self._content = [ - self.dto(source=source, report=report, ref_attr=ref_attr) - for source, report in referenced_reports.items() - ] - - return self.response - - def _attain_referenced_reports( - self, start_iso: datetime, end_iso: datetime, - customer: Optional[str] = None, - tenants: Optional[List[str]] = None, - cloud_ids: Optional[List[str]] = None, - typ: Optional[str] = None, href: bool = False - ) -> Optional[SourceToReport]: - cloud_ids = cloud_ids or [] - ajs = self._ambiguous_job_service - - head = '' - - # Log-Header. - if tenants: - multiple = len(tenants) > 1 - bind = ', '.join(map("'{}'".format, tenants or [])) - if head: - bind = f', bound to {bind}' - head += f'{bind} tenant' - if multiple: - head += 's' - - if customer: - head = 'Tenants' if not head else head - head += f' of \'{customer}\' customer' - - typ_scope = f'{typ} type' if typ else 'all types' - time_scope = f'from {start_iso.isoformat()} till {end_iso.isoformat()}' - job_scope = f'job(s) of {typ_scope}, {time_scope}' - - # todo Responsibility chain - - _LOG.info(f'Obtaining {job_scope}, for {head or "tenants"}.') - head = head or 'Tenants' - typ_params_map = ajs.derive_typ_param_map( - typ=typ, tenants=tenants, - cloud_ids=cloud_ids - ) - source_list = ajs.batch_list( - typ_params_map=typ_params_map, customer=customer, - start=start_iso, end=end_iso, sort=True - ) - if not source_list: - message = f' - no source-data of {job_scope} could be derived.' - _LOG.warning(head + message) - self._code = HTTPStatus.NOT_FOUND - self._content = head + NO_RESOURCES_FOR_REPORT - return - - _source_scope = ', '.join( - ajs.get_attribute(item=s, attr=ID_ATTR) - for s in source_list - ) - _LOG.info(head + f' retrieving reports of {_source_scope} source(s).') - source_to_report = self._attain_source_report_map( - source_list=source_list, href=href - ) - if not source_to_report: - message = f' - no reports of {job_scope} could be derived.' - _LOG.warning(head + message) - self._code = HTTPStatus.NOT_FOUND - self._content = head + NO_RESOURCES_FOR_REPORT - return - - return source_to_report - - def dto(self, source: Source, report: Report, ref_attr: str): - return { - ID_ATTR: self._ambiguous_job_service.get_attribute( - item=source, attr=ID_ATTR - ), - TYPE_ATTR: self._ambiguous_job_service.get_type(item=source), - ref_attr: report - } - - -class EntityDigestHandler(BaseDigestReportHandler): - - def define_action_mapping(self): - return { - TENANT_ENDPOINT: { - HTTPMethod.GET: self.get_by_tenant - }, - TENANTS_ENDPOINT: { - HTTPMethod.GET: self.query_by_tenant - }, + CustodianEndpoint.REPORTS_DIGESTS_TENANTS_TENANT_NAME_JOBS: { + HTTPMethod.GET: self.get_by_tenant_jobs + } } - def get_by_tenant(self, event: dict): - _LOG.info(f'GET State Digest Report(s) of a Tenant - {event}.') - # `tenant` has been injected via the restriction service. - tenant_name = event[TENANT_ATTR] - # Lower bound. - start_iso: datetime = event[START_ISO_ATTR] - # Upper bound. - end_iso: datetime = event[END_ISO_ATTR] - - href = event.get(HREF_ATTR) - customer = event.get(CUSTOMER_ATTR) - tenant = self._attain_tenant( - name=tenant_name, customer=customer, active=True - ) - if not tenant: - return self.response - - referenced_reports = self._attain_referenced_reports( - entity_attr=TENANT_ATTR, entity_value=tenant_name, - start_iso=start_iso, end_iso=end_iso, - customer=tenant.customer_name, tenants=[tenant_name], - typ=event.get(TYPE_ATTR), href=event.get(HREF_ATTR) - ) - if referenced_reports: - ref_attr = HREF_ATTR if href else CONTENT_ATTR - self._code = HTTPStatus.OK - self._content = [ - self.dto( - entity_attr=TENANT_ATTR, entity_value=entity, - report=report, ref_attr=ref_attr - ) - for entity, report in referenced_reports.items() - ] - return self.response - - def query_by_tenant(self, event: dict): - - _LOG.info(f'GET Job Digest Report(s) of Tenant(s) - {event}.') - # `tenants` has been injected via the restriction service. - tenant_names = event[TENANTS_ATTR] - start_iso: datetime = event[START_ISO_ATTR] - end_iso: datetime = event[END_ISO_ATTR] - - customer = event.get(CUSTOMER_ATTR) - href = event.get(HREF_ATTR) - - referenced_reports = self._attain_referenced_reports( - entity_attr=TENANT_ATTR, - start_iso=start_iso, end_iso=end_iso, - customer=customer, tenants=tenant_names, - typ=event.get(TYPE_ATTR), href=event.get(HREF_ATTR) - ) + @validate_kwargs + def get_by_job(self, event: JobDigestReportGetModel, job_id: str): + job = self._ambiguous_job_service.get_job( + job_id=job_id, + typ=event.job_type, + customer=event.customer + ) + if not job: + return build_response( + content='The request job not found', + code=HTTPStatus.NOT_FOUND + ) - if referenced_reports: - ref_attr = HREF_ATTR if href else CONTENT_ATTR - self._code = HTTPStatus.OK - self._content = [ - self.dto( - entity_attr=TENANT_ATTR, entity_value=entity, - report=report, ref_attr=ref_attr + if job.is_platform_job: + platform = self._platform_service.get_nullable(job.platform_id) + if not platform: + return build_response( + content='Job platform not found', + code=HTTPStatus.NOT_FOUND ) - for entity, report in referenced_reports.items() - ] - - return self.response - - def _attain_referenced_reports( - self, entity_attr: str, - start_iso: datetime, end_iso: datetime, - customer: Optional[str] = None, - tenants: Optional[List[str]] = None, - cloud_ids: Optional[List[str]] = None, - typ: Optional[str] = None, href: bool = False, - entity_value: Optional[str] = None - ) -> EntityToReport: - cloud_ids = cloud_ids or [] - ajs = self._ambiguous_job_service - - head = '' - - # Log-Header. - if tenants: - multiple = len(tenants) > 1 - bind = ', '.join(map("'{}'".format, tenants or [])) - if head: - bind = f', bound to {bind}' - head += f'{bind} tenant' - if multiple: - head += 's' - - if customer: - head = 'Tenants' if not head else head - head += f' of \'{customer}\' customer' - - typ_scope = f'{typ} type' if typ else 'all types' - time_scope = f'from {start_iso.isoformat()} till {end_iso.isoformat()}' - job_scope = f'job(s) of {typ_scope}, {time_scope}' - - entity_scope = f'\'{entity_attr}\' entity' - if entity_value: - entity_scope += f' of the \'{entity_value}\' target value' - - # todo Responsibility chain - - _LOG.info(f'Obtaining {job_scope}, for {head or "tenants"}.') - head = head or 'Tenants' - typ_params_map = ajs.derive_typ_param_map( - typ=typ, tenants=tenants, - cloud_ids=cloud_ids - ) - source_list = ajs.batch_list( - typ_params_map=typ_params_map, customer=customer, - start=start_iso, end=end_iso, sort=False - ) - if not source_list: - message = f' - no source-data of {job_scope} could be derived.' - _LOG.warning(head + message) - self._code = HTTPStatus.NOT_FOUND - self._content = head + NO_RESOURCES_FOR_REPORT - return self.response - - _source_scope = ', '.join( - ajs.get_attribute(item=s, attr=ID_ATTR) for s in source_list - ) - - _LOG.info(head + f' retrieving reports of {_source_scope} source(s).') - source_to_report = self._attain_source_report_map( - source_list=source_list - ) - if not source_to_report: - message = f' - no reports of {job_scope} could be derived.' - _LOG.warning(head + message) - self._code = HTTPStatus.NOT_FOUND - self._content = head + NO_RESOURCES_FOR_REPORT - return self.response - - message = f' mapping sourced reports to {entity_scope}.' - _LOG.info(head + message) - - entity_sourced_reports = self._attain_entity_sourced_reports( - entity_attr=entity_attr, entity_value=entity_value, - source_to_report=source_to_report - ) - if not entity_sourced_reports: - message = f' - no reports of {job_scope} could be mapped ' - message += f' to {entity_scope}.' - _LOG.warning(head + message) - self._code = HTTPStatus.NOT_FOUND - self._content = head + NO_RESOURCES_FOR_REPORT - return self.response - - message = f' deriving digest reports of {entity_scope}.' - _LOG.info(head + message) - - entity_to_report = self._attain_entity_report_map_from_sourced_reports( - entity_sourced_reports=entity_sourced_reports, - entity_attr=entity_attr, entity_value=entity_value, href=href + collection = self._rs.platform_job_collection(platform, job.job) + else: + tenant = self._ts.get(job.tenant_name) + modular_helpers.assert_tenant_valid(tenant, event.customer) + collection = self._rs.ambiguous_job_collection(tenant, job) + return build_response( + content=self._collection_response(job, collection) ) - if not entity_to_report: - message = f' - no reports of {job_scope} could be derived' - message += f' based on {entity_scope}.' - _LOG.warning(head + message) - self._code = HTTPStatus.NOT_FOUND - self._content = head + NO_RESOURCES_FOR_REPORT - return self.response - - return entity_to_report @staticmethod - def dto( - entity_attr: str, entity_value: str, report: Report, ref_attr: str - ): - return { - entity_attr: entity_value, - ref_attr: report - } + def _collection_response(job: AmbiguousJob, collection: ShardsCollection, + ) -> dict: + """ + Builds response for the given collection + :param collection: + :param job: + :return: + """ + collection.fetch_all() + report = ShardsCollectionDigestConvertor().convert(collection) + return ReportResponse(job, report, ReportFormat.JSON).dict() + + @validate_kwargs + def get_by_tenant_jobs(self, event: TenantJobsDigestsReportGetModel, + tenant_name: str): + tenant = self._ts.get(tenant_name) + modular_helpers.assert_tenant_valid(tenant, event.customer) + + jobs = self._ambiguous_job_service.get_by_tenant_name( + tenant_name=tenant_name, + job_type=event.job_type, + status=JobState.SUCCEEDED, + start=event.start_iso, + end=event.end_iso + ) + jobs = filter(lambda x: not x.is_platform_job, + self._ambiguous_job_service.to_ambiguous(jobs)) + + job_collection = [] + for job in jobs: + col = self._rs.ambiguous_job_collection(tenant, job) + job_collection.append((job, col)) + # TODO _collection_response to threads? + return build_response(content=map( + lambda pair: self._collection_response(*pair), + job_collection + )) diff --git a/src/handlers/errors_handler.py b/src/handlers/errors_handler.py index aac0b6b8a..bdf0e5fa0 100644 --- a/src/handlers/errors_handler.py +++ b/src/handlers/errors_handler.py @@ -1,558 +1,111 @@ -from datetime import datetime +import io +from functools import cached_property from http import HTTPStatus -from typing import List, Optional, Dict, Any - -from handlers.base_handler import \ - BaseReportHandler, \ - SourceReportDerivation, \ - EntitySourcedReportDerivation, SourcedReport, Report, EntityToReport -from helpers.constants import (CUSTOMER_ATTR, TENANTS_ATTR, TENANT_ATTR, - START_ISO_ATTR, END_ISO_ATTR, HREF_ATTR, - CONTENT_ATTR, - ID_ATTR, FORMAT_ATTR, JSON_ATTR, - PARAM_REQUEST_PATH, PARAM_HTTP_METHOD, - HTTPMethod) -from helpers.log_helper import get_logger -from services.ambiguous_job_service import Source -from services.report_service import \ - STATISTICS_FILE - -DEFAULT_UNRESOLVABLE_RESPONSE = 'Request has run into an unresolvable issue.' - -TYPE_ATTR = 'type' -JOB_ID_ATTR = 'job_id' -RAW_ATTR = 'raw' - -SUBTYPE_ATTR = 'subtype' -ACCESS_ATTR = 'access' -CORE_ATTR = 'core' - -ENTITY_ATTR_KEY = 'entity_attr' -ENTITY_VALUE_KEY = 'entity_value' - -TENANT_NAME_ATTR = 'tenant_name' -TENANT_DISPLAY_NAME_ATTR = 'tenant_display_name' - -_LOG = get_logger(__name__) - -# todo allow explicit file-extension, i.e., .json, .xlsx - -# Report Errors of Jobs resources -JOB_ENDPOINT = '/reports/errors/jobs/{id}' -JOB_ACCESS_ENDPOINT = '/reports/errors/access/jobs/{id}' -JOB_CORE_ENDPOINT = '/reports/errors/core/jobs/{id}' - -# Report Errors of accumulated Jobs, driven by entity-driven scope. -TENANTS_ENDPOINT = '/reports/errors/tenants' -TENANT_ENDPOINT = '/reports/errors/tenants/{tenant_name}' - -TENANTS_ACCESS_ENDPOINT = '/reports/errors/access/tenants' -TENANT_ACCESS_ENDPOINT = '/reports/errors/access/tenants/{tenant_name}' - -TENANTS_CORE_ENDPOINT = '/reports/errors/core/tenants' -TENANT_CORE_ENDPOINT = '/reports/errors/core/tenants/{tenant_name}' - -CHILD_TENANTS_ENDPOINT = '/tenants' -CHILD_TENANT_ENDPOINT = '/tenants/{tenant_name}' - -NO_RESOURCES_FOR_REPORT = ' maintain(s) no resources to derive a report.' - -FailedRule = Dict[str, Dict[str, Any]] # job-id to rule-data -FailedRules = Dict[str, List[FailedRule]] # rule-id to failed rule - - -class BaseErrorsReportHandler(BaseReportHandler): - """ - Provides base behaviour of error-reporting, establishing - report-derivation function. - """ - - @property - def _source_report_derivation_function(self) -> SourceReportDerivation: - return self._statistics_report_derivation - - def _statistics_report_derivation( - self, source: Source, **kwargs: dict - ) -> Optional[Report]: - - """ - Obtains report of rule-failed statistics of a sourced job, - returning a respective report. - :param source: Source - :param kwargs: Dict - :return: Optional[Report] - """ - jid = self._ambiguous_job_service.get_attribute(source, ID_ATTR) - rs = self._report_service - path = rs.derive_job_object_path(job_id=jid, typ=STATISTICS_FILE) - return rs.pull_job_statistics(path=path) - - @property - def _entity_sourced_report_derivation_function(self) -> \ - EntitySourcedReportDerivation: - return self._entity_failed_rule_report_derivation - - def _entity_failed_rule_report_derivation( - self, sourced_reports: List[SourcedReport], **kwargs: dict - ) -> Optional[Report]: - - entity_attr: str = kwargs.get(ENTITY_ATTR_KEY, '') - entity_value: str = kwargs.get(ENTITY_VALUE_KEY, '') - subtype: str = kwargs.get(SUBTYPE_ATTR, '') - href: bool = kwargs.get(HREF_ATTR, False) - frmt: str = kwargs.get(FORMAT_ATTR, '') - assert entity_attr, 'Entity attribute is missing' - return self._attain_failed_rule_map( - sourced_reports=sourced_reports, - entity_attr=entity_attr, - entity_value=entity_value, - href=href, frmt=frmt, - subtype=subtype - ) - - def _attain_failed_rule_map( - self, sourced_reports: List[SourcedReport], - entity_attr: str, entity_value: Optional[str] = None, - href: Optional[bool] = False, frmt: Optional[str] = None, - subtype: Optional[str] = None - ): - """ - Derives relation map of failed rule, merged amongst sourced reports. - :param sourced_reports: List[Source, List[Dict]] - :param entity_attr: str, denotes unique entity id-attribute - :param entity_value: Optional[str], denotes ta target entity-id value - :return: Dict[str, List[Dict]] - """ - head = entity_attr.capitalize() - if entity_value: - head += f':\'{entity_value}\'' - - ajs = self._ambiguous_job_service - rs = self._report_service - - failed_rule_map: FailedRules = {} - _LOG.info(head + ' merging failed rules, amongst report(s).') - - start, end = None, None - source = None - for sourced in sourced_reports: - source, statistics = sourced - - if not entity_value: - _entity = ajs.get_attribute(item=source, attr=entity_attr) - if not entity_value: - entity_value = _entity - - elif entity_value != _entity: - # Precautionary verification. - typ = ajs.get_type(item=source) - uid = ajs.get_attribute(item=source, attr=ID_ATTR) - _e_scope = f'{head}:\'{entity_value}\'' - _s_scope = f'{_entity} of {typ} \'{uid}\' job' - _LOG.error(f'{_e_scope} mismatches {_s_scope}.') - failed_rule_map = {} - break - - # Establishes start and end dates, for the report. - sk = ajs.sort_value_into_datetime(item=source) - if not start or not end: - start = end = sk - start = sk if sk > start else start - end = sk if sk < end else end - - # Derives failed rule map. - jid = self._ambiguous_job_service.get_attribute(source, ID_ATTR) - failed_ref = rs.derive_failed_rule_map( - job_id=jid, statistics=statistics - ) - if not failed_ref: - message = f' could not establish failed rules of {jid} job.' - _LOG.warning(head + message) - continue - - for rid, data_list in failed_ref.items(): - rule_scope = failed_rule_map.setdefault(rid, []) - rule_scope.extend(data_list) - - if not failed_rule_map: - _LOG.warning(head + ' no failed rules could be established.') - - else: - - if subtype: - _LOG.info(head + f' deriving {subtype} typed failed rule map.') - failed_rule_map = rs.derive_type_based_failed_rule_map( - typ=subtype, failed_rule_map=failed_rule_map - ) - - if failed_rule_map and href and frmt: - message = f' providing hypertext reference to {frmt} file of' - message += ' failed rules.' - _LOG.info(head + message) - job_id = self._ambiguous_job_service.get_attribute(source, ID_ATTR) if len(sourced_reports) == 1 else None - object_path = rs.derive_error_report_object_path( - subtype=subtype, entity_value=entity_value, - entity_attr=entity_attr, start=start, end=end, - fext=frmt, job_id=job_id - ) - - if frmt == JSON_ATTR: - message = ' retaining error json hypertext reference.' - _LOG.info(head + message) - if not rs.put_json_concrete_report( - data=failed_rule_map, path=object_path - ): - object_path = None - - else: - _LOG.info(head + ' deriving xlsx compliance report.') - file_name = rs.derive_name_of_report_object_path( - object_path=object_path - ) - stream_path = rs.derive_errors_report_excel_path( - file_name=file_name, failed_rules=failed_rule_map, - subtype=subtype - ) - if stream_path: - message = ' retaining error xlsx hypertext reference.' - _LOG.warning(head + message) - if rs.put_path_retained_concrete_report( - stream_path=stream_path, - object_path=object_path - ) is None: - message = ' xlsx error report hypertext could not' - message += ' be provided.' - _LOG.warning(head + message) - # Explicitly denote reference absence. - object_path = None - - if object_path: - message = ' obtaining hypertext reference.' - _LOG.info(head + message) - failed_rule_map = rs.href_concrete_report( - path=object_path, check=False - ) - - return failed_rule_map - - -class JobsErrorsHandler(BaseErrorsReportHandler): - - def define_action_mapping(self): - return { - JOB_ENDPOINT: { - HTTPMethod.GET.value: self.get_job_errors - }, - JOB_ACCESS_ENDPOINT: { - HTTPMethod.GET.value: self.get_job_access_errors - }, - JOB_CORE_ENDPOINT: { - HTTPMethod.GET.value: self.get_job_core_errors - } - } - - def get_job_errors(self, event: dict): - return self._get_typed_job(event=event) - - def get_job_access_errors(self, event: dict): - return self._get_typed_job(event=event, error_subtype=ACCESS_ATTR) - - def get_job_core_errors(self, event: dict): - return self._get_typed_job(event=event, error_subtype=CORE_ATTR) - - def _get_typed_job(self, event: dict, error_subtype: Optional[str] = None): - error_typ = 'Errors' - if error_subtype: - error_typ = f'{error_subtype.capitalize()} {error_typ}' - _LOG.info(f'GET Job {error_typ} Report - {event}.') - - # Note that `job_id` denotes the primary-key's hash-key of entities. - uid: str = event[ID_ATTR] - typ: str = event[TYPE_ATTR] - customer = event.get(CUSTOMER_ATTR) - tenants = event.get(TENANTS_ATTR) - href = event.get(HREF_ATTR) - frmt = event.get(FORMAT_ATTR) - - entity_attr = f'{typ.capitalize()} Job' - entity_value = uid - head = f'{entity_attr}:\'{entity_value}\'' - - source = self._attain_source( - uid=uid, typ=typ, customer=customer, tenants=tenants +from typing import Iterator + +from xlsxwriter import Workbook +from xlsxwriter.worksheet import Worksheet + +from handlers import AbstractHandler, Mapping +from helpers.constants import CustodianEndpoint, HTTPMethod, ReportFormat +from helpers.lambda_response import build_response +from services import SP +from services.ambiguous_job_service import AmbiguousJobService +from services.report_service import ReportResponse, ReportService, \ + StatisticsItem +from services.xlsx_writer import CellContent, Table, XlsxRowsWriter +from validators.swagger_request_models import JobErrorReportGetModel +from validators.utils import validate_kwargs + + +class ResourceReportXlsxWriter: + def __init__(self, it: Iterator[StatisticsItem]): + self._it = it + + @cached_property + def head(self) -> list: + return ['Rule', 'Region', 'Type', 'Reason'] + + def write(self, wsh: Worksheet, wb: Workbook): + bold = wb.add_format({'bold': True}) + remapped = {} + for item in self._it: + remapped.setdefault(item['policy'], []).append(item) + table = Table() + table.new_row() + for h in self.head: + table.add_cells(CellContent(h, bold)) + for rule, items in remapped.items(): + table.new_row() + table.add_cells(CellContent(rule)) + table.add_cells(*[CellContent(item['region']) for item in items]) + table.add_cells( + *[CellContent(item['error_type']) for item in items]) + table.add_cells(*[CellContent(item['reason']) for item in items]) + XlsxRowsWriter().write(wsh, table) + + +class ErrorsReportHandler(AbstractHandler): + def __init__(self, ambiguous_job_service: AmbiguousJobService, + report_service: ReportService): + self._ambiguous_job_service = ambiguous_job_service + self._report_service = report_service + + @classmethod + def build(cls) -> 'AbstractHandler': + return cls( + ambiguous_job_service=SP.ambiguous_job_service, + report_service=SP.report_service, ) - if not source: - return self.response - statistics = self._statistics_report_derivation(source=source) - if not statistics: - _LOG.warning(head + ' could not obtain statistics report.') - self._code = HTTPStatus.NOT_FOUND - self._content = head + NO_RESOURCES_FOR_REPORT - return self.response - - failed_rule_map = self._attain_failed_rule_map( - sourced_reports=[(source, statistics)], - entity_attr=entity_attr, entity_value=entity_value, - href=href, frmt=frmt, subtype=error_subtype - ) - ref_attr = HREF_ATTR if href else CONTENT_ATTR - self._code = HTTPStatus.OK - self._content = [ - self.dto( - source=source, report=failed_rule_map, ref_attr=ref_attr - ) - ] - - return self.response - - def dto(self, source: Source, report: Report, ref_attr: str): - return { - ID_ATTR: self._ambiguous_job_service.get_attribute( - item=source, attr=ID_ATTR - ), - TYPE_ATTR: self._ambiguous_job_service.get_type(item=source), - ref_attr: report - } - - -class EntityErrorsHandler(BaseErrorsReportHandler): - base_resource: str = '/reports/errors' - - def define_action_mapping(self): + @cached_property + def mapping(self) -> Mapping: return { - TENANT_ENDPOINT: { - HTTPMethod.GET.value: self.mediate_event - }, - TENANTS_ENDPOINT: { - HTTPMethod.GET.value: self.mediate_event - }, - TENANT_ACCESS_ENDPOINT: { - HTTPMethod.GET.value: self.mediate_event - }, - TENANTS_ACCESS_ENDPOINT: { - HTTPMethod.GET.value: self.mediate_event - }, - TENANT_CORE_ENDPOINT: { - HTTPMethod.GET.value: self.mediate_event - }, - TENANTS_CORE_ENDPOINT: { - HTTPMethod.GET.value: self.mediate_event + CustodianEndpoint.REPORTS_ERRORS_JOBS_JOB_ID: { + HTTPMethod.GET: self.get_by_job }, } - @property - def mediator_map(self): - return { - CHILD_TENANT_ENDPOINT: { - HTTPMethod.GET.value: self._get_by_tenant - }, - CHILD_TENANTS_ENDPOINT: { - HTTPMethod.GET.value: self._query_by_tenant - } - } - - def mediate_event(self, event: dict): - path = event.get(PARAM_REQUEST_PATH, '') - method = event.get(PARAM_HTTP_METHOD) - mediated_map = self.mediator_map - _, child_path = path.split(self.base_resource, 1) - params = dict(event=event) - - if child_path not in mediated_map and '/' in child_path[1:]: - child_path = child_path[1:] - index = child_path.index('/') - params.update(error_subtype=child_path[:index]) - child_path = child_path[index:] - - f = mediated_map.get(child_path, {}).get(method, None) - if f: - return f(**params) - - def _get_by_tenant(self, event: dict, error_subtype: Optional[str] = None): - error_typ = 'Errors' - if error_subtype: - error_typ = f'{error_subtype.capitalize()} {error_typ}' - _LOG.info(f'GET {error_typ} Report(s) of a Tenant - {event}.') - # `tenant` has been injected via the restriction service. - tenant_name = event[TENANT_ATTR] - # Lower bound. - start_iso: datetime = event[START_ISO_ATTR] - # Upper bound. - end_iso: datetime = event[END_ISO_ATTR] - - href = event.get(HREF_ATTR) - customer = event.get(CUSTOMER_ATTR) - tenant = self._attain_tenant( - name=tenant_name, customer=customer, active=True + @validate_kwargs + def get_by_job(self, event: JobErrorReportGetModel, job_id: str): + job = self._ambiguous_job_service.get_job( + job_id=job_id, + typ=event.job_type, + customer=event.customer ) - if not tenant: - return self.response - - referenced_reports = self._attain_referenced_reports( - entity_attr=TENANT_ATTR, entity_value=tenant_name, - start_iso=start_iso, end_iso=end_iso, - customer=tenant.customer_name, tenants=[tenant_name], - typ=event.get(TYPE_ATTR), href=href, frmt=event.get(FORMAT_ATTR), - error_subtype=error_subtype - - ) - - ref_attr = HREF_ATTR if href else CONTENT_ATTR - self._code = HTTPStatus.OK - self._content = [ - self.dto( - entity_attr=TENANT_ATTR, entity_value=entity, - report=report, ref_attr=ref_attr + if not job: + return build_response( + content='The request job not found', + code=HTTPStatus.NOT_FOUND ) - for entity, report in referenced_reports.items() - ] - return self.response - - def _query_by_tenant( - self, event: dict, error_subtype: Optional[str] = None - ): - error_typ = 'Errors' - if error_subtype: - error_typ = f'{error_subtype.capitalize()} {error_typ}' - _LOG.info(f'GET {error_typ} Report(s) of Tenant(s) - {event}.') - - # `tenants` has been injected via the restriction service. - tenant_names = event[TENANTS_ATTR] - # Lower bound. - start_iso: datetime = event[START_ISO_ATTR] - # Upper bound. - end_iso: datetime = event[END_ISO_ATTR] - - customer = event.get(CUSTOMER_ATTR) - href = event.get(HREF_ATTR) - - referenced_reports = self._attain_referenced_reports( - entity_attr=TENANT_ATTR, - start_iso=start_iso, end_iso=end_iso, - customer=customer, tenants=tenant_names, - typ=event.get(TYPE_ATTR), - href=href, frmt=event.get(FORMAT_ATTR), - error_subtype=error_subtype - ) - ref_attr = HREF_ATTR if href else CONTENT_ATTR - self._code = HTTPStatus.OK - self._content = [ - self.dto( - entity_attr=TENANT_ATTR, entity_value=entity, - report=report, ref_attr=ref_attr + statistics = self._report_service.job_statistics(job.job) + data = map( + self._report_service.format_failed, + self._report_service.only_failed( + statistic=statistics, + error_type=event.error_type ) - for entity, report in referenced_reports.items() - ] - - return self.response - - def _attain_referenced_reports( - self, entity_attr: str, - start_iso: datetime, end_iso: datetime, - customer: Optional[str] = None, - tenants: Optional[List[str]] = None, - cloud_ids: Optional[List[str]] = None, - typ: Optional[str] = None, - href: bool = False, frmt: Optional[str] = None, - entity_value: Optional[str] = None, - error_subtype: Optional[str] = None - ) -> EntityToReport: - - cloud_ids = cloud_ids or [] - ajs = self._ambiguous_job_service - - head = '' - - # Log-Header. - - if tenants: - multiple = len(tenants) > 1 - bind = ', '.join(map("'{}'".format, tenants or [])) - if head: - bind = f', bound to {bind}' - head += f'{bind} tenant' - if multiple: - head += 's' - - if customer: - head = 'Tenants' if not head else head - head += f' of \'{customer}\' customer' - - typ_scope = f'{typ} type' if typ else 'all types' - time_scope = f'from {start_iso.isoformat()} till {end_iso.isoformat()}' - job_scope = f'job(s) of {typ_scope}, {time_scope}' - - entity_scope = f'\'{entity_attr}\' entity' - if entity_value: - entity_scope += f' of the \'{entity_value}\' target value' - - # todo Responsibility chain - - _LOG.info(f'Obtaining {job_scope}, for {head or "tenants"}.') - head = head or 'Tenants' - typ_params_map = ajs.derive_typ_param_map( - typ=typ, tenants=tenants, - cloud_ids=cloud_ids ) - source_list = ajs.batch_list( - typ_params_map=typ_params_map, customer=customer, - start=start_iso, end=end_iso, sort=False - ) - if not source_list: - message = f' - no source-data of {job_scope} could be derived.' - _LOG.warning(head + message) - self._code = HTTPStatus.NOT_FOUND - self._content = head + NO_RESOURCES_FOR_REPORT - return self.response - - _source_scope = ', '.join( - ajs.get_attribute(item=s, attr=ID_ATTR) for s in source_list - ) - - message = f' retrieving statistics of {_source_scope} source(s).' - _LOG.info(head + message) - source_to_report = self._attain_source_report_map( - source_list=source_list - ) - if not source_to_report: - message = f' - no reports of {job_scope} could be derived.' - _LOG.warning(head + message) - self._code = HTTPStatus.NOT_FOUND - self._content = head + NO_RESOURCES_FOR_REPORT - return self.response - - message = f' mapping sourced reports to {entity_scope}.' - _LOG.info(head + message) - - entity_sourced_reports = self._attain_entity_sourced_reports( - entity_attr=entity_attr, entity_value=entity_value, - source_to_report=source_to_report - ) - if not entity_sourced_reports: - message = f' - no reports of {job_scope} could be mapped ' - message += f' to {entity_scope}.' - _LOG.warning(head + message) - self._code = HTTPStatus.NOT_FOUND - self._content = head + NO_RESOURCES_FOR_REPORT - return self.response - - message = f' deriving failed rule reports of {entity_scope}.' - _LOG.info(head + message) - - entity_to_report = self._attain_entity_report_map_from_sourced_reports( - entity_sourced_reports=entity_sourced_reports, - entity_attr=entity_attr, entity_value=entity_value, - href=href, format=frmt, subtype=error_subtype - ) - - return entity_to_report - - @staticmethod - def dto( - entity_attr: str, entity_value: str, report: Report, ref_attr: str - ): - return { - entity_attr: entity_value, - ref_attr: report - } + content = [] + match event.format: + case ReportFormat.JSON: + if event.href: + url = self._report_service.one_time_url_json( + list(data), f'{job.id}-errors.json' + ) + content = ReportResponse(job, url).dict() + else: + content = list(data) + case ReportFormat.XLSX: + buffer = io.BytesIO() + with Workbook(buffer) as wb: + ResourceReportXlsxWriter(data).write( + wb=wb, + wsh=wb.add_worksheet('Errors') + ) + buffer.seek(0) + url = self._report_service.one_time_url( + buffer, f'{job.id}-errors.xlsx' + ) + content = ReportResponse(job, url, ReportFormat.XLSX).dict() + return build_response(content=content) diff --git a/src/handlers/event_assembler_handler.py b/src/handlers/event_assembler_handler.py index 80ac763a4..63b75150a 100644 --- a/src/handlers/event_assembler_handler.py +++ b/src/handlers/event_assembler_handler.py @@ -1,86 +1,79 @@ import heapq -import operator from http import HTTPStatus from itertools import chain -from typing import Optional, List, Dict, Tuple, Set, Union, Generator +import operator +import time +from typing import Generator -from modular_sdk.commons.constants import ParentType +from modular_sdk.models.tenant import Tenant +from modular_sdk.services.tenant_service import TenantService -import services.cache as cache -from helpers import build_response, adjust_cloud +from helpers import adjust_cloud from helpers.constants import ( - BATCH_ENV_DEFAULT_REPORTS_BUCKET_NAME, BATCH_ENV_AWS_REGION, - BATCH_ENV_JOB_LIFETIME_MIN, BATCH_ENV_MIN_CUSTOM_CORE_VERSION, - BATCH_ENV_CURRENT_CUSTOM_CORE_VERSION, BATCH_ENV_JOB_TYPE, - BATCH_ENV_LOG_LEVEL, BATCH_ENV_SUBMITTED_AT, BATCH_ENV_BATCH_RESULTS_IDS, - BATCH_MULTI_ACCOUNT_EVENT_DRIVEN_JOB_TYPE, - BATCH_ENV_VAR_RULESETS_BUCKET_NAME, - MAESTRO_VENDOR, AWS_VENDOR, BATCH_ENV_SYSTEM_CUSTOMER_NAME, - BATCH_ENV_STATS_S3_BUCKET_NAME + AWS_VENDOR, + BatchJobEnv, + BatchJobType, + CAASEnv, + MAESTRO_VENDOR, ) +from helpers.lambda_response import build_response from helpers.log_helper import get_logger from helpers.system_customer import SYSTEM_CUSTOMER -from helpers.time_helper import utc_iso -from models.modular.application import CustodianLicensesApplicationMeta from services import SERVICE_PROVIDER -from services.abstract_lambda import PARAM_NATIVE_JOB_ID -from services.batch_results_service import BatchResultsService, BatchResults +from services import modular_helpers +from services.batch_results_service import BatchResults, BatchResultsService +import services.cache as cache from services.clients.batch import BatchClient from services.environment_service import EnvironmentService -from services.event_processor_service import EventProcessorService, \ - AccountRegionRuleMap, RegionRuleMap, Stats, BaseEventProcessor, \ - MaestroEventProcessor, EventBridgeEventProcessor, CloudTenantRegionRulesMap -from services.event_service import EventService, Event -from services.license_service import LicenseService, License -from services.modular_service import ModularService, Tenant -from services.ruleset_service import RulesetService, Ruleset -from services.setting_service import SettingsService, \ - EVENT_CURSOR_TIMESTAMP_ATTR - -# Tenant region attrs -_NATIVE_NAME_ATTR = 'native_name' -_IS_ACTIVE_ATTR = 'is_active' +from services.event_processor_service import ( + AccountRegionRuleMap, + BaseEventProcessor, + CloudTenantRegionRulesMap, + EventBridgeEventProcessor, + EventProcessorService, + MaestroEventProcessor, + RegionRuleMap, +) +from services.event_service import Event, EventService +from services.license_service import License, LicenseService +from services.ruleset_service import Ruleset, RulesetService +from services.setting_service import EVENT_CURSOR_TIMESTAMP_ATTR, SettingsService DEFAULT_NOT_FOUND_RESPONSE = 'No events to assemble and process.' DEFAULT_UNRESOLVED_RESPONSE = 'Request has run into an unresolvable issue.' -TenantStats = Dict[str, Dict[str, Union[Stats, bool]]] - _LOG = get_logger(__name__) class EventAssemblerHandler: _code: int - _content: Optional[str] + _content: str | None def __init__( self, event_service: EventService, settings_service: SettingsService, - modular_service: ModularService, ruleset_service: RulesetService, + tenant_service: TenantService, + ruleset_service: RulesetService, event_processor_service: EventProcessorService, license_service: LicenseService, environment_service: EnvironmentService, batch_results_service: BatchResultsService, batch_client: BatchClient, + ): self._event_service = event_service self._event_processor_service = event_processor_service self._settings_service = settings_service - self._modular_service = modular_service + self._tenant_service = tenant_service self._ruleset_service = ruleset_service self._environment_service = environment_service self._license_service = license_service self._batch_results_service = batch_results_service self._batch_client = batch_client - self._raw_stats: Stats = {} # account_id-region stats - self._tenant_stats: TenantStats = {} - - ttu = lambda k, v, now: now + 900 # this cache does not affect user directly, so we can put custom # ttl that does not depend on env - self._licenses_cache = cache.factory(ttu=ttu) - self._rulesets_cache = cache.factory(ttu=ttu) + self._rulesets_cache = cache.factory(ttu=lambda k, v, now: now + 900) self._reset() def _log_cache(self) -> None: @@ -94,14 +87,8 @@ def _log_cache(self) -> None: for attr in attrs: _LOG.debug(f'{attr}: {getattr(self, attr)}') - @cache.cachedmethod(operator.attrgetter('_licenses_cache')) - def get_license(self, license_key: str) -> Optional[License]: - item = self._license_service.get_license(license_key) - if not self._license_service.is_expired(item): - return item - @cache.cachedmethod(operator.attrgetter('_rulesets_cache')) - def get_ruleset(self, _id: str) -> Optional[Ruleset]: + def get_ruleset(self, _id: str) -> Ruleset | None: """ Supposed to be used with licensed rule-sets. """ @@ -110,22 +97,22 @@ def get_ruleset(self, _id: str) -> Optional[Ruleset]: @classmethod def instantiate(cls) -> 'EventAssemblerHandler': return cls( - event_service=SERVICE_PROVIDER.event_service(), - settings_service=SERVICE_PROVIDER.settings_service(), - modular_service=SERVICE_PROVIDER.modular_service(), - ruleset_service=SERVICE_PROVIDER.ruleset_service(), - event_processor_service=SERVICE_PROVIDER.event_processor_service(), - license_service=SERVICE_PROVIDER.license_service(), - environment_service=SERVICE_PROVIDER.environment_service(), - batch_results_service=SERVICE_PROVIDER.batch_results_service(), - batch_client=SERVICE_PROVIDER.batch(), + event_service=SERVICE_PROVIDER.event_service, + settings_service=SERVICE_PROVIDER.settings_service, + tenant_service=SERVICE_PROVIDER.modular_client.tenant_service(), + ruleset_service=SERVICE_PROVIDER.ruleset_service, + event_processor_service=SERVICE_PROVIDER.event_processor_service, + license_service=SERVICE_PROVIDER.license_service, + environment_service=SERVICE_PROVIDER.environment_service, + batch_results_service=SERVICE_PROVIDER.batch_results_service, + batch_client=SERVICE_PROVIDER.batch, ) @property def response(self): _unresolved = DEFAULT_UNRESOLVED_RESPONSE _def_code_map = { - HTTPStatus.NOT_FOUND: DEFAULT_NOT_FOUND_RESPONSE + HTTPStatus.NOT_FOUND.value: DEFAULT_NOT_FOUND_RESPONSE } _code, _content = self._code, self._content if not _content: @@ -134,56 +121,36 @@ def response(self): return build_response(code=_code, content=_content) def _reset(self): - self._code: Optional[int] = HTTPStatus.INTERNAL_SERVER_ERROR - self._content: Optional[str] = None - self._raw_stats.clear() - self._tenant_stats.clear() + self._code: int | None = HTTPStatus.INTERNAL_SERVER_ERROR + self._content: str | None = None def get_allowed_event_driven_license(self, tenant: Tenant - ) -> Optional[License]: + ) -> License | None: """ Makes all the necessary steps to retrieve a license item, which - allows event-driven for the given tenant is such a license exists + allows event-driven for the given tenant if such a license exists """ - application = self._modular_service.get_tenant_application( - tenant, ParentType.CUSTODIAN_LICENSES - ) - if not application: - _LOG.info(f'Tenant {tenant} does not have custodian ' - f'applications') - return - meta = CustodianLicensesApplicationMeta(**application.meta.as_dict()) - license_key = meta.license_key(tenant.cloud) - if not license_key: - _LOG.info(f'Tenant {tenant} does not have license') + lic = self._license_service.get_tenant_license(tenant) + if not lic: + _LOG.info(f'Tenant {tenant} does not have linked license') return - _license = self._license_service.get_license(license_key) - if not _license: - _LOG.error(f'Somehow license item does not exist, but ' - f'license key in application exists') + if lic.is_expired(): + _LOG.info(f'The license {lic.license_key}has expired') return if not self._license_service.is_subject_applicable( - entity=_license, + lic=lic, customer=tenant.customer_name, - tenant=tenant.name): - _LOG.info(f'License {license_key} if not applicable ' - f'for tenant {tenant.name}') + tenant_name=tenant.name): + _LOG.info(f'License {lic.license_key} is not applicable') return - if not _license.event_driven.active: - _LOG.info(f'Event driven is not active for license: ' - f'{license_key}') + if not lic.event_driven.get('active'): + _LOG.info(f'Event driven is not active for ' + f'license {lic.license_key}') return - return _license + return lic def handler(self, event): - _LOG.info('Starting event-assembler handler') self._log_cache() - # todo skip custom core version update, due to high workload? - # Given that update ccc feature is disabled, reference - # version beforehand. - versions = self._get_ccc_version() - if not versions: - return # Collect all events since the last execution. _LOG.info('Going to obtain cursor value of the event assembler.') @@ -211,7 +178,7 @@ def handler(self, event): f'to - {end_event.timestamp}') vendor_maps = self.vendor_rule_map(events) - tenant_batch_result: List[Tuple[Tenant, BatchResults]] = [] + tenant_batch_result: list[tuple[Tenant, BatchResults]] = [] for vendor, mapping in vendor_maps.items(): if not mapping: _LOG.warning(f'{vendor}`s mapping is empty. Skipping') @@ -278,11 +245,10 @@ def handler(self, event): self._content = 'No batch results left after checking licenses' return self.response # here we already have allowed batch_results. Just start a job. - common_envs = self._build_common_envs(versions[0], versions[1]) - common_envs[BATCH_ENV_BATCH_RESULTS_IDS] = ','.join( + common_envs = self._build_common_envs() + common_envs[BatchJobEnv.BATCH_RESULTS_IDS] = ','.join( item.id for item in allowed_batch_results) for br in allowed_batch_results: - br.submitted_at = common_envs[BATCH_ENV_SUBMITTED_AT] br.registration_start = str(start_event.timestamp) br.registration_end = str(end_event.timestamp) self._batch_results_service.batch_save(allowed_batch_results) @@ -292,7 +258,7 @@ def handler(self, event): return self.response def handle_aws_vendor(self, cid_rg_rl_map: AccountRegionRuleMap - ) -> Generator[Tuple[Tenant, BatchResults], None, None]: # noqa + ) -> Generator[tuple[Tenant, BatchResults], None, None]: """ cid_rg_rl_map = { '12424123423': { @@ -314,22 +280,22 @@ def handle_aws_vendor(self, cid_rg_rl_map: AccountRegionRuleMap _LOG.warning(f'No rules within region(s) are accessible to ' f'\'{tenant.name}\' tenant.') continue - batch_result = self._batch_results_service.create(dict( + batch_result = self._batch_results_service.create( rules=accessible_rg_rl_map, tenant_name=tenant.name, customer_name=tenant.customer_name, cloud_identifier=tenant.project - )) + ) yield tenant, batch_result def handle_maestro_vendor(self, cl_tn_rg_rl_map: CloudTenantRegionRulesMap - ) -> Generator[Tuple[Tenant, BatchResults], None, None]: # noqa + ) -> Generator[tuple[Tenant, BatchResults], None, None]: """ Separate logic for maestro audit events { 'AZURE': { 'TEST_TENANT': { - 'AzureCloud': {'rule1', 'rule2'} + 'global': {'rule1', 'rule2'} } } } @@ -345,16 +311,16 @@ def handle_maestro_vendor(self, cl_tn_rg_rl_map: CloudTenantRegionRulesMap continue # here we skip restrictions by regions for AZURE because # currently I don't know how it's supposed to work - batch_result = self._batch_results_service.create(dict( + batch_result = self._batch_results_service.create( rules=rg_rl_map, # sets must be casted to lists after. Otherwise it won't be saved tenant_name=tenant.name, customer_name=tenant.customer_name, cloud_identifier=tenant.project, - )) + ) yield tenant, batch_result - def _obtain_events(self, since: Optional[float] = None) -> List[Event]: + def _obtain_events(self, since: float | None = None) -> list[Event]: """ Makes N queries. N is a number of partitions (from envs). After that merges these N already sorted lists @@ -373,7 +339,7 @@ def _obtain_events(self, since: Optional[float] = None) -> List[Event]: # first and last timestamp. But this "merge" must be fast return list(heapq.merge(*iters, key=lambda e: e.timestamp)) - def vendor_rule_map(self, events: List[Event]) -> Dict[str, Dict]: + def vendor_rule_map(self, events: list[Event]) -> dict[str, dict]: """ For each vendor derives rules mapping in its format. The formats of each vendor differ @@ -401,22 +367,26 @@ def vendor_rule_map(self, events: List[Event]) -> Dict[str, Dict]: result[AWS_VENDOR] = aws_proc.account_region_rule_map(it) return result - def _obtain_tenant(self, name: str) -> Optional[Tenant]: + def _obtain_tenant(self, name: str) -> Tenant | None: _LOG.info(f'Going to retrieve Tenant by \'{name}\' name.') _head = f'Tenant:\'{name}\'' - _tenant = self._modular_service.get_tenant(tenant=name) - if not self._modular_service.is_tenant_valid(tenant=_tenant): + tenant = self._tenant_service.get(name) + if not modular_helpers.is_tenant_valid(tenant=tenant): _LOG.warning(_head + ' is inactive or does not exist') - _tenant = None - return _tenant + return + return tenant - def _obtain_tenant_by_acc(self, acc: str) -> Optional[Tenant]: + def _obtain_tenant_by_acc(self, acc: str) -> Tenant | None: _LOG.info(f'Going to retrieve Tenant by \'{acc}\' cloud id') - return next(self._modular_service.i_get_tenants_by_acc(acc, True), - None) + return next(self._tenant_service.i_get_by_acc( + acc=str(acc), + active=True, + limit=1 + ), None) - def _obtain_tenant_based_region_rule_map( - self, tenant: Tenant, region_rule_map: RegionRuleMap): + @staticmethod + def _obtain_tenant_based_region_rule_map(tenant: Tenant, + region_rule_map: RegionRuleMap): tn = tenant.name _LOG.info(f'Going to restrict {list(region_rule_map)} regions, ' f'based on ones accessible to \'{tn}\' Tenant.') @@ -424,20 +394,18 @@ def _obtain_tenant_based_region_rule_map( # Maestro's regions in tenants have attribute "is_active" ("act"). # But currently (30.01.2023) they ignore it. They deem all the # regions listed in an active tenant to be active as well. So do we - active_rg = self._modular_service.get_tenant_regions(tenant) + active_rg = modular_helpers.get_tenant_regions(tenant) for region in region_rule_map: - # _region_stats = self._raw_stats[cid][region] if region not in active_rg: _LOG.warning(f'Going to exclude `{region}` region(s) based ' f'on `{tn}` tenant inactive region state.') - # _region_stats[STATISTICS_ACTIVITY][TENANT] = False continue - # _region_stats[STATISTICS_ACTIVITY][TENANT] = True ref[region] = region_rule_map[region] return ref - def restrict_region_rule_map(self, mapping: RegionRuleMap, - allowed_rules: Set[str]) -> RegionRuleMap: + @staticmethod + def restrict_region_rule_map(mapping: RegionRuleMap, + allowed_rules: set[str]) -> RegionRuleMap: """ Restrict rules in RegionRuleMap to the scope of allowed rules """ @@ -450,7 +418,7 @@ def restrict_region_rule_map(self, mapping: RegionRuleMap, @staticmethod def _optimize_region_rule_map_size(mapping: RegionRuleMap - ) -> Dict[str, List[str]]: + ) -> dict[str, list[str]]: """ On small payload the benefit is not clearly visible. The method can be skipped as well. Docker can parse both payloads. @@ -467,7 +435,7 @@ def _optimize_region_rule_map_size(mapping: RegionRuleMap 'eu-west-2': ['five'] } """ - _RuleRegionsMap = Dict[str, Set[str]] + _RuleRegionsMap = dict[str, set[str]] def _rule_to_regions(m: RegionRuleMap) -> _RuleRegionsMap: ref = {} @@ -476,7 +444,7 @@ def _rule_to_regions(m: RegionRuleMap) -> _RuleRegionsMap: ref.setdefault(rule, set()).add(region) return ref - def _optimized(m: _RuleRegionsMap) -> Dict[str, List[str]]: + def _optimized(m: _RuleRegionsMap) -> dict[str, list[str]]: ref = {} for rule, regions in m.items(): ref.setdefault(','.join(sorted(regions)), []).append(rule) @@ -484,35 +452,26 @@ def _optimized(m: _RuleRegionsMap) -> Dict[str, List[str]]: return _optimized(_rule_to_regions(mapping)) - def _build_common_envs(self, min_core_version: str, - current_core_version: str) -> dict: + def _build_common_envs(self) -> dict: return { - BATCH_ENV_DEFAULT_REPORTS_BUCKET_NAME: + CAASEnv.REPORTS_BUCKET_NAME: self._environment_service.default_reports_bucket_name(), - BATCH_ENV_AWS_REGION: - self._environment_service.aws_region(), - BATCH_ENV_JOB_LIFETIME_MIN: - self._environment_service.get_job_lifetime_min(), - BATCH_ENV_MIN_CUSTOM_CORE_VERSION: min_core_version, - BATCH_ENV_CURRENT_CUSTOM_CORE_VERSION: current_core_version, - BATCH_ENV_JOB_TYPE: BATCH_MULTI_ACCOUNT_EVENT_DRIVEN_JOB_TYPE, - BATCH_ENV_LOG_LEVEL: - self._environment_service.batch_job_log_level(), - BATCH_ENV_SUBMITTED_AT: utc_iso(), - BATCH_ENV_SYSTEM_CUSTOMER_NAME: SYSTEM_CUSTOMER, - BATCH_ENV_STATS_S3_BUCKET_NAME: + CAASEnv.STATISTICS_BUCKET_NAME: self._environment_service.get_statistics_bucket_name(), - BATCH_ENV_VAR_RULESETS_BUCKET_NAME: + CAASEnv.RULESETS_BUCKET_NAME: self._environment_service.get_rulesets_bucket_name(), + BatchJobEnv.AWS_REGION: + self._environment_service.aws_region(), + CAASEnv.BATCH_JOB_LIFETIME_MINUTES: + self._environment_service.get_job_lifetime_min(), + BatchJobEnv.JOB_TYPE: BatchJobType.EVENT_DRIVEN.value, + 'LOG_LEVEL': self._environment_service.batch_job_log_level(), + BatchJobEnv.SYSTEM_CUSTOMER_NAME: SYSTEM_CUSTOMER, } - def _submit_batch_job(self, environment: Dict[str, str]) -> Optional[str]: + def _submit_batch_job(self, environment: dict[str, str]) -> str | None: - job_owner = 'events' # mock - submitted_at = environment[BATCH_ENV_SUBMITTED_AT] - job_name = f'{job_owner}-{submitted_at}' - job_name = ''.join( - ch if ch.isalnum() or ch in ('-', '_') else '_' for ch in job_name) + job_name = f'event-driven-job-{int(time.time())}' _LOG.info(f'Submitting AWS Batch job with a name - \'{job_name}\'.') try: @@ -521,36 +480,18 @@ def _submit_batch_job(self, environment: Dict[str, str]) -> Optional[str]: job_queue=self._environment_service.get_batch_job_queue(), job_definition=self._environment_service.get_batch_job_def(), environment_variables=environment, - command=f'python /executor/executor.py' ) _LOG.info(f'Batch response: {response}') - + return response['jobId'] except (BaseException, Exception) as e: - _LOG.error('The following issue has occurred during ' - f'{job_name} Batch Job submission - {e}.') - response = None - - if not isinstance(response, dict): - return None - - return response.get(PARAM_NATIVE_JOB_ID, None) - - def _get_ccc_version(self) -> Optional[Tuple[str, str]]: - min_version = '0' - current_ccc_version = self._settings_service.get_current_ccc_version() - if self._environment_service.get_feature_update_ccc_version(): - # todo skip, due to high workload - _LOG.warning('Feature of dynamic CustodianCustomCore Version ' - 'update is no supported.') - if current_ccc_version: - return min_version, current_ccc_version - else: - _LOG.error( - 'Missing setting CURRENT_CCC_VERSION (current custodian custom' - ' core version) in the CaaSSettings table.' + _LOG.exception( + f'Some issue has occurred during ' + f'{job_name} Batch Job submission' ) + return +# probably not used anymore because events are removed by their ttl class EventRemoverHandler: def __init__(self, settings_service: SettingsService, event_service: EventService, @@ -562,12 +503,12 @@ def __init__(self, settings_service: SettingsService, @classmethod def instantiate(cls) -> 'EventRemoverHandler': return cls( - settings_service=SERVICE_PROVIDER.settings_service(), - event_service=SERVICE_PROVIDER.event_service(), - environment_service=SERVICE_PROVIDER.environment_service() + settings_service=SERVICE_PROVIDER.settings_service, + event_service=SERVICE_PROVIDER.event_service, + environment_service=SERVICE_PROVIDER.environment_service ) - def _obtain_events(self, till: float) -> List[Event]: + def _obtain_events(self, till: float) -> list[Event]: """ Makes N queries. N is a number of partitions (from envs). After that merges these N already sorted lists @@ -586,7 +527,6 @@ def _obtain_events(self, till: float) -> List[Event]: return list(chain.from_iterable(iters)) def handler(self, event: dict) -> dict: - _LOG.info(f'Event remover handler: {event}') _LOG.info('Going to procure cursor value of the event assembler.') event_cursor = None diff --git a/src/handlers/findings_handler.py b/src/handlers/findings_handler.py index 355935617..f506f405f 100644 --- a/src/handlers/findings_handler.py +++ b/src/handlers/findings_handler.py @@ -1,256 +1,162 @@ +from functools import cached_property from http import HTTPStatus -from typing import Dict, Union, List, Optional -from modular_sdk.models.tenant import Tenant - -from handlers.abstracts.abstract_handler import AbstractHandler -from helpers import build_response -from helpers.constants import ( - ACCOUNT_ATTR, GET_URL_ATTR, - HTTPMethod, CUSTOMER_ATTR, TENANT_ATTR +from modular_sdk.services.tenant_service import TenantService + +from handlers import AbstractHandler, Mapping +from helpers.constants import CustodianEndpoint, HTTPMethod, JobState, \ + ReportFormat +from helpers.lambda_response import build_response +from services import SP +from services import modular_helpers +from services.ambiguous_job_service import AmbiguousJob, AmbiguousJobService +from services.platform_service import PlatformService +from services.report_convertors import ShardsCollectionFindingsConvertor +from services import obfuscation +from services.report_service import ReportResponse, ReportService +from services.sharding import ShardsCollection +from validators.swagger_request_models import ( + JobFindingsReportGetModel, + TenantJobsFindingsReportGetModel, ) -from helpers.log_helper import get_logger -from services.findings_service import FindingsService, MAP_KEY_ATTR -from services.modular_service import ModularService - -FINDINGS_PATH = '/findings' - -EXPAND_ON_ATTR = 'expand_on' -RAW_ATTR = 'raw' -FILTER_ATTR = 'filter' -DATA_TYPE_ATTR = 'data_type' - -REGIONS_TO_INCLUDE_ATTR = 'regions_to_include' -RULES_TO_INCLUDE_ATTR = 'rules_to_include' -RESOURCE_TYPES_TO_INCLUDE_ATTR = 'resource_types_to_include' -SEVERITIES_TO_INCLUDE_ATTR = 'severities_to_include' -DEPENDENT_INCLUSION_ATTR = 'dependent_inclusion' - -FINDINGS_PERSISTENCE_ERROR = 'Tenant: \'{identifier}\' has no persisted ' \ - 'findings state.' - -FINDINGS_DEMAND_STORE_INACCESSIBLE = 'Request to store \'{identifier}\' ' \ - 'Account Findings state has not been' \ - ' successful.' -FINDINGS_DEMAND_URL_INACCESSIBLE = 'Request to generate \'{identifier}\' ' \ - 'Account Findings state has not been' \ - ' successful.' -GENERIC_DEMAND_URL_INACCESSIBLE = 'URL to retrieve Findings state of' \ - ' \'{identifier}\' account could not' \ - ' be provided.' - -FINDINGS_CLEARED = 'Findings state bound to \'{identifier}\' tenant ' \ - 'has been cleared.' - -ACCOUNT_PERSISTENCE_ERROR = 'Tenant \'{identifier}\' not found.' - -MAP_VALIDATION_KEYS = ('required_type', 'required') - -PRESIGNED_URL_PARAM = 'presigned_url' - -_LOG = get_logger(__name__) - - -class FindingsHandler(AbstractHandler): - """Handles the latest Findings, bound to account """ - - FILTER_KEYS: tuple = ( - RULES_TO_INCLUDE_ATTR, REGIONS_TO_INCLUDE_ATTR, - RESOURCE_TYPES_TO_INCLUDE_ATTR, SEVERITIES_TO_INCLUDE_ATTR - ) - - def __init__(self, service: FindingsService, - modular_service: ModularService): - self._service = service - self._modular_service = modular_service +from validators.utils import validate_kwargs + + +class FindingsReportHandler(AbstractHandler): + def __init__(self, ambiguous_job_service: AmbiguousJobService, + report_service: ReportService, + tenant_service: TenantService, + platform_service: PlatformService): + self._ambiguous_job_service = ambiguous_job_service + self._rs = report_service + self._ts = tenant_service + self._platform_service = platform_service + + @classmethod + def build(cls) -> 'AbstractHandler': + return cls( + ambiguous_job_service=SP.ambiguous_job_service, + report_service=SP.report_service, + tenant_service=SP.modular_client.tenant_service(), + platform_service=SP.platform_service + ) - def define_action_mapping(self): + @cached_property + def mapping(self) -> Mapping: return { - FINDINGS_PATH: { - HTTPMethod.GET: self.get_findings, - HTTPMethod.DELETE: self.delete_findings - } + CustodianEndpoint.REPORTS_FINDINGS_JOBS_JOB_ID: { + HTTPMethod.GET: self.get_by_job + }, + CustodianEndpoint.REPORTS_FINDINGS_TENANTS_TENANT_NAME_JOBS: { + HTTPMethod.GET: self.get_by_tenant_jobs + }, + # '/reports/findings/platforms/k8s/{platform_id}/state/latest': { + # HTTPMethod.GET: self.k8s_platform_get_latest + # }, + # '/reports/findings/tenants/{tenant_name}/state/latest': { + # HTTPMethod.GET: self.get_latest + # }, } - def get_findings(self, event): - """ - Retrieves Findings state bound to an account, driven by - an event. An `account` field, with a respective value, must - derive an entity, cloud identifier of which is used to get the latest - related state, mapped content of which is inverted and expanded - according to the `expand_on` parameter, default value of which is - `resources`. - """ - # self._validate_get_findings(event) - _tenant_name = event.get(TENANT_ATTR) - _expansion = event.get(EXPAND_ON_ATTR) - _raw = event.get(RAW_ATTR) - _tenant = self._get_entity(event) - - self._handle_consequence( - commenced=bool(_tenant), identifier=_tenant_name, - code=HTTPStatus.NOT_FOUND, - respond=ACCOUNT_PERSISTENCE_ERROR - ) - - _identifier = _tenant.project - _findings = self._service.get_findings_content(_identifier) - if _raw: - _content = _findings - _LOG.info('Returning raw findings content') - else: - # Expands collection into an iterable sequence of vulnerability items - _iterator = self._service.expand_content(_findings, _expansion) - - # Instantiate a filterable iterator - _filter_relation = self._get_filter_key_relation() - _filters = self._get_map_from_reference(event, _filter_relation) - - # Injects dependency (given any has been given) among provided filters - _dependent = bool(event.get(DEPENDENT_INCLUSION_ATTR)) - - _iterator = self._service.filter_iterator( - iterator=_iterator, dependent=_dependent, **_filters - ) + def k8s_platform_get_latest(self, event): + pass - # Formats expanded Findings, filtered state in on-demand collection - _map_key = event.get(MAP_KEY_ATTR) - if _map_key: - # Map collection type has been chosen - # Instantiates an extractive iterator - _iterator = self._service.extractive_iterator( - iterator=_iterator, key=_map_key - ) + def get_latest(self, event): + pass - # Formats items in the derived iterator - _data_type = event.get(DATA_TYPE_ATTR) - _content = self._service.format_iterator( - iterator=_iterator, key=_data_type + @validate_kwargs + def get_by_job(self, event: JobFindingsReportGetModel, job_id: str): + job = self._ambiguous_job_service.get_job( + job_id=job_id, + typ=event.job_type, + customer=event.customer + ) + if not job: + return build_response( + content='The request job not found', + code=HTTPStatus.NOT_FOUND ) - if event.get(GET_URL_ATTR): - _content = self._get_content_url( - content=_content, identifier=_identifier, name=_tenant_name + if job.is_platform_job: + platform = self._platform_service.get_nullable(job.platform_id) + if not platform: + return build_response( + content='Job platform not found', + code=HTTPStatus.NOT_FOUND ) - - return build_response(code=HTTPStatus.OK, content=_content) - - def delete_findings(self, event): - """ - Removes the latest Findings state, bound to an account, driven by - an event. An `account` field, with a respective value, must - derive an entity, cloud identifier of which is used to get the latest - related state. - """ - tenant_name = event.get(TENANT_ATTR) - _tenant = self._get_entity(event) - self._handle_consequence( - commenced=bool(_tenant), identifier=tenant_name, - code=HTTPStatus.NOT_FOUND, - respond=ACCOUNT_PERSISTENCE_ERROR - ) - _removed = self._service.delete_findings(_tenant.project) - self._handle_consequence( - commenced=bool(_removed), identifier=tenant_name, - code=HTTPStatus.NOT_FOUND, - respond=FINDINGS_PERSISTENCE_ERROR - ) - _message = FINDINGS_CLEARED.format(identifier=tenant_name) - return build_response(content=_message) - - def _get_entity(self, event: Dict) -> Optional[Tenant]: - """ - Retrieves Account entity, restricting respective access for non - bound customers. - :return: Optional[Tenant] - """ - customer = event.get(CUSTOMER_ATTR) - entity = self._modular_service.get_tenant( - event.get(TENANT_ATTR) - ) - if not entity or not entity.is_active or \ - customer and entity.customer_name != customer: - return - return entity - - def _get_content_url(self, content: Union[Dict, List], - identifier: str, name: str) -> Dict: - """ - Mandates Findings state retrieval, driven by designated storage and - presigned URL reference, having previously outsourced the given - content. - :param content:Union[Dict, List] - :param identifier:str - :param name:str - """ - _demand = self._service.get_demand_folder() - _put = self._service.put_findings(content, identifier, _demand) - - self._handle_consequence( - commenced=bool(_put), identifier=name, - code=HTTPStatus.INTERNAL_SERVER_ERROR, - respond=GENERIC_DEMAND_URL_INACCESSIBLE, - log=FINDINGS_DEMAND_STORE_INACCESSIBLE, - error=True - ) - - _url = self._service.get_findings_url(identifier, _demand) - - self._handle_consequence( - commenced=bool(_url), identifier=name, - code=HTTPStatus.INTERNAL_SERVER_ERROR, - respond=GENERIC_DEMAND_URL_INACCESSIBLE, - log=FINDINGS_DEMAND_URL_INACCESSIBLE, - error=True + collection = self._rs.platform_job_collection(platform, job.job) + collection.meta = self._rs.fetch_meta(platform) + else: + tenant = self._ts.get(job.tenant_name) + modular_helpers.assert_tenant_valid(tenant, event.customer) + collection = self._rs.ambiguous_job_collection(tenant, job) + collection.meta = self._rs.fetch_meta(tenant) + return build_response( + content=self._collection_response(job, collection, event.href, + event.obfuscated) ) - return {PRESIGNED_URL_PARAM: _url} - - @staticmethod - def _handle_consequence(commenced: bool, identifier: str, code: int, - respond: str, log: str = None, - error: bool = False): + def _collection_response(self, job: AmbiguousJob, + collection: ShardsCollection, + href: bool = False, + obfuscated: bool = False + ) -> dict: """ - Mandates the consequences of actions, bound to an Account - entity, derived by a given identifier. Raises a CustodianException - with provided `code`, `log` and `respond` messages, - given the action has not successfully `commenced`. - :parameter commenced:bool - :parameter identifier:str - :parameter code: int - :parameter respond:str - :parameter log:str - :raises: CustodianException - :return None: + Builds response for the given collection + :param collection: + :param job: + :param href: + :return: """ - log = log or respond - if not commenced: - respond = respond.format(identifier=identifier) - log = log.format(identifier=identifier) - _log_action = _LOG.error if error else _LOG.warning - _log_action(log) - return build_response(code=code, content=respond) - - @staticmethod - def _delete_requirement_map() -> Dict[str, Dict]: - return {ACCOUNT_ATTR: dict(required_type=str, required=True)} - - @staticmethod - def _filter_requirement_map(): - return {attr: dict(required_type=str, required=False) - for attr in FindingsHandler.FILTER_KEYS} + collection.fetch_all() + + dictionary_url = None + if obfuscated: + dct = obfuscation.get_obfuscation_dictionary(collection) + dictionary_url = self._rs.one_time_url_json(dct, + 'dictionary.json') + report = ShardsCollectionFindingsConvertor().convert(collection) + if href: + return ReportResponse( + job, + self._rs.one_time_url_json(report, f'{job.id}.json'), + dictionary_url, + ReportFormat.JSON + ).dict() - @staticmethod - def _get_map_from_reference(subject: Dict, reference: Dict): - return {(_sub or _target): subject[_target] - for _target, _sub in reference.items() if _target in subject} - - @staticmethod - def _get_filter_key_relation(): - return dict( - zip(FindingsHandler.FILTER_KEYS, FindingsService.FILTER_KEYS) + else: + return ReportResponse( + job, + report, + dictionary_url, + ReportFormat.JSON + ).dict() + + @validate_kwargs + def get_by_tenant_jobs(self, event: TenantJobsFindingsReportGetModel, + tenant_name: str): + tenant = self._ts.get(tenant_name) + modular_helpers.assert_tenant_valid(tenant, event.customer) + + jobs = self._ambiguous_job_service.get_by_tenant_name( + tenant_name=tenant_name, + job_type=event.job_type, + status=JobState.SUCCEEDED, + start=event.start_iso, + end=event.end_iso ) - - @staticmethod - def _map_key_requirement_map(required: bool): - return {MAP_KEY_ATTR: dict(required_type=str, required=required)} + jobs = filter(lambda x: not x.is_platform_job, + self._ambiguous_job_service.to_ambiguous(jobs)) + + meta = self._rs.fetch_meta(tenant) + job_collection = [] + for job in jobs: + col = self._rs.ambiguous_job_collection(tenant, job) + col.meta = meta + job_collection.append((job, col)) + # TODO _collection_response to threads? + return build_response(content=map( + lambda pair: self._collection_response(*pair, href=event.href, + obfuscated=event.obfuscated), + job_collection + )) diff --git a/src/handlers/license_handler.py b/src/handlers/license_handler.py index 8f75666ba..5a30089d2 100644 --- a/src/handlers/license_handler.py +++ b/src/handlers/license_handler.py @@ -1,20 +1,44 @@ +from functools import cached_property, partial from http import HTTPStatus +from itertools import chain +from typing import Iterable -from modular_sdk.commons.constants import ApplicationType +from modular_sdk.commons.constants import ApplicationType, ParentType, \ + ParentScope +from modular_sdk.models.parent import Parent +from modular_sdk.services.application_service import ApplicationService +from modular_sdk.services.parent_service import ParentService -from handlers.abstracts.abstract_handler import AbstractHandler -from helpers import build_response, validate_params -from helpers.constants import CUSTOMER_ATTR, LICENSE_KEY_ATTR, \ - TENANTS_ATTR, STATUS_CODE_ATTRS, HTTPMethod -from helpers.enums import RuleDomain +from handlers import AbstractHandler, Mapping +from helpers.constants import ( + CustodianEndpoint, + HTTPMethod, + LICENSE_KEY_ATTR, + TENANT_LICENSE_KEY_ATTR, +) +from helpers.lambda_response import ResponseFactory, build_response from helpers.log_helper import get_logger -from models.modular.application import CustodianLicensesApplicationMeta -from services.clients.lambda_func import LambdaClient, \ - LICENSE_UPDATER_LAMBDA_NAME +from services import SP +from services.abs_lambda import ProcessedEvent +from services.clients.lambda_func import LICENSE_UPDATER_LAMBDA_NAME, \ + LambdaClient from services.license_manager_service import LicenseManagerService from services.license_service import LicenseService -from services.modular_service import ModularService +from services.modular_helpers import ( + ResolveParentsPayload, + build_parents, + get_activation_dto, + split_into_to_keep_to_delete, + get_main_scope +) from services.ruleset_service import RulesetService +from validators.swagger_request_models import ( + BaseModel, + LicenseActivationPutModel, + LicensePostModel, + LicenseActivationPatchModel +) +from validators.utils import validate_kwargs _LOG = get_logger(__name__) @@ -29,150 +53,265 @@ def __init__(self, ruleset_service: RulesetService, license_manager_service: LicenseManagerService, lambda_client: LambdaClient, - modular_service: ModularService): + application_service: ApplicationService, + parent_service: ParentService): self.service = self_service self.ruleset_service = ruleset_service self.lambda_client = lambda_client self.license_manager_service = license_manager_service - self.modular_service = modular_service + self.aps = application_service + self.ps = parent_service - def define_action_mapping(self): + @classmethod + def build(cls) -> 'LicenseHandler': + return cls( + self_service=SP.license_service, + ruleset_service=SP.ruleset_service, + license_manager_service=SP.license_manager_service, + lambda_client=SP.lambda_client, + application_service=SP.modular_client.application_service(), + parent_service=SP.modular_client.parent_service() + ) + + @cached_property + def mapping(self) -> Mapping: return { - '/license': { + CustodianEndpoint.LICENSES: { + HTTPMethod.POST: self.post_license, + HTTPMethod.GET: self.query_licenses + }, + CustodianEndpoint.LICENSES_LICENSE_KEY: { HTTPMethod.GET: self.get_license, - HTTPMethod.DELETE: self.delete_license, + HTTPMethod.DELETE: self.delete_license }, - '/license/sync': { + CustodianEndpoint.LICENSE_LICENSE_KEY_ACTIVATION: { + HTTPMethod.PUT: self.activate_license, + HTTPMethod.PATCH: self.update_activation, + HTTPMethod.DELETE: self.deactivate_license, + HTTPMethod.GET: self.get_activation + }, + CustodianEndpoint.LICENSES_LICENSE_KEY_SYNC: { HTTPMethod.POST: self.license_sync }, } - def get_license(self, event): - _LOG.info(f'GET license event - {event}.') - license_key = event.get(LICENSE_KEY_ATTR) - customer = event.get(CUSTOMER_ATTR) - tenants = event.get(TENANTS_ATTR) + def get_all_activations(self, license_key: str, + customer: str | None = None) -> Iterable[Parent]: + it = self.ps.i_list_application_parents( + application_id=license_key, + type_=ParentType.CUSTODIAN_LICENSES, + rate_limit=3 + ) + if customer: + it = filter(lambda p: p.customer_id == customer, it) + return it - if not customer: # SYSTEM - licenses = self.service.list_licenses(license_key) - return build_response( - content=(self.service.dto(_license) for _license in licenses) - ) - # not SYSTEM - applications = self.modular_service.get_applications( - customer=customer, _type=ApplicationType.CUSTODIAN_LICENSES.value + @validate_kwargs + def activate_license(self, event: LicenseActivationPutModel, + license_key: str, _pe: ProcessedEvent): + lic = self.service.get_nullable(license_key) + if not lic: + raise ResponseFactory(HTTPStatus.NOT_FOUND).message( + f'License {license_key} not found' + ).exc() + # either ALL & [cloud] & [exclude] or tenant_names + # Should not be many + payload = ResolveParentsPayload( + parents=list(self.get_all_activations(license_key, event.customer)), + tenant_names=event.tenant_names, + exclude_tenants=event.exclude_tenants, + clouds=event.clouds, + all_tenants=event.all_tenants ) - license_keys = set() - for application in applications: - meta = CustodianLicensesApplicationMeta( - **application.meta.as_dict()) - license_keys.update( - item for item in - (meta.license_key(cloud) for cloud in RuleDomain.iter()) - if item - ) + to_keep, to_delete = split_into_to_keep_to_delete(payload) + for parent in to_delete: + self.ps.force_delete(parent) + to_create = build_parents( + payload=payload, + parent_service=self.ps, + application_id=lic.license_key, + customer_id=event.customer, + type_=ParentType.CUSTODIAN_LICENSES, + created_by=_pe['cognito_user_id'], + ) + for parent in to_create: + self.ps.save(parent) - if license_key: - license_keys &= {license_key} - - licenses = [] - - _LOG.info(f'Deriving customer/tenant accessible licenses.') - is_subject_applicable = self.service.is_subject_applicable - for key in license_keys: - l_title = f'License:{key!r}' - item = self.service.get_license(key) - if not item: - _LOG.warning(f'{l_title} does not exist') - continue - # The code after this line probably can be removed. It checks - # whether the license is available for user's tenants. According - # to business requirements, we must allow each license for all - # the tenants. - _LOG.info(f'{l_title} - checking accessibility of {tenants}.') - _allowed = True - if tenants: - for tenant in tenants: - is_applicable = is_subject_applicable( - entity=item, customer=customer, - tenant=tenant - ) - if is_applicable: - _LOG.info( - f'{l_title} is accessible to {tenant!r} tenant.') - break - else: - # Has iterated through all tenants. - _allowed = False - if _allowed: - licenses.append(item) + return build_response(content=get_activation_dto( + chain(to_keep, to_create) + )) - return build_response( - content=(self.service.dto(_license) for _license in licenses) + @validate_kwargs + def update_activation(self, event: LicenseActivationPatchModel, + license_key: str, _pe: ProcessedEvent): + lic = self.service.get_nullable(license_key) + if not lic: + raise ResponseFactory(HTTPStatus.NOT_FOUND).message( + f'License {license_key} not found' + ).exc() + parents = list(self.get_all_activations(license_key, event.customer)) + payload = ResolveParentsPayload.from_parents_list(parents) + + match get_main_scope(parents): + case ParentScope.SPECIFIC: + payload.tenant_names.update(event.add_tenants) + payload.tenant_names.difference_update(event.remove_tenants) + case ParentScope.ALL: + payload.exclude_tenants.difference_update(event.add_tenants) + payload.exclude_tenants.update(event.remove_tenants) + to_keep, to_delete = split_into_to_keep_to_delete(payload) + + for parent in to_delete: + self.ps.force_delete(parent) + to_create = build_parents( + payload=payload, + parent_service=self.ps, + application_id=lic.license_key, + customer_id=event.customer, + type_=ParentType.CUSTODIAN_LICENSES, + created_by=_pe['cognito_user_id'], ) + for parent in to_create: + self.ps.save(parent) + return build_response(content=get_activation_dto( + chain(to_keep, to_create) + )) - def delete_license(self, event): - _LOG.debug(f'Delete license event: {event}') - # customer can be None is SYSTEM is making the request. - # But for this action we need customer - validate_params(event=event, - required_params_list=[LICENSE_KEY_ATTR]) - customer = event[CUSTOMER_ATTR] - license_key = event[LICENSE_KEY_ATTR] - _success = lambda: build_response( - code=HTTPStatus.OK, - content=f'No traces of \'{license_key}\' left for your customer' + @validate_kwargs + def get_activation(self, event: BaseModel, license_key: str): + lic = self.service.get_nullable(license_key) + if not lic: + raise ResponseFactory(HTTPStatus.NOT_FOUND).message( + f'License {license_key} not found' + ).exc() + activations = self.get_all_activations(license_key, event.customer) + return build_response(content=get_activation_dto(activations)) + + @validate_kwargs + def deactivate_license(self, event: BaseModel, license_key: str): + lic = self.service.get_nullable(license_key) + if not lic: + raise ResponseFactory(HTTPStatus.NOT_FOUND).message( + f'License {license_key} not found' + ).exc() + activations = self.get_all_activations( + license_key=lic.license_key, + customer=event.customer ) - applications = self.modular_service.get_applications( - customer=customer, - _type=ApplicationType.CUSTODIAN_LICENSES.value + for parent in activations: + self.ps.force_delete(parent) + return build_response(code=HTTPStatus.NO_CONTENT) + + @validate_kwargs + def get_license(self, event: BaseModel, license_key: str): + obj = self.service.get_nullable(license_key) + if not obj or event.customer not in obj.customers: + raise ResponseFactory(HTTPStatus.NOT_FOUND).message( + 'License not found').exc() + return build_response(self.service.dto(obj)) + + @validate_kwargs + def query_licenses(self, event: BaseModel): + # TODO, pagination + applications = self.aps.i_get_application_by_customer( + customer_id=event.customer, + application_type=ApplicationType.CUSTODIAN_LICENSES.value, + deleted=False ) - for application in applications: - _LOG.info(f'Removing the license key {license_key} from ' - f'customer application') - meta = CustodianLicensesApplicationMeta( - **application.meta.as_dict() + licenses = self.service.to_licenses(applications) + if event.customer: + licenses = filter(lambda x: event.customer in x.customers, + licenses) + return build_response(content=map(self.service.dto, licenses)) + + @validate_kwargs + def post_license(self, event: LicensePostModel, _pe: ProcessedEvent): + license_key = self.activate_license_lm(event.tenant_license_key, + event.customer) + license_obj = self.service.get_nullable(license_key) + if license_obj: + _LOG.info(f'License object {license_key} already exists') + if event.description: + license_obj.description = event.description + else: + _LOG.info('License object does not exist. Creating') + license_obj = self.service.create( + license_key=license_key, + description=event.description or 'Custodian license', + customer=event.customer, + created_by=_pe['cognito_user_id'], ) - for cloud, lk in meta.cloud_to_license_key().items(): - if lk == license_key: - meta.update_license_key(cloud, None) - application.meta = meta.dict() - self.modular_service.save(application) - - _LOG.info('Removing the license and its rule-sets') - self.service.remove_for_customer(license_key, customer) + self.service.save(license_obj) + self._execute_license_sync([license_key]) + + return build_response( + code=HTTPStatus.ACCEPTED, + content=self.service.dto(license_obj) + ) + + @validate_kwargs + def delete_license(self, event: BaseModel, license_key: str): + _success = partial( + build_response, + code=HTTPStatus.NO_CONTENT + ) + lic = self.service.get_nullable(license_key) + if not lic or event.customer and event.customer not in lic.customers: + return _success() + if not event.customer or len( + lic.customers) == 1 and event.customer in lic.customers: + self.service.delete(lic) + self.service.remove_rulesets_for_license(lic) + # TODO remove parents + return _success() + + lic.customers.pop(event.customer) + self.service.save(lic) return _success() - def license_sync(self, event): + @validate_kwargs + def license_sync(self, event: BaseModel, license_key: str): """ Returns a response from an asynchronously invoked sync-concerned lambda, `license-updater`. :return:Dict[code=202] """ - validate_params(event=event, - required_params_list=(self.hash_key_attr_name,)) - _license_key = event[self.hash_key_attr_name] - _response = self.lambda_client.invoke_function_async( - LICENSE_UPDATER_LAMBDA_NAME, event={ - self.hash_key_attr_name: [_license_key] - } + _response = self._execute_license_sync([license_key]) + return build_response( + code=HTTPStatus.ACCEPTED, + content='License is being synchronized' ) - code = self._derive_status_code( - _response, HTTPStatus.INTERNAL_SERVER_ERROR + + def activate_license_lm(self, tenant_license_key: str, + customer: str) -> str: + forbid = ResponseFactory(HTTPStatus.FORBIDDEN).message( + 'License manager does not allow to active the license' ) - accepted = code == HTTPStatus.ACCEPTED - return build_response( - code=code, - content=f'License:\'{_license_key}\' synchronization request ' - f'{"has been" if accepted else "was not"} accepted.' + response = self.license_manager_service.activate_customer( + customer, tenant_license_key ) + if not response: + _LOG.info('Not successful response from LM') + raise forbid.exc() - @staticmethod - def _derive_status_code(response: dict, default): - return next( - (response[each] for each in STATUS_CODE_ATTRS if each in response), - default) + lk = response.get(LICENSE_KEY_ATTR) + tlk = response.get(TENANT_LICENSE_KEY_ATTR) + if not all((lk, tlk)): + _LOG.warning('Invalid response from LM') + raise forbid.exc() + return lk - @property - def hash_key_attr_name(self): - return 'license_key' + def _execute_license_sync(self, license_keys: list[str]) -> dict: + """ + Returns a response from an asynchronously invoked + sync-concerned lambda, `license-updater`. + :return:Dict[code=202] + """ + _LOG.info('Invoking license updater lambda') + response = self.lambda_client.invoke_function_async( + LICENSE_UPDATER_LAMBDA_NAME, event={ + LICENSE_KEY_ATTR: license_keys + } + ) + _LOG.info(f'License updater lambda was invoked: {response}') + return response diff --git a/src/handlers/license_manager_setting_handler.py b/src/handlers/license_manager_setting_handler.py index 799a144dc..57862a07d 100644 --- a/src/handlers/license_manager_setting_handler.py +++ b/src/handlers/license_manager_setting_handler.py @@ -1,28 +1,38 @@ +from functools import cached_property from http import HTTPStatus -from handlers.abstracts.abstract_handler import AbstractHandler -from helpers import build_response -from helpers.constants import PORT_ATTR, HOST_ATTR, KEY_ID_ATTR, \ - PRIVATE_KEY_ATTR, \ - ALGORITHM_ATTR, FORMAT_ATTR, KID_ATTR, ALG_ATTR, PUBLIC_KEY_ATTR, \ - VALUE_ATTR, PROTOCOL_ATTR, STAGE_ATTR, API_VERSION_ATTR, HTTPMethod +from cryptography.hazmat.primitives.serialization import ( + Encoding, + NoEncryption, + PrivateFormat, + PublicFormat, + load_pem_private_key, +) + +from handlers import AbstractHandler, Mapping +from helpers.constants import ( + ALG_ATTR, + CustodianEndpoint, + HTTPMethod, + KID_ATTR, + VALUE_ATTR, +) +from helpers.lambda_response import ResponseFactory, build_response from helpers.log_helper import get_logger -from services.key_management_service import KeyManagementService, IKey +from services import SP +from services.clients.ssm import AbstractSSMClient from services.license_manager_service import LicenseManagerService -from services.setting_service import SettingsService, Setting - -LM_SETTINGS_PATH = '/settings/license-manager' -CONFIG_PATH = '/config' -CLIENT_PATH = '/client' +from services.setting_service import Setting, SettingsService +from validators.swagger_request_models import ( + BaseModel, + LicenseManagerClientSettingDeleteModel, + LicenseManagerClientSettingPostModel, + LicenseManagerConfigSettingPostModel, +) +from validators.utils import validate_kwargs _LOG = get_logger(__name__) -UNSUPPORTED_ALG_TEMPLATE = 'Algorithm:\'{alg}\' is not supported.' -KEY_OF_ENTITY_TEMPLATE = '{key}:\'{kid}\' of \'{uid}\' {entity}' - -UNRESOLVABLE_ERROR = 'Request has run into an issue, which could not' \ - ' be resolved.' - PEM_ATTR = 'PEM' @@ -31,78 +41,65 @@ class LicenseManagerClientHandler(AbstractHandler): Manages License Manager Client. """ - def __init__( - self, settings_service: SettingsService, - key_management_service: KeyManagementService, - license_manager_service: LicenseManagerService - ): + def __init__(self, settings_service: SettingsService, + license_manager_service: LicenseManagerService, + ssm_client: AbstractSSMClient): self.settings_service = settings_service - self.key_management_service = key_management_service self.license_manager_service = license_manager_service + self._ssm_client = ssm_client - def define_action_mapping(self): + @cached_property + def mapping(self) -> Mapping: return { - LM_SETTINGS_PATH + CLIENT_PATH: { + CustodianEndpoint.SETTINGS_LICENSE_MANAGER_CLIENT: { HTTPMethod.GET: self.get, HTTPMethod.POST: self.post, HTTPMethod.DELETE: self.delete, } } - def get(self, event: dict): - _LOG.info( - f'{HTTPMethod.GET} License Manager Client-Key event: {event}') - - fmt = event.get(FORMAT_ATTR) or PEM_ATTR + @classmethod + def build(cls): + return cls( + settings_service=SP.settings_service, + license_manager_service=SP.license_manager_service, + ssm_client=SP.ssm + ) - configuration: dict = self.settings_service. \ - get_license_manager_client_key_data() or {} + @validate_kwargs + def get(self, event: BaseModel): + configuration: dict = self.settings_service.get_license_manager_client_key_data() or {} + if not configuration: + raise ResponseFactory(HTTPStatus.NOT_FOUND).message( + 'Configuration is not found' + ).exc() kid = configuration.get(KID_ATTR) alg = configuration.get(ALG_ATTR) + name = self.license_manager_service.derive_client_private_key_id( + kid=kid + ) + data = self._ssm_client.get_secret_value(name) + pem = data['value'] + key = load_pem_private_key(pem.encode(), None) + return build_response(content=self.get_dto( + kid=kid, + alg=alg, + public_key=self.get_public(key) + )) - response = None - - if kid and alg: - prk_kid = self.license_manager_service.derive_client_private_key_id( - kid=kid - ) - _LOG.info(f'Going to retrieve private-key by \'{prk_kid}\'.') - prk = self.key_management_service.get_key(kid=prk_kid, alg=alg) - if not prk: - message = KEY_OF_ENTITY_TEMPLATE.format( - key='PrivateKey', kid=prk_kid, uid=alg, - entity='algorithm' - ) - _LOG.warning(message + ' could not be retrieved.') - else: - _LOG.info(f'Going to derive a public-key of \'{kid}\'.') - puk: IKey = self._derive_puk(prk=prk.key) - if puk: - managed = self.key_management_service. \ - instantiate_managed_key( - kid=kid, key=puk, alg=alg - ) - _LOG.info(f'Going to export the \'{kid}\' public-key.') - response = self._response_dto( - exported_key=managed.export_key(frmt=fmt), - value_attr=PUBLIC_KEY_ATTR - ) - - return build_response( - code=HTTPStatus.OK, - content=response or [] + @staticmethod + def get_public(key) -> bytes: + return key.public_key().public_bytes( + Encoding.PEM, PublicFormat.SubjectPublicKeyInfo ) - def post(self, event: dict): + @validate_kwargs + def post(self, event: LicenseManagerClientSettingPostModel): # Validation is taken care of, on the gateway/abstract-handler layer. - _LOG.info( - f'{HTTPMethod.POST} License Manager Client-Key event: {event}' - ) - kid = event.get(KEY_ID_ATTR) - alg = event.get(ALGORITHM_ATTR) - raw_prk = event.get(PRIVATE_KEY_ATTR) - frmt = event.get(FORMAT_ATTR) + kid = event.key_id + alg = event.algorithm + raw_prk: str = event.private_key # Decoding is taking care of within the validation layer. @@ -113,48 +110,26 @@ def post(self, event: dict): content='License Manager Client-Key already exists.' ) - prk = self.key_management_service.import_key( - alg=alg, key_value=raw_prk - ) - - puk = self._derive_puk(prk=prk) - if not puk: - return build_response( - code=HTTPStatus.BAD_REQUEST, - content='Improper private-key.' - ) - - if not prk: - return build_response( - content=UNSUPPORTED_ALG_TEMPLATE.format(alg=alg), - code=HTTPStatus.NOT_FOUND - ) - - prk = self.key_management_service.instantiate_managed_key( - alg=alg, key=prk, - kid=self.license_manager_service.derive_client_private_key_id( - kid=kid - ) - ) - - message = KEY_OF_ENTITY_TEMPLATE.format( - key='PublicKey', kid=prk.kid, uid=prk.alg, entity='algorithm' + try: + prk = load_pem_private_key(raw_prk.encode(), None) + except (ValueError, Exception): + raise ResponseFactory(HTTPStatus.BAD_REQUEST).message( + 'Invalid private key' + ).exc() + name = self.license_manager_service.derive_client_private_key_id( + kid=kid ) - - _LOG.info(message + ' has been instantiated.') - - if not self.key_management_service.save_key( - kid=prk.kid, key=prk.key, frmt=frmt - ): - return build_response( - content=UNRESOLVABLE_ERROR, - code=HTTPStatus.INTERNAL_SERVER_ERROR - ) - - managed_puk = self.key_management_service.instantiate_managed_key( - kid=kid, alg=alg, key=puk + self._ssm_client.create_secret( + secret_name=name, + secret_value={ + VALUE_ATTR: prk.private_bytes( + Encoding.PEM, + PrivateFormat.PKCS8, + NoEncryption() + ) + } ) - + _LOG.info('Private key was saved to SSM') setting = self.settings_service.create_license_manager_client_key_data( kid=kid, alg=alg ) @@ -165,18 +140,29 @@ def post(self, event: dict): self.settings_service.save(setting=setting) return build_response( - code=HTTPStatus.OK, - content=self._response_dto( - exported_key=managed_puk.export_key(frmt=frmt), - value_attr=PUBLIC_KEY_ATTR + code=HTTPStatus.CREATED, + content=self.get_dto( + alg=alg, + kid=kid, + public_key=self.get_public(prk) ) ) - def delete(self, event: dict): - _LOG.info( - f'{HTTPMethod.DELETE} License Manager Client-Key event: {event}') + @staticmethod + def get_dto(alg: str, kid: str, public_key: str | bytes) -> dict: + if isinstance(public_key, bytes): + public_key = public_key.decode() + return { + 'algorithm': alg, + 'b64_encoded': False, + 'format': 'PEM', + 'key_id': kid, + 'public_key': public_key + } - requested_kid = event.get(KEY_ID_ATTR) + @validate_kwargs + def delete(self, event: LicenseManagerClientSettingDeleteModel): + requested_kid = event.key_id head = 'License Manager Client-Key' unretained = ' does not exist' @@ -184,8 +170,7 @@ def delete(self, event: dict): # Default 404 error-response. content = head + unretained - setting = self.settings_service. \ - get_license_manager_client_key_data(value=False) + setting = self.settings_service.get_license_manager_client_key_data(value=False) if not setting: return build_response( @@ -206,50 +191,12 @@ def delete(self, event: dict): head + f' does not contain {requested_kid} \'kid\' data.') return build_response(code=code, content=content) - is_key_data_removed = False - - prk_kid = self.license_manager_service.derive_client_private_key_id( + name = self.license_manager_service.derive_client_private_key_id( kid=kid ) - - _LOG.info(f'Going to retrieve private-key by \'{prk_kid}\'.') - prk = self.key_management_service.get_key(kid=prk_kid, alg=alg) - - prk_head = KEY_OF_ENTITY_TEMPLATE.format( - key='PrivateKey', kid=prk_kid, uid=alg, entity='algorithm' - ) - if not prk: - _LOG.warning(prk_head + ' could not be retrieved.') - else: - if not self.key_management_service.delete_key(kid=prk.kid): - _LOG.warning(prk_head + ' could not be removed.') - else: - is_key_data_removed = True - - if self.settings_service.delete(setting=setting): - committed = 'completely ' if is_key_data_removed else '' - committed += 'removed' - code = HTTPStatus.OK - content = head + f' has been {committed}' - - return build_response(code=code, content=content) - - @staticmethod - def _response_dto(exported_key: dict, value_attr: str): - if VALUE_ATTR in exported_key: - exported_key[value_attr] = exported_key.pop(VALUE_ATTR) - return exported_key - - @staticmethod - def _derive_puk(prk: IKey): - try: - puk = prk.public_key() - except (Exception, ValueError) as e: - message = 'Public-Key could not be derived out of a ' \ - f'private one, due to: "{e}".' - _LOG.warning(message) - puk = None - return puk + self._ssm_client.delete_parameter(name) + self.settings_service.delete(setting=setting) + return build_response(code=HTTPStatus.NO_CONTENT) class LicenseManagerConfigHandler(AbstractHandler): @@ -257,35 +204,33 @@ class LicenseManagerConfigHandler(AbstractHandler): Manages License Manager access-configuration. """ - def __init__( - self, settings_service: SettingsService - ): + def __init__(self, settings_service: SettingsService): self.settings_service = settings_service - def define_action_mapping(self): + @cached_property + def mapping(self) -> Mapping: return { - LM_SETTINGS_PATH + CONFIG_PATH: { + CustodianEndpoint.SETTINGS_LICENSE_MANAGER_CONFIG: { HTTPMethod.GET: self.get, HTTPMethod.POST: self.post, HTTPMethod.DELETE: self.delete, } } - def get(self, event: dict): - _LOG.info( - f'{HTTPMethod.GET} License Manager access-config event: {event}') + @classmethod + def build(cls): + return cls(settings_service=SP.settings_service) - configuration: dict = self.settings_service. \ - get_license_manager_access_data() - return build_response( - code=HTTPStatus.OK, - content=configuration or [] - ) + @validate_kwargs + def get(self, event: BaseModel): + configuration: dict = self.settings_service.get_license_manager_access_data() + if not configuration: + raise ResponseFactory(HTTPStatus.NOT_FOUND).message( + 'Setting not found').exc() + return build_response(content=configuration) - def post(self, event: dict): - _LOG.info( - f'{HTTPMethod.POST} License Manager access-config event: {event}' - ) + @validate_kwargs + def post(self, event: LicenseManagerConfigSettingPostModel): if self.settings_service.get_license_manager_access_data(): return build_response( code=HTTPStatus.CONFLICT, @@ -294,27 +239,20 @@ def post(self, event: dict): # TODO check access ? setting = self.settings_service. \ create_license_manager_access_data_configuration( - host=event[HOST_ATTR], - port=event.get(PORT_ATTR), - protocol=event.get(PROTOCOL_ATTR), - stage=event.get(STAGE_ATTR), - api_version=event.get(API_VERSION_ATTR) + host=event.host, + port=event.port, + protocol=event.protocol, + stage=event.stage, + api_version=event.api_version ) _LOG.info(f'Persisting License Manager config-data: {setting.value}.') self.settings_service.save(setting=setting) - return build_response( - code=HTTPStatus.OK, content=setting.value - ) - - def delete(self, event: dict): - _LOG.info(f'{HTTPMethod.DELETE} License Manager access-config event:' - f' {event}') + return build_response(code=HTTPStatus.CREATED, content=setting.value) - configuration: Setting = \ - self.settings_service.get_license_manager_access_data( - value=False - ) + @validate_kwargs + def delete(self, event: BaseModel): + configuration: Setting = self.settings_service.get_license_manager_access_data(value=False) if not configuration: return build_response( code=HTTPStatus.NOT_FOUND, @@ -323,7 +261,4 @@ def delete(self, event: dict): _LOG.info(f'Removing License Manager config-data:' f' {configuration.value}.') self.settings_service.delete(setting=configuration) - return build_response( - code=HTTPStatus.OK, - content='License Manager config-data has been removed.' - ) + return build_response(code=HTTPStatus.NO_CONTENT) diff --git a/src/handlers/mail_setting_handler.py b/src/handlers/mail_setting_handler.py index 11c689c62..cd9c3432b 100644 --- a/src/handlers/mail_setting_handler.py +++ b/src/handlers/mail_setting_handler.py @@ -1,16 +1,25 @@ +from functools import cached_property from http import HTTPStatus -from handlers.abstracts.abstract_handler import AbstractHandler -from helpers import build_response -from helpers.constants import HTTPMethod, \ - USERNAME_ATTR, PORT_ATTR, HOST_ATTR, PASSWORD_ATTR, DEFAULT_SENDER_ATTR, \ - USE_TLS_ATTR, MAX_EMAILS_ATTR +from handlers import AbstractHandler, Mapping +from helpers.constants import ( + CustodianEndpoint, + HTTPMethod, + PASSWORD_ATTR, +) +from helpers.lambda_response import ResponseFactory +from helpers.lambda_response import build_response from helpers.log_helper import get_logger +from services import SP from services.clients.smtp import SMTPClient from services.clients.ssm import SSMClient -from services.setting_service import SettingsService, Setting - -MAIL_SETTINGS_PATH = '/settings/mail' +from services.setting_service import Setting, SettingsService +from validators.swagger_request_models import ( + BaseModel, + MailSettingGetModel, + MailSettingPostModel, +) +from validators.utils import validate_kwargs _LOG = get_logger(__name__) @@ -23,62 +32,61 @@ class MailSettingHandler(AbstractHandler): Manages Mail configuration credentials. """ - def __init__( - self, settings_service: SettingsService, - smtp_client: SMTPClient, ssm_client: SSMClient - ): + def __init__(self, settings_service: SettingsService, + smtp_client: SMTPClient, ssm_client: SSMClient): self.settings_service = settings_service self.smtp_client = smtp_client self.ssm_client = ssm_client - def define_action_mapping(self): + @cached_property + def mapping(self) -> Mapping: return { - MAIL_SETTINGS_PATH: { + CustodianEndpoint.SETTINGS_MAIL: { HTTPMethod.GET: self.get, HTTPMethod.POST: self.post, HTTPMethod.DELETE: self.delete, } } - def get(self, event: dict): - _LOG.info(f'{HTTPMethod.GET} Mail Configuration event: {event}') - disclose: bool = event.get(DISCLOSE_ATTR) + @classmethod + def build(cls): + return cls( + settings_service=SP.settings_service, + smtp_client=SMTPClient(), + ssm_client=SP.ssm + ) + @validate_kwargs + def get(self, event: MailSettingGetModel): configuration: dict = self.settings_service.get_mail_configuration() - if configuration and disclose: + if not configuration: + raise ResponseFactory(HTTPStatus.NOT_FOUND).message( + 'No mail configuration').exc() + if event.disclose: alias = configuration.get(PASSWORD_ATTR) password = self.ssm_client.get_secret_value(secret_name=alias) - if not password: - message = f'Password:\'{alias}\' could not be retrieved.' - _LOG.error(message) - return build_response( - code=HTTPStatus.NOT_FOUND, content=message - ) configuration[PASSWORD_ATTR] = password - return build_response( - code=HTTPStatus.OK, - content=configuration or [] - ) + return build_response(content=configuration) - def post(self, event: dict): + @validate_kwargs + def post(self, event: MailSettingPostModel): # Validation is taken care of, on the gateway/abstract-handler layer. - username = event.get(USERNAME_ATTR) - password = event.get(PASSWORD_ATTR) - _LOG.info(f'{HTTPMethod.POST} Mail Configuration event: {event}') + username = event.username + password = event.password if self.settings_service.get_mail_configuration(): return build_response( code=HTTPStatus.CONFLICT, content='Mail configuration already exists.' ) - self.smtp_client.host = event.get(HOST_ATTR) - self.smtp_client.port = event.get(PORT_ATTR) + self.smtp_client.host = event.host + self.smtp_client.port = event.port issue = '' with self.smtp_client as client: - if event.get(USE_TLS_ATTR) and not client.tls(): + if event.use_tls and not client.tls(): issue = 'TLS could not be established.' if not issue and not client.authenticate( @@ -91,25 +99,28 @@ def post(self, event: dict): code=HTTPStatus.BAD_REQUEST, content=issue ) - name = event[PASSWORD_ALIAS_ATTR] + name = event.password_alias _LOG.info(f'Persisting password parameter under a \'{name}\' secret') self.ssm_client.create_secret( secret_name=name, secret_value=password ) - payload = { - each: event[each] for each in self._model_required_map() - if each in event - } - setting = self.settings_service.create_mail_configuration(**payload) - _LOG.info(f'Persisting mail-configuration data: {payload}.') + setting = self.settings_service.create_mail_configuration( + username=event.username, + password_alias=event.password_alias, + default_sender=event.default_sender, + host=event.host, + port=event.port, + use_tls=event.use_tls, + max_emails=event.max_emails + ) self.settings_service.save(setting=setting) return build_response( - code=HTTPStatus.OK, content=setting.value + code=HTTPStatus.CREATED, content=setting.value ) - def delete(self, event: dict): - _LOG.info(f'{HTTPMethod.DELETE} Mail Configuration event: {event}') + @validate_kwargs + def delete(self, event: BaseModel): configuration: Setting = self.settings_service.get_mail_configuration( value=False ) @@ -125,26 +136,4 @@ def delete(self, event: dict): self.ssm_client.delete_parameter( secret_name=name ) - return build_response( - code=HTTPStatus.OK, - content='Mail configuration has been removed.' - ) - - @staticmethod - def _model_required_map(): - return { - USERNAME_ATTR: str, - PASSWORD_ALIAS_ATTR: str, - PORT_ATTR: int, - HOST_ATTR: str, - MAX_EMAILS_ATTR: int, - DEFAULT_SENDER_ATTR: str, - USE_TLS_ATTR: bool - } - - @staticmethod - def _session_required_map(): - return { - HOST_ATTR: str, - PORT_ATTR: int - } + return build_response(code=HTTPStatus.NO_CONTENT) diff --git a/src/handlers/parents_handler.py b/src/handlers/parents_handler.py deleted file mode 100644 index cd617b261..000000000 --- a/src/handlers/parents_handler.py +++ /dev/null @@ -1,340 +0,0 @@ -from functools import cached_property -from http import HTTPStatus -from itertools import chain -from typing import Optional, Dict, Set - -from modular_sdk.commons.constants import ApplicationType, ParentType, \ - ParentScope -from modular_sdk.models.application import Application -from modular_sdk.models.tenant import Tenant -from modular_sdk.services.parent_service import ParentService - -from handlers.abstracts.abstract_handler import AbstractHandler -from helpers import adjust_cloud -from helpers import build_response -from helpers.constants import CUSTOMER_ATTR, PARENT_ID_ATTR, \ - SPECIFIC_TENANT_SCOPE, \ - CLOUD_TO_APP_TYPE, HTTPMethod -from helpers.enums import ParentType -from helpers.log_helper import get_logger -from models.modular.parents import Parent, ParentMeta -from services import SERVICE_PROVIDER -from services.modular_service import ModularService -from services.rule_meta_service import RuleService -from validators.request_validation import ParentPostModel, ParentPatchModel -from validators.utils import validate_kwargs - -_LOG = get_logger(__name__) - - -class ParentsHandler(AbstractHandler): - def __init__(self, modular_service: ModularService, - rule_service: RuleService): - self._modular_service = modular_service - self._rule_service = rule_service - self._content = '' # buffer for response - - @classmethod - def build(cls) -> 'ParentsHandler': - return cls( - modular_service=SERVICE_PROVIDER.modular_service(), - rule_service=SERVICE_PROVIDER.rule_service() - ) - - @property - def ps(self) -> ParentService: - return self._modular_service.modular_client.parent_service() - - def define_action_mapping(self) -> dict: - return { - '/parents': { - HTTPMethod.POST: self.post, - HTTPMethod.GET: self.list - }, - '/parents/{parent_id}': { - HTTPMethod.GET: self.get, - HTTPMethod.DELETE: self.delete, - HTTPMethod.PATCH: self.patch - }, - } - - def _is_allowed_to_link_custodian(self, tenant: Tenant, parent: Parent, - application: Application) -> bool: - if parent.type != ParentType.CUSTODIAN: - self._content = f'parent type must be {ParentType.CUSTODIAN} ' \ - f'for {ParentType.CUSTODIAN} linkage' - return False - if application.type != ApplicationType.CUSTODIAN: - self._content = f'application type must be ' \ - f'{ApplicationType.CUSTODIAN} ' \ - f'for {ParentType.CUSTODIAN} linkage' - return False - meta = ParentMeta.from_dict(parent.meta.as_dict()) - if meta.scope != SPECIFIC_TENANT_SCOPE: - self._content = f'parent scope must be {SPECIFIC_TENANT_SCOPE}' - return False - if tenant.cloud not in meta.clouds: - self._content = 'tenant cloud mu be in a list of parent clouds' - return False - return True - - def _is_allowed_to_link_custodian_licenses(self, tenant: Tenant, - parent: Parent, - application: Application - ) -> bool: - if parent.type != ParentType.CUSTODIAN_LICENSES: - self._content = f'parent type must be ' \ - f'{ParentType.CUSTODIAN_LICENSES} ' \ - f'for {ParentType.CUSTODIAN_LICENSES} linkage' - return False - if application.type != ApplicationType.CUSTODIAN_LICENSES: - self._content = f'application type must be ' \ - f'{ApplicationType.CUSTODIAN_LICENSES} ' \ - f'for {ApplicationType.CUSTODIAN_LICENSES} linkage' - return False - meta = ParentMeta.from_dict(parent.meta.as_dict()) - if meta.scope != SPECIFIC_TENANT_SCOPE: - self._content = f'parent scope must be {SPECIFIC_TENANT_SCOPE}' - return False - if tenant.cloud not in meta.clouds: - self._content = 'tenant cloud mu be in a list of parent clouds' - return False - return True - - def _is_allowed_to_link_siem_defect_dojo(self, tenant: Tenant, - parent: Parent, - application: Application - ) -> bool: - if parent.type != ParentType.SIEM_DEFECT_DOJO: - self._content = f'parent type must be ' \ - f'{ParentType.SIEM_DEFECT_DOJO} ' \ - f'for {ParentType.SIEM_DEFECT_DOJO} linkage' - return False - if application.type != ApplicationType.DEFECT_DOJO: - self._content = f'application type must be ' \ - f'{ApplicationType.DEFECT_DOJO} ' \ - f'for {ParentType.SIEM_DEFECT_DOJO} linkage' - return False - meta = ParentMeta.from_dict(parent.meta.as_dict()) - if meta.scope != SPECIFIC_TENANT_SCOPE: - self._content = f'parent scope must be {SPECIFIC_TENANT_SCOPE}' - return False - if tenant.cloud not in meta.clouds: - self._content = 'tenant cloud mu be in a list of parent clouds' - return False - return True - - def _is_allowed_to_link_custodian_access(self, tenant: Tenant, - parent: Parent, - application: Application - ) -> bool: - if parent.type != ParentType.CUSTODIAN_ACCESS: - self._content = f'parent type must be ' \ - f'{ParentType.CUSTODIAN_ACCESS} ' \ - f'for {ParentType.CUSTODIAN_ACCESS} linkage' - return False - if application.type not in CLOUD_TO_APP_TYPE.get(tenant.cloud): - self._content = f'application must provide credentials ' \ - f'for {tenant.cloud} cloud' - return False - return True - - def _assert_allowed_to_link(self, tenant: Tenant, parent: Parent, - type_: str): - """ - Tells whether it's allowed to set the parent's ID to the - tenant's parent_map by key type_ - Tenant.parent_map -> Parent.type -> Application.type - CUSTODIAN -> CUSTODIAN -> CUSTODIAN - CUSTODIAN_LICENSES -> CUSTODIAN_LICENSES -> CUSTODIAN_LICENSES - SIEM_DEFECT_DOJO -> SIEM_DEFECT_DOJO -> DEFECT_DOJO - CUSTODIAN_ACCESS -> CUSTODIAN_ACCESS -> [creds application for tenant's cloud] # noqa - For the first three rows scope must be SPECIFIC_TENANT, cloud be valid - :param tenant: - :param parent: - :param type_: - :return: - """ - application = self._modular_service.get_parent_application(parent) - if not application: - return build_response( - code=HTTPStatus.BAD_REQUEST, - content='Parent`s application not found' - ) - type_method = { - ParentType.CUSTODIAN.value: self._is_allowed_to_link_custodian, - ParentType.CUSTODIAN_LICENSES.value: self._is_allowed_to_link_custodian_licenses, - # noqa - ParentType.SIEM_DEFECT_DOJO.value: self._is_allowed_to_link_siem_defect_dojo, - # noqa - ParentType.CUSTODIAN_ACCESS.value: self._is_allowed_to_link_custodian_access - # noqa - } - method = type_method[type_] # type_ is validated before - if not method(tenant, parent, application): - _LOG.warning(f'Cannot link: {self._content}') - return build_response( - code=HTTPStatus.BAD_REQUEST, - content=f'Cannot link: {self._content}' - ) - - def list(self, event: dict) -> dict: - customer = event.get(CUSTOMER_ATTR) - items = self._modular_service.get_customer_bound_parents( - customer=customer, - parent_type=ParentType.list(), - is_deleted=False - ) - return build_response( - content=(self.ps.get_dto(parent) for parent in items) - ) - - def get(self, event: dict): - customer = event.get(CUSTOMER_ATTR) - parent_id = event.get(PARENT_ID_ATTR) - item = self.get_parent(parent_id, customer) - if not item: - return build_response(content=[]) - return build_response(content=self.ps.get_dto(item)) - - def delete(self, event: dict): - customer = event.get(CUSTOMER_ATTR) - parent_id = event.get(PARENT_ID_ATTR) - item = self.get_parent(parent_id, customer) - if not item: - return build_response( - code=HTTPStatus.NOT_FOUND, - content=f'Parent with id ' - f'{parent_id} within your customer not found' - ) - self._modular_service.modular_client.parent_service().mark_deleted( - item) - return build_response(code=HTTPStatus.NO_CONTENT) - - def get_parent(self, parent_id: str, - customer: Optional[str]) -> Optional[Parent]: - item = self._modular_service.get_parent(parent_id) - if not item or not ParentType.has( - item.type) or customer and item.customer_id != customer: - return - return item - - @cached_property - def parent_type_to_application_type_map(self) -> Dict[str, Set[str]]: - return { - ParentType.SIEM_DEFECT_DOJO.value: { - ApplicationType.DEFECT_DOJO.value}, - ParentType.CUSTODIAN_LICENSES.value: { - ApplicationType.CUSTODIAN_LICENSES.value}, - ParentType.CUSTODIAN.value: {ApplicationType.CUSTODIAN.value}, - ParentType.CUSTODIAN_ACCESS.value: set( - chain.from_iterable(CLOUD_TO_APP_TYPE.values())), - # any creds application - } - - @validate_kwargs - def post(self, event: ParentPostModel) -> dict: - app = self._modular_service.get_application(event.application_id) - _required_types = self.parent_type_to_application_type_map.get( - event.type) - if not app or app.is_deleted or app.customer_id != event.customer or \ - app.type not in _required_types: - return build_response( - code=HTTPStatus.NOT_FOUND, - content=f'Application {event.application_id} with type: ' - f'{", ".join(_required_types)} not found in ' - f'customer {event.customer}' - ) - tenant = None - if event.tenant_name: - tenant = self._modular_service.get_tenant(event.tenant_name) - self._modular_service.assert_tenant_valid(tenant, event.customer) - - meta = {} - if event.type == ParentType.CUSTODIAN_LICENSES: - _LOG.debug(f'Parent is {event.type}. Resolving rule ids') - clouds = set() - if tenant: - clouds.add(adjust_cloud(tenant.cloud)) - elif event.cloud: # scope all - clouds.add(adjust_cloud(event.cloud)) - meta = ParentMeta( - rules_to_exclude=list( - self._rule_service.resolved_names( - names=event.rules_to_exclude, - clouds=clouds - ) - ) - ).dict() - if event.scope == ParentScope.ALL: - parent = self.ps.create_all_scope( - application_id=event.application_id, - customer_id=event.customer, - type_=event.type, - description=event.description, - meta=meta, - cloud=event.cloud - ) - else: - parent = self.ps.create_tenant_scope( - application_id=event.application_id, - customer_id=event.customer, - type_=event.type, - tenant_name=event.tenant_name, - disabled=event.scope == ParentScope.DISABLED, - description=event.description, - meta=meta - ) - self._modular_service.save(parent) - - return build_response( - content=self.ps.get_dto(parent) - ) - - @validate_kwargs - def patch(self, event: ParentPatchModel) -> dict: - - parent = self.get_parent(event.parent_id, event.customer) - if not parent: - return build_response( - code=HTTPStatus.NOT_FOUND, - content=f'Parent with id {event.parent_id} not found' - ) - actions = [] - if event.description: - actions.append(Parent.description.set(event.description)) - if event.application_id: - app = self._modular_service.get_application(event.application_id) - _required_types = self.parent_type_to_application_type_map.get( - parent.type) - if not app or app.is_deleted or app.customer_id != event.customer or app.type not in _required_types: # noqa - return build_response( - code=HTTPStatus.NOT_FOUND, - content=f'Application {event.application_id} with type: ' - f'{", ".join(_required_types)} not found in ' - f'customer {event.customer}' - ) - actions.append(Parent.application_id.set(event.application_id)) - if parent.type == ParentType.CUSTODIAN_ACCESS.value: - _LOG.debug('Custodian access parent has no meta. All possible ' - 'updates are done. Saving') - parent.update(actions) - return build_response( - content=self.ps.get_dto(parent) - ) - - # updating meta for CUSTODIAN, CUSTODIAN_LICENSES, SIEM_DEFECT_DOJO - if parent.type == ParentType.CUSTODIAN_LICENSES.value: - meta = ParentMeta.from_dict(parent.meta.as_dict()) - resolved_to_exclude = set(self._rule_service.resolved_names( - names=event.rules_to_exclude, - )) - existing_rules_to_exclude = set(meta.rules_to_exclude) - existing_rules_to_exclude -= event.rules_to_include - existing_rules_to_exclude |= resolved_to_exclude - meta.rules_to_exclude = list(existing_rules_to_exclude) - actions.append(Parent.meta.set(meta.dict())) - parent.update(actions) - return build_response( - content=self._modular_service.get_dto(parent) - ) diff --git a/src/handlers/platforms_handler.py b/src/handlers/platforms_handler.py index ad056506d..9ce451d34 100644 --- a/src/handlers/platforms_handler.py +++ b/src/handlers/platforms_handler.py @@ -1,126 +1,84 @@ -from enum import Enum +from functools import cached_property from http import HTTPStatus -from typing import Iterator, Generator, Tuple, Optional -from modular_sdk.commons.constants import ParentScope, \ - ApplicationType, ParentType -from modular_sdk.models.application import Application -from modular_sdk.models.parent import Parent +from modular_sdk.commons.constants import ApplicationType, ParentScope, \ + ParentType +from modular_sdk.modular import Modular from modular_sdk.services.application_service import ApplicationService -from modular_sdk.services.impl.maestro_credentials_service import \ - K8SServiceAccountApplicationMeta, K8SServiceAccountApplicationSecret -from modular_sdk.services.parent_service import ParentService +from modular_sdk.services.tenant_service import TenantService -from handlers.abstracts.abstract_handler import AbstractHandler -from helpers import build_response -from helpers.constants import HTTPMethod, PlatformType +from handlers import AbstractHandler, Mapping +from helpers.constants import CustodianEndpoint, HTTPMethod +from helpers.lambda_response import ResponseFactory, build_response from helpers.log_helper import get_logger from services import SP -from services.modular_service import ModularService -from validators.request_validation import PlatformK8sNativePost, \ - PlatformK8sDelete, PlatformK8sEksPost, PreparedEvent, PlatformK8sQuery +from services.rbac_service import TenantsAccessPayload +from services import modular_helpers +from services.abs_lambda import ProcessedEvent +from services.platform_service import K8STokenKubeconfig, Platform, \ + PlatformService +from validators.swagger_request_models import ( + BaseModel, + PlatformK8SPostModel, + PlatformK8sQueryModel, +) from validators.utils import validate_kwargs _LOG = get_logger(__name__) class PlatformsHandler(AbstractHandler): - def __init__(self, modular_service: ModularService): - self._modular_service = modular_service + def __init__(self, modular_client: Modular, + platform_service: PlatformService): + self._modular_client = modular_client + self._ps = platform_service @classmethod def build(cls) -> 'PlatformsHandler': return cls( - modular_service=SP.modular_service(), + modular_client=SP.modular_client, + platform_service=SP.platform_service ) @property - def ps(self) -> ParentService: - return self._modular_service.modular_client.parent_service() + def aps(self) -> ApplicationService: + return self._modular_client.application_service() @property - def aps(self) -> ApplicationService: - return self._modular_service.modular_client.application_service() + def ts(self) -> TenantService: + return self._modular_client.tenant_service() - def define_action_mapping(self) -> dict: + @cached_property + def mapping(self) -> Mapping: return { - '/platforms/k8s': { - HTTPMethod.GET: self.list_k8s - }, - '/platforms/k8s/native': { - HTTPMethod.POST: self.post_k8s_native, - }, - '/platforms/k8s/eks': { - HTTPMethod.POST: self.post_k8s_eks, - }, - '/platforms/k8s/native/{id}': { - HTTPMethod.DELETE: self.delete_k8s_native + CustodianEndpoint.PLATFORMS_K8S: { + HTTPMethod.POST: self.post_k8s, + HTTPMethod.GET: self.list_k8s, }, - '/platforms/k8s/eks/{id}': { - HTTPMethod.DELETE: self.delete_k8s_eks + CustodianEndpoint.PLATFORMS_K8S_ID: { + HTTPMethod.GET: self.get_k8s, + HTTPMethod.DELETE: self.delete_k8s } } - def dto(self, parent: Parent, - application: Optional[Application] = None) -> dict: - native = self.get_type(parent) == PlatformType.NATIVE - data = { - 'id': parent.parent_id, - 'name': parent.meta.as_dict().get('name'), - 'tenant_name': parent.tenant_name, - 'has_token': bool(application and bool(application.secret) and native), - 'type': PlatformType.NATIVE if native else PlatformType.EKS, - 'description': parent.description - } - if not native: - data['region'] = parent.meta.as_dict().get('region') - else: - data['endpoint'] = application.meta.as_dict().get('endpoint') - return data - @validate_kwargs - def post_k8s_eks(self, event: PlatformK8sEksPost): - tenant_item = self._modular_service.get_tenant(event.tenant_name) - self._modular_service.assert_tenant_valid(tenant_item, event.customer) - application = self._modular_service.get_application( - event.application_id) - if not application or application.type not in ( - ApplicationType.AWS_ROLE.value, - ApplicationType.AWS_CREDENTIALS.value): - return build_response( - code=HTTPStatus.NOT_FOUND, - content=f'Application with AWS credentials and ID ' - f'{event.application_id} not found' - ) - parent = self.ps._create( - customer_id=tenant_item.customer_name, - application_id=application.application_id, - type_=ParentType.PLATFORM_K8S.value, - description=event.description or 'Custodian created eks cluster', - meta={'name': event.name, 'region': event.region, - 'type': PlatformType.EKS.value}, - scope=ParentScope.SPECIFIC, - tenant_name=tenant_item.name - ) - self.ps.save(parent) - return build_response(content=self.dto(parent, application)) - - @validate_kwargs - def post_k8s_native(self, event: PlatformK8sNativePost) -> dict: - tenant_item = self._modular_service.get_tenant(event.tenant_name) - self._modular_service.assert_tenant_valid(tenant_item, event.customer) - application = self.aps.create( - customer_id=tenant_item.customer_name, - type=ApplicationType.K8S_SERVICE_ACCOUNT.value, + def post_k8s(self, event: PlatformK8SPostModel, _pe: ProcessedEvent, + _tap: TenantsAccessPayload): + tenant = self.ts.get(event.tenant_name) + if tenant and not _tap.is_allowed_for(tenant.name): + tenant = None + modular_helpers.assert_tenant_valid(tenant, event.customer) + + application = self.aps.build( + customer_id=tenant.customer_name, + type=ApplicationType.K8S_KUBE_CONFIG.value, description='Custodian auto created k8s application', - meta=K8SServiceAccountApplicationMeta( - endpoint=str(event.endpoint), - ca=event.certificate_authority - ).dict() + created_by=_pe['cognito_user_id'], + meta={} ) - if event.token: - _LOG.info('K8s service account token was given. Saving to app') - cl = self._modular_service.modular_client.assume_role_ssm_service() + if event.endpoint: + _LOG.info('K8s endpoint and ca were given creating kubeconfig') + cl = self._modular_client.assume_role_ssm_service() secret_name = cl.safe_name( name=application.customer_id, prefix='m3.custodian.k8s', @@ -128,53 +86,53 @@ def post_k8s_native(self, event: PlatformK8sNativePost) -> dict: ) secret = cl.put_parameter( name=secret_name, - value=K8SServiceAccountApplicationSecret(event.token).dict() + value=K8STokenKubeconfig( + endpoint=str(event.endpoint), + ca=event.certificate_authority, + token=event.token + ).build_config() ) - if not secret: - _LOG.warning('Something went wrong trying to same token ' - 'to ssm. Keeping application.secret empty') application.secret = secret - parent = self.ps._create( - customer_id=tenant_item.customer_name, - application_id=application.application_id, - type_=ParentType.PLATFORM_K8S.value, - description=event.description or 'Custodian created native k8s', - meta={'name': event.name, 'type': PlatformType.NATIVE.value}, - scope=ParentScope.SPECIFIC, - tenant_name=tenant_item.name + platform = self._ps.create( + tenant=tenant, + application=application, + name=event.name, + type_=event.type, + created_by=_pe['cognito_user_id'], + region=event.region.value if event.region else None, + description=event.description ) - self.ps.save(parent) - self.aps.save(application) - return build_response(content=self.dto(parent, application)) + self._ps.save(platform) + return build_response(content=self._ps.dto(platform)) - def _with_applications(self, it: Iterator[Parent] - ) -> Generator[Tuple[Parent, Optional[Application]], None, None]: - """ - Yields parent and its application. Skips applications for EKS parents - because in such situation they don't contain data - :param it: - :return: - """ - _cache = {} - for parent in it: - if self.get_type(parent) == PlatformType.EKS: - yield parent, None - continue - aid = parent.application_id - if aid in _cache: - yield parent, _cache[aid] - continue - application = self.aps.get_application_by_id(aid) - _cache[aid] = application - yield parent, application + @validate_kwargs + def delete_k8s(self, event: BaseModel, platform_id: str): + platform = self._ps.get_nullable(hash_key=platform_id) + if not platform: + return build_response( + code=HTTPStatus.NOT_FOUND, + content=self._ps.not_found_message(platform_id) + ) + self._ps.fetch_application(platform) + if platform.application.secret: + cl = self._modular_client.assume_role_ssm_service() + cl.delete_parameter(platform.application.secret) + self._ps.delete(platform) + return build_response(code=HTTPStatus.NO_CONTENT) - @staticmethod - def get_type(platform: Parent) -> PlatformType: - return PlatformType[platform.meta.as_dict().get('type')] + @validate_kwargs + def get_k8s(self, event: BaseModel, platform_id: str): + platform = self._ps.get_nullable(hash_key=platform_id) + if not platform: + raise ResponseFactory(HTTPStatus.NOT_FOUND).message( + self._ps.not_found_message(platform_id) + ).exc() + return build_response(content=self._ps.dto(platform)) @validate_kwargs - def list_k8s(self, event: PlatformK8sQuery) -> dict: - it = self.ps.query_by_scope_index( + def list_k8s(self, event: PlatformK8sQueryModel): + ps = self._modular_client.parent_service() + it = ps.query_by_scope_index( customer_id=event.customer, tenant_or_cloud=event.tenant_name, scope=ParentScope.SPECIFIC, @@ -182,37 +140,5 @@ def list_k8s(self, event: PlatformK8sQuery) -> dict: is_deleted=False ) return build_response(content=( - self.dto(pair[0], pair[1]) for pair in self._with_applications(it) + self._ps.dto(item) for item in map(Platform, it) )) - - @validate_kwargs - def delete_k8s_native(self, event: PlatformK8sDelete) -> dict: - parent = self.ps.get_parent_by_id(event.id) - if not parent or parent.is_deleted or self.get_type( - parent) != PlatformType.NATIVE: - _LOG.debug(f'Parent {parent} not found or already deleted') - return build_response( - code=HTTPStatus.NOT_FOUND, - content=f'Native platform {event.id} not found' - ) - - application = self.aps.get_application_by_id(parent.application_id) - if application.secret: - cl = self._modular_service.modular_client.assume_role_ssm_service() - cl.delete_parameter(application.secret) - self.ps.mark_deleted(parent) - self.aps.mark_deleted(application) - return build_response(code=HTTPStatus.NO_CONTENT) - - @validate_kwargs - def delete_k8s_eks(self, event: PlatformK8sDelete) -> dict: - parent = self.ps.get_parent_by_id(event.id) - if not parent or parent.is_deleted or self.get_type( - parent) != PlatformType.EKS: - _LOG.debug(f'Parent {parent} not found or already deleted') - return build_response( - code=HTTPStatus.NOT_FOUND, - content=f'EKS platform {event.id} not found' - ) - self.ps.mark_deleted(parent) - return build_response(code=HTTPStatus.NO_CONTENT) diff --git a/src/handlers/policy_handler.py b/src/handlers/policy_handler.py index 69055c050..97d343ed7 100644 --- a/src/handlers/policy_handler.py +++ b/src/handlers/policy_handler.py @@ -1,121 +1,134 @@ +from functools import cached_property from http import HTTPStatus -from typing import Iterable -from helpers import build_response -from helpers.constants import CUSTOMER_ATTR, NAME_ATTR, \ - PERMISSIONS_ATTR, HTTPMethod, \ - PERMISSIONS_TO_ATTACH, PERMISSIONS_TO_DETACH, \ - PARAM_USER_CUSTOMER -from helpers.log_helper import get_logger -from helpers.system_customer import SYSTEM_CUSTOMER -from services.rbac.access_control_service import AccessControlService -from services.rbac.iam_cache_service import CachedIamService +from handlers import AbstractHandler, Mapping +from helpers import NextToken +from helpers.constants import CustodianEndpoint, HTTPMethod +from helpers.lambda_response import ResponseFactory, build_response +from services import SP +from services.rbac_service import PolicyService +from validators.swagger_request_models import ( + BaseModel, + BasePaginationModel, + PolicyPatchModel, + PolicyPostModel, +) +from validators.utils import validate_kwargs -_LOG = get_logger(__name__) - -class PolicyHandler: +class PolicyHandler(AbstractHandler): """ Manage Policy API """ - def __init__(self, cached_iam_service: CachedIamService, - access_control_service: AccessControlService): - self._iam_service = cached_iam_service - self._access_control_service = access_control_service + def __init__(self, policy_service: PolicyService): + self._policy_service = policy_service + + @classmethod + def build(cls): + return cls(policy_service=SP.policy_service) - def define_action_mapping(self): + @cached_property + def mapping(self) -> Mapping: return { - '/policies': { - HTTPMethod.GET: self.get_policy, + CustodianEndpoint.POLICIES: { HTTPMethod.POST: self.create_policy, + HTTPMethod.GET: self.list_policies + }, + CustodianEndpoint.POLICIES_NAME: { + HTTPMethod.GET: self.get_policy, HTTPMethod.PATCH: self.update_policy, HTTPMethod.DELETE: self.delete_policy, - }, - '/policies/cache': { - HTTPMethod.DELETE: self.delete_policy_cache - }, + } } - def get_policy(self, event: dict): - name = event.get(NAME_ATTR) - customer = event.get(CUSTOMER_ATTR) - it = self._iam_service.list_policies( - customer=customer, name=name + @validate_kwargs + def get_policy(self, event: BaseModel, name: str): + item = self._policy_service.get_nullable(event.customer_id, name) + if not item: + raise ResponseFactory(HTTPStatus.NOT_FOUND).message( + self._policy_service.not_found_message(name) + ).exc() + return build_response(content=self._policy_service.dto(item)) + + @validate_kwargs + def list_policies(self, event: BasePaginationModel): + cursor = self._policy_service.query( + customer=event.customer_id, + limit=event.limit, + last_evaluated_key=NextToken.deserialize(event.next_token).value ) - return build_response(content=( - self._iam_service.get_dto(entity) for entity in it - )) - - def create_policy(self, event): - name = event.get(NAME_ATTR) - customer = event.get(CUSTOMER_ATTR) or SYSTEM_CUSTOMER - permissions: set = event.get(PERMISSIONS_ATTR) - self.ensure_permissions_allowed(event.get(PARAM_USER_CUSTOMER), - permissions) - existing = self._iam_service.get_policy(customer, name) + items = tuple(cursor) + return ResponseFactory().items( + it=map(self._policy_service.dto, items), + next_token=NextToken(cursor.last_evaluated_key) + ).build() + + @validate_kwargs + def create_policy(self, event: PolicyPostModel): + customer = event.customer_id + name = event.name + existing = self._policy_service.get_nullable(customer, name) if existing: return build_response( code=HTTPStatus.CONFLICT, content=f'Policy with name {name} already exists' ) - policy = self._iam_service.create_policy({ - 'name': name, - 'customer': customer, - 'permissions': list(permissions) - }) - self._iam_service.save(policy) - return build_response(content=self._iam_service.get_dto(policy)) - - def ensure_permissions_allowed(self, user_customer: str, - permissions: Iterable[str]): - not_allowed = [] - for permission in permissions: - if not self._access_control_service.is_permission_allowed( - customer=user_customer, permission=permission): - not_allowed.append(permission) - if not_allowed: - return build_response( - code=HTTPStatus.BAD_REQUEST, - content=f'Such permissions not allowed: {", ".join(not_allowed)}' - ) + policy = self._policy_service.create( + customer=customer, + name=name, + description=event.description, + permissions=[p.value for p in event.permissions], + tenants=tuple(event.tenants), + effect=event.effect + ) + self._policy_service.save(policy) + return build_response(content=self._policy_service.dto(policy), + code=HTTPStatus.CREATED) - def update_policy(self, event: dict): - name = event.get(NAME_ATTR) - customer = event.get(CUSTOMER_ATTR) or SYSTEM_CUSTOMER - to_attach: set = event.get(PERMISSIONS_TO_ATTACH) - to_detach: set = event.get(PERMISSIONS_TO_DETACH) - self.ensure_permissions_allowed(event.get(PARAM_USER_CUSTOMER), - to_attach) - policy = self._iam_service.get_policy(customer, name) + @validate_kwargs + def update_policy(self, event: PolicyPatchModel, name: str): + customer = event.customer_id + policy = self._policy_service.get_nullable(customer, name) if not policy: return build_response( code=HTTPStatus.NOT_FOUND, content=f'Policy with name {name} already not found' ) - permission = set(policy.permissions or []) + to_attach = {p.value for p in event.permissions_to_attach} + to_detach = {p.value for p in event.permissions_to_detach} + permission = set(policy.permissions or ()) permission -= to_detach permission |= to_attach - policy.permissions = list(permission) - self._iam_service.save(policy) + policy.permissions = sorted(permission) + + tenants = set(policy.tenants or ()) + tenants -= event.tenants_to_remove + tenants |= event.tenants_to_add + policy.tenants = sorted(tenants) + + if event.effect: + policy.effect = event.effect.value + if event.description: + policy.description = event.description + + self._policy_service.save(policy) return build_response( code=HTTPStatus.OK, - content=self._iam_service.get_dto(policy) + content=self._policy_service.dto(policy) ) - def delete_policy(self, event): - name = event.get(NAME_ATTR) - customer = event.get(CUSTOMER_ATTR) or SYSTEM_CUSTOMER - policy = self._iam_service.get_policy(customer, name) + @validate_kwargs + def delete_policy(self, event: BaseModel, name: str): + policy = self._policy_service.get_nullable(event.customer_id, name) if policy: - self._iam_service.delete(policy) - return build_response( - content=f'Policy:{name!r} was successfully deleted' - ) + self._policy_service.delete(policy) + return build_response(code=HTTPStatus.NO_CONTENT) - def delete_policy_cache(self, event): - name = event.get(NAME_ATTR) - customer = event.get(CUSTOMER_ATTR) or SYSTEM_CUSTOMER - self._iam_service.clean_policy_cache(customer=customer, name=name) - return build_response(content='Cleaned') + # @validate_kwargs + # def delete_policy_cache(self, event: PolicyCacheDeleteModel): + # name = event.name + # customer = event.customer + # self._iam_service.clean_policy_cache(customer=customer, name=name) + # return build_response(code=HTTPStatus.NO_CONTENT) diff --git a/src/handlers/push_handler.py b/src/handlers/push_handler.py index 636632f63..09775b32e 100644 --- a/src/handlers/push_handler.py +++ b/src/handlers/push_handler.py @@ -1,419 +1,281 @@ -from datetime import datetime +from functools import cached_property from http import HTTPStatus -from itertools import chain -from pathlib import PurePosixPath -from typing import Optional, Dict, List, Iterable -import requests -from modular_sdk.commons.constants import \ - TENANT_PARENT_MAP_SIEM_DEFECT_DOJO_TYPE, ParentType -from modular_sdk.models.tenant import Tenant -from modular_sdk.services.impl.maestro_credentials_service import \ - DefectDojoApplicationMeta, DefectDojoApplicationSecret - -from handlers.base_handler import \ - BaseReportHandler, Source, AmbiguousJobService, \ - ModularService, ReportService, \ - SourceReportDerivation -from helpers import build_response -from helpers.constants import CUSTOMER_ATTR, TENANT_ATTR, TENANTS_ATTR, \ - HTTPMethod, ID_ATTR, TYPE_ATTR, START_ISO_ATTR, END_ISO_ATTR, \ - JOB_ID_ATTR -from helpers.log_helper import get_logger -from integrations.defect_dojo_adapter import DefectDojoAdapter -from integrations.security_hub_adapter import SecurityHubAdapter -from models.modular.parents import DefectDojoParentMeta -from services.clients.ssm import SSMClient -from services.report_service import DETAILED_REPORT_FILE +from modular_sdk.modular import Modular from modular_sdk.services.parent_service import ParentService -_LOG = get_logger(__name__) - -TENANTS_TO_SKIP = 'tenants_to_skip' - - -class SiemPushHandler(BaseReportHandler): - _source_report_derivation_attr: Optional[SourceReportDerivation] +from handlers import AbstractHandler, Mapping +from helpers.constants import CustodianEndpoint, HTTPMethod, JobState +from helpers.lambda_response import ResponseFactory, build_response +from helpers.time_helper import utc_datetime +from services import SP +from services import modular_helpers +from services.ambiguous_job_service import AmbiguousJob, AmbiguousJobService +from services.clients.dojo_client import DojoV2Client +from services.defect_dojo_service import ( + DefectDojoConfiguration, + DefectDojoParentMeta, + DefectDojoService, +) +from services.integration_service import IntegrationService +from services.platform_service import PlatformService +from services.report_convertors import ShardCollectionDojoConvertor +from services.report_service import ReportService +from services.sharding import ShardsCollection +from validators.swagger_request_models import ( + ReportPushByJobIdModel, + ReportPushMultipleModel, +) +from validators.utils import validate_kwargs + + +class SiemPushHandler(AbstractHandler): def __init__(self, ambiguous_job_service: AmbiguousJobService, - modular_service: ModularService, report_service: ReportService, - ssm_client: SSMClient): - super().__init__( - ambiguous_job_service=ambiguous_job_service, - modular_service=modular_service, - report_service=report_service - ) - self._ssm_client = ssm_client - - def _reset(self): - super()._reset() - self._source_report_derivation_attr = None - - @property - def _source_report_derivation_function(self) -> SourceReportDerivation: - return self._source_report_derivation_attr - - @property - def ajs(self) -> AmbiguousJobService: - return self._ambiguous_job_service - - def _dojo_report_sourced_derivation(self, source: Source, **kwargs): - """ - Obtains dojo-compatible report, based on a given source, be it: - - manual Job - - event-driven Processing - :parameter source: Source=Union[Job, BatchResult] - :parameter kwargs: Dict - :parameter: List[Dict] - """ - ref = None - ajs = self._ambiguous_job_service - rs = self._report_service - path = rs.derive_job_object_path( - job_id=self._ambiguous_job_service.get_attribute(source, ID_ATTR), - typ=DETAILED_REPORT_FILE - ) - - _typ = ajs.get_type(item=source) - _uid = ajs.get_attribute(item=source, attr=ID_ATTR) - _tn = ajs.get_attribute(item=source, attr=TENANT_ATTR) - _cn = ajs.get_attribute(item=source, attr=CUSTOMER_ATTR) - head = f'{_typ.capitalize()} Job:\'{_uid}\' of \'{_tn}\' tenant' - - if _tn in kwargs.get(TENANTS_TO_SKIP, []): - _LOG.warning(head + ' is set to skip, due to adapter issues.') - return ref - - _LOG.info(head + ' pulling formatted, detailed report.') - detailed_report = rs.pull_job_report(path=path) - if detailed_report: - _LOG.info(head + ' preparing dojo compatible report of policies.') - dojo_policy_reports = rs.formatted_to_dojo_policy_report( - detailed_report=detailed_report - ) - if dojo_policy_reports: - ref = dojo_policy_reports - - return ref - - def _download_finding_for_one_job(self, job_id: str, - reports_bucket_name: str - ) -> Iterable[Dict]: - findings_key = str(PurePosixPath(job_id, 'findings')) - s3 = self._report_service.s3_client - keys = ( - k for k in s3.list_dir(reports_bucket_name, findings_key) - if k.endswith('.json') or k.endswith('.json.gz') - ) - return chain.from_iterable( - pair[1] for pair in s3.get_json_batch(reports_bucket_name, keys) + report_service: ReportService, + modular_client: Modular, + platform_service: PlatformService, + integration_service: IntegrationService, + defect_dojo_service: DefectDojoService): + self._ambiguous_job_service = ambiguous_job_service + self._rs = report_service + self._modular_client = modular_client + self._platform_service = platform_service + self._integration_service = integration_service + self._dds = defect_dojo_service + + @classmethod + def build(cls) -> 'AbstractHandler': + return cls( + ambiguous_job_service=SP.ambiguous_job_service, + report_service=SP.report_service, + modular_client=SP.modular_client, + platform_service=SP.platform_service, + integration_service=SP.integration_service, + defect_dojo_service=SP.defect_dojo_service ) - def _download_findings(self, sources: List[Source], - reports_bucket_name: str) -> Dict[Source, List]: - gens = { - source: self._download_finding_for_one_job( - self.ajs.get_attribute(source, ID_ATTR), reports_bucket_name) - for source in sources - } - return {source: list(findings) for source, findings in gens.items()} - - def _attain_sources_to_push( - self, start_iso: datetime, end_iso: datetime, - customer: Optional[str] = None, - tenants: Optional[List[str]] = None, - cloud_ids: Optional[List[str]] = None, - typ: Optional[str] = None - ) -> Optional[List[Source]]: - - cloud_ids = cloud_ids or [] - ajs = self._ambiguous_job_service - - head = '' - - # Log-Header. - if tenants: - multiple = len(tenants) > 1 - bind = ', '.join(map("'{}'".format, tenants or [])) - if head: - bind = f', bound to {bind}' - head += f'{bind} tenant' - if multiple: - head += 's' - - if customer: - head = 'Tenants' if not head else head - head += f' of \'{customer}\' customer' - - typ_scope = f'{typ} type' if typ else 'all types' - time_scope = f'from {start_iso.isoformat()} till {end_iso.isoformat()}' - job_scope = f'job(s) of {typ_scope}, {time_scope}' - - # todo Responsibility chain - - _LOG.info(f'Obtaining {job_scope}, for {head or "tenants"}.') - head = head or 'Tenants' - typ_params_map = ajs.derive_typ_param_map( - typ=typ, tenants=tenants, - cloud_ids=cloud_ids - ) - sources = ajs.batch_list( - typ_params_map=typ_params_map, customer=customer, - start=start_iso, end=end_iso, sort=True - ) - if not sources: - message = f' - no source-data of {job_scope} could be derived.' - _LOG.warning(head + message) - self._code = HTTPStatus.NOT_FOUND - self._content = head + message - - return sources - - def define_action_mapping(self) -> dict: + @cached_property + def mapping(self) -> Mapping: return { - '/reports/push/dojo/{job_id}': { + CustodianEndpoint.REPORTS_PUSH_DOJO_JOB_ID: { HTTPMethod.POST: self.push_dojo_by_job_id }, - '/reports/push/security-hub/{job_id}': { - HTTPMethod.POST: self.push_security_hub_by_job_id - }, - '/reports/push/dojo': { + CustodianEndpoint.REPORTS_PUSH_DOJO: { HTTPMethod.POST: self.push_dojo_multiple_jobs }, - '/reports/push/security-hub': { - HTTPMethod.POST: self.push_security_hub_multiple_jobs - } } @property def ps(self) -> ParentService: - return self._modular_service.modular_client.parent_service() + return self._modular_client.parent_service() - def initialize_dojo_adapter(self, tenant: Tenant) -> DefectDojoAdapter: - _not_configured = lambda: build_response( - code=HTTPStatus.BAD_REQUEST, - content=f'Tenant {tenant.name} does not have dojo configuration' + def _push_dojo(self, client: DojoV2Client, + configuration: DefectDojoParentMeta, + job: AmbiguousJob, collection: ShardsCollection + ) -> tuple[HTTPStatus, str]: + """ + All data is provided, just push + :param client: + :param configuration: + :param job: + :param collection: + :return: return human-readable code and message + """ + convertor = ShardCollectionDojoConvertor.from_scan_type( + configuration.scan_type, + attachment=configuration.attachment, + ) + resp = client.import_scan( + scan_type=configuration.scan_type, + scan_date=utc_datetime(job.stopped_at), + product_type_name=configuration.product_type, + product_name=configuration.product, + engagement_name=configuration.engagement, + test_title=configuration.test, + data=convertor.convert(collection), + tags=self._integration_service.job_tags_dojo(job) ) - parent = self.ps.get_linked_parent_by_tenant( - tenant, ParentType.SIEM_DEFECT_DOJO + match getattr(resp, 'status_code', None): # handles None + case HTTPStatus.CREATED: + return HTTPStatus.OK, 'Pushed' + case HTTPStatus.FORBIDDEN: + return (HTTPStatus.FORBIDDEN, + 'Not enough permission to push to dojo') + case HTTPStatus.INTERNAL_SERVER_ERROR: + return (HTTPStatus.SERVICE_UNAVAILABLE, + 'Dojo failed with internal') + case _: + return (HTTPStatus.SERVICE_UNAVAILABLE, + 'Could not make request to dojo server') + + @validate_kwargs + def push_dojo_by_job_id(self, event: ReportPushByJobIdModel, job_id: str): + job = self._ambiguous_job_service.get_job( + job_id=job_id, + typ=event.type, + customer=event.customer ) - if not parent: - _LOG.debug('Parent does not exist') - return _not_configured() - parent_meta = DefectDojoParentMeta.from_dict(parent.meta.as_dict()) - application = self._modular_service.get_parent_application(parent) - if not application or not application.secret: - _LOG.debug('Application or application.secret do not exist') - return _not_configured() - raw_secret = self._modular_service.modular_client.assume_role_ssm_service().get_parameter(application.secret) - if not raw_secret or not isinstance(raw_secret, dict): - _LOG.debug(f'SSM Secret by name {application.secret} not found') - return _not_configured() - meta = DefectDojoApplicationMeta.from_dict(application.meta.as_dict()) - secret = DefectDojoApplicationSecret.from_dict(raw_secret) - try: - _LOG.info('Initializing dojo client') - return DefectDojoAdapter( - host=meta.url, - api_key=secret.api_key, - entities_mapping=parent_meta.entities_mapping, - display_all_fields=parent_meta.display_all_fields, - upload_files=parent_meta.upload_files, - resource_per_finding=parent_meta.resource_per_finding + if not job: + return build_response( + content='The request job not found', + code=HTTPStatus.NOT_FOUND ) - except requests.RequestException as e: + if not job.is_succeeded: return build_response( - code=HTTPStatus.BAD_REQUEST, - content=f'Could not init dojo client: {e}' + content='Job has not succeeded yet', + code=HTTPStatus.NOT_FOUND ) + tenant = self._modular_client.tenant_service().get(job.tenant_name) + modular_helpers.assert_tenant_valid(tenant, event.customer) - def push_dojo_by_job_id(self, event: dict) -> dict: - job_id = event[JOB_ID_ATTR] - customer = event.get(CUSTOMER_ATTR) - tenants = event.get(TENANTS_ATTR) or [] - item = self._attain_source( - uid=job_id, - customer=customer, - tenants=tenants + dojo, configuration = next( + self._integration_service.get_dojo_adapters(tenant), + (None, None) ) - if not item: - return self.response - # retrieve config - tenant_name = self.ajs.get_attribute(item, TENANT_ATTR) - tenant = self._attain_tenant( - name=tenant_name, customer=customer, active=True) - if not tenant: - return self.response - - adapter = self.initialize_dojo_adapter(tenant) - - report = self._dojo_report_sourced_derivation(item) - if not report: - return build_response(code=HTTPStatus.NOT_FOUND, - content='Could not retrieve job report') - adapter.add_entity( - job_id=self.ajs.get_attribute(item=item, attr=ID_ATTR), - started_at=self.ajs.get_attribute(item, 'started_at'), - stopped_at=self.ajs.get_attribute(item, 'stopped_at'), - tenant_display_name=self.ajs.get_attribute(item=item, - attr=TENANT_ATTR), - customer_display_name=self.ajs.get_attribute(item=item, - attr=CUSTOMER_ATTR), - policy_reports=report, - job_type=self.ajs.get_type(item=item) + if not dojo: + raise ResponseFactory(HTTPStatus.BAD_REQUEST).message( + f'Tenant {tenant.name} does not have linked dojo configuration' + ).exc() + + client = DojoV2Client( + url=dojo.url, + api_key=self._dds.get_api_key(dojo) ) - _LOG.info('Uploading entity to DOJO') - result = adapter.upload_all_entities()[0] # always one here - - return build_response(code=result['status'], content=result) - def push_security_hub_by_job_id(self, event: dict) -> dict: - job_id = event[JOB_ID_ATTR] - customer = event.get(CUSTOMER_ATTR) - tenants = event.get(TENANTS_ATTR) or [] - item = self._attain_source( - uid=job_id, - customer=customer, - tenants=tenants + platform = None + if job.is_platform_job: + platform = self._platform_service.get_nullable(job.platform_id) + if not platform: + return build_response( + content='Job platform not found', + code=HTTPStatus.NOT_FOUND + ) + collection = self._rs.platform_job_collection(platform, job.job) + collection.meta = self._rs.fetch_meta(platform) + else: + collection = self._rs.ambiguous_job_collection(tenant, job) + collection.meta = self._rs.fetch_meta(tenant) + collection.fetch_all() + + configuration = configuration.substitute_fields(job, platform) + code, message = self._push_dojo( + client=client, + configuration=configuration, + job=job, + collection=collection, ) - if not item: - return self.response - tenant_name = self.ajs.get_attribute(item, TENANT_ATTR) - tenant = self._attain_tenant( - name=tenant_name, customer=customer, active=True) - if not tenant: - return self.response - _not_configured = lambda: build_response( - code=HTTPStatus.BAD_REQUEST, - content=f'Tenant {tenant_name} does not have SH configuration' - ) - application = self._modular_service.get_application('mock') # AWS_ROLE - if not application: - return _not_configured() - mcs = self._modular_service.modular_client.maestro_credentials_service() - creds = mcs.get_by_application(application) - if not creds: - return build_response( - code=HTTPStatus.BAD_REQUEST, - content='Cannot get credentials to push to SH' - ) - findings = self._download_finding_for_one_job( - job_id=self.ajs.get_attribute(item, ID_ATTR), - reports_bucket_name=self._report_service.job_report_bucket - ) - adapter = SecurityHubAdapter( - aws_region=creds.AWS_DEFAULT_REGION, - product_arn='', - aws_access_key_id=creds.AWS_ACCESS_KEY_ID, - aws_secret_access_key=creds.AWS_SECRET_ACCESS_KEY, - aws_session_token=creds.AWS_SESSION_TOKEN, - aws_default_region=creds.AWS_DEFAULT_REGION - ) - adapter.add_entity( - job_id=self.ajs.get_attribute(item, ID_ATTR), - job_type=self.ajs.get_type(item=item), - findings=list(findings) - ) - _LOG.info('Uploading entity to Security Hub') - result = adapter.upload_all_entities()[0] # always one here + match code: + case HTTPStatus.OK: + return build_response(self.get_dto(job, dojo, configuration)) + case _: + return build_response( + content=message, + code=HTTPStatus.SERVICE_UNAVAILABLE, + ) + + @staticmethod + def get_dto(job: AmbiguousJob, dojo: DefectDojoConfiguration, + configuration: DefectDojoParentMeta, + error: str | None = None) -> dict: + data = { + 'job_id': job.id, + 'scan_type': configuration.scan_type, + 'product_type_name': configuration.product_type, + 'product_name': configuration.product, + 'engagement_name': configuration.engagement, + 'test_title': configuration.test, + 'attachment': configuration.attachment, + 'tenant_name': job.tenant_name, + 'dojo_integration_id': dojo.id, + 'success': not error, + } + if job.is_platform_job: + data['platform_id'] = job.platform_id + if error: + data['error'] = error + return data - return build_response(code=result['status'], content=result) + @validate_kwargs + def push_dojo_multiple_jobs(self, event: ReportPushMultipleModel): - def push_dojo_multiple_jobs(self, event: dict) -> dict: - tenant = event[TENANT_ATTR] - _type = event.get(TYPE_ATTR) - customer = event.get(CUSTOMER_ATTR) - start_iso: datetime = event.get(START_ISO_ATTR) - end_iso: datetime = event.get(END_ISO_ATTR) - sources = self._attain_sources_to_push( - start_iso=start_iso, end_iso=end_iso, - customer=customer, tenants=[tenant], - typ=_type - ) - if not sources: - return self.response + tenant = self._modular_client.tenant_service().get(event.tenant_name) + modular_helpers.assert_tenant_valid(tenant, event.customer) - tenant_item = self._attain_tenant( - name=tenant, customer=customer, active=True + dojo, configuration = next( + self._integration_service.get_dojo_adapters(tenant), + (None, None) ) - if not tenant_item: - return self.response - adapter = self.initialize_dojo_adapter(tenant_item) - self._source_report_derivation_attr = self._dojo_report_sourced_derivation - - source_to_reports: Dict[Source, List] = self._attain_source_report_map( - source_list=sources + if not dojo: + raise ResponseFactory(HTTPStatus.BAD_REQUEST).message( + f'Tenant {tenant.name} does not have linked dojo configuration' + ).exc() + client = DojoV2Client( + url=dojo.url, + api_key=self._dds.get_api_key(dojo) ) - if not source_to_reports: - return build_response( - code=HTTPStatus.NOT_FOUND, - content='No reports found' - ) - self._source_report_derivation_attr = None - - for source, reports in source_to_reports.items(): - adapter.add_entity( - job_id=self.ajs.get_attribute(item=source, attr=ID_ATTR), - started_at=self.ajs.get_attribute(source, 'started_at'), - stopped_at=self.ajs.get_attribute(source, 'stopped_at'), - tenant_display_name=tenant, - customer_display_name=self.ajs.get_attribute(item=source, - attr=CUSTOMER_ATTR), - policy_reports=reports, - job_type=self.ajs.get_type(item=source) - ) - self._content = adapter.upload_all_entities() - self._code = HTTPStatus.OK - return self.response - def push_security_hub_multiple_jobs(self, event: dict) -> dict: - tenant = event[TENANT_ATTR] - _type = event.get(TYPE_ATTR) - customer = event.get(CUSTOMER_ATTR) - start_iso: datetime = event.get(START_ISO_ATTR) - end_iso: datetime = event.get(END_ISO_ATTR) - sources = self._attain_sources_to_push( - start_iso=start_iso, end_iso=end_iso, - customer=customer, tenants=[tenant], - typ=_type + jobs = self._ambiguous_job_service.get_by_tenant_name( + tenant_name=tenant.name, + job_type=event.type, + status=JobState.SUCCEEDED, + start=event.start_iso, + end=event.end_iso ) - if not sources: - return self.response - tenant_item = self._attain_tenant( - name=tenant, customer=customer, active=True - ) - if not tenant_item: - return self.response - _not_configured = lambda: build_response( - code=HTTPStatus.BAD_REQUEST, - content=f'Tenant {tenant} does not have SH configuration' - ) - application = self._modular_service.get_application('mock') - if not application: - return _not_configured() - mcs = self._modular_service.modular_client.maestro_credentials_service() - creds = mcs.get_by_application(application) - if not creds: - return build_response( - code=HTTPStatus.BAD_REQUEST, - content='Cannot get credentials to push to SH' + tenant_meta = self._rs.fetch_meta(tenant) + responses = [] + platforms = {} # cache locally platform_id to platform and meta + for job in self._ambiguous_job_service.to_ambiguous(jobs): + platform = None + match job.is_platform_job: + case True: + # A bit of devilish logic because we need to handle both + # tenant and platforms in one endpoint. I think it will + # be split into two endpoints + pid = job.platform_id + if pid not in platforms: + platform = self._platform_service.get_nullable(pid) + meta = {} + if platform: + meta = self._rs.fetch_meta(platform) + platforms[pid] = ( + self._platform_service.get_nullable(pid), + meta + ) + platform, meta = platforms[pid] + if not platform: + continue + collection = self._rs.platform_job_collection(platform, + job.job) + collection.meta = meta + case _: # only False can be, but underscore for linter + collection = self._rs.ambiguous_job_collection(tenant, job) + collection.meta = tenant_meta + collection.fetch_all() + + _configuration = configuration.substitute_fields( + job=job, + platform=platform ) - adapter = SecurityHubAdapter( - aws_region=creds.AWS_DEFAULT_REGION, - product_arn='', # TODO from parent ? - aws_access_key_id=creds.AWS_ACCESS_KEY_ID, - aws_secret_access_key=creds.AWS_SECRET_ACCESS_KEY, - aws_session_token=creds.AWS_SESSION_TOKEN, - aws_default_region=creds.AWS_DEFAULT_REGION - ) - reports_bucket_name = self._report_service.job_report_bucket - source_findings = self._download_findings(sources, reports_bucket_name) - for source, findings in source_findings.items(): - adapter.add_entity( - job_id=self.ajs.get_attribute(item=source, attr=ID_ATTR), - job_type=self.ajs.get_type(item=source), - findings=findings + code, message = self._push_dojo( + client=client, + configuration=_configuration, + job=job, + collection=collection ) - self._content = adapter.upload_all_entities() - self._code = HTTPStatus.OK - return self.response + match code: + case HTTPStatus.OK: + resp = self.get_dto( + job=job, + dojo=dojo, + configuration=_configuration, + ) + case _: + resp = self.get_dto( + job=job, + dojo=dojo, + configuration=_configuration, + error=message + ) + responses.append(resp) + return build_response(responses) diff --git a/src/handlers/rabbitmq_handler.py b/src/handlers/rabbitmq_handler.py index de9264c27..3cabfc288 100644 --- a/src/handlers/rabbitmq_handler.py +++ b/src/handlers/rabbitmq_handler.py @@ -1,40 +1,48 @@ +from functools import cached_property from http import HTTPStatus from modular_sdk.commons.constants import ApplicationType -from modular_sdk.services.impl.maestro_credentials_service import \ - RabbitMQApplicationMeta, RabbitMQApplicationSecret +from modular_sdk.models.application import Application +from modular_sdk.services.application_service import ApplicationService +from modular_sdk.services.impl.maestro_credentials_service import ( + RabbitMQApplicationMeta, + RabbitMQApplicationSecret, +) -from handlers.abstracts.abstract_handler import AbstractHandler -from helpers import build_response -from helpers.constants import HTTPMethod, \ - CUSTOMER_ATTR, MAESTRO_USER_ATTR, RABBIT_EXCHANGE_ATTR, \ - REQUEST_QUEUE_ATTR, RESPONSE_QUEUE_ATTR, SDK_ACCESS_KEY_ATTR, \ - CONNECTION_URL_ATTR, SDK_SECRET_KEY_ATTR +from handlers import AbstractHandler, Mapping +from helpers.constants import CUSTOMER_ATTR, CustodianEndpoint, HTTPMethod +from helpers.lambda_response import ResponseFactory, build_response from helpers.log_helper import get_logger -from models.modular.application import Application -from services import SERVICE_PROVIDER -from services.modular_service import ModularService +from services import SP +from services.abs_lambda import ProcessedEvent from services.ssm_service import SSMService +from validators.swagger_request_models import ( + RabbitMQDeleteModel, + RabbitMQGetModel, + RabbitMQPostModel, +) +from validators.utils import validate_kwargs _LOG = get_logger(__name__) class RabbitMQHandler(AbstractHandler): - def __init__(self, modular_service: ModularService, + def __init__(self, application_service: ApplicationService, ssm_service: SSMService): - self._modular_service = modular_service + self._application_service = application_service self._ssm_service = ssm_service @classmethod def build(cls) -> 'RabbitMQHandler': return cls( - modular_service=SERVICE_PROVIDER.modular_service(), - ssm_service=SERVICE_PROVIDER.ssm_service() + application_service=SP.modular_client.application_service(), + ssm_service=SP.ssm_service ) - def define_action_mapping(self) -> dict: + @cached_property + def mapping(self) -> Mapping: return { - '/customers/rabbitmq': { + CustodianEndpoint.CUSTOMERS_RABBITMQ: { HTTPMethod.POST: self.post, HTTPMethod.GET: self.get, HTTPMethod.DELETE: self.delete @@ -53,82 +61,78 @@ def get_dto(application: Application) -> dict: **application.meta.as_dict() } - def post(self, event: dict) -> dict: - customer = event[CUSTOMER_ATTR] - item = next(self._modular_service.get_applications( + @validate_kwargs + def post(self, event: RabbitMQPostModel, _pe: ProcessedEvent): + customer = event.customer + item = next(self._application_service.list( customer=customer, - _type=ApplicationType.RABBITMQ, + _type=ApplicationType.RABBITMQ.value, limit=1, deleted=False ), None) if item: - return build_response( - code=HTTPStatus.CONFLICT, - content='RabbitMQ configuration already exists' - ) + raise ResponseFactory(HTTPStatus.CONFLICT).message( + 'RabbitMQ configuration already exists' + ).exc() meta = RabbitMQApplicationMeta( - maestro_user=event[MAESTRO_USER_ATTR], - rabbit_exchange=event.get(RABBIT_EXCHANGE_ATTR), - request_queue=event[REQUEST_QUEUE_ATTR], - response_queue=event[RESPONSE_QUEUE_ATTR], - sdk_access_key=event[SDK_ACCESS_KEY_ATTR] + maestro_user=event.maestro_user, + rabbit_exchange=event.rabbit_exchange, + request_queue=event.request_queue, + response_queue=event.response_queue, + sdk_access_key=event.sdk_access_key ) name = self._ssm_service.save_data( name=f'{customer}-rabbitmq-configuration', value=RabbitMQApplicationSecret( - connection_url=event[CONNECTION_URL_ATTR], - sdk_secret_key=event[SDK_SECRET_KEY_ATTR] + connection_url=str(event.connection_url), + sdk_secret_key=event.sdk_secret_key ).dict(), prefix='caas' ) - application = self._modular_service.create_application( - customer=customer, - _type=ApplicationType.RABBITMQ, + application = self._application_service.build( + customer_id=customer, + type=ApplicationType.RABBITMQ, + created_by=_pe['cognito_user_id'], + is_deleted=False, description='RabbitMQ configuration for Custodian', meta=meta.dict(), secret=name ) _LOG.info('Saving application item') - self._modular_service.save(application) + self._application_service.save(application) return build_response(content=self.get_dto(application)) - def get(self, event) -> dict: - customer = event[CUSTOMER_ATTR] - application = next(self._modular_service.get_applications( + @validate_kwargs + def get(self, event: RabbitMQGetModel): + customer = event.customer + application = next(self._application_service.list( customer=customer, _type=ApplicationType.RABBITMQ, limit=1, deleted=False ), None) if not application: - return build_response( - code=HTTPStatus.NOT_FOUND, - content=f'RabbitMQ configuration not found' - ) + raise ResponseFactory(HTTPStatus.NOT_FOUND).message( + 'RabbitMQ configuration not found' + ).exc() return build_response(content=self.get_dto(application)) - def delete(self, event) -> dict: - customer = event[CUSTOMER_ATTR] - application = next(self._modular_service.get_applications( + @validate_kwargs + def delete(self, event: RabbitMQDeleteModel): + customer = event.customer + application = next(self._application_service.list( customer=customer, _type=ApplicationType.RABBITMQ, limit=1, deleted=False ), None) if not application: - return build_response(code=HTTPStatus.NO_CONTENT) - erased = self._modular_service.delete(application) - if not erased: - return build_response( - code=HTTPStatus.BAD_REQUEST, - content='Could not remove the application. ' - 'Probably it\'s used by some parents.' - ) - # erased + raise ResponseFactory(HTTPStatus.NOT_FOUND).default().exc() + self._application_service.mark_deleted(application) if application.secret: _LOG.info(f'Removing application secret: {application.secret}') if not self._ssm_service.delete_secret(application.secret): _LOG.warning(f'Could not remove secret: {application.secret}') # Modular sdk does not remove the app, just sets is_deleted - self._modular_service.save(application) return build_response(code=HTTPStatus.NO_CONTENT) + diff --git a/src/handlers/raw_report_handler.py b/src/handlers/raw_report_handler.py new file mode 100644 index 000000000..7923bbbb3 --- /dev/null +++ b/src/handlers/raw_report_handler.py @@ -0,0 +1,75 @@ +from functools import cached_property +from typing import cast + +from modular_sdk.models.tenant import Tenant +from modular_sdk.services.tenant_service import TenantService + +from handlers import AbstractHandler, Mapping +from helpers import flip_dict +from helpers.constants import CustodianEndpoint, HTTPMethod +from helpers.lambda_response import build_response +from helpers.log_helper import get_logger +from services import SP +from services import modular_helpers +from services import obfuscation +from services.report_service import ReportService +from validators.swagger_request_models import RawReportGetModel +from validators.utils import validate_kwargs + +_LOG = get_logger(__name__) + + +class RawReportHandler(AbstractHandler): + __slots__ = '_rs', '_ts' + + def __init__(self, report_service: ReportService, + tenant_service: TenantService): + self._rs = report_service + self._ts = tenant_service + + @classmethod + def build(cls) -> 'RawReportHandler': + return cls( + report_service=SP.report_service, + tenant_service=SP.modular_client.tenant_service(), + ) + + @cached_property + def mapping(self) -> Mapping: + return { + CustodianEndpoint.REPORTS_RAW_TENANTS_TENANT_NAME_STATE_LATEST: { + HTTPMethod.GET: self.get_by_tenant + }, + } + + @validate_kwargs + def get_by_tenant(self, event: RawReportGetModel, tenant_name: str): + tenant = self._ts.get(tenant_name) + modular_helpers.assert_tenant_valid(tenant, event.customer_id) + tenant = cast(Tenant, tenant) + + collection = self._rs.tenant_latest_collection(tenant) + collection.fetch_all() + resp = { + 'customer_name': tenant.customer_name, + 'tenant_name': tenant.name, + 'obfuscated': event.obfuscated, + } + if event.obfuscated: + _LOG.info('Going to obfuscate raw report') + dictionary_out = {} + obfuscation.obfuscate_collection(collection, dictionary_out) + flip_dict(dictionary_out) + resp['dictionary_url'] = self._rs.one_time_url_json( + dictionary_out, 'obfuscation_dictionary.json' + ) + # msgspec can dump parts directly + resp['url'] = self._rs.one_time_url_json( + tuple(collection.iter_parts()), + f'{tenant.name}-raw.json' + ) + if event.meta: + collection.fetch_meta() + resp['meta_url'] = self._rs.one_time_url_json(collection.meta, + 'rules-meta.json') + return build_response(content=resp) diff --git a/src/handlers/report_status_handler.py b/src/handlers/report_status_handler.py new file mode 100644 index 000000000..31b22156f --- /dev/null +++ b/src/handlers/report_status_handler.py @@ -0,0 +1,47 @@ +from functools import cached_property +from http import HTTPStatus + +from handlers import AbstractHandler, Mapping +from helpers import get_logger +from helpers.constants import CustodianEndpoint, HTTPMethod +from helpers.lambda_response import build_response +from services import SP +from services.report_statistics_service import ReportStatisticsService +from validators.swagger_request_models import ReportStatusGetModel +from validators.utils import validate_kwargs + +_LOG = get_logger(__name__) + + +class ReportStatusHandlerHandler(AbstractHandler): + + def __init__(self, report_statistics_service: ReportStatisticsService): + self.report_statistics_service = report_statistics_service + + @cached_property + def mapping(self) -> Mapping: + return { + CustodianEndpoint.REPORTS_STATUS: { + HTTPMethod.GET: self.get_status + } + } + + @classmethod + def build(cls) -> 'ReportStatusHandlerHandler': + return cls( + report_statistics_service=SP.report_statistics_service + ) + + @validate_kwargs + def get_status(self, event: ReportStatusGetModel): + _LOG.debug(f'Retrieving items from CaaSReportStatistics table with id ' + f'{event.job_id} and customer {event.customer}') + items = self.report_statistics_service.iter_by_id( + job_id=event.job_id, + customer=event.customer, + limit=1 if not event.complete else None + ) + return build_response( + code=HTTPStatus.OK, + content=map(self.report_statistics_service.dto, items) + ) diff --git a/src/handlers/resource_report_handler.py b/src/handlers/resource_report_handler.py index cefc361b9..7037485f4 100644 --- a/src/handlers/resource_report_handler.py +++ b/src/handlers/resource_report_handler.py @@ -1,59 +1,81 @@ +import tempfile from datetime import datetime -from functools import cached_property +from functools import cached_property, cmp_to_key from http import HTTPStatus -from typing import Tuple, Set, Iterator, Optional, Any, TypedDict, List, Dict +from itertools import chain +from typing import Any, Iterator, Optional, TypedDict, Generator from modular_sdk.models.tenant import Tenant - -from handlers.abstracts.abstract_handler import AbstractHandler -from helpers import build_response -from helpers.constants import AZURE_CLOUD_ATTR, CUSTOMER_ATTR, \ - TENANT_NAME_ATTR, START_ISO_ATTR, END_ISO_ATTR, TYPE_ATTR, \ - ID_ATTR, JOB_ID_ATTR, TENANT_ATTR, HTTPMethod -from helpers.constants import IDENTIFIER_ATTR +from modular_sdk.services.tenant_service import TenantService +from xlsxwriter import Workbook +from xlsxwriter.worksheet import Worksheet + +from handlers import AbstractHandler, Mapping +from helpers import filter_dict, hashable, flip_dict +from helpers.constants import ( + CustodianEndpoint, + HTTPMethod, + JOB_ID_ATTR, + JobState, + JobType, + REPORT_FIELDS, + ReportFormat, + Severity, + TYPE_ATTR, +) +from helpers.lambda_response import build_response from helpers.log_helper import get_logger -from helpers.reports import hashable -from helpers.time_helper import utc_datetime -from models.job import SUBMITTED_AT_ATTR -from services import SERVICE_PROVIDER -from services.ambiguous_job_service import AmbiguousJobService, Source +from helpers.reports import severity_cmp +from helpers.time_helper import utc_iso +from services import SP +from services import modular_helpers +from services.ambiguous_job_service import AmbiguousJob, AmbiguousJobService from services.clients.s3 import S3Client from services.environment_service import EnvironmentService -from services.findings_service import FindingsService +from services.mappings_collector import LazyLoadedMappingsCollector from services.metrics_service import MetricsService, ResourcesGenerator -from services.modular_service import ModularService -from services.report_service import ReportService, DETAILED_REPORT_FILE -from services.rule_meta_service import LazyLoadedMappingsCollector +from services.platform_service import Platform, PlatformService +from services.report_service import ReportResponse, ReportService +from services.sharding import ShardsCollection +from services import obfuscation +from services.xlsx_writer import CellContent, Table, XlsxRowsWriter +from validators.swagger_request_models import ( + PlatformK8sResourcesReportGetModel, + ResourceReportJobGetModel, + ResourceReportJobsGetModel, + ResourcesReportGetModel, +) +from validators.utils import validate_kwargs _LOG = get_logger(__name__) +# rule, region, dto, matched_dto, timestamp +Payload = tuple[str, str, dict, dict, float] -class MatchedFindingsIterator(Iterator): - def __init__(self, findings: dict, cloud: str, resource_id: str, - exact_match: bool = True, search_by: Optional[set] = None, - search_by_all: bool = False, - resource_type: Optional[str] = None, - region: Optional[str] = None): - self._findings = findings - self._cloud = cloud - self._resource_id = resource_id - self._exact_match = exact_match +class MatchedResourcesIterator(Iterator[Payload]): - self._search_by = search_by - self._search_by_all = search_by_all + def __init__(self, collection: ShardsCollection, + resource_type: Optional[str] = None, + region: Optional[str] = None, + exact_match: bool = True, + search_by_all: bool = False, + search_by: Optional[dict] = None, + dictionary_out: dict | None = None): + self._collection = collection self._resource_type = resource_type self._region = region - self._it = None + self._exact_match = exact_match + self._search_by_all = search_by_all + self._search_by = search_by or {} + self._dictionary_out = dictionary_out - @property - def findings(self) -> dict: - return self._findings + self._it = None @property - def resource_id(self) -> str: - return self._resource_id + def collection(self) -> ShardsCollection: + return self._collection def create_resources_generator(self) -> ResourcesGenerator: """ @@ -61,149 +83,291 @@ def create_resources_generator(self) -> ResourcesGenerator: :return: """ ms = self.metrics_service - resources = ms.iter_resources(self._findings) - if self._cloud != AZURE_CLOUD_ATTR: - resources = ms.expose_multiregional(resources) - resources = ms.custom_modify(resources, self._findings) - # removing duplicates within rule-region - resources = ms.deduplicated(resources) + resources = ms.iter_resources(self._collection.iter_parts()) + resources = ms.custom_modify(resources, self._collection.meta) if self._region: resources = ms.allow_only_regions(resources, {self._region}) if self._resource_type: - resources = ms.allow_only_resource_type(resources, self._findings, - self._resource_type) + resources = ms.allow_only_resource_type( + resources, self._collection.meta, self._resource_type + ) return resources - @cached_property - def mappings_collector(self) -> LazyLoadedMappingsCollector: - return SERVICE_PROVIDER.mappings_collector() - - @cached_property + @property def metrics_service(self) -> MetricsService: - return SERVICE_PROVIDER.metrics_service() - - def rule_report_fields(self, rule: str) -> Set[str]: - """ - Fields to search by. If search_by is specified, they will be used. - In case the parameter is not specified, report_fields will be used. - In case search_by_all is True, all fields will be checked - :param rule: - :return: - """ - if self._search_by_all: - return set() - rf = self._search_by - if not rf: - rf = self.mappings_collector.human_data.get(rule, {}).get( - 'report_fields') or set() - return set(k.lower() for k in rf or set()) + return SP.metrics_service def __iter__(self): self._it = self.create_resources_generator() return self - def does_match(self, value: Any) -> bool: + def does_match(self, provided: Any, real: Any) -> bool: + if isinstance(real, (list, dict)): + return False if self._exact_match: - return str(self._resource_id) == str(value) - return str(self._resource_id).lower() in str(value).lower() - - def __next__(self) -> Tuple[str, str, dict, dict]: - while True: - rule, region, dto = next(self._it) - search_by = self.rule_report_fields(rule) - for key, value in dto.items(): # nested_items(dto) - if search_by and key.lower() not in search_by: - continue # skipping key and value - if self.does_match(value): - return rule, region, dto, {key: value} - - -class ViolatedRule(TypedDict): - name: str - description: Optional[str] - severity: str + return str(provided) == str(real) + return str(provided).lower() in str(real).lower() + def match_by_all(self, dto: dict) -> dict: + """ + Takes all the values from search_by and tries to match all the keys. + Returns the first matched + """ + expected = set(map(str, self._search_by.values())) + for key, real in dto.items(): # nested_items(dto) + for provided in expected: + if self.does_match(provided, real): + return {key: real} + return {} + + def match(self, dto: dict) -> dict: + """ + Matches by all the keys and values from search_by + """ + result = {} + for key, provided in self._search_by.items(): + real = dto.get(key) + if not self.does_match(provided, real): + return {} + result[key] = real + return result -class ResourceReport(TypedDict): - account_id: str - input_identifier: str # one user provided - identifier: str # one that was matched by user's - last_scan_date: Optional[str] - violated_rules: List[ViolatedRule] - data: Dict - region: str - resource_type: str + def __next__(self) -> Payload: + while True: + rule, region, dto, ts = next(self._it) + if not self._search_by: + if isinstance(self._dictionary_out, dict): + obfuscation.obfuscate_finding(dto, self._dictionary_out) + return rule, region, dto, {}, ts + if self._search_by_all: + match = self.match_by_all(dto) + else: + match = self.match(dto) + if match: + if isinstance(self._dictionary_out, dict): + obfuscation.obfuscate_finding(dto, self._dictionary_out) + return rule, region, dto, match, ts class ResourceReportBuilder: - def __init__(self, matched_findings_iterator: MatchedFindingsIterator, - tenant_item: Tenant, last_scan_date: Optional[str] = None): + class ResourceReport(TypedDict): + account_id: Optional[str] + platform_id: Optional[str] + data: dict + violated_rules: list[dict] + matched_by: dict + region: str + resource_type: str + last_found: float + + def __init__(self, matched_findings_iterator: MatchedResourcesIterator, + entity: Tenant | Platform, full: bool = True): self._it = matched_findings_iterator - self._tenant_item = tenant_item - self._last_scan_date = last_scan_date + self._entity = entity + self._full = full @cached_property - def mappings_collector(self) -> LazyLoadedMappingsCollector: - return SERVICE_PROVIDER.mappings_collector() + def mc(self) -> LazyLoadedMappingsCollector: + return SP.mappings_collector @cached_property def metrics_service(self) -> MetricsService: - return SERVICE_PROVIDER.metrics_service() + return SP.metrics_service - def _build_rules(self, rules: List[str]) -> List[ViolatedRule]: + def _build_rules(self, rules: set[str]) -> list[dict]: + severity = self.mc.severity + hd = self.mc.human_data return [{ 'name': rule, - 'description': self._it.findings.get(rule, {}).get('description'), - 'severity': str( - self.mappings_collector.severity.get(rule) or 'Unknown') + 'description': self._it.collection.meta.get(rule, {}).get( + 'description'), + 'severity': str(severity.get(rule) or 'Unknown'), + 'remediation': (r_data := hd.get(rule, {})).get('remediation'), + 'article': r_data.get('article'), + 'impact': r_data.get('impact') } for rule in rules] - def build(self) -> List[ResourceReport]: + def build(self) -> list[ResourceReport]: datas = {} # the same resources have the same resource_type, - # region and match_identifier - for rule, region, dto, match_dto in self._it: - unique = hashable({ - # 'identifier': next(iter(match_dto.values())), - 'identifier': match_dto, - 'region': region, - 'resource_type': self.metrics_service.adjust_resource_type( - self._it.findings.get(rule, {}).get('resourceType', '') - ) - }) - inner = datas.setdefault(unique, {'data': {}, 'rules': []}) - inner['data'].update(dto) - inner['rules'].append(rule) + # region and REPORT_FIELDS (id, name, arn) + hd = self.mc.human_data + get_report_fields = lambda r: set(hd.get(r, {}).get('report_fields') + or []) | REPORT_FIELDS + + for rule, region, dto, match_dto, ts in self._it: + unique = hashable(( + filter_dict(dto, REPORT_FIELDS), + region, + self._it.collection.meta.get(rule, {}).get('resource') + )) + # data, rules, matched_by, timestamp + inner = datas.setdefault(unique, [{}, set(), {}, ts]) + if not self._full: + dto = filter_dict(dto, get_report_fields(rule)) + inner[0].update(dto) + inner[1].add(rule) + inner[2].update(match_dto) + inner[3] = max(inner[3], ts) result = [] + identifier = {'account_id': self._entity.project} \ + if isinstance(self._entity, Tenant) else \ + {'platform_id': self._entity.id} for unique, inner in datas.items(): result.append({ - 'account_id': self._tenant_item.project, - 'input_identifier': self._it.resource_id, - 'last_scan_date': self._last_scan_date, - 'data': inner['data'], - **unique, # identifier, region & resource_type - 'violated_rules': self._build_rules(inner['rules']) + **identifier, + 'data': inner[0], + 'violated_rules': self._build_rules(inner[1]), + 'matched_by': inner[2], + 'region': unique[1], + 'resource_type': unique[2], + 'last_found': inner[3] }) return result +class ResourceReportXlsxWriter: + def __init__(self, it: MatchedResourcesIterator, full: bool = True, + keep_region: bool = True): + self._it = it + self._full = full + self._keep_region = keep_region + + @property + def mc(self) -> LazyLoadedMappingsCollector: + return SP.mappings_collector + + @property + def ms(self) -> MetricsService: + return SP.metrics_service + + def _aggregated(self) -> dict[tuple, list]: + """ + Just makes a mapping of a unique resource to rules it violates + """ + hd = self.mc.human_data + get_report_fields = lambda r: set(hd.get(r, {}).get('report_fields') + or []) | REPORT_FIELDS + res = {} + for rule, region, dto, match_dto, ts in self._it: + unique = hashable(( + filter_dict(dto, REPORT_FIELDS), + region, + self._it.collection.meta.get(rule, {}).get('resource') + )) + data = res.setdefault(unique, [set(), {}, ts]) + data[0].add(rule) + if not self._full: + dto = filter_dict(dto, get_report_fields(rule)) + data[1].update(dto) + data[2] = max(data[2], ts) + return res + + @cached_property + def head(self) -> list: + return [ + '№', 'Service', 'Resource', 'Region', + 'Date updated', 'Rule', 'Description', 'Severity', 'Article', + 'Remediation', + ] + + def write(self, wsh: Worksheet, wb: Workbook): + service = self.mc.service + severity = self.mc.severity + human_data = self.mc.human_data + bold = wb.add_format({'bold': True}) + red = wb.add_format({'bg_color': '#da9694'}) + yellow = wb.add_format({'bg_color': '#ffff00'}) + green = wb.add_format({'bg_color': '#92d051'}) + gray = wb.add_format({'bg_color': '#bfbfbf'}) + + def sf(sev: str): + """Format for severity""" + if sev == Severity.HIGH: + return red + if sev == Severity.MEDIUM: + return yellow + if sev == Severity.LOW: + return green + return gray + + table = Table() + table.new_row() + for h in self.head: + table.add_cells(CellContent(h, bold)) + + # a bit devilish code :( + # imagine you have a list of lists or ints. The thing below sorts the + # main lists when the key equal to the maximum value of inner lists. + # But, + # - instead of ints -> severities and custom cmp function + # - instead of list of lists -> dict there values are tuples with + # the first element - that list + # - values of inner lists not the actual values to sort by. They + # are not severities. Actual severities must be retrieved from a map + key = cmp_to_key(severity_cmp) + aggregated = dict(sorted( + self._aggregated().items(), + key=lambda p: key(severity.get( + max(p[1][0], key=lambda x: key(severity.get(x))))), + reverse=True + )) + i = 0 + for unique, data in aggregated.items(): + _, region, resource = unique + rules, dto, ts = data + rules = sorted(rules, key=lambda x: key(severity.get(x)), + reverse=True) + table.new_row() + table.add_cells(CellContent(i)) + services = set(filter(None, (service.get(rule) for rule in rules))) + if services: + table.add_cells(CellContent(', '.join(services))) + else: + table.add_cells() + table.add_cells(CellContent(dto)) + if self._keep_region: + table.add_cells(CellContent(region)) + else: + table.add_cells(CellContent()) + table.add_cells(CellContent(utc_iso(datetime.fromtimestamp(ts)))) + table.add_cells(*[CellContent(rule) for rule in rules]) + table.add_cells(*[CellContent( + self._it.collection.meta.get(rule).get('description') + ) for rule in rules]) + table.add_cells(*[CellContent( + severity.get(rule), sf(severity.get(rule))) for rule in rules + ]) + table.add_cells(*[ + CellContent(human_data.get(rule, {}).get('article')) + for rule in rules + ]) + table.add_cells(*[ + CellContent(human_data.get(rule, {}).get('remediation')) + for rule in rules + ]) + i += 1 + writer = XlsxRowsWriter() + writer.write(wsh, table) + + class ResourceReportHandler(AbstractHandler): def __init__(self, ambiguous_job_service: AmbiguousJobService, - modular_service: ModularService, + tenant_service: TenantService, report_service: ReportService, - findings_service: FindingsService, metrics_service: MetricsService, mappings_collector: LazyLoadedMappingsCollector, s3_client: S3Client, - environment_service: EnvironmentService): + environment_service: EnvironmentService, + platform_service: PlatformService): self._ambiguous_job_service = ambiguous_job_service - self._modular_service = modular_service + self._tenant_service = tenant_service self._report_service = report_service - self._findings_service = findings_service self._metrics_service = metrics_service self._mappings_collector = mappings_collector self._s3_client = s3_client self._environment_service = environment_service + self._platform_service = platform_service @property def rs(self): @@ -216,169 +380,279 @@ def ajs(self): @classmethod def build(cls): return cls( - ambiguous_job_service=SERVICE_PROVIDER.ambiguous_job_service(), - modular_service=SERVICE_PROVIDER.modular_service(), - report_service=SERVICE_PROVIDER.report_service(), - findings_service=SERVICE_PROVIDER.findings_service(), - metrics_service=SERVICE_PROVIDER.metrics_service(), - mappings_collector=SERVICE_PROVIDER.mappings_collector(), - s3_client=SERVICE_PROVIDER.s3(), - environment_service=SERVICE_PROVIDER.environment_service() + ambiguous_job_service=SP.ambiguous_job_service, + tenant_service=SP.modular_client.tenant_service(), + report_service=SP.report_service, + metrics_service=SP.metrics_service, + mappings_collector=SP.mappings_collector, + s3_client=SP.s3, + environment_service=SP.environment_service, + platform_service=SP.platform_service ) - def define_action_mapping(self) -> dict: + @cached_property + def mapping(self) -> Mapping: return { - '/reports/resources/tenants/{tenant_name}/state/latest': { + CustodianEndpoint.REPORTS_RESOURCES_PLATFORMS_K8S_PLATFORM_ID_LATEST: { + HTTPMethod.GET: self.k8s_platform_get_latest + }, + CustodianEndpoint.REPORTS_RESOURCES_TENANTS_TENANT_NAME_LATEST: { HTTPMethod.GET: self.get_latest }, - '/reports/resources/tenants/{tenant_name}/jobs': { + CustodianEndpoint.REPORTS_RESOURCES_TENANTS_TENANT_NAME_JOBS: { HTTPMethod.GET: self.get_jobs }, - '/reports/resources/jobs/{id}': { + CustodianEndpoint.REPORTS_RESOURCES_JOBS_JOB_ID: { HTTPMethod.GET: self.get_specific_job } } - def get_latest(self, event: dict) -> dict: - tenant_name = event.get(TENANT_NAME_ATTR) - customer = event.get(CUSTOMER_ATTR) - resource_id = event.get(IDENTIFIER_ATTR) # can be literally anything - exact_match: bool = event.get('exact_match') - search_by = event.get('search_by') - search_by_all = event.get('search_by_all') - resource_type = event.get('resource_type') - region = event.get('region') - - tenant_item = self._modular_service.get_tenant(tenant_name) - self._modular_service.assert_tenant_valid(tenant_item, customer) - findings = self._findings_service.get_findings_content( - tenant_item.project) - matched = MatchedFindingsIterator( - findings=findings, cloud=tenant_item.cloud, - resource_id=resource_id, exact_match=exact_match, - search_by=search_by, search_by_all=search_by_all, - resource_type=resource_type, region=region + @validate_kwargs + def k8s_platform_get_latest(self, event: PlatformK8sResourcesReportGetModel, + platform_id: str): + platform = self._platform_service.get_nullable( + hash_key=platform_id) + if not platform or event.customer and platform.customer != event.customer: + return build_response(code=HTTPStatus.NOT_FOUND, + content='Platform not found') + collection = self._report_service.platform_latest_collection(platform) + _LOG.debug('Fetching collection') + collection.fetch_all() + _LOG.debug('Fetching meta') + collection.fetch_meta() + + dictionary_url = None + dictionary = {} # todo maybe refactor somehow + matched = MatchedResourcesIterator( + collection=collection, + resource_type=event.resource_type, + exact_match=event.exact_match, + search_by_all=event.search_by_all, + search_by=event.extras, + dictionary_out=dictionary if event.obfuscated else None ) - last_scan_date = self._findings_service.get_latest_findings_key( - tenant_item.project) - _LOG.debug(f'Last scan date: {last_scan_date}') - if not last_scan_date: - _LOG.warning('Something is wrong with last_scan_date') + content = {} + match event.format: + case ReportFormat.JSON: + content = ResourceReportBuilder( + matched_findings_iterator=matched, + entity=platform, + full=event.full + ).build() + if event.obfuscated: + flip_dict(dictionary) + dictionary_url = self._report_service.one_time_url_json( + dictionary, 'dictionary.json') + if event.href: + url = self._report_service.one_time_url_json( + content, f'{platform.id}-latest.json' + ) + content = ReportResponse(platform, url, dictionary_url, + event.format).dict() + case ReportFormat.XLSX: + buffer = tempfile.TemporaryFile() + with Workbook(buffer, {'strings_to_numbers': True}) as wb: + ResourceReportXlsxWriter(matched, full=event.full, keep_region=False).write( + wb=wb, + wsh=wb.add_worksheet('resources') + ) + if event.obfuscated: + flip_dict(dictionary) + dictionary_url = self._report_service.one_time_url_json( + dictionary, 'dictionary.json') + buffer.seek(0) + url = self._report_service.one_time_url( + buffer, f'{platform.id}-latest.xlsx' + ) + content = ReportResponse(platform, url, dictionary_url, + event.format).dict() + return build_response(content=content) + + @validate_kwargs + def get_latest(self, event: ResourcesReportGetModel, tenant_name: str): + tenant_item = self._tenant_service.get(tenant_name) + modular_helpers.assert_tenant_valid(tenant_item, event.customer) + + collection = self._report_service.tenant_latest_collection(tenant_item) + if event.region: + _LOG.debug('Region is provided. Fetching only shard with ' + 'this region') + collection.fetch(region=event.region) else: - last_scan_date = last_scan_date.strip('/').split('/')[1] - _LOG.debug(f'Last scan date after processing {last_scan_date}') - response = ResourceReportBuilder( - matched_findings_iterator=matched, - tenant_item=tenant_item, - last_scan_date=last_scan_date - ).build() - return build_response(content=response) - - def get_jobs(self, event: dict) -> dict: - customer = event.get(CUSTOMER_ATTR) - tenant_name = event[TENANT_NAME_ATTR] - start_iso: datetime = event[START_ISO_ATTR] - end_iso: datetime = event[END_ISO_ATTR] - # href = event.get(HREF_ATTR) - typ = event.get(TYPE_ATTR) - - resource_id = event.get(IDENTIFIER_ATTR) # can be literally anything - exact_match: bool = event.get('exact_match') - search_by = event.get('search_by') - search_by_all = event.get('search_by_all') - resource_type = event.get('resource_type') - region = event.get('region') - - tenant_item = self._modular_service.get_tenant(tenant_name) - self._modular_service.assert_tenant_valid(tenant_item, customer) - - typ_params_map = self._ambiguous_job_service.derive_typ_param_map( - typ=typ, tenants=[tenant_item.name], cloud_ids=[] + _LOG.debug('Region is not provided. Fetching all shards') + collection.fetch_all() + _LOG.debug('Fetching meta') + collection.fetch_meta() + + dictionary_url = None + dictionary = {} # todo maybe refactor somehow + matched = MatchedResourcesIterator( + collection=collection, + resource_type=event.resource_type, + region=event.region, + exact_match=event.exact_match, + search_by_all=event.search_by_all, + search_by=event.extras, + dictionary_out=dictionary if event.obfuscated else None ) - source_list = self._ambiguous_job_service.batch_list( - typ_params_map=typ_params_map, customer=customer, - start=start_iso, end=end_iso, sort=True + content = {} + match event.format: + case ReportFormat.JSON: + content = ResourceReportBuilder( + matched_findings_iterator=matched, + entity=tenant_item, + full=event.full + ).build() + if event.obfuscated: + flip_dict(dictionary) + dictionary_url = self._report_service.one_time_url_json( + dictionary, 'dictionary.json') + if event.href: + url = self._report_service.one_time_url_json( + content, f'{tenant_name}-latest.json' + ) + content = ReportResponse(tenant_item, url, dictionary_url, + event.format).dict() + case ReportFormat.XLSX: + buffer = tempfile.TemporaryFile() + with Workbook(buffer, {'strings_to_numbers': True}) as wb: + ResourceReportXlsxWriter(matched).write( + wb=wb, + wsh=wb.add_worksheet(tenant_name) + ) + if event.obfuscated: + flip_dict(dictionary) + dictionary_url = self._report_service.one_time_url_json( + dictionary, 'dictionary.json') + buffer.seek(0) + url = self._report_service.one_time_url( + buffer, f'{tenant_name}-latest.xlsx' + ) + content = ReportResponse(tenant_item, url, dictionary_url, + event.format).dict() + return build_response(content=content) + + @validate_kwargs + def get_jobs(self, event: ResourceReportJobsGetModel, tenant_name: str): + tenant_item = self._tenant_service.get(tenant_name) + modular_helpers.assert_tenant_valid(tenant_item, event.customer) + + jobs = self.ajs.get_by_tenant_name( + tenant_name=tenant_name, + job_type=event.job_type, + status=JobState.SUCCEEDED, + start=event.start_iso, + end=event.end_iso ) - if not source_list: - return build_response(code=HTTPStatus.NOT_FOUND, - content='No jobs found') - key_source = { - self.rs.derive_job_object_path( - self.ajs.get_attribute(item, ID_ATTR), - DETAILED_REPORT_FILE): item for item in source_list - } # sorted - gen = self._s3_client.get_json_batch( - bucket_name=self._environment_service.default_reports_bucket_name(), - keys=key_source.keys() - ) # not sorted - key_response = {} - for key, detailed in gen: - findings = self.rs.derive_findings_from_report(detailed, True) - matched = MatchedFindingsIterator( - findings=findings.serialize(), cloud=tenant_item.cloud, - resource_id=resource_id, exact_match=exact_match, - search_by=search_by, search_by_all=search_by_all, - resource_type=resource_type, region=region + + source_response = {} + for source in self.ajs.to_ambiguous(jobs): + if source.is_platform_job: + continue + if not source.is_ed_job: + collection = self._report_service.job_collection( + tenant_item, source.job + ) + else: + collection = self._report_service.ed_job_collection( + tenant_item, source.job + ) + if event.region: + _LOG.debug('Region is provided. Fetching only shard with ' + 'this region') + collection.fetch(region=event.region) + else: + _LOG.debug('Region is not provided. Fetching all shards') + collection.fetch_all() + collection.meta = self._report_service.fetch_meta(tenant_item) + matched = MatchedResourcesIterator( + collection=collection, + resource_type=event.resource_type, + region=event.region, + exact_match=event.exact_match, + search_by_all=event.search_by_all, + search_by=event.extras ) - sa = self.ajs.get_attribute(key_source[key], SUBMITTED_AT_ATTR) response = ResourceReportBuilder( matched_findings_iterator=matched, - tenant_item=tenant_item, - last_scan_date=utc_datetime(sa).date().isoformat() + entity=tenant_item, + full=event.full ).build() if not response: - _LOG.info(f'No resources found for job {key}. Skipping') + _LOG.debug(f'No resources found for job {source}. Skipping') continue - key_response[key] = response - result = [] - for key, resources in key_response.items(): - result.extend(self.dto(key_source.get(key), resources)) - return build_response(content=result) - - def get_specific_job(self, event: dict) -> dict: - job_id: str = event[ID_ATTR] - typ: str = event[TYPE_ATTR] - customer = event.get(CUSTOMER_ATTR) - - resource_id = event.get(IDENTIFIER_ATTR) # can be literally anything - exact_match: bool = event.get('exact_match') - search_by = event.get('search_by') - search_by_all = event.get('search_by_all') - resource_type = event.get('resource_type') - region = event.get('region') - - source = self.ajs.get(job_id, typ) - if not source: + source_response[source] = response + return build_response(content=chain.from_iterable( + self.dto(s, r) for s, r in source_response.items() + )) + + @validate_kwargs + def get_specific_job(self, event: ResourceReportJobGetModel, job_id: str): + job = self.ajs.get_job( + job_id=job_id, + typ=event.job_type, + customer=event.customer + ) + if not job: return build_response(content=f'Job {job_id} not found', code=HTTPStatus.NOT_FOUND) - tenant_item = self._modular_service.get_tenant(self.ajs.get_attribute( - source, TENANT_ATTR)) - self._modular_service.assert_tenant_valid(tenant_item, customer) - - key = self.rs.derive_job_object_path( - self.ajs.get_attribute(source, ID_ATTR), DETAILED_REPORT_FILE) - detailed = self.rs.pull_job_report(key) - findings = self.rs.derive_findings_from_report(detailed, True) - matched = MatchedFindingsIterator( - findings=findings.serialize(), cloud=tenant_item.cloud, - resource_id=resource_id, exact_match=exact_match, - search_by=search_by, search_by_all=search_by_all, - resource_type=resource_type, region=region + if job.is_platform_job: + return build_response( + code=HTTPStatus.NOT_IMPLEMENTED, + content='Platform job resources report is not available now' + ) + tenant = self._tenant_service.get(job.tenant_name) + modular_helpers.assert_tenant_valid(tenant, event.customer) + + if job.type == JobType.MANUAL: + collection = self._report_service.job_collection(tenant, job.job) + else: + collection = self._report_service.ed_job_collection(tenant, + job.job) + if event.region: + _LOG.debug('Region is provided. Fetching only shard with ' + 'this region') + collection.fetch(region=event.region) + else: + _LOG.debug('Region is not provided. Fetching all shards') + collection.fetch_all() + collection.meta = self._report_service.fetch_meta(tenant) + + dictionary_url = None + dictionary = {} + matched = MatchedResourcesIterator( + collection=collection, + resource_type=event.resource_type, + region=event.region, + exact_match=event.exact_match, + search_by_all=event.search_by_all, + search_by=event.extras, + dictionary_out=dictionary if event.obfuscated else None ) - sa = self.ajs.get_attribute(source, SUBMITTED_AT_ATTR) response = ResourceReportBuilder( matched_findings_iterator=matched, - tenant_item=tenant_item, - last_scan_date=utc_datetime(sa).date().isoformat() + entity=tenant, + full=event.full ).build() - return build_response(content=self.dto(source, response)) + if event.obfuscated: + flip_dict(dictionary) + dictionary_url = self._report_service.one_time_url_json( + dictionary, 'dictionary.json' + ) + if event.href: + url = self._report_service.one_time_url_json( + response, f'{job_id}-resources.json' + ) + content = ReportResponse(job, url, dictionary_url, + ReportFormat.JSON).dict() + else: + content = self.dto(job, response) + return build_response(content=content) - def dto(self, source: Source, response: List[Dict]) -> List[dict]: + @staticmethod + def dto(job: AmbiguousJob, response: list) -> list[dict]: return [{ - JOB_ID_ATTR: self.ajs.get_attribute(item=source, attr=ID_ATTR), - TYPE_ATTR: self.ajs.get_type(source), - SUBMITTED_AT_ATTR: self.ajs.get_attribute(source, - SUBMITTED_AT_ATTR), + JOB_ID_ATTR: job.id, + TYPE_ATTR: job.type, **res, - } for res in response] diff --git a/src/handlers/role_handler.py b/src/handlers/role_handler.py index 8d435fbb4..d0bd95eb1 100644 --- a/src/handlers/role_handler.py +++ b/src/handlers/role_handler.py @@ -1,122 +1,140 @@ -from datetime import datetime +from functools import cached_property from http import HTTPStatus from typing import Iterable -from helpers import build_response -from helpers.constants import CUSTOMER_ATTR, NAME_ATTR, EXPIRATION_ATTR, \ - POLICIES_ATTR, POLICIES_TO_ATTACH, POLICIES_TO_DETACH, HTTPMethod -from helpers.log_helper import get_logger -from helpers.system_customer import SYSTEM_CUSTOMER +from handlers import AbstractHandler, Mapping +from helpers import NextToken +from helpers.constants import CustodianEndpoint, HTTPMethod +from helpers.lambda_response import ResponseFactory, build_response from helpers.time_helper import utc_iso -from services.rbac.iam_cache_service import CachedIamService - -_LOG = get_logger(__name__) - - -class RoleHandler: +from services import SP +from services.rbac_service import PolicyService, RoleService +from validators.swagger_request_models import ( + BaseModel, + BasePaginationModel, + RolePatchModel, + RolePostModel, +) +from validators.utils import validate_kwargs + + +class RoleHandler(AbstractHandler): """ Manage Role API """ - def __init__(self, cached_iam_service: CachedIamService): - self._iam_service = cached_iam_service + def __init__(self, role_service: RoleService, + policy_service: PolicyService): + self._role_service = role_service + self._policy_service = policy_service - def define_action_mapping(self): + @classmethod + def build(cls): + return cls(role_service=SP.role_service, + policy_service=SP.policy_service) + + @cached_property + def mapping(self) -> Mapping: return { - '/roles': { - HTTPMethod.GET: self.get_role, + CustodianEndpoint.ROLES: { HTTPMethod.POST: self.create_role, - HTTPMethod.PATCH: self.update_role, - HTTPMethod.DELETE: self.delete_role, - }, - '/roles/cache': { - HTTPMethod.DELETE: self.delete_role_cache + HTTPMethod.GET: self.list_roles }, + CustodianEndpoint.ROLES_NAME: { + HTTPMethod.GET: self.get_role, + HTTPMethod.DELETE: self.delete_role, + HTTPMethod.PATCH: self.update_role + } } - def get_role(self, event): - name = event.get(NAME_ATTR) - customer = event.get(CUSTOMER_ATTR) - it = self._iam_service.list_roles( - customer=customer, name=name + @validate_kwargs + def get_role(self, event: BaseModel, name: str): + item = self._role_service.get_nullable(event.customer_id, name) + if not item: + raise ResponseFactory(HTTPStatus.NOT_FOUND).message( + self._role_service.not_found_message(name) + ).exc() + return build_response(self._role_service.dto(item)) + + @validate_kwargs + def list_roles(self, event: BasePaginationModel): + cursor = self._role_service.query( + customer=event.customer_id, + limit=event.limit, + last_evaluated_key=NextToken.deserialize(event.next_token).value ) - return build_response(content=( - self._iam_service.get_dto(entity) for entity in it - )) - - def create_role(self, event): - name = event.get(NAME_ATTR) - customer = event.get(CUSTOMER_ATTR) or SYSTEM_CUSTOMER - policies: set = event.get(POLICIES_ATTR) - expiration: datetime = event.get(EXPIRATION_ATTR) - - existing = self._iam_service.get_role(customer, name) + items = tuple(cursor) + return ResponseFactory().items( + it=map(self._role_service.dto, items), + next_token=NextToken(cursor.last_evaluated_key) + ).build() + + @validate_kwargs + def create_role(self, event: RolePostModel): + name = event.name + customer = event.customer_id + existing = self._role_service.get_nullable(customer, name) if existing: - return build_response( - code=HTTPStatus.CONFLICT, - content=f'Role with name {name} already exists' - ) - self.ensure_policies_exist(customer, policies) - role = self._iam_service.create_role({ - 'name': name, - 'customer': customer, - 'policies': list(policies), - 'expiration': utc_iso(expiration) - }) - self._iam_service.save(role) - return build_response(content=self._iam_service.get_dto(role)) + raise ResponseFactory(HTTPStatus.CONFLICT).message( + f'Role with name {name} already exists' + ).exc() + self.ensure_policies_exist(customer, event.policies) + role = self._role_service.create( + customer=customer, + name=name, + policies=tuple(event.policies), + expiration=event.expiration, + description=event.description + ) + self._role_service.save(role) + return build_response(content=self._role_service.dto(role), + code=HTTPStatus.CREATED) def ensure_policies_exist(self, customer: str, policies: Iterable[str]): for name in policies: - item = self._iam_service.get_policy(customer, name) + item = self._policy_service.get_nullable(customer, name) if not item: - return build_response( - code=HTTPStatus.NOT_FOUND, - content=f'Policy \'{name}\' not found' - ) - - def update_role(self, event): - name = event.get(NAME_ATTR) - customer = event.get(CUSTOMER_ATTR) or SYSTEM_CUSTOMER - # todo validate to_attach - to_attach: set = event.get(POLICIES_TO_ATTACH) - to_detach: set = event.get(POLICIES_TO_DETACH) - expiration: datetime = event.get(EXPIRATION_ATTR) - - role = self._iam_service.get_role(customer, name) + raise ResponseFactory(HTTPStatus.BAD_REQUEST).message( + f'Policy {name} not found' + ).exc() + + @validate_kwargs + def update_role(self, event: RolePatchModel, name: str): + customer = event.customer_id + + role = self._role_service.get_nullable(customer, name) if not role: - return build_response( - code=HTTPStatus.NOT_FOUND, - content=f'Role with name {name} already not found' - ) - self.ensure_policies_exist(customer, to_attach) + raise ResponseFactory(HTTPStatus.NOT_FOUND).message( + f'Role with name {name} already not found' + ).exc() + self.ensure_policies_exist(customer, event.policies_to_attach) policies = set(role.policies or []) - policies -= to_detach - policies |= to_attach + policies -= event.policies_to_attach + policies |= event.policies_to_detach role.policies = list(policies) - if expiration: - role.expiration = utc_iso(expiration) + if event.expiration: + role.expiration = utc_iso(event.expiration) + if event.description: + role.description = event.description - self._iam_service.save(role) + self._role_service.save(role) return build_response( code=HTTPStatus.OK, - content=self._iam_service.get_dto(role) + content=self._role_service.dto(role) ) - def delete_role(self, event): - name = event.get(NAME_ATTR) - customer = event.get(CUSTOMER_ATTR) or SYSTEM_CUSTOMER - role = self._iam_service.get_role(customer, name) + @validate_kwargs + def delete_role(self, event: BaseModel, name: str): + role = self._role_service.get_nullable(event.customer_id, name) if role: - self._iam_service.delete(role) - return build_response( - content=f'No traces of role \'{name}\' left in customer {customer}' - ) - - def delete_role_cache(self, event): - name = event.get(NAME_ATTR) - customer = event.get(CUSTOMER_ATTR) or SYSTEM_CUSTOMER - self._iam_service.clean_role_cache(customer=customer, name=name) - return build_response(content='Cleaned') + self._role_service.delete(role) + return build_response(code=HTTPStatus.NO_CONTENT) + + # @validate_kwargs + # def delete_role_cache(self, event: RoleCacheDeleteModel): + # name = event.name + # customer = event.customer + # self._iam_service.clean_role_cache(customer=customer, name=name) + # return build_response(code=HTTPStatus.NO_CONTENT) diff --git a/src/handlers/rule_handler.py b/src/handlers/rule_handler.py index 1bda23c0d..c1dae93a3 100644 --- a/src/handlers/rule_handler.py +++ b/src/handlers/rule_handler.py @@ -1,17 +1,18 @@ +from functools import cached_property from http import HTTPStatus from modular_sdk.models.pynamodb_extension.base_model import \ LastEvaluatedKey as Lek -from handlers.abstracts.abstract_handler import AbstractHandler -from helpers import build_response -from helpers.constants import HTTPMethod, NEXT_TOKEN_ATTR +from handlers import AbstractHandler, Mapping +from helpers.constants import CustodianEndpoint, HTTPMethod +from helpers.lambda_response import ResponseFactory, build_response from helpers.log_helper import get_logger from helpers.system_customer import SYSTEM_CUSTOMER -from services.modular_service import ModularService +from services import SP from services.rule_meta_service import RuleService from services.rule_source_service import RuleSourceService -from validators.request_validation import RuleGetModel, RuleDeleteModel +from validators.swagger_request_models import RuleDeleteModel, RuleGetModel from validators.utils import validate_kwargs _LOG = get_logger(__name__) @@ -22,23 +23,29 @@ class RuleHandler(AbstractHandler): Manage Rule API """ - def __init__(self, modular_service: ModularService, - rule_service: RuleService, + def __init__(self, rule_service: RuleService, rule_source_service: RuleSourceService): self.rule_service = rule_service self.rule_source_service = rule_source_service - self.modular_service = modular_service - def define_action_mapping(self): + @classmethod + def build(cls): + return cls( + rule_service=SP.rule_service, + rule_source_service=SP.rule_source_service + ) + + @cached_property + def mapping(self) -> Mapping: return { - '/rules': { + CustodianEndpoint.RULES: { HTTPMethod.GET: self.get_rule, HTTPMethod.DELETE: self.delete_rule } } @validate_kwargs - def get_rule(self, event: RuleGetModel) -> dict: + def get_rule(self, event: RuleGetModel): _LOG.debug(f'Get rules action') customer = event.customer or SYSTEM_CUSTOMER lek = Lek.deserialize(event.next_token or None) @@ -74,14 +81,14 @@ def get_rule(self, event: RuleGetModel) -> dict: ) dto = list(self.rule_service.dto(rule) for rule in cursor) lek.value = cursor.last_evaluated_key - return build_response( - content=dto, - meta={NEXT_TOKEN_ATTR: lek.serialize()} if lek else None - ) + + return ResponseFactory().items( + dto, next_token=lek.serialize() if lek else None + ).build() @validate_kwargs def delete_rule(self, event: RuleDeleteModel): - _LOG.debug(f'Delete rule action') + _LOG.debug('Delete rule action') customer = event.customer or SYSTEM_CUSTOMER if event.rule: diff --git a/src/handlers/rule_meta_handler.py b/src/handlers/rule_meta_handler.py index 8c84de1ce..cdbf01267 100644 --- a/src/handlers/rule_meta_handler.py +++ b/src/handlers/rule_meta_handler.py @@ -1,69 +1,63 @@ -from handlers.abstracts.abstract_handler import AbstractHandler -from helpers import build_response +from functools import cached_property from http import HTTPStatus -from helpers.constants import HTTPMethod, USER_CUSTOMER_ATTR -from helpers.log_helper import get_logger -from helpers.system_customer import SYSTEM_CUSTOMER -from services import SERVICE_PROVIDER -from services.clients.lambda_func import LambdaClient, \ - RULE_META_UPDATER_LAMBDA_NAME -_LOG = get_logger(__name__) +from handlers import AbstractHandler, Mapping +from helpers.constants import CustodianEndpoint, HTTPMethod +from helpers.lambda_response import build_response +from services import SERVICE_PROVIDER +from services.clients.lambda_func import LambdaClient, RULE_META_UPDATER_LAMBDA_NAME +from validators.swagger_request_models import BaseModel +from validators.utils import validate_kwargs class RuleMetaHandler(AbstractHandler): """ Manage Rule API """ - - @staticmethod - def _only_for_system(event: dict): - if event.get(USER_CUSTOMER_ATTR) != SYSTEM_CUSTOMER: - return build_response(code=HTTPStatus.FORBIDDEN, - content='Not allowed') - def __init__(self, lambda_client: LambdaClient): self._lambda_client = lambda_client @classmethod def build(cls) -> 'RuleMetaHandler': return cls( - lambda_client=SERVICE_PROVIDER.lambda_func() + lambda_client=SERVICE_PROVIDER.lambda_client ) - def define_action_mapping(self): + @cached_property + def mapping(self) -> Mapping: return { - '/rule-meta/standards': { + CustodianEndpoint.META_STANDARDS: { HTTPMethod.POST: self.pull_standards, }, - '/rule-meta/mappings': { + CustodianEndpoint.META_MAPPINGS: { HTTPMethod.POST: self.pull_mappings, }, - '/rule-meta/meta': { + CustodianEndpoint.META_META: { HTTPMethod.POST: self.pull_meta, } } - def pull_standards(self, event: dict): - self._only_for_system(event) + @validate_kwargs + def pull_standards(self, event: BaseModel): self._lambda_client.invoke_function_async( RULE_META_UPDATER_LAMBDA_NAME, {'action': 'standards'}) return build_response(code=HTTPStatus.ACCEPTED, content='Standards update was triggered') - def pull_mappings(self, event: dict): - self._only_for_system(event) + @validate_kwargs + def pull_mappings(self, event: BaseModel): self._lambda_client.invoke_function_async( RULE_META_UPDATER_LAMBDA_NAME, {'action': 'mappings'}) return build_response(code=HTTPStatus.ACCEPTED, content='Meta mappings update was triggered') - def pull_meta(self, event: dict): - self._only_for_system(event) + @validate_kwargs + def pull_meta(self, event: BaseModel): # Purposefully don't allow to update meta # because currently we use only meta from mappings self._lambda_client.invoke_function_async( - RULE_META_UPDATER_LAMBDA_NAME, {'action': 'mappings'} # not a mistake + RULE_META_UPDATER_LAMBDA_NAME, {'action': 'mappings'} + # not a mistake ) return build_response(code=HTTPStatus.ACCEPTED, content='Meta update was triggered') diff --git a/src/handlers/rule_source_handler.py b/src/handlers/rule_source_handler.py index 4e9f555c8..313ee2751 100644 --- a/src/handlers/rule_source_handler.py +++ b/src/handlers/rule_source_handler.py @@ -1,44 +1,33 @@ +from functools import cached_property from http import HTTPStatus -from typing import Optional, Iterable, List -from handlers.abstracts.abstract_handler import AbstractHandler -from helpers import build_response -from helpers.constants import CUSTOMER_ATTR, HTTPMethod, \ - RULE_SOURCE_REQUIRED_ATTRS, GIT_PROJECT_ID_ATTR, \ - ID_ATTR, ALLOWED_FOR_ATTR, TENANTS_ATTR, TENANT_RESTRICTION, \ - TENANT_ALLOWANCE, DESCRIPTION_ATTR, GIT_URL_ATTR, GIT_REF_ATTR, \ - GIT_RULES_PREFIX_ATTR -from helpers.log_helper import get_logger +from handlers import AbstractHandler, Mapping +from helpers.constants import CustodianEndpoint, HTTPMethod +from helpers.lambda_response import ResponseFactory, build_response from helpers.system_customer import SYSTEM_CUSTOMER from models.rule_source import RuleSource -from services.modular_service import ModularService -from services.rbac.restriction_service import RestrictionService +from services import SP from services.rule_meta_service import RuleService from services.rule_source_service import RuleSourceService - -_LOG = get_logger(__name__) -RULE_SOURCES_NOT_FOUND_MESSAGE = 'No rule sources found matching given query' -CONCRETE_RULE_SOURCE_NOT_FOUND_MESSAGE = \ - 'Customer \'{customer}\' does not have a rule-source ' \ - 'with project id \'{rule_source_id}\'' -RULE_SOURCE_FOUND_MESSAGE = \ - 'Customer \'{customer}\' already has a rule-source ' \ - 'with project id \'{rule_source_id}\'' +from validators.swagger_request_models import ( + RuleSourceDeleteModel, + RuleSourceGetModel, + RuleSourcePatchModel, + RuleSourcePostModel, +) +from validators.utils import validate_kwargs class RuleSourceHandler(AbstractHandler): def __init__(self, rule_source_service: RuleSourceService, - modular_service: ModularService, - rule_service: RuleService, - restriction_service: RestrictionService): + rule_service: RuleService): self.rule_source_service = rule_source_service - self.modular_service = modular_service self.rule_service = rule_service - self.restriction_service = restriction_service - def define_action_mapping(self): + @cached_property + def mapping(self) -> Mapping: return { - '/rule-sources': { + CustodianEndpoint.RULE_SOURCES: { HTTPMethod.GET: self.get_rule_source, HTTPMethod.POST: self.create_rule_source, HTTPMethod.PATCH: self.update_rule_source, @@ -46,42 +35,36 @@ def define_action_mapping(self): } } - def get_rule_source(self, event): - _LOG.debug(f'Describe rule source event: {event}') - customer = event.get(CUSTOMER_ATTR) - tenants = event.get(TENANTS_ATTR) + @classmethod + def build(cls): + return cls( + rule_source_service=SP.rule_source_service, + rule_service=SP.rule_service, + ) - rule_source_id = event.get(ID_ATTR) + @validate_kwargs + def get_rule_source(self, event: RuleSourceGetModel): + customer = event.customer or SYSTEM_CUSTOMER + rule_source_id = event.id service = self.rule_source_service if rule_source_id: - entity = self._attain_rule_source( + entity = self._obtain_rule_source( rule_source_id=rule_source_id, - customer=customer, tenants=tenants + customer=customer, ) if not entity: return build_response( - content=f'Rule-source:{rule_source_id!r} does not exist.', + content=f'Rule-source:{rule_source_id} does not exist.', code=HTTPStatus.NOT_FOUND ) rule_sources = [entity] else: - git_project_id = event.get(GIT_PROJECT_ID_ATTR) - params = dict(customer=customer, git_project_id=git_project_id) - _list_rule_sources = self.rule_source_service.list_rule_sources - rule_sources = self.rule_source_service.filter_by_tenants( - _list_rule_sources(**params) + git_project_id = event.git_project_id + rule_sources = self.rule_source_service.list_rule_sources( + customer=customer, + git_project_id=git_project_id ) - # DO NOT remove the commented code. It's a disabled feature - # Note: first be must filter by tenants and second - expand - # with SYSTEM's (in case inherit=True of course) and NOT vice versa. - # That is why, we cannot use `derive_from_system` decorator - # if self.modular_service.customer_inherit(customer): - # rule_sources = self.rule_source_service.expand_systems( - # _list_rule_sources(**self.rule_source_service.system_payload( - # params)), rule_sources - # ) - _LOG.debug(f'Rule-sources to return: {rule_sources}') return build_response( code=HTTPStatus.OK, content=[ @@ -90,42 +73,15 @@ def get_rule_source(self, event): ] ) - def create_rule_source(self, event): - """ - Handles POST request, by either: - * creating a new rule-source - * or updating tenants & git-access-credentials - of the rule-source - :param event: dict - :return: dict - """ - _LOG.debug(f'Create rule source event: {event}') - customer = event.get(CUSTOMER_ATTR) or SYSTEM_CUSTOMER - git_url = event.get(GIT_URL_ATTR) - git_project_id = event.get(GIT_PROJECT_ID_ATTR) - git_ref = event.get(GIT_REF_ATTR) - git_rules_prefix = event.get(GIT_RULES_PREFIX_ATTR) - description = event.get(DESCRIPTION_ATTR) - - repo_conf = { - attr: event.get(attr) for attr in - RULE_SOURCE_REQUIRED_ATTRS - } - repo_conf[CUSTOMER_ATTR] = customer - - # Given no explicit tenants to allow access for - refer to user's - allowed_tenants = ( - event.get(TENANT_ALLOWANCE) or event.get(TENANTS_ATTR)) + @validate_kwargs + def create_rule_source(self, event: RuleSourcePostModel): + customer = event.customer or SYSTEM_CUSTOMER + git_url = event.git_url + git_project_id = event.git_project_id + git_ref = event.git_ref + git_rules_prefix = event.git_rules_prefix - service = self.rule_source_service - - errors = self._check_tenants(allowed_tenants) - if errors: - return build_response( - code=HTTPStatus.NOT_FOUND, - content='\n'.join(errors) - ) - rsid = service.derive_rule_source_id( + rsid = self.rule_source_service.derive_rule_source_id( customer=customer, git_url=git_url, git_project_id=git_project_id, @@ -133,234 +89,98 @@ def create_rule_source(self, event): git_rules_prefix=git_rules_prefix ) - title = f'Rule-source:{rsid!r}' # Obtaining just an entity. - entity = self._attain_rule_source( + entity = self._obtain_rule_source( rule_source_id=rsid, customer=None, - tenants=None ) if entity: - _LOG.info(f'{title} - exists, checking for accessibility.') - is_subject_applicable = service.is_subject_applicable - is_applicable = is_subject_applicable( - rule_source=entity, customer=customer, - tenants=allowed_tenants - ) - - if not is_applicable: - return self._respond_with_unauthorized(entity=entity) - - _LOG.info(f'{title} - checking for new tenants to update for.') - derive_updated_tenants = service.derive_updated_tenants - updated = derive_updated_tenants( - rule_source=entity, - tenants=allowed_tenants, - restrict=False - ) - if not updated: - return self._respond_with_already_exists(entity=entity) - - repo_conf[ALLOWED_FOR_ATTR] = allowed_tenants - _LOG.info(f'{title} updating, based on - {repo_conf}.') - entity = service.update_rule_source( - rule_source=entity, - rule_source_data=repo_conf - ) - - else: - repo_conf[ALLOWED_FOR_ATTR] = allowed_tenants - _LOG.info( - f'{title} does not exist, creating one, based on - {repo_conf}.') - entity = service.create_rule_source(rule_source_data=repo_conf) - - entity.description = description - service.save_rule_source(entity) + raise ResponseFactory(HTTPStatus.CONFLICT).message( + 'Such rule source already exists within a customer' + ).exc() + self.rule_source_service.validate_git_access_data( + git_project_id=git_project_id, + git_url=git_url, + git_access_secret=event.git_access_secret + ) + entity = self.rule_source_service.create_rule_source( + git_project_id=git_project_id, + git_url=git_url, + git_ref=git_ref, + git_rules_prefix=git_rules_prefix, + git_access_type=event.git_access_type, + customer=customer, + description=event.description, + git_access_secret=event.git_access_secret + ) + self.rule_source_service.save_rule_source(entity) return build_response( - code=HTTPStatus.OK, - content=service.get_rule_source_dto(rule_source=entity) + code=HTTPStatus.CREATED, + content=self.rule_source_service.get_rule_source_dto(rule_source=entity) ) - def update_rule_source(self, event): - _LOG.debug(f'Update rule source event: {event}') - customer = event.get(CUSTOMER_ATTR) or SYSTEM_CUSTOMER - tenants = event.get(TENANTS_ATTR) or [] # Authorization-claim - description = event.get(DESCRIPTION_ATTR) - rsid = event.get(ID_ATTR) # noqa rule_source_id - title = f'Rule-Source:{rsid!r}' - - service = self.rule_source_service + @validate_kwargs + def update_rule_source(self, event: RuleSourcePatchModel): + customer = event.customer or SYSTEM_CUSTOMER - entity = self._attain_rule_source( - rule_source_id=rsid, + entity = self._obtain_rule_source( + rule_source_id=event.id, customer=customer, - tenants=tenants # Checks for user-tenant access. ) if not entity: return build_response( code=HTTPStatus.NOT_FOUND, - content=f'{title} does not exist.' + content='Rule source not found' ) - repo_conf = { - attr: event.get(attr) for attr in - RULE_SOURCE_REQUIRED_ATTRS - } - - to_allow = event.get(TENANT_ALLOWANCE) - to_restrict = event.get(TENANT_RESTRICTION) - errors = self._check_tenants( - tenants=(to_allow or []) + (to_restrict or []) - ) - if errors: - return build_response( - code=HTTPStatus.NOT_FOUND, - content='\n'.join(errors) + if event.git_access_secret: + self.rule_source_service.validate_git_access_data( + git_project_id=entity.git_project_id, + git_url=entity.git_url, + git_access_secret=event.git_access_secret ) - - action_to_update_with_map = zip((False, True), (to_allow, to_restrict)) - allowed_for = entity.allowed_for or [] - for action, update_with in action_to_update_with_map: - if update_with is not None: - updated = service.derive_updated_tenants( - rule_source=entity, - tenants=update_with, - restrict=action - ) # Updates iff attached-tenants have changed. - allowed_for = updated or allowed_for - - # If allowed_for := [] i.e., allows ALL - check user authorization. - if not allowed_for and tenants: - return self._respond_with_unauthorized(entity=entity) - - repo_conf[ALLOWED_FOR_ATTR] = allowed_for - - _LOG.debug(f'{title} - being updated with {repo_conf}.') - entity = service.update_rule_source( - rule_source=entity, - rule_source_data=repo_conf - ) - _LOG.debug(f'{title} - saving data.') - if description: - entity.description = description - service.save_rule_source(rule_source=entity) + self.rule_source_service.set_secret(entity, + event.git_access_secret) + if event.description: + entity.description = event.description + self.rule_source_service.save_rule_source(rule_source=entity) return build_response( code=HTTPStatus.OK, - content=service.get_rule_source_dto(rule_source=entity) + content=self.rule_source_service.get_rule_source_dto(rule_source=entity) ) - def delete_rule_source(self, event): - _LOG.debug(f'Delete rule source event: {event}') + @validate_kwargs + def delete_rule_source(self, event: RuleSourceDeleteModel): - customer = event.get(CUSTOMER_ATTR) or SYSTEM_CUSTOMER - tenants = event.get(TENANTS_ATTR) or [] # Authorization-claim - rsid = event.get(ID_ATTR) # noqa rule_source_id - title = f'Rule-Source:{rsid!r}' - scope = ', '.join(map("'{}'".format, tenants)) + ' tenants' + customer = event.customer or SYSTEM_CUSTOMER service = self.rule_source_service - entity = self._attain_rule_source( - rule_source_id=rsid, + entity = self._obtain_rule_source( + rule_source_id=event.id, customer=customer, - tenants=tenants # Checks for user-tenant access. ) - if not entity: return build_response( code=HTTPStatus.NOT_FOUND, - content=f'{title} does not exist.' + content=f'Rule source {event.id} does not exist.' ) - _LOG.info(f'{title} - restricting access for {scope}.') - allowed_for = service.derive_updated_tenants( - rule_source=entity, - tenants=tenants, - restrict=True + rules = self.rule_service.get_by_location_index( + customer=customer, + project=entity.git_project_id ) - # Removes iff auth-claim affects to all/attached tenants. - to_remove = not tenants - to_remove |= not allowed_for and allowed_for is not None - - if to_remove: - _LOG.info(f'{title} - removing all related rules.') - rules = self.rule_service.get_by_location_index( - customer=customer, - project=entity.git_project_id - ) - self.rule_service.batch_delete(rules) - _LOG.info(f'{title} - being removed.') - service.delete_rule_source(rule_source=entity) - elif allowed_for: - _LOG.info(f'{title} removing access of {scope}.') - entity.allowed_for = allowed_for - _LOG.info(f'{title} - being persisted.') - service.save_rule_source(rule_source=entity) - else: - # Error within ._attain_rule_source, failed to assert either: - # * user-tenants == [] - # * set(user-tenants) intersects set(rule_source.allowed_for) - raise RuntimeError(f'{title} tenant-access has not changed.') - + self.rule_service.batch_delete(rules) + service.delete_rule_source(rule_source=entity) return build_response(code=HTTPStatus.NO_CONTENT) - def _attain_rule_source( - self, rule_source_id: str, - customer: Optional[str] = None, - tenants: Optional[List[str]] = None - ): - """ - Returns a rule-source entity, on a given identifier, - provided no customer, tenant conflict is present. - Note: rule-source is attainable, given any tenant - is allowed access. - - :param rule_source_id: str - :param customer: str - :param tenants: List[str] - :return: Optional[RuleSource] - """ - log_head = f'Rule-source:{rule_source_id!r}' + def _obtain_rule_source(self, rule_source_id: str, + customer: str | None = None) -> RuleSource | None: service = self.rule_source_service - _LOG.info(f'{log_head} - obtaining entity.') - is_subject_applicable = service.is_subject_applicable entity = service.get(rule_source_id=rule_source_id) - if not entity: - _LOG.warning(f'{log_head} - does not exist.') - elif not is_subject_applicable(rule_source=entity, customer=customer, - tenants=tenants): - entity = None + if not entity or customer and entity.customer != customer: + return return entity - - def _check_tenants(self, tenants: Iterable[str]) -> List[str]: - errors = [] - _LOG.info(f'Verifying whether access to {", ".join(tenants)} tenants.') - for tenant_name in tenants: - if not self.restriction_service.is_allowed_tenant(tenant_name): - errors.append(f'Tenant {tenant_name} not found') - return errors - - @classmethod - def _respond_with_already_exists(cls, entity: RuleSource): - title = cls._get_response_title(entity=entity) - message = f'{title} - already exists' - return build_response( - code=HTTPStatus.CONFLICT, - content=message - ) - - @classmethod - def _respond_with_unauthorized(cls, entity: RuleSource): - title = cls._get_response_title(entity=entity) - message = f'{title} access could not be authorized.' - return build_response( - code=HTTPStatus.UNAUTHORIZED, - content=message - ) - - @staticmethod - def _get_response_title(entity: RuleSource): - return f'Rule-source:{entity.id!r}' diff --git a/src/handlers/rules_handler.py b/src/handlers/rules_handler.py index e30ac0381..76a739ecb 100644 --- a/src/handlers/rules_handler.py +++ b/src/handlers/rules_handler.py @@ -1,485 +1,183 @@ -from datetime import datetime +import io +from functools import cached_property from http import HTTPStatus -from typing import List, Optional, Dict - -from handlers.base_handler import \ - BaseReportHandler, \ - SourceReportDerivation, \ - EntitySourcedReportDerivation, SourcedReport, Report, EntityToReport -from helpers.constants import ( - HTTPMethod, CUSTOMER_ATTR, TENANTS_ATTR, TENANT_ATTR, - START_ISO_ATTR, END_ISO_ATTR, HREF_ATTR, CONTENT_ATTR, - ID_ATTR, FORMAT_ATTR, - JSON_ATTR, RULE_ATTR +from typing import Iterator + +from modular_sdk.services.tenant_service import TenantService +from xlsxwriter import Workbook +from xlsxwriter.worksheet import Worksheet + +from handlers import AbstractHandler, Mapping +from helpers.constants import CustodianEndpoint, HTTPMethod, JobState, \ + ReportFormat +from helpers.lambda_response import build_response +from services import SP +from services import modular_helpers +from services.ambiguous_job_service import AmbiguousJobService +from services.report_service import ReportResponse, ReportService +from services.xlsx_writer import CellContent, Table, XlsxRowsWriter +from validators.swagger_request_models import ( + JobRuleReportGetModel, + TenantRuleReportGetModel, ) -from helpers.log_helper import get_logger -from services.ambiguous_job_service import Source -from services.report_service import \ - STATISTICS_FILE - -DEFAULT_UNRESOLVABLE_RESPONSE = 'Request has run into an unresolvable issue.' - -TYPE_ATTR = 'type' - -ENTITY_ATTR_KEY = 'entity_attr' -ENTITY_VALUE_KEY = 'entity_value' - -_LOG = get_logger(__name__) - -# Report Errors of Jobs resources -JOB_ENDPOINT = '/reports/rules/jobs/{id}' -# Report Errors of accumulated Jobs, driven by entity-driven scope. -TENANTS_ENDPOINT = '/reports/rules/tenants' -TENANT_ENDPOINT = '/reports/rules/tenants/{tenant_name}' - -NO_RESOURCES_FOR_REPORT = ' maintain(s) no resources to derive a report.' +from validators.utils import validate_kwargs -class BaseRulesReportHandler(BaseReportHandler): - """ - Provides base behaviour of rule-reporting, establishing - report-derivation function. - """ - - @property - def _source_report_derivation_function(self) -> SourceReportDerivation: - return self._statistics_report_derivation - - def _statistics_report_derivation( - self, source: Source, **kwargs: dict - ) -> Optional[Report]: - - """ - Obtains report of rule-failed statistics of a sourced job, - returning a respective report. - :param source: Source - :param kwargs: Dict - :return: Optional[Report] - """ - jid = self._ambiguous_job_service.get_attribute(source, ID_ATTR) - rs = self._report_service - path = rs.derive_job_object_path(job_id=jid, typ=STATISTICS_FILE) - return rs.pull_job_statistics(path=path) - - @property - def _entity_sourced_report_derivation_function(self) -> \ - EntitySourcedReportDerivation: - return self._entity_statistics_report_derivation +class RulesReportXlsxWriter: + def __init__(self, it: Iterator[dict]): + self._it = it - def _entity_statistics_report_derivation( - self, sourced_reports: List[SourcedReport], **kwargs: dict - ) -> Optional[Report]: + @cached_property + def head(self) -> list: + return [ + 'Rule', 'Region', 'Executed successfully', + 'Execution time (seconds)', 'Failed Resources' + ] - entity_attr: str = kwargs.get(ENTITY_ATTR_KEY, '') - entity_value: str = kwargs.get(ENTITY_VALUE_KEY, '') - href: bool = kwargs.get(HREF_ATTR, False) - frmt: str = kwargs.get(FORMAT_ATTR, '') - target_rule: str = kwargs.get(RULE_ATTR, '') - assert entity_attr, 'Entity attribute is missing' - return self._attain_statistics_report( - sourced_reports=sourced_reports, - entity_attr=entity_attr, - entity_value=entity_value, - href=href, frmt=frmt, - target_rule=target_rule - ) - - def _attain_statistics_report( - self, sourced_reports: List[SourcedReport], - entity_attr: str, entity_value: Optional[str] = None, - href: Optional[bool] = False, frmt: Optional[str] = None, - target_rule: Optional[str] = None - ): + @staticmethod + def status_empty(st: bool) -> str: """ - Derives relation map of failed rule, merged amongst sourced reports. - :param sourced_reports: List[Source, List[Dict]] - :param entity_attr: str, denotes unique entity id-attribute - :param entity_value: Optional[str], denotes ta target entity-id value - :return: Dict[str, List[Dict]] + What value will be should in 'Failed Resources' field in case rule + was executed successfully or unsuccessfully + :param st: + :return: """ - head = entity_attr - if entity_value: - head += f':\'{entity_value}\'' - - ajs = self._ambiguous_job_service - rs = self._report_service - - statistics_list: List[List[Dict]] = [] - _LOG.info(head + ' extending statistics list, for each sourced list.') - - start, end = None, None - source = None - for sourced in sourced_reports: - source, statistics = sourced - - if not entity_value: - _entity = ajs.get_attribute(item=source, attr=entity_attr) - if not entity_value: - entity_value = _entity - - elif entity_value != _entity: - # Precautionary verification. - typ = ajs.get_type(item=source) - uid = ajs.get_attribute(item=source, attr=ID_ATTR) - _e_scope = f'{head}:\'{entity_value}\'' - _s_scope = f'{_entity} of {typ} \'{uid}\' job' - _LOG.error(f'{_e_scope} mismatches {_s_scope}.') - failed_rule_map = {} - break - - # Establishes start and end dates, for the report. - sk = ajs.sort_value_into_datetime(item=source) - if not start or not end: - start = end = sk - start = sk if sk > start else start - end = sk if sk < end else end - - # Derives failed rule map. - statistics_list.append(statistics) - - job_id = self._ambiguous_job_service.get_attribute(source, ID_ATTR) \ - if len(sourced_reports) == 1 else None - object_path = rs.derive_rule_report_object_path( - entity_value=entity_value, entity_attr=entity_attr, - start=start, end=end, fext=frmt, job_id=job_id - ) - - if href and frmt: - _LOG.info(head + ' going to obtain hypertext reference' - ' of the rule statistic report.') - ref: Optional[str] = self._report_service.href_job_report( - path=object_path, check=True + if st: + return '0' + return '' + + def write(self, wsh: Worksheet, wb: Workbook): + bold = wb.add_format({'bold': True}) + red = wb.add_format({'bg_color': '#da9694'}) + green = wb.add_format({'bg_color': '#92d051'}) + + def status_color(st: bool): + if st: + return green + return red + + remapped = {} + for item in self._it: + remapped.setdefault(item['policy'], []).append(item) + # sorts buy the total number of failed resources per rule + # across all the regions + data = dict(sorted( + remapped.items(), + key=lambda p: sum(i.get('failed_resources') or 0 for i in p[1]), + reverse=True + )) + + table = Table() + table.new_row() + for h in self.head: + table.add_cells(CellContent(h, bold)) + for rule, items in data.items(): + table.new_row() + table.add_cells(CellContent(rule)) + items = sorted( + items, + key=lambda i: i.get('failed_resources') or 0, + reverse=True ) - if ref: - return ref - - # Otherwise produces data, on its own. - - message = ' removing duplicates' - if target_rule: - message += f', filtering by {target_rule} rule' - message += ' within aggregated statistic lists.' - _LOG.info(head + message) - - statistics_list: List[ - List[Dict]] = rs.derive_clean_filtered_statistics_list( - statistic_list=statistics_list, target_rule=target_rule - ) - if not statistics_list: - _LOG.warning(head + ' no statistics could be established.') - - else: - - _LOG.info(head + ' averaging out statistics.') - statistics_list: List[Dict] = rs.average_out_statistics( - statistics_list=statistics_list - ) - - if statistics_list and href and frmt: - message = f' providing hypertext reference to {frmt} file of' - message += ' rule statistics.' - _LOG.info(head + message) - if frmt == JSON_ATTR: - message = ' retaining error json hypertext reference.' - _LOG.info(head + message) - if not rs.put_json_concrete_report( - data=statistics_list, path=object_path - ): - object_path = None - - else: # xlsx - _LOG.info(head + ' deriving xlsx statistics report.') - file_name = rs.derive_name_of_report_object_path( - object_path=object_path - ) - stream_path = rs.derive_rule_statistics_report_xlsx_path( - file_name=file_name, - averaged_statistics=statistics_list - ) - if stream_path: - message = ' retaining xlsx hypertext reference.' - _LOG.warning(head + message) - if rs.put_path_retained_concrete_report( - stream_path=stream_path, - object_path=object_path - ) is None: - message = ' xlsx statistic report hypertext could' - message += ' not be provided.' - _LOG.warning(head + message) - # Explicitly denote reference absence. - object_path = None - - if object_path: - message = ' obtaining hypertext reference.' - _LOG.info(head + message) - statistics_list = rs.href_concrete_report( - path=object_path, check=False - ) - - return statistics_list - - -class JobsRulesHandler(BaseRulesReportHandler): - - def define_action_mapping(self): - return { - JOB_ENDPOINT: { - HTTPMethod.GET: self.get_job - } - } - - def get_job(self, event: dict): - _LOG.info(f'GET Job Rule Report - {event}.') - # Note that `job_id` denotes the primary-key's hash-key of entities. - uid: str = event[ID_ATTR] - typ: str = event[TYPE_ATTR] - customer = event.get(CUSTOMER_ATTR) - tenants = event.get(TENANTS_ATTR) - href = event.get(HREF_ATTR) - frmt = event.get(FORMAT_ATTR) - rule = event.get(RULE_ATTR) - - entity_attr = f'{typ.capitalize()} Job' - entity_value = uid - head = f'{entity_attr}:\'{entity_value}\'' - - source = self._attain_source( - uid=uid, typ=typ, customer=customer, tenants=tenants - ) - if not source: - return self.response - - statistics = self._statistics_report_derivation(source=source) - if not statistics: - _LOG.warning(head + ' could not obtain statistics report.') - self._code = HTTPStatus.NOT_FOUND - self._content = head + NO_RESOURCES_FOR_REPORT - return self.response - - statistic_report = self._attain_statistics_report( - sourced_reports=[(source, statistics)], - entity_attr=entity_attr, entity_value=entity_value, - href=href, frmt=frmt, target_rule=rule + table.add_cells(*[ + CellContent(item['region']) for item in items + ]) + table.add_cells(*[ + CellContent(str(item['succeeded']).lower(), + status_color(item['succeeded'])) + for item in items + ]) + table.add_cells(*[ + CellContent(item['execution_time']) for item in items + ]) + table.add_cells(*[ + CellContent(item.get('failed_resources') or + self.status_empty(item['succeeded'])) + for item in items + ]) + writer = XlsxRowsWriter() + writer.write(wsh, table) + + +class JobsRulesHandler(AbstractHandler): + def __init__(self, ambiguous_job_service: AmbiguousJobService, + report_service: ReportService, + tenant_service: TenantService): + self._ambiguous_job_service = ambiguous_job_service + self._report_service = report_service + self._tenant_service = tenant_service + + @classmethod + def build(cls) -> 'AbstractHandler': + return cls( + ambiguous_job_service=SP.ambiguous_job_service, + report_service=SP.report_service, + tenant_service=SP.modular_client.tenant_service() ) - if not statistic_report: - _LOG.warning(head + ' could not derive rule statistics.') - self._code = HTTPStatus.NOT_FOUND - self._content = head + NO_RESOURCES_FOR_REPORT - return self.response - else: - ref_attr = HREF_ATTR if href else CONTENT_ATTR - self._code = HTTPStatus.OK - self._content = [ - self.dto( - source=source, report=statistic_report, ref_attr=ref_attr - ) - ] - - return self.response - def dto(self, source: Source, report: Report, ref_attr: str): + @cached_property + def mapping(self) -> Mapping: return { - ID_ATTR: self._ambiguous_job_service.get_attribute( - item=source, attr=ID_ATTR - ), - TYPE_ATTR: self._ambiguous_job_service.get_type(item=source), - ref_attr: report - } - - -class EntityRulesHandler(BaseRulesReportHandler): - - def define_action_mapping(self): - return { - TENANT_ENDPOINT: { - HTTPMethod.GET: self.get_by_tenant - }, - TENANTS_ENDPOINT: { - HTTPMethod.GET: self.query_by_tenant + CustodianEndpoint.REPORTS_RULES_JOBS_JOB_ID: { + HTTPMethod.GET: self.get_by_job }, + CustodianEndpoint.REPORTS_RULES_TENANTS_TENANT_NAME: { + HTTPMethod.GET: self.get_by_tenant_accumulated + } } - def get_by_tenant(self, event: dict): - _LOG.info(f'GET Rule Report(s) of a Tenant - {event}.') - # `tenant` has been injected via the restriction service. - tenant_name = event[TENANT_ATTR] - # Lower bound. - start_iso: datetime = event[START_ISO_ATTR] - # Upper bound. - end_iso: datetime = event[END_ISO_ATTR] - - href = event.get(HREF_ATTR) - customer = event.get(CUSTOMER_ATTR) - tenant = self._attain_tenant( - name=tenant_name, customer=customer, active=True + @validate_kwargs + def get_by_job(self, event: JobRuleReportGetModel, job_id: str): + job = self._ambiguous_job_service.get_job( + job_id=job_id, + typ=event.job_type, + customer=event.customer ) - if not tenant: - return self.response - - referenced_reports = self._attain_referenced_reports( - entity_attr=TENANT_ATTR, entity_value=tenant_name, - start_iso=start_iso, end_iso=end_iso, - customer=tenant.customer_name, tenants=[tenant_name], - typ=event.get(TYPE_ATTR), href=href, frmt=event.get(FORMAT_ATTR), - target_rule=event.get(RULE_ATTR) - ) - if referenced_reports: - ref_attr = HREF_ATTR if href else CONTENT_ATTR - self._code = HTTPStatus.OK - self._content = [ - self.dto( - entity_attr=TENANT_ATTR, entity_value=entity, - report=report, ref_attr=ref_attr - ) - for entity, report in referenced_reports.items() - ] - return self.response - - def query_by_tenant(self, event: dict): - _LOG.info(f'GET Rule Report(s) of Tenant(s) - {event}.') - - # `tenants` has been injected via the restriction service. - tenant_names = event[TENANTS_ATTR] - start_iso: datetime = event[START_ISO_ATTR] - end_iso: datetime = event[END_ISO_ATTR] - - customer = event.get(CUSTOMER_ATTR) - href = event.get(HREF_ATTR) - - referenced_reports = self._attain_referenced_reports( - entity_attr=TENANT_ATTR, - start_iso=start_iso, end_iso=end_iso, - customer=customer, tenants=tenant_names, - typ=event.get(TYPE_ATTR), - href=href, frmt=event.get(FORMAT_ATTR), - target_rule=event.get(RULE_ATTR) - ) - - if referenced_reports: - ref_attr = HREF_ATTR if href else CONTENT_ATTR - self._code = HTTPStatus.OK - self._content = [ - self.dto( - entity_attr=TENANT_ATTR, entity_value=entity, - report=report, ref_attr=ref_attr + if not job: + return build_response( + content='The request job not found', + code=HTTPStatus.NOT_FOUND + ) + statistics = self._report_service.job_statistics(job.job) + data = map(self._report_service.format_statistic, statistics) + content = [] + match event.format: + case ReportFormat.JSON: + if event.href: + url = self._report_service.one_time_url_json( + list(data), f'{job.id}-rules.json' + ) + content = ReportResponse(job, url).dict() + else: + content = list(data) + case ReportFormat.XLSX: + buffer = io.BytesIO() + with Workbook(buffer, {'strings_to_numbers': True}) as wb: + RulesReportXlsxWriter(data).write( + wb=wb, + wsh=wb.add_worksheet('Rules') + ) + buffer.seek(0) + url = self._report_service.one_time_url( + buffer, f'{job.id}-rules.xlsx' ) - for entity, report in referenced_reports.items() - ] - - return self.response - - def _attain_referenced_reports( - self, entity_attr: str, - start_iso: datetime, end_iso: datetime, - customer: Optional[str] = None, - tenants: Optional[List[str]] = None, - cloud_ids: Optional[List[str]] = None, - typ: Optional[str] = None, - href: bool = False, frmt: Optional[str] = None, - entity_value: Optional[str] = None, - target_rule: Optional[str] = None - ) -> EntityToReport: - cloud_ids = cloud_ids or [] - ajs = self._ambiguous_job_service - - head = '' - - # Log-Header. - - if tenants: - multiple = len(tenants) > 1 - bind = ', '.join(map("'{}'".format, tenants or [])) - if head: - bind = f', bound to {bind}' - head += f'{bind} tenant' - if multiple: - head += 's' - - if customer: - head = 'Tenants' if not head else head - head += f' of \'{customer}\' customer' - - typ_scope = f'{typ} type' if typ else 'all types' - time_scope = f'from {start_iso.isoformat()} till {end_iso.isoformat()}' - job_scope = f'job(s) of {typ_scope}, {time_scope}' - - entity_scope = f'\'{entity_attr}\' entity' - if entity_value: - entity_scope += f' of the \'{entity_value}\' target value' - - # todo Responsibility chain - - _LOG.info(f'Obtaining {job_scope}, for {head or "tenants"}.') - head = head or 'Tenants' - typ_params_map = ajs.derive_typ_param_map( - typ=typ, tenants=tenants, - cloud_ids=cloud_ids - ) - source_list = ajs.batch_list( - typ_params_map=typ_params_map, customer=customer, - start=start_iso, end=end_iso, sort=False + content = ReportResponse(job, url, ReportFormat.XLSX).dict() + return build_response(content=content) + + @validate_kwargs + def get_by_tenant_accumulated(self, event: TenantRuleReportGetModel, + tenant_name: str): + tenant = self._tenant_service.get(tenant_name) + modular_helpers.assert_tenant_valid(tenant) + + jobs = self._ambiguous_job_service.get_by_tenant_name( + tenant_name=tenant_name, + job_type=event.job_type, + status=JobState.SUCCEEDED, + start=event.start_iso, + end=event.end_iso, ) - if not source_list: - message = f' - no source-data of {job_scope} could be derived.' - _LOG.warning(head + message) - self._code = HTTPStatus.NOT_FOUND - self._content = head + NO_RESOURCES_FOR_REPORT - return self.response - - _source_scope = ', '.join( - ajs.get_attribute(item=s, attr=ID_ATTR) for s in source_list - ) - - message = f' retrieving statistics of {_source_scope} source(s).' - _LOG.info(head + message) - source_to_report = self._attain_source_report_map( - source_list=source_list - ) - if not source_to_report: - message = f' - no reports of {job_scope} could be derived.' - _LOG.warning(head + message) - self._code = HTTPStatus.NOT_FOUND - self._content = head + NO_RESOURCES_FOR_REPORT - return self.response - - message = f' mapping sourced reports to {entity_scope}.' - _LOG.info(head + message) - - entity_sourced_reports = self._attain_entity_sourced_reports( - entity_attr=entity_attr, entity_value=entity_value, - source_to_report=source_to_report - ) - if not entity_sourced_reports: - message = f' - no reports of {job_scope} could be mapped ' - message += f' to {entity_scope}.' - _LOG.warning(head + message) - self._code = HTTPStatus.NOT_FOUND - self._content = head + NO_RESOURCES_FOR_REPORT - return self.response - - message = f' deriving failed rule reports of {entity_scope}.' - _LOG.info(head + message) - - entity_to_report = self._attain_entity_report_map_from_sourced_reports( - entity_sourced_reports=entity_sourced_reports, - entity_attr=entity_attr, entity_value=entity_value, - href=href, format=frmt, rule=target_rule - ) - if not entity_to_report: - message = f' - no reports of {job_scope} could be derived' - message += f' based on {entity_scope}.' - _LOG.warning(head + message) - self._code = HTTPStatus.NOT_FOUND - self._content = head + NO_RESOURCES_FOR_REPORT - return self.response - - return entity_to_report - - @staticmethod - def dto( - entity_attr: str, entity_value: str, report: Report, ref_attr: str - ): - return { - entity_attr: entity_value, - ref_attr: report - } + average = self._report_service.average_statistics(*map( + self._report_service.job_statistics, jobs + )) + return build_response(content=average) diff --git a/src/handlers/ruleset_handler.py b/src/handlers/ruleset_handler.py index cb8966906..cdc41e493 100644 --- a/src/handlers/ruleset_handler.py +++ b/src/handlers/ruleset_handler.py @@ -1,37 +1,50 @@ -import json from functools import cached_property from http import HTTPStatus from itertools import chain -from typing import Optional, Any, Iterable, List, Generator - -from handlers.abstracts.abstract_handler import AbstractHandler -from helpers import build_response -from helpers.constants import ID_ATTR, EVENT_DRIVEN_ATTR, VERSION_ATTR, \ - RULES_ATTR, CLOUD_ATTR, S3_PATH_ATTR, \ - ED_AWS_RULESET_NAME, ED_AZURE_RULESET_NAME, \ - ED_GOOGLE_RULESET_NAME, HTTPMethod, ED_KUBERNETES_RULESET_NAME -from helpers.enums import RuleDomain +from typing import Generator, Optional +from modular_sdk.commons.constants import ApplicationType + +from handlers import AbstractHandler, Mapping +from helpers.constants import ( + CustodianEndpoint, + ED_AWS_RULESET_NAME, + ED_AZURE_RULESET_NAME, + ED_GOOGLE_RULESET_NAME, + ED_KUBERNETES_RULESET_NAME, + EVENT_DRIVEN_ATTR, + HTTPMethod, + ID_ATTR, + RULES_ATTR, + RuleDomain, + S3_PATH_ATTR, +) +from helpers.lambda_response import ResponseFactory, build_response from helpers.log_helper import get_logger from helpers.reports import Standard from helpers.system_customer import SYSTEM_CUSTOMER from helpers.time_helper import utc_iso -from models.modular.application import CustodianLicensesApplicationMeta from models.rule import Rule from models.ruleset import Ruleset -from modular_sdk.commons.constants import ApplicationType from services import SERVICE_PROVIDER from services.clients.s3 import S3Client from services.environment_service import EnvironmentService from services.license_service import LicenseService -from services.modular_service import ModularService -from services.rbac.restriction_service import RestrictionService -from services.rule_meta_service import RuleService, LazyLoadedMappingsCollector +from services.mappings_collector import LazyLoadedMappingsCollector +from services.rule_meta_service import RuleService from services.rule_source_service import RuleSourceService from services.ruleset_service import RulesetService from services.setting_service import SettingsService -from validators.request_validation import RulesetGetModel, RulesetPostModel, \ - RulesetPatchModel, RulesetDeleteModel, RulesetContentGetModel, \ - EventDrivenRulesetGetModel, EventDrivenRulesetPostModel +from validators.swagger_request_models import ( + EventDrivenRulesetDeleteModel, + EventDrivenRulesetGetModel, + EventDrivenRulesetPostModel, + RulesetContentGetModel, + RulesetDeleteModel, + RulesetGetModel, + RulesetPatchModel, + RulesetPostModel, +) +from modular_sdk.services.application_service import ApplicationService from validators.utils import validate_kwargs _LOG = get_logger(__name__) @@ -47,21 +60,19 @@ class RulesetHandler(AbstractHandler): def __init__(self, ruleset_service: RulesetService, - modular_service: ModularService, + application_service: ApplicationService, rule_service: RuleService, s3_client: S3Client, environment_service: EnvironmentService, - restriction_service: RestrictionService, rule_source_service: RuleSourceService, license_service: LicenseService, settings_service: SettingsService, mappings_collector: LazyLoadedMappingsCollector): self.ruleset_service = ruleset_service - self.modular_service = modular_service + self.application_service = application_service self.rule_service = rule_service self.s3_client = s3_client self.environment_service = environment_service - self.restriction_service = restriction_service self.rule_source_service = rule_source_service self.license_service = license_service self.settings_service = settings_service @@ -70,30 +81,30 @@ def __init__(self, ruleset_service: RulesetService, @classmethod def build(cls) -> 'RulesetHandler': return cls( - ruleset_service=SERVICE_PROVIDER.ruleset_service(), - modular_service=SERVICE_PROVIDER.modular_service(), - rule_service=SERVICE_PROVIDER.rule_service(), - s3_client=SERVICE_PROVIDER.s3(), - environment_service=SERVICE_PROVIDER.environment_service(), - restriction_service=SERVICE_PROVIDER.restriction_service(), - rule_source_service=SERVICE_PROVIDER.rule_source_service(), - license_service=SERVICE_PROVIDER.license_service(), - settings_service=SERVICE_PROVIDER.settings_service(), - mappings_collector=SERVICE_PROVIDER.mappings_collector() + ruleset_service=SERVICE_PROVIDER.ruleset_service, + application_service=SERVICE_PROVIDER.modular_client.application_service(), + rule_service=SERVICE_PROVIDER.rule_service, + s3_client=SERVICE_PROVIDER.s3, + environment_service=SERVICE_PROVIDER.environment_service, + rule_source_service=SERVICE_PROVIDER.rule_source_service, + license_service=SERVICE_PROVIDER.license_service, + settings_service=SERVICE_PROVIDER.settings_service, + mappings_collector=SERVICE_PROVIDER.mappings_collector ) - def define_action_mapping(self): + @cached_property + def mapping(self) -> Mapping: return { - '/rulesets': { + CustodianEndpoint.RULESETS: { HTTPMethod.GET: self.get_ruleset, HTTPMethod.POST: self.create_ruleset, HTTPMethod.PATCH: self.update_ruleset, HTTPMethod.DELETE: self.delete_ruleset }, - '/rulesets/content': { + CustodianEndpoint.RULESETS_CONTENT: { HTTPMethod.GET: self.pull_ruleset_content }, - '/rulesets/event-driven': { + CustodianEndpoint.ED_RULESETS: { HTTPMethod.GET: self.get_event_driven_ruleset, HTTPMethod.POST: self.post_event_driven_ruleset, HTTPMethod.DELETE: self.delete_event_driven_ruleset @@ -109,16 +120,8 @@ def cloud_to_ed_ruleset_name(self) -> dict: RuleDomain.KUBERNETES.value: ED_KUBERNETES_RULESET_NAME } - @staticmethod - def _only_for_system(customer: Optional[str]): - if customer and customer != SYSTEM_CUSTOMER: - return build_response(code=HTTPStatus.FORBIDDEN, - content='Not allowed') - @validate_kwargs - def get_event_driven_ruleset(self, event: EventDrivenRulesetGetModel - ) -> dict: - self._only_for_system(event.customer) + def get_event_driven_ruleset(self, event: EventDrivenRulesetGetModel): _LOG.debug('Get event-driven rulesets') items = self.ruleset_service.iter_standard( customer=SYSTEM_CUSTOMER, @@ -135,9 +138,7 @@ def get_event_driven_ruleset(self, event: EventDrivenRulesetGetModel ) @validate_kwargs - def post_event_driven_ruleset(self, event: EventDrivenRulesetPostModel - ) -> dict: - self._only_for_system(event.customer) + def post_event_driven_ruleset(self, event: EventDrivenRulesetPostModel): _LOG.debug('Create event-driven rulesets') name = self.cloud_to_ed_ruleset_name[event.cloud] version = str(event.version) @@ -145,8 +146,10 @@ def post_event_driven_ruleset(self, event: EventDrivenRulesetPostModel maybe_ruleset = self.ruleset_service.get_standard( customer=SYSTEM_CUSTOMER, name=name, version=version, ) - self._assert_does_not_exist(maybe_ruleset, name=name, - version=version, customer=SYSTEM_CUSTOMER) + if maybe_ruleset: + raise ResponseFactory(HTTPStatus.CONFLICT).message( + f'Ruleset {name}:{version} already exists' + ).exc() if event.rules: rules = ( self.rule_service.get_latest_rule(SYSTEM_CUSTOMER, @@ -184,14 +187,14 @@ def post_event_driven_ruleset(self, event: EventDrivenRulesetPostModel self.ruleset_service.save(ruleset) return build_response(self.ruleset_service.dto( ruleset, params_to_exclude={RULES_ATTR, S3_PATH_ATTR} - )) + ), code=HTTPStatus.CREATED) - def delete_event_driven_ruleset(self, event: dict) -> dict: - self._only_for_system(event) + @validate_kwargs + def delete_event_driven_ruleset(self, event: EventDrivenRulesetDeleteModel): _LOG.debug('Delete event-driven rulesets') - cloud = event[CLOUD_ATTR] + cloud = event.cloud name = self.cloud_to_ed_ruleset_name[cloud] - version = str(event.get(VERSION_ATTR)) + version = str(event.version) item = self.ruleset_service.get_standard( customer=SYSTEM_CUSTOMER, name=name, @@ -206,7 +209,7 @@ def delete_event_driven_ruleset(self, event: dict) -> dict: code=HTTPStatus.NO_CONTENT, ) - def yield_standard_rulesets(self, customer: Optional[str] = None, + def yield_standard_rulesets(self, customer: str, name: Optional[str] = None, version: Optional[str] = None, cloud: Optional[str] = None, @@ -243,39 +246,35 @@ def _check(ruleset: Ruleset) -> bool: return True if not customer: # SYSTEM - source = self.ruleset_service.iter_licensed( + # TODO probably remove + yield from self.ruleset_service.iter_licensed( name=name, version=version, cloud=cloud, active=active ) - else: - license_keys = set() - applications = self.modular_service.get_applications( - customer=customer, - _type=ApplicationType.CUSTODIAN_LICENSES.value - ) - for application in applications: - meta = CustodianLicensesApplicationMeta( - **application.meta.as_dict()) - license_keys.update( - key for key in meta.cloud_to_license_key().values() if key - ) - licenses = ( - self.license_service.get_license(lk) for lk in license_keys - ) - ids = chain.from_iterable( - _license.ruleset_ids for _license in licenses if _license - ) - source = self.ruleset_service.iter_by_lm_id(ids) - # source contains rule-sets from applications, now we - # just filter them by input params - source = filter(_check, source) - yield from source + return + applications = self.application_service.list( + customer=customer, + _type=ApplicationType.CUSTODIAN_LICENSES.value, + deleted=False + ) + + licenses = tuple(self.license_service.to_licenses(applications)) + license_keys = {_license.license_key for _license in licenses} + + ids = chain.from_iterable( + _license.ruleset_ids for _license in licenses + ) + source = self.ruleset_service.iter_by_lm_id(ids) + # source contains rule-sets from applications, now we + # just filter them by input params + for rs in filter(_check, source): + rs.license_keys = list(set(rs.license_keys) & license_keys) + yield rs @validate_kwargs def get_ruleset(self, event: RulesetGetModel): - _LOG.debug(f'Get ruleset event: {event}') # maybe filter licensed rule-sets by tenants. params = dict( @@ -303,14 +302,7 @@ def get_ruleset(self, event: RulesetGetModel): item, params_to_exclude) for item in items) ) - def _check_tenants(self, tenants: Iterable[str]) -> List[str]: - errors = [] - for tenant_name in tenants: - if not self.restriction_service.is_allowed_tenant(tenant_name): - errors.append(f'Tenant {tenant_name} not found') - return errors - - def _filtered_rules(self, rules: List[Rule], severity: Optional[str], + def _filtered_rules(self, rules: list[Rule], severity: Optional[str], service_section: Optional[str], standard: set, mitre: set) -> Generator[Rule, None, None]: mappings = self.mappings_collector @@ -326,8 +318,7 @@ def _filtered_rules(self, rules: List[Rule], severity: Optional[str], continue if standard: # list st = mappings.standard.get(name) or {} - available = (Standard.deserialize(st, return_strings=True) | - st.keys()) + available = (Standard.deserialize_to_strs(st) | st.keys()) if not all(item in available for item in standard): _LOG.debug(f'Skipping rule {name}. ' f'Standard does not match') @@ -340,7 +331,7 @@ def _filtered_rules(self, rules: List[Rule], severity: Optional[str], yield rule @validate_kwargs - def create_ruleset(self, event: RulesetPostModel) -> dict: + def create_ruleset(self, event: RulesetPostModel): """ Filtering the rules by standards, severity, mitre and service section is performed in python after the rules were queried from DB. @@ -360,23 +351,15 @@ def create_ruleset(self, event: RulesetPostModel) -> dict: :param event: :return: """ - _LOG.debug(f'Create ruleset event: {event}') customer = event.customer or SYSTEM_CUSTOMER - # allowed_tenants = (event.get(TENANT_ALLOWANCE) - # or event.get(TENANTS_ATTR)) - # errors = self._check_tenants(allowed_tenants) - # if errors: - # return build_response( - # code=RESPONSE_RESOURCE_NOT_FOUND_CODE, - # content='\n'.join(errors) - # ) - ruleset = self.ruleset_service.get_standard( customer=customer, name=event.name, version=event.version, ) - self._assert_does_not_exist(ruleset, name=event.name, - version=event.version, customer=customer) + if ruleset: + raise ResponseFactory(HTTPStatus.CONFLICT).message( + f'Ruleset {event.name}:{event.version} already exists' + ).exc() # here we must collect a list of Rule items using incoming params if event.rules: _LOG.info('Concrete rules were provided. ' @@ -389,10 +372,9 @@ def create_ruleset(self, event: RulesetPostModel) -> dict: cloud=event.cloud ) if not rule: - return build_response( - code=HTTPStatus.NOT_FOUND, - content=self.rule_service.not_found_message(rule_name) - ) + raise ResponseFactory(HTTPStatus.NOT_FOUND).message( + self.rule_service.not_found_message(rule_name) + ).exc() rules.append(rule) rules = self.rule_service.filter_by( rules=rules, @@ -426,8 +408,10 @@ def create_ruleset(self, event: RulesetPostModel) -> dict: if not rules: _LOG.warning('No rules found by filters') - return build_response(code=HTTPStatus.BAD_REQUEST, - content='No rules left after filtering') + raise ResponseFactory(HTTPStatus.BAD_REQUEST).message( + 'No rules left after filtering' + ).exc() + ruleset = self.ruleset_service.create( customer=customer, name=event.name, @@ -448,10 +432,10 @@ def create_ruleset(self, event: RulesetPostModel) -> dict: self.ruleset_service.save(ruleset) return build_response(self.ruleset_service.dto( ruleset, params_to_exclude={RULES_ATTR, S3_PATH_ATTR} - )) + ), code=HTTPStatus.CREATED) @staticmethod - def build_policy(rules: List[Rule]) -> dict: + def build_policy(rules: list[Rule]) -> dict: return {'policies': [ rule.build_policy() for rule in rules ]} @@ -465,37 +449,36 @@ def upload_ruleset(self, ruleset: Ruleset, content: dict) -> None: """ bucket = self.environment_service.get_rulesets_bucket_name() key = self.ruleset_service.build_s3_key(ruleset) - self.s3_client.put_object( - bucket_name=bucket, - object_name=key, - body=json.dumps(content, separators=(",", ":")) + self.s3_client.gz_put_json( + bucket=bucket, + key=key, + obj=content ) self.ruleset_service.set_s3_path(ruleset, bucket=bucket, key=key) @validate_kwargs def update_ruleset(self, event: RulesetPatchModel): - _LOG.debug(f'Update ruleset event: {event}') - customer = event.customer or SYSTEM_CUSTOMER - tenant_allowance = set(event.tenant_allowance) - tenant_restriction = set(event.tenant_restriction) - - ruleset_to_update = self.ruleset_service. \ - get_ruleset_filtered_by_tenant(customer=customer, name=event.name, - version=event.version) - self._assert_exists(ruleset_to_update, name=event.name, - version=event.version, customer=customer) + ruleset_to_update = self.ruleset_service.get_standard( + customer=customer, + name=event.name, + version=event.version + ) + if not ruleset_to_update: + raise ResponseFactory(HTTPStatus.NOT_FOUND).message( + f'Ruleset {event.name}:{event.version} not found' + ).exc() s3_path = ruleset_to_update.s3_path.as_dict() if not s3_path: return build_response(code=HTTPStatus.BAD_REQUEST, content='Cannot update empty ruleset') if event.rules_to_attach or event.rules_to_detach: - content = self.s3_client.get_json_file_content( - bucket_name=s3_path.get('bucket_name'), - full_file_name=s3_path.get('path') + content = self.s3_client.gz_get_json( + bucket=s3_path.get('bucket_name'), + key=s3_path.get('path') ) name_body = self.rule_name_to_body(content) for to_detach in event.rules_to_detach: @@ -515,20 +498,6 @@ def update_ruleset(self, event: RulesetPatchModel): self.upload_ruleset(ruleset_to_update, {'policies': list(name_body.values())}) - # tenant restriction - existing_allowed_tenants = set(ruleset_to_update.allowed_for or []) - to_check = tenant_allowance - existing_allowed_tenants - errors = self._check_tenants(to_check) - if errors: - return build_response( - code=HTTPStatus.NOT_FOUND, - content='\n'.join(errors) - ) - existing_allowed_tenants -= tenant_restriction - existing_allowed_tenants.update(to_check) - ruleset_to_update.allowed_for = list(existing_allowed_tenants) - # end tenant restriction - was_active = ruleset_to_update.active if isinstance(event.active, bool): ruleset_to_update.active = event.active @@ -560,16 +529,17 @@ def rule_name_to_body(content: dict) -> dict: @validate_kwargs def delete_ruleset(self, event: RulesetDeleteModel): - _LOG.debug(f'Delete ruleset event: {event}') - customer = event.customer or SYSTEM_CUSTOMER - ruleset = self.ruleset_service.get_ruleset_filtered_by_tenant( - customer=customer, name=event.name, version=event.version + ruleset = self.ruleset_service.get_standard( + customer=customer, + name=event.name, + version=event.version ) - self._assert_exists(ruleset, CONCRETE_RULESET_NOT_FOUND_MESSAGE, - customer=customer, name=event.name, - version=event.version) + if not ruleset: + raise ResponseFactory(HTTPStatus.NOT_FOUND).message( + f'Ruleset {event.name}:{event.version} not found' + ).exc() previous = next( self.ruleset_service.get_previous_ruleset(ruleset, limit=1), None ) @@ -584,7 +554,6 @@ def delete_ruleset(self, event: RulesetDeleteModel): @validate_kwargs def pull_ruleset_content(self, event: RulesetContentGetModel): - _LOG.debug(f'Pull rulesets\' content event: {event}') customer = event.customer or SYSTEM_CUSTOMER name = event.name @@ -609,30 +578,10 @@ def pull_ruleset_content(self, event: RulesetContentGetModel): f'in the customer {customer} has not yet been ' f'successfully assembled' ) - _LOG.debug(f'S3 path: {s3_path}') - if not self.s3_client.file_exists(s3_path.get('bucket_name'), - s3_path.get('path')): - return build_response( - code=HTTPStatus.NOT_FOUND, - content=f'The content of ruleset \'{name}\' version {version} ' - f'in the customer {customer} does not exist' - ) - url = self.s3_client.generate_presigned_url( - bucket_name=s3_path.get('bucket_name'), - full_file_name=s3_path.get('path'), - expires_in_sec=300 - ) + # by default all new files have this header in metadata. But some + # old gzipped files can have octet-stream type, so + # I forcefully set the right encoding for response return build_response( code=HTTPStatus.OK, - content=url + content=self.ruleset_service.download_url(ruleset) ) - - def _assert_exists(self, entity: Optional[Any] = None, - message: str = None, **kwargs) -> None: - super()._assert_exists( - entity, message or CONCRETE_RULESET_NOT_FOUND_MESSAGE, **kwargs) - - def _assert_does_not_exist(self, entity: Optional[Any] = None, - message: str = None, **kwargs) -> None: - super()._assert_does_not_exist( - entity, message or RULESET_FOUND_MESSAGE, **kwargs) diff --git a/src/handlers/self_integration_handler.py b/src/handlers/self_integration_handler.py new file mode 100644 index 000000000..7e0938e88 --- /dev/null +++ b/src/handlers/self_integration_handler.py @@ -0,0 +1,280 @@ +from functools import cached_property +from http import HTTPStatus +from itertools import chain +from typing import Iterable + +from modular_sdk.commons.constants import ApplicationType, ParentScope, ParentType +from modular_sdk.models.application import Application +from modular_sdk.models.parent import Parent +from modular_sdk.services.application_service import ApplicationService +from modular_sdk.services.impl.maestro_credentials_service import ( + CustodianApplicationMeta, +) +from modular_sdk.services.parent_service import ParentService +from modular_sdk.services.ssm_service import AbstractSSMClient + +from handlers import AbstractHandler, Mapping +from helpers.constants import CustodianEndpoint, HTTPMethod +from helpers.lambda_response import ResponseFactory, build_response +from helpers.log_helper import get_logger +from services import SP +from services.abs_lambda import ProcessedEvent +from services.clients.cognito import BaseAuthClient +from services.environment_service import EnvironmentService +from services.modular_helpers import ( + ResolveParentsPayload, + build_parents, + get_activation_dto, + get_main_scope, + split_into_to_keep_to_delete, +) +from validators.swagger_request_models import ( + BaseModel, + SelfIntegrationPatchModel, + SelfIntegrationPutModel, +) +from validators.utils import validate_kwargs + +_LOG = get_logger(__name__) + + +class SelfIntegrationHandler(AbstractHandler): + """ + Self integration means that we create an application with type CUSTODIAN + that contains access to custodian instance so that other users of + modular sdk (maestro) could use it. One integration per customer is allowed + """ + + def __init__(self, application_service: ApplicationService, + parent_service: ParentService, + users_client: BaseAuthClient, + environment_service: EnvironmentService, + ssm_service: AbstractSSMClient): + self._aps = application_service + self._ps = parent_service + self._uc = users_client + self._env = environment_service + self._ssm = ssm_service + + @classmethod + def build(cls) -> 'SelfIntegrationHandler': + return cls( + application_service=SP.modular_client.application_service(), + parent_service=SP.modular_client.parent_service(), + users_client=SP.users_client, + environment_service=SP.environment_service, + ssm_service=SP.modular_client.assume_role_ssm_service() + ) + + @cached_property + def mapping(self) -> Mapping: + return { + CustodianEndpoint.INTEGRATIONS_SELF: { + HTTPMethod.PUT: self.put, + HTTPMethod.GET: self.get, + HTTPMethod.DELETE: self.delete, + HTTPMethod.PATCH: self.patch + }, + } + + def validate_username(self, username: str, customer: str): + """ + May raise CustodianException + :param customer: + :param username: + :return: + """ + user = self._uc.get_user_by_username(username) + if user and user.customer == customer: + return + raise ResponseFactory(HTTPStatus.NOT_FOUND).message( + f'User {username} not found inside {customer} customer' + ).exc() + + def set_user_password(self, application: Application, password: str): + """ + Modifies the incoming application with secret + :param application: + :param password: + """ + secret_name = application.secret + if not secret_name: + secret_name = self._ssm.safe_name( + name=application.customer_id, + prefix='m3.custodian.application', + date=False + ) + _LOG.debug('Saving password to SSM') + secret = self._ssm.put_parameter( + name=secret_name, + value=password + ) + if not secret: + _LOG.warning('Something went wrong trying to same password ' + 'to ssm. Keeping application.secret empty') + _LOG.debug('Password was saved to SSM') + application.secret = secret + + def get_all_activations(self, application_id: str, + customer: str | None = None) -> Iterable[Parent]: + it = self._ps.i_list_application_parents( + application_id=application_id, + type_=ParentType.CUSTODIAN, + rate_limit=3 + ) + if customer: + it = filter(lambda p: p.customer_id == customer, it) + return it + + @validate_kwargs + def put(self, event: SelfIntegrationPutModel, _pe: ProcessedEvent): + customer = event.customer + application = next(self._aps.list( + customer=customer, + _type=ApplicationType.CUSTODIAN.value, + limit=1, + deleted=False + ), None) + if not application: + application = self._aps.build( + customer_id=event.customer, + created_by=_pe['cognito_user_id'], + type=ApplicationType.CUSTODIAN.value, + description=event.description, + is_deleted=False, + ) + + self.validate_username(event.username, customer) + meta = CustodianApplicationMeta.from_dict({}) + if event.auto_resolve_access: + meta.update_host( + host=self._env.api_gateway_host(), + stage=self._env.api_gateway_stage() + ) + else: # url + url = event.url + meta.update_host( + host=url.host, + port=int(url.port) if url.port else None, + protocol=url.scheme, + stage=url.path + ) + if event.results_storage: + meta.results_storage = event.results_storage + meta.username = event.username + application.meta = meta.dict() + self.set_user_password(application, event.password) + + self._aps.save(application) + + # creating parents + payload = ResolveParentsPayload( + parents=list(self.get_all_activations(application.application_id, + event.customer)), + tenant_names=event.tenant_names, + exclude_tenants=event.exclude_tenants, + clouds=event.clouds, + all_tenants=event.all_tenants + ) + to_keep, to_delete = split_into_to_keep_to_delete(payload) + for parent in to_delete: + self._ps.force_delete(parent) + to_create = build_parents( + payload=payload, + parent_service=self._ps, + application_id=application.application_id, + customer_id=event.customer, + type_=ParentType.CUSTODIAN, + created_by=_pe['cognito_user_id'], + ) + for parent in to_create: + self._ps.save(parent) + + return build_response(content=self.get_dto(application, + chain(to_create, to_keep)), + code=HTTPStatus.CREATED) + + @validate_kwargs + def get(self, event: BaseModel): + customer = event.customer + existing = next(self._aps.list( + customer=customer, + _type=ApplicationType.CUSTODIAN.value, + limit=1, + deleted=False + ), None) + if not existing: + raise ResponseFactory(HTTPStatus.NOT_FOUND).message( + f'Self integration was not created for customer {customer}' + ).exc() + parents = self.get_all_activations(existing.application_id) + return build_response(self.get_dto(existing, parents)) + + @validate_kwargs + def delete(self, event: BaseModel): + customer = event.customer + existing = next(self._aps.list( + customer=customer, + _type=ApplicationType.CUSTODIAN.value, + limit=1, + deleted=False + ), None) + if not existing: + return build_response(code=HTTPStatus.NO_CONTENT) + self._aps.mark_deleted(existing) + if name := existing.secret: + self._ssm.delete_parameter(name) + for parent in self.get_all_activations(existing.application_id): + self._ps.force_delete(parent) + return build_response(code=HTTPStatus.NO_CONTENT) + + @validate_kwargs + def patch(self, event: SelfIntegrationPatchModel, _pe: ProcessedEvent): + customer = event.customer + existing = next(self._aps.list( + customer=customer, + _type=ApplicationType.CUSTODIAN.value, + limit=1, + deleted=False + ), None) + if not existing: + raise ResponseFactory(HTTPStatus.NOT_FOUND).message( + f'Self integration was not created for customer {customer}' + ).exc() + parents = list(self.get_all_activations(existing.application_id)) + payload = ResolveParentsPayload.from_parents_list(parents) + match get_main_scope(parents): + case ParentScope.SPECIFIC: + payload.tenant_names.update(event.add_tenants) + payload.tenant_names.difference_update(event.remove_tenants) + case ParentScope.ALL: + payload.exclude_tenants.difference_update(event.add_tenants) + payload.exclude_tenants.update(event.remove_tenants) + to_keep, to_delete = split_into_to_keep_to_delete(payload) + + for parent in to_delete: + self._ps.force_delete(parent) + to_create = build_parents( + payload=payload, + parent_service=self._ps, + application_id=existing.application_id, + customer_id=event.customer, + type_=ParentType.CUSTODIAN, + created_by=_pe['cognito_user_id'], + ) + for parent in to_create: + self._ps.save(parent) + + return build_response(content=self.get_dto(existing, + chain(to_create, to_keep)), + code=HTTPStatus.OK) + + @staticmethod + def get_dto(application: Application, parents: Iterable[Parent]) -> dict: + meta = CustodianApplicationMeta.from_dict(application.meta.as_dict()) + return { + **get_activation_dto(parents), + 'customer_name': application.customer_id, + 'description': application.description, + **meta.dict() + } diff --git a/src/handlers/send_report_setting_handler.py b/src/handlers/send_report_setting_handler.py new file mode 100644 index 000000000..e3d3a74e1 --- /dev/null +++ b/src/handlers/send_report_setting_handler.py @@ -0,0 +1,119 @@ +from functools import cached_property +from http import HTTPStatus + +from modular_sdk.services.customer_service import CustomerService + +from handlers import AbstractHandler, Mapping +from helpers.constants import CustodianEndpoint, HTTPMethod +from helpers.lambda_response import build_response +from helpers.log_helper import get_logger +from helpers.system_customer import SYSTEM_CUSTOMER +from services import SP +from services.clients.step_function import ScriptClient, StepFunctionClient +from services.health_check_service import RabbitMQConnectionCheck +from services.setting_service import SettingsService +from validators.swagger_request_models import ReportsSendingSettingPostModel +from validators.utils import validate_kwargs + +RETRY_REPORT_STATE_MACHINE = 'retry_send_reports' +_LOG = get_logger(__name__) + + +class ReportsSendingSettingHandler(AbstractHandler): + """ + Manages reports sending configuration. + """ + + def __init__(self, settings_service: SettingsService, + step_function_client: ScriptClient | StepFunctionClient, + customer_service: CustomerService): + self.settings_service = settings_service + self.step_function_client = step_function_client + self.customer_service = customer_service + + @cached_property + def mapping(self) -> Mapping: + return { + CustodianEndpoint.SETTINGS_SEND_REPORTS: { + HTTPMethod.POST: self.post + } + } + + @classmethod + def build(cls): + return cls( + settings_service=SP.settings_service, + step_function_client=SP.step_function, + customer_service=SP.modular_client.customer_service() + ) + + @staticmethod + def build_retry_event() -> dict: + res = CustodianEndpoint.REPORTS_RETRY.value + return { + 'resource': res, + 'path': res, + 'httpMethod': HTTPMethod.POST, + 'queryStringParameters': {}, + 'pathParameters': {}, + 'requestContext': { + 'path': f'/caas/{res}', # stage does not matter here + 'resourcePath': res, + 'httpMethod': HTTPMethod.POST, + 'protocol': 'HTTP/1.1', + 'authorizer': { + 'claims': { + 'custom:customer': SYSTEM_CUSTOMER, + } + } + }, + 'body': '', + 'isBase64Encoded': False + } + + @validate_kwargs + def post(self, event: ReportsSendingSettingPostModel): + + setting = self.settings_service.get_send_reports() + if setting == event.enable: + return build_response( + code=HTTPStatus.OK, + content=f'The SEND_REPORTS setting is already ' + f'{"enabled" if event.enable else "disabled"}' + ) + if event.enable: + health_check_result = [] + instance = RabbitMQConnectionCheck.build() + for customer in self.customer_service.i_get_customer(): + try: + result = instance.check(customer=customer.name) + except Exception as e: + _LOG.exception( + f'An unknown exception occurred trying to execute ' + f'check `{instance.identifier()}` for customer ' + f'{customer.name}') + result = instance.unknown_result(details={'error': str(e)}) + _LOG.info(f'Check: {instance.identifier()} for customer ' + f'{customer.name} has finished') + health_check_result.append(result.is_ok()) + if not any(health_check_result): + # for what? + _LOG.warning( + 'Could not enable reports sending system because ALL ' + 'customers have non-working RabbitMQ configuration.') + return build_response( + code=HTTPStatus.OK, + content='Could not enable reports sending system.' + ) + self.settings_service.enable_send_reports() + self.step_function_client.invoke( + RETRY_REPORT_STATE_MACHINE, + event=self.build_retry_event() + ) + else: + self.settings_service.disable_send_reports() + + return build_response( + code=HTTPStatus.OK, + content=f'Value of SEND_REPORTS setting was changed to {event.enable}' + ) diff --git a/src/handlers/tenant_handler.py b/src/handlers/tenant_handler.py new file mode 100644 index 000000000..7bb8c6b0e --- /dev/null +++ b/src/handlers/tenant_handler.py @@ -0,0 +1,294 @@ +from functools import cached_property +from http import HTTPStatus +from itertools import islice +from typing import TYPE_CHECKING +import uuid + +from botocore.exceptions import ClientError +from modular_sdk.models.parent import Parent +from modular_sdk.models.pynamodb_extension.base_model import LastEvaluatedKey as Lek +from modular_sdk.models.region import RegionModel +from modular_sdk.models.tenant import Tenant + +from handlers import AbstractHandler, Mapping +from helpers.constants import ( + CustodianEndpoint, + DEFAULT_OWNER_ATTR, + HTTPMethod, + PRIMARY_CONTACTS_ATTR, + SECONDARY_CONTACTS_ATTR, + TENANT_MANAGER_CONTACTS_ATTR, + TS_EXCLUDED_RULES_KEY, +) +from helpers.lambda_response import ResponseFactory +from helpers.lambda_response import build_response +from helpers.log_helper import get_logger +from helpers.regions import get_region_by_cloud +from helpers.time_helper import utc_iso +from services import SP +from services import modular_helpers +from services.license_service import License, LicenseService +from validators.swagger_request_models import ( + BaseModel, + MultipleTenantsGetModel, + TenantExcludedRulesPutModel, + TenantGetActiveLicensesModel, + TenantPostModel, + TenantRegionPostModel, +) +from validators.utils import validate_kwargs + +if TYPE_CHECKING: + from modular_sdk.services.parent_service import ParentService + from modular_sdk.services.application_service import ApplicationService + from modular_sdk.services.tenant_service import TenantService + from modular_sdk.services.region_service import RegionService + from modular_sdk.services.tenant_settings_service import TenantSettingsService + from services.license_service import LicenseService + +_LOG = get_logger(__name__) + + +class TenantHandler(AbstractHandler): + + def __init__(self, tenant_service: 'TenantService', + parent_service: 'ParentService', + application_service: 'ApplicationService', + region_service: 'RegionService', + tenant_settings_service: 'TenantSettingsService', + license_service: 'LicenseService'): + self._ts = tenant_service + self._rs = region_service + self._ps = parent_service + self._aps = application_service + self._tss = tenant_settings_service + self._license_service = license_service + + @classmethod + def build(cls) -> 'AbstractHandler': + return cls( + tenant_service=SP.modular_client.tenant_service(), + parent_service=SP.modular_client.parent_service(), + application_service=SP.modular_client.application_service(), + region_service=SP.modular_client.region_service(), + tenant_settings_service=SP.modular_client.tenant_settings_service(), + license_service=SP.license_service + ) + + def get_dto(self, tenant: Tenant) -> dict: + dct = self._ts.get_dto(tenant) + dct.pop('contacts', None) + dct.pop('parent_map', None) + dct.pop('read_only', None) + return dct + + @cached_property + def mapping(self) -> Mapping: + return { + CustodianEndpoint.TENANTS: { + HTTPMethod.GET: self.query, + # HTTPMethod.POST: self.post + }, + CustodianEndpoint.TENANTS_TENANT_NAME: { + HTTPMethod.GET: self.get, + # HTTPMethod.DELETE: self.delete + }, + # CustodianEndpoint.TENANTS_TENANT_NAME_REGIONS: { + # HTTPMethod.POST: self.add_region + # }, + CustodianEndpoint.TENANTS_TENANT_NAME_ACTIVE_LICENSES: { + HTTPMethod.GET: self.get_tenant_active_license + }, + CustodianEndpoint.TENANTS_TENANT_NAME_EXCLUDED_RULES: { + HTTPMethod.PUT: self.set_excluded_rules, + HTTPMethod.GET: self.get_excluded_rules + } + } + + @validate_kwargs + def get(self, event: BaseModel, tenant_name: str): + # TODO api implement get by account id + tenant = self._ts.get(tenant_name) + if not tenant: + _LOG.info('Tenant was not found by name. Looking by account id') + tenant = next(self._ts.i_get_by_acc( + acc=tenant_name, + limit=1, + ), None) + if not tenant or event.customer and tenant.customer_name != event.customer: + raise ResponseFactory(HTTPStatus.NOT_FOUND).message( + 'Tenant is not found' + ).exc() + return build_response(self.get_dto(tenant)) + + @validate_kwargs + def query(self, event: MultipleTenantsGetModel): + old_lek = Lek.deserialize(event.next_token) + new_lek = Lek() + + cursor = self._ts.i_get_tenant_by_customer( + customer_id=event.customer, + active=event.active, + cloud=event.cloud.value if event.cloud else None, + limit=event.limit, + last_evaluated_key=old_lek.value + ) + items = list(cursor) + new_lek.value = cursor.last_evaluated_key + return ResponseFactory(HTTPStatus.OK).items( + it=map(self.get_dto, items), + next_token=new_lek.serialize() if new_lek else None + ).build() + + @validate_kwargs + def post(self, event: TenantPostModel): + by_name = self._ts.get(event.name) + if by_name: + raise ResponseFactory(HTTPStatus.CONFLICT).message( + 'Name already used').exc() + by_acc = next(self._ts.i_get_by_acc(event.account_id, limit=1), None) + if by_acc: + raise ResponseFactory(HTTPStatus.CONFLICT).message( + 'Tenant with such account id already used' + ).exc() + # modular sdk does not have create() method. Going manually + + item = Tenant( + name=event.name, + display_name=event.display_name, + display_name_to_lower=event.display_name.lower(), + read_only=False, + is_active=True, + customer_name=event.customer, + cloud=event.cloud, + activation_date=utc_iso(), + project=event.account_id, + contacts={ + PRIMARY_CONTACTS_ATTR: event.primary_contacts, + SECONDARY_CONTACTS_ATTR: event.secondary_contacts, + TENANT_MANAGER_CONTACTS_ATTR: event.tenant_manager_contacts, + DEFAULT_OWNER_ATTR: event.default_owner + + } + ) + try: + item.save() + except ClientError: + _LOG.exception('Cannot save tenant to DB') + raise ResponseFactory(HTTPStatus.FORBIDDEN).message( + 'Cannot create tenant' + ).exc() + return build_response(content=self.get_dto(item), + code=HTTPStatus.CREATED) + + @validate_kwargs + def delete(self, event: BaseModel, tenant_name: str): + item = self._ts.get(tenant_name) + if not item or event.customer and item.customer_name != event.customer: + return build_response(code=HTTPStatus.NO_CONTENT) + try: + item.delete() + except ClientError: + _LOG.exception('Cannot delete tenant to DB') + raise ResponseFactory(HTTPStatus.FORBIDDEN).message( + 'Cannot delete tenant' + ).exc() + return build_response(code=HTTPStatus.NO_CONTENT) + + @validate_kwargs + def add_region(self, event: TenantRegionPostModel, tenant_name: str): + tenant = self._ts.get(tenant_name) + if not tenant: + raise ResponseFactory(HTTPStatus.NOT_FOUND).message( + 'Tenant not found').exc() + if event.region in modular_helpers.get_tenant_regions(tenant): + return build_response( + code=HTTPStatus.CONFLICT, + content=f'Region: {event.region} already active for tenant' + ) + if event.region not in get_region_by_cloud(tenant.cloud): + return build_response( + code=HTTPStatus.BAD_REQUEST, + content='Region does not belong to tenant`s cloud' + ) + region = self._rs.get_region_by_native_name(event.region, tenant.cloud) + if not region: + region = RegionModel( + maestro_name=event.region.upper(), + native_name=event.region, + cloud=tenant.cloud, + region_id=str(uuid.uuid4()), + is_active=True, + ) + tenant.regions.append(region) + tenant.save() + return build_response(code=HTTPStatus.CREATED, + content=self.get_dto(tenant)) + + def get_complemented_license_dto(self, parent: Parent, + lic: License) -> dict: + dct = self._license_service.dto(lic) + dct['scope'] = parent.scope + return dct + + @validate_kwargs + def get_tenant_active_license(self, event: TenantGetActiveLicensesModel, + tenant_name: str): + """ + Generally, only the license manager can determine whether the license + is allowed for a specific tenant. So license_key is enough for Rule + Engine to execute a scan for a tenant. But on top of that Rule + Engine & Maestro have another mechanism of "activation" licenses for + tenants. First, we add a license to Custodian installation (which is + already enough). Second, we additionally link this license to + ALL/SPECIFIC tenants. This endpoint resolves that linkage and returns + a list of licenses that are active for a specific tenant + """ + tenant = self._ts.get(tenant_name) + if not tenant or event.customer and tenant.customer_name != event.customer: + raise ResponseFactory(HTTPStatus.NOT_FOUND).message( + 'Tenant is not found' + ).exc() + it = self._license_service.iter_tenant_licenses(tenant, + limit=event.limit) + it = islice(it, event.limit) + return build_response( + content=(self.get_complemented_license_dto(*i) for i in it) + ) + + @validate_kwargs + def get_excluded_rules(self, event: BaseModel, tenant_name: str): + tenant = self._ts.get(tenant_name) + if not tenant or event.customer and tenant.customer_name != event.customer: + raise ResponseFactory(HTTPStatus.NOT_FOUND).message( + 'Tenant is not found' + ).exc() + item = self._tss.get( + tenant_name=tenant.name, + key=TS_EXCLUDED_RULES_KEY + ) + if not item: + return build_response({'rules': [], 'tenant_name': tenant.name}) + return build_response({ + 'rules': item.value.as_dict().get('rules') or [], + 'tenant_name': tenant.name + }) + + @validate_kwargs + def set_excluded_rules(self, event: TenantExcludedRulesPutModel, + tenant_name: str): + tenant = self._ts.get(tenant_name) + if not tenant or event.customer and tenant.customer_name != event.customer: + raise ResponseFactory(HTTPStatus.NOT_FOUND).message( + 'Tenant is not found' + ).exc() + data = {'rules': list(event.rules)} + item = self._tss.create( + tenant_name=tenant.name, + key=TS_EXCLUDED_RULES_KEY, + value=data + ) + self._tss.save(item) + data['tenant_name'] = tenant.name + return build_response(data) + diff --git a/src/handlers/tenants/__init__.py b/src/handlers/tenants/__init__.py deleted file mode 100644 index ede4d8d14..000000000 --- a/src/handlers/tenants/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -from handlers.abstracts.abstract_handler import AbstractComposedHandler -from handlers.tenants.tenant_handler import instantiate_tenant_handler -from services.environment_service import EnvironmentService -from services.modular_service import ModularService - - -class TenantsHandler(AbstractComposedHandler): - ... - - -def instantiate_tenants_handler(modular_service: ModularService, - environment_service: EnvironmentService): - _tenant_handler = instantiate_tenant_handler( - modular_service=modular_service, - environment_service=environment_service - ) - return TenantsHandler( - resource_map={ - **_tenant_handler.define_handler_mapping(), - } - ) diff --git a/src/handlers/tenants/license_priority_handler.py b/src/handlers/tenants/license_priority_handler.py deleted file mode 100644 index e0a741ff6..000000000 --- a/src/handlers/tenants/license_priority_handler.py +++ /dev/null @@ -1,1494 +0,0 @@ -from typing import Iterable, Optional, Callable, Dict, Union, Type, List, \ - Iterator - -from helpers import retrieve_invalid_parameter_types, \ - BAD_REQUEST_IMPROPER_TYPES, RESPONSE_BAD_REQUEST_CODE, \ - RESPONSE_RESOURCE_NOT_FOUND_CODE, RESPONSE_OK_CODE, RESPONSE_CONFLICT - -from helpers.constants import GET_METHOD, POST_METHOD, DELETE_METHOD,\ - PATCH_METHOD, LICENSE_KEYS_ATTR, \ - CUSTOMER_ATTR, TENANT_ATTR, TENANTS_ATTR, \ - GOVERNANCE_ENTITY_ID_ATTR, GOVERNANCE_ENTITY_TYPE_ATTR, RULESET_ATTR, \ - ALLOWED_GOVERNANCE_ENTITY_TYPE_ATTRS, ACCOUNT_ATTR, \ - LICENSE_PRIORITY_TYPE_ATTR, LICENSE_KEYS_TO_APPEND_ATTR,\ - LICENSE_KEYS_TO_PREPEND_ATTR, LICENSE_KEYS_TO_DETACH_ATTR - -from handlers.abstracts.abstract_handler import AbstractComposedHandler - -from handlers.abstracts.abstract_modular_entity_handler import \ - ModularService, ENTITY_TEMPLATE, AbstractModularEntityHandler - -from services.modular_service import Tenant, Complemented -from services.rbac.governance.priority_governance_service import\ - PriorityGovernanceService, MANAGEMENT_ID_ATTR, GOVERNANCE_ATTR, \ - MANAGEMENT_ATTR -from services.license_service import LicenseService -from services.ruleset_service import RulesetService, Ruleset - -from helpers.log_helper import get_logger - -_LOG = get_logger('tenant_license_priority_handler') - -TENANTS_LICENSE_PRIORITIES_PATH = '/tenants/license-priorities' - -FORBIDDEN_ACCESS = 'Access to {} entity is forbidden.' - -AUTHORIZATION = 'Authorization' -SPECIFICATION = 'Query-Specification' -VALIDATION = 'Validation' -ACCESSIBILITY = 'Entity-Accessibility' -INSTANTIATION = 'Entity-Instantiation' -AMENDMENT = 'Entity-Amendment' -PERSISTENCE = 'Entity-Persistence' - -SPECIFICATION_ATTR = 'specification' - -PERSISTENCE_ERROR = ' does not exist' -RETAIN_ERROR = ' could not be persisted' - -RETRIEVED = ' has been retrieved' -INSTANTIATED = ' has been instantiated' -REMOVED = ' has been removed' -CONFLICT = ' already exists' - -ATTRIBUTE_UPDATED = ' {} has been set to {}' -RETAINED = ' has been persisted' - -I_SOURCE_ATTR = 'i_source' -CUSTOMER_ENTITY_ATTR = 'customer_entity' - -ACCOUNTS_ATTR = ACCOUNT_ATTR + 's' - -PRIORITY_ID_ATTR = 'priority_id' - - -class BaseTenantLicensePriorityHandler(AbstractModularEntityHandler): - - tenant_entity: Optional[Union[Tenant, Complemented]] - governance_entity_type: Optional[str] - - def __init__( - self, modular_service: ModularService, - priority_governance_service: PriorityGovernanceService - ): - self.priority_governance_service = priority_governance_service - # Attaches resource specific attributes. - self.priority_governance_service.entity_type = Tenant - self.priority_governance_service.managed_attr = LICENSE_KEYS_ATTR - self.priority_governance_service.delegated_attr = ACCOUNTS_ATTR - super().__init__(modular_service=modular_service) - - def _reset(self): - # Mutative action-applicable. - super()._reset() - self.tenant_entity = None - self.mid = None - self.governance_entity_type = None - - @property - def entity(self) -> str: - """ - :return: str, `Tenant-License-Priority` - """ - subtype = LICENSE_PRIORITY_TYPE_ATTR.split('_') - subtype = '-'.join(each.capitalize() for each in subtype) - return f'{TENANT_ATTR.capitalize()}-{subtype}' - - def _get_expanded_dto(self, dto: Dict[str, Union[str, Dict]]): - """ - Expands given object-aggregated management-dto into a list of separate - managed dto(s). - :parameter dto: Dict[str, Union[str. Dict]] - :return: List[Dict[str, Union[str, Dict]]] - """ - expanded = [] - managed_attr = self.priority_governance_service.managed_attr - mid = dto.get(MANAGEMENT_ID_ATTR) - tenant = dto.get(TENANT_ATTR) or self.tenant_entity.name - for key, value in (dto.get(managed_attr) or dict()).items(): - _dto = { - TENANT_ATTR: tenant, - PRIORITY_ID_ATTR: mid, - self.governance_entity_type: key, - managed_attr: value - } - expanded.append(_dto) - return expanded - - def _validate_governance_entity_data_step(self, event: Dict): - """ - Mandates `governance_entity_type` data validation and persistence.\n - Response variables are assigned, under the following predicates: - \nGiven absence: `code` - 404, `content` - persistence reason. - :parameter event: Dict - :return: Union[Dict, Type[None]] - """ - _allowed = ALLOWED_GOVERNANCE_ENTITY_TYPE_ATTRS - _label = GOVERNANCE_ENTITY_TYPE_ATTR - gvc_type: str = event.get(_label) - - if gvc_type not in _allowed: - issue = f'Value of \'{_label}\' parameter must reflect ' \ - f'one of the following value(s): {_allowed}.' - - self._code = RESPONSE_BAD_REQUEST_CODE - self._content = f'Bad request. {issue}' - return None - - self.governance_entity_type = gvc_type - - entity = self.entity.upper() - _, *subtype = entity.split('-') - subtype = '_'.join(map(str.upper, subtype)) - - _type = f'{gvc_type.upper()}_{subtype}' - # Derives $ENTITY_LICENSE_PRIORITY, ie RULESET_LICENSE_PRIORITY - _LOG.info(f'Deriving \'{_type}\' {GOVERNANCE_ATTR.lower()}.') - - self.priority_governance_service.governance_type = _type - return event - - -class CommandTenantLicensePriorityHandler(BaseTenantLicensePriorityHandler): - - tenant_entity: Optional[Union[Tenant, Complemented]] - mid: Optional[str] - - # Post and Patch mutative action variables. - ruleset_entity: Optional[Ruleset] - license_service: Optional[LicenseService] - - def __init__( - self, modular_service: ModularService, - priority_governance_service: PriorityGovernanceService - ): - super().__init__( - modular_service=modular_service, - priority_governance_service=priority_governance_service - ) - - def _reset(self): - super()._reset() - # Mutative action-applicable attributes. - self.tenant_entity = None - self.mid = None - - def _validate_customer_data_step(self, event: dict): - """ - Designated to validate for optionally issued `customer` data, issued - by a non-system user. - Note: meant to be shared across command-handlers. - :param event: dict - :return: Optional[dict] - """ - customer: Optional[str] = event.get(CUSTOMER_ATTR, None) - - if customer is not None and not isinstance(customer, str): - event = None - self._code = RESPONSE_BAD_REQUEST_CODE - self._content = BAD_REQUEST_IMPROPER_TYPES.format( - f'\'{CUSTOMER_ATTR}\':str' - ) - return event - - def _access_tenant_entity_step(self, event: Dict): - """ - Mandates Tenant entity retrieval and lack of persistence.\n - Response variables are assigned, under the following predicates: - \nGiven absence: `code` - 404, `content` - persistence reason. - :parameter event: Dict - :return: Union[Dict, Type[None]] - """ - priority_service = self.priority_governance_service - name = event.get(TENANT_ATTR) - customer = event.get(CUSTOMER_ATTR) - _template = ENTITY_TEMPLATE.format(entity=self.entity, id=name) - - entity = None - if customer and name: - fetcher = self.modular_service.i_get_customer_tenant - iterator = fetcher(customer=customer, name=name, active=True) - entity = next(iterator, None) - elif name: - entity = name - - if entity: - entity = priority_service.get_entity(entity=entity) - - self.tenant_entity = entity - - if not self.tenant_entity: - event = None - self._code = RESPONSE_RESOURCE_NOT_FOUND_CODE - self._content = _template + PERSISTENCE_ERROR - - return event - - def _access_license_relation_entity_step(self, event: Dict): - """ - Mandates Tenant entity access to a pending licensed ruleset, as well - as lack of persistence and license-key(s) attachment.\n - Response variables are assigned, under the following predicates: - \nGiven absence: `code` - 404, `content` - persistence reason. - :parameter event: Dict - :return: Union[Dict, Type[None]] - """ - # Generalized by license_keys attr. - license_keys = event.get(LICENSE_KEYS_ATTR) - if not license_keys: - _LOG.error('Execution step is missing' - f' \'{LICENSE_KEYS_ATTR}\' variable.') - return None - - ruleset = self.ruleset_entity - if not ruleset: - _LOG.error('Execution step is missing \'ruleset\' variable.') - return None - - entity: Complemented = self.tenant_entity - - _template = ENTITY_TEMPLATE.format(entity=self.entity, id=entity.name) - - for license_key in license_keys: - head = ENTITY_TEMPLATE.format(entity='License', id=license_key) - if license_key not in ruleset.license_keys: - issue = PERSISTENCE_ERROR + f' within \'{ruleset.id}\' Ruleset' - self._code = RESPONSE_RESOURCE_NOT_FOUND_CODE - self._content = head + issue - event = None - break - - _license = self.license_service.get_license(license_id=license_key) - if not _license: - # Unlikely to occur - as consequence of improper license-sync. - _LOG.error(head + PERSISTENCE_ERROR) - self._code = RESPONSE_RESOURCE_NOT_FOUND_CODE - self._content = head + PERSISTENCE_ERROR - event = None - break - - if not self.license_service.is_subject_applicable( - entity=_license, customer=entity.customer_name, - tenant=entity.name - ): - _LOG.warning(head + f' is not \'{entity.name}\' tenant' - ' applicable.') - self._code = RESPONSE_RESOURCE_NOT_FOUND_CODE - self._content = head + PERSISTENCE_ERROR - event = None - - return event - - def _persist_priority_management_step(self, event: Dict): - """ - Mandates Settings-complemented Tenant entity persistence.\n - Response variables are assigned, under the following predicates: - \nGiven absence: `code` - 500, `content` - failed to retain reason. - :parameter event: Dict - :return: Union[Dict, Type[None]] - """ - entity: Complemented = self.tenant_entity - mid = self.mid or event.get(MANAGEMENT_ID_ATTR) - if not entity: - _LOG.error('Variable \'tenant_entity\' is missing.') - return None - - _template = ENTITY_TEMPLATE.format(entity=self.entity, id=entity.name) - if self.modular_service.save(entity=entity): - label = self.priority_governance_service.governance_type - message = f'{mid} {label}' if mid else f' \'{label}\'' - _LOG.info(_template + message + RETAINED) - else: - event = None - self._content = _template + RETAIN_ERROR - - return event - - def _produce_response_dto(self, event: Optional[Dict] = None) -> \ - Union[str, Dict, List, Type[None]]: - """ - Mandates derivation of a patch-response data transfer object, - based on a pending Customer Parent entity. - :parameter event: Dict - :return: Union[Dict, List, Type[None]] - """ - entity: Complemented = self.tenant_entity - mid: str = self.mid or event.get(MANAGEMENT_ID_ATTR) - if not entity: - _LOG.error('Variable \'tenant_entity\' is missing.') - return None - if mid in entity.management: - get_dto = self.priority_governance_service.get_management - response = get_dto(entity=entity, mid=mid) - response = self._get_expanded_dto(dto=response or dict()) - else: - response = [] - return response - - -class GetTenantLicensePriorityHandler(BaseTenantLicensePriorityHandler): - def __init__( - self, modular_service: ModularService, - priority_governance_service: PriorityGovernanceService - ): - super().__init__( - modular_service=modular_service, - priority_governance_service=priority_governance_service - ) - # Declares a subjected query specification - self.specification: Dict = dict() - - def define_action_mapping(self): - return { - TENANTS_LICENSE_PRIORITIES_PATH: {GET_METHOD: self.get} - } - - def get(self, event): - action = GET_METHOD.capitalize() - return self._process_action(event=event, action=action) - - @property - def attributes_to_log(self) -> Dict[Union[str, Type[None]], Iterable[str]]: - return { - VALIDATION: [GOVERNANCE_ENTITY_TYPE_ATTR], - SPECIFICATION: [ - CUSTOMER_ATTR, TENANT_ATTR, GOVERNANCE_ENTITY_ID_ATTR, - TENANTS_ATTR - ] - } - - @property - def responsibilities(self) -> Dict[ - str, Iterable[Callable[[Dict], Union[Dict, Type[None]]]] - ]: - return { - VALIDATION: self.validation_responsibility, - SPECIFICATION: self.specification_responsibilities - } - - @property - def validation_responsibility(self): - return [ - self._validate_governance_entity_data_step - ] - - @property - def specification_responsibilities(self): - return [ - self._obscure_specification_step, - self._name_specification_step, - self._governance_entity_id_specification_step, - self._priority_id_specification_step - ] - - @property - def i_query(self) -> Iterator: - """ - Produces a query iterator-like output, based on the pending - specification, resetting it afterwards, having installed the query: - 1. Given only tenant-names, retrieves demanded entities. - 2. Given a customer name, fetches every entity belonging to the - customer, singling out active tenants, provided any. - 3. Otherwise, retrieves every active tenant entity. - - Afterwards for each tenant, produced by said query, respective - Tenant-Setting based priority-governance is derived, and sub-queried: - 1. Based on a priority-id, which reflects to management-id - 2. Based on a governance entity, i.e. ruleset, within said priority - - :return: Iterator - """ - specification = self.specification - - tenants: Optional[List[str]] = specification.get(TENANTS_ATTR) - tenant: Optional[str] = specification.get(TENANT_ATTR) - customer: Optional[str] = specification.get(CUSTOMER_ATTR) - # Priority id. - priority_id: Optional[str] = specification.get(MANAGEMENT_ID_ATTR) - # Managed entity id, i.e. ruleset-id - gvc_entity_id: Optional[str] = specification.get( - GOVERNANCE_ENTITY_ID_ATTR - ) - self.specification = dict() - if customer and not tenants: - # Conceals response view, given a non-system request's been issued. - query = self.modular_service.i_get_customer_tenant( - customer=customer, name=tenant, active=True - ) - elif tenant or tenants: - if tenant: - query = self.modular_service.i_get_tenant(iterator=iter([tenant])) - else: - # Given `tenants` must be related to `customer`, given any. - query = self.modular_service.i_get_tenant(iterator=iter(tenants)) - if customer: - query = (t for t in query if t.customer_name == customer) - # Get active only. - query = (t for t in query if t.is_active) - else: - query = self.modular_service.i_get_tenants(active=True) - - for tenant in query: - entity = self.priority_governance_service.get_entity(entity=tenant) - if not entity: - continue - - if priority_id: - data = self.priority_governance_service.get_management( - entity=entity, mid=priority_id - ) - subquery = iter([data] if data else []) - else: - subquery = self.priority_governance_service.i_get_management( - entity=entity - ) - for data in subquery: - attr = self.priority_governance_service.managed_attr - - managed = data.get(attr, {}) - if gvc_entity_id in managed: - # Retrieves managed-entity id data. - managed = {gvc_entity_id: managed.get(gvc_entity_id)} - elif gvc_entity_id: - managed = {} - - data[attr] = managed - if data[attr]: - data[TENANT_ATTR] = entity.name - for each in self._get_expanded_dto(dto=data): - yield each - - def _obscure_specification_step(self, event: Dict) -> \ - Union[Dict, Type[None]]: - """ - Mandates concealing specification, based on given query. - Obscures the view, based on: - * tenant-specific restrictions - * customer scope from the non-system issuer. - :parameter event: Dict - :return: Union[Dict, Type[None]] - """ - tenants = event.get(TENANTS_ATTR) - customer = event.get(CUSTOMER_ATTR) - if isinstance(tenants, list) and tenants: - self.specification[TENANTS_ATTR] = tenants - - if customer: - self.specification[CUSTOMER_ATTR] = customer - - return event - - def _name_specification_step(self, event: Dict): - """ - Mandates Tenant name specification, based on given query. - Given the aforementioned partition attribute, alters - specification with a list based on said name. - :parameter event: Dict - :return: Union[Dict, Type[None]] - """ - tenant = event.get(TENANT_ATTR) - if tenant: - self.specification[TENANT_ATTR] = tenant - return event - - def _governance_entity_id_specification_step(self, event: Dict): - """ - Mandates entity-id specification of a governance entity, - based on given query. Given the aforementioned partition attribute, - alters specification with a list based on said name. - :parameter event: Dict - :return: Union[Dict, Type[None]] - """ - # Declares governance_entity_id, based on a respective entity-type. - gvc_entity_id = event.get(GOVERNANCE_ENTITY_ID_ATTR) - if gvc_entity_id: - self.specification[GOVERNANCE_ENTITY_ID_ATTR] = gvc_entity_id - return event - - def _priority_id_specification_step(self, event: Dict): - """ - Mandates priority-id specification, based on given query. - Given the aforementioned partition attribute, alters - specification with a list based on said name. - :parameter event: Dict - :return: Union[Dict, Type[None]] - """ - priority_id = event.get(MANAGEMENT_ID_ATTR) - if priority_id: - self.specification[MANAGEMENT_ID_ATTR] = priority_id - return event - - def _produce_response_dto(self, event: Optional[Dict] = None) -> \ - Union[str, Dict, List, Type[None]]: - """ - Mandates derivation of a query-response data transfer object, - based on a pending source-iterator. Apart from that, - for each entity, a user-respective latest-login is injected - into attached customer dto. - :parameter event: Dict - :return: Union[Dict, List, Type[None]] - """ - return [*self.i_query] - - -class PostTenantLicensePriorityHandler(CommandTenantLicensePriorityHandler): - def __init__( - self, modular_service: ModularService, license_service: LicenseService, - priority_governance_service: PriorityGovernanceService, - ruleset_service: RulesetService - ): - - # Declares a subjected query specification - self.license_service = license_service - self.ruleset_service = ruleset_service - - super().__init__( - modular_service=modular_service, - priority_governance_service=priority_governance_service - ) - - def _reset(self): - super()._reset() - # Declares request-pending customer entity - self.ruleset_entity: Optional[Ruleset] = None - - def define_action_mapping(self): - return { - TENANTS_LICENSE_PRIORITIES_PATH: { - POST_METHOD: self.post_license_priority - } - } - - def post_license_priority(self, event): - action = POST_METHOD.capitalize() - return self._process_action(event=event, action=action) - - @property - def attributes_to_log(self) -> Dict[Union[str, Type[None]], Iterable[str]]: - return { - VALIDATION: list(self._post_required_map) + [CUSTOMER_ATTR], - ACCESSIBILITY: [ - TENANT_ATTR, GOVERNANCE_ENTITY_ID_ATTR, - LICENSE_KEYS_ATTR, MANAGEMENT_ID_ATTR - ], - INSTANTIATION: [ - 'tenant_entity', 'mid', MANAGEMENT_ID_ATTR - ], - PERSISTENCE: ['tenant_entity', 'mid'] - } - - @property - def responsibilities(self) -> Dict[ - str, Iterable[Callable[[Dict], Union[Dict, Type[None]]]] - ]: - return { - VALIDATION: self.validation_responsibilities, - ACCESSIBILITY: self.access_responsibilities, - INSTANTIATION: self.instantiation_responsibilities, - PERSISTENCE: self.persistence_responsibilities - } - - @property - def validation_responsibilities(self): - return [ - self._validate_payload_types_step, - self._validate_customer_data_step, - self._validate_governance_entity_data_step - ] - - @property - def access_responsibilities(self): - return [ - self._access_tenant_entity_step, - self._access_priority_management_data_step, - self._access_ruleset_entity_step, - self._access_license_relation_entity_step - ] - - @property - def instantiation_responsibilities(self): - return [ - self._instantiate_data_within_priority_step, - self._instantiate_priority_management_step - ] - - @property - def persistence_responsibilities(self): - return [self._persist_priority_management_step] - - @property - def _post_required_map(self): - return { - TENANT_ATTR: str, - LICENSE_KEYS_ATTR: list, - GOVERNANCE_ENTITY_TYPE_ATTR: str, - GOVERNANCE_ENTITY_ID_ATTR: str - } - - def _validate_payload_types_step(self, event: Dict): - """ - Mandates payload parameters for the patch event, adhering - to the respective requirement map. - :parameter event: Dict - :return: Union[Dict, Type[None]] - """ - content = retrieve_invalid_parameter_types( - event=event, required_param_types=self._post_required_map - ) - - ks: list = event.get(LICENSE_KEYS_ATTR) - condition = not ks or any(True for k in ks if not isinstance(k, str)) - - mid: str = event.get(MANAGEMENT_ID_ATTR, None) - - template = BAD_REQUEST_IMPROPER_TYPES - issue = None - - if not content and condition: - issue = f'each of {LICENSE_KEYS_ATTR}: str.' - - elif not content and len(ks) != len(set(ks)): - issue = f'Values of \'{LICENSE_KEYS_ATTR}\' ' \ - f'parameter must be unique.' - template = 'Bad request. {}' - - elif not content and mid is not None and not isinstance(mid, str): - issue = f'\'{MANAGEMENT_ID_ATTR}\':str' - - content = template.format(issue) if issue else content - - if content: - event = None - self._code = RESPONSE_BAD_REQUEST_CODE - self._content = content - - return event - - def _access_priority_management_data_step(self, event: Dict): - """ - Mandates priority management data retrieval and lack of persistence. - Establishes - Response variables are assigned, under the following predicates: - Given absence: `code` - 404, `content` - persistence reason. - Given managed-entity id conflict: `code` - 409, `content` - conflict. - :parameter event: Dict - :return: Union[Dict, Type[None]] - """ - - complemented = self.tenant_entity - if not complemented: - _LOG.error('Execution step is missing \'tenant_entity\' variable.') - return None - - _id = complemented.name - _template = ENTITY_TEMPLATE.format(entity=self.entity, id=_id) - - # Managed entity id, i.e. ruleset-id. - gvc_id = event[GOVERNANCE_ENTITY_ID_ATTR] - - mgmt = complemented.management or dict() - - _type = event[GOVERNANCE_ENTITY_TYPE_ATTR] - - conflict = f' \'{gvc_id}\' {_type}' + CONFLICT - - # Until the delegation(s) and governance is not put into place. - # Check for cross priority collision as well. - - if any(_pid for _pid in mgmt if gvc_id in mgmt[_pid]): - issue = conflict + f' within {MANAGEMENT_ATTR}' - _LOG.warning(_template + issue) - self._code = RESPONSE_CONFLICT - self._content = _template + issue - return None - - # Separate concern of access within data. - pid = event.get(MANAGEMENT_ID_ATTR, None) - if not pid: - return event - - if event and pid not in mgmt: - issue = f' priority \'{pid}\' {MANAGEMENT_ID_ATTR}' - _LOG.warning(_template + issue + PERSISTENCE_ERROR) - - issue, _ = issue.rsplit(' ', 1) - issue += f' {PRIORITY_ID_ATTR}' - self._code = RESPONSE_RESOURCE_NOT_FOUND_CODE - self._content = _template + issue + PERSISTENCE_ERROR - event = None - - if event: - # Attaches management-id, if priority has already been given. - self.mid = pid - - return event - - def _access_ruleset_entity_step(self, event: Dict): - """ - Mandates Ruleset entity access and lack of persistence.\n - Response variables are assigned, under the following predicates: - \nGiven absence: `code` - 404, `content` - persistence reason. - :parameter event: Dict - :return: Union[Dict, Type[None]] - """ - if event[GOVERNANCE_ENTITY_TYPE_ATTR] != RULESET_ATTR: - return event - - ruleset_service = self.ruleset_service - rid = event.get(GOVERNANCE_ENTITY_ID_ATTR, '') - _template = ENTITY_TEMPLATE.format(entity='Ruleset', id=rid) - - self.ruleset_entity = ruleset_service.get_ruleset_by_id( - ruleset_id=rid - ) - - if not self.ruleset_entity or not self.ruleset_entity.licensed: - event = None - self._code = RESPONSE_RESOURCE_NOT_FOUND_CODE - self._content = _template + PERSISTENCE_ERROR - - return event - - def _instantiate_data_within_priority_step(self, event: Dict): - """ - Mandates Settings-complemented Tenant entity instantiation of - ruleset-license-priority-management type, driven by given - license-keys(s).\n Response variables are assigned, under the - following predicates: \nGiven absence: `code` - 404, `content` - - persistence reason. - :parameter event: Dict - :return: Union[Dict, Type[None]] - """ - mid: str = self.mid - complemented: Complemented = self.tenant_entity - if not mid: - # Request has been issued to create a priority. - return event - - if not complemented: - return None - - _template = ENTITY_TEMPLATE.format( - entity=self.entity, id=complemented.name - ) - - pid = event[MANAGEMENT_ID_ATTR] - mgmt: dict = complemented.management[pid] - mgmt[event[GOVERNANCE_ENTITY_ID_ATTR]] = event[LICENSE_KEYS_ATTR] - _type = event[GOVERNANCE_ENTITY_TYPE_ATTR] - updated = ATTRIBUTE_UPDATED.format( - f'\'{mid}\' {_type} {MANAGEMENT_ATTR}:\'{pid}\'', - ', '.join(event[LICENSE_KEYS_ATTR]) - ) - _LOG.info(_template + updated) - - complemented.management[pid] = mgmt - self.mid = mid - return event - - def _instantiate_priority_management_step(self, event: Dict): - """ - Mandates Settings-complemented Tenant entity instantiation of - ruleset-license-priority-management type, driven by given - license-keys(s).\n Response variables are assigned, under the - following predicates: \nGiven absence: `code` - 404, `content` - - persistence reason. - :parameter event: Dict - :return: Union[Dict, Type[None]] - """ - mid: str = self.mid - if mid: - # Request is issued to create a managed data within a priority. - return event - - complemented = self.tenant_entity - - if not complemented: - return None - - _template = ENTITY_TEMPLATE.format( - entity=self.entity, id=complemented.name - ) - - priority_service = self.priority_governance_service - - self.mid = priority_service.create_management( - entity=complemented, data={ - event[GOVERNANCE_ENTITY_ID_ATTR]: event[LICENSE_KEYS_ATTR] - } - ) - label = self.priority_governance_service.governance_type - message = f' \'{self.mid}\' \'{label}\'' - _LOG.info(_template + message + INSTANTIATED) - - return event - - -class PatchTenantLicensePriorityHandler(CommandTenantLicensePriorityHandler): - - ruleset_entity: Optional[Ruleset] - - def __init__( - self, modular_service: ModularService, license_service: LicenseService, - priority_governance_service: PriorityGovernanceService, - ruleset_service: RulesetService - ): - - # Declares a subjected query specification - self.license_service = license_service - self.ruleset_service = ruleset_service - - super().__init__( - modular_service=modular_service, - priority_governance_service=priority_governance_service - ) - - def _reset(self): - super()._reset() - # Declares request-pending customer entity - self.ruleset_entity = None - - def define_action_mapping(self): - return { - TENANTS_LICENSE_PRIORITIES_PATH: { - PATCH_METHOD: self.patch_license_priority - } - } - - def patch_license_priority(self, event): - action = PATCH_METHOD.capitalize() - return self._process_action(event=event, action=action) - - @property - def attributes_to_log(self) -> Dict[Union[str, Type[None]], Iterable[str]]: - license_keys = [ - LICENSE_KEYS_TO_APPEND_ATTR, LICENSE_KEYS_TO_PREPEND_ATTR, - LICENSE_KEYS_TO_DETACH_ATTR - ] - return { - VALIDATION: list(self._patch_required_map) + license_keys + [ - CUSTOMER_ATTR - ], - ACCESSIBILITY: [ - TENANT_ATTR, GOVERNANCE_ENTITY_ID_ATTR, - MANAGEMENT_ID_ATTR, *license_keys - ], - INSTANTIATION: ['tenant_entity', MANAGEMENT_ID_ATTR], - PERSISTENCE: ['tenant_entity'] - } - - @property - def responsibilities(self) -> Dict[ - str, Iterable[Callable[[Dict], Union[Dict, Type[None]]]] - ]: - return { - VALIDATION: self.validation_responsibilities, - ACCESSIBILITY: self.access_responsibilities, - AMENDMENT: self.amendment_responsibilities, - PERSISTENCE: self.persistence_responsibilities - } - - @property - def validation_responsibilities(self): - return [ - self._validate_payload_types_step, - self._validate_customer_data_step, - self._validate_governance_entity_data_step - ] - - @property - def access_responsibilities(self): - return [ - self._access_tenant_entity_step, - self._access_priority_management_data_step, - self._access_ruleset_entity_step, - self._access_attachable_license_keys_step - ] - - @property - def amendment_responsibilities(self): - return [ - self._amend_data_within_priority_step, - self._descended_amendment_step - ] - - @property - def persistence_responsibilities(self): - return [self._persist_priority_management_step] - - @property - def _patch_required_map(self): - return { - TENANT_ATTR: str, - GOVERNANCE_ENTITY_TYPE_ATTR: str, - GOVERNANCE_ENTITY_ID_ATTR: str, - MANAGEMENT_ID_ATTR: str - } - - def _validate_payload_types_step(self, event: Dict): - """ - Mandates payload parameters for the patch event, adhering - to the respective requirement map. - :parameter event: Dict - :return: Union[Dict, Type[None]] - """ - content = retrieve_invalid_parameter_types( - event=event, required_param_types=self._patch_required_map - ) - key_attrs = ( - LICENSE_KEYS_TO_PREPEND_ATTR, LICENSE_KEYS_TO_APPEND_ATTR, - LICENSE_KEYS_TO_DETACH_ATTR - ) - - if not content: - _applicable = False - - for attr in key_attrs: - keys: List = event.get(attr) - cdn = (not isinstance(keys, list) or - any(True for k in keys if not isinstance(k, str))) - - if keys is not None and cdn: - issue = f'each of \'{attr}\' list: str.' - content = BAD_REQUEST_IMPROPER_TYPES.format(issue) - break - - elif keys and len(keys) != len(set(keys)): - issue = f'each of \'{attr}\' must be unique.' - content = BAD_REQUEST_IMPROPER_TYPES.format(issue) - break - - if keys: - _applicable = True - - if not content and not _applicable: - stringed = ', '.join(f'\'{attr}\''for attr in key_attrs) - content = 'Bad request. At least one out of the following ' \ - f'parameters must be provided: {stringed}.' - - if content: - event = None - self._code = RESPONSE_BAD_REQUEST_CODE - self._content = content - - return event - - def _access_priority_management_data_step(self, event: Dict): - """ - Mandates priority management data retrieval and lack of persistence.\n - Response variables are assigned, under the following predicates: - Given absence: `code` - 404, `content` - persistence reason. - Given managed-entity id conflict: `code` - 409, `content` - conflict. - :parameter event: Dict - :return: Union[Dict, Type[None]] - """ - complemented = self.tenant_entity - if not complemented: - return None - - _id = complemented.name - _template = ENTITY_TEMPLATE.format(entity=self.entity, id=_id) - - gvc_id = event[GOVERNANCE_ENTITY_ID_ATTR] - mid = event[MANAGEMENT_ID_ATTR] - - mgmt = complemented.management or dict() - - if mid not in mgmt: - issue = f' priority \'{mid}\' {MANAGEMENT_ID_ATTR}' - _LOG.warning(_template + issue + PERSISTENCE_ERROR) - - issue, _ = issue.rsplit(' ', 1) - issue += f' {PRIORITY_ID_ATTR}' - self._code = RESPONSE_RESOURCE_NOT_FOUND_CODE - self._content = _template + issue + PERSISTENCE_ERROR - event = None - - elif gvc_id not in mgmt[mid]: - _type = event[GOVERNANCE_ENTITY_TYPE_ATTR] - scope = f' within \'{mid}\' ' - issue = f' \'{gvc_id}\' {_type}' + PERSISTENCE_ERROR + scope - _LOG.warning(_template + issue + MANAGEMENT_ID_ATTR) - - self._code = RESPONSE_CONFLICT - self._content = _template + issue + scope + PRIORITY_ID_ATTR - event = None - - return event - - def _access_ruleset_entity_step(self, event: Dict): - """ - Mandates Ruleset entity access and lack of persistence.\n - Response variables are assigned, under the following predicates: - \nGiven absence: `code` - 404, `content` - persistence reason. - :parameter event: Dict - :return: Union[Dict, Type[None]] - """ - if event[GOVERNANCE_ENTITY_TYPE_ATTR] != RULESET_ATTR: - return event - - complemented: Complemented = self.tenant_entity - ruleset_service = self.ruleset_service - - rid = event.get(GOVERNANCE_ENTITY_ID_ATTR, '') - pid = event.get(MANAGEMENT_ID_ATTR) - _template = ENTITY_TEMPLATE.format(entity='Ruleset', id=rid) - - mgmt = (complemented.management or dict()).get(pid, dict()) - - retained = rid in mgmt - self.ruleset_entity = None if not retained else ruleset_service. \ - get_ruleset_by_id(ruleset_id=rid) - - if not self.ruleset_entity or not self.ruleset_entity.licensed: - if retained: - # Provide eventually-consistent self-healing. - _LOG.warning(_template + ' has become obsolete - removing it.') - mgmt.pop(rid) - self.mid = rid - self._persist_priority_management_step(event=event) - event = None - self._code = RESPONSE_RESOURCE_NOT_FOUND_CODE - self._content = _template + PERSISTENCE_ERROR - - return event - - def _access_attachable_license_keys_step(self, event: Dict): - """ - Mandates Tenant entity access to given license key(s) to prepend - or append, as well respective relation with the pending ruleset.\n - Response variables are assigned, under the following predicates: - \nGiven absence: `code` - 404, `content` - persistence reason. - :parameter event: Dict - :return: Union[Dict, Type[None]] - """ - key_attrs = ( - LICENSE_KEYS_TO_PREPEND_ATTR, LICENSE_KEYS_TO_APPEND_ATTR - ) - for attr in key_attrs: - keys = event.get(attr) or [] - if keys: - is_applicable = self._access_license_relation_entity_step( - event={LICENSE_KEYS_ATTR: keys} - ) - if not is_applicable: - event = None - break - return event - - def _amend_data_within_priority_step(self, event: Dict): - """ - Mandates Settings-complemented Tenant entity instantiation of - ruleset-license-priority-management type, driven by given - license-keys(s).\n Response variables are assigned, under the - following predicates: \nGiven absence: `code` - 404, `content` - - persistence reason. - :parameter event: Dict - :return: Union[Dict, Type[None]] - """ - complemented: Complemented = self.tenant_entity - - if not complemented: - return None - - _template = ENTITY_TEMPLATE.format( - entity=self.entity, id=complemented.name - ) - - mid = event[MANAGEMENT_ID_ATTR] - gvc_id = event[GOVERNANCE_ENTITY_ID_ATTR] - - mgmt: dict = complemented.management[mid] - - license_key_priority: list = mgmt[gvc_id] or [] - - to_detach: list = event.get(LICENSE_KEYS_TO_DETACH_ATTR) - - if to_detach: - for key in to_detach: - if key in license_key_priority: - license_key_priority.remove(key) - - to_prepend: list = event.get(LICENSE_KEYS_TO_PREPEND_ATTR) or [] - to_append: list = event.get(LICENSE_KEYS_TO_APPEND_ATTR) or [] - - for each in to_prepend[::-1]: - if each not in license_key_priority: - license_key_priority.insert(0, each) - - for each in to_append: - if each not in license_key_priority: - license_key_priority.append(each) - - mgmt[gvc_id] = license_key_priority - - _type = event[GOVERNANCE_ENTITY_TYPE_ATTR] - updated = ATTRIBUTE_UPDATED.format( - f'\'{mid}\' {_type} {MANAGEMENT_ATTR}:\'{mid}\'', - ', '.join(mgmt[gvc_id]) - ) - _LOG.info(_template + updated) - - complemented.management[mid] = mgmt - return event - - def _descended_amendment_step(self, event: Dict): - - priority_governance_service = self.priority_governance_service - complemented: Complemented = self.tenant_entity - - if not complemented: - return None - - _template = ENTITY_TEMPLATE.format( - entity=self.entity, id=complemented.name - ) - - mid = event[MANAGEMENT_ID_ATTR] - eid = event[GOVERNANCE_ENTITY_ID_ATTR] - mgmt = complemented.management or dict() - managed = mgmt.get(mid, dict()) - - if eid in managed and not managed.get(eid): - managed.pop(eid) - attr = event[GOVERNANCE_ENTITY_TYPE_ATTR] - scope = f'\'{mid}\' {MANAGEMENT_ATTR}' - message = f' \'{eid}\' {attr} removed out of {scope}' - _LOG.info(_template + message) - - if mid in mgmt and not managed: - # No managed data is left. - self.tenant_entity = priority_governance_service.delete_management( - entity=complemented, mid=mid - ) - if not self.tenant_entity: - # Unlikely: as it is verified on the _access step, beforehand. - issue = f' {mid} {PRIORITY_ID_ATTR} {PERSISTENCE_ERROR}' - self._code = RESPONSE_RESOURCE_NOT_FOUND_CODE - self._content = _template + issue - event = None - - return event - - -class DeleteTenantLicensePriorityHandler(CommandTenantLicensePriorityHandler): - def __init__( - self, modular_service: ModularService, - priority_governance_service: PriorityGovernanceService - ): - super().__init__( - modular_service=modular_service, - priority_governance_service=priority_governance_service - ) - - def define_action_mapping(self): - return { - TENANTS_LICENSE_PRIORITIES_PATH: { - DELETE_METHOD: self.delete_license_priority - } - } - - def delete_license_priority(self, event): - action = DELETE_METHOD.capitalize() - return self._process_action(event=event, action=action) - - @property - def attributes_to_log(self) -> Dict[Union[str, Type[None]], Iterable[str]]: - return { - VALIDATION: list(self._delete_required_map), - ACCESSIBILITY: [ - TENANT_ATTR, GOVERNANCE_ENTITY_ID_ATTR, MANAGEMENT_ID_ATTR - ], - AMENDMENT: ['tenant_entity', 'mid', 'data_id'], - PERSISTENCE: ['tenant_entity'] - } - - @property - def responsibilities(self) -> Dict[ - str, Iterable[Callable[[Dict], Union[Dict, Type[None]]]] - ]: - return { - VALIDATION: self.validation_responsibilities, - ACCESSIBILITY: self.access_responsibilities, - AMENDMENT: self.amendment_responsibilities, - PERSISTENCE: self.persistence_responsibilities - } - - @property - def validation_responsibilities(self): - return [ - self._validate_payload_types_step, - self._validate_customer_data_step, - self._validate_governance_entity_data_step - ] - - @property - def access_responsibilities(self): - return [ - self._access_tenant_entity_step, - self._access_priority_management_data_step, - self._access_entity_within_priority_data_step - ] - - @property - def amendment_responsibilities(self): - return [ - self._amend_entity_persistence_step, - self._amend_scoped_priority_data_step - ] - - @property - def persistence_responsibilities(self): - return [self._persist_priority_management_step] - - @property - def _delete_required_map(self): - return { - TENANT_ATTR: str, - GOVERNANCE_ENTITY_TYPE_ATTR: str - } - - def _validate_payload_types_step(self, event: Dict): - """ - Mandates payload parameters for the delete event, adhering - to the respective requirement map. - :parameter event: Dict - :return: Union[Dict, Type[None]] - """ - content = retrieve_invalid_parameter_types( - event=event, required_param_types=self._delete_required_map - ) - - mgmt_id = event.get(MANAGEMENT_ID_ATTR, None) - gvc_id = event.get(GOVERNANCE_ENTITY_ID_ATTR, None) - - template = BAD_REQUEST_IMPROPER_TYPES - issue = '' - - if not content: - if mgmt_id and not isinstance(mgmt_id, str): - issue = f'{MANAGEMENT_ID_ATTR}:str' - - elif gvc_id and not isinstance(gvc_id, str): - issue = issue or f'{GOVERNANCE_ENTITY_ID_ATTR}:str' - - if not mgmt_id and gvc_id is not None: - issue = f'Parameter \'{GOVERNANCE_ENTITY_ID_ATTR}\' must be' - issue += f' provided with \'{MANAGEMENT_ID_ATTR}\'' - template = 'Bad request. {}.' - - content = template.format(issue) if issue else content - if content: - event = None - self._code = RESPONSE_BAD_REQUEST_CODE - self._content = content - - return event - - def _access_priority_management_data_step(self, event: Dict): - """ - Mandates priority management data retrieval and lack of persistence.\n - Response variables are assigned, under the following predicates: - \nGiven absence: `code` - 404, `content` - persistence reason. - :parameter event: Dict - :return: Union[Dict, Type[None]] - """ - complemented = self.tenant_entity - if not complemented: - return None - - _id = complemented.name - _template = ENTITY_TEMPLATE.format(entity=self.entity, id=_id) - - # Priority-id. - mid = event.get(MANAGEMENT_ID_ATTR, None) - if mid is None: - return event - - mgmt = complemented.management or dict() - - if mid not in mgmt: - issue = f' priority \'{mid}\' {MANAGEMENT_ID_ATTR}' - _LOG.warning(_template + issue + PERSISTENCE_ERROR) - - issue, _ = issue.rsplit(' ', 1) - issue += f' {PRIORITY_ID_ATTR}' - self._code = RESPONSE_RESOURCE_NOT_FOUND_CODE - self._content = _template + issue + PERSISTENCE_ERROR - event = None - - return event - - def _access_entity_within_priority_data_step(self, event: Dict): - """ - Mandates priority management data retrieval and lack of persistence.\n - Response variables are assigned, under the following predicates: - \nGiven absence: `code` - 404, `content` - persistence reason. - :parameter event: Dict - :return: Union[Dict, Type[None]] - """ - complemented = self.tenant_entity - if not complemented: - return None - - _id = complemented.name - _template = ENTITY_TEMPLATE.format(entity=self.entity, id=_id) - - pid = event.get(MANAGEMENT_ID_ATTR, None) - if pid is None: - return event - - mgmt = complemented.management or dict() - priority_data = mgmt[pid] - - gvc_id = event.get(GOVERNANCE_ENTITY_ID_ATTR, None) - if gvc_id is None: - return event - - if gvc_id and gvc_id not in priority_data: - _type = event[GOVERNANCE_ENTITY_TYPE_ATTR] - scope = f' within \'{pid}\' ' - issue = f' \'{gvc_id}\' {_type}' + PERSISTENCE_ERROR + scope - _LOG.warning(_template + issue + MANAGEMENT_ATTR) - self._code = RESPONSE_RESOURCE_NOT_FOUND_CODE - self._content = _template + issue + PRIORITY_ID_ATTR - event = None - - return event - - def _amend_entity_persistence_step(self, event: Dict): - """ - Designated to amend data, by deleting pending entity, given the - request has not been issued to amend data within. - :parameter event: Dict - :return: Optional[dict] - """ - if MANAGEMENT_ID_ATTR in event: - # Request has derived a management or managed-entity to remove. - return event - - complemented: Complemented = self.tenant_entity - if not complemented: - return None - - _id = complemented.name - _template = ENTITY_TEMPLATE.format(entity=self.entity, id=_id) - - governance_type = event[GOVERNANCE_ENTITY_TYPE_ATTR].capitalize() - scope = _template + f' of \'{governance_type}\' management' - - _LOG.debug(_template + ' is going to be deleted.') - if self.modular_service.delete(entity=complemented): - message = scope + ' has been removed.' - _LOG.info(message) - self._code = RESPONSE_OK_CODE - self._content = message - else: - message = scope + ' could not be removed.' - _LOG.error(message) - self._content = message - - return None - - def _amend_scoped_priority_data_step(self, event: Dict): - complemented: Complemented = self.tenant_entity - if not complemented: - return None - - _id = complemented.name - - _template = ENTITY_TEMPLATE.format(entity=self.entity, id=_id) - priority_governance_service = self.priority_governance_service - - mid = event[MANAGEMENT_ID_ATTR] - eid = event.get(GOVERNANCE_ENTITY_ID_ATTR, None) - - # Issued to remove managed entity data, within priority - if eid: - complemented.management[mid].pop(eid) - attr = event[GOVERNANCE_ENTITY_TYPE_ATTR] - scope = f'\'{mid}\' {MANAGEMENT_ATTR}' - message = f' \'{eid}\' {attr} removed out of {scope}' - _LOG.info(_template + message) - - # Issued to remove priority data or consequence of managed data absence - if not eid or not complemented.management[mid]: - self.tenant_entity = priority_governance_service.delete_management( - entity=complemented, mid=mid - ) - if not self.tenant_entity: - # Verified on the _access step, beforehand. - issue = f' {mid} {PRIORITY_ID_ATTR} {PERSISTENCE_ERROR}' - self._code = RESPONSE_RESOURCE_NOT_FOUND_CODE - self._content = _template + issue - event = None - - return event - - def _produce_response_dto(self, event: Optional[Dict] = None) -> \ - Union[str, Dict, List, Type[None]]: - """ - Mandates derivation of a patch-response data transfer object, - based on a pending Customer Parent entity. - :parameter event: Dict - :return: Union[Dict, List, Type[None]] - """ - # Executes given a incomplete removal. - entity: Complemented = self.tenant_entity - mid = event.get(MANAGEMENT_ID_ATTR) - gvc_id = event.get(GOVERNANCE_ENTITY_ID_ATTR) - data_id = gvc_id or mid - - if not entity: - _LOG.error('Variables \'tenant_entity\' and ' - '\'data_id\' are missing.') - return None - - if data_id != mid and mid in entity.management: - get_dto = self.priority_governance_service.get_management - response = get_dto(entity=entity, mid=mid) - response = self._get_expanded_dto(dto=response or dict()) - else: - response = f'Priority \'{mid}\' has been removed.' - - return response - - -class TenantLicensePriorityHandler(AbstractComposedHandler): - ... - - -def instantiate_tenant_license_priority_handler( - modular_service: ModularService, ruleset_service: RulesetService, - priority_governance_service: PriorityGovernanceService, - license_service: LicenseService -): - get_handler = GetTenantLicensePriorityHandler( - modular_service=modular_service, - priority_governance_service=priority_governance_service - ) - post_handler = PostTenantLicensePriorityHandler( - modular_service=modular_service, - ruleset_service=ruleset_service, license_service=license_service, - priority_governance_service=priority_governance_service - ) - patch_handler = PatchTenantLicensePriorityHandler( - modular_service=modular_service, - ruleset_service=ruleset_service, license_service=license_service, - priority_governance_service=priority_governance_service - ) - delete_handler = DeleteTenantLicensePriorityHandler( - modular_service=modular_service, - priority_governance_service=priority_governance_service - ) - - return TenantLicensePriorityHandler( - resource_map={ - TENANTS_LICENSE_PRIORITIES_PATH: { - GET_METHOD: get_handler, - POST_METHOD: post_handler, - PATCH_METHOD: patch_handler, - DELETE_METHOD: delete_handler - } - } - ) diff --git a/src/handlers/tenants/tenant_handler.py b/src/handlers/tenants/tenant_handler.py deleted file mode 100644 index 738c82bb2..000000000 --- a/src/handlers/tenants/tenant_handler.py +++ /dev/null @@ -1,468 +0,0 @@ -import uuid -from http import HTTPStatus -from typing import Iterable, Optional, Callable, Dict, Union, Type, List, \ - Any - -from botocore.exceptions import ClientError -from modular_sdk.models.pynamodb_extension.base_model import \ - LastEvaluatedKey, ResultIterator -from modular_sdk.models.pynamodb_extension.pynamodb_to_pymongo_adapter import \ - Result -from modular_sdk.models.region import RegionModel - -from handlers.abstracts.abstract_handler import AbstractComposedHandler -from handlers.abstracts.abstract_modular_entity_handler import \ - ModularService, AbstractModularEntityHandler -from helpers import build_response -from helpers import validate_params -from helpers.constants import CUSTOMER_ATTR, TENANT_ATTR, TENANTS_ATTR, \ - LIMIT_ATTR, NEXT_TOKEN_ATTR, \ - CLOUD_IDENTIFIER_ATTR, NAME_ATTR, DISPLAY_NAME_ATTR, CLOUD_ATTR, \ - PRIMARY_CONTACTS_ATTR, SECONDARY_CONTACTS_ATTR, DEFAULT_OWNER_ATTR, \ - TENANT_MANAGER_CONTACTS_ATTR, REGION_ATTR, \ - RULES_TO_EXCLUDE_ATTR, RULES_TO_INCLUDE_ATTR, PARAM_COMPLETE, \ - HTTPMethod -from helpers.log_helper import get_logger -from helpers.regions import get_region_by_cloud, CLOUD_REGIONS -from helpers.time_helper import utc_iso -from modular_sdk.models.tenant import Tenant -from services.environment_service import EnvironmentService - -_LOG = get_logger(__name__) - -TENANTS_PATH = '/tenants' -TENANTS_REGIONS_PATH = '/tenants/regions' - -FORBIDDEN_ACCESS = 'Access to {} entity is forbidden.' - -AUTHORIZATION = 'Authorization' -SPECIFICATION = 'Query-Specification' -ACCESSIBILITY = 'Entity-Accessibility' - -SPECIFICATION_ATTR = 'specification' - -PERSISTENCE_ERROR = ' does not exist' -RETAIN_ERROR = ' could not be persisted' - -RETRIEVED = ' has been retrieved' -INSTANTIATED = ' has been instantiated' - -ATTRIBUTE_UPDATED = ' {} has been set to {}' -RETAINED = ' has been persisted' - - -class BaseTenantHandler(AbstractModularEntityHandler): - - @property - def entity(self): - return TENANT_ATTR.capitalize() - - -class GetTenantHandler(BaseTenantHandler): - specification: Dict[str, Any] - - def __init__(self, modular_service: ModularService): - super().__init__(modular_service=modular_service) - self._last_evaluated_key = None - - def _reset(self): - super()._reset() - # Declares a subjected query specification - self.specification = dict() - self._last_evaluated_key = None - - def define_action_mapping(self): - return { - TENANTS_PATH: { - HTTPMethod.GET: self.get_tenants, - }, - } - - def post_tenant(self, event: dict): - return {} - - def get_tenants(self, event): - return self._process_action(event=event, action=HTTPMethod.GET) - - @property - def attributes_to_log(self) -> Dict[Union[str, Type[None]], Iterable[str]]: - return { - SPECIFICATION: [CUSTOMER_ATTR, TENANTS_ATTR, SPECIFICATION_ATTR] - } - - @property - def responsibilities(self) -> Dict[ - str, Iterable[Callable[[Dict], Union[Dict, Type[None]]]] - ]: - return { - SPECIFICATION: self.specification_responsibilities - } - - @property - def specification_responsibilities(self): - return [ - self._obscure_specification_step, - self._name_specification_step, - self._prepare_last_evaluated_key_step, - self._cid_specification_step - ] - - @property - def i_query(self) -> Union[ResultIterator, Result]: - """ - Produces a query iterator-like output, based on the pending - specification, resetting it afterwards, having installed the query: - 1. Given only a tenants-name, retrieves demanded entity. - 2. Given a customer name, fetches every entity belonging to the - customer, singling out active tenants, provided any. - 3. Otherwise, retrieves every active tenant entity. - :return: Iterator - """ - specification = self.specification - - customer: Optional[str] = specification.get(CUSTOMER_ATTR) - scope: Optional[list] = specification.get(TENANTS_ATTR) - cloud_identifier: Optional[str] = specification.get( - CLOUD_IDENTIFIER_ATTR) - complete: bool = specification.get(PARAM_COMPLETE) - limit: Optional[int] = specification.get(LIMIT_ATTR) - lek: Optional[str] = specification.get(NEXT_TOKEN_ATTR) - params = dict(active=True, limit=limit, last_evaluated_key=lek) - - if cloud_identifier: - tenant = next(self.modular_service.i_get_tenants_by_acc( - cloud_identifier), None) - if not tenant or customer and tenant.customer_name != customer or scope and tenant.name not in scope: - query = iter([]) - else: - query = iter([tenant]) - elif scope: - # TODO here can be multiple values but limit won't work - query = self.modular_service.i_get_tenant(iterator=iter(scope)) - elif customer: - query = self.modular_service.i_get_customer_tenant( - **{'customer': customer, **params} - ) - else: - query = self.modular_service.i_get_tenants(**params) - if complete: - query = self.modular_service.i_get_tenant(query) - - self.specification = dict() - return query - - def _obscure_specification_step(self, event: Dict) -> \ - Union[Dict, Type[None]]: - """ - Mandates concealing specification, based on given query. - Given a non-system customer has issued one, obscures - the view to the said customer tenants. - :parameter event: Dict - :return: Union[Dict, Type[None]] - """ - customer = event.get(CUSTOMER_ATTR) - - if customer: - self.specification[CUSTOMER_ATTR] = customer - self.specification[PARAM_COMPLETE] = event.get(PARAM_COMPLETE) - return event - - def _name_specification_step(self, event: Dict): - """ - Mandates Tenant name specification, based on given query. - Given the aforementioned partition attribute, alters - specification with the appropriate name iterator. - :parameter event: Dict - :return: Union[Dict, Type[None]] - """ - tenants: list = event.get(TENANTS_ATTR) - if tenants: - self.specification[TENANTS_ATTR] = tenants - return event - - def _cid_specification_step(self, event: Dict): - """ - Mandates Tenant name specification, based on given query. - Given the aforementioned partition attribute, alters - specification with the appropriate name iterator. - :parameter event: Dict - :return: Union[Dict, Type[None]] - """ - cid: str = event.get(CLOUD_IDENTIFIER_ATTR) - if cid: - self.specification[CLOUD_IDENTIFIER_ATTR] = cid - return event - - def _prepare_last_evaluated_key_step(self, event: Dict): - """ - Retrieves limit and last evaluated key from event and prepares them - """ - limit = int(event.get(LIMIT_ATTR)) if event.get(LIMIT_ATTR) else None - old_lek = LastEvaluatedKey.deserialize( - event.get(NEXT_TOKEN_ATTR) or None) - self.specification[LIMIT_ATTR] = limit - self.specification[NEXT_TOKEN_ATTR] = old_lek.value - return event - - def last_evaluated_key(self) -> LastEvaluatedKey: - return LastEvaluatedKey(self._last_evaluated_key) - - def _produce_response_dto(self, event: Optional[Dict] = None) -> \ - Union[str, Dict, List, Type[None]]: - """ - Mandates derivation of a query-response data transfer object, - based on a pending source-iterator. Apart from that, - for each entity, a user-respective latest-login is injected - into attached customer dto. - :parameter event: Dict - :return: Union[Dict, List, Type[None]] - """ - i_query = self.i_query - dto = [self.modular_service.get_dto(each) for each in i_query] - self._last_evaluated_key = getattr(i_query, 'last_evaluated_key', None) - return dto - - -class PostTenantHandler: - def __init__(self, modular_service: ModularService, - environment_service: EnvironmentService): - self._modular_service = modular_service - self._environment_service = environment_service - - def define_action_mapping(self) -> dict: - return { - TENANTS_PATH: { - HTTPMethod.POST: self.post_tenant, - }, - } - - def post_tenant(self, event: dict) -> dict: - validate_params(event, (CUSTOMER_ATTR,)) - name = event[NAME_ATTR] - display_name = event[DISPLAY_NAME_ATTR] - cloud = event[CLOUD_ATTR] - acc = event[CLOUD_IDENTIFIER_ATTR] - customer = event.get(CUSTOMER_ATTR) - contacts = { - PRIMARY_CONTACTS_ATTR: event.get(PRIMARY_CONTACTS_ATTR), - SECONDARY_CONTACTS_ATTR: event.get(SECONDARY_CONTACTS_ATTR), - TENANT_MANAGER_CONTACTS_ATTR: event.get( - TENANT_MANAGER_CONTACTS_ATTR), - DEFAULT_OWNER_ATTR: event.get(DEFAULT_OWNER_ATTR) - - } - by_name = self._modular_service.get_tenant(name) - if by_name: - return build_response(code=HTTPStatus.CONFLICT, - content=f'Tenant `{name}` already exists') - by_acc = next(self._modular_service.i_get_tenants_by_acc(acc), None) - if by_acc: - return build_response( - code=HTTPStatus.CONFLICT, - content=f'Cloud id `{acc}` already exists in db' - ) - tenant_service = self._modular_service.modular_client.tenant_service() - - if hasattr(tenant_service, 'create'): - _LOG.warning('Tenant service can create a tenant') - item = tenant_service.create( - tenant_name=name, - display_name=display_name, - customer_name=customer, - cloud=cloud, - acc=acc, - contacts=contacts - ) - try: - tenant_service.save(item) - except ClientError as e: - _LOG.info(f'Expected client error occurred trying ' - f'to save tenant: {e}') - return build_response( - code=HTTPStatus.FORBIDDEN, - content='You cannot activate a new tenant on the ' - 'current env' - ) - return build_response(code=HTTPStatus.CREATED, - content=self._modular_service.get_dto(item)) - # no create method - if not self._environment_service.is_docker(): - return build_response( - code=HTTPStatus.BAD_REQUEST, - content='Currently the action is not available' - ) - # kludge for on-prem - item = Tenant( - name=name, - display_name=display_name, - display_name_to_lower=display_name.lower(), - read_only=False, - is_active=True, - customer_name=customer, - cloud=cloud, - activation_date=utc_iso(), - project=acc, - contacts=contacts - ) - item.save() - return build_response(code=HTTPStatus.CREATED, - content=self._modular_service.get_dto(item)) - - -class TenantRegionHandler: - def __init__(self, modular_service: ModularService, - environment_service: EnvironmentService): - self._modular_service = modular_service - self._environment_service = environment_service - - def define_action_mapping(self) -> dict: - return { - TENANTS_REGIONS_PATH: { - HTTPMethod.POST: self.post_tenant_region, - }, - } - - def post_tenant_region(self, event: dict) -> dict: - tenant_name: str = event[TENANT_ATTR] - region_nn: str = event[REGION_ATTR] - tenant = self._modular_service.get_tenant(tenant_name) - if not tenant: - return build_response( - code=HTTPStatus.NOT_FOUND, - content=f'Tenant {tenant_name} not found' - ) - if region_nn in self._modular_service.get_tenant_regions(tenant): - return build_response( - code=HTTPStatus.CONFLICT, - content=f'Region: {region_nn} already active for tenant' - ) - if tenant.cloud not in CLOUD_REGIONS: - return build_response( - code=HTTPStatus.BAD_REQUEST, - content=f'Tenant {tenant_name} belongs to {tenant.cloud}. ' - f'Allowed clouds: {", ".join(CLOUD_REGIONS)}' - ) - if region_nn not in get_region_by_cloud(tenant.cloud): - return build_response( - code=HTTPStatus.BAD_REQUEST, - content=f'Region {region_nn} does not belong to ' - f'{tenant.cloud} cloud.' - ) - region_service = self._modular_service.modular_client.region_service() - if hasattr(region_service, 'create_light'): - try: - region = region_service.get_region_by_native_name( - region_nn, tenant.cloud) - if region: - _LOG.info(f'Region with native name: {region_nn} already ' - f'exists in table. Adding it to tenant.') - else: - _LOG.info( - f'Region with native name: {region_nn} not found in ' - f'table. Creating it and adding to tenant') - region = region_service.create_light( - maestro_name=region_nn.upper(), - native_name=region_nn, - cloud=tenant.cloud, - ) - region_service.save(region) - region_service.activate_region_in_tenant(tenant, region) - self._modular_service.modular_client.tenant_service().save( - tenant) - except ClientError as e: - _LOG.info(f'Expected client error occurred trying ' - f'to save tenant: {e}') - return build_response( - code=HTTPStatus.FORBIDDEN, - content='You cannot activate tenant region on the current env' - ) - return build_response(code=HTTPStatus.CREATED, - content=self._modular_service.get_dto( - tenant)) - # no create_light - if not self._environment_service.is_docker(): - return build_response( - code=HTTPStatus.BAD_REQUEST, - content='Currently the action is not available' - ) - region = region_service.get_region_by_native_name( - region_nn, tenant.cloud) - if not region: - region = RegionModel( - maestro_name=region_nn.upper(), - native_name=region_nn, - cloud=tenant.cloud, - region_id=str(uuid.uuid4()), - is_active=True, - ) - tenant.regions.append(region) - tenant.save() - return build_response(code=HTTPStatus.CREATED, - content=self._modular_service.get_dto(tenant)) - - -class PatchTenantHandler: - """ - Updates only TenantSettings, not the Tenants table itself - """ - - def __init__(self, modular_service: ModularService): - self._modular_service = modular_service - - def define_action_mapping(self) -> dict: - return { - TENANTS_PATH: { - HTTPMethod.PATCH: self.patch_tenant, - }, - } - - def patch_tenant(self, event: dict) -> dict: - tenant_name = event[TENANT_ATTR] - customer_name = event.get(CUSTOMER_ATTR) - rules_to_exclude_new = event.get(RULES_TO_EXCLUDE_ATTR) - rules_to_include_new = event.get(RULES_TO_INCLUDE_ATTR) - # send_results = event.get(SEND_SCAN_RESULT_ATTR) - - tenant = self._modular_service.get_tenant(tenant_name) - self._modular_service.assert_tenant_valid(tenant, customer_name) - complemented = self._modular_service.get_complemented_tenant(tenant) - - rules_to_exclude = set(complemented.rules_to_exclude or []) - - if rules_to_include_new: - rules_to_exclude -= rules_to_include_new - - if rules_to_exclude_new: - rules_to_exclude |= rules_to_exclude_new - complemented.rules_to_exclude = list(rules_to_exclude) - self._modular_service.save(complemented) - - return build_response( - content=self._modular_service.get_dto(complemented)) - - -class TenantHandler(AbstractComposedHandler): - ... - - -def instantiate_tenant_handler(modular_service: ModularService, - environment_service: EnvironmentService): - patch_handler = PatchTenantHandler(modular_service=modular_service) - return TenantHandler( - resource_map={ - TENANTS_PATH: { - HTTPMethod.GET: GetTenantHandler( - modular_service=modular_service), - HTTPMethod.POST: PostTenantHandler( - modular_service=modular_service, - environment_service=environment_service), - HTTPMethod.PATCH: patch_handler - }, - TENANTS_REGIONS_PATH: { - HTTPMethod.POST: TenantRegionHandler( - modular_service=modular_service, - environment_service=environment_service - ) - } - } - ) diff --git a/src/handlers/user_customer_handler.py b/src/handlers/user_customer_handler.py deleted file mode 100644 index 8386e3732..000000000 --- a/src/handlers/user_customer_handler.py +++ /dev/null @@ -1,72 +0,0 @@ -from handlers.abstracts.abstract_user_handler import AbstractUserHandler -from helpers import build_response -from http import HTTPStatus -from helpers.constants import CUSTOMER_ATTR, HTTPMethod -from helpers.log_helper import get_logger -from services.modular_service import ModularService -from services.user_service import CognitoUserService - -_LOG = get_logger(__name__) - - -class UserCustomerHandler(AbstractUserHandler): - """ - Manage User Customer API - """ - - def __init__(self, modular_service: ModularService, - user_service: CognitoUserService): - self.modular_service = modular_service - self.user_service = user_service - - def define_action_mapping(self): - return { - '/users/customer': { - HTTPMethod.GET: self.get_customer_attribute, - HTTPMethod.POST: self.set_customer_attribute, - HTTPMethod.PATCH: self.update_customer_attribute, - HTTPMethod.DELETE: self.delete_customer_attribute - } - } - - def get_attribute_value(self, username: str): - return self.user_service.get_user_customer(user=username) - - def check_user_exist(self, username: str): - return self.user_service.is_user_exists(username=username) - - def update_attribute(self, username: str, customer: str): - return self.user_service.update_customer(username=username, - customer=customer) - - def validate_value(self, event: dict): - attribute_value = event.get(self.attribute_name) - - if not self.modular_service.get_customer(customer=attribute_value): - _LOG.error(f'Invalid value for attribute {self.attribute_name}: ' - f'{attribute_value}') - return build_response( - code=HTTPStatus.BAD_REQUEST, - content=f'Invalid value for attribute {self.attribute_name}: ' - f'{attribute_value}') - - return True - - def delete_attribute(self, username: str): - return self.user_service.delete_customer(username=username) - - @property - def attribute_name(self): - return CUSTOMER_ATTR - - def get_customer_attribute(self, event): - return self._basic_get_user_attribute_handler(event=event) - - def set_customer_attribute(self, event): - return self._basic_set_user_attribute_handler(event=event) - - def update_customer_attribute(self, event): - return self._basic_update_user_attribute_handler(event=event) - - def delete_customer_attribute(self, event): - return self._basic_delete_user_attribute_handler(event=event) diff --git a/src/handlers/user_role_handler.py b/src/handlers/user_role_handler.py deleted file mode 100644 index 81a8cc457..000000000 --- a/src/handlers/user_role_handler.py +++ /dev/null @@ -1,74 +0,0 @@ -from http import HTTPStatus - -from handlers.abstracts.abstract_user_handler import AbstractUserHandler -from helpers import build_response -from helpers.constants import HTTPMethod, ROLE_ATTR -from helpers.log_helper import get_logger -from services.rbac.iam_cache_service import CachedIamService -from services.user_service import CognitoUserService - -_LOG = get_logger(__name__) - - -class UserRoleHandler(AbstractUserHandler): - """ - Manage User Role API - """ - def __init__(self, user_service: CognitoUserService, - cached_iam_service: CachedIamService): - self.user_service = user_service - self.iam_service = cached_iam_service - - def define_action_mapping(self): - return { - '/users/role': { - HTTPMethod.GET: self.get_role_attribute, - HTTPMethod.POST: self.set_role_attribute, - HTTPMethod.PATCH: self.update_role_attribute, - HTTPMethod.DELETE: self.delete_role_attribute - } - } - - def get_attribute_value(self, username: str): - return self.user_service.get_user_role_name(user=username) - - def check_user_exist(self, username: str): - return self.user_service.is_user_exists(username=username) - - def update_attribute(self, username: str, role: str): - return self.user_service.update_role(username=username, - role=role) - - def validate_value(self, event: dict): - target_user = event.get('target_user') - attribute_value = event.get(self.attribute_name) - customer_display_name = self.user_service.get_user_customer( - target_user) - if not self.iam_service.get_role( - customer=customer_display_name, - name=attribute_value): - _LOG.error(f'Invalid value for attribute {self.attribute_name}: ' - f'{attribute_value}') - return build_response( - code=HTTPStatus.BAD_REQUEST, - content=f'Invalid value for attribute {self.attribute_name}: ' - f'{attribute_value}') - - def delete_attribute(self, username: str): - return self.user_service.delete_role(username=username) - - @property - def attribute_name(self): - return ROLE_ATTR - - def get_role_attribute(self, event): - return self._basic_get_user_attribute_handler(event=event) - - def set_role_attribute(self, event): - return self._basic_set_user_attribute_handler(event=event) - - def update_role_attribute(self, event): - return self._basic_update_user_attribute_handler(event=event) - - def delete_role_attribute(self, event): - return self._basic_delete_user_attribute_handler(event=event) diff --git a/src/handlers/user_tenants_handler.py b/src/handlers/user_tenants_handler.py deleted file mode 100644 index 945054c7b..000000000 --- a/src/handlers/user_tenants_handler.py +++ /dev/null @@ -1,209 +0,0 @@ -from http import HTTPStatus - -from handlers.abstracts.abstract_user_handler import AbstractUserHandler -from helpers import build_response, validate_params -from helpers.constants import HTTPMethod, TENANTS_ATTR -from helpers.log_helper import get_logger -from services.modular_service import ModularService -from services.user_service import CognitoUserService - -_LOG = get_logger(__name__) - - -class UserTenantsHandler(AbstractUserHandler): - """ - Manage User Tenants API - """ - - def __init__(self, modular_service: ModularService, - user_service: CognitoUserService): - self.modular_service = modular_service - self.user_service = user_service - - def define_action_mapping(self): - return { - '/users/tenants': { - HTTPMethod.GET: self.get_tenants_attribute, - HTTPMethod.PATCH: self.update_tenants_attribute, - HTTPMethod.DELETE: self.delete_tenants_attribute - } - } - - def get_attribute_value(self, username: str): - return self.user_service.get_user_tenants(user=username) - - def check_user_exist(self, username: str): - return self.user_service.is_user_exists(username=username) - - def update_attribute(self, username: str, tenants: str): - return self.user_service.update_tenants(username=username, - tenants=tenants) - - def validate_value(self, event: dict): - target_user = event.get('target_user') - if not target_user: - target_user = event.get('user_id') - - user_customer = self.user_service.get_user_customer(target_user) - attribute_value = event.get(self.attribute_name, []) - result_value = attribute_value.copy() - for t in attribute_value: - tenant = self.modular_service.get_tenant(t) - if not tenant: - _LOG.warning(f'Invalid tenant name for attribute ' - f'{self.attribute_name}: {t}') - result_value.remove(t) - elif tenant.attribute_values.get('customer_name') != user_customer: - _LOG.warning(f'No tenant {t} within customer {user_customer}') - result_value.remove(t) - if len(result_value) == 0: - return build_response( - code=HTTPStatus.BAD_REQUEST, - content=f'Invalid value for attribute ' - f'{self.attribute_name}: {attribute_value}') - return result_value - - def delete_attribute(self, username: str): - return self.user_service.delete_tenants(username=username) - - @property - def attribute_name(self): - return TENANTS_ATTR - - def get_tenants_attribute(self, event): - return self._basic_get_user_attribute_handler(event=event) - - def set_tenants_attribute(self, event): - _LOG.debug(f'Create {self.attribute_name} attribute for user: {event}') - validate_params(event=event, required_params_list=['user_id']) - - target_user = event.get('target_user') - if not target_user: - target_user = event.get('user_id') - - if self.get_attribute_value(target_user): - _LOG.error(f'Attribute {self.attribute_name} for user ' - f'{target_user} already exists') - return build_response( - code=HTTPStatus.BAD_REQUEST, - content=f'Attribute {self.attribute_name} for user ' - f'{target_user} already exists') - - attribute_value = event.get(self.attribute_name) - if not attribute_value: - _LOG.debug( - f'Attribute value for the {self.attribute_name} attribute ' - f'is not specified') - return build_response( - code=HTTPStatus.BAD_REQUEST, - content=f'Attribute value for the {self.attribute_name} ' - f'attribute is not specified') - # return True if value is valid - attribute_value = self.validate_value(event=event) - - _LOG.debug(f'Creating {self.attribute_name} attribute') - self.update_attribute(target_user, attribute_value) - - return build_response( - code=HTTPStatus.OK, - content=f'Attribute {self.attribute_name} has been added to user ' - f'{target_user}.') - - def update_tenants_attribute(self, event): - validate_params(event=event, required_params_list=['user_id']) - target_user = event.get('target_user') - if not target_user: - target_user = event.get('user_id') - self._validate_user_existence(username=target_user) - - existing_tenants = self.user_service.get_user_tenants(target_user) - if not existing_tenants: - return self.set_tenants_attribute(event=event) - - existing_tenants = existing_tenants.replace(' ', '').split(',') - existing_tenants.extend(event.get(TENANTS_ATTR, [])) - event[TENANTS_ATTR] = list(set(existing_tenants)) - _LOG.debug(f'Update {self.attribute_name} attribute for user: ' - f'{event}') - # for what???? - # validate_params(event=event, required_params_list=['user_id']) - # - # self._validate_user_existence(username=target_user) - - if not self.get_attribute_value(target_user): - _LOG.error( - f'Attribute {self.attribute_name} for user {target_user} ' - f'does not exist') - return build_response( - code=HTTPStatus.NOT_FOUND, - content=f'Attribute {self.attribute_name} for user ' - f'{target_user} does not exist') - - attribute_value = event.get(self.attribute_name) - if not attribute_value: - _LOG.error( - f'Attribute value for the {self.attribute_name} attribute ' - f'is not specified') - return build_response( - code=HTTPStatus.BAD_REQUEST, - content=f'Attribute value for the {self.attribute_name} ' - f'attribute is not specified') - - attribute_value = self.validate_value(event=event) - - _LOG.debug(f'Updating {self.attribute_name} attribute') - self.update_attribute(target_user, attribute_value) - - return build_response( - code=HTTPStatus.OK, - content={self.attribute_name: attribute_value}) - - def delete_tenants_attribute(self, event): - _LOG.debug(f'Delete {self.attribute_name} attribute for user: {event}') - - validate_params(event=event, required_params_list=['user_id']) - target_user = event.get('target_user') - if not target_user: - target_user = event.get('user_id') - self._validate_user_existence(username=target_user) - - if not event.get('all'): - _LOG.debug(f'Removing some tenants from {self.attribute_name} ' - f'attribute') - existing_tenants = self.user_service.get_user_tenants(target_user) - if not existing_tenants: - return build_response( - code=HTTPStatus.OK, - content=f'Attribute {self.attribute_name} for user ' - f'{target_user} is already empty.') - - tenants_to_delete = event.get(TENANTS_ATTR, []) - existing_tenants = existing_tenants.replace(' ', '').split(',') - for t in tenants_to_delete: - if t in existing_tenants: - existing_tenants.remove(t) - existing_tenants = ','.join(existing_tenants) if existing_tenants \ - else '' - _LOG.debug(f'Removing {self.attribute_name} attribute') - self.delete_attribute(target_user) - self.update_attribute(target_user, existing_tenants) - return build_response( - code=HTTPStatus.OK, - content=f'Attribute {self.attribute_name} for user ' - f'{target_user} has been updated.') - - if not self.get_attribute_value(target_user): - _LOG.debug( - f'Attribute {self.attribute_name} for user {target_user} ' - f'does not exist') - return build_response( - code=HTTPStatus.NOT_FOUND, - content=f'Attribute {self.attribute_name} for user ' - f'{target_user} does not exist') - - _LOG.debug(f'Removing {self.attribute_name} attribute') - self.delete_attribute(target_user) - return build_response( - code=HTTPStatus.OK, - content=f'Attribute {self.attribute_name} for user ' - f'{target_user} has been deleted.') diff --git a/src/helpers/__init__.py b/src/helpers/__init__.py index a4467e657..f6f5e3fe1 100644 --- a/src/helpers/__init__.py +++ b/src/helpers/__init__.py @@ -1,19 +1,38 @@ -import collections.abc -import functools -import json -import uuid +import base64 +import binascii +from contextlib import contextmanager from enum import Enum as _Enum +import json +import functools from functools import reduce -from http import HTTPStatus -from itertools import islice, chain -from typing import Dict, Type, Any, Union, TypeVar, Optional, List, Tuple, \ - Iterable, Generator, Callable, Hashable -from uuid import uuid4 - -from helpers.constants import PARAM_MESSAGE, PARAM_ITEMS, PARAM_TRACE_ID, \ - GOOGLE_CLOUD_ATTR, GCP_CLOUD_ATTR -from helpers.exception import CustodianException +import io +from itertools import chain, islice +import math +import re +import msgspec +import time +from types import NoneType +from typing import ( + Any, + BinaryIO, + Callable, + Generator, + Hashable, + Iterable, + Iterator, + Optional, + TypeVar, + TYPE_CHECKING +) +from typing_extensions import Self +import uuid + +import requests + +from helpers.constants import GCP_CLOUD_ATTR, GOOGLE_CLOUD_ATTR from helpers.log_helper import get_logger +if TYPE_CHECKING: + from services.abs_lambda import ProcessedEvent T = TypeVar('T') @@ -21,112 +40,21 @@ PARAM_USER_ID = 'user_id' -LINE_SEP = '/' -REPO_DYNAMODB_ROOT = 'dynamodb' -REPO_S3_ROOT = 's3' - -REPO_ROLES_FOLDER = 'Roles' -REPO_POLICIES_FOLDER = 'Policies' -REPO_SETTINGS_FOLDER = 'Settings' -REPO_LICENSES_FOLDER = 'Licenses' -REPO_SIEM_FOLDER = 'SIEMManager' - -REPO_SETTINGS_PATH = LINE_SEP.join((REPO_DYNAMODB_ROOT, REPO_SETTINGS_FOLDER)) - -STATUS_READY_TO_SCAN = 'READY_TO_SCAN' -BAD_REQUEST_IMPROPER_TYPES = 'Bad Request. The following parameters ' \ - 'don\'t adhere to the respective types: {0}' BAD_REQUEST_MISSING_PARAMETERS = 'Bad Request. The following parameters ' \ 'are missing: {0}' -def build_response(content: Optional[Union[str, dict, list, Iterable]] = None, - code: int = HTTPStatus.OK.value, - meta: Optional[dict] = None): - context = _import_request_context() - meta = meta or {} - _body = { - PARAM_TRACE_ID: context.aws_request_id, - **meta - } - if isinstance(content, str): - _body.update({PARAM_MESSAGE: content}) - elif isinstance(content, dict) and content: - _body.update({PARAM_ITEMS: [content, ]}) - elif isinstance(content, list): - _body.update({PARAM_ITEMS: content}) - elif isinstance(content, Iterable): - _body.update({PARAM_ITEMS: list(content)}) - else: - _body.update({PARAM_ITEMS: []}) - - if 200 <= code <= 207: - return { - 'statusCode': code, - 'headers': { - 'Content-Type': 'application/json', - 'Access-Control-Allow-Headers': 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token', - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': '*' - }, - 'isBase64Encoded': False, - 'multiValueHeaders': {}, - 'body': json.dumps(_body, sort_keys=True, separators=(',', ':')) - } - raise CustodianException( - code=code, - content=content - ) - - class RequestContext: - def __init__(self, request_id: str = None): - self.aws_request_id: str = request_id or str(uuid.uuid4()) - - -def _import_request_context(): - """Imports request_context global variable from abstract_api_handler_lambda - and abstract_lambda. Only one of them will be initialized, but here we - cannot know which will. So just try""" - from services.abstract_api_handler_lambda import REQUEST_CONTEXT as first - from services.abstract_lambda import REQUEST_CONTEXT as second - if not first and not second: - _LOG.warning('NO REQUEST CONTEXT WAS FOUND.') - return RequestContext('Custom trace_id') - return first if first else second - - -def raise_error_response(code, content): - raise CustodianException(code=code, content=content) - - -def get_invalid_parameter_types( - event: Dict, required_param_types: Dict[str, Type[Any]] -) -> Dict: - _missing = {} - for param, _type in required_param_types.items(): - data = event.get(param, None) - if data is None or not isinstance(data, _type): - _missing[param] = _type - return _missing + __slots__ = ('aws_request_id', 'invoked_function_arn') + def __init__(self, request_id: str | None = None): + self.aws_request_id: str = request_id or str(uuid.uuid4()) + self.invoked_function_arn = None -def retrieve_invalid_parameter_types( - event: Dict, required_param_types: Dict[str, Type[Any]] -) -> Union[str, Type[None]]: - """ - Checks if all required parameters are given in lambda payload, - and follow each respective type. - :param event: the lambda payload - :param required_param_types: list of the lambda required parameters - :return: Union[str, Type[None]] - """ - _missing = get_invalid_parameter_types(event, required_param_types) - if _missing: - _items = _missing.items() - _missing = ', '.join(f'{k}:{v.__name__}' for k, v in _items) - return BAD_REQUEST_IMPROPER_TYPES.format(_missing) if _missing else None + @staticmethod + def get_remaining_time_in_millis(): + return math.inf def get_missing_parameters(event, required_params_list): @@ -137,56 +65,7 @@ def get_missing_parameters(event, required_params_list): return missing_params_list -def validate_params(event, required_params_list): - """ - Checks if all required parameters present in lambda payload. - :param event: the lambda payload - :param required_params_list: list of the lambda required parameters - :return: bad request response if some parameter[s] is/are missing, - otherwise - none - """ - missing_params_list = get_missing_parameters(event, required_params_list) - - if missing_params_list: - raise_error_response( - HTTPStatus.BAD_REQUEST.value, - BAD_REQUEST_MISSING_PARAMETERS.format(missing_params_list) - ) - - -def deep_update(source, overrides): - """ - Update a nested dictionary or similar mapping, with extending - inner list by unique items only. - """ - source = source.copy() - for key, value in overrides.items(): - if isinstance(value, collections.abc.Mapping) and value: - returned = deep_update(source.get(key, {}), value) - source[key] = returned - elif isinstance(value, list) and \ - isinstance(source.get(key), list) and value: - for item in value: - if item not in source[key]: - source[key].append(item) - else: - source[key] = overrides[key] - return source - - -def generate_id(): - return str(uuid4()) - - -def trim_milliseconds_from_iso_string(iso_string): - try: - index = iso_string.index('.') - return iso_string[:index] - except: - return iso_string - - -def deep_get(dct: dict, path: Union[list, tuple]) -> Any: +def deep_get(dct: dict, path: list | tuple) -> Any: """ >>> d = {'a': {'b': 1}} >>> deep_get(d, ('a', 'b')) @@ -196,7 +75,8 @@ def deep_get(dct: dict, path: Union[list, tuple]) -> Any: """ return reduce( lambda d, key: d.get(key, None) if isinstance(d, dict) else None, - path, dct) + path, dct + ) def deep_set(dct: dict, path: tuple, item: Any): @@ -209,13 +89,7 @@ def deep_set(dct: dict, path: tuple, item: Any): deep_set(dct[path[0]], path[1:], item) -def remove_duplicated_dicts(lst: List[Dict]) -> List[Dict]: - return list( - {json.dumps(dct, sort_keys=True): dct for dct in lst}.values() - ) - - -def title_keys(item: Union[dict, list]) -> Union[dict, list]: +def title_keys(item: dict | list) -> dict | list: if isinstance(item, dict): titled = {} for k, v in item.items(): @@ -223,14 +97,11 @@ def title_keys(item: Union[dict, list]) -> Union[dict, list]: title_keys(v) return titled elif isinstance(item, list): - titled = [] - for i in item: - titled.append(title_keys(i)) - return titled + return [title_keys(i) for i in item] return item -def setdefault(obj: object, name: str, value: T) -> Union[T, Any]: +def setdefault(obj: object, name: str, value: T) -> T | Any: """ Works like dict.setdefault """ @@ -239,22 +110,7 @@ def setdefault(obj: object, name: str, value: T) -> Union[T, Any]: return getattr(obj, name) -def list_update(target_list: List[T], source_list: List[T], - update_by: Tuple[str, ...]) -> List[T]: - """ - Updates objects in target_list from source_list by specified attributes. - """ - _make_dict = lambda _list, _attrs: { - tuple(getattr(obj, attr, None) for attr in _attrs): obj - for obj in _list - } - target = _make_dict(target_list, update_by) - source = _make_dict(source_list, update_by) - target.update(source) - return list(target.values()) - - -def batches(iterable: Iterable, n: int) -> Generator[List, None, None]: +def batches(iterable: Iterable, n: int) -> Generator[list, None, None]: """ Batch data into lists of length n. The last batch may be shorter. """ @@ -267,35 +123,29 @@ def batches(iterable: Iterable, n: int) -> Generator[List, None, None]: batch = list(islice(it, n)) -def filter_dict(d: dict, keys: Union[set, tuple, list]) -> dict: +def filter_dict(d: dict, keys: set | list | tuple) -> dict: if keys: return {k: v for k, v in d.items() if k in keys} return d class HashableDict(dict): - def __hash__(self): + def __hash__(self) -> int: return hash(frozenset(self.items())) -def hashable(item: Union[dict, list, str, float, int, type(None)] - ) -> Hashable: - """Makes hashable from the given item - >>> d = {'q': [1,3,5, {'h': 34, 'c': ['1', '2']}], 'v': {1: [1,2,3]}} - >>> d1 = {'v': {1: [1,2,3]}, 'q': [1,3,5, {'h': 34, 'c': ['1', '2']}]} - >>> hash(hashable(d)) == hash(hashable(d1)) - True - """ +def hashable(item: dict | list | tuple | set | str | float | int | None): + """ + Makes hashable from the given item + >>> d = {'q': [1,3,5, {'h': 34, 'c': ['1', '2']}], 'v': {1: [1,2,3]}} + >>> d1 = {'v': {1: [1,2,3]}, 'q': [1,3,5, {'h': 34, 'c': ['1', '2']}]} + >>> hash(hashable(d)) == hash(hashable(d1)) + True + """ if isinstance(item, dict): - h_dict = HashableDict() - for k, v in item.items(): - h_dict[k] = hashable(v) - return h_dict - elif isinstance(item, list): - h_list = [] - for i in item: - h_list.append(hashable(i)) - return tuple(h_list) + return HashableDict(zip(item.keys(), map(hashable, item.values()))) + elif isinstance(item, (tuple, list, set)): + return tuple(map(hashable, item)) else: # str, int, bool, None (all hashable) return item @@ -314,20 +164,6 @@ def __iter__(self): self.value = yield from self._generator -def keep_value(func: Callable): - """ - Decorator that allows to keep generator's value in `value` attribute - :param func: function thar returns generator obj - :return: function that returns the wrapped generator - """ - - @functools.wraps(func) - def wrapper(*args, **kwargs): - return KeepValueGenerator(func(*args, **kwargs)) - - return wrapper - - class SingletonMeta(type): _instances = {} @@ -354,7 +190,7 @@ def has(cls, value: str) -> bool: return value in iter(cls) @classmethod - def build(cls, name: str, items: Iterable) -> Type['Enum']: + def build(cls, name: str, items: Iterable) -> type['Enum']: """ Values can contain spaces and even "+" or "-" :param name: @@ -393,8 +229,8 @@ def start(*args, **kwargs): return start -def nested_items(item: Union[dict, list, str, float, int, type(None)] - ) -> Generator[Tuple[str, Any], None, bool]: +def nested_items(item: dict | list | str | float | int | None + ) -> Generator[tuple[str, Any], None, bool]: """ Recursively iterates over nested key-values >>> d = {'key': 'value', 'key2': [{1: 2, 3: 4, 'k': 'v'}, {5: 6}, 1, 2, 3]} @@ -403,7 +239,7 @@ def nested_items(item: Union[dict, list, str, float, int, type(None)] :param item: :return: """ - if isinstance(item, (str, float, int, type(None))): + if isinstance(item, (str, float, int, NoneType)): return False # means not iterated, because it's a leaf # list or dict -> can be iterated over if isinstance(item, dict): @@ -417,9 +253,418 @@ def nested_items(item: Union[dict, list, str, float, int, type(None)] return True -def peek(iterable) -> Optional[Tuple[Any, chain]]: +def peek(iterable) -> Optional[tuple[Any, chain]]: try: first = next(iterable) except StopIteration: return return first, chain([first], iterable) + + +def urljoin(*args: str) -> str: + """ + Joins all the parts with one "/" + :param args: + :return: + """ + return '/'.join(map(lambda x: str(x).strip('/'), args)) + + +def skip_indexes(iterable: Iterable[T], skip: set[int] + ) -> Generator[T, None, None]: + """ + Iterates over the collection skipping specific indexes + :param iterable: + :param skip: + :return: + """ + it = iter(iterable) + for i, item in enumerate(it): + if i in skip: + continue + yield item + + +def get_last_element(string: str, delimiter: str) -> str: + return string.split(delimiter)[-1] + + +def catchdefault(method: Callable, default: Any = None): + """ + Returns method's result. In case it fails -> returns default + :param method: + :param default: + :return: + """ + try: + return method() + except Exception: # noqa + return default + + +class NotHereDescriptor: + def __get__(self, obj, type=None): + raise AttributeError + + +JSON_PATH_LIST_INDEXES = re.compile(r'\w*\[(-?\d+)\]') + + +def json_path_get(d: dict | list, path: str) -> Any: + """ + Simple json paths with only basic operations supported + >>> json_path_get({'a': 'b', 'c': [1,2,3, [{'b': 'c'}]]}, 'c[-1][0].b') + 'c' + >>> json_path_get([-1, {'one': 'two'}], 'c[-1][0].b') is None + True + >>> json_path_get([-1, {'one': 'two'}], '[-1].one') + 'two' + """ + if path.startswith('$'): + path = path[1:] + if path.startswith('.'): + path = path[1:] + parts = path.split('.') + + item = d + for part in parts: + try: + _key = part.split('[')[0] + _indexes = re.findall(JSON_PATH_LIST_INDEXES, part) + if _key: + item = item.get(_key) + for i in _indexes: + item = item[int(i)] + except (IndexError, TypeError, AttributeError): + item = None + break + return item + + +FT = TypeVar('FT', bound=BinaryIO) # file type + + +def download_url(url: str, out: FT | None = None) -> FT | io.BytesIO | None: + """ + Downloads the content by the url handling compression and other encoding + in case those are specified in headers + :param url: + :param out: temp file opened in binary mode + :return: + """ + if not out: + out = io.BytesIO() + try: + with requests.get(url, stream=True) as resp: + for chunk in resp.raw.stream(decode_content=True): + out.write(chunk) + out.seek(0) + return out + except Exception: + _LOG.exception(f'Could not download file from url: {url}') + return + + +def sifted(request: dict) -> dict: + return { + k: v for k, v in request.items() + if isinstance(v, (bool, int)) or v + } + + +HT = TypeVar('HT', bound=Hashable) + + +def without_duplicates(iterable: Iterable[HT]) -> Generator[HT, None, None]: + """ + Iterates over the collection skipping already yielded items + :param iterable: + :return: + """ + it = iter(iterable) + buffer = set() + for i in it: + if i in buffer: + continue + buffer.add(i) + yield i + + +CT = TypeVar('CT') + + +class MultipleCursorsWithOneLimitIterator(Iterable[CT]): + def __init__(self, limit: int | None, + *factories: Callable[[int | None], Iterator[CT]]): + """ + This class will consider the number of yielded items and won't query + more. See tests for this thing to understand better + :param limit: common limit for all the cursors + :param factories: accepts any number of functions with one argument + `limit` assuming that if limit is None, - there is no limit. Each + function must build a cursor (supposedly a DB cursor) with that limit. + """ + self._limit = limit + if not factories: + raise ValueError('Invalid usage: at least one factory should be') + self._factories = factories + + def __iter__(self) -> Iterator[CT]: + self._current_limit = self._limit + self._chain = iter(self._factories) + factory = next(self._chain) + self._it = factory(self._current_limit) + return self + + def __next__(self) -> CT: + if self._current_limit == 0: + raise StopIteration + while True: + try: + item = next(self._it) + if isinstance(self._current_limit, int): + self._current_limit -= 1 + return item + except StopIteration: + # current cursor came to its end. Making the next one + factory = next(self._chain) + self._it = factory(self._current_limit) + + +# todo test +def to_api_gateway_event(processed_event: 'ProcessedEvent') -> dict: + """ + Converts our ProcessedEvent back to api gateway event. It does not + contain all the fields only some that are necessary for high level reports + endpoints + :param processed_event: + :return: + """ + assert processed_event['resource'], \ + 'Only event with existing resource supported' + + # values can be just strings in case we get this event from a database + resource = processed_event['resource'] + if isinstance(resource, Enum): + resource = resource.value + method = processed_event['method'] + if isinstance(method, Enum): + method = method.value + return { + 'resource': resource, + 'path': resource, + 'httpMethod': method, + 'headers': { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'Accept-Encoding': 'gzip,deflate', + }, + 'multiValueHeaders': { + 'Accept': ['application/json'], + 'Accept-Encoding': ['gzip,deflate'], + 'Content-Type': ['application/json'], + }, + 'queryStringParameters': processed_event['query'], + 'pathParameters': processed_event['path_params'], + 'requestContext': { + 'path': processed_event['fullpath'], + 'resourcePath': resource, + 'httpMethod': method, + 'requestTimeEpoch': time.time() * 1e3, + 'protocol': 'HTTP/1.1', + 'authorizer': { + 'claims': { + 'sub': processed_event['cognito_user_id'], + 'custom:customer': processed_event['cognito_customer'], + 'cognito:username': processed_event['cognito_username'], + 'custom:role': processed_event['cognito_user_role'] + } + } + }, + 'body': json.dumps(processed_event['body'], separators=(',', ':')), + 'isBase64Encoded': False + } + + +JT = TypeVar('JT') # json type +IT = TypeVar('IT') # item type + + +def _default_hook(x): + return isinstance(x, (str, int, bool, NoneType)) + + +def iter_values(finding: JT, + hook: Callable[[IT], bool] = _default_hook + ) -> Generator[IT, Any, JT]: + """ + Yields values from the given finding with an ability to send back + the desired values. I proudly think this is cool, because we can put + values replacement login outside of this generator + >>> gen = iter_values({'1':'q', '2': ['w', 'e'], '3': {'4': 'r'}}) + >>> next(gen) + q + >>> gen.send('instead of q') + w + >>> gen.send('instead of w') + e + >>> gen.send('instead of e') + r + >>> gen.send('instead of r') + After the last command StopIteration will be raised, and it + will contain the changed finding. + Changes the given finding in-place for performance purposes so be careful + :param finding: + :param hook: + :return: + """ + if hook(finding): + new = yield finding + return new + elif _default_hook(finding): # anyway we need this default one + return finding + if isinstance(finding, dict): + for k, v in finding.items(): + finding[k] = yield from iter_values(v, hook) + return finding + if isinstance(finding, list): + for i, v in enumerate(finding): + finding[i] = yield from iter_values(v, hook) + return finding + + +def dereference_json(obj: dict) -> None: + """ + Changes the given dict in place de-referencing all $ref. Does not support + files and http references. If you need them, better use jsonref + lib. Works only for dict as root object. + Note that it does not create new objects but only replaces {'$ref': ''} + with objects that ref(s) are referring to, so: + - works really fast, 20x faster than jsonref, at least relying on my + benchmarks; + - changes your existing object; + - can reference the same object multiple times so changing some arbitrary + values afterward can change object in multiple places. + Though, it's perfectly fine in case you need to dereference obj, dump it + to file and forget + :param obj: + :return: + """ + def _inner(o): + if isinstance(o, (str, int, float, bool, NoneType)): + return + # dict or list + if isinstance(o, dict): + for k, v in o.items(): + if isinstance(v, dict) and isinstance(v.get('$ref'), str): + _path = v['$ref'].strip('#/').split('/') + o[k] = deep_get(obj, _path) + else: + _inner(v) + else: # isinstance(o, list) + for i, v in enumerate(o): + if isinstance(v, dict) and isinstance(v.get('$ref'), str): + _path = v['$ref'].strip('#/').split('/') + o[i] = deep_get(obj, _path) + else: + _inner(v) + _inner(obj) + + +@contextmanager +def measure_time(): + holder = [time.perf_counter_ns(), None] + try: + yield holder + finally: + holder[1] = time.perf_counter_ns() + + +class NextToken: + __slots__ = ('_lak',) + + def __init__(self, lak: dict | int | str | None = None): + """ + Wrapper over dynamodb last_evaluated_key and pymongo offset + :param lak: + """ + self._lak = lak + + def __json__(self) -> str | None: + """ + Handled only inside commons.lambda_response + :return: + """ + return self.serialize() + + def serialize(self) -> str | None: + if not self: + return + return base64.urlsafe_b64encode(msgspec.json.encode(self._lak)).decode() + + @property + def value(self) -> dict | int | str | None: + return self._lak + + @classmethod + def deserialize(cls, s: str | None = None) -> Self: + if not s or not isinstance(s, str): + return cls() + decoded = None + try: + decoded = msgspec.json.decode(base64.urlsafe_b64decode(s)) + except (binascii.Error, msgspec.DecodeError): + pass + except Exception: # noqa + pass + return cls(decoded) + + def __bool__(self) -> bool: + return not not self._lak # 0 and empty dict are None + + +class TermColor: + HEADER = '\033[95m' + OKBLUE = '\033[94m' + OKCYAN = '\033[96m' + OKGREEN = '\033[92m' + WARNING = '\033[93m' + DEBUG = '\033[90m' + FAIL = '\033[91m' + ENDC = '\033[0m' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' + + @classmethod + def blue(cls, st: str) -> str: + return f'{cls.OKBLUE}{st}{cls.ENDC}' + + @classmethod + def cyan(cls, st: str) -> str: + return f'{cls.OKCYAN}{st}{cls.ENDC}' + + @classmethod + def green(cls, st: str) -> str: + return f'{cls.OKGREEN}{st}{cls.ENDC}' + + @classmethod + def yellow(cls, st: str) -> str: + return f'{cls.WARNING}{st}{cls.ENDC}' + + @classmethod + def red(cls, st: str) -> str: + return f'{cls.FAIL}{st}{cls.ENDC}' + + @classmethod + def gray(cls, st: str) -> str: + return f'{cls.DEBUG}{st}{cls.DEBUG}' + + +def flip_dict(d: dict): + """ + In place + :param d: + :return: + """ + for k in tuple(d.keys()): + d[d.pop(k)] = k diff --git a/src/helpers/__version__.py b/src/helpers/__version__.py index 3ea0d2f6e..a5e451313 100644 --- a/src/helpers/__version__.py +++ b/src/helpers/__version__.py @@ -1 +1 @@ -__version__ = '4.15.0' +__version__ = '5.1.0' diff --git a/src/helpers/constants.py b/src/helpers/constants.py index 6a9ec363f..bc10c217d 100644 --- a/src/helpers/constants.py +++ b/src/helpers/constants.py @@ -1,109 +1,257 @@ +import operator from enum import Enum +from itertools import filterfalse +from typing import Iterator + +from typing_extensions import Self + + +# from http import HTTPMethod # python3.11+ + +class HTTPMethod(str, Enum): + HEAD = 'HEAD' + GET = 'GET' + POST = 'POST' + PATCH = 'PATCH' + DELETE = 'DELETE' + PUT = 'PUT' + + +class CustodianEndpoint(str, Enum): + """ + Should correspond to Api gateway models + """ + DOC = '/doc' + JOBS = '/jobs' + ROLES = '/roles' + RULES = '/rules' + USERS = '/users' + EVENT = '/event' + SIGNIN = '/signin' + SIGNUP = '/signup' + HEALTH = '/health' + REFRESH = '/refresh' + TENANTS = '/tenants' + RULESETS = '/rulesets' + LICENSES = '/licenses' + POLICIES = '/policies' + JOBS_K8S = '/jobs/k8s' + CUSTOMERS = '/customers' + HEALTH_ID = '/health/{id}' + JOBS_JOB = '/jobs/{job_id}' + DOC_PROXY = '/doc/{proxy+}' + ROLES_NAME = '/roles/{name}' + CREDENTIALS = '/credentials' + META_META = '/rule-meta/meta' + RULE_SOURCES = '/rule-sources' + USERS_WHOAMI = '/users/whoami' + SCHEDULED_JOB = '/scheduled-job' + PLATFORMS_K8S = '/platforms/k8s' + SETTINGS_MAIL = '/settings/mail' + JOBS_STANDARD = '/jobs/standard' + BATCH_RESULTS = '/batch-results' + REPORTS_RETRY = '/reports/retry' + POLICIES_NAME = '/policies/{name}' + METRICS_STATUS = '/metrics/status' + REPORTS_CLEVEL = '/reports/clevel' + METRICS_UPDATE = '/metrics/update' + REPORTS_STATUS = '/reports/status' + REPORTS_PROJECT = '/reports/project' + USERS_USERNAME = '/users/{username}' + CREDENTIALS_ID = '/credentials/{id}' + META_MAPPINGS = '/rule-meta/mappings' + RULESETS_CONTENT = '/rulesets/content' + ED_RULESETS = '/rulesets/event-driven' + DOC_SWAGGER_JSON = '/doc/swagger.json' + META_STANDARDS = '/rule-meta/standards' + RULE_META_UPDATER = '/rules/update-meta' + REPORTS_PUSH_DOJO = '/reports/push/dojo' + CUSTOMERS_RABBITMQ = '/customers/rabbitmq' + REPORTS_DIAGNOSTIC = '/reports/diagnostic' + REPORTS_DEPARTMENT = '/reports/department' + INTEGRATIONS_SELF = '/integrations/temp/sre' + SCHEDULED_JOB_NAME = '/scheduled-job/{name}' + REPORTS_OPERATIONAL = '/reports/operational' + TENANTS_TENANT_NAME = '/tenants/{tenant_name}' + USERS_RESET_PASSWORD = '/users/reset-password' + REPORTS_EVENT_DRIVEN = '/reports/event_driven' + LICENSES_LICENSE_KEY = '/licenses/{license_key}' + SETTINGS_SEND_REPORTS = '/settings/send_reports' + PLATFORMS_K8S_ID = '/platforms/k8s/{platform_id}' + CREDENTIALS_ID_BINDING = '/credentials/{id}/binding' + CUSTOMERS_EXCLUDED_RULES = '/customers/excluded-rules' + INTEGRATIONS_DEFECT_DOJO = '/integrations/defect-dojo' + REPORTS_PUSH_DOJO_JOB_ID = '/reports/push/dojo/{job_id}' + REPORTS_RULES_JOBS_JOB_ID = '/reports/rules/jobs/{job_id}' + BATCH_RESULTS_JOB_ID = '/batch-results/{batch_results_id}' + LICENSES_LICENSE_KEY_SYNC = '/licenses/{license_key}/sync' + REPORTS_ERRORS_JOBS_JOB_ID = '/reports/errors/jobs/{job_id}' + INTEGRATIONS_DEFECT_DOJO_ID = '/integrations/defect-dojo/{id}' + REPORTS_DIGESTS_JOBS_JOB_ID = '/reports/digests/jobs/{job_id}' + REPORTS_DETAILS_JOBS_JOB_ID = '/reports/details/jobs/{job_id}' + TENANTS_TENANT_NAME_REGIONS = '/tenants/{tenant_name}/regions' + REPORTS_FINDINGS_JOBS_JOB_ID = '/reports/findings/jobs/{job_id}' + REPORTS_RESOURCES_JOBS_JOB_ID = '/reports/resources/jobs/{job_id}' + REPORTS_COMPLIANCE_JOBS_JOB_ID = '/reports/compliance/jobs/{job_id}' + SETTINGS_LICENSE_MANAGER_CLIENT = '/settings/license-manager/client' + SETTINGS_LICENSE_MANAGER_CONFIG = '/settings/license-manager/config' + LICENSE_LICENSE_KEY_ACTIVATION = '/licenses/{license_key}/activation' + REPORTS_RULES_TENANTS_TENANT_NAME = '/reports/rules/tenants/{tenant_name}' + TENANTS_TENANT_NAME_EXCLUDED_RULES = '/tenants/{tenant_name}/excluded-rules' + TENANTS_TENANT_NAME_ACTIVE_LICENSES = '/tenants/{tenant_name}/active-licenses' + REPORTS_COMPLIANCE_TENANTS_TENANT_NAME = '/reports/compliance/tenants/{tenant_name}' + INTEGRATIONS_DEFECT_DOJO_ID_ACTIVATION = '/integrations/defect-dojo/{id}/activation' + REPORTS_DETAILS_TENANTS_TENANT_NAME_JOBS = '/reports/details/tenants/{tenant_name}/jobs' + REPORTS_DIGESTS_TENANTS_TENANT_NAME_JOBS = '/reports/digests/tenants/{tenant_name}/jobs' + REPORTS_FINDINGS_TENANTS_TENANT_NAME_JOBS = '/reports/findings/tenants/{tenant_name}/jobs' + REPORTS_RESOURCES_TENANTS_TENANT_NAME_JOBS = '/reports/resources/tenants/{tenant_name}/jobs' + REPORTS_RAW_TENANTS_TENANT_NAME_STATE_LATEST = '/reports/raw/tenants/{tenant_name}/state/latest' + REPORTS_RESOURCES_TENANTS_TENANT_NAME_LATEST = '/reports/resources/tenants/{tenant_name}/state/latest' + REPORTS_RESOURCES_PLATFORMS_K8S_PLATFORM_ID_LATEST = '/reports/resources/platforms/k8s/{platform_id}/state/latest' + + @classmethod + def match(cls, resource: str) -> Self | None: + """ + Tries to resolve endpoint from our enum from Api Gateway resource. + Enum contains endpoints without stage. Though in general trailing + slashes matter and endpoints with and without such slash are + considered different we ignore this and consider such paths equal: + - /path/to/resource + - /path/to/resource/ + This method does the following: + >>> CustodianEndpoint.match('/jobs/{job_id}') == CustodianEndpoint.JOBS_JOB + >>> CustodianEndpoint.match('jobs/{job_id}') == CustodianEndpoint.JOBS_JOB + >>> CustodianEndpoint.match('jobs/{job_id}/') == CustodianEndpoint.JOBS_JOB + :param resource: + :return: + """ + raw = resource.strip('/') # without trailing slashes + for case in (raw, f'/{raw}', f'{raw}/', f'/{raw}/'): + try: + return cls(case) + except ValueError: + pass + return + + +LAMBDA_URL_HEADER_CONTENT_TYPE_UPPER = 'Content-Type' +JSON_CONTENT_TYPE = 'application/json' -try: - from http import HTTPMethod # python3.11+ -except ImportError: - class HTTPMethod(str, Enum): - HEAD = 'HEAD' - GET = 'GET' - POST = 'POST' - PATCH = 'PATCH' - DELETE = 'DELETE' - PUT = 'PUT' DEFAULT_SYSTEM_CUSTOMER: str = 'SYSTEM' - -TESTING_MODE_ENV = 'CUSTODIAN_TESTING' -TESTING_MODE_ENV_TRUE = 'true' +DEFAULT_RULES_METADATA_REPO_ACCESS_SSM_NAME = \ + 'custodian.rules-metadata-repo-access' ACTION_PARAM = 'action' -ACTION_PARAM_ERROR = 'There is no handler for the endpoint {endpoint}' - -CUSTOMER_ACTION = 'customer' STANDARD = 'standard' -MITRE = 'mitre' -SERVICE_SECTION = 'service_section' - -HTTP_METHOD_ERROR = 'The server does not support the HTTP method {method} ' \ - 'for the resource {resource}' # Modular:Parent related attributes and types CUSTODIAN_TYPE = 'CUSTODIAN' # application that contains access to CUSTODIAN SCHEDULED_JOB_TYPE = 'SCHEDULED_JOB' -PARENT_ID_ATTR = 'parent_id' -APPLICATION_ID_ATTR = 'application_id' META_ATTR = 'meta' TENANT_ENTITY_TYPE = 'TENANT' VALUE_ATTR = 'value' -SCHEDULE_ATTR = 'schedule' -SCOPE_ATTR = 'scope' +TYPES_ATTR = 'types' ID_ATTR = 'id' CUSTOMER_ATTR = 'customer' -USER_CUSTOMER_ATTR = 'user_customer' TENANT_ATTR = 'tenant' TENANTS_ATTR = 'tenants' TENANT_NAMES_ATTR = 'tenant_names' ACCOUNT_ID_ATTR = 'account_id' -ACCOUNT_ATTR = 'account' -CUSTOMER_DISPLAY_NAME_ATTR = 'customer_display_name' TENANT_DISPLAY_NAME_ATTR = 'tenant_display_name' TENANT_DISPLAY_NAMES_ATTR = 'tenant_display_names' TENANT_NAME_ATTR = 'tenant_name' -CUSTOMER_NAME_ATTR = "customer_name" -RULE_ID_ATTR = 'rule_id' -DISPLAY_NAME_ATTR = 'display_name' LATEST_LOGIN_ATTR = 'latest_login' PRIMARY_CONTACTS_ATTR = 'primary_contacts' SECONDARY_CONTACTS_ATTR = 'secondary_contacts' TENANT_MANAGER_CONTACTS_ATTR = 'tenant_manager_contacts' DEFAULT_OWNER_ATTR = 'default_owner' -ACTIVATION_DATE_ATTR = 'activation_date' -INHERIT_ATTR = 'inherit' CLOUD_ATTR = 'cloud' -CLOUDS_ATTR = 'clouds' -ACCESS_APPLICATION_ID_ATTR = 'access_application_id' CLOUD_IDENTIFIER_ATTR = 'cloud_identifier' -RULES_TO_EXCLUDE_ATTR = 'rules_to_exclude' -RULES_TO_INCLUDE_ATTR = 'rules_to_include' REGION_ATTR = 'region' JOB_ID_ATTR = 'job_id' -LIMIT_ATTR = 'limit' -NEXT_TOKEN_ATTR = 'next_token' -RULE_VERSION_ATTR = 'rule_version' AWS_CLOUD_ATTR = 'AWS' AZURE_CLOUD_ATTR = 'AZURE' # the same, but first is obsolete, second is the one from Maestro's tenants GCP_CLOUD_ATTR, GOOGLE_CLOUD_ATTR = 'GCP', 'GOOGLE' KUBERNETES_CLOUD_ATTR = 'KUBERNETES' # from rules metadata -AZURE_ULTIMATE_REGION = 'AzureCloud' -GOOGLE_ULTIMATE_REGION = 'us-central1' -PERMISSIONS_ATTR = 'permissions' -EXP_ATTR = 'exp' +class Cloud(str, Enum): + """ + More like provider. "Cloud" is just a name that happen to be used + """ + AWS = 'AWS' + AZURE = 'AZURE' + GOOGLE = 'GOOGLE' + GCP = 'GOOGLE' # alias + KUBERNETES = 'KUBERNETES' + + +# The values of this enum represent what Custom core can scan, i.e. what +# type of rules and ruleset(s) we can have. These are not tenant clouds +class RuleDomain(str, Enum): + AWS = AWS_CLOUD_ATTR + AZURE = AZURE_CLOUD_ATTR + GCP = GCP_CLOUD_ATTR + KUBERNETES = KUBERNETES_CLOUD_ATTR + + @classmethod + def from_tenant_cloud(cls, cloud: str) -> Self | None: + match cloud: + case 'AWS': + return cls.AWS + case 'AZURE': + return cls.AZURE + case 'GOOGLE': + return cls.GCP + + +class JobType(str, Enum): + MANUAL = 'manual' + REACTIVE = 'reactive' + + +class ReportFormat(str, Enum): + JSON = 'json' + XLSX = 'xlsx' + + +class ReportDispatchStatus(str, Enum): + FAILED = 'FAILED' + SUCCEEDED = 'SUCCEEDED' + DUPLICATE = 'DUPLICATE' + RETRIED = 'RETRIED' + PENDING = 'PENDING' + + +class PolicyErrorType(str, Enum): + """ + For statistics + """ + SKIPPED = 'SKIPPED' + ACCESS = 'ACCESS' # not enough permissions + CREDENTIALS = 'CREDENTIALS' # invalid credentials + CLIENT = 'CLIENT' # some other client error + INTERNAL = 'INTERNAL' # unexpected error + + EXPIRATION_ATTR = 'expiration' -POLICIES_ATTR = 'policies' NAME_ATTR = 'name' -DESCRIPTION_ATTR = 'description' IMPACT_ATTR = 'impact' -MIN_CORE_VERSION = 'min_core_version' -SEVERITY_ATTR = 'severity' +RESOURCE_TYPE_ATTR = 'resource_type' VERSION_ATTR = 'version' FILTERS_ATTR = 'filters' LOCATION_ATTR = 'location' COMMENT_ATTR = 'comment' -UPDATED_DATE_ATTR = 'updated_date' LATEST_SYNC_ATTR = 'latest_sync' COMMIT_HASH_ATTR = 'commit_hash' COMMIT_TIME_ATTR = 'commit_time' -SOURCE_ATTR = 'source' RULES_ATTR = 'rules' -GET_RULES_ATTR = 'get_rules' RULESETS_ATTR = 'rulesets' -RULES_TO_SCAN_ATTR = 'rules_to_scan' RULE_SOURCE_ID_ATTR = 'rule_source_id' S3_PATH_ATTR = 's3_path' RULES_NUMBER = 'rules_number' @@ -120,128 +268,345 @@ class HTTPMethod(str, Enum): STATUS_SYNCED = 'SYNCED' STATUS_SYNCING_FAILED = 'SYNCING_FAILED' -ROLE_ATTR = 'role' -ACTIVE_ATTR = 'active' EVENT_DRIVEN_ATTR = 'event_driven' -ACTIVE_REGION_STATE = 'ACTIVE' -INACTIVE_REGION_STATE = 'INACTIVE' -POLICIES_TO_ATTACH = 'policies_to_attach' -POLICIES_TO_DETACH = 'policies_to_detach' -PERMISSIONS_TO_ATTACH = 'permissions_to_attach' -PERMISSIONS_TO_DETACH = 'permissions_to_detach' - -RULES_TO_ATTACH = 'rules_to_attach' -RULES_TO_DETACH = 'rules_to_detach' DATA_ATTR = 'data' -CONTENT_ATTR = 'content' ENABLED = 'enabled' TRUSTED_ROLE_ARN = 'trusted_role_arn' TYPE_ATTR = 'type' -ENTITIES_MAPPING_ATTR = 'entities_mapping' -CLEAR_EXISTING_MAPPING_ATTR = 'clear_existing_mapping' -PRODUCT_TYPE_NAME_ATTR = 'product_type_name' -PRODUCT_NAME_ATTR = 'product_name' -ENGAGEMENT_NAME_ATTR = 'engagement_name' -TEST_TITLE_ATTR = 'test_title' -ENTITIES_MAPPING_POSSIBLE_PARAMS = {PRODUCT_TYPE_NAME_ATTR, PRODUCT_NAME_ATTR, - ENGAGEMENT_NAME_ATTR, TEST_TITLE_ATTR} - -ALLOWED_FOR_ATTR = 'allowed_for' RESTRICT_FROM_ATTR = 'restrict_from' -TENANT_ALLOWANCE = 'tenant_allowance' -TENANT_RESTRICTION = 'tenant_restriction' LICENSED_ATTR = 'licensed' LICENSE_KEY_ATTR = 'license_key' LICENSE_KEYS_ATTR = 'license_keys' -LICENSE_KEYS_TO_PREPEND_ATTR = 'license_keys_to_prepend' -LICENSE_KEYS_TO_APPEND_ATTR = 'license_keys_to_append' -LICENSE_KEYS_TO_DETACH_ATTR = 'license_keys_to_detach' TENANT_LICENSE_KEY_ATTR = 'tenant_license_key' TENANT_LICENSE_KEYS_ATTR = 'tenant_license_keys' -ATTACHMENT_MODEL_ATTR = 'attachment_model' CUSTOMERS_ATTR = 'customers' -MAESTRO_USER_ATTR = 'maestro_user' -RABBIT_EXCHANGE_ATTR = 'rabbit_exchange' -REQUEST_QUEUE_ATTR = 'request_queue' -RESPONSE_QUEUE_ATTR = 'response_queue' -SDK_ACCESS_KEY_ATTR = 'sdk_access_key' -CONNECTION_URL_ATTR = 'connection_url' -SDK_SECRET_KEY_ATTR = 'sdk_secret_key' - -API_KEY_ATTR = 'api_key' -URL_ATTR = 'url' -AUTO_RESOLVE_ACCESS_ATTR = 'auto_resolve_access' -RESULTS_STORAGE_ATTR = 'results_storage' # License Manager[Setting].Config: -PORT_ATTR = 'port' HOST_ATTR = 'host' # License Manager[Setting].Client: -KEY_ID_ATTR = 'key_id' -ALGORITHM_ATTR = 'algorithm' -PRIVATE_KEY_ATTR = 'private_key' -PUBLIC_KEY_ATTR = 'public_key' -FORMAT_ATTR = 'format' -B64ENCODED_ATTR = 'b64_encoded' KID_ATTR = 'kid' ALG_ATTR = 'alg' -TYP_ATTR = 'typ' - -START_ATTR = 'start' -END_ATTR = 'end' - -CLIENT_TOKEN_ATTR = 'client-token' -GET_URL_ATTR = 'get_url' - -CHECK_PERMISSION_ATTR = 'check_permission' +TOKEN_ATTR = 'token' PARAM_USER_ID = 'user_id' -PARAM_REQUEST_PATH = 'path' -PARAM_RESOURCE_PATH = 'resourcePath' +PARAM_USER_SUB = 'user_sub' PARAM_HTTP_METHOD = 'httpMethod' PARAM_CUSTOMER = 'customer' PARAM_USER_ROLE = 'user_role' PARAM_USER_CUSTOMER = 'user_customer' -PARAM_ITEMS = 'items' -PARAM_MESSAGE = 'message' -PARAM_TRACE_ID = 'trace_id' AUTHORIZATION_PARAM = 'authorization' -PARAM_COMPLETE = 'complete' -IDENTIFIER_ATTR = 'identifier' +# on-prem +DOCKER_SERVICE_MODE, SAAS_SERVICE_MODE = 'docker', 'saas' -RULE_SOURCE_REQUIRED_ATTRS = {GIT_PROJECT_ID_ATTR, GIT_URL_ATTR, GIT_REF_ATTR, - GIT_RULES_PREFIX_ATTR, GIT_ACCESS_TYPE_ATTR, - GIT_ACCESS_SECRET_ATTR} +ENV_TRUE = {'1', 'true', 'yes', 'y'} -ENV_VAR_REGION = 'AWS_REGION' +# RabbitMQ request +EXTERNAL_DATA_ATTR = 'externalData' +EXTERNAL_DATA_KEY_ATTR = 'externalDataKey' +EXTERNAL_DATA_BUCKET_ATTR = 'externalDataBucket' + +LOG_FORMAT = '%(asctime)s - %(levelname)s - %(name)s - %(message)s' -# on-prem -ENV_SERVICE_MODE = 'SERVICE_MODE' -DOCKER_SERVICE_MODE, SAAS_SERVICE_MODE = 'docker', 'saas' -ENV_MONGODB_USER = 'MONGO_USER' -ENV_MONGODB_PASSWORD = 'MONGO_PASSWORD' -ENV_MONGODB_URL = 'MONGO_URL' # host:port -ENV_MONGODB_DATABASE = 'MONGO_DATABASE' # custodian_as_a_service +class CAASEnv: + """ + Envs that can be set for lambdas of custodian service + """ + # modes + SERVICE_MODE = 'CAAS_SERVICE_MODE' + TESTING_MODE = 'CAAS_TESTING' + MOCKED_RABBIT_MQ_S3 = 'CAAS_MOCK_RABBIT_MQ_S3' + SYSTEM_CUSTOMER_NAME = 'SYSTEM_CUSTOMER_NAME' + LOG_LEVEL = 'CAAS_LOG_LEVEL' -ENV_MINIO_HOST = 'MINIO_HOST' -ENV_MINIO_PORT = 'MINIO_PORT' -ENV_MINIO_ACCESS_KEY = 'MINIO_ACCESS_KEY' -ENV_MINIO_SECRET_ACCESS_KEY = 'MINIO_SECRET_ACCESS_KEY' + # inner envs (they are set automatically when request comes) + API_GATEWAY_HOST = '_CAAS_API_GATEWAY_HOST' + API_GATEWAY_STAGE = '_CAAS_API_GATEWAY_STAGE' + INVOCATION_REQUEST_ID = '_INVOCATION_REQUEST_ID' -ENV_VAULT_TOKEN = 'VAULT_TOKEN' -ENV_VAULT_HOST = 'VAULT_URL' -ENV_VAULT_PORT = 'VAULT_SERVICE_SERVICE_PORT' # env from Kubernetes + # buckets + RULESETS_BUCKET_NAME = 'CAAS_RULESETS_BUCKET_NAME' + REPORTS_BUCKET_NAME = 'CAAS_REPORTS_BUCKET_NAME' + METRICS_BUCKET_NAME = 'CAAS_METRICS_BUCKET_NAME' + STATISTICS_BUCKET_NAME = 'CAAS_STATISTICS_BUCKET_NAME' + RECOMMENDATIONS_BUCKET_NAME = 'CAAS_RECOMMENDATIONS_BUCKET_NAME' + + # Cognito either one will work, but ID faster and safer + USER_POOL_NAME = 'CAAS_USER_POOL_NAME' + USER_POOL_ID = 'CAAS_USER_POOL_ID' + + # rbac + ALLOW_DISABLED_PERMISSIONS_FOR_STANDARD_USERS = 'CAAS_ALLOW_DISABLED_PERMISSIONS_FOR_STANDARD_USERS' # noqa, can be useful for QA + + # lm + LM_TOKEN_LIFETIME_MINUTES = 'CAAS_LM_TOKEN_LIFETIME_MINUTES' + + # some deployment options + ACCOUNT_ID = 'CAAS_ACCOUNT_ID' + LAMBDA_ALIAS_NAME = 'CAAS_LAMBDA_ALIAS_NAME' + + # batch options + BATCH_JOB_DEF_NAME = 'CAAS_BATCH_JOB_DEF_NAME' + BATCH_JOB_QUEUE_NAME = 'CAAS_BATCH_JOB_QUEUE_NAME' + BATCH_JOB_LOG_LEVEL = 'CAAS_BATCH_JOB_LOG_LEVEL' + BATCH_JOB_LIFETIME_MINUTES = 'CAAS_BATCH_JOB_LIFETIME_MINUTES' + EB_SERVICE_ROLE_TO_INVOKE_BATCH = 'CAAS_EB_SERVICE_ROLE_TO_INVOKE_BATCH' + + # events + EVENTS_TTL_HOURS = 'CAAS_EVENTS_TTL_HOURS' + NATIVE_EVENTS_PER_ITEM = 'CAAS_NATIVE_EVENTS_PER_ITEM' + EVENT_ASSEMBLER_PULL_EVENTS_PAGE_SIZE = 'CAAS_EVENT_ASSEMBLER_PULL_EVENTS_PAGE_SIZE' # noqa + NUMBER_OF_PARTITIONS_FOR_EVENTS = 'CAAS_NUMBER_OF_PARTITIONS_FOR_EVENTS' + + # jobs + JOBS_TIME_TO_LIVE_DAYS = 'CAAS_JOBS_TIME_TO_LIVE_DAYS' + + # some logic setting + SKIP_CLOUD_IDENTIFIER_VALIDATION = 'CAAS_SKIP_CLOUD_IDENTIFIER_VALIDATION' + ALLOW_SIMULTANEOUS_JOBS_FOR_ONE_TENANT = 'CAAS_ALLOW_SIMULTANEOUS_JOBS_FOR_ONE_TENANT' # noqa + + # cache + INNER_CACHE_TTL_SECONDS = 'CAAS_INNER_CACHE_TTL_SECONDS' + + # on-prem access + MINIO_ENDPOINT = 'CAAS_MINIO_ENDPOINT' + MINIO_ACCESS_KEY_ID = 'CAAS_MINIO_ACCESS_KEY_ID' + MINIO_SECRET_ACCESS_KEY = 'CAAS_MINIO_SECRET_ACCESS_KEY' + + VAULT_ENDPOINT = 'CAAS_VAULT_ENDPOINT' + VAULT_TOKEN = 'CAAS_VAULT_TOKEN' + + MONGO_URI = 'CAAS_MONGO_URI' + MONGO_DATABASE = 'CAAS_MONGO_DATABASE' + + AWS_REGION = 'AWS_REGION' + + # init envs + SYSTEM_USER_PASSWORD = 'CAAS_SYSTEM_USER_PASSWORD' + + +class BatchJobEnv(CAASEnv): + """ + Batch executor specific envs. Note that batch can contain some envs from + lambdas, but these that are listed here -> only for batch + """ + JOB_ID = 'AWS_BATCH_JOB_ID' + CUSTODIAN_JOB_ID = 'CUSTODIAN_JOB_ID' + BATCH_RESULTS_IDS = 'BATCH_RESULTS_IDS' + + TARGET_RULESETS = 'TARGET_RULESETS' + TARGET_REGIONS = 'TARGET_REGIONS' + LICENSED_RULESETS = 'LICENSED_RULESETS' + AFFECTED_LICENSES = 'AFFECTED_LICENSES' + + EXECUTOR_MODE = 'EXECUTOR_MODE' + JOB_TYPE = 'JOB_TYPE' + SUBMITTED_AT = 'SUBMITTED_AT' + + AWS_DEFAULT_REGION = 'AWS_DEFAULT_REGION' + CREDENTIALS_KEY = 'CREDENTIALS_KEY' + + SCHEDULED_JOB_NAME = 'SCHEDULED_JOB_NAME' + TENANT_NAME = 'TENANT_NAME' + PLATFORM_ID = 'PLATFORM_ID' + ALLOW_MANAGEMENT_CREDS = 'ALLOW_MANAGEMENT_CREDENTIALS' + + +class JobComponentName(CAASEnv): + RECOMMENDATIONS = 'custodian-service-recommendations' + METRICS = 'custodian-service-metrics' + + +class Permission(str, Enum): + is_disabled: bool + depends_on_tenant: bool + + def __new__(cls, value: str, is_disabled: bool = False, + depends_on_tenant: bool = False): + """ + Hidden permissions are those that currently cannot be used by standard + users even if the user has one. Those endpoints are available only for + system user (because permissions are not checked if system user makes + a request) + :param value: + :param is_disabled: is_disabled == allowed only for system + :param depends_on_tenant: whether this permission can be allowed for + one tenant and forbidden for another within one customer + """ + obj = str.__new__(cls, value) + obj._value_ = value + + obj.is_disabled = is_disabled + obj.depends_on_tenant = depends_on_tenant + return obj + + def __str__(self) -> str: + return self.value + + # todo implement tenant restrictions where the True is commented + REPORT_PUSH_TO_DOJO = 'report:push_report_to_dojo', False, True + REPORT_PUSH_TO_DOJO_BATCH = 'report:push_to_dojo_batch', False, # True + REPORT_OPERATIONAL = 'report:post_operational', False, True + REPORT_PROJECT = 'report:post_project', False, # True + REPORT_DEPARTMENT = 'report:post_department', False, # True + REPORT_CLEVEL = 'report:post_clevel' + REPORT_DIAGNOSTIC = 'report:get_diagnostic' + REPORT_STATUS = 'report:get_status' + + REPORT_DIGEST_DESCRIBE = 'report:get_digest', False, True + REPORT_DIGEST_DESCRIBE_BATCH = 'report:get_digest_batch', False, True + REPORT_DETAILS_DESCRIBE = 'report:get_details', False, True + REPORT_DETAILS_DESCRIBE_BATCH = 'report:get_details_batch', False, True + REPORT_FINDINGS_DESCRIBE = 'report:get_findings', False, True + REPORT_FINDINGS_DESCRIBE_BATCH = 'report:get_findings_batch', False, True + REPORT_COMPLIANCE_DESCRIBE_JOB = 'report:get_job_compliance', False, True + REPORT_COMPLIANCE_DESCRIBE_TENANT = 'report:get_tenant_compliance', False, True + REPORT_ERRORS_DESCRIBE = 'report:get_job_errors', False, True + REPORT_RULES_DESCRIBE_JOB = 'report:get_job_rules', False, True + REPORT_RULES_DESCRIBE_TENANT = 'report:get_tenant_rules', False, True + REPORT_RESOURCES_GET_TENANT_LATEST = 'report:get_tenant_latest_resources', False, True + REPORT_RESOURCES_GET_K8S_PLATFORM_LATEST = 'report:get_k8s_platform_latest_resources', False, True + REPORT_RESOURCES_GET_JOBS = 'report:get_job_resources', False, True + REPORT_RESOURCES_GET_JOBS_BATCH = 'report:get_job_resources_batch', False, True + REPORT_RAW_GET_TENANT_LATEST = 'report:get_tenant_latest_raw_report', False, True + + JOB_POST_STANDARD = 'job:post_for_tenant_standard', False, True + JOB_QUERY = 'job:query', False, # True + JOB_GET = 'job:get', False, True + JOB_POST_LICENSED = 'job:post_for_tenant', False, True + JOB_POST_K8S = 'job:post_for_k8s_platform', False, True + JOB_TERMINATE = 'job:terminate', False, True + + CUSTOMER_DESCRIBE = 'customer:describe' + CUSTOMER_SET_EXCLUDED_RULES = 'customer:set_excluded_rules' + CUSTOMER_GET_EXCLUDED_RULES = 'customer:get_excluded_rules' + + TENANT_QUERY = 'tenant:query', False, # True + TENANT_GET = 'tenant:get', False, True + TENANT_GET_ACTIVE_LICENSES = 'tenant:get_active_licenses', False, True + TENANT_SET_EXCLUDED_RULES = 'tenant:set_excluded_rules', False, True + TENANT_GET_EXCLUDED_RULES = 'tenant:get_excluded_rules', False, True + + POLICY_DESCRIBE = 'iam:describe_policy' + POLICY_CREATE = 'iam:create_policy' + POLICY_UPDATE = 'iam:update_policy' + POLICY_DELETE = 'iam:remove_policy' + + ROLE_DESCRIBE = 'iam:describe_role' + ROLE_CREATE = 'iam:create_role' + ROLE_UPDATE = 'iam:update_role' + ROLE_DELETE = 'iam:remove_role' + + RULE_DESCRIBE = 'rule:describe' + RULE_DELETE = 'rule:delete' + RULE_UPDATE_META = 'system:update_meta' + + METRICS_UPDATE = 'system:update_metrics', True + METRICS_STATUS = 'system:metrics_status' + + META_UPDATE_STANDARDS = 'meta:update_standards', True + META_UPDATE_MAPPINGS = 'meta:update_mappings', True + META_UPDATE_META = 'meta:update_meta', True + + RULESET_DESCRIBE = 'ruleset:describe', False, # True + RULESET_CREATE = 'ruleset:create' + RULESET_UPDATE = 'ruleset:update' + RULESET_DELETE = 'ruleset:delete' + RULESET_GET_CONTENT = 'ruleset:get_content' + RULESET_DESCRIBE_ED = 'ruleset:describe_event_driven', True + RULESET_CREATE_ED = 'ruleset:create_event_driven', True + RULESET_DELETE_ED = 'ruleset:delete_event_driven', True + + RULE_SOURCE_DESCRIBE = 'rule_source:describe' + RULE_SOURCE_CREATE = 'rule_source:create' + RULE_SOURCE_UPDATE = 'rule_source:update' + RULE_SOURCE_DELETE = 'rule_source:delete' + + EVENT_POST = 'event:post' + + LICENSE_ADD = 'license:add_license' + LICENSE_QUERY = 'license:query', False, # True + LICENSE_GET = 'license:get', False, # True + LICENSE_DELETE = 'license:delete_license', False, # True + LICENSE_SYNC = 'license:sync', True + LICENSE_ACTIVATE = 'license:activate' + LICENSE_GET_ACTIVATION = 'license:get_activation', False, # True + LICENSE_DELETE_ACTIVATION = 'license:delete_activation', False, + LICENSE_UPDATE_ACTIVATION = 'license:update_activation', False, + + SCHEDULED_JOB_GET = 'scheduled-job:get', False, # True + SCHEDULED_JOB_QUERY = 'scheduled-job:query', False, # True + SCHEDULED_JOB_CREATE = 'scheduled-job:register', False, # True + SCHEDULED_JOB_DELETE = 'scheduled-job:deregister', False, # True + SCHEDULED_JOB_UPDATE = 'scheduled-job:update', False, # True + + SETTINGS_DESCRIBE_MAIL = 'settings:describe_mail', True + SETTINGS_CREATE_MAIL = 'settings:create_mail', True + SETTINGS_DELETE_MAIL = 'settings:delete_mail', True + SETTINGS_CHANGE_SET_REPORTS = 'settings:change_send_reports', True # TODO make PUT + SETTINGS_DESCRIBE_LM_CONFIG = 'settings:describe_lm_config' + SETTINGS_CREATE_LM_CONFIG = 'settings:create_lm_config', True + SETTINGS_DELETE_LM_CONFIG = 'settings:delete_lm_config', True + SETTINGS_DESCRIBE_LM_CLIENT = 'settings:describe_lm_client' + SETTINGS_CREATE_LM_CLIENT = 'settings:create_lm_client', True + SETTINGS_DELETE_LM_CLIENT = 'settings:delete_lm_client', True + + RABBITMQ_DESCRIBE = 'rabbitmq:describe' + RABBITMQ_CREATE = 'rabbitmq:create' + RABBITMQ_DELETE = 'rabbitmq:delete' + + BATCH_RESULTS_GET = 'batch_results:get', False, True + BATCH_RESULTS_QUERY = 'batch_results:query', False, # True + + PLATFORM_GET_K8S = 'platform:get_k8s', False, True + PLATFORM_QUERY_K8S = 'platform:query_k8', False, # True + PLATFORM_CREATE_K8S = 'platform:create_k8s', False, True + PLATFORM_DELETE_K8S = 'platform:delete_k8s', False, True + + SRE_INTEGRATION_CREATE = 'self_integration:create' + SRE_INTEGRATION_UPDATE = 'self_integration:update' + SRE_INTEGRATION_DESCRIBE = 'self_integration:describe' + SRE_INTEGRATION_DELETE = 'self_integration:delete' + + DOJO_INTEGRATION_CREATE = 'dojo_integration:create' + DOJO_INTEGRATION_DESCRIBE = 'dojo_integration:describe' + DOJO_INTEGRATION_DELETE = 'dojo_integration:delete' + DOJO_INTEGRATION_ACTIVATE = 'dojo_integration:activate' + DOJO_INTEGRATION_GET_ACTIVATION = 'dojo_integration:get_activation' + DOJO_INTEGRATION_DELETE_ACTIVATION = 'dojo_integration:delete_activation' + + CREDENTIALS_DESCRIBE = 'credentials:describe' + CREDENTIALS_BIND = 'credentials:bind' + CREDENTIALS_UNBIND = 'credentials:unbind' + CREDENTIALS_GET_BINDING = 'credentials:get_binding' + + USERS_DESCRIBE = 'users:describe' + USERS_CREATE = 'users:create' + USERS_UPDATE = 'users:update' + USERS_DELETE = 'users:delete' + USERS_GET_CALLER = 'users:get_caller' + USERS_RESET_PASSWORD = 'users:reset_password' + + @classmethod + def iter_enabled(cls) -> Iterator[Self]: + """ + Iterates over all the currently available permission + :return: + """ + return filterfalse(operator.attrgetter('is_disabled'), cls) + + @classmethod + def iter_disabled(cls) -> Iterator[Self]: + return filter(operator.attrgetter('is_disabled'), cls) -ENV_MAX_NUMBER_OF_JOBS_ON_PREM = 'MAX_NUMBER_OF_JOBS' # Modular # Tenant @@ -258,153 +623,71 @@ class HTTPMethod(str, Enum): MODULAR_SECRET = 'secret' MODULAR_TYPE = 'type' -# Batch -BATCH_ENV_TENANT_NAME = 'TENANT_NAME' -BATCH_ENV_PLATFORM_ID = 'PLATFORM_ID' -BATCH_ENV_DEFAULT_REPORTS_BUCKET_NAME = 'DEFAULT_REPORTS_BUCKET_NAME' -BATCH_ENV_AWS_REGION = 'AWS_REGION' -BATCH_ENV_CREDENTIALS_KEY = 'CREDENTIALS_KEY' -BATCH_ENV_STATS_S3_BUCKET_NAME = 'STATS_S3_BUCKET_NAME' -BATCH_ENV_VAR_RULESETS_BUCKET_NAME = 'RULESETS_BUCKET_NAME' -BATCH_ENV_JOB_LIFETIME_MIN = 'JOB_LIFETIME_MIN' -BATCH_ENV_MIN_CUSTOM_CORE_VERSION = 'MIN_CUSTOM_CORE_VERSION' -BATCH_ENV_CURRENT_CUSTOM_CORE_VERSION = 'CURRENT_CUSTOM_CORE_VERSION' -BATCH_ENV_EVENT_DRIVEN = 'EVENT_DRIVEN' -BATCH_ENV_LOG_LEVEL = 'LOG_LEVEL' -BATCH_ENV_LM_ACCESS_DATA_HOST = 'LM_ACCESS_DATA_HOST' -BATCH_ENV_SUBMITTED_AT = 'SUBMITTED_AT' -BATCH_ENV_TARGET_REGIONS = 'TARGET_REGIONS' -BATCH_ENV_TARGET_RULESETS = 'TARGET_RULESETS' -BATCH_ENV_TARGET_RULESETS_VIEW = 'TARGET_RULESETS_VIEW' -BATCH_ENV_AFFECTED_LICENSES = 'AFFECTED_LICENSES' -BATCH_ENV_LICENSED_RULESETS = 'LICENSED_RULESETS' -BATCH_ENV_JOB_ID = 'AWS_BATCH_JOB_ID' -BATCH_ENV_SCHEDULED_JOB_NAME = 'SCHEDULED_JOB_NAME' -BATCH_ENV_LM_CLIENT_KEY = 'LM_CLIENT_KEY' -BATCH_ENV_JOB_TYPE = 'JOB_TYPE' -BATCH_ENV_BATCH_RESULTS_ID = 'BATCH_RESULTS_ID' -BATCH_ENV_BATCH_RESULTS_IDS = 'BATCH_RESULTS_IDS' -BATCH_ENV_SYSTEM_CUSTOMER_NAME = 'SYSTEM_CUSTOMER_NAME' - -BATCH_STANDARD_JOB_TYPE = 'standard' -BATCH_EVENT_DRIVEN_JOB_TYPE = 'event-driven' -BATCH_MULTI_ACCOUNT_EVENT_DRIVEN_JOB_TYPE = 'event-driven-multi-account' -BATCH_SCHEDULED_JOB_TYPE = 'scheduled' - -# CaaSJobs ttl -ENV_VAR_JOBS_TIME_TO_LIVE_DAYS = 'JOBS_TIME_TO_LIVE_DAYS' - -# CaaSEvent ttl -ENV_VAR_EVENTS_TTL = 'EVENTS_TTL_HOURS' - -ENV_VAR_NUMBER_OF_PARTITIONS_FOR_EVENTS = 'NUMBER_OF_PARTITIONS_FOR_EVENTS' + +class BatchJobType(str, Enum): + """ + Our inner types + """ + STANDARD = 'standard' + EVENT_DRIVEN = 'event-driven-multi-account' + SCHEDULED = 'scheduled' + + DEFAULT_NUMBER_OF_PARTITIONS_FOR_EVENTS = 10 -ENV_NUMBER_OF_EVENTS_IN_EVENT_ITEM = 'number_of_native_events_in_event_item' DEFAULT_NUMBER_OF_EVENTS_IN_EVENT_ITEM: int = 100 DEFAULT_EVENTS_TTL_HOURS = 48 DEFAULT_INNER_CACHE_TTL_SECONDS: int = 300 -ENV_API_GATEWAY_HOST = 'API_GATEWAY_HOST' -ENV_API_GATEWAY_STAGE = 'API_GATEWAY_STAGE' - -ENV_ALLOW_SIMULTANEOUS_JOBS_FOR_ONE_TENANT = \ - 'ALLOW_SIMULTANEOUS_JOBS_FOR_ONE_TENANT' -ENV_INNER_CACHE_TTL_SECONDS = 'INNER_CACHE_TTL_SECONDS' - -# Batch envs -PARAM_TARGET_RULESETS = 'target_rulesets' -PARAM_RULESET_LICENSE_PRIORITY = 'ruleset_license_priority' -PARAM_RULESET_OVERLAP = 'ruleset_overlap' -PARAM_TARGET_REGIONS = 'target_regions' -PARAM_CREDENTIALS = 'credentials' -PARAM_LICENSED_RULESETS = 'licensed_rulesets' -PARAM_AFFECTED_LICENSES = 'affected_licenses' +DEFAULT_LM_TOKEN_LIFETIME_MINUTES = 120 # event-driven AWS_VENDOR = 'AWS' MAESTRO_VENDOR = 'MAESTRO' # smtp -USERNAME_ATTR = 'username' PASSWORD_ATTR = 'password' -DEFAULT_SENDER_ATTR = 'default_sender' -USE_TLS_ATTR = 'use_tls' -MAX_EMAILS_ATTR = 'max_emails' - -# Report related attributes -START_ISO_ATTR = 'start_iso' -END_ISO_ATTR = 'end_iso' -HREF_ATTR = 'href' -JSON_ATTR = 'json' -XLSX_ATTR = 'xlsx' -RULE_ATTR = 'rule' - -# event statistics -ENV_EVENT_STATISTICS_TYPE = 'event_statistics_type' -EVENT_STATISTICS_TYPE_VERBOSE = 'verbose' -EVENT_STATISTICS_TYPE_SHORT = 'short' - -# cred envs -AZURE_TENANT_ID = 'AZURE_TENANT_ID' -AZURE_SUBSCRIPTION_ID = 'AZURE_SUBSCRIPTION_ID' -AZURE_CLIENT_ID = 'AZURE_CLIENT_ID' -AZURE_CLIENT_SECRET = 'AZURE_CLIENT_SECRET' - -AWS_ACCESS_KEY_ID = 'AWS_ACCESS_KEY_ID' -AWS_SECRET_ACCESS_KEY = 'AWS_SECRET_ACCESS_KEY' -AWS_DEFAULT_REGION = 'AWS_DEFAULT_REGION' -AWS_SESSION_TOKEN = 'AWS_SESSION_TOKEN' DEFAULT_REPORTS_BUCKET_NAME = 'reports' DEFAULT_RULESETS_BUCKET_NAME = 'rulesets' -DEFAULT_SSM_BACKUP_BUCKET_NAME = 'ssm-backup' DEFAULT_STATISTICS_BUCKET_NAME = 'statistics' -DEFAULT_TEMPLATES_BUCKET_NAME = 'templates' DEFAULT_METRICS_BUCKET_NAME = 'metrics' DEFAULT_RECOMMENDATION_BUCKET_NAME = 'recommendation' -PROTOCOL_ATTR = 'protocol' -STAGE_ATTR = 'stage' -API_VERSION_ATTR = 'api_version' -HTTP_ATTR, HTTPS_ATTR = 'HTTP', 'HTTPS' - # reports DATA_TYPE = 'data_type' -FINDINGS_ATTR = 'findings' TOTAL_SCANS_ATTR = 'total_scans' FAILED_SCANS_ATTR = 'failed_scans' SUCCEEDED_SCANS_ATTR = 'succeeded_scans' -REGIONS_TO_EXCLUDE_ATTR = 'regions_to_exclude' -ACTIVE_ONLY_ATTR = 'active_only' -REPORT_TYPE = 'report_type' COMPLIANCE_TYPE = 'compliance' RULE_TYPE = 'rule' OVERVIEW_TYPE = 'overview' RESOURCES_TYPE = 'resources' ATTACK_VECTOR_TYPE = 'attack_vector' FINOPS_TYPE = 'finops' -OPERATIONAL_REPORT_TYPE = 'operational' -PROJECT_REPORT_TYPE = 'project' -DEPARTMENT_REPORT_TYPE = 'department' -C_LEVEL_REPORT_TYPE = 'c-level' +KUBERNETES_TYPE = 'kubernetes' LAST_SCAN_DATE = 'last_scan_date' RESOURCE_TYPES_DATA_ATTR = 'resource_types_data' -CURRENT_ATTR = 'current' SEVERITY_DATA_ATTR = 'severity_data' ACTIVATED_REGIONS_ATTR = 'activated_regions' AVERAGE_DATA_ATTR = 'average_data' END_DATE = 'end_date' OUTDATED_TENANTS = 'outdated_tenants' +ARCHIVE_PREFIX = 'archive' -# job status -JOB_SUCCEEDED_STATUS = 'SUCCEEDED' -JOB_FAILED_STATUS = 'FAILED' -JOB_STARTED_STATUS = 'STARTED' -JOB_RUNNABLE_STATUS = 'RUNNABLE' -JOB_RUNNING_STATUS = 'RUNNING' -STATUS_CODE_ATTRS = ('code', 'statusCode', 'StatusCode') +class JobState(str, Enum): + """ + https://docs.aws.amazon.com/batch/latest/userguide/job_states.html + """ + SUBMITTED = 'SUBMITTED' + PENDING = 'PENDING' + RUNNABLE = 'RUNNABLE' + STARTING = 'STARTING' + RUNNING = 'RUNNING' + FAILED = 'FAILED' + SUCCEEDED = 'SUCCEEDED' + # Maestro Credentials Applications types AZURE_CREDENTIALS_APP_TYPE = 'AZURE_CREDENTIALS' @@ -414,22 +697,7 @@ class HTTPMethod(str, Enum): GCP_COMPUTE_ACCOUNT_APP_TYPE = 'GCP_COMPUTE_ACCOUNT' GCP_SERVICE_ACCOUNT_APP_TYPE = 'GCP_SERVICE_ACCOUNT' -CLOUD_TO_APP_TYPE = { - AWS_CLOUD_ATTR: { - AWS_CREDENTIALS_APP_TYPE, - AWS_ROLE_APP_TYPE - }, - AZURE_CLOUD_ATTR: { - AZURE_CREDENTIALS_APP_TYPE, - AZURE_CERTIFICATE_APP_TYPE - }, - GOOGLE_CLOUD_ATTR: { - GCP_COMPUTE_ACCOUNT_APP_TYPE, - GCP_SERVICE_ACCOUNT_APP_TYPE - } -} - -MULTIREGION = 'multiregion' +GLOBAL_REGION = 'global' class HealthCheckStatus(str, Enum): @@ -438,13 +706,9 @@ class HealthCheckStatus(str, Enum): NOT_OK = 'NOT_OK' -SPECIFIC_TENANT_SCOPE = 'SPECIFIC_TENANT' -ALL_SCOPE = 'ALL' - -DEFAULT_CACHE_LIFETIME = 600 # 10 minutes - # cognito COGNITO_USERNAME = 'cognito:username' +COGNITO_SUB = 'sub' CUSTOM_ROLE_ATTR = 'custom:role' CUSTOM_CUSTOMER_ATTR = 'custom:customer' CUSTOM_LATEST_LOGIN_ATTR = 'custom:latest_login' @@ -467,9 +731,8 @@ class HealthCheckStatus(str, Enum): 'Impact': 'TA0040' } -MANUAL_TYPE_ATTR = 'manual' -REACTIVE_TYPE_ATTR = 'reactive' -COMPONENT_NAME_ATTR = 'component_name' +RETRY_REPORT_STATE_MACHINE = 'retry_send_reports' +SEND_REPORTS_STATE_MACHINE = 'send_reports' START_DATE = 'start_date' ARTICLE_ATTR = 'article' @@ -487,25 +750,84 @@ class RuleSourceType(str, Enum): GITLAB = 'GITLAB' -# the next settings are updated automatically when rules meta is pulled. -# They can be set both to s3 and CaaSSettings table. Currently, we set -# them to S3 -KEY_RULES_TO_SERVICE_SECTION = 'RULES_TO_SERVICE_SECTION' -KEY_RULES_TO_SEVERITY = 'RULES_TO_SEVERITY' -KEY_RULES_TO_STANDARDS = 'RULES_TO_STANDARDS' -KEY_RULES_TO_MITRE = 'RULES_TO_MITRE' -KEY_CLOUD_TO_RULES = 'CLOUD_TO_RULES' -KEY_HUMAN_DATA = 'HUMAN_DATA' -KEY_RULES_TO_SERVICE = 'RULES_TO_SERVICE' -KEY_RULES_TO_CATEGORY = 'RULES_TO_CATEGORY' -KEY_AWS_STANDARDS_COVERAGE = 'AWS_STANDARDS_COVERAGE' -KEY_AZURE_STANDARDS_COVERAGE = 'AZURE_STANDARDS_COVERAGE' -KEY_GOOGLE_STANDARDS_COVERAGE = 'GOOGLE_STANDARDS_COVERAGE' -KEY_AWS_EVENTS = 'AWS_EVENTS' -KEY_AZURE_EVENTS = 'AZURE_EVENTS' -KEY_GOOGLE_EVENTS = 'GOOGLE_EVENTS' +class S3SettingKey(str, Enum): + RULES_TO_SEVERITY = 'RULES_TO_SEVERITY' + RULES_TO_SERVICE_SECTION = 'RULES_TO_SERVICE_SECTION' + RULES_TO_STANDARDS = 'RULES_TO_STANDARDS' + RULES_TO_SERVICE = 'RULES_TO_SERVICE' + RULES_TO_MITRE = 'RULES_TO_MITRE' + RULES_TO_CATEGORY = 'RULES_TO_CATEGORY' + HUMAN_DATA = 'HUMAN_DATA' + CLOUD_TO_RULES = 'CLOUD_TO_RULES' + + AWS_STANDARDS_COVERAGE = 'AWS_STANDARDS_COVERAGE' + AZURE_STANDARDS_COVERAGE = 'AZURE_STANDARDS_COVERAGE' + GOOGLE_STANDARDS_COVERAGE = 'GOOGLE_STANDARDS_COVERAGE' + + AWS_EVENTS = 'AWS_EVENTS' + AZURE_EVENTS = 'AZURE_EVENTS' + GOOGLE_EVENTS = 'GOOGLE_EVENTS' + + EVENT_BRIDGE_EVENT_SOURCE_TO_RULES_MAPPING = \ + 'EVENT_BRIDGE_EVENT_SOURCE_TO_RULES_MAPPING' + MAESTRO_SUBGROUP_ACTION_TO_AZURE_EVENTS_MAPPING = \ + 'MAESTRO_SUBGROUP_ACTION_TO_AZURE_EVENTS_MAPPING' + MAESTRO_SUBGROUP_ACTION_TO_GOOGLE_EVENTS_MAPPING = \ + 'MAESTRO_SUBGROUP_ACTION_TO_GOOGLE_EVENTS_MAPPING' + + +class SettingKey(str, Enum): + MAIL_CONFIGURATION = 'MAIL_CONFIGURATION' + LM_CLIENT_KEY = 'LM_CLIENT_KEY' + ACCESS_DATA_LM = 'ACCESS_DATA_LM' + TEMPLATE_BUCKET = 'TEMPLATES_S3_BUCKET_NAME' + SYSTEM_CUSTOMER = 'SYSTEM_CUSTOMER_NAME' + EVENT_ASSEMBLER = 'EVENT_ASSEMBLER' + REPORT_DATE_MARKER = 'REPORT_DATE_MARKER' + RULES_METADATA_REPO_ACCESS_SSM_NAME = 'RULES_METADATA_REPO_ACCESS_SSM_NAME' + + AWS_STANDARDS_COVERAGE = 'AWS_STANDARDS_COVERAGE' + AZURE_STANDARDS_COVERAGE = 'AZURE_STANDARDS_COVERAGE' + GOOGLE_STANDARDS_COVERAGE = 'GOOGLE_STANDARDS_COVERAGE' + + SEND_REPORTS = 'SEND_REPORTS' + MAX_ATTEMPT = 'MAX_ATTEMPT' + MAX_CRON_NUMBER = 'MAX_CRON_NUMBER' + MAX_RABBITMQ_REQUEST_SIZE = 'MAX_RABBITMQ_REQUEST_SIZE' class PlatformType(str, Enum): - EKS = 'EKS' - NATIVE = 'NATIVE' + SELF_MANAGED = 'SELF_MANAGED' # any + EKS = 'EKS' # aws + AKS = 'AKS' # azure + GKS = 'GKS' # google + + +class Severity(str, Enum): + """ + Low to High + """ + INFO = 'Info' + LOW = 'Low' + MEDIUM = 'Medium' + HIGH = 'High' + + @classmethod + def iter(cls): + return map(operator.attrgetter('value'), cls) + + +REPORT_FIELDS = { + 'id', + 'name', + 'arn', # aws specific + 'namespace' # k8s specific +} # from Cloud Custodian + + +PRIVATE_KEY_SECRET_NAME = 'rule-engine-private-key' + + +# tenant setting keys +TS_EXCLUDED_RULES_KEY = 'CUSTODIAN_EXCLUDED_RULES' +TS_JOB_LOCK_KEY = 'CUSTODIAN_JOB_LOCK' diff --git a/src/helpers/docker.py b/src/helpers/docker.py deleted file mode 100644 index 828eae79e..000000000 --- a/src/helpers/docker.py +++ /dev/null @@ -1,114 +0,0 @@ -import functools -import json -import time - -import six -from boto3.dynamodb.types import TypeSerializer -from dynamodb_json.json_util import json_serial - -from helpers.constants import CUSTOMER_ATTR, \ - NAME_ATTR, VERSION_ATTR -from helpers.log_helper import get_logger -from models.ruleset import Ruleset -from services import SERVICE_PROVIDER -from helpers.system_customer import SYSTEM_CUSTOMER - - -_LOG = get_logger(__name__) - - -def compile_rulesets(method): - """Be careful, the decorator must be used only with those methods where - their class implements some methods that are used inside here. - For instance: `self._check_and_convert_version`""" - - @functools.wraps(method) - def wrapper(self, *args, **kwargs): - environment_service = SERVICE_PROVIDER.environment_service() - if not environment_service.is_docker(): - return method(self, *args, **kwargs) - - event = kwargs.get('event', {}) - customer = event.get(CUSTOMER_ATTR) or SYSTEM_CUSTOMER - name = event.get(NAME_ATTR) - version = event.get(VERSION_ATTR) - - if not (customer and name and version): - _LOG.warning(f'Expected composed hash_key: ' - f'\'customer\' & \'name\' & \'version\' is missing. ' - f'Skipping compiling on docker') - return method(self, *args, **kwargs) - - old_entity = self.ruleset_service.get_ruleset( - customer=customer, ruleset_name=name, version=version - ) - result = method(self, *args, **kwargs) - new_entity = self.ruleset_service.get_ruleset( - customer=customer, ruleset_name=name, version=version - ) - _LOG.debug('Preparing event for ruleset-compiler') - compiler_event = prepare_ruleset_compiler_event( - old_entity=old_entity, - new_entity=new_entity - ) - _LOG.debug('Invoking ruleset compiler') - lambda_client = SERVICE_PROVIDER.lambda_func() - lambda_client._invoke_function_docker( - function_name='caas-ruleset-compiler', - event=compiler_event - ) - return result - - return wrapper - - -def dumps_dict_to_ddb(dct, as_dict=False, **kwargs): - """ Dump the dict to json in DynamoDB Format - You can use any other simplejson or json options - :param dct - the dict to dump - :param as_dict - returns the result as python dict (useful for - DynamoDB boto3 library) or as json sting - :returns: DynamoDB json format. - """ - - result_ = TypeSerializer().serialize( - json.loads(json.dumps(dct, default=json_serial))) - if as_dict: - return next(six.iteritems(result_))[1] - else: - return json.dumps(next(six.iteritems(result_))[1], **kwargs) - - -def prepare_ruleset_compiler_event(old_entity=None, new_entity=None): - """ - This method used for creating json file with all needed data to use in - ruleset-compiler - """ - if not (old_entity or new_entity): - raise AssertionError('Either old_entity or new_entity must be given') - - ddb_dict = {} - if old_entity and new_entity: - mode = "MODIFY" - new_image = dumps_dict_to_ddb(new_entity.get_json(), as_dict=True) - old_image = dumps_dict_to_ddb(old_entity.get_json(), as_dict=True) - ddb_dict['NewImage'] = new_image - ddb_dict['OldImage'] = old_image - elif old_entity: - old_image = dumps_dict_to_ddb(old_entity.get_json(), as_dict=True) - mode = "REMOVE" - ddb_dict['OldImage'] = old_image - else: - new_image = dumps_dict_to_ddb(new_entity.get_json(), as_dict=True) - mode = 'INSERT' - ddb_dict['NewImage'] = new_image - - res_dict = { - "Records": [{ - "eventID": time.time(), - "eventName": mode, - "eventSourceARN": f"/{Ruleset.Meta.table_name}/", - "dynamodb": ddb_dict - }] - } - return res_dict diff --git a/src/helpers/enums.py b/src/helpers/enums.py deleted file mode 100644 index 370bed5c5..000000000 --- a/src/helpers/enums.py +++ /dev/null @@ -1,24 +0,0 @@ -from modular_sdk.commons.constants import ParentType as _ParentType - -from helpers import Enum as CustomEnum -from helpers.constants import AWS_CLOUD_ATTR, AZURE_CLOUD_ATTR, \ - GCP_CLOUD_ATTR, KUBERNETES_CLOUD_ATTR - -# These enums are used for pydantic models and just to keep related data -ParentType = CustomEnum.build( - 'ParentType', [ - _ParentType.CUSTODIAN.value, - _ParentType.CUSTODIAN_LICENSES.value, - _ParentType.SIEM_DEFECT_DOJO.value, - _ParentType.CUSTODIAN_ACCESS.value - ] -) - - -# The values of this enum represent what Custom core can scan, i.e. what -# type of rules and ruleset(s) we can have. These are not tenant clouds -class RuleDomain(CustomEnum): - AWS = AWS_CLOUD_ATTR - AZURE = AZURE_CLOUD_ATTR - GCP = GCP_CLOUD_ATTR - KUBERNETES = KUBERNETES_CLOUD_ATTR diff --git a/src/helpers/exception.py b/src/helpers/exception.py deleted file mode 100644 index c486d8204..000000000 --- a/src/helpers/exception.py +++ /dev/null @@ -1,54 +0,0 @@ -import json -from helpers.constants import PARAM_MESSAGE, PARAM_TRACE_ID - - -class CustodianException(Exception): - - def __init__(self, code, content): - self._code = code - self._content = content - - @property - def code(self) -> str: - return self._code - - @code.setter - def code(self, value): - self._code = value - - @property - def content(self) -> str: - return self._content - - @content.setter - def content(self, value): - self._content = value - - def __str__(self): - return f'{self._code}:{self._content}' - - def __repr__(self): - return self.__str__() - - def response(self): - from helpers import _import_request_context - context = _import_request_context() - return { - 'statusCode': self._code, - 'headers': { - 'Content-Type': 'application/json', - 'x-amzn-ErrorType': self._code, - 'Access-Control-Allow-Headers': 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token', - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': '*' - }, - 'isBase64Encoded': False, - 'body': json.dumps({ - PARAM_MESSAGE: self._content, - PARAM_TRACE_ID: context.aws_request_id - }, sort_keys=True, separators=(",", ":")) - } - - -class MetricsUpdateException(CustodianException): - ... diff --git a/src/helpers/json_util.py b/src/helpers/json_util.py deleted file mode 100644 index 5f4f5f74a..000000000 --- a/src/helpers/json_util.py +++ /dev/null @@ -1,77 +0,0 @@ -import re -from decimal import Decimal -import six -import sys -from helpers.time_helper import utc_datetime -import simplejson as json - - -def object_hook(dct): - """ DynamoDB object hook to return python values """ - try: - # First - Try to parse the dct as DynamoDB parsed - if 'BOOL' in dct: - return dct['BOOL'] - if 'S' in dct: - val = dct['S'] - try: - return utc_datetime(_from=val) - except Exception: - return str(val) - if 'SS' in dct: - return set(dct['SS']) - if 'N' in dct: - if re.match("^-?\d+?\.\d+?$", dct['N']) is not None: - return float(dct['N']) - else: - try: - return int(dct['N']) - except Exception: - return int(dct['N']) - if 'B' in dct: - return str(dct['B']) - if 'NS' in dct: - return set(dct['NS']) - if 'BS' in dct: - return set(dct['BS']) - if 'M' in dct: - return dct['M'] - if 'L' in dct: - return dct['L'] - if 'NULL' in dct and dct['NULL'] is True: - return None - except Exception: - return dct - - # In a Case of returning a regular python dict - for key, val in six.iteritems(dct): - if isinstance(val, six.string_types): - try: - dct[key] = utc_datetime(_from=val) - except Exception: - # This is a regular Basestring object - pass - - if isinstance(val, Decimal): - if val % 1 > 0: - dct[key] = float(val) - elif six.PY3: - dct[key] = int(val) - elif val < sys.maxsize: - dct[key] = int(val) - else: - dct[key] = int(val) - - return dct - - -def loads(s, as_dict=False, *args, **kwargs): - """ Loads dynamodb json format to a python dict. - :param s - the json string or dict (with the as_dict variable set to - True) to convert - :returns python dict object - """ - if as_dict or (not isinstance(s, six.string_types)): - s = json.dumps(s) - kwargs['object_hook'] = object_hook - return json.loads(s, *args, **kwargs) diff --git a/src/helpers/lambda_response.py b/src/helpers/lambda_response.py new file mode 100644 index 000000000..89c525928 --- /dev/null +++ b/src/helpers/lambda_response.py @@ -0,0 +1,245 @@ +import base64 +import os +from http import HTTPStatus +from typing import Iterable, TypedDict, TypeVar, Final, Any + +import msgspec +from helpers.__version__ import __version__ +from helpers.constants import JSON_CONTENT_TYPE, \ + LAMBDA_URL_HEADER_CONTENT_TYPE_UPPER, CAASEnv +from helpers.log_helper import get_logger + +_LOG = get_logger(__name__) + + +Content = dict | list | str | Iterable | None + +# https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html +# https://zaccharles.medium.com/deep-dive-lambdas-response-payload-size-limit-8aedba9530ed +PAYLOAD_SIZE_LIMIT: Final[int] = (2 << 19) * 6 # 6mb + + +class LambdaOutput(TypedDict): + statusCode: int + headers: dict[str, str] + body: str + isBase64Encoded: bool + + +class LambdaForceExit(Exception): + """ + Can be used not only for 400 or 500. It actually can be very convenient + to make 200 exit anywhere in the code. Still... don't do that + """ + __slots__ = ('_response',) + + def __init__(self, response: 'LambdaResponse'): + self._response = response + + @property + def response(self) -> 'LambdaResponse': + return self._response + + def __str__(self): + return f'<{self._response.code}:{str(self._response.content)}>' + + __repr__ = __str__ + + def build(self) -> LambdaOutput: + return self._response.build() + + +class CustodianException(LambdaForceExit): + ... + + +class MetricsUpdateException(LambdaForceExit): + ... + + +class ReportNotSendException(LambdaForceExit): + ... + + +TE = TypeVar('TE', bound=LambdaForceExit) + + +class LambdaResponse: + __slots__ = ('_code', '_content', '_headers') + + def __init__(self, code: HTTPStatus = HTTPStatus.OK, + content: Any = '', + headers: dict[str, str] | None = None): + self._code = code + self._content = content + self._headers = headers or {} + + @property + def code(self) -> HTTPStatus: + return self._code + + @property + def content(self) -> Any: + return self._content + + @property + def ok(self) -> bool: + return 200 <= self._code <= 206 or 301 <= self._code <= 308 + + def _common_headers(self) -> dict[str, str]: + headers = { + 'Access-Control-Allow-Headers': 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': '*', + 'Accept-Version': __version__, # TODO API think about header name + } + if trace_id := os.getenv(CAASEnv.INVOCATION_REQUEST_ID): + headers['Lambda-Invocation-Trace-Id'] = trace_id + if not self.ok: + headers['x-amzn-ErrorType'] = str(self._code.value) + headers.update(self._headers) + return headers + + def build(self) -> LambdaOutput: + """ + Must return a format which is expected by Lambda Url/Api gateway + :return: + """ + return { + 'statusCode': self._code.value, + 'headers': self._common_headers(), + 'isBase64Encoded': False, + 'body': self._content, + } + + def exc(self, exc_type: type[TE] = CustodianException) -> TE: + return exc_type(response=self) + + +class BinaryResponse(LambdaResponse): + """ + Returns binary data + """ + def __init__(self, code: HTTPStatus = HTTPStatus.OK, + content: bytes = b'', + content_type: str | None = None): + super().__init__( + code=code, + content=content, + headers={'Content-Type': content_type} if content_type else {} + ) + + def build(self) -> LambdaOutput: + return { + 'headers': self._common_headers(), + 'body': base64.b64encode(self._content).decode(), + 'isBase64Encoded': True, + 'statusCode': self._code.value + } + + +class JsonLambdaResponse(LambdaResponse): + def __init__(self, code: HTTPStatus = HTTPStatus.OK, + content: Content = None, + headers: dict[str, str] | None = None): + headers = headers or {} + headers.update({ + LAMBDA_URL_HEADER_CONTENT_TYPE_UPPER: JSON_CONTENT_TYPE + }) + super().__init__( + code=code, + content=content, + headers=headers, + ) + + @staticmethod + def _default(obj): + """ + Default hook for json serializer + :param obj: + :return: + """ + if hasattr(obj, '__json__'): + return obj.__json__() + if isinstance(obj, bytes): + return obj.decode() + if isinstance(obj, Iterable): + return list(obj) + raise TypeError + + encoder = msgspec.json.Encoder(enc_hook=_default, order='sorted') + + def build(self) -> LambdaOutput: + _LOG.debug('Dumping output to Json') + body = self.encoder.encode(self._content) + _LOG.debug('Output was dumped') + if len(body) >= PAYLOAD_SIZE_LIMIT: + _LOG.warning('Output is too large to be returned from lambda') + raise ResponseFactory(HTTPStatus.REQUEST_ENTITY_TOO_LARGE).message( + 'Entity is too large. Use href=true query param or ' + 'connect support' + ).exc() + return { + 'headers': self._common_headers(), + 'body': body.decode(), + 'isBase64Encoded': False, + 'statusCode': self._code.value + } + + +class ResponseFactory: + """ + Builds some common JSON responses + >>> response = ResponseFactory(HTTPStatus.OK).items() + >>> raise ResponseFactory(HTTPStatus.BAD_REQUEST).default().exc() + """ + __slots__ = ('_code',) + + def __init__(self, code: HTTPStatus | int = HTTPStatus.OK): + self._code = HTTPStatus(code) if isinstance(code, int) else code + + def items(self, it: Iterable, next_token: Any = None + ) -> JsonLambdaResponse: + content = {'items': it} + if next_token: + content['next_token'] = next_token + return self.raw(content) + + def data(self, data: dict) -> JsonLambdaResponse: + return self.raw({'data': data}) + + def message(self, message: str) -> JsonLambdaResponse: + return self.raw({'message': message}) + + def errors(self, errors: list[dict]) -> JsonLambdaResponse: + return self.raw({'errors': errors}) + + def raw(self, raw: Content) -> JsonLambdaResponse: + return JsonLambdaResponse(code=self._code, content=raw) + + def default(self) -> JsonLambdaResponse: + return self.message(message=self._code.phrase) + + +def build_response(content: Content = None, + code: HTTPStatus | int = HTTPStatus.OK) -> LambdaOutput: + """ + Auxiliary function. Use ResponseFactory in case you want your own response + format + """ + f = ResponseFactory(code) + match content: + case str(): + resp = f.message(content) + case dict(): + resp = f.data(content) + case list(): + resp = f.items(content) + case None: + resp = f.default() + case _: # generator / iterator + resp = f.items(list(content)) + if not resp.ok: + raise resp.exc() + # return + return resp.build() diff --git a/src/helpers/log_helper.py b/src/helpers/log_helper.py index 33e356fd4..0e6ff2bc5 100644 --- a/src/helpers/log_helper.py +++ b/src/helpers/log_helper.py @@ -1,67 +1,17 @@ +import json import logging import os -import re -from sys import stdout -from functools import cached_property -from typing import Dict - -LOG_FORMAT = '%(asctime)s - %(levelname)s - %(name)s - %(message)s' - - -class SensitiveFormatter(logging.Formatter): - """Formatter that removes sensitive information.""" - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self._param_to_regex: Dict[str, re.Pattern] = {} - - @cached_property - def secured_params(self) -> set: - return { - 'refresh_token', 'id_token', 'password', 'authorization', 'secret', - 'AWS_SECRET_ACCESS_KEY', 'AWS_SESSION_TOKEN', 'git_access_secret', - 'api_key', 'AZURE_CLIENT_ID', 'AZURE_CLIENT_SECRET', - 'GOOGLE_APPLICATION_CREDENTIALS', 'private_key', 'private_key_id', - 'Authorization', 'Authentication', 'client_email' - } - - @staticmethod - def _compile_param_regex(param: str) -> re.Pattern: - """ - It searches for values in JSON objects where key is $param: - If param is "password" the string '{"password": "blabla"}' will be - printed as '{"password": "****"}' - [\'"] - single or double quote; [ ]* - zero or more spaces - (\[(.*?)\]) - value in braces - | or - """ - return re.compile( - f'[\'"]{param}[\'"]:[ ]*(?:(\[(.*?)\])|[\'"](.*?)[\'"])') - - def get_param_regex(self, param: str) -> re.Pattern: - if param not in self._param_to_regex: - self._param_to_regex[param] = self._compile_param_regex(param) - return self._param_to_regex[param] - - def _filter(self, string): - # Hoping that this regex substitutions do not hit performance... - for param in self.secured_params: - string = re.sub(self.get_param_regex(param), - f'\'{param}\': \'****\'', string) - return string - - def format(self, record): - original = logging.Formatter.format(self, record) - return self._filter(original) +from typing import TypeVar +from helpers.constants import CAASEnv, LOG_FORMAT custodian_logger = logging.getLogger('custodian') custodian_logger.propagate = False -console_handler = logging.StreamHandler(stream=stdout) -console_handler.setFormatter(SensitiveFormatter(LOG_FORMAT)) +console_handler = logging.StreamHandler() +console_handler.setFormatter(logging.Formatter(LOG_FORMAT)) custodian_logger.addHandler(console_handler) -log_level = os.getenv('CUSTODIAN_LOG_LEVEL') or 'DEBUG' +log_level = os.getenv(CAASEnv.LOG_LEVEL) or 'DEBUG' try: custodian_logger.setLevel(log_level) except ValueError: # not valid log level name @@ -69,18 +19,57 @@ def format(self, record): logging.captureWarnings(True) -def get_logger(log_name, level=log_level): - """ - :param level: CRITICAL = 50 - ERROR = 40 - WARNING = 30 - INFO = 20 - DEBUG = 10 - NOTSET = 0 - :type log_name: str - :type level: int - """ +def get_logger(log_name: str, level: str = log_level): module_logger = custodian_logger.getChild(log_name) if level: module_logger.setLevel(level) return module_logger + + +SECRET_KEYS = { + 'refresh_token', 'id_token', 'password', 'authorization', 'secret', + 'AWS_SECRET_ACCESS_KEY', 'AWS_SESSION_TOKEN', 'git_access_secret', + 'api_key', 'AZURE_CLIENT_ID', 'AZURE_CLIENT_SECRET', + 'GOOGLE_APPLICATION_CREDENTIALS', 'private_key', 'private_key_id', + 'Authorization', 'Authentication', 'certificate' +} + +JT = TypeVar('JT') # json type + + +def hide_secret_values(obj: JT, secret_keys: set[str] | None = None, + replacement: str = '****') -> JT: + """ + Does not change the incoming object, creates a new one. The event after + this function is just supposed to be printed. + :param obj: + :param secret_keys: + :param replacement: + :return: + """ + if not secret_keys: + secret_keys = SECRET_KEYS + match obj: + case dict(): + res = {} + for k, v in obj.items(): + if k in secret_keys: + res[k] = replacement + else: + res[k] = hide_secret_values(v, secret_keys, replacement) + return res + case list(): + return [ + hide_secret_values(v, secret_keys, replacement) for v in obj + ] + case str(): + try: + return hide_secret_values( + json.loads(obj), + secret_keys, + replacement + ) + except json.JSONDecodeError: + return obj + case _: + return obj diff --git a/src/helpers/recommendations.py b/src/helpers/recommendations.py index 7f00d36b8..8c31a59d8 100644 --- a/src/helpers/recommendations.py +++ b/src/helpers/recommendations.py @@ -1488,3 +1488,24 @@ } } } + +K8S_RECOMMENDATION_MODEL = { + "resource_id": "{cluster_id}", + "resource_type": "K8S_CLUSTER", + "source": "CUSTODIAN", + "severity": "HIGH", + "stats": { + "scan_date": None, + "status": "OK", + "message": "Processed successfully" + }, + "meta": None, + "general_actions": [], # ROLE, POD CONFIG + "recommendation": { + "resource_id": "{id}", + "resource_type": "{type}", + "article": "{article}", + "impact": "{impact}", + "description": "{description}" + } + } diff --git a/src/helpers/regions.py b/src/helpers/regions.py index 4e16c2d6f..d14be30d2 100644 --- a/src/helpers/regions.py +++ b/src/helpers/regions.py @@ -6,7 +6,7 @@ from helpers import Enum from helpers.constants import AWS_CLOUD_ATTR, AZURE_CLOUD_ATTR, \ - GOOGLE_CLOUD_ATTR, MULTIREGION + GOOGLE_CLOUD_ATTR, GLOBAL_REGION AWS_REGIONS = { 'ap-northeast-3', 'ap-southeast-1', 'ap-south-1', @@ -60,18 +60,18 @@ GOOGLE_CLOUD_ATTR: GOOGLE_REGIONS } -AWSRegion = Enum.build('AWSRegion', AWS_REGIONS) -AZURERegion = Enum.build('AZURERegion', AZURE_REGIONS) -GOOGLERegion = Enum.build('GOOGLERegion', GOOGLE_REGIONS) +AWSRegion = Enum.build('AWSRegion', sorted(AWS_REGIONS)) +AZURERegion = Enum.build('AZURERegion', sorted(AZURE_REGIONS)) +GOOGLERegion = Enum.build('GOOGLERegion', sorted(GOOGLE_REGIONS)) AllRegions = Enum.build( 'AllRegions', chain(AWSRegion.iter(), AZURERegion.iter(), GOOGLERegion.iter()) ) -AllRegionsWithMultiregional = Enum.build( +AllRegionsWithGlobal = Enum.build( 'AllRegionsWithMultiregional', - chain(AllRegions.iter(), iter([MULTIREGION])) + chain(AllRegions.iter(), iter([GLOBAL_REGION])) ) diff --git a/src/helpers/reports.py b/src/helpers/reports.py index 0ad0926dd..581f48163 100644 --- a/src/helpers/reports.py +++ b/src/helpers/reports.py @@ -1,233 +1,41 @@ -import json -from dataclasses import dataclass -from datetime import datetime -from typing import Dict, List, Optional, Union, Set, TypeVar +from helpers.constants import Severity -from helpers import filter_dict, hashable -from helpers.log_helper import get_logger - -_LOG = get_logger(__name__) - -DETAILED_REPORT_FILE = 'detailed_report.json' -USER_REPORT_FILE = 'user_detailed_report.json' -DIGEST_REPORT_FILE = 'report.json' - -STATISTICS_FILE = 'statistics.json' -API_CALLS_FILE = 'api_calls.json' - -KEYS_TO_EXCLUDE_FOR_USER = {'standard_points', } - -Coverage = Dict[str, Dict[str, float]] - -# Failed rule types. -ACCESS_TYPE = 'access' -CORE_TYPE = 'core' - -T = TypeVar('T') - - -@dataclass -class ChartsData: - """Charts images in bytes and decoded to UTF-8""" - checks_performed: Optional[str] - severity: Optional[str] - resource_type: Optional[str] - - -@dataclass -class AccountsData: - """Data about specific account""" - account_name: str - regions: list - total_checks: int - success: int - fail: int - last_sync: datetime - vulnerabilities: dict - charts: ChartsData - - -@dataclass -class CloudData: - """Data about all accounts within specific cloud provider""" - all_regions: set - total_checks: int - total_successful: int - total_failed: int - total_vulnerabilities: int - total_critical: int - total_high: int - total_medium: int - total_low: int - total_info: int - total_unknown: int - accounts: List[AccountsData] - - -class FindingsCollection: - keys_to_keep: set = { - 'description', 'resourceType' - } - - def __init__(self, data: dict = None, rules_data: dict = None): - self._data: Dict[tuple, set] = data or {} - self._rules_data: Dict[str, Dict] = rules_data or {} - - @property - def rules_data(self) -> dict: - return self._rules_data - - @classmethod - def from_detailed_report( - cls, report: dict, - only_report_fields: bool = True, - retain_all_keys: bool = False, - keys_to_exclude: List[str] = None - ) -> 'FindingsCollection': - - """Imports data from detailed_report's format. In addition, collects - the descriptions and other info about a rule.""" - - rak = retain_all_keys - kte = keys_to_exclude or [] - - result, rules_data = {}, {} - for region, region_policies in report.items(): - for policy in region_policies: - p_data = policy['policy'] - if p_data['name'] not in rules_data: # retrieve rules info - - keys = tuple(p_data) if rak else tuple(cls.keys_to_keep) - if kte: - keys = set(keys) - set(kte) - - if 0 < len(keys) < len(p_data): - kept = filter_dict(p_data, keys) - elif len(keys) == len(p_data): - kept = p_data - else: - continue - - rules_data[p_data['name']] = kept - - report_fields = set() - if only_report_fields: - report_fields = set(p_data.get('report_fields') or []) - - result.setdefault((p_data['name'], region), set()) - for resource in policy.get('resources', []): - result[(p_data['name'], region)].add( - hashable(filter_dict(resource, report_fields)) - ) - return cls(result, rules_data) - - @classmethod - def deserialize(cls, report: dict) -> 'FindingsCollection': - """Deserializes from a standard dict to the inner fancy one""" - result, rules_data = {}, {} - for rule, data in report.items(): - rules_data[rule] = filter_dict(data, cls.keys_to_keep) - for region, resources in data.get('resources', {}).items(): - result.setdefault((rule, region), set()) - for resource in resources: - result[(rule, region)].add(hashable(resource)) - return cls(result, rules_data) - - def serialize(self) -> dict: - """Serializes to a dict acceptable for json.dump""" - result = {} - for k, v in self._data.items(): - rule, region = k - if rule not in result: - result[rule] = filter_dict(self._rules_data.get(rule, {}), - self.keys_to_keep) - result[rule].setdefault('resources', {})[region] = list(v) - return result - - @property - def region_report(self): - result = {} - for k, resources in self._data.items(): - rule, region = k - scope = result.setdefault(region, []) - scope.append( - { - 'policy': { - 'name': rule, **self._rules_data.get(rule, {}) - }, - 'resources': list(resources) - } - ) - return result - - def json(self) -> str: - return json.dumps(self.serialize(), separators=(',', ':')) - - def update(self, other: 'FindingsCollection') -> None: - self._data.update(other._data) - self._rules_data.update(other._rules_data) - - def __sub__(self, other: 'FindingsCollection') -> 'FindingsCollection': - result = {} - for k, v in self._data.items(): - found_resource = v - other._data.get(k, set()) - if found_resource: - result[k] = found_resource - return FindingsCollection(result, - {**other._rules_data, **self._rules_data}) - - def __len__(self) -> int: - length = 0 - for v in self._data.values(): - length += len(v) - return length - - def __bool__(self) -> bool: - return bool(self._data) +NONE_VERSION = 'null' class Standard: - """Basic representation of rule's standard with version""" - NONE_VERSION = 'null' + """ + Basic representation of rule's standard with version + """ + __slots__ = ('name', 'version', 'points') - def __init__(self, name: str, version: str = None, points: set = None): - self._name = name - self._version = version or self.NONE_VERSION - self._points = points or set() + def __init__(self, name: str, version: str = NONE_VERSION, + points: set[str] | None = None): + self.name: str = name + self.version: str = version + self.points: set[str] = points or set() def __hash__(self): - return hash((self._name, self._version)) + return hash((self.name, self.version)) - def __eq__(self, other): - if isinstance(other, type(self)): - return (self._name, self._version) == (other._name, other._version) + def __eq__(self, other) -> bool: + if isinstance(other, Standard): + return (self.name, self.version) == (other.name, other.version) elif isinstance(other, tuple) and len(other) == 2: - return (self._name, self._version) == (other[0], other[1]) - raise NotImplementedError() + return (self.name, self.version) == (other[0], other[1]) + return False def __repr__(self): - return f'({self._name}, {self._version})' - - @property - def name(self): - return self._name - - @property - def version(self): - return self._version - - @property - def points(self): - return self._points + return f'({self.name}, {self.version})' @property def full_name(self): - return f'{self._name} {self._version}' \ - if self._version != self.NONE_VERSION else self._name + return f'{self.name} {self.version}' \ + if self.version != NONE_VERSION else self.name @classmethod - def deserialize(cls, standards: Union[Dict[str, List], Dict[str, Dict]], - return_strings=False) -> Union[Set['Standard'], Set[str]]: + def deserialize(cls, standards: dict[str, list] | dict[str, dict] + ) -> set['Standard']: """Currently rules' standards look like it's showed below { 'Standard_1': [ @@ -261,13 +69,15 @@ def deserialize(cls, standards: Union[Dict[str, List], Dict[str, Dict]], raise ValueError(f'Wrong rule standard format: ' f'{standard}, {version}') result.add(cls(**params)) - - if return_strings: - return {standard.full_name for standard in result} return result + @classmethod + def deserialize_to_strs(cls, standards: dict[str, list] | dict[str, dict] + ) -> set[str]: + return {item.full_name for item in cls.deserialize(standards)} + -def keep_highest(*args: Set[T]): +def keep_highest(*args: set): """ >>> a, b, c = {0,1,2,3}, {2,3,4}, {1, 2, 3, 4, 5} >>> keep_highest(a, b, c) @@ -284,3 +94,31 @@ def keep_highest(*args: Set[T]): if item in ne: to_remove.append(item) cur.difference_update(to_remove) + + +severity_chain = {v: i for i, v in enumerate(Severity.iter())} + + +def severity_cmp(one: str, two: str) -> int: + oi = severity_chain.get(one) + ti = severity_chain.get(two) + if not isinstance(oi, int): + return 1 + if not isinstance(ti, int): + return -1 + return oi - ti + + +def merge_dictionaries(dict_to_merge: dict, dict_in: dict): + """ + Merge one dict into another + + :param dict_to_merge: dictionary that we will merge + :param dict_in: dictionary in which we will merge + :return: + """ + for key in dict_to_merge: + if key in dict_in: + dict_in[key].update(dict_to_merge[key]) + else: + dict_in[key] = dict_to_merge[key] diff --git a/src/helpers/security.py b/src/helpers/security.py deleted file mode 100644 index 4a06ce65c..000000000 --- a/src/helpers/security.py +++ /dev/null @@ -1,100 +0,0 @@ -import secrets -import string -from abc import ABC, abstractmethod -from base64 import urlsafe_b64encode -from datetime import datetime -from json import dumps - -from helpers.constants import KID_ATTR, ALG_ATTR, TYP_ATTR -from services.clients.abstract_key_management import \ - AbstractKeyManagementClient - -EXPIRATION_ATTR = 'exp' -ENCODING = 'utf-8' - - -class AbstractTokenEncoder(ABC): - # Token: Header fields - kid: str - typ: str - alg: str - - # Token: Production fields - key_management: AbstractKeyManagementClient - - # Key Management: reference fields - prk_id: str - - def __init__(self): - self._reset() - - @abstractmethod - def __setitem__(self, key, value): - raise NotImplemented() - - @abstractmethod - def expire(self, dnt: datetime): - raise NotImplemented() - - @abstractmethod - def _reset(self): - raise NotImplemented() - - @property - @abstractmethod - def product(self): - message = 'Could not produce a Token, due to improper {} attribute.' - for key, required_type in self.__annotations__.items(): - obj = getattr(self, key, None) - assert isinstance(obj, required_type), message.format(key) - - -class TokenEncoder(AbstractTokenEncoder): - - def __setitem__(self, key, value): - self._payload[key] = value - - def expire(self, dnt: datetime): - self._payload[EXPIRATION_ATTR] = dnt.timestamp() - - def _reset(self): - self._payload = {} - - @property - def product(self): - _ = super().product - - # Allows to inject header-data, independently. - header = { - TYP_ATTR: self.typ, ALG_ATTR: self.alg, KID_ATTR: self.kid - } - - payload = self._payload - - message = b'.'.join( - self._encode(dumps(each, separators=(",", ":")).encode(ENCODING)) - for each in (header, payload) - ) - - signature = self.key_management.sign( - key_id=self.prk_id, message=message, algorithm=self.alg - ) - token = message + b'.' + self._encode(signature) - self._reset() - return token.decode(ENCODING) - - @staticmethod - def _encode(data: bytes): - return urlsafe_b64encode(data).replace(b"=", b"") - - -def gen_password(digits: int = 20) -> str: - allowed_punctuation = ''.join(set(string.punctuation) - {'"', "'", "!"}) - chars = string.ascii_letters + string.digits + allowed_punctuation - while True: - password = ''.join(secrets.choice(chars) for _ in range(digits)) + '=' - if (any(c.islower() for c in password) - and any(c.isupper() for c in password) - and sum(c.isdigit() for c in password) >= 3): - break - return password diff --git a/src/helpers/system_customer.py b/src/helpers/system_customer.py index 267292e5a..708c5468a 100644 --- a/src/helpers/system_customer.py +++ b/src/helpers/system_customer.py @@ -4,10 +4,17 @@ are set before importing from here. Otherwise, it could lead to timeout or an undesirable request to AWS. """ +import os + +from helpers.constants import CAASEnv +from helpers.log_helper import get_logger from services import SERVICE_PROVIDER +from typing import Final + +_LOG = get_logger(__name__) -# One and sole and onliest SYSTEM customer variable. Don't you dare use -# somewhere string: 'SYSTEM' or define one more such a variable :) -SYSTEM_CUSTOMER = SERVICE_PROVIDER.settings_service(). \ - get_system_customer_name() -print(f'SYSTEM Customer name: \'{SYSTEM_CUSTOMER}\'') +if CAASEnv.SYSTEM_CUSTOMER_NAME in os.environ: + SYSTEM_CUSTOMER: Final[str] = os.getenv(CAASEnv.SYSTEM_CUSTOMER_NAME) +else: + SYSTEM_CUSTOMER: Final[str] = SERVICE_PROVIDER.settings_service.get_system_customer_name() # noqa +_LOG.info(f'SYSTEM Customer name: \'{SYSTEM_CUSTOMER}\'') diff --git a/src/helpers/time_helper.py b/src/helpers/time_helper.py index 5014bead7..11f329a20 100644 --- a/src/helpers/time_helper.py +++ b/src/helpers/time_helper.py @@ -1,11 +1,9 @@ -from datetime import datetime, timezone -from time import time, sleep -from typing import Optional, Union +from datetime import datetime, timezone, date from dateutil.parser import isoparse -def utc_datetime(_from: Optional[str] = None, utc: bool = True) -> datetime: +def utc_datetime(_from: str | None = None, utc: bool = True) -> datetime: """ Returns time-zone aware datetime object in UTC. You can optionally pass an existing ISO string. The function will parse it to object and make @@ -17,7 +15,7 @@ def utc_datetime(_from: Optional[str] = None, utc: bool = True) -> datetime: return obj.astimezone(timezone.utc) if utc else obj.astimezone() -def utc_iso(_from: Optional[datetime] = None) -> str: +def utc_iso(_from: datetime | None = None) -> str: """ Returns time-zone aware datetime ISO string in UTC with military suffix. You can optionally pass datetime object. The function will make it @@ -29,17 +27,9 @@ def utc_iso(_from: Optional[datetime] = None) -> str: return obj.astimezone(timezone.utc).isoformat().replace('+00:00', 'Z') -def ts_datetime(_from: Optional[float] = None): - return datetime.fromtimestamp(_from or time(), timezone.utc) - - -def ts_from_iso(_from: Optional[str] = None) -> str: - return str((utc_datetime(_from) if _from else ts_datetime()).timestamp()) - - -def wait(seconds: int): - sleep(seconds) +def make_timestamp_java_compatible(timestamp: int | float) -> int: + return round(timestamp * 1000) -def make_timestamp_java_compatible(timestamp: Union[int, float]) -> int: - return round(timestamp * 1000) +def week_number(_from: datetime | date | None = None) -> int: + return (_from.day - 1) // 7 + 1 diff --git a/src/helpers/utils.py b/src/helpers/utils.py deleted file mode 100644 index 24f873cf5..000000000 --- a/src/helpers/utils.py +++ /dev/null @@ -1,17 +0,0 @@ -def get_last_element(string: str, delimiter: str) -> str: - return string.split(delimiter)[-1] - - -severity_chain = { - v: i for i, v in enumerate(('Info', 'Low', 'Medium', 'High')) -} - - -def severity_cmp(one: str, two: str) -> int: - oi = severity_chain.get(one) - ti = severity_chain.get(two) - if not isinstance(oi, int): - return 1 - if not isinstance(ti, int): - return -1 - return oi - ti diff --git a/src/integrations/__init__.py b/src/integrations/__init__.py deleted file mode 100644 index 9782c784e..000000000 --- a/src/integrations/__init__.py +++ /dev/null @@ -1,74 +0,0 @@ -from abc import ABC, abstractmethod -from http import HTTPStatus -from typing import TypedDict, Optional, List - -import requests -from botocore.exceptions import ClientError - -from helpers.log_helper import get_logger - -_LOG = get_logger(__name__) - - -class Result(TypedDict): - job_id: str - job_type: str - type: str - status: int - message: Optional[str] - error: Optional[str] - - -class AbstractAdapter(ABC): - siem_type = None - request_error = None - - def __init__(self, **kwargs): - self._entities = [] - - @abstractmethod - def add_entity(self, **kwargs): - raise NotImplementedError() - - def upload_all_entities(self) -> List[Result]: - results = [] - for entity in self._entities: - try: - self.upload(**entity) - results.append( - self.result( - job_id=entity.get('job_id'), - job_type=entity.get('job_type') - ) - ) - except (requests.RequestException, ClientError, ValueError) as e: - _LOG.error(f'{self.request_error} Error: {str(e)}') - results.append( - self.result( - job_id=entity.get('job_id'), - job_type=entity.get('job_type'), error=str(e) - ) - ) - return results - - @abstractmethod - def upload(self, **kwargs): - raise NotImplementedError() - - @classmethod - def result(cls, job_id, job_type, message='Pushed successfully', - error=None) -> Result: - result = { - 'job_id': job_id, 'job_type': job_type, 'type': cls.siem_type, - 'status': HTTPStatus.OK, 'message': message - } - if error: - result.pop('message') - result.update({ - 'status': HTTPStatus.SERVICE_UNAVAILABLE, - 'error': error - }) - return result - - def purge(self): - self._entities.clear() diff --git a/src/integrations/defect_dojo_adapter.py b/src/integrations/defect_dojo_adapter.py deleted file mode 100644 index c6b014255..000000000 --- a/src/integrations/defect_dojo_adapter.py +++ /dev/null @@ -1,325 +0,0 @@ -import copy -import csv -import io -import itertools -import json -import uuid -from base64 import b64encode -from datetime import timedelta, datetime -from typing import List, Dict - -from helpers.constants import PRODUCT_TYPE_NAME_ATTR, PRODUCT_NAME_ATTR, \ - ENGAGEMENT_NAME_ATTR, \ - TEST_TITLE_ATTR -from helpers.log_helper import get_logger -from helpers.time_helper import utc_datetime -from integrations import AbstractAdapter -from services.clients.dojo_client import DojoClient, DOJO_CAAS_SCAN_TYPE -from services.coverage_service import Standard - -_LOG = get_logger(__name__) -NB_SPACE = ' ' # non-breaking space -SIEM_DOJO_TYPE = 'dojo' -TIME_FORMAT = "%H:%M:%S" - - -class CustodianToDefectDojoEntitiesMapper: - defaults = { - PRODUCT_TYPE_NAME_ATTR: 'Custodian scan for {customer}', - PRODUCT_NAME_ATTR: '{tenant}', - ENGAGEMENT_NAME_ATTR: '{day_scope}', - TEST_TITLE_ATTR: '{job_scope}: {job_id}' - } - - class _SkipKeyErrorDict(dict): - def __missing__(self, key): - return '{' + key + '}' - - def __init__(self, mapping: dict, **kwargs): - """The kwargs are used to substitute keys from the given mapping. - Currently, a set of possible keys to substitute is not defined and - depends only on the kwargs which were given.""" - self.mapping = mapping.copy() - for key, value in self.defaults.items(): - self.mapping.setdefault(key, value) - for key in set(self.mapping) - set(self.defaults): - self.mapping.pop(key) - self.dictionary = self._SkipKeyErrorDict(kwargs) - - def generate(self) -> dict: - for key, value in self.mapping.items(): - self.mapping[key] = value.format_map(self.dictionary) - return self.mapping - - -class DefectDojoAdapter(AbstractAdapter): - siem_type = SIEM_DOJO_TYPE - request_error = f'Request error occurred while uploading ' \ - f'findings to {SIEM_DOJO_TYPE}.' - - def __init__(self, host: str, api_key: str, entities_mapping: dict = None, - display_all_fields: bool = False, upload_files: bool = False, - resource_per_finding: bool = False): - self.entities_mapping = entities_mapping or {} - self.display_all_fields = display_all_fields if isinstance( - display_all_fields, bool) else False - self.upload_files = upload_files if isinstance(upload_files, - bool) else False - self.resource_per_finding = resource_per_finding if isinstance( - resource_per_finding, bool) else False - self.client = DojoClient( - host=host, - api_key=api_key, - ) - super().__init__() - - def add_entity(self, job_id, job_type, started_at, stopped_at, - tenant_display_name, customer_display_name, - policy_reports: List[Dict]): - - _LOG.info(f'Jos:\'{job_id}\' - formatting compatible policy reports.') - formatted_findings = self.format_policy_report_list( - policy_report_list=policy_reports - ) - # formatted_findings = self.policy_report_to_generic(policy_reports) - - started_at_obj = utc_datetime(_from=started_at) - stopped_at_obj = utc_datetime(_from=stopped_at) if \ - stopped_at else utc_datetime() - - job_scope = f'{started_at_obj.strftime(TIME_FORMAT)} - ' \ - f'{stopped_at_obj.strftime(TIME_FORMAT)}' - stopped_at_date_obj = stopped_at_obj.date() - day_scope = \ - f'{stopped_at_date_obj.isoformat()} - ' \ - f'{(stopped_at_date_obj + timedelta(days=1)).isoformat()}' - self._entities.append({ - 'job_id': job_id, # not for dojo but for our code - 'job_type': job_type, - 'scan_date': stopped_at_date_obj.isoformat(), - 'formatted_findings': formatted_findings, - **CustodianToDefectDojoEntitiesMapper( - self.entities_mapping, - customer=customer_display_name, - tenant=tenant_display_name, - day_scope=day_scope, - job_scope=job_scope, - job_id=job_id).generate() - }) - - def policy_report_to_generic(self, policy_reports: List[Dict]) -> Dict: - # TODO get meta from mappings - findings = [] - for report in policy_reports: - vuln_id = report['vuln_id_from_tool'] - _len = report.get('resources #') - if not _len: - _LOG.debug(f'Skipping policy {vuln_id} because ' - f'no resources violate it') - continue - resources_str = self.make_markdown_table( - report['resources'], report['report_fields'] - ) - findings.append({ - 'title': report.get('description') or 'No title', - 'date': datetime.now().date().isoformat(), - 'description': f'{report["article"]}\nNumber of resources found: ' \ - f'**{_len}**\n\n{resources_str}', - 'severity': (report.get('severity') or 'Medium').title(), - 'mitigation': report['remediation'], - 'impact': report.get('impact'), - 'references': '\n'.join( - sorted(st.name for st in - Standard.deserialize(report.get('standard') or {})) - ), - 'service': report["service"], - 'tags': report.get('tags') or [], - 'vuln_id_from_tool': vuln_id, - }) - return {'findings': findings} - - def upload(self, engagement_name, product_name, product_type_name, - formatted_findings, test_title=None, job_id=None, - scan_date=None, job_type=None, scan_type=DOJO_CAAS_SCAN_TYPE): - - _LOG.info(f'Importing \'{job_id}\' job of {job_type} type ' - f'with \'{test_title}\' DefectDojo test') - formatted_report_buffer = io.BytesIO( - json.dumps(formatted_findings, sort_keys=True, separators=(",", ":")).encode() - ) - self.client.import_scan( - product_type_name=product_type_name, - product_name=product_name, - engagement_name=engagement_name, - test_title=test_title, - reimport=True, - buffer=formatted_report_buffer, - scan_date=scan_date, - scan_type=scan_type - ) - formatted_report_buffer.close() - - def upload_all_entities(self): - # _LOG.info('Creating Dojo classes for entities synchronously') - # batches = set((e.get('product_type_name'), e.get('product_name'), - # e.get('engagement_name'), e.get('scan_date')) - # for e in self._entities) - # try: - # for batch in batches: - # ptn, prn, en, sd = batch - # _LOG.info(f'Creating context for {prn} of {ptn} type' - # f' with engagement name of {en} of {sd} date.') - # self.client.create_context(product_type_name=batch[0], - # product_name=batch[1], - # engagement_name=batch[2], - # scan_date=batch[3]) - # except requests.RequestException as e: - # _LOG.error(f'An error occurred while creating Dojo context - {e}') - # return [ - # self.result(job_id='ALL', job_type='ALL', error=str(e)) - # ] - # _LOG.info('Necessary Dojo classes were created. Importing findings') - return super().upload_all_entities() - - def format_policy_report_list( - self, policy_report_list: List[Dict] - ) -> List[Dict]: - """ - Returns EPAM`s Defect Dojo compatible findings-object, derived - out of policy report(s), each of which is either resource-specific or - accumulated - based on the one_res_per_finding. - :param policy_report_list: List[Dict] - :return: List[Dict] - """ - findings = [] - - one_res_per_finding = self.resource_per_finding - helper_keys = ('report_fields',) - for policy_report in policy_report_list: - vuln_id = policy_report.get('vuln_id_from_tool') # policy-name. - - helper_values = [] - # Removes helper key-value pairs. - for key in helper_keys: - value = None - if key in policy_report: - value = policy_report.pop(key) - helper_values.append(value) - - report_fields, *_ = helper_values - report_fields = report_fields or [] - - if not report_fields and not self.display_all_fields: - _LOG.warning( - f'Policy:\'{vuln_id}\' maintains no \'report_fields\' ' - f'- going to default reporting all keys.' - ) - - resources = [] - for _resource in policy_report.get('resources', []): - resource = {} - if not self.display_all_fields and report_fields: - for field in report_fields: - resource[field] = _resource.get(field) - else: - resource = _resource - - if resource: - resources.append(resource) - - resource_type = policy_report.get('service') - - # v3.3.1 Date, Description is self set-up on the DefectDojo side. - # todo table-markdown may not be required, as it given within - # `description`, which may be used, for non-multi-regional titles. - - # todo Consider 'resources': List[str of rows of a resources table] - - if one_res_per_finding: - - for resource in resources: - _report = copy.deepcopy(policy_report) - _report['resources'] = [resource] - _report['resources #'] = 1 - - if self.upload_files: - _report['files'] = { - 'title': f'{resource_type}-{str(uuid.uuid4())[:8]}' - f'.json', - 'data': b64encode(json.dumps(resource).encode()). - decode() - } - - if 'Arn' in resource: - _report["component_name"] = resource["Arn"] - - elif 'component_name' in _report: - del _report["component_name"] - - findings.append(_report) - - else: - policy_report['resources'] = resources - if self.upload_files: - policy_report['files'] = [ - { - 'title': f'{resource_type}-{str(uuid.uuid4())[:8]}' - f'.json', - 'data': b64encode(json.dumps(resource).encode()). - decode() - } - for resource in resources - ] - - findings.append(policy_report) - - return findings - - def make_markdown_table(self, detailed_resources, report_fields): - from pytablewriter import MarkdownTableWriter - display_all_fields = self.display_all_fields - try: - - key_report = report_fields[0] if report_fields else None - if key_report: - detailed_resources = sorted(detailed_resources, - key=lambda d: d[key_report]) - except KeyError: - _LOG.warning(f'Invalid detailed_report.json!!! ' - f'Fields: {report_fields}, ' - f'resources :{detailed_resources}') - display_all_fields = True - - report_fields = set(report_fields) if report_fields and not \ - display_all_fields else set( - itertools.chain.from_iterable( - resource.keys() for resource in detailed_resources - ) - ) - resources = [ - {f'{field[0].upper() + field[1:]}{NB_SPACE}': f'{value}{NB_SPACE}' - for field, value in resource.items() if field in report_fields - and isinstance(value, (str, int, float))} - for resource in detailed_resources - ] - - try: - fieldnames = sorted(set(itertools.chain.from_iterable( - resource.keys() for resource in resources - ))) - table_writer = MarkdownTableWriter() - with io.StringIO() as buffer: - dict_writer = csv.DictWriter(buffer, fieldnames) - dict_writer.writeheader() - dict_writer.writerows(resources) - table_writer.from_csv(buffer.getvalue()) - table_str = table_writer.dumps() - except Exception as e: - _LOG.warning(f'Something went wrong while making resources table ' - f'for DefectDojo\'s finding: {e}. Dumping resources ' - f'to JSON') - table_str = json.dumps( - resources, sort_keys=True, indent=4).translate( - {ord(c): None for c in '{[]}",'} - ) - return table_str diff --git a/src/integrations/security_hub_adapter.py b/src/integrations/security_hub_adapter.py deleted file mode 100644 index bafc9d258..000000000 --- a/src/integrations/security_hub_adapter.py +++ /dev/null @@ -1,78 +0,0 @@ -from typing import Optional - -import boto3 - -from helpers import batches -from helpers.log_helper import get_logger -from integrations import AbstractAdapter - -SIEM_SH_TYPE = 'security_hub' - -_LOG = get_logger(__name__) - - -class SecurityHubAdapter(AbstractAdapter): - siem_type = SIEM_SH_TYPE - request_error = f'An error occurred while uploading ' \ - f'findings to {SIEM_SH_TYPE}.' - - def __init__(self, aws_region: str, product_arn: str, - aws_access_key_id: str, aws_secret_access_key: str, - aws_session_token: Optional[str] = None, - aws_default_region: Optional[str] = None): - self.region = aws_region or aws_default_region - self.product_arn = product_arn - self.client = boto3.client( - 'securityhub', - aws_access_key_id=aws_access_key_id, - aws_secret_access_key=aws_secret_access_key, - aws_session_token=aws_session_token, - region_name=self.region - ) - super().__init__() - - def add_entity(self, job_id, job_type, findings): - for finding in findings: - finding["ProductArn"] = self.product_arn - # finding["AwsAccountId"] = re.findall( - # '/(\d+)/', self.product_arn)[0] - self._entities.append({ - 'job_id': job_id, - 'job_type': job_type, - 'findings': findings - }) - - def upload(self, job_id, job_type, findings: list): - """ - Upload findings to Security Hub by batches - """ - _LOG.info(f'Going to upload {len(findings)} findings ' - f'for {job_type} job: {job_id}') - max_findings_in_request = 100 - responses = [] - for batch in batches(findings, max_findings_in_request): - if len(batch) == 0: - continue - responses.append(self.client.batch_import_findings( - Findings=batch - )) - _LOG.debug(f'Security hub responses: {responses}') - - _LOG.info('Counting errors from SecurityHub responses') - total_failed_count, total_succeeded_count, errors = 0, 0, set() - for response in responses: - failed = response.get('FailedCount', 0) - total_failed_count += failed - total_succeeded_count += response.get('SuccessCount', 0) - if failed > 0: - errors.update([finding.get('ErrorMessage') for finding in - response.get('FailedFindings')]) - _LOG.warning(f'Errors were counted. Total failed: {total_failed_count}' - f', total succeeded: {total_succeeded_count}') - if errors: - raise ValueError( - f'Total failed findings count: {total_failed_count}\n' - f'Total succeeded findings count: {total_succeeded_count}\n' - f'Occurred errors:\n' + '\n'.join(errors) - ) - return responses diff --git a/src/lambdas/caas_jobs_backupper/__init__.py b/src/lambdas/caas_jobs_backupper/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/lambdas/caas_jobs_backupper/deployment_resources.json b/src/lambdas/caas_jobs_backupper/deployment_resources.json deleted file mode 100644 index 6c5ded0d9..000000000 --- a/src/lambdas/caas_jobs_backupper/deployment_resources.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "caas-jobs-backupper-role": { - "predefined_policies": [ - "AWSLambdaDynamoDBExecutionRole", - "AmazonKinesisFirehoseFullAccess" - ], - "principal_service": "lambda", - "custom_policies": [ - "lambda-basic-execution" - ], - "resource_type": "iam_role" - } -} \ No newline at end of file diff --git a/src/lambdas/caas_jobs_backupper/handler.py b/src/lambdas/caas_jobs_backupper/handler.py deleted file mode 100644 index c600ddfb4..000000000 --- a/src/lambdas/caas_jobs_backupper/handler.py +++ /dev/null @@ -1,328 +0,0 @@ -""" -Two cases are currently possible: -- you want to use AWS Firehose to inject DynamoDB Stream records to S3; -- you want to put the records directly to S3 without the firehose. -The first method is more effective in terms of costs (and probably in the -matter of execution time & memory which anyway comes down to costs). -The second method allows to build custom S3 paths based on the content of -the streaming data. - -The first method is chosen by default. It depends on such envs: -- FIREHOSE_STREAM_NAME: the name of configured AWS firehose stream (obviously); - -The second method can be enabled by setting env `FORCE_DIRECT_UPLOAD` equal -to `true` and it depends on such envs: -- DIRECT_UPLOAD_BUCKET_NAME: the name of an S3 bucket to upload files to; -- DIRECT_UPLOAD_BUCKET_PREFIX: the key prefix to upload to. Default - value is `Direct/CaaSJobs`; -- DIRECT_UPLOAD_JOB_PER_FILE: the number of job items to write to one file. - In case the env is equal to `1` the name of each file is the inner job's id. - In case the env is not set, the maximum number of jobs will be pushed - to a single file. Its name will be built based on the first and the - last jobs execution time. -The jobs in files are sorted in ascending order by `submitted_at` attr -if you are using the direct upload. - -Both methods support one similar env: -- FILTER_RECORDS: if equal to `true` the input records will be filtered to - keep only those that came from DynamoDB TTL. Since we are using Lambda - Event Source Mapping filter, no custom filtering are needed. So by default - this env is equal to `false` -""" -import gzip -import json -import logging -import os -import time -from abc import ABC, abstractmethod -from concurrent.futures import ThreadPoolExecutor -from functools import wraps -from itertools import islice -from pathlib import PurePosixPath -from secrets import token_hex -from typing import List, Dict, Generator, Optional, Iterable, IO, Union, Tuple, \ - Callable - -import boto3 -from botocore.exceptions import ClientError - -DEFAULT_BUCKET_PREFIX = 'Direct/CaaSJobs' -LINE_SEP = '\n' -GZIP_EXTENSION = '.gz' -NOT_EXISTING_ENTITY = 'Empty' - -FIREHOSE_STREAM_NAME = os.getenv('FIREHOSE_STREAM_NAME') -FILTER_RECORDS = str(os.getenv('FILTER_RECORDS')).lower() == 'false' - -FORCE_DIRECT_UPLOAD = str(os.getenv('FORCE_DIRECT_UPLOAD')).lower() == 'true' -BUCKET_NAME = os.getenv('DIRECT_UPLOAD_BUCKET_NAME') -BUCKET_PREFIX = (os.getenv( - 'DIRECT_UPLOAD_BUCKET_PREFIX') or DEFAULT_BUCKET_PREFIX).strip('/') -JOBS_PER_FILE = int(os.getenv('DIRECT_UPLOAD_JOB_PER_FILE')) if os.getenv( - 'DIRECT_UPLOAD_JOB_PER_FILE') else None - -_LOG = logging.getLogger() -_LOG.setLevel(logging.INFO) - - -def batches(iterable: Iterable, n: int) -> Generator[List, None, None]: - if n < 1: - raise ValueError('n must be >= 1') - it = iter(iterable) - batch = list(islice(it, n)) - while batch: - yield batch - batch = list(islice(it, n)) - - -def backoff(max_retries: int = 5): - """ - The decorated function must return bool: true if succeeded and false if not - """ - - def decorator(func: Callable): - @wraps(func) - def wrapper(*args, **kwargs): - succeeded, retry = func(*args, **kwargs), 0 - while not succeeded: - if retry == max_retries: - _LOG.error(f'The number of max retries has been reached ' - f'and function \'{func.__name__}\' has not ' - f'succeeded. Quiting.') - break - _delay = 2 ** retry - _LOG.warning(f'Function \'{func.__name__}\' has not ' - f'succeeded. Trying again after {_delay} secs') - time.sleep(_delay) - succeeded = func(*args, **kwargs) - retry += 1 - - return wrapper - - return decorator - - -class RecordsUploader(ABC): - def __init__(self, records: Optional[List[Dict]] = None): - self._records = records or [] - - @property - def i_records(self) -> Generator[Dict, None, None]: - i_source = filter(self.is_valid_record, self._records) \ - if FILTER_RECORDS else iter(self._records) - yield from i_source - - @property - def i_old_images(self) -> Generator[Dict, None, None]: - for record in self.i_records: - yield record['dynamodb']['OldImage'] - - @property - def records(self) -> List[Dict]: - return list(self.i_records) - - @property - def old_images(self) -> List[Dict]: - return list(self.i_old_images) - - @records.setter - def records(self, value: List[Dict]): - self._records = value - - @staticmethod - def _record(image: dict) -> str: - return json.dumps(image) + LINE_SEP - - @staticmethod - def is_valid_record(record: dict) -> bool: - _remove = record.get('eventName') == 'REMOVE' - - identity = record.get('userIdentity', {}) - _ttl = (identity.get('principalId') == 'dynamodb.amazonaws.com' and - identity.get('type') == 'Service') - return _remove and _ttl - - @abstractmethod - def upload(self): - ... - - -class FirehoseUploader(RecordsUploader): - def __init__(self, records: Optional[List[Dict]] = None): - super().__init__(records) - self._client = None - - @property - def client(self): - if not self._client: - _LOG.info('Initializing boto3 firehose client') - self._client = boto3.client('firehose') - return self._client - - def upload(self): - _LOG.info('Uploading via firehose') - data = tuple( - {'Data': self._record(image)} for image in self.i_old_images - ) - response = self.client.put_record_batch( - DeliveryStreamName=FIREHOSE_STREAM_NAME, Records=data - ) - failed: int = response['FailedPutCount'] - if failed: - _LOG.warning(f'{failed} items were not injected to the delivery ' - f'stream. Trying to send them individually') - for i, record in enumerate(response['RequestResponses']): - if 'ErrorCode' not in record: - continue - _LOG.info(f'Failed record: {record}') - self.client.put_record( - DeliveryStreamName=FIREHOSE_STREAM_NAME, - Record={'Data': data[i]} - ) - else: - _LOG.info('All the items were successfully sent in one request') - _LOG.info('Uploading via firehose have finished') - - -class DirectS3Uploader(RecordsUploader): - def __init__(self, records: Optional[List[Dict]] = None): - super().__init__(records) - self._client = None - - @staticmethod - def _gz_key(key: str) -> str: - if not key.endswith(GZIP_EXTENSION): - key = key.strip('.') + GZIP_EXTENSION - return key - - @property - def client(self): - if not self._client: - _LOG.info('Initializing boto3 s3 client') - self._client = boto3.client('s3') - return self._client - - @backoff(5) - def put_object(self, bucket_name: str, key: str, body: Union[bytes, IO]): - _LOG.debug(f'Uploading object \'{key}\'') - try: - self.client.put_object( - Bucket=bucket_name, - Key=key, - Body=body - ) - return True - except (ClientError, Exception) as e: - _LOG.warning(f'An error occurred trying to ' - f'upload file \'{key}\': {e}.') - return False - - def put_objects_batch(self, bucket_name: str, - key_body: Iterable[Tuple[str, Union[IO, bytes]]]): - with ThreadPoolExecutor() as executor: - for pair in key_body: - executor.submit(self.put_object, bucket_name, *pair) - - @abstractmethod - def upload(self): - ... - - -class CaaSJobsS3Uploader(DirectS3Uploader): - def sort_jobs(self, records: List[Dict]) -> List[Dict]: - return sorted(records, key=lambda x: self._job_time(x)) - - @staticmethod - def _job_time(record: Dict) -> str: - return record['submitted_at']['S'] - - def get_bounds(self, records: List[Dict]) -> Tuple[str, str]: - """ - Records must be already sorted. - """ - _len = len(records) - if _len < 1: - raise ValueError('n must be >= 1') - if _len == 1: - _submitted_at = self._job_time(records[0]) - return _submitted_at, _submitted_at - if _len > 1: - return self._job_time(records[0]), self._job_time(records[-1]) - - def upload(self): - _LOG.info('Uploading directly to S3') - entities = {} - for image in self.i_old_images: - entities.setdefault(( - image.get('customer_display_name', {}).get('S'), - image.get('tenant_display_name', {}).get('S'), - image.get('account_display_name', {}).get('S')), [] - ).append(image) - _ = self.client # init client before threads otherwise there are problems - # TODO stream data in case MemoryError ? - if JOBS_PER_FILE == 1: - _LOG.info('Special case, JOBS_PER_FILE is equal to 1. ' - 'Key is job`s id') - key_body = [] - for c_t_a, images in entities.items(): - _key = PurePosixPath( - BUCKET_PREFIX, *(e or NOT_EXISTING_ENTITY for e in c_t_a)) - key_body.extend(( - (self._gz_key(str(_key / image['job_id']['S'])), - gzip.compress(self._record(image).encode())) for image in - images - )) - elif JOBS_PER_FILE: - _LOG.info(f'Uploading batches with {JOBS_PER_FILE} jobs in a file') - key_body = [] - for c_t_a, images in entities.items(): - _key = PurePosixPath( - BUCKET_PREFIX, *(e or NOT_EXISTING_ENTITY for e in c_t_a)) - for batch in batches(images, JOBS_PER_FILE): - items_s = self.sort_jobs(batch) - start, end = self.get_bounds(items_s) - key_body.append(( - self._gz_key( - str(_key / f'{start}-{end}-{token_hex(8)}')), - gzip.compress( - ''.join(map(self._record, items_s)).encode()) - )) - else: - _LOG.info('JOB_PER_FILE is not set. Uploading all at once') - key_body = [] - for c_t_a, images in entities.items(): - _key = PurePosixPath( - BUCKET_PREFIX, *(e or NOT_EXISTING_ENTITY for e in c_t_a)) - items_s = self.sort_jobs(images) - start, end = self.get_bounds(items_s) - key_body.append(( - self._gz_key(str(_key / f'{start}-{end}-{token_hex(8)}')), - gzip.compress(''.join(map(self._record, items_s)).encode()) - )) - self.put_objects_batch(BUCKET_NAME, key_body) - _LOG.info('Uploading directly to S3 has finished') - - -firehose_uploader = FirehoseUploader() -s3_uploader = CaaSJobsS3Uploader() - - -def lambda_handler(event, context): - _LOG.info(f'Event: {event}') - code, message = 200, 'Success' - try: - uploader = s3_uploader if FORCE_DIRECT_UPLOAD else firehose_uploader - uploader.records = event['Records'] - uploader.upload() - except ClientError as e: - _LOG.error(f'Botocore error occurred: \'{e}\'') - code, message = 500, str(e) - except Exception as e: - _LOG.error(f'Unexpected error occurred: \'{e}\'') - code, message = 500, str(e) - response = { - 'statusCode': code, - 'body': json.dumps({'message': message}) - } - _LOG.info(f'Response: {response}') - return response diff --git a/src/lambdas/caas_jobs_backupper/lambda_config.json.old b/src/lambdas/caas_jobs_backupper/lambda_config.json.old deleted file mode 100644 index 8369fc042..000000000 --- a/src/lambdas/caas_jobs_backupper/lambda_config.json.old +++ /dev/null @@ -1,33 +0,0 @@ -{ - "version": "1.0", - "name": "caas-jobs-backupper", - "func_name": "handler.lambda_handler", - "resource_type": "lambda", - "iam_role_name": "caas-jobs-backupper-role", - "runtime": "python3.8", - "memory": 256, - "timeout": 100, - "lambda_path": "lambdas/caas_jobs_backupper", - "dependencies": [], - "event_sources": [ - { - "resource_type": "dynamodb_trigger", - "target_table": "CaaSJobs", - "batch_size": 100, - "batch_window": 300, - "filters": [ - { - "Pattern": "{\"userIdentity\": {\"type\":[\"Service\"],\"principalId\":[\"dynamodb.amazonaws.com\"]}}" - } - ] - } - ], - "env_variables": { - "FIREHOSE_STREAM_NAME": "CustodianJobsBackupperStream", - "FILTER_RECORDS": "False" - }, - "publish_version": true, - "alias": "${lambdas_alias_name}", - "url_config": {}, - "ephemeral_storage": 512 -} \ No newline at end of file diff --git a/src/lambdas/caas_jobs_backupper/local_requirements.txt b/src/lambdas/caas_jobs_backupper/local_requirements.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/lambdas/caas_jobs_backupper/requirements.txt b/src/lambdas/caas_jobs_backupper/requirements.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/lambdas/custodian_api_handler/README.md b/src/lambdas/custodian_api_handler/README.md index 92c3512c5..5c4bd7b46 100644 --- a/src/lambdas/custodian_api_handler/README.md +++ b/src/lambdas/custodian_api_handler/README.md @@ -45,7 +45,6 @@ This lambda uses the following resources: * CaaSSettings - the table used by service to get and store settings data; * CaaSUsers - the table used by service to get and store user data: user_id and tenant; -* CaaSLicenses - the table used by service to get and store data about ruleset licenses; * CaaSRulesets - the table used by service to get and store data about custom and licensed rulesets. ### Lambda Configuration @@ -84,15 +83,8 @@ This lambda uses the following resources: * `stats_s3_bucket_name`: name of the bucket to store scan statistics; * `job_lifetime_min`: lifetime of the job in minutes. After this time, the job will be forcibly stopped; -* `last_scan_threshold`: time (in hours) that has to elapse since the last - account scan in order to start the next one. This parameter is ignored only if - the status of the previous job is FAILED. Doesn't affect other accounts; -* `feature_filter_jobs_request`: name of the cloud to scan. If this variable is - not set, then all clouds will be scanned; * `feature_skip_cloud_identifier_validation`: choose this option if you want to skip validation of credentials and cloud identifier; -* `feature_allow_only_temp_aws_credentials`: choose this option if you - want to allow initiate scan for AWS only with temporary credentials. #### Trigger event diff --git a/src/lambdas/custodian_api_handler/deployment_resources.json b/src/lambdas/custodian_api_handler/deployment_resources.json index a8bec688b..3c5d30cb6 100644 --- a/src/lambdas/custodian_api_handler/deployment_resources.json +++ b/src/lambdas/custodian_api_handler/deployment_resources.json @@ -17,12 +17,12 @@ "cognito-idp:AdminCreateUser", "cognito-idp:AdminSetUserPassword", "cognito-idp:ListUserPools", - "cognito-idp:AdminRespondToAuthChallenge", "cognito-idp:AdminUpdateUserAttributes", "cognito-idp:ListUserPoolClients", "cognito-idp:ListUsers", "cognito-idp:AdminUpdateUserAttributes", "cognito-idp:AdminDeleteUser", + "cognito-idp:AdminGetUser", "ssm:PutParameter", "ssm:DeleteParameter", "sts:AssumeRole", diff --git a/src/lambdas/custodian_api_handler/handler.py b/src/lambdas/custodian_api_handler/handler.py index 142cbd8f1..3e8abef18 100644 --- a/src/lambdas/custodian_api_handler/handler.py +++ b/src/lambdas/custodian_api_handler/handler.py @@ -1,366 +1,75 @@ from functools import cached_property -from http import HTTPStatus -from typing import List, Type -from modular_sdk.commons.error_helper import RESPONSE_SERVICE_UNAVAILABLE_CODE +from typing_extensions import Self -from helpers import ( - raise_error_response, validate_params, - build_response, PARAM_USER_ID, CustodianException, - batches, KeepValueGenerator) -from helpers.__version__ import __version__ -from helpers.constants import PARAM_HTTP_METHOD, PARAM_REQUEST_PATH, \ - USER_CUSTOMER_ATTR, HTTPMethod +from handlers import Mapping +from helpers import RequestContext from helpers.log_helper import get_logger -from helpers.system_customer import SYSTEM_CUSTOMER -from lambdas.custodian_api_handler.handlers import AbstractHandler, Mapping -from lambdas.custodian_api_handler.handlers.batch_result_handler import \ - BatchResultsHandler -from lambdas.custodian_api_handler.handlers.health_check_handler import \ - HealthCheckHandler +from lambdas.custodian_api_handler.handlers.batch_result_handler import ( + BatchResultsHandler, +) +from lambdas.custodian_api_handler.handlers.events_handler import EventsHandler +from lambdas.custodian_api_handler.handlers.health_check_handler import ( + HealthCheckHandler, +) from lambdas.custodian_api_handler.handlers.job_handler import JobHandler -from lambdas.custodian_api_handler.handlers.metrics_status_handler import \ - MetricsStatusHandler -from services import SERVICE_PROVIDER -from services.abstract_api_handler_lambda import AbstractApiHandlerLambda -from services.abstract_lambda import (PARAM_ROLE) -from services.clients.batch import BatchClient -from services.clients.ecr import ECRClient -from services.environment_service import EnvironmentService -from services.event_processor_service import EventProcessorService -from services.event_service import EventService -from services.modular_service import ModularService -from services.rbac.iam_cache_service import CachedIamService -from services.ruleset_service import RulesetService -from services.scheduler_service import SchedulerService -from services.setting_service import SettingsService, \ - KEY_CURRENT_CUSTODIAN_CUSTOM_CORE_VERSION -from services.user_service import CognitoUserService +from lambdas.custodian_api_handler.handlers.metrics_status_handler import ( + MetricsStatusHandler, +) +from lambdas.custodian_api_handler.handlers.new_swagger_handler import \ + SwaggerHandler +from lambdas.custodian_api_handler.handlers.users_handler import UsersHandler +from services import SP +from services.abs_lambda import ( + ApiGatewayEventProcessor, + CheckPermissionEventProcessor, + RestrictCustomerEventProcessor, + ExpandEnvironmentEventProcessor, + ApiEventProcessorLambdaHandler, + RestrictTenantEventProcessor +) +from validators.registry import permissions_mapping _LOG = get_logger('custodian-api-handler') -ACTION_PARAM_ERROR = 'There is no handler for the \'{endpoint}\' endpoint' - -PARAM_USERNAME = 'username' -PARAM_PASSWORD = 'password' -PARAM_TENANTS = 'tenants' -PARAM_CUSTOMER = 'customer' -PARAM_VENDOR = 'vendor' -PARAM_EVENTS = 'events' - -HTTP_METHOD_ERROR = 'The server does not support the HTTP method {method} ' \ - 'for the resource {resource}' - -UNABLE_TO_START_SCAN_ERROR_MESSAGE = \ - 'Cannot start a scan: custodian custom core is outdated and cannot be' \ - ' updated to version to the latest version \'{0}\'.' - -class ApiHandler(AbstractApiHandlerLambda): +class ApiHandler(ApiEventProcessorLambdaHandler): + processors = ( + ExpandEnvironmentEventProcessor.build(), + ApiGatewayEventProcessor(permissions_mapping), + RestrictCustomerEventProcessor.build(), + CheckPermissionEventProcessor.build(), + RestrictTenantEventProcessor.build() + ) - def __init__(self, batch_client, modular_service, - environment_service, user_service, - cached_iam_service, settings_service, - ecr_client, event_service, - event_processor_service, - ruleset_service, scheduler_service): - self.batch_client: BatchClient = batch_client - self.modular_service: ModularService = modular_service - self.environment_service: EnvironmentService = environment_service - self.user_service: CognitoUserService = user_service - self.cached_iam_service: CachedIamService = cached_iam_service - self.settings_service: SettingsService = settings_service - self.ecr_client: ECRClient = ecr_client - self.event_service: EventService = event_service - self.event_processor_service: EventProcessorService = \ - event_processor_service - self.ruleset_service: RulesetService = ruleset_service - self.scheduler_service: SchedulerService = scheduler_service - - @cached_property - def additional_handlers(self) -> List[Type[AbstractHandler]]: - return [ + def __init__(self): + self.additional_handlers = [ BatchResultsHandler, + UsersHandler, JobHandler, HealthCheckHandler, - MetricsStatusHandler + MetricsStatusHandler, + EventsHandler ] + if not SP.environment_service.is_docker(): + self.additional_handlers.append(SwaggerHandler) + + @classmethod + def build(cls) -> Self: + return cls() @cached_property def mapping(self) -> Mapping: - # todo use only additional handlers - res = { - '/users': { - HTTPMethod.DELETE: self.user_delete - }, - '/users/password-reset': { - HTTPMethod.POST: self.user_reset_password - }, - '/signup': { - HTTPMethod.POST: self.signup_action - }, - '/signin': { - HTTPMethod.POST: self.signin_action - }, - '/event': { - HTTPMethod.POST: self.event_action - } - } + res = {} for handler in self.additional_handlers: res.update( handler.build().mapping ) return res - def handle_request(self, event, context): - request_path = event[PARAM_REQUEST_PATH] - method_name = event[PARAM_HTTP_METHOD] - handler_functions = self.mapping.get(request_path) - if not handler_functions: - raise_error_response( - HTTPStatus.BAD_REQUEST, - ACTION_PARAM_ERROR.format(endpoint=request_path)) - handler_func = handler_functions.get(method_name) - if not handler_func: - return build_response( - code=HTTPStatus.BAD_REQUEST, - content=HTTP_METHOD_ERROR.format(method=method_name, - resource=request_path)) - return handler_func(event=event) - - def signup_action(self, event): - _LOG.debug('Going to signup user') - username = event.get(PARAM_USERNAME) - password = event.get(PARAM_PASSWORD) - customer = event.get(PARAM_CUSTOMER) - role = event.get(PARAM_ROLE) - if not all([username, password, customer, role]): - raise CustodianException( - code=HTTPStatus.BAD_REQUEST, - content='You must specify all required parameters: username, ' - 'password, customer, role.') - tenants = event.get(PARAM_TENANTS) - if not self.modular_service.get_customer(customer): - raise CustodianException( - code=HTTPStatus.BAD_REQUEST, - content=f'Invalid customer: {customer}') - _LOG.debug(f'Customer \'{customer}\' exists') - if not self.cached_iam_service.get_role(customer, role): - raise CustodianException( - code=HTTPStatus.BAD_REQUEST, - content=f'Invalid role name: {role}') - _LOG.debug(f'Role \'{role}\' exists') - _LOG.info(f'Saving user: {username}:{customer}') - self.user_service.save(username=username, password=password, - customer=customer, role=role, tenants=tenants) - return build_response( - code=HTTPStatus.CREATED, - content={PARAM_USERNAME: username, PARAM_CUSTOMER: customer, - PARAM_ROLE: role, PARAM_TENANTS: tenants}) - - def signin_action(self, event): - username = event.get(PARAM_USERNAME) - password = event.get(PARAM_PASSWORD) - if not username or not password: - raise CustodianException( - code=HTTPStatus.BAD_REQUEST, - content='You must specify both username and password') - - _LOG.info('Going to initiate the authentication flow') - auth_result = self.user_service.initiate_auth( - username=username, - password=password) - if not auth_result: - raise CustodianException( - code=HTTPStatus.BAD_REQUEST, - content='Incorrect username and/or password') - - _state = "contains" if auth_result.get( - "ChallengeName") else "does not contain" - _LOG.debug(f'Authentication initiation response ' - f'{_state} the challenge') - if auth_result.get('ChallengeName'): - _LOG.debug(f'Responding to an authentication challenge ' - f'{auth_result.get("ChallengeName")} ') - auth_result = self.user_service.respond_to_auth_challenge( - challenge_name=auth_result['ChallengeName']) - refresh_token = auth_result['AuthenticationResult']['RefreshToken'] - id_token = auth_result['AuthenticationResult']['IdToken'] - - return build_response( - code=HTTPStatus.OK, - content={'id_token': id_token, 'refresh_token': refresh_token, - 'api_version': __version__}) - - def user_delete(self, event: dict) -> dict: - _LOG.info('Delete user event') - user_id = event[PARAM_USER_ID] - if event[USER_CUSTOMER_ATTR] == SYSTEM_CUSTOMER: - validate_params(event, [PARAM_USERNAME]) - _target_user_id = event[PARAM_USERNAME] - if _target_user_id == user_id: - return build_response('SYSTEM user cannot remove himself.') - user_id = _target_user_id - if not self.user_service.is_user_exists(user_id): - return build_response(f'User \'{user_id}\' does not exist', - code=HTTPStatus.NOT_FOUND) - self.user_service.admin_delete_user(user_id) - return build_response(content=f'User \'{user_id}\' has been deleted') - - def user_reset_password(self, event: dict) -> dict: - _LOG.info('Reset user password user') - validate_params(event, [PARAM_PASSWORD]) - user_id = event[PARAM_USER_ID] - if (event[USER_CUSTOMER_ATTR] == SYSTEM_CUSTOMER and - event.get(PARAM_USERNAME)): - user_id = event.get(PARAM_USERNAME) - if not self.user_service.is_user_exists(user_id): - return build_response(f'User \'{user_id}\' does not exist', - code=HTTPStatus.NOT_FOUND) - self.user_service.set_password(user_id, event[PARAM_PASSWORD]) - return build_response(content=f'Password was reset for ' - f'user \'{user_id}\'') - - def event_action(self, event): - # _LOG.debug(f'Event: {event}') # it's too huge - vendor = event[PARAM_VENDOR] - events_in_item = self.environment_service. \ - number_of_native_events_in_event_item() - - _LOG.info('Initializing event processors') - processor = self.event_processor_service.get_processor(vendor) - processor.events = event[PARAM_EVENTS] - n_received = processor.number_of_received() - gen = KeepValueGenerator( - processor.without_duplicates(processor.prepared_events()) - ) - entities = ( - self.event_service.create(events=batch, vendor=vendor) - for batch in batches(gen, events_in_item) - ) - self.event_service.batch_save(entities) - - return build_response( - code=HTTPStatus.CREATED, content={ - 'received': n_received, - 'saved': gen.value - } - ) - - def _validate_custom_core_version(self, required_ruleset_names: list, - customer: str, current_version: str, - cloud: str): - # TODO rewrite - rules = set() - rules_with_newer_version = {} - min_version = None - rulesets = list(self.ruleset_service.list_customer_cloud_rulesets( - customer=customer, cloud=cloud)) - for ruleset in rulesets: - if ruleset.name in required_ruleset_names: - rules.update(ruleset.rules) - _LOG.debug(f'Retrieved following rulesets: {rulesets}') - for rule in rules: - rule_version = self.rules_service.get_rule_version( - customer=customer, rule_id=rule) - if current_version < rule_version: - rules_with_newer_version.update({rule: rule_version}) - if not min_version or rule_version > min_version: - min_version = rule_version - if min_version == '-1' and not rules_with_newer_version: - min_version = current_version - _LOG.debug(f'Found {len(rules_with_newer_version)} rule(s) with newer ' - f'version of custodian custom core: ' - f'{rules_with_newer_version}') - return rules_with_newer_version, min_version - - def _update_job_def(self, ccc_version): - job_def_name = self.environment_service.get_batch_job_def() - last_job_def = self.batch_client.get_job_definition_by_name( - job_def_name=job_def_name) - if not last_job_def: - _LOG.error('Invalid configuration. Last job definition is empty, ' - 'cannot update job definition.') - return build_response( - code=HTTPStatus.INTERNAL_SERVER_ERROR, - content=UNABLE_TO_START_SCAN_ERROR_MESSAGE.format(ccc_version)) - last_job_def = last_job_def[0] - image_url = self._get_image_url(version=ccc_version) - self.batch_client.create_job_definition_from_existing_one( - job_def=last_job_def, image_url=image_url) - - _LOG.debug('Overwriting current custodian custom core version setting') - setting = self.settings_service.create( - name=KEY_CURRENT_CUSTODIAN_CUSTOM_CORE_VERSION, - value=ccc_version) - self.settings_service.save(setting) - - def _get_image_url(self, version): - image_folder_url = self.environment_service.get_image_folder_url() - if not image_folder_url: - _LOG.error('Missing property \'image_folder_url\'') - return build_response( - code=HTTPStatus.INTERNAL_SERVER_ERROR, - content=UNABLE_TO_START_SCAN_ERROR_MESSAGE.format(version)) - - image_url = f'{image_folder_url}:{version}' - _LOG.debug(f'Image URL: {image_url}. Checking image existence') - is_image_exists = self.ecr_client.is_image_with_tag_exists( - tag=version, - repository_name=image_folder_url.split('/')[-1]) - if not is_image_exists: - _LOG.error(f'Image with URL {image_url} does not exist. Unable ' - f'to update job definition.') - return build_response( - code=HTTPStatus.INTERNAL_SERVER_ERROR, - content=UNABLE_TO_START_SCAN_ERROR_MESSAGE.format(version)) - return image_url - - def _update_ccc_version(self, target_rulesets, customer, cloud): - current_ccc_version = self.settings_service.get_current_ccc_version() - min_version = '0' - if self.environment_service.get_feature_update_ccc_version(): - if not current_ccc_version: - _LOG.error('Missing setting CURRENT_CCC_VERSION (current ' - 'custodian custom core version) in the CaaSSettings' - ' table') - return build_response( - code=RESPONSE_SERVICE_UNAVAILABLE_CODE, - content='Application Service is not configured properly: ' - 'missing setting.') - _LOG.debug(f'Current custom core version is ' - f'\'{current_ccc_version}\'') - rules_with_newer_ver, min_version = \ - self._validate_custom_core_version( - required_ruleset_names=target_rulesets, - customer=customer, current_version=current_ccc_version, - cloud=cloud) - if rules_with_newer_ver: - max_version = max(rules_with_newer_ver.values()) - _LOG.warning(f'Custom core is outdated. Need to update it to ' - f'the version \'{max_version}\'. This rules ' - f'require newer version: ' - f'{", ".join(rules_with_newer_ver)}') - self._update_job_def(max_version) - min_version = '0' if not min_version else min_version - return current_ccc_version, min_version - - -API_HANDLER = ApiHandler( - batch_client=SERVICE_PROVIDER.batch(), - modular_service=SERVICE_PROVIDER.modular_service(), - environment_service=SERVICE_PROVIDER.environment_service(), - user_service=SERVICE_PROVIDER.user_service(), - cached_iam_service=SERVICE_PROVIDER.iam_cache_service(), - settings_service=SERVICE_PROVIDER.settings_service(), - ecr_client=SERVICE_PROVIDER.ecr(), - event_service=SERVICE_PROVIDER.event_service(), - event_processor_service=SERVICE_PROVIDER.event_processor_service(), - ruleset_service=SERVICE_PROVIDER.ruleset_service(), - scheduler_service=SERVICE_PROVIDER.scheduler_service() -) +API_HANDLER = ApiHandler.build() -def lambda_handler(event, context): +def lambda_handler(event: dict, context: RequestContext): return API_HANDLER.lambda_handler(event=event, context=context) diff --git a/src/lambdas/custodian_api_handler/handlers/__init__.py b/src/lambdas/custodian_api_handler/handlers/__init__.py index ec36c5519..e69de29bb 100644 --- a/src/lambdas/custodian_api_handler/handlers/__init__.py +++ b/src/lambdas/custodian_api_handler/handlers/__init__.py @@ -1,26 +0,0 @@ -from abc import ABC, abstractmethod -from functools import cached_property -from typing import Dict, Callable - -Mapping = Dict[str, Dict[str, Callable]] - - -# gradual refactoring -class AbstractHandler(ABC): - @classmethod - @abstractmethod - def build(cls) -> 'AbstractHandler': - """ - Builds the instance of the class - """ - - @cached_property - @abstractmethod - def mapping(self) -> Mapping: - """ - { - "path": { - "method": self.handler - } - } - """ diff --git a/src/lambdas/custodian_api_handler/handlers/batch_result_handler.py b/src/lambdas/custodian_api_handler/handlers/batch_result_handler.py index 31d659773..3b2c7508e 100644 --- a/src/lambdas/custodian_api_handler/handlers/batch_result_handler.py +++ b/src/lambdas/custodian_api_handler/handlers/batch_result_handler.py @@ -1,172 +1,72 @@ -from datetime import datetime from functools import cached_property from http import HTTPStatus -from typing import List, Optional, Union -from modular_sdk.models.pynamodb_extension.base_model import \ - LastEvaluatedKey as Lek +from modular_sdk.models.pynamodb_extension.base_model import LastEvaluatedKey as Lek -from helpers import build_response -from helpers.constants import ( - CUSTOMER_ATTR, TENANTS_ATTR, LIMIT_ATTR, NEXT_TOKEN_ATTR, START_ATTR, - END_ATTR, HTTPMethod -) -from helpers.log_helper import get_logger -from lambdas.custodian_api_handler.handlers import AbstractHandler, Mapping +from helpers.constants import CustodianEndpoint, HTTPMethod +from helpers.lambda_response import ResponseFactory, build_response +from handlers import AbstractHandler, Mapping from services import SERVICE_PROVIDER from services.batch_results_service import BatchResultsService - -DEFAULT_UNRESOLVABLE_RESPONSE = 'Request has run into an unresolvable issue.' - -BATCH_RESULTS_ID_ATTR = 'batch_results_id' - -_LOG = get_logger(__name__) - -BATCH_RESULTS_ENDPOINT = '/batch_results' -BATCH_RESULT_ENDPOINT = '/batch_results/{batch_results_id}' +from validators.swagger_request_models import BaseModel, BatchResultsQueryModel +from validators.utils import validate_kwargs class BatchResultsHandler(AbstractHandler): - _code: int - _content: Union[str, dict, list] - _meta: Optional[dict] def __init__(self, batch_results_service: BatchResultsService): self._batch_results_service = batch_results_service - self._reset() @classmethod def build(cls) -> 'BatchResultsHandler': return cls( - batch_results_service=SERVICE_PROVIDER.batch_results_service() + batch_results_service=SERVICE_PROVIDER.batch_results_service ) @cached_property def mapping(self) -> Mapping: return { - BATCH_RESULT_ENDPOINT: { + CustodianEndpoint.BATCH_RESULTS_JOB_ID: { HTTPMethod.GET: self.get }, - BATCH_RESULTS_ENDPOINT: { + CustodianEndpoint.BATCH_RESULTS: { HTTPMethod.GET: self.query } } - @property - def response(self): - _code, _content, _meta = self._code, self._content, self._meta - self._reset() - _LOG.info(f'Going to respond with the following ' - f'code={_code}, content={_content}, meta={_meta}.') - return build_response(code=_code, content=_content, meta=_meta) - - def _reset(self): - self._code: Optional[int] = HTTPStatus.INTERNAL_SERVER_ERROR.value - self._content: Optional[str] = DEFAULT_UNRESOLVABLE_RESPONSE - self._meta: Optional[dict] = None - - def get(self, event: dict): - _LOG.info(f'GET sole BatchResults - {event}.') - brid = event.get(BATCH_RESULTS_ID_ATTR) - customer = event.get(CUSTOMER_ATTR) - tenants = event.get(TENANTS_ATTR) or [] - entity = self._attain_batch_results( - brid=brid, customer=customer, tenants=tenants + @validate_kwargs + def get(self, event: BaseModel, batch_results_id: str): + item = self._batch_results_service.get_nullable(batch_results_id) + if not item or event.customer and item.customer_name != event.customer: + raise ResponseFactory(HTTPStatus.NOT_FOUND).default().exc() + return build_response( + content=self._batch_results_service.dto(item) ) - if entity: - self._code = HTTPStatus.OK.valueT - self._content = self._batch_results_service.dto(entity=entity) - return self.response - def query(self, event: dict): - _LOG.info(f'GET BatchResults - {event}.') - customer = event.get(CUSTOMER_ATTR) - tenants = event.get(TENANTS_ATTR) or [] - - # Lower bound. - start: Optional[datetime] = event.get(START_ATTR) - if start: - start: str = str(start.timestamp()) - - # Upper bound. - end: Optional[datetime] = event.get(END_ATTR) - if end: - end: str = str(end.timestamp()) - - limit = event.get(LIMIT_ATTR) - last_evaluated_key = event.get(NEXT_TOKEN_ATTR) - - i_entities = self._i_attain_batch_results( - customer=customer, tenants=tenants, - start=start, end=end, last_evaluated_key=last_evaluated_key, - limit=limit - ) - - self._code = HTTPStatus.OK.value - self._content = [ - self._batch_results_service.dto(each) for each in i_entities - ] - - new_lek = i_entities.last_evaluated_key - if self._content and new_lek: - self._meta = { - NEXT_TOKEN_ATTR: Lek(new_lek).serialize() - } - return self.response - - def _attain_batch_results(self, brid: str, - customer: Optional[str] = None, - tenants: Optional[List[str]] = None): - """ - Obtains a Batch Results entity, based on a given `brid` partition key, - verifying access, based on a customer and tenant. - :param brid: str - :param customer: Optional[str] - :param tenants: Optional[List[str]] - :return: Optional[BatchResults] - """ - _head = f'BatchResults:\'{brid}\'' - _default_404 = _head + ' does not exist.' - _LOG.info(_head + ' is being obtained.') - entity = self._batch_results_service.get(batch_results=brid) - - if not entity: - _LOG.warning(_default_404) - elif customer and entity.customer_name != customer: - _LOG.warning(_head + f' is not bound to \'{customer}\' customer.') - entity = None - elif tenants and entity.tenant_name not in tenants: - _scope = ', '.join(map("'{}'".format, tenants)) + ' tenant(s)' - _LOG.warning(_head + f' is not bound to any of {_scope}.') - entity = None - - if not entity: - self._code = HTTPStatus.NOT_FOUND.value - self._content = _default_404 - - return entity - - def _i_attain_batch_results( - self, customer: Optional[str] = None, - tenants: Optional[List[str]] = None, - start: Optional[str] = None, end: Optional[str] = None, - limit: Optional[int] = None, - last_evaluated_key: Optional[str] = None - ): - """ - Obtains Batch Result entities, based on a provided customer, tenant - view scope. - :param customer: Optional[str] - :param tenants: Optional[str] - :return: Iterable[BatchResults] - """ - _service = self._batch_results_service - rk_condition = _service.get_registered_scope_condition( - start=start, end=end - ) - return self._batch_results_service.inquery( - customer=customer, tenants=tenants, - ascending=False, range_condition=rk_condition, - last_evaluated_key=last_evaluated_key, - limit=limit - ) + @validate_kwargs + def query(self, event: BatchResultsQueryModel): + old_lek = Lek.deserialize(event.next_token) + new_lek = Lek() + + if event.tenant_name: + cursor = self._batch_results_service.get_by_tenant_name( + tenant_name=event.tenant_name, + limit=event.limit, + last_evaluated_key=old_lek.value, + start=event.start, + end=event.end + ) + else: + cursor = self._batch_results_service.get_by_customer_name( + customer_name=event.customer, + limit=event.limit, + last_evaluated_key=old_lek.value, + start=event.start, + end=event.end + ) + jobs = list(cursor) + new_lek.value = cursor.last_evaluated_key + return ResponseFactory().items( + it=map(self._batch_results_service.dto, jobs), + next_token=new_lek.serialize() if new_lek else None + ).build() diff --git a/src/lambdas/custodian_api_handler/handlers/events_handler.py b/src/lambdas/custodian_api_handler/handlers/events_handler.py new file mode 100644 index 000000000..e3ce0bcec --- /dev/null +++ b/src/lambdas/custodian_api_handler/handlers/events_handler.py @@ -0,0 +1,67 @@ +from functools import cached_property +from http import HTTPStatus + +from helpers import KeepValueGenerator, batches +from helpers.constants import CustodianEndpoint, HTTPMethod +from helpers.lambda_response import build_response +from helpers.log_helper import get_logger +from handlers import AbstractHandler, Mapping +from services import SP +from services.environment_service import EnvironmentService +from services.event_processor_service import EventProcessorService +from services.event_service import EventService +from validators.swagger_request_models import ( + EventPostModel +) +from validators.utils import validate_kwargs + +_LOG = get_logger(__name__) + + +class EventsHandler(AbstractHandler): + def __init__(self, events_service: EventService, + event_processor_service: EventProcessorService, + environment_service: EnvironmentService): + self._es = events_service + self._eps = event_processor_service + self._env = environment_service + + @classmethod + def build(cls) -> 'EventsHandler': + return cls( + events_service=SP.event_service, + event_processor_service=SP.event_processor_service, + environment_service=SP.environment_service + ) + + @cached_property + def mapping(self) -> Mapping: + return { + CustodianEndpoint.EVENT: { + HTTPMethod.POST: self.event_action + } + } + + @validate_kwargs + def event_action(self, event: EventPostModel): + events_in_item = self._env.number_of_native_events_in_event_item() + + _LOG.info('Initializing event processors') + processor = self._eps.get_processor(event.vendor) + processor.events = event.events + n_received = processor.number_of_received() + gen = KeepValueGenerator( + processor.without_duplicates(processor.prepared_events()) + ) + entities = ( + self._es.create(events=batch, vendor=event.vendor) + for batch in batches(gen, events_in_item) + ) + self._es.batch_save(entities) + + return build_response( + code=HTTPStatus.ACCEPTED, content={ + 'received': n_received, + 'saved': gen.value + } + ) diff --git a/src/lambdas/custodian_api_handler/handlers/health_check_handler.py b/src/lambdas/custodian_api_handler/handlers/health_check_handler.py index ca1b0db4d..1d8d725c9 100644 --- a/src/lambdas/custodian_api_handler/handlers/health_check_handler.py +++ b/src/lambdas/custodian_api_handler/handlers/health_check_handler.py @@ -1,21 +1,33 @@ from concurrent.futures import ThreadPoolExecutor, as_completed from functools import cached_property -from typing import List, Type, Dict, Iterable, Generator from http import HTTPStatus -from helpers import build_response -from helpers.constants import ID_ATTR, STATUS_ATTR, CUSTOMER_ATTR, HTTPMethod +from typing import Generator, Iterable + +from helpers.constants import CustodianEndpoint, HTTPMethod +from helpers.lambda_response import build_response from helpers.log_helper import get_logger -from lambdas.custodian_api_handler.handlers import AbstractHandler, Mapping +from handlers import AbstractHandler, Mapping from services import SERVICE_PROVIDER from services.environment_service import EnvironmentService -from services.health_check_service import AbstractHealthCheck, \ - CheckResult, \ - SystemCustomerSettingCheck, LicenseManagerIntegrationCheck, \ - LicenseManagerClientKeyCheck, VaultAuthTokenIsSetCheck, \ - VaultConnectionCheck, AllS3BucketsExist, MongoConnectionCheck, \ - MinioConnectionCheck, ReportDateMarkerSettingCheck, \ - RabbitMQConnectionCheck, EventDrivenRulesetsExist, DefectDojoCheck, \ - RulesMetaAccessDataCheck, RulesMetaCheck +from services.health_check_service import ( + AbstractHealthCheck, + AllS3BucketsExist, + CheckResult, + EventDrivenRulesetsExist, + LicenseManagerClientKeyCheck, + LicenseManagerIntegrationCheck, + MinioConnectionCheck, + MongoConnectionCheck, + RabbitMQConnectionCheck, + ReportDateMarkerSettingCheck, + RulesMetaAccessDataCheck, + RulesMetaCheck, + SystemCustomerSettingCheck, + VaultAuthTokenIsSetCheck, + VaultConnectionCheck, +) +from validators.swagger_request_models import BaseModel, HealthCheckQueryModel +from validators.utils import validate_kwargs _LOG = get_logger(__name__) @@ -27,22 +39,22 @@ def __init__(self, environment_service: EnvironmentService): @classmethod def build(cls) -> 'HealthCheckHandler': return cls( - environment_service=SERVICE_PROVIDER.environment_service() + environment_service=SERVICE_PROVIDER.environment_service ) @cached_property def mapping(self) -> Mapping: return { - '/health': { + CustodianEndpoint.HEALTH: { HTTPMethod.GET: self.list }, - '/health/{id}': { + CustodianEndpoint.HEALTH_ID: { HTTPMethod.GET: self.get } } @cached_property - def on_prem_specific_checks(self) -> List[Type[AbstractHealthCheck]]: + def on_prem_specific_checks(self) -> list[type[AbstractHealthCheck]]: return [ VaultConnectionCheck, VaultAuthTokenIsSetCheck, @@ -51,7 +63,7 @@ def on_prem_specific_checks(self) -> List[Type[AbstractHealthCheck]]: ] @cached_property - def saas_specific_checks(self) -> List[Type[AbstractHealthCheck]]: + def saas_specific_checks(self) -> list[type[AbstractHealthCheck]]: return [ RulesMetaAccessDataCheck, RulesMetaCheck, @@ -59,7 +71,7 @@ def saas_specific_checks(self) -> List[Type[AbstractHealthCheck]]: ] @cached_property - def common_checks(self) -> List[Type[AbstractHealthCheck]]: + def common_checks(self) -> list[type[AbstractHealthCheck]]: return [ SystemCustomerSettingCheck, LicenseManagerIntegrationCheck, @@ -67,11 +79,10 @@ def common_checks(self) -> List[Type[AbstractHealthCheck]]: AllS3BucketsExist, ReportDateMarkerSettingCheck, RabbitMQConnectionCheck, - DefectDojoCheck ] @cached_property - def checks(self) -> List[Type[AbstractHealthCheck]]: + def checks(self) -> list[type[AbstractHealthCheck]]: """ Must return a list of all the necessary checks for the current installation. @@ -88,7 +99,7 @@ def checks(self) -> List[Type[AbstractHealthCheck]]: return commons + extras @cached_property - def identifier_to_instance(self) -> Dict[str, AbstractHealthCheck]: + def identifier_to_instance(self) -> dict[str, AbstractHealthCheck]: return { _class.identifier(): _class.build() for _class in self.checks } @@ -127,27 +138,28 @@ def execute_concurrently(self, checks: Iterable[AbstractHealthCheck], yield future.result() _LOG.info('All the checks have finished') - def list(self, event: dict) -> dict: - status = event.get(STATUS_ATTR) + @validate_kwargs + def list(self, event: HealthCheckQueryModel): + status = event.status it = self.execute_concurrently( self.identifier_to_instance.values(), - customer=event.get(CUSTOMER_ATTR) + customer=event.customer ) if status: it = filter(lambda x: x.status == status, it) it = sorted(it, key=lambda result: result.id) return build_response( - content=(result.dict(exclude_none=True) for result in it) + content=(result.model_dump(exclude_none=True) for result in it) ) - def get(self, event: dict) -> dict: - _id = event.get(ID_ATTR) - instance = self.identifier_to_instance.get(_id) + @validate_kwargs + def get(self, event: BaseModel, id: str): + instance = self.identifier_to_instance.get(id) if not instance: return build_response(code=HTTPStatus.NOT_FOUND, - content=f'Not available check: {_id}') + content=f'Not available check: {id}') result = self._execute_check( instance, - customer=event.get(CUSTOMER_ATTR) + customer=event.customer ) - return build_response(content=result.dict(exclude_none=True)) + return build_response(content=result.model_dump(exclude_none=True)) diff --git a/src/lambdas/custodian_api_handler/handlers/job_handler.py b/src/lambdas/custodian_api_handler/handlers/job_handler.py index 458bbf9e7..f6dda4806 100644 --- a/src/lambdas/custodian_api_handler/handlers/job_handler.py +++ b/src/lambdas/custodian_api_handler/handlers/job_handler.py @@ -2,54 +2,64 @@ from functools import cached_property from http import HTTPStatus from itertools import chain -from typing import Optional, Set, Tuple, List, Iterable +from typing import Iterable from botocore.exceptions import ClientError -from modular_sdk.commons.constants import \ - ParentType from modular_sdk.models.pynamodb_extension.base_model import \ LastEvaluatedKey as Lek from modular_sdk.models.tenant import Tenant - -from helpers import build_response, adjust_cloud -from helpers.constants import TENANT_ATTR, PARAM_TARGET_RULESETS, \ - PARAM_TARGET_REGIONS, CUSTOMER_ATTR, TENANT_LICENSE_KEY_ATTR, \ - BATCH_ENV_SUBMITTED_AT, \ - PARAM_USER_ID, JOB_ID_ATTR, TENANTS_ATTR, LIMIT_ATTR, NEXT_TOKEN_ATTR, \ - CUSTOMER_DISPLAY_NAME_ATTR, JOB_SUCCEEDED_STATUS, JOB_FAILED_STATUS, \ - PARAM_CREDENTIALS, AWS_CLOUD_ATTR, AZURE_CLOUD_ATTR, GOOGLE_CLOUD_ATTR, \ - MULTIREGION, SCHEDULE_ATTR, NAME_ATTR, \ - BATCH_SCHEDULED_JOB_TYPE, ENABLED, GCP_CLOUD_ATTR, RULES_TO_SCAN_ATTR, \ - HTTPMethod -from helpers.enums import RuleDomain +from modular_sdk.services.tenant_service import TenantService + +from helpers import adjust_cloud +from helpers.constants import ( + BatchJobType, + Cloud, + CustodianEndpoint, + GLOBAL_REGION, + HTTPMethod, + JobState, + RuleDomain, +) +from helpers.lambda_response import ResponseFactory, build_response from helpers.log_helper import get_logger from helpers.system_customer import SYSTEM_CUSTOMER -from helpers.time_helper import utc_datetime -from lambdas.custodian_api_handler.handlers import AbstractHandler, Mapping -from models.licenses import License -from models.modular.application import CustodianLicensesApplicationMeta +from handlers import AbstractHandler, Mapping +from models.job import Job from models.ruleset import Ruleset from services import SERVICE_PROVIDER +from services import modular_helpers +from services.abs_lambda import ProcessedEvent from services.assemble_service import AssembleService from services.clients.batch import BatchClient from services.clients.sts import StsClient from services.environment_service import EnvironmentService -from services.job_service import JobService, TenantSettingJobLock +from services.rbac_service import TenantsAccessPayload +from services.job_lock import TenantSettingJobLock +from services.job_service import JobService from services.license_manager_service import LicenseManagerService -from services.license_service import LicenseService -from services.modular_service import ModularService -from services.rule_meta_service import RuleService, RuleNamesResolver +from services.license_service import License, LicenseService +from services.platform_service import PlatformService +from services.rule_meta_service import RuleNamesResolver, RuleService from services.ruleset_service import RulesetService from services.scheduler_service import SchedulerService from services.ssm_service import SSMService -from validators.request_validation import K8sJobPostModel +from validators.swagger_request_models import ( + BaseModel, + JobGetModel, + JobPostModel, + K8sJobPostModel, + ScheduledJobGetModel, + ScheduledJobPatchModel, + ScheduledJobPostModel, + StandardJobPostModel, +) from validators.utils import validate_kwargs _LOG = get_logger(__name__) class JobHandler(AbstractHandler): - def __init__(self, modular_service: ModularService, + def __init__(self, tenant_service: TenantService, environment_service: EnvironmentService, job_service: JobService, license_service: LicenseService, @@ -60,8 +70,9 @@ def __init__(self, modular_service: ModularService, sts_client: StsClient, ssm_service: SSMService, scheduler_service: SchedulerService, - rule_service: RuleService): - self._modular_service = modular_service + rule_service: RuleService, + platform_service: PlatformService): + self._tenant_service = tenant_service self._environment_service = environment_service self._job_service = job_service self._license_service = license_service @@ -73,22 +84,24 @@ def __init__(self, modular_service: ModularService, self._ssm_service = ssm_service self._scheduler_service = scheduler_service self._rule_service = rule_service + self._platform_service = platform_service @classmethod def build(cls) -> 'JobHandler': return cls( - modular_service=SERVICE_PROVIDER.modular_service(), - environment_service=SERVICE_PROVIDER.environment_service(), - job_service=SERVICE_PROVIDER.job_service(), - license_service=SERVICE_PROVIDER.license_service(), - license_manager_service=SERVICE_PROVIDER.license_manager_service(), - ruleset_service=SERVICE_PROVIDER.ruleset_service(), - assemble_service=SERVICE_PROVIDER.assemble_service(), - batch_client=SERVICE_PROVIDER.batch(), - sts_client=SERVICE_PROVIDER.sts_client(), - ssm_service=SERVICE_PROVIDER.ssm_service(), - scheduler_service=SERVICE_PROVIDER.scheduler_service(), - rule_service=SERVICE_PROVIDER.rule_service() + tenant_service=SERVICE_PROVIDER.modular_client.tenant_service(), + environment_service=SERVICE_PROVIDER.environment_service, + job_service=SERVICE_PROVIDER.job_service, + license_service=SERVICE_PROVIDER.license_service, + license_manager_service=SERVICE_PROVIDER.license_manager_service, + ruleset_service=SERVICE_PROVIDER.ruleset_service, + assemble_service=SERVICE_PROVIDER.assemble_service, + batch_client=SERVICE_PROVIDER.batch, + sts_client=SERVICE_PROVIDER.sts, + ssm_service=SERVICE_PROVIDER.ssm_service, + scheduler_service=SERVICE_PROVIDER.scheduler_service, + rule_service=SERVICE_PROVIDER.rule_service, + platform_service=SERVICE_PROVIDER.platform_service ) @cached_property @@ -99,55 +112,51 @@ def mapping(self) -> Mapping: :return: """ return { - '/jobs': { + CustodianEndpoint.JOBS: { HTTPMethod.POST: self.post, HTTPMethod.GET: self.query, }, - '/jobs/standard': { + CustodianEndpoint.JOBS_STANDARD: { HTTPMethod.POST: self.post_standard, }, - '/jobs/k8s': { + CustodianEndpoint.JOBS_K8S: { HTTPMethod.POST: self.post_k8s }, - '/jobs/{job_id}': { + CustodianEndpoint.JOBS_JOB: { HTTPMethod.GET: self.get, HTTPMethod.DELETE: self.delete }, - '/scheduled-job': { + CustodianEndpoint.SCHEDULED_JOB: { HTTPMethod.POST: self.post_scheduled, HTTPMethod.GET: self.query_scheduled, }, - '/scheduled-job/{name}': { + CustodianEndpoint.SCHEDULED_JOB_NAME: { HTTPMethod.GET: self.get_scheduled, HTTPMethod.DELETE: self.delete_scheduled, HTTPMethod.PATCH: self.patch_scheduled } } - def _obtain_tenant(self, tenant_name: str, - customer: Optional[str] = None) -> Tenant: - tenant = self._modular_service.get_tenant(tenant=tenant_name) - if not self._modular_service.is_tenant_valid(tenant, customer): - _message = f'Active tenant `{tenant_name}` not found' - _LOG.info(_message) - return build_response(code=HTTPStatus.NOT_FOUND, - content=_message) + def _obtain_tenant(self, tenant_name: str, tap: TenantsAccessPayload, + customer: str | None = None) -> Tenant: + tenant = self._tenant_service.get(tenant_name) + if tenant and not tap.is_allowed_for(tenant.name): + tenant = None + modular_helpers.assert_tenant_valid(tenant, customer) return tenant - def post_standard(self, event: dict) -> dict: + @validate_kwargs + def post_standard(self, event: StandardJobPostModel, + _tap: TenantsAccessPayload): """ Post job for the given tenant. Only not-licensed rule-sets """ - customer: str = event.get(CUSTOMER_ATTR) - tenant_name: str = event.get(TENANT_ATTR) - target_rulesets: set = event.get(PARAM_TARGET_RULESETS) - target_regions: set = event.get(PARAM_TARGET_REGIONS) - credentials: dict = event.get(PARAM_CREDENTIALS) - tenant = self._obtain_tenant(tenant_name, customer) + tenant = self._obtain_tenant(event.tenant_name, _tap, event.customer) credentials_key = None - if credentials: + if event.credentials: + credentials = event.credentials.dict() if not self._environment_service.skip_cloud_identifier_validation(): _LOG.info('Validating cloud identifier') self._validate_cloud_identifier( @@ -158,50 +167,59 @@ def post_standard(self, event: dict) -> dict: credentials_key = self._ssm_service.save_data( name=tenant.name, value=credentials ) - if tenant.cloud not in self._environment_service.allowed_clouds_to_scan(): - _message = f'Scan for `{tenant.cloud}` is not allowed' - _LOG.info(_message) - return build_response(code=HTTPStatus.FORBIDDEN, - content=_message) + + regions_to_scan = self._resolve_regions_to_scan( + target_regions=event.target_regions, + tenant=tenant + ) + if not self._environment_service.allow_simultaneous_jobs_for_one_tenant(): - lock = TenantSettingJobLock(tenant_name) - if lock.locked(): + lock = TenantSettingJobLock(event.tenant_name) + if job_id := lock.locked_for(regions_to_scan): return build_response( code=HTTPStatus.FORBIDDEN, - content=f'Job {lock.job_id} is already running ' - f'for tenant {tenant_name}' + content=f'Some requested regions are already being ' + f'scanned in another tenant`s job {job_id}' ) - left = self._validate_tenant_last_scan(tenant.name) - if left: - return build_response( - code=HTTPStatus.FORBIDDEN, - content=f'This tenant can be scanned after {left}' - ) - regions_to_scan = self._resolve_regions_to_scan( - target_regions=target_regions, - tenant=tenant - ) ids = list( (item.id, item.name, item.version) for item in - self.retrieve_standard_rulesets(tenant, target_rulesets) + self.retrieve_standard_rulesets(tenant, event.target_rulesets) ) if not ids: return build_response(code=HTTPStatus.NOT_FOUND, content='No standard rule-sets found') - # todo add rules_to_scan here. Currently business does not need this + + ttl_days = self._environment_service.jobs_time_to_live_days() + ttl = None + if ttl_days: + ttl = timedelta(days=ttl_days) + job = self._job_service.create( + customer_name=tenant.customer_name, + tenant_name=tenant.name, + regions=list(regions_to_scan), + rulesets=[f'{i[1]}:{i[2]}' for i in ids], + ttl=ttl + ) + self._job_service.save(job) envs = self._assemble_service.build_job_envs( tenant=tenant, + job_id=job.id, target_regions=list(regions_to_scan), target_rulesets=ids, - credentials_key=credentials_key + credentials_key=credentials_key, + ) + bid = self._submit_job_to_batch(tenant=tenant, job=job, envs=envs) + self._job_service.update(job, bid) + TenantSettingJobLock(tenant.name).acquire(job.id, regions_to_scan) + return build_response( + code=HTTPStatus.CREATED, + content=self._job_service.dto(job) ) - return self._submit_batch_job(event=event, tenant=tenant, envs=envs) - def retrieve_standard_rulesets(self, tenant: Tenant, names: Set[str] + def retrieve_standard_rulesets(self, tenant: Tenant, names: set[str] ) -> Iterable[Ruleset]: cloud = adjust_cloud(tenant.cloud) - assert cloud in (AWS_CLOUD_ATTR, AZURE_CLOUD_ATTR, GCP_CLOUD_ATTR) if names: return chain.from_iterable([ self._ruleset_service.iter_standard( @@ -215,21 +233,60 @@ def retrieve_standard_rulesets(self, tenant: Tenant, names: Set[str] event_driven=False, active=True ) - def post(self, event) -> dict: + def resolve_rulesets(self, licenses: Iterable[License], tenant: Tenant, + domain: RuleDomain, names: set[str] + ) -> dict[str, list[Ruleset]]: """ - Post job for the given tenant. Only licensed rule-sets + Resolves licensed rulesets that will be used for that scan. + Logic here is somewhat tangled, but it should work as expected + :param licenses: + :param tenant: + :param domain: + :param names: + :return: (affected_licenses, licensed_rulesets, rulesets): + - affected_license: [tenant_license_key1, tenant_license_key2] + - licensed_rulesets: ['0:Full AWS 1', '0:Full AWS 1'] """ - customer: str = event[CUSTOMER_ATTR] - tenant_name: str = event.get(TENANT_ATTR) - target_rulesets: set = event.get(PARAM_TARGET_RULESETS) - target_regions: set = event.get(PARAM_TARGET_REGIONS) - credentials: dict = event.get(PARAM_CREDENTIALS) - rules_to_scan = event.get(RULES_TO_SCAN_ATTR) + # TODO tests + mapping = {} # ruleset id to list of tenant license keys + for lic in licenses: + tlk = lic.customers.get(tenant.customer_name, + {}).get('tenant_license_key') + if not tlk: + continue + for _id in lic.ruleset_ids: + mapping.setdefault(_id, []).append(tlk) + + # in case one ruleset is given by multiple tenant license keys, + # we just use the first one, because it is excessive configuration, + # this shouldn't happen + reversed_mapping = {} # tenant_license_key to rulesets + for _id, tenant_license_keys in mapping.items(): + ruleset = self._ruleset_service.by_lm_id(_id) + if not ruleset or ruleset.cloud != domain.value: + continue + if names and ruleset.name not in names: + continue + # ruleset is acceptable + reversed_mapping.setdefault(tenant_license_keys[0], + []).append(ruleset) + return reversed_mapping - tenant = self._obtain_tenant(tenant_name, customer) + @validate_kwargs + def post(self, event: JobPostModel, _tap: TenantsAccessPayload): + """ + Post job for the given tenant. Only licensed rule-sets + """ + tenant = self._obtain_tenant(event.tenant_name, _tap, event.customer) + domain = RuleDomain.from_tenant_cloud(tenant.cloud) + if not domain: + raise ResponseFactory(HTTPStatus.BAD_REQUEST).message( + f'Cannot start job for tenant with cloud {tenant.cloud}' + ).exc() credentials_key = None - if credentials: + if event.credentials: + credentials = event.credentials.dict() if not self._environment_service.skip_cloud_identifier_validation(): _LOG.info('Validating cloud identifier') self._validate_cloud_identifier( @@ -241,80 +298,58 @@ def post(self, event) -> dict: name=tenant.name, value=credentials ) - if tenant.cloud not in self._environment_service.allowed_clouds_to_scan(): - _message = f'Scan for `{tenant.cloud}` is not allowed' - _LOG.info(_message) - return build_response(code=HTTPStatus.FORBIDDEN, - content=_message) + regions_to_scan = self._resolve_regions_to_scan( + target_regions=event.target_regions, + tenant=tenant + ) + if not self._environment_service.allow_simultaneous_jobs_for_one_tenant(): - lock = TenantSettingJobLock(tenant_name) - if lock.locked(): + lock = TenantSettingJobLock(event.tenant_name) + if job_id := lock.locked_for(regions_to_scan): return build_response( code=HTTPStatus.FORBIDDEN, - content=f'Job {lock.job_id} is already running ' - f'for tenant {tenant_name}' + content=f'Some requested regions are already being ' + f'scanned in another tenant`s job {job_id}' ) - left = self._validate_tenant_last_scan(tenant.name) - if left: - return build_response( - code=HTTPStatus.FORBIDDEN, - content=f'This tenant can be scanned after {left}' - ) - - regions_to_scan = self._resolve_regions_to_scan( - target_regions=target_regions, - tenant=tenant - ) - application = self._modular_service.get_tenant_application( - tenant, ParentType.CUSTODIAN_LICENSES + licenses = [ + l[1] for l in self._license_service.iter_tenant_licenses(tenant) + ] + if not licenses: + raise ResponseFactory(HTTPStatus.FORBIDDEN).message( + 'There is no linked licenses for this tenant' + ).exc() + if all([lic.is_expired() for lic in licenses]): + raise ResponseFactory(HTTPStatus.FORBIDDEN).message( + 'All activated licenses have expired' + ).exc() + mapping = self.resolve_rulesets( + licenses=licenses, + tenant=tenant, + domain=domain, + names=event.target_rulesets ) - if not application: - return build_response( - code=HTTPStatus.BAD_REQUEST, - content=f'Custodian application has not been ' - f'linked to tenant: {tenant.name}' - ) - meta = CustodianLicensesApplicationMeta(**application.meta.as_dict()) - license_key = meta.license_key(tenant.cloud) - if not license_key: - return build_response( - code=HTTPStatus.BAD_REQUEST, - content=f'Customer {customer} has not been assigned an ' - f'AWS License yet' - ) - _license = self._license_service.get_license(license_key) - if not _license or self._license_service.is_expired(_license): - return build_response( - code=HTTPStatus.BAD_REQUEST, - content='Affected license has expired' - ) - tenant_license_key = _license.customers.as_dict().get( - customer, {}).get(TENANT_LICENSE_KEY_ATTR) - + _LOG.debug(f'Resolved rulesets mapping: {mapping}') + if not mapping: + raise ResponseFactory(HTTPStatus.FORBIDDEN).message( + 'No appropriate licensed rulesets found for the requests scan' + ).exc() + # currently allow only jobs that exhaust one tenant license + tenant_license_key, rulesets = next(iter(mapping.items())) self.ensure_job_is_allowed(tenant, tenant_license_key) + affected_licenses = [tenant_license_key] + licensed_rulesets = [f'0:{r.license_manager_id}' for r in rulesets] - affected_licensed, licensed_rulesets, rule_sets = self.retrieve_rulesets( - _license=_license, - customer=tenant.customer_name, - cloud=tenant.cloud, - target_rulesets=target_rulesets - ) - if not licensed_rulesets: - # actually is this block is executed, something really wrong - return build_response( - code=HTTPStatus.BAD_REQUEST, - content='No rule-sets found in license' - ) + rules_to_scan = event.rules_to_scan if rules_to_scan: _LOG.info('Rules to scan were provided. Resolving them') - available = set(chain.from_iterable(r.rules for r in rule_sets)) + available = set(chain.from_iterable(r.rules for r in rulesets)) resolver = RuleNamesResolver( resolve_from=list(available), - allow_ambiguous=True + allow_multiple=True ) resolved, not_resolved = [], [] for rule, is_resolved in resolver.resolve_multiple_names( - rules_to_scan): + event.rules_to_scan): if is_resolved: resolved.append(rule) else: @@ -328,90 +363,50 @@ def post(self, event) -> dict: ) rules_to_scan = resolved + ttl_days = self._environment_service.jobs_time_to_live_days() + ttl = None + if ttl_days: + ttl = timedelta(days=ttl_days) + job = self._job_service.create( + customer_name=tenant.customer_name, + tenant_name=tenant.name, + regions=list(regions_to_scan), + rulesets=[r.license_manager_id for r in rulesets], + rules_to_scan=list(rules_to_scan or []), + ttl=ttl + ) + self._job_service.save(job) envs = self._assemble_service.build_job_envs( tenant=tenant, + job_id=job.id, target_regions=list(regions_to_scan), - affected_licenses=affected_licensed, + affected_licenses=affected_licenses, licensed_rulesets=licensed_rulesets, credentials_key=credentials_key ) - return self._submit_batch_job( - event=event, - tenant=tenant, - envs=envs, - rules_to_scan=rules_to_scan + bid = self._submit_job_to_batch(tenant=tenant, job=job, envs=envs) + self._job_service.update(job, bid) + TenantSettingJobLock(tenant.name).acquire(job.id, regions_to_scan) + return build_response( + code=HTTPStatus.CREATED, + content=self._job_service.dto(job) ) - def _submit_batch_job(self, event: dict, tenant: Tenant, envs: dict, - platform_id: Optional[str] = None, - rules_to_scan: Optional[Iterable[str]] = None): - submitted_at = envs.get(BATCH_ENV_SUBMITTED_AT) - job_owner = event.get(PARAM_USER_ID) - job_name = f'{tenant.name}-{job_owner}-{submitted_at}' - job_name = ''.join(ch if ch.isalnum() or ch in {'-', '_'} - else '_' for ch in job_name) + def _submit_job_to_batch(self, tenant: Tenant, job: Job, envs: dict + ) -> str: + job_name = f'{tenant.name}-{job.submitted_at}' + job_name = ''.join( + ch if ch.isalnum() or ch in ('-', '_') else '_' for ch in job_name + ) _LOG.debug(f'Going to submit AWS Batch job with name {job_name}') - response = self._batch_client.submit_job( job_name=job_name, job_queue=self._environment_service.get_batch_job_queue(), job_definition=self._environment_service.get_batch_job_def(), environment_variables=envs, - command=f'python /executor/executor.py' ) - _LOG.debug(f'Batch response: {response}') - if not response: - return build_response(code=HTTPStatus.SERVICE_UNAVAILABLE, - content='AWS Batch failed to respond') - ttl_days = self._environment_service.jobs_time_to_live_days() - ttl = None - if ttl_days: - ttl = timedelta(days=ttl_days) - job = self._job_service.create(dict( - job_id=response['jobId'], - job_owner=job_owner, - tenant_display_name=tenant.name, - customer_display_name=tenant.customer_name, - submitted_at=submitted_at, - ttl=ttl, - platform_id=platform_id, - rules_to_scan=list(rules_to_scan or []) - )) - self._job_service.save(job) - TenantSettingJobLock(tenant.name).acquire(job.job_id) - return build_response( - code=HTTPStatus.CREATED, - content=self._job_service.get_job_dto(job=job) - ) - - def retrieve_rulesets(self, _license: License, customer: str, cloud: str, - target_rulesets: Set[str] - ) -> Tuple[List, List, List[Ruleset]]: - """ - This option is written based on our old logic of rule-sets resolving. - It also checks whether the licensed rule-sets belongs to tenant's - cloud. We don't need this, (according to business logic, each \ - license can have rule-sets that belong to only one cloud), but... - - This option makes more queries to DB, but it actually can be used ) - """ - it = self._ruleset_service.iter_by_lm_id(_license.ruleset_ids) - # cloud filter just in case business logic changes. By default, a - # licensed is not supposed to contain rule-sets from different clouds - it = filter( - lambda ruleset: ruleset.cloud == adjust_cloud(cloud), it - ) - it = filter( - lambda ruleset: ruleset.name in target_rulesets if - target_rulesets else True, it - ) - rule_sets = list(it) - licensed_rulesets = [f'0:{rs.license_manager_id}' for rs in rule_sets] - affected_licenses = [ - _license.customers.as_dict().get(customer, {}).get( - TENANT_LICENSE_KEY_ATTR) - ] - return affected_licenses, licensed_rulesets, rule_sets + _LOG.debug(f'Batch job was submitted: {response}') + return response['jobId'] def ensure_job_is_allowed(self, tenant: Tenant, tlk: str): _LOG.info(f'Going to check for permission to exhaust' @@ -420,301 +415,273 @@ def ensure_job_is_allowed(self, tenant: Tenant, tlk: str): customer=tenant.customer_name, tenant=tenant.name, tenant_license_keys=[tlk]): message = f'Tenant:\'{tenant.name}\' could not be granted ' \ - f'to start a licensed job.' + f'to start a licensed job with tenant license {tlk}' return build_response( content=message, code=HTTPStatus.FORBIDDEN ) _LOG.info(f'Tenant:\'{tenant.name}\' has been granted ' f'permission to submit a licensed job.') - def _resolve_regions_to_scan(self, target_regions: Set[str], - tenant: Tenant) -> Set[str]: - if tenant.cloud == GOOGLE_CLOUD_ATTR: - return {MULTIREGION, } # cannot scan individual regions - tenant_region = self._modular_service.get_tenant_regions(tenant) + def _resolve_regions_to_scan(self, target_regions: set[str], + tenant: Tenant) -> set[str]: + cloud = modular_helpers.tenant_cloud(tenant) + if cloud == Cloud.AZURE or cloud == Cloud.GOOGLE: + return {GLOBAL_REGION, } # cannot scan individual regions + tenant_region = modular_helpers.get_tenant_regions(tenant) missing = target_regions - tenant_region if missing: - return build_response( - code=HTTPStatus.BAD_REQUEST, - content=f'Regions: {", ".join(missing)} not active ' - f'in tenant: {tenant.name}' - ) + raise ResponseFactory(HTTPStatus.BAD_REQUEST).message( + f'Regions: {", ".join(missing)} not active ' + f'in tenant: {tenant.name}' + ).exc() if not target_regions: # all available must be scanned target_regions = tenant_region return target_regions - def _validate_tenant_last_scan(self, tenant_name: str - ) -> Optional[timedelta]: - """ - Gets the latest job by this tenant and validates it against - last_scan_threshold - """ - threshold = self._environment_service.get_last_scan_threshold() - if not threshold: - return - last_job = self._job_service.get_last_tenant_job( - tenant_name, status=[JOB_SUCCEEDED_STATUS] - ) - if not last_job: - return - allowed_after = utc_datetime(_from=last_job.submitted_at) + timedelta( - seconds=threshold) - now = utc_datetime() - if allowed_after < now: - return - return allowed_after - now - - def query(self, event: dict) -> dict: - customer = event.get(CUSTOMER_ATTR) - tenants = event.get(TENANTS_ATTR) - limit = event.get(LIMIT_ATTR) - - old_lek = Lek.deserialize(event.get(NEXT_TOKEN_ATTR) or None) + @validate_kwargs + def query(self, event: JobGetModel): + old_lek = Lek.deserialize(event.next_token) new_lek = Lek() - _LOG.info('Job id was not given. Filtering jobs by other params') - cursor = self._job_service.list( - tenants, customer, limit=limit, lek=old_lek.value) - entities = list(cursor) - new_lek.value = cursor.last_evaluated_key - _exclude = set() - if customer: - _exclude.add(CUSTOMER_DISPLAY_NAME_ATTR) - return build_response( - content=( - self._job_service.get_job_dto(job, _exclude) - for job in entities - ), - meta={NEXT_TOKEN_ATTR: new_lek.serialize()} if new_lek else None - ) - def get(self, event) -> dict: - job_id = event.get(JOB_ID_ATTR) - customer = event.get(CUSTOMER_ATTR) - tenants = event.get(TENANTS_ATTR) - _LOG.info('Job id was given, querying using it') - job = self._job_service.get_job(job_id) - if not job or not self._job_service.is_allowed(job, customer, tenants): - jobs = [] # todo 404 would be better, but historically it's an empty list + if event.tenant_name: + cursor = self._job_service.get_by_tenant_name( + tenant_name=event.tenant_name, + status=event.status, + limit=event.limit, + start=event.start_iso, + end=event.end_iso, + last_evaluated_key=old_lek.value, + ) else: - jobs = [job, ] - return build_response( - content=(self._job_service.get_job_dto(j) for j in jobs) - ) + cursor = self._job_service.get_by_customer_name( + customer_name=event.customer, + status=event.status, + limit=event.limit, + start=event.start_iso, + end=event.end_iso, + last_evaluated_key=old_lek.value + ) + jobs = list(cursor) + new_lek.value = cursor.last_evaluated_key + return ResponseFactory().items( + it=map(self._job_service.dto, jobs), + next_token=new_lek.serialize() if new_lek else None + ).build() - def delete(self, event) -> dict: - job_id = event.get(JOB_ID_ATTR) - customer = event.get(CUSTOMER_ATTR) - tenants = event.get(TENANTS_ATTR) - user = event.get(PARAM_USER_ID) + @validate_kwargs + def get(self, event: BaseModel, job_id: str): + _LOG.info('Job id was given, querying using it') + job = self._job_service.get_nullable(job_id) + if not job or event.customer and job.customer_name != event.customer: + raise ResponseFactory(HTTPStatus.NOT_FOUND).message( + self._job_service.not_found_message(job_id) + ).exc() + return build_response(content=self._job_service.dto(job)) - job = self._job_service.get_job(job_id) - if not job or not self._job_service.is_allowed(job, customer, tenants): + @validate_kwargs + def delete(self, event: BaseModel, job_id: str, _pe: ProcessedEvent): + job = self._job_service.get_nullable(job_id) + if not job or event.customer and job.customer_name != event.customer: return build_response( code=HTTPStatus.NOT_FOUND, - content=f'Job with id \'{job_id}\' was not found' + content=self._job_service.not_found_message(job_id) ) - - if job.status == JOB_SUCCEEDED_STATUS or \ - job.status == JOB_FAILED_STATUS: + if job.status in (JobState.SUCCEEDED, JobState.FAILED): message = f'Can not terminate job with status {job.status}' _LOG.warning(message) return build_response(content=message, code=HTTPStatus.BAD_REQUEST) - reason = f'Initiated by user \'{user}\' ' \ - f'(customer \'{customer or SYSTEM_CUSTOMER}\')' - self._job_service.set_job_failed_status(job, reason) - self._job_service.save(job) - TenantSettingJobLock(job.tenant_display_name).release() + user_id = _pe['cognito_username'] + reason = f'Initiated by user \'{user_id}\' ' \ + f'(customer \'{event.customer or SYSTEM_CUSTOMER}\')' + + self._job_service.update( + job=job, + reason=reason, + status=JobState.FAILED + ) + TenantSettingJobLock(job.tenant_name).release(job.id) - _LOG.info(f"Going to terminate job with id '{job_id}'") + _LOG.info(f"Going to terminate job with id '{job.id}'") self._batch_client.terminate_job( - job_id=job_id, + job_id=job.batch_job_id, reason=reason ) # reason is just for AWS BatchClient here return build_response( - content=f'The job with id \'{job_id}\' will be terminated' + code=HTTPStatus.ACCEPTED, + content=f'The job with id \'{job.id}\' will is terminated' ) @validate_kwargs - def post_k8s(self, event: K8sJobPostModel) -> dict: - ps = self._modular_service.modular_client.parent_service() - platform = ps.get_parent_by_id(event.platform_id) - if not platform or platform.is_deleted or event.customer and platform.customer_id != event.customer: + def post_k8s(self, event: K8sJobPostModel, _tap: TenantsAccessPayload): + platform = self._platform_service.get_nullable( + hash_key=event.platform_id) + if not platform or ( + event.customer and platform.customer != event.customer): return build_response( code=HTTPStatus.NOT_FOUND, content=f'Active platform: {event.platform_id} not found' ) - tenant = self._obtain_tenant(platform.tenant_name, event.customer) - customer = tenant.customer_name + tenant = self._obtain_tenant(platform.tenant_name, _tap, + event.customer) if not self._environment_service.allow_simultaneous_jobs_for_one_tenant(): lock = TenantSettingJobLock(tenant.name) - if lock.locked(): + if job_id := lock.locked_for({platform.platform_id}): return build_response( code=HTTPStatus.FORBIDDEN, - content=f'Job {lock.job_id} is already running ' + content=f'Job {job_id} is already running ' f'for tenant {tenant.name}' ) - application = self._modular_service.get_tenant_application( - tenant, ParentType.CUSTODIAN_LICENSES + licenses = [ + l[1] for l in self._license_service.iter_tenant_licenses(tenant) + ] + if not licenses: + raise ResponseFactory(HTTPStatus.FORBIDDEN).message( + 'There is no linked licenses for this tenant' + ).exc() + if all([lic.is_expired() for lic in licenses]): + raise ResponseFactory(HTTPStatus.FORBIDDEN).message( + 'All activated licenses have expired' + ).exc() + mapping = self.resolve_rulesets( + licenses=licenses, + tenant=tenant, + domain=RuleDomain.KUBERNETES, + names=event.target_rulesets ) - if not application: - return build_response( - code=HTTPStatus.BAD_REQUEST, - content=f'Custodian application has not been ' - f'linked to tenant: {tenant.name}' - ) - meta = CustodianLicensesApplicationMeta(**application.meta.as_dict()) - license_key = meta.license_key(RuleDomain.KUBERNETES.value) - if not license_key: - return build_response( - code=HTTPStatus.BAD_REQUEST, - content=f'Customer {customer} has not been assigned an ' - f'KUBERNETES License yet' - ) - _license = self._license_service.get_license(license_key) - if not _license or self._license_service.is_expired(_license): - return build_response( - code=HTTPStatus.BAD_REQUEST, - content='Affected license has expired' - ) - tenant_license_key = _license.customers.as_dict().get( - customer, {}).get(TENANT_LICENSE_KEY_ATTR) - + _LOG.debug(f'Resolved rulesets mapping: {mapping}') + if not mapping: + raise ResponseFactory(HTTPStatus.FORBIDDEN).message( + 'No appropriate licensed rulesets found for the requests scan' + ).exc() + # currently allow only jobs that exhaust one tenant license + tenant_license_key, rulesets = next(iter(mapping.items())) self.ensure_job_is_allowed(tenant, tenant_license_key) + affected_licenses = [tenant_license_key] + licensed_rulesets = [f'0:{r.license_manager_id}' for r in rulesets] - affected_licensed, licensed_rulesets, rule_sets = self.retrieve_rulesets( - _license=_license, - customer=customer, - cloud=RuleDomain.KUBERNETES.value, - target_rulesets=event.target_rulesets, - ) - if not licensed_rulesets: - # actually is this block is executed, something really wrong - return build_response( - code=HTTPStatus.BAD_REQUEST, - content='No rule-sets found in license' - ) credentials_key = None # TODO K8S validate whether long-lived token exists, validate whether it belongs to a cluster? if event.token: _LOG.debug('Temp token was provided. Saving to ssm') credentials_key = self._ssm_service.save_data( name=tenant.name, value=event.token ) + ttl_days = self._environment_service.jobs_time_to_live_days() + ttl = None + if ttl_days: + ttl = timedelta(days=ttl_days) + job = self._job_service.create( + customer_name=tenant.customer_name, + tenant_name=tenant.name, + regions=[], + rulesets=[r.license_manager_id for r in rulesets], + ttl=ttl, + platform_id=platform.id + ) + self._job_service.save(job) envs = self._assemble_service.build_job_envs( tenant=tenant, - platform_id=platform.parent_id, - affected_licenses=affected_licensed, + job_id=job.id, + platform_id=platform.id, + affected_licenses=affected_licenses, licensed_rulesets=licensed_rulesets, credentials_key=credentials_key ) - return self._submit_batch_job( - event=event.dict(), - tenant=tenant, - envs=envs, - platform_id=platform.parent_id, + bid = self._submit_job_to_batch(tenant=tenant, job=job, envs=envs) + self._job_service.update(job, bid) + TenantSettingJobLock(tenant.name).acquire(job.id, + {platform.platform_id}) + return build_response( + code=HTTPStatus.CREATED, + content=self._job_service.dto(job) ) - def post_scheduled(self, event: dict) -> dict: - _LOG.info(f'Post scheduled-job action: {event}') - customer = event[CUSTOMER_ATTR] - schedule = event[SCHEDULE_ATTR] - target_rulesets: set = event.get(PARAM_TARGET_RULESETS) - target_regions: set = event.get(PARAM_TARGET_REGIONS) - name = event.get(NAME_ATTR) - - tenant = self._obtain_tenant(event.get(TENANT_ATTR), customer) - if tenant.cloud not in self._environment_service.allowed_clouds_to_scan(): - _message = f'Scan for `{tenant.cloud}` is not allowed' - _LOG.info(_message) - return build_response(code=HTTPStatus.FORBIDDEN, - content=_message) + @validate_kwargs + def post_scheduled(self, event: ScheduledJobPostModel, + _tap: TenantsAccessPayload): + tenant = self._obtain_tenant(event.tenant_name, _tap, event.customer) + domain = RuleDomain.from_tenant_cloud(tenant.cloud) + if not domain: + raise ResponseFactory(HTTPStatus.BAD_REQUEST).message( + f'Cannot start job for tenant with cloud {tenant.cloud}' + ).exc() # the same flow as for not scheduled jobs regions_to_scan = self._resolve_regions_to_scan( - target_regions=target_regions, + target_regions=event.target_regions, tenant=tenant ) - application = self._modular_service.get_tenant_application( - tenant, ParentType.CUSTODIAN_LICENSES - ) - if not application: - return build_response( - code=HTTPStatus.BAD_REQUEST, - content=f'Custodian application has not been ' - f'activated for customer: {customer}' - ) - meta = CustodianLicensesApplicationMeta(**application.meta.as_dict()) - license_key = meta.license_key(tenant.cloud) - if not license_key: - return build_response( - code=HTTPStatus.BAD_REQUEST, - content=f'Customer {customer} has not been assigned an ' - f'AWS License yet' - ) - _license = self._license_service.get_license(license_key) - if self._license_service.is_expired(_license): - return build_response( - code=HTTPStatus.BAD_REQUEST, - content='Affected license has expired' - ) - affected_licensed, licensed_rulesets, _ = self.retrieve_rulesets( - _license=_license, - customer=tenant.customer_name, - cloud=tenant.cloud, - target_rulesets=target_rulesets + licenses = [ + l[1] for l in self._license_service.iter_tenant_licenses(tenant) + ] + if not licenses: + raise ResponseFactory(HTTPStatus.FORBIDDEN).message( + 'There is no linked licenses for this tenant' + ).exc() + if all([lic.is_expired() for lic in licenses]): + raise ResponseFactory(HTTPStatus.FORBIDDEN).message( + 'All activated licenses have expired' + ).exc() + mapping = self.resolve_rulesets( + licenses=licenses, + tenant=tenant, + domain=domain, + names=event.target_rulesets ) - if not licensed_rulesets: - # actually is this block is executed, something really wrong - return build_response( - code=HTTPStatus.BAD_REQUEST, - content='No rule-sets found in license' - ) + if not mapping: + raise ResponseFactory(HTTPStatus.FORBIDDEN).message( + 'No appropriate licensed rulesets found for the requests scan' + ).exc() + # currently allow only jobs that exhaust one tenant license + tenant_license_key, rulesets = next(iter(mapping.items())) + self.ensure_job_is_allowed(tenant, tenant_license_key) + affected_licenses = [tenant_license_key] + licensed_rulesets = [f'0:{r.license_manager_id}' for r in rulesets] + envs = self._assemble_service.build_job_envs( tenant=tenant, target_regions=list(regions_to_scan), - affected_licenses=affected_licensed, + affected_licenses=affected_licenses, licensed_rulesets=licensed_rulesets, - job_type=BATCH_SCHEDULED_JOB_TYPE + job_type=BatchJobType.SCHEDULED ) + # MOVE TODO fix terminate for scheduled job = self._scheduler_service.register_job( - tenant, schedule, envs, name + tenant, event.schedule, envs, event.name ) return build_response( code=HTTPStatus.CREATED, content=self._scheduler_service.dto(job) ) - def query_scheduled(self, event: dict) -> dict: - customer = event.get(CUSTOMER_ATTR) - tenants: list = event.get(TENANTS_ATTR) - + @validate_kwargs + def query_scheduled(self, event: ScheduledJobGetModel): + tenants = set() + if event.tenant_name: + tenants.add(event.tenant_name) items = self._scheduler_service.list( - customer=customer, tenants=set(tenants) + customer=event.customer, + tenants=tenants ) return build_response(content=( self._scheduler_service.dto(item) for item in items )) - def get_scheduled(self, event: dict) -> dict: - name = event[NAME_ATTR] - tenants: list = event.get(TENANTS_ATTR) - customer = event.get(CUSTOMER_ATTR) - items = self._scheduler_service.list( - name=name, customer=customer, tenants=set(tenants) - ) - return build_response(content=( - self._scheduler_service.dto(item) for item in items - )) + @validate_kwargs + def get_scheduled(self, event: BaseModel, name: str): + item = next(self._scheduler_service.list( + name=name, customer=event.customer + ), None) + if not item: + raise ResponseFactory(HTTPStatus.NOT_FOUND).default().exc() + return build_response(content=self._scheduler_service.dto(item)) - def delete_scheduled(self, event: dict) -> dict: - _LOG.info(f'Delete scheduled-job action: {event}') - name = event[NAME_ATTR] - customer = event.get(CUSTOMER_ATTR) - tenants: list = event.get(TENANTS_ATTR) + @validate_kwargs + def delete_scheduled(self, event: BaseModel, name: str): - item = self._scheduler_service.get(name, customer, set(tenants)) + item = self._scheduler_service.get(name, event.customer) if not item: return build_response( code=HTTPStatus.NOT_FOUND, @@ -723,15 +690,13 @@ def delete_scheduled(self, event: dict) -> dict: self._scheduler_service.deregister_job(name) return build_response(code=HTTPStatus.NO_CONTENT) - def patch_scheduled(self, event: dict) -> dict: - _LOG.info(f'Update scheduled-job action: {event}') - name = event[NAME_ATTR] - is_enabled = event.get(ENABLED) - customer = event.get(CUSTOMER_ATTR) - schedule = event.get(SCHEDULE_ATTR) - tenants: list = event.get(TENANTS_ATTR) + @validate_kwargs + def patch_scheduled(self, event: ScheduledJobPatchModel, name: str): + is_enabled = event.enabled + customer = event.customer + schedule = event.schedule - item = self._scheduler_service.get(name, customer, set(tenants)) + item = self._scheduler_service.get(name, customer) if not item: return build_response( code=HTTPStatus.NOT_FOUND, @@ -744,11 +709,11 @@ def patch_scheduled(self, event: dict) -> dict: return build_response(content=self._scheduler_service.dto(item)) def _validate_cloud_identifier(self, cloud_identifier: int, - credentials: dict, cloud: str): + credentials: dict, cloud: Cloud): identifier_validators_mapping = { - AWS_CLOUD_ATTR: self._validate_aws_account_id, - AZURE_CLOUD_ATTR: None, - GOOGLE_CLOUD_ATTR: self._validate_gcp_project_id + Cloud.AWS: self._validate_aws_account_id, + Cloud.AZURE: None, + Cloud.GOOGLE: self._validate_gcp_project_id } validator = identifier_validators_mapping.get(cloud) if not validator: diff --git a/src/lambdas/custodian_api_handler/handlers/metrics_status_handler.py b/src/lambdas/custodian_api_handler/handlers/metrics_status_handler.py index d019baca5..eaaa70ecc 100644 --- a/src/lambdas/custodian_api_handler/handlers/metrics_status_handler.py +++ b/src/lambdas/custodian_api_handler/handlers/metrics_status_handler.py @@ -1,52 +1,78 @@ +from datetime import date, datetime from functools import cached_property from modular_sdk.models.job import Job +from modular_sdk.modular import Modular -from helpers import build_response -from helpers.constants import HTTPMethod +from helpers.constants import CustodianEndpoint, HTTPMethod +from helpers.lambda_response import build_response from helpers.log_helper import get_logger -from lambdas.custodian_api_handler.handlers import Mapping +from handlers import AbstractHandler, Mapping from services import SERVICE_PROVIDER from services.environment_service import EnvironmentService -from services.modular_service import ModularService +from validators.swagger_request_models import MetricsStatusGetModel +from validators.utils import validate_kwargs _LOG = get_logger(__name__) -class MetricsStatusHandler: - def __init__(self, modular_service: ModularService, +class MetricsStatusHandler(AbstractHandler): + def __init__(self, modular_client: Modular, environment_service: EnvironmentService): - self.modular_service = modular_service + self.modular_client = modular_client self.environment_service = environment_service @classmethod def build(cls) -> 'MetricsStatusHandler': return cls( - modular_service=SERVICE_PROVIDER.modular_service(), - environment_service=SERVICE_PROVIDER.environment_service() + modular_client=SERVICE_PROVIDER.modular_client, + environment_service=SERVICE_PROVIDER.environment_service ) @cached_property def mapping(self) -> Mapping: return { - '/metrics/status': { + CustodianEndpoint.METRICS_STATUS: { HTTPMethod.GET: self.get } } - def get(self, event: dict) -> dict: - component_name = self.environment_service.component_name() - item = list(Job.job_started_at_index.query( - hash_key=component_name, limit=1, - scan_index_forward=False)) - if not item: - _msg = 'Cannot find latest metrics update job' - _LOG.error(_msg + f' with component name: {component_name}') - return build_response(_msg) - item = item[0] - started_at = item.attribute_values.get("started_at") - state = item.attribute_values.get("state") - _LOG.debug(f'Retrieved ModularJobs item: {item.to_json()}') - return build_response( - content=f'Last metrics update was started at {started_at} with ' - f'status {state}') + @validate_kwargs + def get(self, event: MetricsStatusGetModel): + from_ = event.start_iso + to = event.end_iso + rkc = None + component_name = (self.modular_client. + environment_service().component()) + + if from_ and to: + rkc = (Job.started_at.between(from_, to)) + elif from_: + rkc = (Job.started_at >= from_) + elif to: + rkc = (Job.started_at < to) + _LOG.debug(f'Range key condition: {rkc}') + + # TODO api add job_service with corresponding methods + items = list(Job.job_started_at_index.query( + hash_key=component_name, limit=1 if rkc is None else 10, + range_key_condition=rkc, scan_index_forward=False)) + + if not items: + _LOG.error(f'Cannot find metrics update job with component name: ' + f'{component_name}') + response = [] + for item in items: + _LOG.debug(f'Retrieved ModularJobs item: {item.to_json()}') + response.append(self.get_metrics_status_dto(item)) + return build_response(content=response) + + @staticmethod + def get_metrics_status_dto(item: Job): + started_at = item.started_at + if isinstance(started_at, datetime | date): + started_at = started_at.isoformat(sep=' ', timespec="seconds") + return { + 'started_at': started_at, + 'state': item.state + } diff --git a/src/lambdas/custodian_api_handler/handlers/new_swagger_handler.py b/src/lambdas/custodian_api_handler/handlers/new_swagger_handler.py new file mode 100644 index 000000000..3cb315419 --- /dev/null +++ b/src/lambdas/custodian_api_handler/handlers/new_swagger_handler.py @@ -0,0 +1,98 @@ +from functools import cached_property +from http import HTTPStatus +from typing import TYPE_CHECKING + +from helpers import urljoin +from helpers.__version__ import __version__ +from helpers.constants import CustodianEndpoint, HTTPMethod +from helpers.lambda_response import LambdaResponse, ResponseFactory +from helpers.log_helper import get_logger +from handlers import AbstractHandler, Mapping +from services import SP +from services.openapi_spec_generator import OpenApiGenerator +from validators.registry import iter_all + +if TYPE_CHECKING: + from services.environment_service import EnvironmentService + +_LOG = get_logger(__name__) + +SWAGGER_HTML = \ +""" + + + + + + + SwaggerUI + + + +
+ + + + + +""" + + +class SwaggerHandler(AbstractHandler): + def __init__(self, environment_service: 'EnvironmentService'): + self._env = environment_service + + @classmethod + def build(cls) -> 'SwaggerHandler': + return cls(environment_service=SP.environment_service) + + @cached_property + def mapping(self) -> Mapping: + return { + CustodianEndpoint.DOC: { + HTTPMethod.GET: self.get + }, + CustodianEndpoint.DOC_SWAGGER_JSON: { + HTTPMethod.GET: self.get_spec + } + } + + def get(self, event: dict): + out = SWAGGER_HTML.format( + version='latest', # get from env? + url=urljoin( + '/', + self._env.api_gateway_stage(), + CustodianEndpoint.DOC_SWAGGER_JSON.value + ) + ) + return LambdaResponse( + code=HTTPStatus.OK, + content=out, + headers={'Content-Type': 'text/html'} + ).build() + + def get_spec(self, event: dict): + _LOG.debug('Returning openapi spec') + spec = OpenApiGenerator( + title='Rule Engine - OpenAPI 3.0', + description='Rule engine rest api', + url=f'https://{self._env.api_gateway_host()}', + stages=self._env.api_gateway_stage(), + version=__version__, + endpoints=iter_all() + ).generate() + _LOG.debug('Open api spec was generated') + return ResponseFactory().raw(spec).build() diff --git a/src/lambdas/custodian_api_handler/handlers/users_handler.py b/src/lambdas/custodian_api_handler/handlers/users_handler.py new file mode 100644 index 000000000..be1d068f8 --- /dev/null +++ b/src/lambdas/custodian_api_handler/handlers/users_handler.py @@ -0,0 +1,253 @@ +from functools import cached_property +from http import HTTPStatus +from typing import cast + +from botocore.exceptions import ClientError +from modular_sdk.models.customer import Customer +from modular_sdk.services.customer_service import CustomerService + +from handlers import AbstractHandler, Mapping +from helpers import NextToken +from helpers.constants import CustodianEndpoint, HTTPMethod, Permission +from helpers.lambda_response import ResponseFactory, build_response +from helpers.log_helper import get_logger +from helpers.time_helper import utc_datetime +from models.policy import PolicyEffect +from services import SP +from services.abs_lambda import ProcessedEvent +from services.clients.cognito import BaseAuthClient, UserWrapper +from services.rbac_service import PolicyService, RoleService +from validators.swagger_request_models import ( + BaseModel, + BasePaginationModel, + RefreshPostModel, + SignInPostModel, + SignUpModel, + UserPatchModel, + UserPostModel, + UserResetPasswordModel, +) +from validators.utils import validate_kwargs + +_LOG = get_logger(__name__) + + +class UsersHandler(AbstractHandler): + def __init__(self, user_client: BaseAuthClient, + customer_service: CustomerService, + role_service: RoleService, policy_service: PolicyService): + self._user_client = user_client + self._cs = customer_service + self._rs = role_service + self._ps = policy_service + + @classmethod + def build(cls) -> 'UsersHandler': + return cls( + user_client=SP.users_client, + customer_service=SP.modular_client.customer_service(), + role_service=SP.role_service, + policy_service=SP.policy_service + ) + + @cached_property + def mapping(self) -> Mapping: + return { + CustodianEndpoint.USERS_RESET_PASSWORD: { + HTTPMethod.POST: self.reset_password + }, + CustodianEndpoint.USERS_WHOAMI: { + HTTPMethod.GET: self.whoami + }, + CustodianEndpoint.USERS: { + HTTPMethod.GET: self.query, + HTTPMethod.POST: self.post + }, + CustodianEndpoint.USERS_USERNAME: { + HTTPMethod.GET: self.get, + HTTPMethod.PATCH: self.patch, + HTTPMethod.DELETE: self.delete + }, + CustodianEndpoint.SIGNUP: { + HTTPMethod.POST: self.signup + }, + CustodianEndpoint.SIGNIN: { + HTTPMethod.POST: self.signin + }, + CustodianEndpoint.REFRESH: { + HTTPMethod.POST: self.refresh, + }, + } + + @validate_kwargs + def query(self, event: BasePaginationModel): + cursor = self._user_client.query_users( + customer=event.customer, + limit=event.limit, + next_token=NextToken.deserialize(event.next_token).value + ) + items = list(cursor) + return ResponseFactory().items( + it=(i.get_dto() for i in items), + next_token=NextToken(cursor.next_token) + ).build() + + @validate_kwargs + def get(self, event: BaseModel, username: str): + item = self._user_client.get_user_by_username( + username + ) + if not item or event.customer and item.customer != event.customer: + raise ResponseFactory(HTTPStatus.NOT_FOUND).message( + 'User not found' + ).exc() + return build_response(item.get_dto()) + + @validate_kwargs + def post(self, event: UserPostModel): + if self._user_client.does_user_exist(event.username): + raise ResponseFactory(HTTPStatus.CONFLICT).message( + f'User with such username already exists' + ).exc() + if not self._rs.get_nullable(event.customer_id, event.role_name): + raise ResponseFactory(HTTPStatus.BAD_REQUEST).message( + f'Role {event.role_name} not found in customer ' + f'{event.customer_id}' + ).exc() + + user = self._user_client.signup_user( + username=event.username, + password=event.password, + customer=event.customer_id, + role=event.role_name + ) + # seems like we need this additional step for cognito + self._user_client.set_user_password(event.username, event.password) + return build_response(user.get_dto()) + + @validate_kwargs + def patch(self, event: UserPatchModel, username: str): + item = self._user_client.get_user_by_username(username) + if not item or event.customer_id and item.customer != event.customer_id: + raise ResponseFactory(HTTPStatus.NOT_FOUND).message( + 'User not found' + ).exc() + params = dict() + if event.role_name: + params['role'] = event.role_name + item.role = event.role_name + to_update = UserWrapper( + username=username, + **params + ) + self._user_client.update_user_attributes(to_update) + if event.password: + _LOG.info('Password was provided. Updating user password') + self._user_client.set_user_password(username, event.password) + + return build_response(item.get_dto()) + + @validate_kwargs + def delete(self, event: BaseModel, username: str): + user = self._user_client.get_user_by_username(username) + if not user or event.customer_id and user.customer != event.customer_id: + return build_response(code=HTTPStatus.NO_CONTENT) + # users exists and it belongs to this customer + self._user_client.delete_user(username) + return build_response(code=HTTPStatus.NO_CONTENT) + + @validate_kwargs + def whoami(self, event: BaseModel, _pe: ProcessedEvent): + username = cast(str, _pe['cognito_username']) + _LOG.debug(f'Getting user by username {username}') + item = self._user_client.get_user_by_username(username) + assert item, 'Something strange happening, probably a bug' + return build_response(item.get_dto()) + + @validate_kwargs + def signin(self, event: SignInPostModel): + username = event.username + password = event.password + _LOG.info('Going to initiate the authentication flow') + auth_result = self._user_client.authenticate_user( + username=username, + password=password + ) + if not auth_result: + raise ResponseFactory(HTTPStatus.UNAUTHORIZED).message( + 'Incorrect username and/or password' + ).exc() + self._user_client.update_user_attributes(UserWrapper( + username=event.username, + latest_login=utc_datetime() + )) + + return ResponseFactory().raw({ + 'access_token': auth_result['id_token'], + 'refresh_token': auth_result['refresh_token'], + 'expires_in': auth_result['expires_in'] + }).build() + + @validate_kwargs + def signup(self, event: SignUpModel): + if self._cs.get(event.customer_name): + raise ResponseFactory(HTTPStatus.CONFLICT).message( + f'Customer {event.customer_name} already exists' + ).exc() + customer = Customer( + name=event.customer_name, + display_name=event.customer_display_name, + admins=list(event.customer_admins), + is_active=True + ) + policy = self._ps.create( + customer=event.customer_name, + name='admin_policy', + permissions=sorted([i.value for i in Permission.iter_enabled()]), + description='Auto-created policy for newly signed up user', + effect=PolicyEffect.ALLOW, + tenants=('*', ) + ) + role = self._rs.create( + customer=event.customer_name, + name='admin_role', + expiration=None, + policies=('admin_policy', ), + description='Auto-created role for newly signed up user' + ) + try: + customer.save() + except ClientError: + _LOG.warning('Cannot save customer. Probably no permissions.', + exc_info=True) + raise ResponseFactory(HTTPStatus.FORBIDDEN).message( + 'Cannot create a new user. Please, contact support' + ).exc() + self._rs.save(role) + self._ps.save(policy) + self._user_client.signup_user( + username=event.username, + password=event.password, + role='admin_role', + customer=event.customer_name + ) + _LOG.debug(f'Saving user: {event.username}') + return build_response(content=f'The user {event.username} was created') + + @validate_kwargs + def refresh(self, event: RefreshPostModel): + auth_result = self._user_client.refresh_token(event.refresh_token) + if not auth_result: + raise ResponseFactory(HTTPStatus.UNAUTHORIZED).default().exc() + return ResponseFactory().raw({ + 'access_token': auth_result['id_token'], + 'refresh_token': auth_result['refresh_token'], + 'expires_in': auth_result['expires_in'] + }).build() + + @validate_kwargs + def reset_password(self, event: UserResetPasswordModel, + _pe: ProcessedEvent): + username = cast(str, _pe['cognito_username']) + self._user_client.set_user_password(username, event.new_password) + return build_response(code=HTTPStatus.NO_CONTENT) diff --git a/src/lambdas/custodian_api_handler/lambda_config.json b/src/lambdas/custodian_api_handler/lambda_config.json index 6b761172a..ef667e9c4 100644 --- a/src/lambdas/custodian_api_handler/lambda_config.json +++ b/src/lambdas/custodian_api_handler/lambda_config.json @@ -8,6 +8,7 @@ "memory": 512, "timeout": 100, "lambda_path": "/lambdas/custodian_api_handler", + "logs_expiration": "${logs_expiration}", "dependencies": [ { "resource_name": "CaaSRules", @@ -32,27 +33,21 @@ ], "event_sources": [], "env_variables": { - "account_id": "${account_id}", - "batch_job_def_name": "${reports-submit-job-definition}", - "batch_job_queue_name": "${reports-submit-job-queue}", - "reports_bucket_name": "${reports-bucket}", - "caas_rulesets_bucket": "${caas_rulesets_bucket}", - "batch_job_log_level": "DEBUG", - "caas_user_pool_name": "${caas_user_pool_name}", - "last_scan_threshold": "${last_scan_threshold}", - "feature_skip_cloud_identifier_validation": "${feature_skip_cloud_identifier_validation}", - "job_lifetime_min": "${job_lifetime_min}", - "feature_filter_jobs_request": "${feature_filter_jobs_request}", - "feature_allow_only_temp_aws_credentials": "${feature_allow_only_temp_aws_credentials}", - "event_bridge_service_role_to_invoke_batch": "${event-bridge-service-role-to-invoke-batch}", + "CAAS_BATCH_JOB_DEF_NAME": "${reports-submit-job-definition}", + "CAAS_BATCH_JOB_QUEUE_NAME": "${reports-submit-job-queue}", + "CAAS_REPORTS_BUCKET_NAME": "${reports-bucket}", + "CAAS_RULESETS_BUCKET_NAME": "${caas_rulesets_bucket}", + "CAAS_BATCH_JOB_LOG_LEVEL": "DEBUG", + "CAAS_USER_POOL_NAME": "${caas_user_pool_name}", + "CAAS_SKIP_CLOUD_IDENTIFIER_VALIDATION": "${feature_skip_cloud_identifier_validation}", + "CAAS_BATCH_JOB_LIFETIME_MINUTES": "${job_lifetime_min}", + "CAAS_EB_SERVICE_ROLE_TO_INVOKE_BATCH": "${event-bridge-service-role-to-invoke-batch}", + "CAAS_JOBS_TIME_TO_LIVE_DAYS": "${JOBS_TIME_TO_LIVE_DAYS}", + "CAAS_STATISTICS_BUCKET_NAME": "${stats_s3_bucket_name}", + "CAAS_METRICS_BUCKET_NAME": "${caas_metrics_bucket_name}", + "component_name": "custodian-as-a-service", "modular_assume_role_arn": "${modular_assume_role_arn}", - "MODULAR_AWS_REGION": "${MODULAR_AWS_REGION}", - "JOBS_TIME_TO_LIVE_DAYS": "${JOBS_TIME_TO_LIVE_DAYS}", - "stats_s3_bucket_name": "${stats_s3_bucket_name}", - "caas_ssm_backup_bucket": "${caas_ssm_backup_bucket}", - "templates_s3_bucket_name": "${templates_s3_bucket_name}", - "metrics_bucket_name": "${caas_metrics_bucket_name}", - "component_name": "custodian-as-a-service" + "MODULAR_AWS_REGION": "${MODULAR_AWS_REGION}" }, "publish_version": true, "alias": "${lambdas_alias_name}", @@ -64,5 +59,8 @@ ], "layers": [ "custodian_common_dependencies_layer" + ], + "platforms": [ + "manylinux2014_x86_64" ] } \ No newline at end of file diff --git a/src/lambdas/custodian_api_handler/requirements.txt b/src/lambdas/custodian_api_handler/requirements.txt index d72daac61..d01b811a2 100644 --- a/src/lambdas/custodian_api_handler/requirements.txt +++ b/src/lambdas/custodian_api_handler/requirements.txt @@ -1 +1,3 @@ -pycryptodome==3.19.0 \ No newline at end of file +cryptography==42.0.2 +# swagger-ui-py==23.9.23 +# bottle==0.12.23 \ No newline at end of file diff --git a/src/lambdas/custodian_configuration_api_handler/README.md b/src/lambdas/custodian_configuration_api_handler/README.md index 343668782..ec2dc2153 100644 --- a/src/lambdas/custodian_configuration_api_handler/README.md +++ b/src/lambdas/custodian_configuration_api_handler/README.md @@ -1410,8 +1410,6 @@ This lambda uses the following resource: * CaaSJobs - the table used by service to store data about scans; * CaaSSettings - the table used by service to get and store settings data; * CaaSUsers - the table used by service to store user data; -* CaaSSIEMManager - the table used to manage SIEM configurations (dojo and security hub); -* CaaSLicenses - the table used to manage licenses. #### Systems Manager Parameter Store diff --git a/src/lambdas/custodian_configuration_api_handler/deployment_resources.json b/src/lambdas/custodian_configuration_api_handler/deployment_resources.json index adc067baa..9976ed095 100644 --- a/src/lambdas/custodian_configuration_api_handler/deployment_resources.json +++ b/src/lambdas/custodian_configuration_api_handler/deployment_resources.json @@ -21,6 +21,8 @@ "cognito-idp:ListUsers", "cognito-idp:AdminUpdateUserAttributes", "cognito-idp:AdminDeleteUserAttributes", + "cognito-idp:AdminDeleteUser", + "cognito-idp:AdminGetUser", "ssm:PutParameter", "ssm:DeleteParameter", "sts:AssumeRole" @@ -40,6 +42,15 @@ "arn:aws:lambda:${region}:${account_id}:function:caas-configuration-backupper:${lambdas_alias_name}", "arn:aws:lambda:${region}:${account_id}:function:caas-license-updater:${lambdas_alias_name}" ] + }, + { + "Action": [ + "states:StartExecution" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:states:${region}:${account_id}:stateMachine:retry_send_reports" + ] } ] } diff --git a/src/lambdas/custodian_configuration_api_handler/handler.py b/src/lambdas/custodian_configuration_api_handler/handler.py index ec996734c..2c8f053da 100644 --- a/src/lambdas/custodian_configuration_api_handler/handler.py +++ b/src/lambdas/custodian_configuration_api_handler/handler.py @@ -1,39 +1,45 @@ +from functools import cached_property from http import HTTPStatus -from typing import List, Union -from handlers.abstracts.abstract_handler import AbstractHandler -from handlers.applications_handler import ApplicationsHandler -from handlers.credentials_manager_handler import CredentialsManagerHandler -from handlers.customer_handler import instantiate_customer_handler -from handlers.findings_handler import FindingsHandler +from handlers.credentials_handler import CredentialsHandler +from handlers.customer_handler import CustomerHandler +from handlers.defect_dojo_handler import DefectDojoHandler from handlers.license_handler import LicenseHandler from handlers.license_manager_setting_handler import \ LicenseManagerConfigHandler, LicenseManagerClientHandler from handlers.mail_setting_handler import MailSettingHandler -from handlers.parents_handler import ParentsHandler from handlers.platforms_handler import PlatformsHandler from handlers.policy_handler import PolicyHandler from handlers.rabbitmq_handler import RabbitMQHandler +from handlers.report_status_handler import ReportStatusHandlerHandler from handlers.role_handler import RoleHandler from handlers.rule_handler import RuleHandler from handlers.rule_meta_handler import RuleMetaHandler from handlers.rule_source_handler import RuleSourceHandler from handlers.ruleset_handler import RulesetHandler -from handlers.tenants import instantiate_tenants_handler -from handlers.user_customer_handler import UserCustomerHandler -from handlers.user_role_handler import UserRoleHandler -from handlers.user_tenants_handler import UserTenantsHandler -from helpers import build_response -from helpers.constants import HTTPMethod, ACTION_PARAM_ERROR, \ - HTTP_METHOD_ERROR, CUSTOMER_ATTR, PARAM_REQUEST_PATH, PARAM_HTTP_METHOD, \ - RULE_SOURCE_ID_ATTR +from handlers.self_integration_handler import SelfIntegrationHandler +from handlers.send_report_setting_handler import \ + ReportsSendingSettingHandler +from handlers.tenant_handler import TenantHandler +from helpers.constants import HTTPMethod, CustodianEndpoint +from helpers.lambda_response import build_response from helpers.log_helper import get_logger from helpers.system_customer import SYSTEM_CUSTOMER from services import SERVICE_PROVIDER -from services.abstract_api_handler_lambda import AbstractApiHandlerLambda +from services.abs_lambda import ( + ApiGatewayEventProcessor, + CheckPermissionEventProcessor, + RestrictCustomerEventProcessor, + ExpandEnvironmentEventProcessor, + ApiEventProcessorLambdaHandler, + RestrictTenantEventProcessor +) from services.clients.lambda_func import LambdaClient -from services.clients.smtp import SMTPClient from services.rule_source_service import RuleSourceService +from validators.registry import permissions_mapping +from validators.swagger_request_models import RuleUpdateMetaPostModel, \ + BaseModel +from validators.utils import validate_kwargs RULE_META_UPDATER_LAMBDA_NAME = 'caas-rule-meta-updater' CONFIGURATION_BACKUPPER_LAMBDA_NAME = 'caas-configuration-backupper' @@ -42,58 +48,66 @@ _LOG = get_logger('custodian-configuration-api-handler') -class ConfigurationApiHandler(AbstractApiHandlerLambda): - - def __init__(self, handlers: List[AbstractHandler], - lambda_client: LambdaClient, +class ConfigurationApiHandler(ApiEventProcessorLambdaHandler): + processors = ( + ExpandEnvironmentEventProcessor.build(), + ApiGatewayEventProcessor(permissions_mapping), + RestrictCustomerEventProcessor.build(), + CheckPermissionEventProcessor.build(), + RestrictTenantEventProcessor.build() + ) + handlers = ( + PolicyHandler, + RoleHandler, + RuleHandler, + RulesetHandler, + RuleMetaHandler, + PlatformsHandler, + MailSettingHandler, + CustomerHandler, + RabbitMQHandler, + DefectDojoHandler, + ReportsSendingSettingHandler, + TenantHandler, + LicenseHandler, + ReportStatusHandlerHandler, + SelfIntegrationHandler, + LicenseManagerClientHandler, + LicenseManagerConfigHandler, + RuleSourceHandler, + CredentialsHandler + ) + + def __init__(self, lambda_client: LambdaClient, rule_source_service: RuleSourceService): - self.REQUEST_PATH_HANDLER_MAPPING = { - '/rules/update-meta': { + self.lambda_client = lambda_client + self.rule_source_service = rule_source_service + + @cached_property + def mapping(self): + res = { + CustodianEndpoint.RULE_META_UPDATER: { HTTPMethod.POST: self.invoke_rule_meta_updater }, - '/backup': { - HTTPMethod.POST: self.backup_configuration - }, - '/metrics/update': { + CustodianEndpoint.METRICS_UPDATE: { HTTPMethod.POST: self.update_metrics }, } - for handler in handlers: - self.REQUEST_PATH_HANDLER_MAPPING.update( - handler.define_action_mapping()) - self.lambda_client = lambda_client - self.rule_source_service = rule_source_service - - def handle_request(self, event, context): - request_path = event[PARAM_REQUEST_PATH] - method_name = event[PARAM_HTTP_METHOD] - action_mapping = self.REQUEST_PATH_HANDLER_MAPPING.get( - request_path) - if not action_mapping: - _LOG.warning( - ACTION_PARAM_ERROR.format(endpoint=request_path)) - return build_response( - code=HTTPStatus.BAD_REQUEST, - content='Service is temporarily unavailable') - handler_func = action_mapping.get(method_name) - if not handler_func: - return build_response( - code=HTTPStatus.BAD_REQUEST, - content=HTTP_METHOD_ERROR.format(method=method_name, - resource=request_path)) - return handler_func(event=event) + for h in self.handlers: + res.update(h.build().mapping) + return res - def invoke_rule_meta_updater(self, event): - _LOG.debug(f'Invoke meta updater event: {event}') + @validate_kwargs + def invoke_rule_meta_updater(self, event: RuleUpdateMetaPostModel): - customer = event.get(CUSTOMER_ATTR) or SYSTEM_CUSTOMER + customer = event.customer or SYSTEM_CUSTOMER - rs_service = rule_source_service + rs_service = self.rule_source_service - rule_source_id = event.get(RULE_SOURCE_ID_ATTR) + rule_source_id = event.rule_source_id rule_sources = [] if rule_source_id: - rule_source = rule_source_service.get( + rule_source = rs_service.get( rule_source_id=rule_source_id) if not rule_source or customer != SYSTEM_CUSTOMER and rule_source.customer != customer: return build_response( @@ -105,10 +119,8 @@ def invoke_rule_meta_updater(self, event): else: rule_sources = rs_service.list_rule_sources(customer=customer) - rule_sources = rs_service.filter_by_tenants(entities=rule_sources) - ids_to_sync = [] - responses: Union[str, List] = [] + responses: str | list = [] for rule_source in rule_sources: log_head = f'RuleSource:{rule_source.id!r} of ' \ f'{rule_source.customer!r} customer' @@ -145,15 +157,15 @@ def invoke_rule_meta_updater(self, event): content=responses ) - def update_metrics(self, event): - _LOG.debug(f'Invoke metrics updater event: {event}') + @validate_kwargs + def update_metrics(self, event: BaseModel): response = self.lambda_client.invoke_function_async( CONFIGURATION_METRICS_UPDATER_NAME, event={'data_type': 'tenants'}) if response.get('StatusCode') == HTTPStatus.ACCEPTED: _LOG.debug( f'{CONFIGURATION_BACKUPPER_LAMBDA_NAME} has been triggered') return build_response( - code=HTTPStatus.OK, + code=HTTPStatus.ACCEPTED, content='Metrics update has been submitted' ) _LOG.error( @@ -164,130 +176,12 @@ def update_metrics(self, event): code=HTTPStatus.INTERNAL_SERVER_ERROR, content='Internal Server Error') - def backup_configuration(self, event): - _LOG.debug(f'Invoke backupper event: {event}') - response = self.lambda_client.invoke_function_async( - CONFIGURATION_BACKUPPER_LAMBDA_NAME) - if response.get('StatusCode') == HTTPStatus.ACCEPTED: - _LOG.debug( - f'{CONFIGURATION_BACKUPPER_LAMBDA_NAME} has been triggered') - return build_response( - code=HTTPStatus.OK, - content=f'Backup event has been submitted' - ) - _LOG.error( - f'Response code is not {HTTPStatus.ACCEPTED}. ' - f'Response: {response}.\n{CONFIGURATION_BACKUPPER_LAMBDA_NAME} ' - f'has not been triggered') - return build_response( - code=HTTPStatus.INTERNAL_SERVER_ERROR, - content='Internal Server Error') - - -modular_service = SERVICE_PROVIDER.modular_service() -rule_service = SERVICE_PROVIDER.rule_service() -cached_iam_service = SERVICE_PROVIDER.iam_cache_service() -access_control_service = SERVICE_PROVIDER.access_control_service() -user_service = SERVICE_PROVIDER.user_service() -ssm_service = SERVICE_PROVIDER.ssm_service() -settings_service = SERVICE_PROVIDER.settings_service() -credential_manager_service = SERVICE_PROVIDER.credential_manager_service() -ruleset_service = SERVICE_PROVIDER.ruleset_service() -rule_source_service = SERVICE_PROVIDER.rule_source_service() -license_service = SERVICE_PROVIDER.license_service() -license_manager_service = SERVICE_PROVIDER.license_manager_service() -lambda_client = SERVICE_PROVIDER.lambda_func() -findings_service = SERVICE_PROVIDER.findings_service() -key_management_service = SERVICE_PROVIDER.key_management_service() - -customer_handler = instantiate_customer_handler( - modular_service=modular_service, user_service=user_service -) -tenant_handler = instantiate_tenants_handler( - modular_service=modular_service, - environment_service=SERVICE_PROVIDER.environment_service() -) -ruleset_handler = RulesetHandler.build() -rule_source_handler = RuleSourceHandler( - rule_source_service=rule_source_service, - modular_service=modular_service, - rule_service=rule_service, - restriction_service=SERVICE_PROVIDER.restriction_service() -) -credentials_manager_handler = CredentialsManagerHandler( - credential_manager_service=credential_manager_service, - user_service=user_service, - modular_service=modular_service -) -role_handler = RoleHandler( - cached_iam_service=cached_iam_service -) -policy_handler = PolicyHandler( - cached_iam_service=cached_iam_service, - access_control_service=access_control_service -) - -rule_service = RuleHandler( - rule_service=rule_service, - modular_service=modular_service, - rule_source_service=rule_source_service -) - -user_customer_handler = UserCustomerHandler( - modular_service=modular_service, - user_service=user_service -) -user_role_handler = UserRoleHandler( - user_service=user_service, - cached_iam_service=cached_iam_service -) -user_tenants_handler = UserTenantsHandler( - modular_service=modular_service, - user_service=user_service -) -license_handler = LicenseHandler( - self_service=license_service, - ruleset_service=ruleset_service, - lambda_client=lambda_client, - license_manager_service=license_manager_service, - modular_service=modular_service -) -findings_handler = FindingsHandler( - service=findings_service, - modular_service=modular_service -) -mail_setting_handler = MailSettingHandler( - settings_service=settings_service, - smtp_client=SMTPClient(), - ssm_client=SERVICE_PROVIDER.ssm() -) -lm_config_handler = LicenseManagerConfigHandler( - settings_service=settings_service -) -lm_client_handler = LicenseManagerClientHandler( - settings_service=settings_service, - license_manager_service=license_manager_service, - key_management_service=key_management_service -) -rule_meta_handler = RuleMetaHandler.build() -application_handler = ApplicationsHandler.build() -parent_handler = ParentsHandler.build() -rabbitmq_handler = RabbitMQHandler.build() -platforms_handler = PlatformsHandler.build() API_HANDLER = ConfigurationApiHandler( - handlers=[customer_handler, tenant_handler, - role_handler, policy_handler, rule_service, - user_customer_handler, user_role_handler, - credentials_manager_handler, - ruleset_handler, rule_source_handler, license_handler, - findings_handler, user_tenants_handler, mail_setting_handler, - lm_config_handler, lm_client_handler, application_handler, - parent_handler, rabbitmq_handler, rule_meta_handler, - platforms_handler], - lambda_client=SERVICE_PROVIDER.lambda_func(), - rule_source_service=SERVICE_PROVIDER.rule_source_service()) + lambda_client=SERVICE_PROVIDER.lambda_client, + rule_source_service=SERVICE_PROVIDER.rule_source_service +) def lambda_handler(event, context): - return API_HANDLER.lambda_handler(event=event, context=context) + return API_HANDLER.lambda_handler(event=event, context=context) \ No newline at end of file diff --git a/src/lambdas/custodian_configuration_api_handler/lambda_config.json b/src/lambdas/custodian_configuration_api_handler/lambda_config.json index dae3b1ce7..b4fc3b133 100644 --- a/src/lambdas/custodian_configuration_api_handler/lambda_config.json +++ b/src/lambdas/custodian_configuration_api_handler/lambda_config.json @@ -8,11 +8,8 @@ "memory": 256, "timeout": 100, "lambda_path": "/lambdas/custodian_configuration_api_handler", + "logs_expiration": "${logs_expiration}", "dependencies": [ - { - "resource_name": "CaaSCredentialsManager", - "resource_type": "dynamodb_table" - }, { "resource_name": "CaaSRules", "resource_type": "dynamodb_table" @@ -32,21 +29,16 @@ { "resource_name": "CaaSPolicies", "resource_type": "dynamodb_table" - }, - { - "resource_name": "CaaSLicenses", - "resource_type": "dynamodb_table" } ], "event_sources": [], "env_variables": { - "caas_user_pool_name": "custodian_as_a_service", - "not_invoke_ruleset_compiler": "${not_invoke_ruleset_compiler}", + "CAAS_USER_POOL_NAME": "custodian_as_a_service", + "CAAS_RULESETS_BUCKET_NAME": "${caas_rulesets_bucket}", + "CAAS_STATISTICS_BUCKET_NAME": "${stats_s3_bucket_name}", + "CAAS_LAMBDA_ALIAS_NAME": "${lambdas_alias_name}", "modular_assume_role_arn": "${modular_assume_role_arn}", - "MODULAR_AWS_REGION": "${MODULAR_AWS_REGION}", - "caas_rulesets_bucket": "${caas_rulesets_bucket}", - "stats_s3_bucket_name": "${stats_s3_bucket_name}", - "lambdas_alias_name": "${lambdas_alias_name}" + "MODULAR_AWS_REGION": "${MODULAR_AWS_REGION}" }, "publish_version": true, "alias": "${lambdas_alias_name}", @@ -58,5 +50,8 @@ ], "layers": [ "custodian_common_dependencies_layer" + ], + "platforms": [ + "manylinux2014_x86_64" ] -} \ No newline at end of file +} diff --git a/src/lambdas/custodian_configuration_api_handler/requirements.txt b/src/lambdas/custodian_configuration_api_handler/requirements.txt index d72daac61..1041fe690 100644 --- a/src/lambdas/custodian_configuration_api_handler/requirements.txt +++ b/src/lambdas/custodian_configuration_api_handler/requirements.txt @@ -1 +1 @@ -pycryptodome==3.19.0 \ No newline at end of file +cryptography==42.0.2 diff --git a/src/lambdas/custodian_configuration_backupper/README.md b/src/lambdas/custodian_configuration_backupper/README.md deleted file mode 100644 index e72fb6325..000000000 --- a/src/lambdas/custodian_configuration_backupper/README.md +++ /dev/null @@ -1,68 +0,0 @@ -## custodian-configuration-backupper - -Back up current service configuration, to git and S3 in folders to be able to -restore current configuration using custodian-configuration-updater -lambda. Will be back upped next tables: - -1. Backup repo: - - Customers - - Tenants - - Accounts - - Settings - - Policies - - Rules meta - - Ruleset files - -2. Backup s3 bucket: - - encrypted credentials - -### Lambda Configuration - -#### Should have next permission actions: -- Allow: batch:SubmitJob -- Allow: kms:Encrypt -- Allow: ssm:DescribeParameters -- Allow: ssm:GetParameters -- Allow: ssm:GetParameter -- Allow: xray:PutTraceSegments -- Allow: xray:PutTelemetryRecords -- Allow: logs:CreateLogGroup -- Allow: logs:CreateLogStream -- Allow: logs:PutLogEvents -- Allow: dynamodb:GetItem -- Allow: dynamodb:* -- Allow: s3:Get* -- Allow: s3:List* -- Allow: s3:* - - -#### Settings: - -- `BACKUP_REPO_ACCESS_INFO` - setting with git access info for configuration - repository. Format: - -```json5 -{ - "name": "BACKUP_REPO_ACCESS_INFO", - "value": { - "git_access_secret": "caas-configuration-repo-credentials", - // SSM param name - "git_access_type": "TOKEN", - "git_project_id": 104097, - "git_ref": "change-folders-structure", - // name of branch/commit to pull conf - "git_url": "https://git.epam.com/bohdan_onsha/caas-configuration" - } -} -``` - -#### Env Variables: - -- `caas_ssm_backup_bucket` - caas-ssm-backup - name of s3 bucket to store - encrypted secrets. -- `caas_ssm_backup_kms_key_id` - KMS key id to encrypt secrets data. - -#### Trigger event - -Cloudwatch rule `caas-configuration-backupper-event-trigger`. The cron is set to -run the lambda daily. \ No newline at end of file diff --git a/src/lambdas/custodian_configuration_backupper/__init__.py b/src/lambdas/custodian_configuration_backupper/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/lambdas/custodian_configuration_backupper/deployment_resources.json b/src/lambdas/custodian_configuration_backupper/deployment_resources.json deleted file mode 100644 index d8eb6d936..000000000 --- a/src/lambdas/custodian_configuration_backupper/deployment_resources.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "configuration-backupper-policy": { - "resource_type": "iam_policy", - "policy_content": { - "Version": "2012-10-17", - "Statement": [ - { - "Action": [ - "dynamodb:GetItem", - "s3:Get*", - "s3:List*", - "kms:Encrypt", - "ssm:DescribeParameters", - "ssm:GetParameters", - "ssm:GetParameter" - ], - "Resource": "*", - "Effect": "Allow" - } - ] - } - }, - "configuration-backupper-role": { - "predefined_policies": [], - "principal_service": "lambda", - "custom_policies": [ - "lambda-basic-execution", - "configuration-backupper-policy" - ], - "resource_type": "iam_role" - }, - "caas-configuration-backupper-event-trigger": { - "resource_type": "cloudwatch_rule", - "rule_type": "schedule", - "expression": "cron(20 8 * * ? *)" - } -} \ No newline at end of file diff --git a/src/lambdas/custodian_configuration_backupper/handler.py b/src/lambdas/custodian_configuration_backupper/handler.py deleted file mode 100644 index 503a962e6..000000000 --- a/src/lambdas/custodian_configuration_backupper/handler.py +++ /dev/null @@ -1,405 +0,0 @@ -import json -from datetime import datetime -from http import HTTPStatus - -from services.gitlab_service import GitlabService - -from helpers import build_response, \ - LINE_SEP, REPO_S3_ROOT, REPO_DYNAMODB_ROOT, REPO_SETTINGS_FOLDER, \ - REPO_POLICIES_FOLDER, REPO_ROLES_FOLDER, \ - CustodianException, REPO_LICENSES_FOLDER, REPO_SIEM_FOLDER -from helpers.log_helper import get_logger -from services import SERVICE_PROVIDER -from services.abstract_lambda import AbstractLambda -from services.clients.kms import KMSClient -from services.clients.s3 import S3Client -from services.environment_service import EnvironmentService -from services.license_service import LicenseService -from services.modular_service import ModularService -from services.rbac.iam_cache_service import CachedIamService -from services.rule_source_service import RuleSourceService -from services.ruleset_service import RulesetService -from services.setting_service import SettingsService -from services.ssm_service import SSMService - -_LOG = get_logger('custodian-configuration-backupper') - -COMMIT_MESSAGE_TEMPLATE = 'Backup from \'{build_name}\'' -STATUS_READY_TO_SCAN = 'READY_TO_SCAN' - - -def error_with_validation_env_vars(kms_key_id, ssm_backup_bucket): - """ - Must be set upped - AWS_REGION=eu-central-1; - caas_ssm_backup_kms_key_id=KMS_KEY_ID; - caas_ssm_backup_bucket=BUCKET - """ - message = '' - - if not kms_key_id or not ssm_backup_bucket: - message = "Service is improperly configured. Please contact " \ - "Custodian Service support team." - return message - - -class ConfigurationBackupper(AbstractLambda): - - def __init__(self, environment_service: EnvironmentService, - ssm_service: SSMService, s3_client: S3Client, - kms_client: KMSClient, settings_service: SettingsService, - modular_service: ModularService, ruleset_service: RulesetService, - cached_iam_service: CachedIamService, - rulesource_service: RuleSourceService, - license_service: LicenseService): - - self.environment_service = environment_service - self.ssm_service = ssm_service - self.s3_client = s3_client - self.kms_client = kms_client - self.settings_service = settings_service - self.modular_service = modular_service - self.cached_iam_service = cached_iam_service - self.ruleset_service = ruleset_service - self.rulesource_service = rulesource_service - self.license_service = license_service - self.accounts = [] - self.policies = [] - self.roles = [] - self.settings = [] - self.rulesets = [] - self.rulesources = [] - self.licenses = [] - self.siem = [] - - def configuration_git(self): - self.git_access_data = SERVICE_PROVIDER.settings_service() \ - .get_backup_repo_settings() - self.validating_git_access_data(self.git_access_data) - secret_name = self.git_access_data.get('git_access_secret') - self.validate_secret_name(secret_name) - git_access_secret = self.ssm_service.get_secret_value( - secret_name=secret_name) - self.git_access_data['git_access_secret'] = git_access_secret - self.gitlab_service: GitlabService = SERVICE_PROVIDER.gitlab_service( - git_access_data=self.git_access_data) - - def create_list_data_for_backup(self): - self.policies = self.cached_iam_service.list_policies() - self.roles = self.cached_iam_service.list_roles() - self.settings = list(self.settings_service.get_all_settings()) - self.get_all_rulesets() - self.get_all_rulesources(self.accounts) - self.licenses = list(self.license_service.scan()) - self.siem = list(self.siem_manager_service.list()) - - def handle_request(self, event, context): - self.configuration_git() - - _LOG.debug(f'Event: {event}') - - _LOG.debug('Extracting data from dynamodb tables.') - self.create_list_data_for_backup() - - dynamodb_config = self.generate_ddb_config() - - build_name = self.generate_build_name() - _LOG.debug(f'Build name generated: {build_name}') - commit_message = self.generate_commit_message(build_name=build_name) - _LOG.debug(f'Commit message generated: {commit_message}') - - commit_data = { - 'branch': self.gitlab_service.git_ref, - 'commit_message': commit_message, - 'actions': [] - } - - table_meta_actions = self.backup_table_meta(dynamodb_config, - build_name) - commit_data["actions"].extend(table_meta_actions) - - _LOG.info('Backupping settings table meta') - settings_table_actions = self.backup_settings_table(build_name) - commit_data["actions"].extend(settings_table_actions) - - _LOG.info('Backupping compiled ruleset files') - s3_actions = self.get_s3_actions(build_name=build_name) - - commit_data['actions'].extend(s3_actions) - - _LOG.info('Backupping SSM Parameters') - ssm_params_backed_up = self.backup_ssm( - build_name=build_name, - conf_objects=self.rulesources) - - # creating commit - _LOG.info(f'Creating a commit in branch:' - f' \'{self.gitlab_service.git_ref}\'') - commit = self.gitlab_service.create_commit( - commit_data=commit_data) - - files_created = [item.get('file_path') for item in - commit_data.get('actions')] - - _LOG.info('Commit has been created successfully') - - response_content = { - 'title': commit.title, - 'commit_url': commit.web_url, - 'stats': commit.stats, - 'git_files_created': files_created, - 'ssm_params_created': ssm_params_backed_up - } - return build_response(code=HTTPStatus.OK, content=response_content) - - def validate_request(self, event): - - kms_key_id = self.environment_service.get_ssm_backup_kms_key_id() - ssm_backup_bucket = self.environment_service.get_ssm_backup_bucket() - - if error_with_validation_env_vars(kms_key_id, ssm_backup_bucket): - _LOG.warning("Please check setting up environment variables") - raise CustodianException( - code=502, - content="The service is not configured correctly. Please " - "contact Custodian Service support." - ) - - def backup_ssm(self, build_name: str, conf_objects: list) -> list: - if not conf_objects: - return [] - ssm_param_names = [] - - for conf_object in conf_objects: - conf_json = conf_object.get_json() - - rules_repo_secret = conf_json.get('git_access_secret') - if rules_repo_secret: - ssm_param_names.append(rules_repo_secret) - - self.validation_ssm_param_names(ssm_param_names) - - param_values = self.ssm_service.get_secret_values( - secret_names=ssm_param_names) - kms_key_id = self.environment_service.get_ssm_backup_kms_key_id() - ssm_backup_bucket = self.environment_service.get_ssm_backup_bucket() - - params_backed_up = [] - for cred_key, value in param_values.items(): - value_encrypted = self.kms_client.encrypt(key_id=kms_key_id, - value=value) - obj_key = LINE_SEP.join((build_name, cred_key)) - - self.s3_client.put_object(bucket_name=ssm_backup_bucket, - object_name=obj_key, - body=value_encrypted) - params_backed_up.append( - LINE_SEP.join((ssm_backup_bucket, obj_key))) - return params_backed_up - - def get_all_rulesets(self): - rulesets = self.ruleset_service.list_rulesets( - licensed=False) - self.rulesets.extend(self.add_validated_rulesets(rulesets)) - - def get_all_rulesources(self, configuration_objects: list): - for conf_object in configuration_objects: - self.rulesources.extend( - self.rulesource_service.list_rule_sources(customer=conf_object.customer_display_name)) - - def get_s3_actions(self, build_name: str) -> list: - """ - Append rulesets from S3 to action (list of files to add in Git). - - configuration_objects: list of Accounts from DDB to backup. - Criteria to backup rulesets: - 1. Status - READY_TO_SCAN - 2. Has S3 path - 3. Has content - """ - actions = [] - for ruleset in self.rulesets: - s3_path = ruleset.attribute_values.get('path') - bucket = ruleset.attribute_values.get('bucket_name') - if s3_path and bucket: - content = self.s3_client.get_file_content( - bucket_name=bucket, full_file_name=s3_path) - if content: - repo_path = LINE_SEP.join( - (build_name, REPO_S3_ROOT, bucket, s3_path)) - actions.append({'action': 'create', - 'file_path': repo_path, - 'content': content}) - return actions - - @staticmethod - def get_settings_actions(build_name: str, settings: list) -> list: - build_folder_path = LINE_SEP.join((build_name, REPO_DYNAMODB_ROOT, - REPO_SETTINGS_FOLDER)) - filename = 'settings.json' - content = {} - for setting in settings: - content[setting.name] = setting.value - - return [{'action': 'create', - 'file_path': LINE_SEP.join((build_folder_path, filename)), - 'content': json.dumps(content, indent=4)}] - - @staticmethod - def get_dynamodb_objects_actions(configuration_objects: list, - build_folder_path: str, - identifier_attribute_name='id') -> list: - """ - Creates action with all fields from DDB and file path. Append it to - actions list - """ - actions = [] - for obj in configuration_objects: - content = obj.get_json() - if hasattr(obj, identifier_attribute_name): - file_name = f'{getattr(obj, identifier_attribute_name)}.json' - elif obj._hash_keyname and obj._range_keyname: - _hash_key = getattr(obj, obj._hash_keyname) - _range_ket = getattr(obj, obj._range_keyname) - file_name = f'{_hash_key}-{_range_ket}.json' - else: - file_name = f'{getattr(obj, obj._hash_keyname)}.json' - - file_path = LINE_SEP.join((build_folder_path, file_name)) - actions.append({ - 'action': 'create', - 'file_path': file_path, - 'content': json.dumps(content, indent=4)}) - return actions - - @staticmethod - def generate_commit_message(build_name: str): - return COMMIT_MESSAGE_TEMPLATE.format(build_name=build_name) - - @staticmethod - def generate_build_name(): - return datetime.now().strftime('%Y-%m-%d-%H-%M') - - def generate_ddb_config(self): - dynamodb_config = { - 'Policies': { - 'objects': self.policies, - 'folder_name': REPO_POLICIES_FOLDER - }, - 'Roles': { - 'objects': self.roles, - 'folder_name': REPO_ROLES_FOLDER - }, - 'Licenses': { - 'objects': self.licenses, - 'folder_name': REPO_LICENSES_FOLDER - }, - 'SIEMManager': { - 'objects': self.siem, - 'folder_name': REPO_SIEM_FOLDER - } - } - return dynamodb_config - - def backup_table_meta(self, _dynamodb_config, _build_name): - """ - For-loop where take table name, data from this table and create - actions (list with files to create) - """ - _actions = [] - for table_name, config in _dynamodb_config.items(): - _LOG.info(f'Backupping \'{table_name}\' table meta') - objects = config.get('objects') - build_folder_path = LINE_SEP.join((_build_name, REPO_DYNAMODB_ROOT, - config.get('folder_name'))) - actions = self.get_dynamodb_objects_actions( - configuration_objects=objects, - build_folder_path=build_folder_path) - - _LOG.debug(f'{table_name} actions: {actions}') - _actions.extend(actions) - return _actions - - def backup_settings_table(self, build_name): - _actions = [] - settings_actions = self.get_settings_actions(build_name=build_name, - settings=self.settings) - _LOG.debug(f'Settings actions: {settings_actions}') - _actions.extend(settings_actions) - return _actions - - @staticmethod - def validation_ssm_param_names(ssm_param_names): - if not ssm_param_names: - build_response( - content={"message": "Cannot backup SSM parameters because no " - "model has the 'git_access_secret' and " - "'credentials' fields"}, - code=HTTPStatus.BAD_REQUEST - ) - - @staticmethod - def validate_secret_name(secret_name): - error_message = "Please check that Git Access Data contains Git " \ - "access secret" - - if not secret_name: - build_response( - content=error_message, - code=HTTPStatus.BAD_REQUEST - ) - - @staticmethod - def validating_git_access_data(git_access_data): - - _required_fields = ["git_ref", "git_access_type", "git_url", - "git_project_id", "git_access_secret"] - - if not isinstance(git_access_data, dict): - error_message = "Please check git access data, it should " \ - "provide keys and values" - build_response( - content=error_message, - code=HTTPStatus.BAD_REQUEST - ) - - for key in ["git_ref", "git_access_type", "git_url", - "git_project_id", "git_access_secret"]: - if not git_access_data.get(key): - error_message = "Please, provide all required fields " \ - f"first - {_required_fields}" - build_response( - content=error_message, - code=HTTPStatus.BAD_REQUEST - ) - - @staticmethod - def add_validated_rulesets(_rulesets): - _rulesets_to_backup = [] - for ruleset in _rulesets: - status = ruleset.attribute_values.get('status') - if status: - status_code = status.attribute_values.get('code') - if status_code == STATUS_READY_TO_SCAN: - s3_path = ruleset.attribute_values.get('s3_path') - if s3_path: - _rulesets_to_backup.append(s3_path) - return _rulesets_to_backup - - -HANDLER = ConfigurationBackupper( - environment_service=SERVICE_PROVIDER.environment_service(), - ssm_service=SERVICE_PROVIDER.ssm_service(), - s3_client=SERVICE_PROVIDER.s3(), - kms_client=SERVICE_PROVIDER.kms(), - settings_service=SERVICE_PROVIDER.settings_service(), - modular_service=SERVICE_PROVIDER.modular_service(), - cached_iam_service=SERVICE_PROVIDER.iam_cache_service(), - ruleset_service=SERVICE_PROVIDER.ruleset_service(), - rulesource_service=SERVICE_PROVIDER.rule_source_service(), - license_service=SERVICE_PROVIDER.license_service(), -) - - -def lambda_handler(event, context): - return HANDLER.lambda_handler(event=event, context=context) diff --git a/src/lambdas/custodian_configuration_backupper/lambda_config.json b/src/lambdas/custodian_configuration_backupper/lambda_config.json deleted file mode 100644 index 99157f0c1..000000000 --- a/src/lambdas/custodian_configuration_backupper/lambda_config.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "version": "1.0", - "name": "caas-configuration-backupper", - "func_name": "handler.lambda_handler", - "resource_type": "lambda", - "iam_role_name": "configuration-backupper-role", - "runtime": "python3.10", - "memory": 128, - "timeout": 100, - "lambda_path": "/lambdas/custodian_configuration_backupper", - "dependencies": [ - { - "resource_name": "CaaSRules", - "resource_type": "dynamodb_table" - }, - { - "resource_name": "CaaSSettings", - "resource_type": "dynamodb_table" - } - ], - "event_sources": [ - { - "resource_type": "cloudwatch_rule_trigger", - "target_rule": "caas-configuration-backupper-event-trigger" - } - ], - "env_variables": { - "caas_ssm_backup_bucket": "${caas_ssm_backup_bucket}", - "caas_ssm_backup_kms_key_id": "${caas_ssm_backup_kms_key_id}" - }, - "publish_version": true, - "alias": "${lambdas_alias_name}", - "subnet_ids": [ - "${lambda_private_subnet_id_1}" - ], - "security_group_ids": [ - "${lambda_security_group_1}" - ], - "layers": [ - "custodian_common_dependencies_layer" - ] -} \ No newline at end of file diff --git a/src/lambdas/custodian_configuration_backupper/local_requirements.txt b/src/lambdas/custodian_configuration_backupper/local_requirements.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/lambdas/custodian_configuration_backupper/requirements.txt b/src/lambdas/custodian_configuration_backupper/requirements.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/lambdas/custodian_configuration_updater/README.md b/src/lambdas/custodian_configuration_updater/README.md deleted file mode 100644 index 6385c9213..000000000 --- a/src/lambdas/custodian_configuration_updater/README.md +++ /dev/null @@ -1,68 +0,0 @@ -## custodian-configuration-updater - -Synchronize configurations according to state stored in git repo Push -configuration data to: - -1. Dynamodb: - - Customers - - Tenants - - Accounts - - Rules - - Policies - - Roles - - Settings - -2. S3: - - rulesets - -3. Secrets: - - SSM (Parameter Store) - -### Lambda Configuration - -#### Should have next permission actions: - -- Allow: batch:SubmitJob -- Allow: kms:Decrypt -- Allow: ssm:GetParameter -- Allow: xray:PutTraceSegments -- Allow: xray:PutTelemetryRecords -- Allow: logs:CreateLogGroup -- Allow: logs:CreateLogStream -- Allow: logs:PutLogEvents -- Allow: dynamodb:GetItem -- Allow: dynamodb:* -- Allow: s3:Get* -- Allow: s3:List* -- Allow: s3:* - - -#### Settings: - -- `BACKUP_REPO_ACCESS_INFO` - setting with git access info for configuration - repository. Format: - -```json5 -{ - "name": "BACKUP_REPO_ACCESS_INFO", - "value": { - "git_access_secret": "caas-configuration-repo-credentials", - // SSM param name - "git_access_type": "TOKEN", - "git_project_id": 104097, - "git_ref": "change-folders-structure", - // name of branch/commit to pull conf - "git_url": "https://git.epam.com/bohdan_onsha/caas-configuration" - } -} -``` - -#### Env Variables: - -- `caas_rulesets_bucket` - name of s3 bucket to store ruleset files. DEV - bucket: `caas-rulesets-dev2` - -- `caas_ssm_backup_bucket` - caas-ssm-backup - name of s3 bucket to store - encrypted secrets. - -- `caas_ssm_backup_kms_key_id` - KMS key id to encrypt secrets data. diff --git a/src/lambdas/custodian_configuration_updater/__init__.py b/src/lambdas/custodian_configuration_updater/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/lambdas/custodian_configuration_updater/deployment_resources.json b/src/lambdas/custodian_configuration_updater/deployment_resources.json deleted file mode 100644 index 7b1ee19ab..000000000 --- a/src/lambdas/custodian_configuration_updater/deployment_resources.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "configuration-updater-policy": { - "resource_type": "iam_policy", - "policy_content": { - "Version": "2012-10-17", - "Statement": [ - { - "Action": [ - "kms:Decrypt", - "s3:Get*", - "s3:List*", - "kms:Decrypt" - ], - "Resource": "*", - "Effect": "Allow" - } - ] - } - }, - "configuration-updater-role": { - "predefined_policies": [], - "principal_service": "lambda", - "custom_policies": [ - "lambda-basic-execution", - "configuration-updater-policy" - ], - "resource_type": "iam_role" - } -} \ No newline at end of file diff --git a/src/lambdas/custodian_configuration_updater/handler.py b/src/lambdas/custodian_configuration_updater/handler.py deleted file mode 100644 index 3cc5b252f..000000000 --- a/src/lambdas/custodian_configuration_updater/handler.py +++ /dev/null @@ -1,354 +0,0 @@ -from modular_sdk.commons.error_helper import RESPONSE_BAD_REQUEST_CODE, \ - RESPONSE_OK_CODE, RESPONSE_SERVICE_UNAVAILABLE_CODE - -from helpers import build_response, REPO_SETTINGS_PATH, REPO_S3_ROOT, \ - LINE_SEP, CustodianException -from helpers.log_helper import get_logger -from models.licenses import License -from models.policy import Policy -from models.role import Role -from services import SERVICE_PROVIDER -from services.abstract_lambda import AbstractLambda -from services.clients.kms import KMSClient -from services.clients.s3 import S3Client -from services.environment_service import EnvironmentService -from services.gitlab_service import GitlabService -from services.license_service import LicenseService -from services.rbac.iam_cache_service import CachedIamService -from services.setting_service import SettingsService -from services.ssm_service import SSMService - -_LOG = get_logger('custodian-configuration-updater') - -PARAM_BUILD_NAME = 'build_name' -ACCOUNTS = 'Accounts' -POLICIES = 'Policies' -ROLES = 'Roles' -LICENSES = 'Licenses' -SIEM = 'SIEMManager' - - -class ConfigurationUpdater(AbstractLambda): - - def __init__(self, environment_service: EnvironmentService, - ssm_service: SSMService, kms_client: KMSClient, - s3_client: S3Client, settings_service: SettingsService, - cached_iam_service: CachedIamService, - license_service: LicenseService): - self.environment_service = environment_service - self.ssm_service = ssm_service - self.s3_client = s3_client - self.kms_client = kms_client - self.settings_service = settings_service - self.cached_iam_service = cached_iam_service - self.license_service = license_service - - def configuration_git(self): - git_access_data = self.settings_service.get_backup_repo_settings() - if not git_access_data: - build_response( - content='Please check Backup repo configuration', - code=RESPONSE_BAD_REQUEST_CODE - ) - - secret_name = git_access_data.get('git_access_secret') - git_access_secret = self.ssm_service.get_secret_value( - secret_name=secret_name) - git_access_data['git_access_secret'] = git_access_secret - - self.gitlab_service: GitlabService = SERVICE_PROVIDER.gitlab_service( - git_access_data=git_access_data) - - def validate_request(self, event): - bucket = self.environment_service.get_ssm_backup_bucket() - kms_key_id = self.environment_service.get_ssm_backup_kms_key_id() - - if self.error_with_validation_env_vars(kms_key_id, bucket): - _LOG.error('Missing one of the following env variables: ' - '\'caas_ssm_backup_kms_key_id\', ' - '\'caas_ssm_backup_bucket\'') - raise CustodianException( - code=502, - content="The service is not configured correctly. Please " - "contact Custodian Service support." - ) - - def handle_request(self, event, context): - self.configuration_git() - _LOG.debug(f'Event: {event}') - - _LOG.debug('Resolving build folder') - build_folder = self.create_build_folder_or_error(event) - - _LOG.info(f'Processing build: {build_folder}') - - entities = self.gitlab_service.pull_structure_of_ddb_tables( - folder_path=f"{build_folder}/dynamodb") - - _LOG.info(f'Creating {entities} configurations.') - - configuration_objects = self.create_configuration( - build_folder=build_folder, entities=entities) - _LOG.debug(f'Configuration objects: {configuration_objects}') - - secrets = self.create_secrets(build_folder=build_folder) - - _LOG.info('Creating Settings') - settings = self.create_settings(build_folder=build_folder) - _LOG.debug(f'Settings objects: {settings}') - - _LOG.info('Uploading files to s3') - files = self.upload_git_files_to_s3(build_folder=build_folder) - _LOG.debug(f'Files: {files}') - - for table_name, configurations in configuration_objects.items(): - for index, conf_object in enumerate(configurations): - configurations[index] = conf_object.get_json() - - file_paths = [file.get('path') for file in files] - return build_response( - code=RESPONSE_OK_CODE, - content={ - 'configuration_objects': configuration_objects, - 'settings': settings, - 'secrets': secrets, - 'ruleset_files': file_paths - } - ) - - def create_configuration(self, build_folder, entities): - """ - 1. Pull content from Git - 2. Creating configuration for each entity - 3. Save models to DDB - """ - - data_entities = self.get_data_from_git(_entities=entities, - _build_folder=build_folder) - - _LOG.debug(f'Data from Git: {data_entities}') - - objects = self.populate_configuration( - accounts_data=data_entities.get("Accounts") if data_entities.get( - "Accounts") else [], - policies_data=data_entities.get("Policies") if data_entities.get( - "Policies") else [], - roles_data=data_entities.get("Roles") if data_entities.get( - "Roles") else [], - licenses_data=data_entities.get("Licenses") if data_entities.get( - "Licenses") else [], - siem_data=data_entities.get("SIEMManager") if data_entities.get( - "SIEMManager") else [] - ) - - self.batch_save_configuration( - policies_data=objects.get('Policies'), - roles_data=objects.get('Roles'), - licenses_data=objects.get('Licenses'), - siem_data=objects.get('SIEMManager') - ) - _LOG.debug(f'Configuration objects: {objects}') - return objects - - def create_secrets(self, build_folder): - bucket = self.environment_service.get_ssm_backup_bucket() - kms_key_id = self.environment_service.get_ssm_backup_kms_key_id() - - _LOG.debug(f'SSM backup bucket: {bucket}') - encrypted_objects = list(self.s3_client.list_objects( - bucket_name=bucket, prefix=build_folder - )) - object_keys = [obj.get('Key') for obj in encrypted_objects] - _LOG.debug(f'List of objects to import as SSM parameters: ' - f'{object_keys}') - if not encrypted_objects: - return [] - - created_secrets = [] - for object_key in object_keys: - _LOG.debug(f'Processing {object_key} parameter') - secret_name = object_key.split('/')[-1] - _LOG.info('Downloading encrypted content') - content = self.s3_client.get_file_content( - bucket_name=bucket, - full_file_name=object_key) - decrypted_value = self.kms_client.decrypt(value=content, - key_id=kms_key_id) - _LOG.debug('Secret value has been decrypted') - self.ssm_service.create_secret_value(secret_name=secret_name, - secret_value=decrypted_value) - created_secrets.append(secret_name) - _LOG.info(f'SSM Parameter with name \'{secret_name}\'' - f' has been created.') - return created_secrets - - def create_settings(self, build_folder): - full_settings_path = LINE_SEP.join((build_folder, REPO_SETTINGS_PATH)) - settings_data = self.gitlab_service.pull_folder_files_content( - folder_path=full_settings_path) - _LOG.debug(f'Settings data: {settings_data}') - - settings = {} - for setting_data in settings_data: - for name, value in setting_data.items(): - _LOG.debug(f'Creating Setting: {name}:{value}') - setting = self.settings_service.create(name=name, value=value) - self.settings_service.save(setting) - settings[name] = value - return settings - - def upload_git_files_to_s3(self, build_folder): - """ - 1. Get bucket name from env vars - 2. Validate if bucket exists - 3. Build path in s3 (repo_s3_root) - 4. Get files from Git - 5. Upload files into S3 - 6. return files from Git - """ - bucket_name = self.environment_service.get_rulesets_bucket_name() - _LOG.debug(f'Rulesets bucket name: {bucket_name}') - - if not self.s3_client.is_bucket_exists(bucket_name=bucket_name): - _LOG.error(f'Specified rulesets bucket \'{bucket_name}\' ' - f'does not exist') - build_response( - code=RESPONSE_SERVICE_UNAVAILABLE_CODE, - content=f'Specified rulesets bucket \'{bucket_name}\' ' - f'does not exist') - - repo_s3_root = LINE_SEP.join((build_folder, REPO_S3_ROOT, bucket_name)) - - files_data = self.gitlab_service.pull_recursive_folder_files_content( - folder_path=repo_s3_root) - _LOG.debug(f'Files data: {files_data}') - - for file_data in files_data: - _LOG.debug(f'Uploading file: {file_data.get("path")}') - self.s3_client.put_object( - bucket_name=bucket_name, - object_name=file_data.get('path'), - body=file_data.get('content')) - - return files_data - - def resolve_build_folder(self, build_folder: str) -> str: - if build_folder and self.gitlab_service.build_exists( - build_name=build_folder): - _LOG.info(f'Build specified in the request ' - f'will be used: {build_folder}') - else: - _LOG.info('Last available build will be used') - build_folder = self.gitlab_service.get_last_build_folder_name() - return build_folder - - @staticmethod - def error_with_validation_env_vars(kms_key_id, bucket): - if not kms_key_id or not bucket: - return False - - def create_build_folder_or_error(self, _event): - build_folder = self.resolve_build_folder( - build_folder=_event.get(PARAM_BUILD_NAME)) - - if not build_folder: - _LOG.warning(f'No builds available in target repository: ' - f'{self.gitlab_service.git_url} , ' - f'ref: {self.gitlab_service.git_ref}') - return build_response( - code=RESPONSE_SERVICE_UNAVAILABLE_CODE, - content=f'No builds available in target repository: ' - f'{self.gitlab_service.git_url} , ' - f'ref: {self.gitlab_service.git_ref}') - return build_folder - - def get_data_from_git(self, _entities, _build_folder): - """ - Get files from Git repo (/dynamodb/) - entity could be from list of _entities - """ - _data_entities = dict().fromkeys(_entities) - - for entity in _entities: # ['Accounts', 'Policies', - # 'Roles', 'Rules', 'Settings'] - path_for_entity = LINE_SEP.join( - (_build_folder, 'dynamodb', entity)) - entity_data = self.gitlab_service.pull_folder_files_content( - folder_path=path_for_entity) - _LOG.debug(f'{entity} data: {entity_data}') - _data_entities[entity] = entity_data - return _data_entities - - @staticmethod - def modify_name(entity_data): - for file in entity_data: - if file.get('display_name'): - file['display_name'] += '_restored' - if file.get('name'): - file['name'] += '_restored' - return entity_data - - def populate_configuration(self, accounts_data: list, policies_data: list, - roles_data: list, licenses_data: list, - siem_data: list) -> dict: - configs = {i: [] for i in - (ACCOUNTS, POLICIES, ROLES, LICENSES, SIEM)} - - for policy_data in policies_data: - _LOG.debug(f'Creating policy configuration: {policy_data}') - config = self.cached_iam_service.create_policy(policy_data) - if config: - configs[POLICIES].append(config) - - for role_data in roles_data: - _LOG.debug(f'Creating role configuration: {role_data}') - config = self.cached_iam_service.create_role(role_data) - if config: - configs[ROLES].append(config) - - for license_data in licenses_data: - _LOG.debug(f'Creating license configuration: {license_data}') - config = self.license_service.create(license_data) - if config: - configs[LICENSES].append(config) - - for siem in siem_data: - _LOG.debug(f'Creating SIEM configuration: {siem}') - config = self.siem_manager_service.create(siem) - if config: - configs[SIEM].append(config) - - return configs - - @staticmethod - def batch_save_configuration(policies_data: list = None, - roles_data: list = None, - licenses_data: list = None, - siem_data: list = None): - if roles_data: - with Role.batch_write() as batch: - for role in roles_data: - batch.save(role) - if policies_data: - with Policy.batch_write() as batch: - for policy in policies_data: - batch.save(policy) - if licenses_data: - with License.batch_write() as batch: - for _license in licenses_data: - batch.save(_license) - - -HANDLER = ConfigurationUpdater( - environment_service=SERVICE_PROVIDER.environment_service(), - ssm_service=SERVICE_PROVIDER.ssm_service(), - s3_client=SERVICE_PROVIDER.s3(), - kms_client=SERVICE_PROVIDER.kms(), - settings_service=SERVICE_PROVIDER.settings_service(), - cached_iam_service=SERVICE_PROVIDER.iam_cache_service(), - license_service=SERVICE_PROVIDER.license_service() -) - - -def lambda_handler(event, context): - return HANDLER.lambda_handler(event=event, context=context) diff --git a/src/lambdas/custodian_configuration_updater/lambda_config.json b/src/lambdas/custodian_configuration_updater/lambda_config.json deleted file mode 100644 index 576d3978a..000000000 --- a/src/lambdas/custodian_configuration_updater/lambda_config.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "version": "1.0", - "name": "caas-configuration-updater", - "func_name": "handler.lambda_handler", - "resource_type": "lambda", - "iam_role_name": "configuration-updater-role", - "runtime": "python3.10", - "memory": 128, - "timeout": 100, - "lambda_path": "/lambdas/custodian_configuration_updater", - "dependencies": [ - { - "resource_name": "CaaSRules", - "resource_type": "dynamodb_table" - }, - { - "resource_name": "CaaSSettings", - "resource_type": "dynamodb_table" - } - ], - "event_sources": [], - "env_variables": { - "caas_rulesets_bucket": "${caas_rulesets_bucket}", - "caas_ssm_backup_bucket": "${caas_ssm_backup_bucket}", - "caas_ssm_backup_kms_key_id": "${caas_ssm_backup_kms_key_id}" - }, - "publish_version": true, - "alias": "${lambdas_alias_name}", - "subnet_ids": [ - "${lambda_private_subnet_id_1}" - ], - "security_group_ids": [ - "${lambda_security_group_1}" - ], - "layers": [ - "custodian_common_dependencies_layer" - ] -} \ No newline at end of file diff --git a/src/lambdas/custodian_configuration_updater/local_requirements.txt b/src/lambdas/custodian_configuration_updater/local_requirements.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/lambdas/custodian_configuration_updater/requirements.txt b/src/lambdas/custodian_configuration_updater/requirements.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/lambdas/custodian_event_handler/handler.py b/src/lambdas/custodian_event_handler/handler.py index 4f64181e0..34b7663e3 100644 --- a/src/lambdas/custodian_event_handler/handler.py +++ b/src/lambdas/custodian_event_handler/handler.py @@ -1,27 +1,29 @@ from functools import cached_property -from typing import Union, Dict, Type, Optional from http import HTTPStatus -from handlers.event_assembler_handler import EventAssemblerHandler, \ - EventRemoverHandler -from helpers import build_response + +from handlers.event_assembler_handler import (EventAssemblerHandler, + EventRemoverHandler) +from helpers import RequestContext from helpers.constants import ACTION_PARAM +from helpers.lambda_response import build_response from helpers.log_helper import get_logger -from services.abstract_lambda import AbstractLambda +from services.abs_lambda import EventProcessorLambdaHandler _LOG = get_logger('custodian-event-handler') CLEAR_EVENTS_ACTION = 'clear-events' ASSEMBLE_EVENTS_ACTION = 'assemble-events' -Handler = Union[EventRemoverHandler, EventAssemblerHandler] +Handler = EventRemoverHandler | EventAssemblerHandler -class EventHandler(AbstractLambda): +class EventHandler(EventProcessorLambdaHandler): + processors = () def __init__(self): - self._action_handler: Dict[str, Handler] = {} + self._action_handler: dict[str, Handler] = {} - def handle_request(self, event: dict, context: object) -> dict: + def handle_request(self, event: dict, context: RequestContext): _default_action = ASSEMBLE_EVENTS_ACTION event_action = event.get(ACTION_PARAM) or _default_action _LOG.info(f'Event action: `{event_action}`. Retrieving handler') @@ -35,7 +37,7 @@ def handle_request(self, event: dict, context: object) -> dict: content=message) return handler.handler(event) - def get_handler(self, action: str) -> Optional[Handler]: + def get_handler(self, action: str) -> Handler | None: if action not in self._action_handler: _LOG.info(f'Instantiating handler for {action} action') _type = self.action_handler_map.get(action) @@ -44,7 +46,7 @@ def get_handler(self, action: str) -> Optional[Handler]: return self._action_handler.get(action) @cached_property - def action_handler_map(self) -> Dict[str, Type[Handler]]: + def action_handler_map(self) -> dict[str, type[Handler]]: return { CLEAR_EVENTS_ACTION: EventRemoverHandler, ASSEMBLE_EVENTS_ACTION: EventAssemblerHandler diff --git a/src/lambdas/custodian_event_handler/lambda_config.json b/src/lambdas/custodian_event_handler/lambda_config.json index 51ff7cbeb..499141073 100644 --- a/src/lambdas/custodian_event_handler/lambda_config.json +++ b/src/lambdas/custodian_event_handler/lambda_config.json @@ -8,6 +8,7 @@ "memory": 512, "timeout": 100, "lambda_path": "/lambdas/custodian_event_handler", + "logs_expiration": "${logs_expiration}", "dependencies": [ { "resource_name": "CaaSEvents", @@ -44,20 +45,15 @@ } ], "env_variables": { - "account_id": "${account_id}", - "batch_job_def_name": "${reports-submit-job-definition}", - "batch_job_queue_name": "${reports-submit-job-queue}", - "reports_bucket_name": "${reports-bucket}", - "caas_rulesets_bucket": "${caas_rulesets_bucket}", - "batch_job_log_level": "DEBUG", - "last_scan_threshold": "${last_scan_threshold}", - "feature_skip_cloud_identifier_validation": "${feature_skip_cloud_identifier_validation}", - "job_lifetime_min": "${job_lifetime_min}", - "feature_filter_jobs_request": "${feature_filter_jobs_request}", - "feature_allow_only_temp_aws_credentials": "${feature_allow_only_temp_aws_credentials}", + "CAAS_BATCH_JOB_DEF_NAME": "${reports-submit-job-definition}", + "CAAS_BATCH_JOB_QUEUE_NAME": "${reports-submit-job-queue}", + "CAAS_REPORTS_BUCKET_NAME": "${reports-bucket}", + "CAAS_RULESETS_BUCKET_NAME": "${caas_rulesets_bucket}", + "CAAS_BATCH_JOB_LOG_LEVEL": "DEBUG", + "CAAS_BATCH_JOB_LIFETIME_MINUTES": "${job_lifetime_min}", + "CAAS_STATISTICS_BUCKET_NAME": "${stats_s3_bucket_name}", "modular_assume_role_arn": "${modular_assume_role_arn}", - "MODULAR_AWS_REGION": "${MODULAR_AWS_REGION}", - "stats_s3_bucket_name": "${stats_s3_bucket_name}" + "MODULAR_AWS_REGION": "${MODULAR_AWS_REGION}" }, "publish_version": true, "alias": "${lambdas_alias_name}", @@ -69,5 +65,8 @@ ], "layers": [ "custodian_common_dependencies_layer" + ], + "platforms": [ + "manylinux2014_x86_64" ] } \ No newline at end of file diff --git a/src/lambdas/custodian_job_updater/deployment_resources.json b/src/lambdas/custodian_job_updater/deployment_resources.json index a2e7360aa..8016ee222 100644 --- a/src/lambdas/custodian_job_updater/deployment_resources.json +++ b/src/lambdas/custodian_job_updater/deployment_resources.json @@ -28,6 +28,9 @@ "resource_type": "cloudwatch_rule", "rule_type": "api_call", "custom_pattern": { + "detail-type": [ + "Batch Job State Change" + ], "source": [ "aws.batch" ] diff --git a/src/lambdas/custodian_job_updater/handler.py b/src/lambdas/custodian_job_updater/handler.py index 146e37934..10f623535 100644 --- a/src/lambdas/custodian_job_updater/handler.py +++ b/src/lambdas/custodian_job_updater/handler.py @@ -1,198 +1,125 @@ from datetime import datetime, timezone -from typing import Union +from http import HTTPStatus +from typing import Literal, TypedDict, cast -from helpers import build_response -from helpers.constants import BATCH_ENV_SCHEDULED_JOB_NAME, \ - BATCH_ENV_TARGET_RULESETS_VIEW, BATCH_ENV_LICENSED_RULESETS, \ - BATCH_ENV_TARGET_REGIONS, BATCH_ENV_AFFECTED_LICENSES, BATCH_ENV_JOB_TYPE, \ - BATCH_EVENT_DRIVEN_JOB_TYPE, BATCH_ENV_BATCH_RESULTS_ID, \ - BATCH_MULTI_ACCOUNT_EVENT_DRIVEN_JOB_TYPE, BATCH_ENV_BATCH_RESULTS_IDS, \ - BATCH_ENV_TENANT_NAME +from helpers.constants import BatchJobEnv, BatchJobType, JobState +from helpers.lambda_response import build_response from helpers.log_helper import get_logger from helpers.time_helper import utc_iso -from models.job import Job from services import SERVICE_PROVIDER -from services.abstract_lambda import AbstractLambda, PARAM_NATIVE_JOB_ID -from services.batch_results_service import BatchResultsService -from services.job_service import JobService +from services.abs_lambda import EventProcessorLambdaHandler +from services.job_service import JobUpdater from services.license_manager_service import LicenseManagerService -from services.ruleset_service import RulesetService from services.ssm_service import SSMService -from services.modular_service import ModularService - -PARAM_STOPPED_AT = 'stoppedAt' -PARAM_STARTED_AT = 'startedAt' -PARAM_CREATED_AT = 'createdAt' -PARAM_DETAIL = 'detail' -PARAM_STATUS = 'status' - -PARAM_FAILED = 'FAILED' -PARAM_SUCCEEDED = 'SUCCEEDED' -PARAM_CREDENTIALS_KEY = 'CREDENTIALS_KEY' - -ENV_ACCOUNT_ID = 'ACCOUNT_ID' -ENV_SUBMITTED_AT = 'SUBMITTED_AT' -ENV_EVENT_DRIVEN = 'EVENT_DRIVEN' _LOG = get_logger('custodian-job-updater') -class JobUpdater(AbstractLambda): - - def __init__(self, job_service: JobService, ssm_service: SSMService, - license_manager_service: LicenseManagerService, - ruleset_service: RulesetService, - batch_results_service: BatchResultsService, - modular_service: ModularService): - self.job_service = job_service +class Env(TypedDict): + name: str + value: str + + +class StateChangeEventDetailContainer(TypedDict): + image: str + command: list[str] + volumes: list + environment: list[Env] + mountPoints: list + ulimits: list + networkInterfaces: list + resourceRequirements: list[dict] + secrets: list + + +class StateChangeEventDetail(TypedDict, total=False): + jobArn: str + jobName: str + jobId: str + jobQueue: str + status: str # JobState + attempts: list + createdAt: int | None # java ts + startedAt: int | None + stoppedAt: int | None + retryStrategy: dict + dependsOn: list + jobDefinition: str + parameters: dict + container: StateChangeEventDetailContainer + tags: dict + propagateTags: bool + platformCapabilities: list + + +class StateChangeEvent(TypedDict): + version: str + id: str + # detail-type + source: Literal['aws.batch'] + account: str + time: str # utc iso + region: str + resources: list[str] + detail: StateChangeEventDetail + + +class JobUpdaterHandler(EventProcessorLambdaHandler): + processors = () + + def __init__(self, ssm_service: SSMService, + license_manager_service: LicenseManagerService): self.ssm_service = ssm_service self.license_manager_service = license_manager_service - self.ruleset_service = ruleset_service - self.batch_result_service = batch_results_service - self.modular_service = modular_service - def _update_job_in_lm(self, job: Union[str, Job], detail: dict) -> int: - """ - Updates the job in License Manager. Returns alteration's status code. - """ - _LOG.info('The job is licensed. Updating job in License Manager') - if isinstance(job, Job): - _LOG.info(f'Job instance is given: {job}. Probably the job is ' - f'standard (scheduled)') - job_id = job.job_id - created_at = job.created_at - started_at = job.started_at - stopped_at = job.stopped_at - elif isinstance(job, str): - _LOG.info(f'Just job id is given: {job}. Probably the job ' - f'is event-driven') - job_id = job - _get_iso = lambda p: self.timestamp_to_iso(detail.get(p)) if p in detail else None - created_at = _get_iso(PARAM_CREATED_AT) - started_at = _get_iso(PARAM_STARTED_AT) - stopped_at = _get_iso(PARAM_STOPPED_AT) - else: - _LOG.error(f'Not available type of job: {job.__class__.__name__}. ' - f'Skipping.') - return 404 - return self.license_manager_service.update_job_in_license_manager( - job_id=job_id, - created_at=created_at, - started_at=started_at, - stopped_at=stopped_at, - status=detail['status'] - ) - - def process_standard_job(self, job_id: str, detail: dict, - environment: dict) -> None: - _LOG.info(f'Processing a standard job with id {job_id}') - job_item = self.job_service.get_job(job_id) - if not job_item: - _LOG.warning(f'Job with id {job_id} does not exist in DB. ' - f'It will be created') - job_item = self._create_job(job_id, environment) - actions = [] - if not job_item.created_at and detail.get(PARAM_CREATED_AT): - actions.append( - Job.created_at.set(self.timestamp_to_iso(detail[PARAM_CREATED_AT])) - ) - if not job_item.started_at and detail.get(PARAM_STARTED_AT): - actions.append( - Job.started_at.set(self.timestamp_to_iso(detail[PARAM_STARTED_AT])) + def update_standard(self, detail: StateChangeEventDetail, + environment: dict[str, str]): + _LOG.info('Updating a standard job') + job_id = environment.get(BatchJobEnv.CUSTODIAN_JOB_ID) + updater = JobUpdater.from_job_id(job_id) + updater.status = detail['status'] + if not updater.job.created_at and detail.get('createdAt'): + updater.created_at = self.timestamp_to_iso(detail['createdAt']) + if not updater.job.started_at and detail.get('startedAt'): + updater.started_at = self.timestamp_to_iso(detail['startedAt']) + if not updater.job.stopped_at and detail.get('stoppedAt'): + updater.stopped_at = self.timestamp_to_iso(detail['stoppedAt']) + if not updater.job.queue: + updater.queue = detail['jobQueue'] + if not updater.job.definition: + updater.definition = detail['jobDefinition'] + updater.update() + if detail['status'] in (JobState.FAILED, JobState.SUCCEEDED): + self._delete_temporary_credentials(environment) + + if detail.get('stoppedAt') and self.is_licensed_job(environment): + job = updater.job + self.license_manager_service.update_job_in_license_manager( + job_id=job.id, + created_at=job.created_at, + stopped_at=job.stopped_at, + started_at=job.started_at, + status=job.status ) - if detail.get(PARAM_STOPPED_AT): - if not job_item.stopped_at: - actions.append( - Job.stopped_at.set(self.timestamp_to_iso(detail[PARAM_STOPPED_AT])) - ) - if not job_item.job_queue: - actions.append( - Job.job_queue.set(detail['jobQueue']) - ) - if not job_item.job_definition: - actions.append( - Job.job_definition.set(detail['jobDefinition']) - ) - - if not job_item.scan_regions: - actions.append( - Job.scan_regions.set(self._get_job_scan_regions(environment)) - ) - - if not job_item.scan_rulesets: - actions.append( - Job.scan_rulesets.set(self._get_job_scan_rulesets(environment)) - ) - - actions.append(Job.status.set(detail[PARAM_STATUS])) - - if detail['status'] in (PARAM_FAILED, PARAM_SUCCEEDED): - self._delete_temporary_credentials(detail=detail) - job_item.update(actions) - - if detail.get(PARAM_STOPPED_AT) and self.is_licensed_job(environment): - code = self._update_job_in_lm(job=job_item, detail=detail) - if code == 404: - _LOG.warning( - f'No jobs with id \'{job_id}\' found on license ' - f'manager side.') - _LOG.info('Processing has finished') - - def process_event_driven_job(self, job_id: str, detail: dict, - environment: dict) -> None: - _LOG.info(f'Processing an event-driven job with id {job_id}') - batch_result = self.batch_result_service.get( - environment.get(BATCH_ENV_BATCH_RESULTS_ID) or 'mock-id' - ) - assert batch_result, 'How come an event-driven job is executed ' \ - 'without BatchResults item?' - if detail.get(PARAM_STOPPED_AT) and self.is_licensed_job(environment): - code = self._update_job_in_lm(job=job_id, detail=detail) - if code == 404: - _LOG.warning( - f'No jobs with id \'{job_id}\' found on license ' - f'manager side.') - batch_result.status = detail[PARAM_STATUS] - batch_result.job_id = job_id - _LOG.info(f'Saving an updated BatchResults item: {batch_result}') - batch_result.save() - _LOG.info('Processing has finished') - - def process_multi_account_event_driven_job(self, job_id, detail, - environment): - _LOG.info(f'Processing a multi account ' - f'event-driven job with id {job_id}') - status = detail[PARAM_STATUS] - if status == PARAM_SUCCEEDED: - # in case this job is succeeded, each BatchResult has its - # individual status which was set inside the job - return - ids: str = environment.get(BATCH_ENV_BATCH_RESULTS_IDS) - if not ids: - return - for _id in ids.split(','): - batch_result = self.batch_result_service.get(_id) - if not batch_result: - _LOG.warning(f'Batch result with id {_id} not found') - continue - batch_result.status = status - batch_result.job_id = job_id - batch_result.save() _LOG.info('Processing has finished') - def handle_request(self, event, context): - detail = event[PARAM_DETAIL] - job_id = detail[PARAM_NATIVE_JOB_ID] + def handle_request(self, event: dict, context): + dt = event.get('detail-type') + if dt != 'Batch Job State Change': + message = f'Not expected detail type came: {dt}. Skipping' + _LOG.warning(message) + return build_response(code=HTTPStatus.BAD_REQUEST, + content=message) + event = cast(StateChangeEvent, event) + detail = event['detail'] environment = self.convert_batch_environment( - detail.get('container', {}).get('environment', []) + detail['container']['environment'] ) - if environment.get(BATCH_ENV_JOB_TYPE) == BATCH_MULTI_ACCOUNT_EVENT_DRIVEN_JOB_TYPE: - self.process_multi_account_event_driven_job(job_id, detail, environment) - if environment.get(BATCH_ENV_JOB_TYPE) == BATCH_EVENT_DRIVEN_JOB_TYPE: - self.process_event_driven_job(job_id, detail, environment) - else: - self.process_standard_job(job_id, detail, environment) - return build_response(content={'job_id': job_id}) + # event driven jobs are updated inside batch + if environment.get(BatchJobEnv.JOB_TYPE) == BatchJobType.STANDARD: + # not scheduled and not ed + self.update_standard(detail, environment) + return build_response() @staticmethod def is_licensed_job(environment: dict) -> bool: @@ -200,82 +127,32 @@ def is_licensed_job(environment: dict) -> bool: Returns true in case the job is licensed. A licensed job is the one which involves at least one licensed ruleset. """ - return bool(environment.get(BATCH_ENV_AFFECTED_LICENSES)) + return bool(environment.get(BatchJobEnv.AFFECTED_LICENSES)) @staticmethod - def timestamp_to_iso(timestamp): + def timestamp_to_iso(timestamp: float) -> str: """ Batch events contains timestamps in UTC """ - date = datetime.fromtimestamp(timestamp / 1000, tz=timezone.utc) + date = datetime.fromtimestamp(timestamp / 1e3, tz=timezone.utc) return utc_iso(_from=date) - @staticmethod - def _get_job_scan_regions(environment: dict) -> list: - scan_regions = [] - _regions = environment.get(BATCH_ENV_TARGET_REGIONS) - if _regions and isinstance(_regions, str): - scan_regions.extend(_regions.split(',')) - if not scan_regions: - scan_regions.append('ALL') - return scan_regions - - @staticmethod - def _get_job_scan_rulesets(environment: dict) -> list: - scan_rulesets = [] - _rulesets = environment.get(BATCH_ENV_TARGET_RULESETS_VIEW) - if _rulesets and isinstance(_rulesets, str): - scan_rulesets.extend(_rulesets.split(',')) - _l_rulesets = environment.get(BATCH_ENV_LICENSED_RULESETS) - if _l_rulesets and isinstance(_l_rulesets, str): - scan_rulesets.extend( - each.split(':', maxsplit=1)[-1] - for each in _l_rulesets.split(',') if ':' in each - ) - - if not scan_rulesets: - scan_rulesets.append('ALL') - return scan_rulesets - - def _delete_temporary_credentials(self, detail): + def _delete_temporary_credentials(self, environment: dict[str, str]): _LOG.info(f'Deleting used temporary credentials secret') - job_envs = detail.get('container', {}).get('environment', []) - for job_env in job_envs: - if job_env.get('name') == PARAM_CREDENTIALS_KEY: - credentials_key = job_env.get('value') - _LOG.debug(f'Deleting smm parameter: \'{credentials_key}\'') - self.ssm_service.delete_secret( - secret_name=credentials_key) - break + key = environment.get(BatchJobEnv.CREDENTIALS_KEY) + if not key: + return + _LOG.debug(f'Deleting smm parameter: \'{key}\'') + self.ssm_service.delete_secret(secret_name=key) @staticmethod - def convert_batch_environment(environment: dict) -> dict: - envs = {} - for env in environment: - envs[env.get('name')] = env.get('value') - return envs - - def _create_job(self, job_id: str, environment: dict) -> Job: - tenant = self.modular_service.get_tenant( - environment.get(BATCH_ENV_TENANT_NAME)) - params = dict( - job_id=job_id, - job_owner=tenant.customer_name, - # because we cannot access user_id - tenant_display_name=tenant.name, - customer_display_name=tenant.customer_name, - submitted_at=environment.get(ENV_SUBMITTED_AT), - scheduled_rule_name=environment.get(BATCH_ENV_SCHEDULED_JOB_NAME)) - return self.job_service.create(params) + def convert_batch_environment(environment: list[Env]) -> dict: + return {env['name']: env['value'] for env in environment} -HANDLER = JobUpdater( - job_service=SERVICE_PROVIDER.job_service(), - ssm_service=SERVICE_PROVIDER.ssm_service(), - license_manager_service=SERVICE_PROVIDER.license_manager_service(), - ruleset_service=SERVICE_PROVIDER.ruleset_service(), - batch_results_service=SERVICE_PROVIDER.batch_results_service(), - modular_service=SERVICE_PROVIDER.modular_service() +HANDLER = JobUpdaterHandler( + ssm_service=SERVICE_PROVIDER.ssm_service, + license_manager_service=SERVICE_PROVIDER.license_manager_service, ) diff --git a/src/lambdas/custodian_job_updater/lambda_config.json b/src/lambdas/custodian_job_updater/lambda_config.json index 16000aa6d..42a9a0cb4 100644 --- a/src/lambdas/custodian_job_updater/lambda_config.json +++ b/src/lambdas/custodian_job_updater/lambda_config.json @@ -8,6 +8,7 @@ "memory": 128, "timeout": 100, "lambda_path": "/lambdas/custodian_job_updater", + "logs_expiration": "${logs_expiration}", "dependencies": [ { "resource_name": "caas-job-state-update", @@ -38,5 +39,8 @@ ], "layers": [ "custodian_common_dependencies_layer" + ], + "platforms": [ + "manylinux2014_x86_64" ] } \ No newline at end of file diff --git a/src/lambdas/custodian_job_updater/requirements.txt b/src/lambdas/custodian_job_updater/requirements.txt index d72daac61..820289d05 100644 --- a/src/lambdas/custodian_job_updater/requirements.txt +++ b/src/lambdas/custodian_job_updater/requirements.txt @@ -1 +1 @@ -pycryptodome==3.19.0 \ No newline at end of file +cryptography==42.0.2 \ No newline at end of file diff --git a/src/lambdas/custodian_license_updater/README.md b/src/lambdas/custodian_license_updater/README.md index 1da54fd91..f1864b133 100644 --- a/src/lambdas/custodian_license_updater/README.md +++ b/src/lambdas/custodian_license_updater/README.md @@ -13,7 +13,6 @@ It will update items in `CaaSLicenses` DynamoDB table and ruleset files in s3 bu - Allow: s3:PutObject #### DynamoDB -* CaaSLicenses * CaaSSettings * Customers diff --git a/src/lambdas/custodian_license_updater/handler.py b/src/lambdas/custodian_license_updater/handler.py index 4e8179079..72aaf5d17 100644 --- a/src/lambdas/custodian_license_updater/handler.py +++ b/src/lambdas/custodian_license_updater/handler.py @@ -1,56 +1,42 @@ +import operator from concurrent.futures import ( ThreadPoolExecutor, as_completed, CancelledError, TimeoutError ) from http import HTTPStatus +from itertools import chain from json.decoder import JSONDecodeError -from typing import List, Any, Dict, Tuple -from typing import Union, Type, Callable, Optional +from typing import Dict, Type, Optional from modular_sdk.commons.constants import ApplicationType +from modular_sdk.models.application import Application +from modular_sdk.services.application_service import ApplicationService +from modular_sdk.services.customer_service import CustomerService from requests import Response, ConnectionError, RequestException -from helpers import ( - raise_error_response, build_response, get_missing_parameters, - CustodianException -) +from helpers import get_missing_parameters from helpers.constants import CUSTOMERS_ATTR, RULESETS_ATTR, \ NAME_ATTR, VERSION_ATTR, CLOUD_ATTR, ID_ATTR, RULES_ATTR, \ EXPIRATION_ATTR, LATEST_SYNC_ATTR, LICENSE_KEYS_ATTR, EVENT_DRIVEN_ATTR +from helpers.lambda_response import build_response, ResponseFactory, \ + CustodianException from helpers.log_helper import get_logger from helpers.system_customer import SYSTEM_CUSTOMER from helpers.time_helper import utc_iso -from models.licenses import License -from models.modular import BaseModel -from models.modular.application import CustodianLicensesApplicationMeta from models.ruleset import Ruleset from services import SERVICE_PROVIDER -from services.abstract_lambda import AbstractLambda +from services.abs_lambda import EventProcessorLambdaHandler from services.clients.s3 import S3Client from services.environment_service import EnvironmentService from services.license_manager_service import LicenseManagerService -from services.license_service import LicenseService -from services.modular_service import ModularService +from services.license_service import LicenseService, License from services.ruleset_service import RulesetService _LOG = get_logger('custodian-license-updater') -LICENSE_HASH_KEY = 'license_key' -VALID_UNTIL_ATTR = 'valid_until' -RULESET_CONTENT_ATTR = 'ruleset_content' -LIMITATIONS_ATTR = 'limitations' -ALLOWANCE_ATTR = 'allowance' FUTURE_TIMEOUT = COMPLETED_TIMEOUT = None -IMPROPER_TYPE_CONTENT = '\'license_key\' parameter must be ' \ - 'expressed as a list.' -IMPROPER_SUBTYPE_CONTENT = '\'license_key\' must only contain string elements.' -GENERIC_LICENSE_ABSENCE = 'A given license key has not been found.' LICENSE_BOUND = 'License:\'{key}\'. ' RULESET_BOUND = 'Ruleset:\'{_id}\'. ' -LICENSE_NOT_FOUND = 'Could not be found.' -COMMENCE_SUBJECTION = '{} license(s) has(ve) been subjected to be synced.' -SYNC_RESPONSE_OK = 'Synchronization for \'{}\' license has been successful.' -SYNC_NOT_COMMENCED = 'No license has been synchronized.' SYNC_CANCELLED = 'Synchronization has been cancelled, due to' \ ' the following reason. \'{}\'.' @@ -62,10 +48,6 @@ SKIP_CONSEQUENCE = 'Skipping.' HALTING_CONSEQUENCE = 'Halting.' -GENERIC_ERROR_RESPONSE = 'Execution has ran into a problem, ' \ - 'the request has been subdued.' -HALT_TEMPLATE = 'Lambda invocation has been deemed to halt, due to ' \ - 'the following: {}' CONFOUNDING_RESPONSE = 'A synchronization request has encountered an unknown' \ ' response: {}.' DECODING_ERROR = 'A synchronization response contains a malformed ' \ @@ -90,23 +72,27 @@ ATTR_UPDATED = '\'{attr}\' attribute has been updated to \'{value}\'.' -class LicenseUpdater(AbstractLambda): +class LicenseUpdater(EventProcessorLambdaHandler): + processors = () + def __init__(self, license_service: LicenseService, license_manager_service: LicenseManagerService, ruleset_service: RulesetService, s3_client: S3Client, environment_service: EnvironmentService, - modular_service: ModularService): + customer_service: CustomerService, + application_service: ApplicationService): self.license_service = license_service self.license_manager_service = license_manager_service self.ruleset_service = ruleset_service self.s3_client = s3_client self.ruleset_bucket = environment_service.get_rulesets_bucket_name() - self.modular_service = modular_service + self.application_service = application_service + self.customer_service = customer_service # Describes required response parameters for respective entities self._response_parameters = { - License: (LICENSE_HASH_KEY, VALID_UNTIL_ATTR), - Ruleset: (ID_ATTR, NAME_ATTR, CLOUD_ATTR, RULESET_CONTENT_ATTR) + License: ('license_key', 'valid_until'), + Ruleset: (ID_ATTR, NAME_ATTR, CLOUD_ATTR, 'ruleset_content') } self._response_handler_dispatcher = { @@ -137,47 +123,27 @@ def handle_request(self, event, context): # Establish the subjected licenses, by accepting if any # or retrieving each non expired. - _hash_keys = event.get(LICENSE_HASH_KEY, []) - if _hash_keys: + license_keys = event.get('license_key', []) + if license_keys: # Retrieve license entities, based on the string license_keys. - _licenses: List[License] = list(map( - self.license_service.get_license, _hash_keys - )) - _invalid_key_gen = (_hash_keys[index] for index, each - in enumerate(_licenses) if each is None) - _invalid_key = next(_invalid_key_gen, None) - if _invalid_key: - _LOG.error( - LICENSE_BOUND.format(key=_invalid_key) + LICENSE_NOT_FOUND - ) - raise_error_response(HTTPStatus.NOT_FOUND, - GENERIC_LICENSE_ABSENCE) + licenses = list(filter(None, map( + self.application_service.get_application_by_id, license_keys + ))) else: - _licenses: List[License] = \ - self.license_service.get_all_non_expired_licenses() - - _LOG.debug(COMMENCE_SUBJECTION.format(len(_licenses))) - - _processed = self._process_license_list(_licenses) \ - if _licenses else None + customers = map(operator.attrgetter('name'), + self.customer_service.i_get_customer()) + licenses = list(chain.from_iterable( + self.application_service.i_get_application_by_customer(name, + ApplicationType.CUSTODIAN_LICENSES.value, + deleted=False) + for name in customers + )) + licenses = list(map(License, licenses)) - if _processed: - _key_stream = ', '.join(each.license_key for each in _processed) - message = SYNC_RESPONSE_OK.format(_key_stream) - _LOG.info(message) - return build_response( - code=HTTPStatus.OK, - content=message - ) - else: - _LOG.warning(SYNC_NOT_COMMENCED) - return build_response( - code=HTTPStatus.NOT_FOUND, - content=SYNC_NOT_COMMENCED - ) + _processed = self._process_license_list(licenses) + return build_response() - def _process_license_list(self, _licenses: List[License]) -> \ - Union[List[License], List]: + def _process_license_list(self, licenses: list[License]): """ Commences the batched license synchronization, awaiting respective futures, executed with the ThreadPoolExecutor. @@ -193,22 +159,20 @@ def _process_license_list(self, _licenses: List[License]) -> \ _halt: Optional[CustodianException] = None # Establish map-references. - _license_map: Dict[str, License] = { - each.license_key: each for each in _licenses - } + _license_map = {each.license_key: each for each in licenses} - _ruleset_map: Dict[str, Ruleset] = {} + _ruleset_map: dict[str, Ruleset] = {} # Establish reference to the previous state of attached rulesets. - stale_license_ruleset_map: Dict[str: List[str]] = { + stale_license_ruleset_map = { each.license_key: (each.ruleset_ids or []) - for each in _licenses + for each in licenses } # Stores list of obsolete License-Keys. - _canceled: List[License] = [] + _canceled: list[License] = [] # Stores list of prepared ruleset id(s) to retain. - _prepared: List[License] = [] + _prepared: list[License] = [] ruleset_head = 'Ruleset:\'{}\'' @@ -216,7 +180,7 @@ def _process_license_list(self, _licenses: List[License]) -> \ # Store could-a-be obsolete ruleset_ids _future_licenses = { _executor.submit(self._process_license, _l): _l.license_key - for _l in _licenses + for _l in licenses } for _future in as_completed(_future_licenses, COMPLETED_TIMEOUT): @@ -227,7 +191,7 @@ def _process_license_list(self, _licenses: List[License]) -> \ # Updates state of already accessible license objects. # Retrieves ruleset entities with pre-related license-keys. # See prepare ruleset action. - rulesets: List[Ruleset] = _future.result(FUTURE_TIMEOUT) + rulesets: list[Ruleset] = _future.result(FUTURE_TIMEOUT) _prepared.append(_license_map[_key]) for ruleset in rulesets: rid = ruleset.id @@ -317,19 +281,15 @@ def _process_license_list(self, _licenses: List[License]) -> \ store = remove_rulesets if no_keys else save_rulesets store.append(ruleset) - synchronized = self._handle_persistence( + self._handle_persistence( licenses_to_save=_prepared, licenses_to_remove=_canceled, rulesets_to_remove=remove_rulesets, rulesets_to_save=save_rulesets ) if _halt: - _response_content = HALT_TEMPLATE.format(GENERIC_ERROR_RESPONSE) - _LOG.error(HALT_TEMPLATE.format(_halt.content)) - raise_error_response(_halt.code, _response_content) - - return synchronized + raise _halt - def _process_license(self, _license: License) -> Optional[List[Ruleset]]: + def _process_license(self, _license: License) -> list[Ruleset] | None: """ Sends a synchronization request to the external resource, which response of which is taken on by the designated handlers, @@ -340,14 +300,14 @@ def _process_license(self, _license: License) -> Optional[List[Ruleset]]: _response = self.license_manager_service.synchronize_license( license_key=_license.license_key ) - _dispatcher: Dict[Type, Callable] = self._response_handler_dispatcher - _handler: Callable[[License, Any], Any] = _dispatcher.get( + _dispatcher = self._response_handler_dispatcher + _handler = _dispatcher.get( _response.__class__, self._default_response_handler ) return _handler(_license, _response) - def _handle_license_update(self, _license: License, _response: Response) \ - -> List[Ruleset]: + def _handle_license_update(self, _license: License, _response: Response + ) -> list[Ruleset]: """ Handles License entity synchronization based update, driven by an external respective response, by adhering to state and required @@ -396,15 +356,15 @@ def _handle_license_update(self, _license: License, _response: Response) \ attr=RULESETS_ATTR, value=_license.ruleset_ids )) - _license.expiration = item[VALID_UNTIL_ATTR] + _license.expiration = item['valid_until'] _LOG.debug(_license_bound + ATTR_UPDATED.format( attr=EXPIRATION_ATTR, value=_license.expiration )) - _allowance = item.get(ALLOWANCE_ATTR) + _allowance = item.get('allowance') _license.allowance = _allowance _LOG.debug(_license_bound + ATTR_UPDATED.format( - attr=ALLOWANCE_ATTR, value=_allowance + attr='allowance', value=_allowance )) _license.latest_sync = utc_iso() @@ -421,8 +381,8 @@ def _handle_license_update(self, _license: License, _response: Response) \ return _ruleset_list - def _prepare_licensed_ruleset(self, _body: Dict, _license: License) \ - -> Ruleset: + def _prepare_licensed_ruleset(self, _body: Dict, _license: License + ) -> Ruleset: """ Prepares a licensed Ruleset entity, derived from a given response body which is validated for id, name and cloud attributes. @@ -472,9 +432,8 @@ def _prepare_licensed_ruleset(self, _body: Dict, _license: License) \ license_manager_id=_body[ID_ATTR] ) - def _get_required_response_parameters( - self, key: Type[Union[License, Ruleset]] - ) -> Tuple[str]: + def _get_required_response_parameters(self, key: Type[License | Ruleset] + ) -> tuple[str]: """ Returns required outbound synchronization response parameters, respective of each entity instantiation|update. @@ -482,102 +441,29 @@ def _get_required_response_parameters( """ return self._response_parameters.get(key, tuple()) - def _handle_persistence(self, licenses_to_save: List[License], - rulesets_to_save: List[Ruleset], - licenses_to_remove: List[License], - rulesets_to_remove: List[Ruleset]): + def _handle_persistence(self, licenses_to_save: list[License], + rulesets_to_save: list[Ruleset], + licenses_to_remove: list[License], + rulesets_to_remove: list[Ruleset]): """ Mandates persistence concern of affected license and ruleset entities. :return: List[License] """ - - batch_context = { - 'delete': { - Ruleset: (rulesets_to_remove, 'id'), - License: (licenses_to_remove, 'license_key') - }, - 'save': { - Ruleset: (rulesets_to_save, 'id'), - License: (licenses_to_save, 'license_key') - } - } - - retained = [] - - for action, context in batch_context.items(): - for model, payload in context.items(): - data, attr = payload - if data: - _stringed = ', '.join(getattr(i, attr, None) for i in data) - _type = model.__name__ - _LOG.info(f'Going to {action}: {_stringed} {_type}(s).') - - with model.batch_write() as writer: - _action: Callable = getattr(writer, action) - persisted = self._batch_action( - id_attr=attr, type_attr=_type, - items=data, action=_action, - action_label=f'{action}d' - ) - if action == 'save' and model == License: - retained = persisted - - # by now licenses_to_remove already removed with their rule-sets. - # We need to remove them from applications. Here I just clean - # application in case the license is removed. Maybe this logic - # must be rewritten - for _license in licenses_to_remove: - for customer in _license.customers.as_dict(): - apps = self.modular_service.get_applications( - customer=customer, - _type=ApplicationType.CUSTODIAN_LICENSES.value - ) - for app in apps: - meta = CustodianLicensesApplicationMeta( - **app.meta.as_dict()) - for cloud, lk in meta.cloud_to_license_key().items(): - if lk == _license.license_key: - meta.update_license_key(cloud, None) - app.meta = meta.dict() - self.modular_service.save(app) - return retained - - @staticmethod - def _batch_action( - action: Callable[[BaseModel], Any], items: List[BaseModel], - id_attr: str, type_attr: str, action_label: str - ) -> List[BaseModel]: - """ - Commences given action per each provided entity within `items`, - logging out result of each said issue. - - :parameter action: Callable[[BaseModel], Any] - :parameter items: List[BaseModel] - :parameter id_attr: str - :parameter type_attr: str - :return: None - """ - head = '{}:\'{}\'' - _type = type_attr - commenced = [] - for item in items: - _id = getattr(item, id_attr, None) - if _id is None: - _LOG.error( - f'{_type}: could not resolve {id_attr} attribute.' - ) - _head = head.format(_type, _id) - try: - action(item) - except (Exception, BaseException) as e: - issue = f' could not be {action_label}, due to - {e}' - _LOG.error(_head + issue) - continue - - commenced.append(item) - _LOG.info(_head + f' has been {action_label}.') - - return commenced + with Ruleset.batch_write() as writer: + for r in rulesets_to_save: + writer.save(r) + for r in rulesets_to_remove: + writer.delete(r) + for lc in licenses_to_remove: + # todo add batch operations to modular sdk + self.application_service.force_delete(lc.application) + for lc in licenses_to_save: + # only meta is updated + self.application_service.update( + application=lc.application, + attributes=[Application.meta], + updated_by='custodian service' + ) @staticmethod def _handle_unknown_response(_license: License, _response): @@ -590,9 +476,8 @@ def _handle_unknown_response(_license: License, _response): """ _license_bound = LICENSE_BOUND.format(key=_license.license_key) content = _license_bound + CONFOUNDING_RESPONSE.format(_response) - raise CustodianException( - code=HTTPStatus.INTERNAL_SERVER_ERROR, content=content - ) + raise ResponseFactory(HTTPStatus.INTERNAL_SERVER_ERROR).message( + content).exc() @staticmethod def _handle_connection_error( @@ -608,10 +493,9 @@ def _handle_connection_error( """ _license_bound = LICENSE_BOUND.format(key=_license.license_key) content = _license_bound + REQUEST_ERROR.format(_response) - raise CustodianException( - code=HTTPStatus.INTERNAL_SERVER_ERROR, - content=content + HALTING_CONSEQUENCE - ) + raise ResponseFactory(HTTPStatus.INTERNAL_SERVER_ERROR).message( + content + HALTING_CONSEQUENCE + ).exc() @staticmethod def _handle_request_error(_license: License, _response: RequestException): @@ -628,9 +512,8 @@ def _handle_request_error(_license: License, _response: RequestException): raise CancelledError(content + SKIP_CONSEQUENCE) @staticmethod - def _attain_response_body( - license_entity: License, response: Response - ) -> Optional[dict]: + def _attain_response_body(license_entity: License, response: Response + ) -> dict | None: """ Provides outbound LicenseManager synchronization response validation of: @@ -649,10 +532,9 @@ def _attain_response_body( items = response.json() except JSONDecodeError as _je: content = _license_bound + DECODING_ERROR.format(_je) - raise CustodianException( - code=HTTPStatus.INTERNAL_SERVER_ERROR, - content=content + HALTING_CONSEQUENCE - ) + raise ResponseFactory(HTTPStatus.INTERNAL_SERVER_ERROR).message( + content + HALTING_CONSEQUENCE + ).exc() _LOG.debug( _validation_template.format(STATUS_CODE.format(HTTPStatus.OK)) @@ -678,9 +560,9 @@ def _attain_response_body( return item if isinstance(item, dict) else {} @staticmethod - def _validate_outbound_response_parameters( - _body: Dict, _required: Tuple, _bound: str - ) -> Type[None]: + def _validate_outbound_response_parameters(_body: dict, + _required: tuple, + _bound: str) -> None: """ Provides outbound response validation of required parameters, retrieving respective keys. Given any missing key a @@ -701,12 +583,9 @@ def _validate_outbound_response_parameters( if isinstance(_body, dict) else _required if _missing: _missing_stream = ', '.join(_missing) - raise CustodianException( - code=HTTPStatus.INTERNAL_SERVER_ERROR, - content=_bound + MISSING_PARAMETER_ERROR.format( - keys=_missing_stream - ) - ) + raise ResponseFactory(HTTPStatus.INTERNAL_SERVER_ERROR).message( + _bound + MISSING_PARAMETER_ERROR.format(keys=_missing_stream) + ).exc() _LOG.info(_bound + VALIDATED_RESPONSE.format( data=RESPONSE_PARAMETERS.format(parameters=_required_stream) @@ -714,12 +593,13 @@ def _validate_outbound_response_parameters( HANDLER = LicenseUpdater( - license_service=SERVICE_PROVIDER.license_service(), - license_manager_service=SERVICE_PROVIDER.license_manager_service(), - ruleset_service=SERVICE_PROVIDER.ruleset_service(), - s3_client=SERVICE_PROVIDER.s3(), - modular_service=SERVICE_PROVIDER.modular_service(), - environment_service=SERVICE_PROVIDER.environment_service() + license_service=SERVICE_PROVIDER.license_service, + license_manager_service=SERVICE_PROVIDER.license_manager_service, + ruleset_service=SERVICE_PROVIDER.ruleset_service, + s3_client=SERVICE_PROVIDER.s3, + environment_service=SERVICE_PROVIDER.environment_service, + customer_service=SERVICE_PROVIDER.modular_client.customer_service(), + application_service=SERVICE_PROVIDER.modular_client.application_service() ) diff --git a/src/lambdas/custodian_license_updater/lambda_config.json b/src/lambdas/custodian_license_updater/lambda_config.json index 5f72afb0b..d64fc25e5 100644 --- a/src/lambdas/custodian_license_updater/lambda_config.json +++ b/src/lambdas/custodian_license_updater/lambda_config.json @@ -8,14 +8,11 @@ "memory": 128, "timeout": 300, "lambda_path": "/lambdas/custodian_license_updater", + "logs_expiration": "${logs_expiration}", "dependencies": [ { "resource_name": "caas-license-update-trigger", "resource_type": "cloudwatch_rule_trigger" - }, - { - "resource_name": "CaaSLicenses", - "resource_type": "dynamodb_table" } ], "event_sources": [ @@ -25,7 +22,7 @@ } ], "env_variables": { - "caas_rulesets_bucket": "${caas_rulesets_bucket}", + "CAAS_RULESETS_BUCKET_NAME": "${caas_rulesets_bucket}", "modular_assume_role_arn": "${modular_assume_role_arn}", "MODULAR_AWS_REGION": "${MODULAR_AWS_REGION}" }, @@ -39,5 +36,8 @@ ], "layers": [ "custodian_common_dependencies_layer" + ], + "platforms": [ + "manylinux2014_x86_64" ] } \ No newline at end of file diff --git a/src/lambdas/custodian_license_updater/requirements.txt b/src/lambdas/custodian_license_updater/requirements.txt index d72daac61..820289d05 100644 --- a/src/lambdas/custodian_license_updater/requirements.txt +++ b/src/lambdas/custodian_license_updater/requirements.txt @@ -1 +1 @@ -pycryptodome==3.19.0 \ No newline at end of file +cryptography==42.0.2 \ No newline at end of file diff --git a/src/lambdas/custodian_metrics_updater/deployment_resources.json b/src/lambdas/custodian_metrics_updater/deployment_resources.json index f7066e942..7c8ad3623 100644 --- a/src/lambdas/custodian_metrics_updater/deployment_resources.json +++ b/src/lambdas/custodian_metrics_updater/deployment_resources.json @@ -44,5 +44,10 @@ "resource_type": "cloudwatch_rule", "rule_type": "schedule", "expression": "cron(0 0 * * ? *)" + }, + "caas-diagnostic-report-trigger": { + "resource_type": "cloudwatch_rule", + "rule_type": "schedule", + "expression": "cron(0 4 1 * ? *)" } } \ No newline at end of file diff --git a/src/lambdas/custodian_metrics_updater/handler.py b/src/lambdas/custodian_metrics_updater/handler.py index 1e8138801..8663b7090 100644 --- a/src/lambdas/custodian_metrics_updater/handler.py +++ b/src/lambdas/custodian_metrics_updater/handler.py @@ -1,34 +1,46 @@ from datetime import datetime from http import HTTPStatus -from typing import Dict from modular_sdk.commons.trace_helper import tracer_decorator -from helpers import raise_error_response, \ - get_logger, build_response from helpers.constants import DATA_TYPE -from helpers.exception import MetricsUpdateException, CustodianException -from lambdas.custodian_metrics_updater.processors.findings_processor \ - import FINDINGS_UPDATER -from lambdas.custodian_metrics_updater.processors. \ - metric_difference_processor import TENANT_METRICS_DIFF -from lambdas.custodian_metrics_updater.processors.recommendation_processor \ - import RECOMMENDATION_METRICS -from lambdas.custodian_metrics_updater.processors. \ - tenant_group_metrics_processor import TENANT_GROUP_METRICS -from lambdas.custodian_metrics_updater.processors. \ - tenant_metrics_processor import TENANT_METRICS -from lambdas.custodian_metrics_updater.processors.top_metrics_processor import \ - CUSTOMER_METRICS +from helpers.lambda_response import ( + CustodianException, + MetricsUpdateException, + ResponseFactory, + build_response, +) +from lambdas.custodian_metrics_updater.processors.diagnostic_metrics_processor import ( + DIAGNOSTIC_METRICS, +) +from lambdas.custodian_metrics_updater.processors.findings_processor import ( + FINDINGS_UPDATER, +) +from lambdas.custodian_metrics_updater.processors.metric_difference_processor import ( + TENANT_METRICS_DIFF, +) +from lambdas.custodian_metrics_updater.processors.recommendation_processor import ( + RECOMMENDATION_METRICS, +) +from lambdas.custodian_metrics_updater.processors.tenant_group_metrics_processor import ( + TENANT_GROUP_METRICS, +) +from lambdas.custodian_metrics_updater.processors.tenant_metrics_processor import ( + TENANT_METRICS, +) +from lambdas.custodian_metrics_updater.processors.top_metrics_processor import ( + CUSTOMER_METRICS, +) from services import SERVICE_PROVIDER -from services.abstract_lambda import AbstractLambda +from services.abs_lambda import EventProcessorLambdaHandler from services.clients.lambda_func import LambdaClient METRICS_UPDATER_LAMBDA_NAME = 'caas-metrics-updater' -_LOG = get_logger(__name__) -class MetricsUpdater(AbstractLambda): +class MetricsUpdater(EventProcessorLambdaHandler): + processors = () + def __init__(self, lambda_client: LambdaClient): self.lambda_client = lambda_client @@ -38,7 +50,8 @@ def __init__(self, lambda_client: LambdaClient): 'customer': CUSTOMER_METRICS, 'difference': TENANT_METRICS_DIFF, 'findings': FINDINGS_UPDATER, - 'recommendations': RECOMMENDATION_METRICS + 'recommendations': RECOMMENDATION_METRICS, + 'diagnostic': DIAGNOSTIC_METRICS } self.today = datetime.utcnow().date() @@ -49,38 +62,40 @@ def handle_request(self, event, context): data_pipeline_type = event.get(DATA_TYPE) handler_function = self.PIPELINE_TYPE_MAPPING.get(data_pipeline_type) if not handler_function: - raise_error_response( - HTTPStatus.BAD_REQUEST.value, - f'Cannot resolve pipeline type {data_pipeline_type}') + raise ResponseFactory(HTTPStatus.BAD_REQUEST).message( + f'Cannot resolve pipeline type {data_pipeline_type}' + ).exc() try: next_lambda_event = handler_function.process_data(event) if next_lambda_event.get(DATA_TYPE): self._invoke_next_step(next_lambda_event, METRICS_UPDATER_LAMBDA_NAME) except CustodianException as e: + resp = e.response raise MetricsUpdateException( - code=e.code, - content=f'Stage {data_pipeline_type}: {e.content}') + response=ResponseFactory(resp.code).message( + f'Stage {data_pipeline_type}: {resp.content}' + ) + ) except Exception as e: raise MetricsUpdateException( - code=HTTPStatus.INTERNAL_SERVER_ERROR.value, - content=f'Stage {data_pipeline_type}: {e}') - + response=ResponseFactory(HTTPStatus.INTERNAL_SERVER_ERROR).message( + f'Stage {data_pipeline_type}: {e}' + ) + ) return build_response( - content=f'Stage \'{data_pipeline_type}\' executed successfully.') + content=f'Stage \'{data_pipeline_type}\' executed successfully.' + ) - def _invoke_next_step(self, event: Dict[str, str], + def _invoke_next_step(self, event: dict[str, str], lambda_name: str = METRICS_UPDATER_LAMBDA_NAME): - _LOG.debug(f'Invocation of \'{lambda_name}\' lambda ' - f'with event: {event}') response = self.lambda_client.invoke_function_async( function_name=lambda_name, event=event) - _LOG.debug(f'Response: {response}') return response HANDLER = MetricsUpdater( - lambda_client=SERVICE_PROVIDER.lambda_func() + lambda_client=SERVICE_PROVIDER.lambda_client ) diff --git a/src/lambdas/custodian_metrics_updater/lambda_config.json b/src/lambdas/custodian_metrics_updater/lambda_config.json index a17f42ead..2fdf3d4d8 100644 --- a/src/lambdas/custodian_metrics_updater/lambda_config.json +++ b/src/lambdas/custodian_metrics_updater/lambda_config.json @@ -6,8 +6,9 @@ "iam_role_name": "metrics-updater-role", "runtime": "python3.10", "memory": 512, - "timeout": 300, + "timeout": 900, "lambda_path": "/lambdas/custodian_metrics_updater", + "logs_expiration": "${logs_expiration}", "dependencies": [ { "resource_name": "CaaSJobs", @@ -37,19 +38,30 @@ }, { "resource_type": "cloudwatch_rule_trigger", - "target_rule": "caas-findings-updater-trigger" + "target_rule": "caas-findings-updater-trigger", + "input": { + "data_type": "findings" + } + }, + { + "resource_type": "cloudwatch_rule_trigger", + "target_rule": "caas-diagnostic-report-trigger", + "input": { + "data_type": "diagnostic" + } } ], "env_variables": { - "caas_rulesets_bucket": "${caas_rulesets_bucket}", - "stats_s3_bucket_name": "${stats_s3_bucket_name}", - "modular_assume_role_arn": "${modular_assume_role_arn}", - "MODULAR_AWS_REGION": "${MODULAR_AWS_REGION}", + "CAAS_RULESETS_BUCKET_NAME": "${caas_rulesets_bucket}", + "CAAS_STATISTICS_BUCKET_NAME": "${stats_s3_bucket_name}", "application_name": "custodian-service", "component_name": "custodian-as-a-service", - "metrics_bucket_name": "${caas_metrics_bucket_name}", - "lambdas_alias_name": "${lambdas_alias_name}", - "caas_recommendations_bucket": "${caas_recommendations_bucket}" + "CAAS_METRICS_BUCKET_NAME": "${caas_metrics_bucket_name}", + "CAAS_LAMBDA_ALIAS_NAME": "${lambdas_alias_name}", + "CAAS_RECOMMENDATIONS_BUCKET_NAME": "${caas_recommendations_bucket}", + "CAAS_REPORTS_BUCKET_NAME": "${reports-bucket}", + "modular_assume_role_arn": "${modular_assume_role_arn}", + "MODULAR_AWS_REGION": "${MODULAR_AWS_REGION}" }, "publish_version": true, "alias": "${lambdas_alias_name}", @@ -61,5 +73,8 @@ ], "layers": [ "custodian_common_dependencies_layer" + ], + "platforms": [ + "manylinux2014_x86_64" ] } \ No newline at end of file diff --git a/src/lambdas/custodian_metrics_updater/processors/customer_metrics_processor.py b/src/lambdas/custodian_metrics_updater/processors/customer_metrics_processor.py deleted file mode 100644 index bd0a7da8c..000000000 --- a/src/lambdas/custodian_metrics_updater/processors/customer_metrics_processor.py +++ /dev/null @@ -1,282 +0,0 @@ -import json -from datetime import datetime, timedelta, date - -from dateutil.relativedelta import relativedelta, MO - -from helpers import get_logger -from helpers.constants import OVERVIEW_TYPE, DATA_TYPE, START_DATE -from services import SERVICE_PROVIDER -from services.batch_results_service import BatchResultsService -from services.clients.s3 import S3Client -from services.environment_service import EnvironmentService -from services.findings_service import FindingsService -from services.job_service import JobService -from services.job_statistics_service import JobStatisticsService -from services.modular_service import ModularService -from services.metrics_service import TenantMetricsService, \ - CustomerMetricsService -from services.rule_meta_service import RuleMetaService -from services.setting_service import SettingsService - -_LOG = get_logger(__name__) - -CLOUDS = ['aws', 'azure', 'google'] -SEVERITIES = ['Critical', 'High', 'Medium', 'Low', 'Info'] - -TENANT_GROUP_METRICS_FILE_PATH = '{customer}/tenants/{date}/{tenant}.json' -TENANT_GROUP_METRICS_FOLDER_PATH = '{customer}/tenants/{date}/' - -TYPE_ATTR = 'type' -NEXT_STEP = 'difference' - - -class CustomerMetrics: - def __init__(self, s3_client: S3Client, - environment_service: EnvironmentService, - settings_service: SettingsService, - tenant_metrics_service: TenantMetricsService, - customer_metrics_service: CustomerMetricsService, - modular_service: ModularService, findings_service: FindingsService, - job_statistics_service: JobStatisticsService, - job_service: JobService, rule_meta_service: RuleMetaService, - batch_results_service: BatchResultsService): - self.s3_client = s3_client - self.environment_service = environment_service - self.settings_service = settings_service - self.tenant_metrics_service = tenant_metrics_service - self.customer_metrics_service = customer_metrics_service - self.modular_service = modular_service - self.findings_service = findings_service - self.job_statistics_service = job_statistics_service - self.job_service = job_service - self.batch_results_service = batch_results_service - self.rule_meta_service = rule_meta_service - - self.today_date = datetime.utcnow().today().date() - self._date_marker = self.settings_service.get_report_date_marker() - self.month_first_day = self.today_date.replace(day=1) - self.last_month_date = ( - self.month_first_day - timedelta(days=1)).replace(day=1) - self.metadata = {} - - self.CUSTOMER_OVERVIEW = None - - def process_data(self, event): - metrics_bucket = self.environment_service.get_metrics_bucket_name() - customers = set( - customer.split('/')[0] - for customer in self.s3_client.list_dir(bucket_name=metrics_bucket) - ) - customers_to_process = [customer for customer in customers if - not self.current_month_customer_metrics_exist( - customer)] - - for customer in customers_to_process: - tenant_to_object = {} - self.CUSTOMER_OVERVIEW = {c: {'total_scanned_tenants': 0, - 'last_scan_date': None, - 'resources_violated': 0, - 'succeeded_scans': 0, - 'failed_scans': 0, - 'severity_data': {}, - 'resource_types_data': {}, - 'total_scans': 0} for c in CLOUDS} - - weekly_data = self._get_weekly_statistics( - customer, self.last_month_date, self.month_first_day) - if not weekly_data: - _LOG.debug(f'There is no data for customer {customer}') - continue - - for data in weekly_data: - for acc in (data.tenants or []): - if acc not in tenant_to_object: - tenant_obj = self._get_or_create_tenant_object( - tenant_to_object, acc) - if not tenant_obj: - _LOG.warning(f'No tenant with project id {acc}') - continue - cloud = tenant_obj.cloud.lower() - self._update_customer_overview_with_findings( - cloud, tenant_to_object.get(acc)) - self._update_customer_overview_with_scan_data(data) - - self._update_customer_overview_with_total_scanned_tenants( - tenant_to_object) - items = self._collect_chief_overview_item( - customer=customer, customer_data=self.CUSTOMER_OVERVIEW) - self.customer_metrics_service.batch_save([items]) - - return {DATA_TYPE: NEXT_STEP, START_DATE: event.get(START_DATE), - 'continuously': event.get('continuously')} - - def _get_or_create_tenant_object(self, tenant_to_object, acc): - if acc not in tenant_to_object: - tenant_obj = next(self.modular_service.i_get_tenants_by_acc( - acc), None) - if not tenant_obj: - _LOG.warning( - f'Unknown tenant with id {acc}. Skipping...') - return None - tenant_to_object[acc] = tenant_obj - return tenant_to_object.get(acc) - - def _update_customer_overview_with_findings(self, cloud, tenant_obj): - findings = self._get_last_month_findings(tenant_obj.project) - self.CUSTOMER_OVERVIEW[cloud]['resources_violated'] += len( - self.findings_service.unique_resources_from_raw_findings( - findings)) - severity_type_data = self.get_number_of_resources_by_severity_and_type( - findings) - for s, v in severity_type_data.get('severity_data', - {}).items(): - self.CUSTOMER_OVERVIEW[cloud]['severity_data'].setdefault(s, 0) - self.CUSTOMER_OVERVIEW[cloud]['severity_data'][s] += v - for t, v in severity_type_data.get('resource_types_data', - {}).items(): - self.CUSTOMER_OVERVIEW[cloud]['resource_types_data'].setdefault( - t, 0) - self.CUSTOMER_OVERVIEW[cloud]['resource_types_data'][t] += v - - def _update_customer_overview_with_scan_data(self, data): - self.CUSTOMER_OVERVIEW[data.cloud]['succeeded_scans'] += \ - data.succeeded - self.CUSTOMER_OVERVIEW[data.cloud]['failed_scans'] += \ - data.failed - self.CUSTOMER_OVERVIEW[data.cloud]['total_scans'] += \ - data.failed + data.succeeded - - last_scan = self.CUSTOMER_OVERVIEW[data.cloud]['last_scan_date'] - if not last_scan or last_scan < data.last_scan_date: - self.CUSTOMER_OVERVIEW[data.cloud]['last_scan_date'] = \ - data.last_scan_date - - def _update_customer_overview_with_total_scanned_tenants( - self, tenant_to_object): - for c in CLOUDS: - if not self.CUSTOMER_OVERVIEW.get(c): - continue - self.CUSTOMER_OVERVIEW[c]['total_scanned_tenants'] = \ - len([v for _, v in tenant_to_object.items() if - v.cloud == c.upper()]) - - def _collect_chief_overview_item(self, customer: str, customer_data: dict): - overview_item = { - TYPE_ATTR: OVERVIEW_TYPE.upper(), - **{c: customer_data.get(c, {}) for c in CLOUDS}, - 'customer': customer, - 'date': self.month_first_day.isoformat()} - for c in CLOUDS: - if all(self._is_empty(v) for v in overview_item[c].values()): - overview_item[c] = {} - elif overview_item[c]['succeeded_scans'] == 0: - overview_item[c]['resources_violated'] = 0 - overview_item[c]['resource_types_data'] = {} - overview_item[c]['severity_data'] = {} - return self.customer_metrics_service.create(overview_item) - - def _get_last_month_findings(self, acc_id: str) -> dict: - statistics_bucket = self.environment_service.get_statistics_bucket_name() - file = self.s3_client.get_file_content( - bucket_name=statistics_bucket, decode=True, - full_file_name=f'findings/{self.month_first_day.isoformat()}/' - f'{acc_id}.json.gz') - if not file: - file = self.s3_client.get_file_content( - bucket_name=statistics_bucket, decode=True, - full_file_name=f'findings/{self.today_date.isoformat()}/' - f'{acc_id}.json.gz') - if not file: - file = self.s3_client.get_file_content( - bucket_name=statistics_bucket, decode=True, - full_file_name=f'findings/{acc_id}.json.gz') - return json.loads(file) - - def create_customer_items(self, customer): - overview_item = { - TYPE_ATTR: OVERVIEW_TYPE.upper(), - **{c: self.CUSTOMER_OVERVIEW.get(c, {}) for c in CLOUDS}, - 'customer': customer, - 'date': self.month_first_day.isoformat()} - - return self.customer_metrics_service.create(overview_item) - - def _get_weekly_statistics(self, customer: str, from_date: date, - to_date: date): - items = self.job_statistics_service.get_by_customer_and_date( - customer, from_date.isoformat(), to_date.isoformat()) - return items - - def current_month_customer_metrics_exist(self, customer): - return self.customer_metrics_service.get_all_types_by_customer_date( - customer=customer, overview_only=True, - date=self.month_first_day.isoformat()).get(OVERVIEW_TYPE.upper()) - - @staticmethod - def _is_empty(value): - if value in [None, 0, {}, []]: - return True - return False - - def _get_last_week_scans(self, customer): - start_week_date = (self.today_date + relativedelta( - weekday=MO(-1))) - - customer_jobs = self.job_service.get_customer_jobs_between_period( - end_period=self.today_date, start_period=start_week_date, - customer=customer, only_succeeded=False, limit=10, - attributes_to_get=["stopped_at", 'tenant_display_name', "status"]) - customer_ed_jobs = self.batch_results_service.get_between_period_by_customer( - customer_name=customer, start=start_week_date.isoformat(), - end=self.today_date.isoformat(), limit=10, - attributes_to_get=['t', 's', 'jsta'] - ) - return customer_jobs + customer_ed_jobs - - def get_number_of_resources_by_severity_and_type(self, - content: dict) -> dict: - result = {} - for policy, value in content.items(): - policy = policy.replace('epam-', 'ecc-') - severity = self.metadata.get(policy) - if not severity: - _LOG.debug(f'Get metadata for policy {policy}') - self.metadata[policy] = self.rule_meta_service.\ - get_latest_meta(policy) - if not self.metadata.get(policy): - _LOG.warning(f'Cannot find metadata for rule {policy}') - severity = 'Unknown' - else: - severity = self.metadata.get(policy).get_json().get( - 'severity', 'Unknown').capitalize() - resource_type = value.get('resourceType', '') - regions_data = value.get('resources', {}) - for _, resources in regions_data.items(): - if not resources: - continue - result.setdefault('severity_data', {}).setdefault(severity, []) - result.setdefault('resource_types_data', {}).setdefault( - resource_type, []) - resources_str = [':'.join([f'{k}:{v}' for k, v in r.items()]) for r in resources] - result['severity_data'][severity].extend(resources_str) - result['resource_types_data'][resource_type].extend(resources_str) - for s, v in result['severity_data'].items(): - result['severity_data'][s] = len(set(v)) - for r, v in result['resource_types_data'].items(): - result['resource_types_data'][r] = len(set(v)) - return result - - -CUSTOMER_METRICS_DIFF = CustomerMetrics( - s3_client=SERVICE_PROVIDER.s3(), - environment_service=SERVICE_PROVIDER.environment_service(), - settings_service=SERVICE_PROVIDER.settings_service(), - tenant_metrics_service=SERVICE_PROVIDER.tenant_metrics_service(), - customer_metrics_service=SERVICE_PROVIDER.customer_metrics_service(), - modular_service=SERVICE_PROVIDER.modular_service(), - findings_service=SERVICE_PROVIDER.findings_service(), - job_statistics_service=SERVICE_PROVIDER.job_statistics_service(), - job_service=SERVICE_PROVIDER.job_service(), - rule_meta_service=SERVICE_PROVIDER.rule_meta_service(), - batch_results_service=SERVICE_PROVIDER.batch_results_service() -) diff --git a/src/lambdas/custodian_metrics_updater/processors/diagnostic_metrics_processor.py b/src/lambdas/custodian_metrics_updater/processors/diagnostic_metrics_processor.py new file mode 100644 index 000000000..861d5e383 --- /dev/null +++ b/src/lambdas/custodian_metrics_updater/processors/diagnostic_metrics_processor.py @@ -0,0 +1,220 @@ +import json +from datetime import datetime + +from dateutil.relativedelta import relativedelta +from modular_sdk.modular import Modular + +from helpers import get_logger +from helpers.constants import JobState +from helpers.time_helper import utc_datetime +from services import SERVICE_PROVIDER +from services.ambiguous_job_service import AmbiguousJobService +from services.clients.s3 import S3Client +from services.environment_service import EnvironmentService +from services.job_statistics_service import JobStatisticsService +from services.report_service import ReportService +from services.report_statistics_service import ReportStatisticsService +from services.reports_bucket import StatisticsBucketKeysBuilder +from services.scheduler_service import SchedulerService +from services.setting_service import SettingsService + +_LOG = get_logger(__name__) + + +class DiagnosticMetrics: + def __init__(self, modular_client: Modular, + environment_service: EnvironmentService, + s3_service: S3Client, settings_service: SettingsService, + report_statistics_service: ReportStatisticsService, + ambiguous_job_service: AmbiguousJobService, + job_statistics_service: JobStatisticsService, + scheduler_service: SchedulerService, + report_service: ReportService): + self.modular_client = modular_client + self.environment_service = environment_service + self.s3_service = s3_service + self.settings_service = settings_service + self.report_statistics_service = report_statistics_service + self.ambiguous_job_service = ambiguous_job_service + self.job_statistics_service = job_statistics_service + self.scheduler_service = scheduler_service + self.report_service = report_service + + self.stat_bucket_name = \ + self.environment_service.get_statistics_bucket_name() + + self.today_date = utc_datetime() + self.end_date_obj = datetime.combine( + self.today_date.replace(day=1), + datetime.min.time() + ) + self.end_date = self.end_date_obj.isoformat() + self.start_date_obj = datetime.combine( + self.today_date + relativedelta(months=-1, day=1), + datetime.min.time() + ) + self.start_date = self.start_date_obj.isoformat() + + self.TO_UPDATE_MARKER = False + self.tenant_obj_mapping = {} + + @classmethod + def build(cls) -> 'DiagnosticMetrics': + return cls( + modular_client=SERVICE_PROVIDER.modular_client, + environment_service=SERVICE_PROVIDER.environment_service, + settings_service=SERVICE_PROVIDER.settings_service, + s3_service=SERVICE_PROVIDER.s3, + report_statistics_service=SERVICE_PROVIDER.report_statistics_service, + ambiguous_job_service=SERVICE_PROVIDER.ambiguous_job_service, + job_statistics_service=SERVICE_PROVIDER.job_statistics_service, + scheduler_service=SERVICE_PROVIDER.scheduler_service, + report_service=SERVICE_PROVIDER.report_service + ) + + def process_data(self, event): + for customer in self.modular_client.customer_service().i_get_customer(): + name = customer.name + key_path = StatisticsBucketKeysBuilder.report_statistics( + self.start_date_obj, name) + + report_dto = { + 'report_type': 'DIAGNOSTIC', 'from': self.start_date, + 'to': self.end_date, 'customer': name, + 'data': { + 'scans_data': { + 'scheduled_scans_data': + list(self._i_get_scheduled_jobs(name)), + 'executed_scans_data': + self._get_scans_statistics(name) + }, + 'reports_data': { + 'triggered_reports_data': + self._get_report_statistics(name) + }, + 'rules_data': { + 'execution_data': self.get_rule_statistics(name) + } + } + } + + _LOG.debug(f'Saving diagnostic file by key {key_path}') + self.s3_service.gz_put_json( + bucket=self.stat_bucket_name, key=key_path, obj=report_dto) + return {} + + def _get_scans_statistics(self, customer): + scans_data = { + 'failed': 0, + 'succeeded': 0, + 'cloud_data': [] + } + cloud_data = {} + tenants_data = {} + region_scans_data = {} + + items = self.job_statistics_service.get_by_customer_and_date( + customer=customer, from_date=self.start_date, + to_date=self.end_date + ) + for item in items: + scans_data['failed'] += item.failed + scans_data['succeeded'] += item.succeeded + for tenant_id, data in json.loads(item.to_json()).get( + 'scanned_regions', {}).items(): + if not (tenant := self.tenant_obj_mapping.get(tenant_id)): + tenant = next(self.modular_client.tenant_service().i_get_by_acc( + tenant_id + ), None) + self.tenant_obj_mapping[tenant_id] = tenant + region_scans_data.setdefault(tenant.name, {}) + for region, number in data.items(): + region_scans_data[tenant.name].setdefault(region, 0) + region_scans_data[tenant.name][region] += number + + for tenant_id, scans in item.tenants.attribute_values.items(): + if not (tenant := self.tenant_obj_mapping.get(tenant_id)): + tenant = next(self.modular_client.tenant_service().i_get_by_acc( + tenant_id + ), None) + self.tenant_obj_mapping[tenant_id] = tenant + tenants_data.setdefault(tenant.name, { + 'failed_scans': 0, + 'succeeded_scans': 0, + 'failed_scans_reasons': {}, + 'region_scans_data': {} + }) + tenants_data[tenant.name]['failed_scans'] += scans.get( + 'failed_scans', 0) + tenants_data[tenant.name]['succeeded_scans'] += scans.get( + 'succeeded_scans', 0) + tenants_data[tenant.name]['cloud'] = tenant.cloud + for reason, number in item.__dict__.get('reason', {}).items(): + tenants_data[tenant.name][ + 'failed_scans_reasons'].setdefault(reason, 0) + tenants_data[tenant.name]['failed_scans_reasons'][ + reason] += number + + for tenant, data in region_scans_data.items(): + tenants_data[tenant]['region_scans_data'] = data + + for tenant, data in tenants_data.items(): + cloud = data.pop('cloud') + cloud_data.setdefault(cloud, {'tenants_data': []}) + cloud_data[cloud]['tenants_data'].append({ + 'tenant_name': tenant, **data + }) + scans_data['cloud_data'] = [{'cloud': cloud, **data} for cloud, data in + cloud_data.items()] + return scans_data + + def _i_get_scheduled_jobs(self, customer): + items = self.scheduler_service.list(customer=customer) + for i in items: + dto = self.scheduler_service.dto(i) + if dto.get('context', {}).get('is_enabled'): + dto.pop('customer_name') + yield dto + + def get_rule_statistics(self, customer): + items = [] + files = self.s3_service.list_dir( + bucket_name=self.stat_bucket_name, + key=StatisticsBucketKeysBuilder.tenant_statistics( + self.start_date_obj, customer=customer)) + for file in files: + items.extend(self.s3_service.gz_get_json( + bucket=self.stat_bucket_name, + key=file + )) + return self.report_service.sum_average_statistics(items) + + def _get_report_statistics(self, customer: str): + result = {} + items = self.report_statistics_service.iter_by_customer( + customer, triggered_at=self.start_date, end_date=self.end_date + ) + for item in items: + level = item.attribute_values.pop('level') + item.attribute_values.pop('customer_name') + item.attribute_values.pop('id') + item.attribute_values.pop('event') + item.attribute_values.pop('attempt') + result.setdefault(level, { + 'triggers': 0, + 'failed': 0, + 'succeeded': 0, + 'failed_reports_data': [] + }) + result[level]['triggers'] += 1 + if item.status == JobState.FAILED: + result[level]['failed'] += 1 + result[level]['failed_reports_data'].append( + item.attribute_values) + elif item.status == JobState.SUCCEEDED: + result[level]['succeeded'] += 1 + + return [{'level': level, **data} for level, data in result.items()] + + +DIAGNOSTIC_METRICS = DiagnosticMetrics.build() diff --git a/src/lambdas/custodian_metrics_updater/processors/findings_processor.py b/src/lambdas/custodian_metrics_updater/processors/findings_processor.py index cd3abbafe..25c586d1d 100644 --- a/src/lambdas/custodian_metrics_updater/processors/findings_processor.py +++ b/src/lambdas/custodian_metrics_updater/processors/findings_processor.py @@ -1,13 +1,11 @@ -import json -from datetime import timedelta +from pathlib import PurePosixPath from helpers import get_logger from helpers.constants import DATA_TYPE, START_DATE -from helpers.reports import FindingsCollection -from helpers.time_helper import utc_datetime from services import SERVICE_PROVIDER from services.clients.s3 import S3Client from services.environment_service import EnvironmentService +from services.reports_bucket import ReportsBucketKeysBuilder _LOG = get_logger(__name__) NEXT_STEP = 'recommendations' @@ -16,49 +14,53 @@ class FindingsUpdater: def __init__(self, s3_client: S3Client, environment_service: EnvironmentService): - self.s3_client = s3_client - self.environment_service = environment_service - self.today = utc_datetime(utc=False).date().isoformat() - self.yesterday = (utc_datetime(utc=False) - - timedelta(days=1)).date().isoformat() - - def process_data(self, event): - bucket = self.environment_service.get_statistics_bucket_name() - yesterday_findings_path = f'findings/{self.yesterday}/' - today_findings_path = f'findings/{self.today}/' - objects = list(self.s3_client.list_dir( - bucket_name=bucket, key=yesterday_findings_path - )) - - _LOG.debug(f'Retrieved objects: {objects}') - if not objects: - _LOG.warning(f'Folder {yesterday_findings_path} is empty!') - - for file in objects: - today_filename = f'{today_findings_path}{file.split("/")[-1]}' - content = self.s3_client.get_file_content( - bucket_name=bucket, full_file_name=file) - - if self.s3_client.file_exists(bucket, today_filename): - _LOG.debug(f'File {today_filename} already exists, merging..') - new_content = self.s3_client.get_json_file_content( - bucket, today_filename) - new = FindingsCollection.deserialize(new_content) - old = FindingsCollection.deserialize(json.loads(content)) - old.update(new) - content = old.json() - - _LOG.debug(f'Update file {file}') - self.s3_client.put_object( - bucket_name=bucket, - object_name=today_filename, - body=content) + self._s3_client = s3_client + self._environment_service = environment_service + def process_data(self, event: dict): + """ + When this processor is executed we make a snapshot of existing + findings for specified tenants. + :param event: + :return: + """ + bucket = self._environment_service.default_reports_bucket_name() + prefixes = self._s3_client.common_prefixes( + bucket=bucket, + delimiter=ReportsBucketKeysBuilder.latest, + prefix=ReportsBucketKeysBuilder.prefix + ) + # todo use s3control.create_job for this + for prefix in prefixes: + _LOG.debug(f'Processing key: {prefix}') + objects = self._s3_client.list_objects( + bucket=bucket, + prefix=prefix, + ) + for obj in objects: + # prefix: /bla/bla/latest + # key: /bla/bla/latest/1.json.gz + # destination: /bla/bla/snapshots/2023/10/10/10/1.json.gz + key = obj.key + path = PurePosixPath(key) + destination = ReportsBucketKeysBuilder.urljoin( + str(path.parent.parent), + ReportsBucketKeysBuilder.snapshots, + ReportsBucketKeysBuilder.datetime() + ) + destination += path.name + _LOG.debug(f'Copying {key} to {destination}') + self._s3_client.copy( + bucket=bucket, + key=key, + destination_bucket=bucket, + destination_key=destination + ) return {DATA_TYPE: NEXT_STEP, START_DATE: event.get(START_DATE), 'continuously': event.get('continuously')} FINDINGS_UPDATER = FindingsUpdater( - s3_client=SERVICE_PROVIDER.s3(), - environment_service=SERVICE_PROVIDER.environment_service() + s3_client=SERVICE_PROVIDER.s3, + environment_service=SERVICE_PROVIDER.environment_service ) diff --git a/src/lambdas/custodian_metrics_updater/processors/metric_difference_processor.py b/src/lambdas/custodian_metrics_updater/processors/metric_difference_processor.py index d537b188d..f02a1c28d 100644 --- a/src/lambdas/custodian_metrics_updater/processors/metric_difference_processor.py +++ b/src/lambdas/custodian_metrics_updater/processors/metric_difference_processor.py @@ -1,14 +1,13 @@ -import json from datetime import datetime, timedelta from dateutil.relativedelta import SU, relativedelta +from helpers import get_last_element from helpers import get_logger from helpers.constants import ATTACK_VECTOR_TYPE, START_DATE, DATA_TYPE, \ END_DATE -from helpers.time_helper import utc_datetime -from helpers.utils import get_last_element from helpers.difference import calculate_dict_diff +from helpers.time_helper import utc_datetime from services import SERVICE_PROVIDER from services.clients.s3 import S3Client from services.environment_service import EnvironmentService @@ -42,7 +41,7 @@ def __init__(self, s3_client: S3Client, self.last_week_date = utc_datetime( self.last_week_date if self.last_week_date else ( self.today_date - relativedelta(weekday=SU(-1)) - ).date().isoformat(), utc=False) + ).isoformat(), utc=False) def process_data(self, event): end_date = event.get(END_DATE) @@ -100,28 +99,31 @@ def process_data(self, event): if (tenant_file := get_last_element(file, DELIMITER)) in \ previous_metric_filenames: index = previous_metric_filenames.index(tenant_file) - previous = self.s3_client.get_json_file_content( - bucket_name=metrics_bucket, - full_file_name=previous_metric_full_filenames[index]) + previous = self.s3_client.gz_get_json( + bucket=metrics_bucket, + key=previous_metric_full_filenames[index]) previous_metric_filenames.pop(index) else: _LOG.debug(f'Previous metrics for tenant {tenant_file} ' f'were not found') previous = {} - current = self.s3_client.get_json_file_content( - bucket_name=metrics_bucket, full_file_name=file) + current = self.s3_client.gz_get_json( + bucket=metrics_bucket, key=file) diff = calculate_dict_diff(current, previous, exclude=ATTACK_VECTOR_TYPE) - self.s3_client.put_object(bucket_name=metrics_bucket, - object_name=file, - body=json.dumps(diff)) + self.s3_client.gz_put_json( + bucket=metrics_bucket, + key=file, + obj=diff + ) if self.TO_UPDATE_MARKER: obj_name = TENANT_METRICS_PATH.format( customer=customer, date=last_week_date) - self.s3_client.put_object( - bucket_name=metrics_bucket, - object_name=f'{obj_name}/{tenant_file}', - body=json.dumps(diff)) + self.s3_client.gz_put_json( + bucket=metrics_bucket, + key=f'{obj_name}/{tenant_file}', + obj=diff + ) # Monday if self.TO_UPDATE_MARKER: @@ -145,7 +147,7 @@ def process_data(self, event): TENANT_METRICS_DIFF = TenantMetricsDifference( - s3_client=SERVICE_PROVIDER.s3(), - environment_service=SERVICE_PROVIDER.environment_service(), - settings_service=SERVICE_PROVIDER.settings_service() + s3_client=SERVICE_PROVIDER.s3, + environment_service=SERVICE_PROVIDER.environment_service, + settings_service=SERVICE_PROVIDER.settings_service ) diff --git a/src/lambdas/custodian_metrics_updater/processors/recommendation_processor.py b/src/lambdas/custodian_metrics_updater/processors/recommendation_processor.py index e83daf79f..ccf42a48c 100644 --- a/src/lambdas/custodian_metrics_updater/processors/recommendation_processor.py +++ b/src/lambdas/custodian_metrics_updater/processors/recommendation_processor.py @@ -1,23 +1,27 @@ import io import json import uuid +from copy import deepcopy from datetime import datetime, timezone -from http import HTTPStatus -from modular_sdk.commons.constants import RABBITMQ_TYPE +from modular_sdk.commons.constants import RABBITMQ_TYPE, ParentType +from modular_sdk.modular import Modular -from helpers import get_logger, CustodianException -from helpers.constants import ARTICLE_ATTR, IMPACT_ATTR -from helpers.recommendations import RULE_RECOMMENDATION_MAPPING +from helpers import get_logger +from helpers.constants import ARTICLE_ATTR, IMPACT_ATTR, RESOURCE_TYPE_ATTR, \ + Cloud +from helpers.recommendations import RULE_RECOMMENDATION_MAPPING, \ + K8S_RECOMMENDATION_MODEL from helpers.time_helper import utc_datetime, make_timestamp_java_compatible from services import SERVICE_PROVIDER from services.clients.s3 import S3Client, ModularAssumeRoleS3Service from services.environment_service import EnvironmentService -from services.findings_service import FindingsService -from services.modular_service import ModularService +from services.mappings_collector import LazyLoadedMappingsCollector +from services.platform_service import PlatformService, Platform from services.rabbitmq_service import RabbitMQService -from services.rule_meta_service import RuleMetaService, \ - LazyLoadedMappingsCollector +from services.report_service import ReportService +from services.reports_bucket import ReportsBucketKeysBuilder, \ + PlatformReportsBucketKeysBuilder, TenantReportsBucketKeysBuilder _LOG = get_logger(__name__) @@ -36,173 +40,300 @@ "attachments": [] } +K8S_TYPE_ACTION_MAPPING = { + 'ConfigMap': 'CONFIG', + 'ClusterRole': 'ROLE', + 'Role': 'ROLE', + 'Deployment': 'DEPLOYMENT', + 'Secret': 'SECRET', + 'ServiceAccount': 'SERVICE_ACCOUNT', + 'Namespace': 'NAMESPACE' +} + class Recommendation: - def __init__(self, findings_service: FindingsService, - environment_service: EnvironmentService, s3_client: S3Client, - modular_service: ModularService, + def __init__(self, environment_service: EnvironmentService, + s3_client: S3Client, + modular_client: Modular, rabbitmq_service: RabbitMQService, assume_role_s3: ModularAssumeRoleS3Service, - rule_meta_service: RuleMetaService, - mappings_collector: LazyLoadedMappingsCollector): - self.findings_service = findings_service + mappings_collector: LazyLoadedMappingsCollector, + report_service: ReportService, + platform_service: PlatformService): self.environment_service = environment_service self.s3_client = s3_client self.assume_role_s3 = assume_role_s3 - self.modular_service = modular_service + self.modular_client = modular_client self.rabbitmq_service = rabbitmq_service - self.rule_meta_service = rule_meta_service self.mappings_collector = mappings_collector + self.report_service = report_service + self.platform_service = platform_service self.today = utc_datetime(utc=False).date().isoformat() + self.bucket = self.environment_service.default_reports_bucket_name() self.customer_rabbit_mapping = {} self.recommendation_to_article_impact = {} + self.cluster_platform_mapping = {} + self.tenant_obj_mapping = {} + self.now = datetime.now(timezone.utc) + self.timestamp = make_timestamp_java_compatible(self.now.timestamp()) def process_data(self, _): - bucket = self.environment_service.get_statistics_bucket_name() - prefix = f'findings/{self.today}/' - objects = list(self.s3_client.list_objects( - bucket_name=bucket, prefix=prefix - )) - - # todo take this duplicate to separate function - _LOG.debug(f'Retrieved objects (first attempt): {objects}') - if not objects: - prefix = 'findings/' - objects = [o for o in self.s3_client.list_objects( - bucket_name=bucket, prefix=prefix) - if o.get('Key').endswith('json.gz') or o.get('Key'). - endswith('json')] - _LOG.debug(f'Retrieved objects (second attempt): {objects}') - - # timestamp = 1690363338133 - now = datetime.now(timezone.utc) - timestamp = make_timestamp_java_compatible(now.timestamp()) - for obj in objects: - file = obj.get('Key') - _LOG.debug(f'Get file {file} content') - content = self.s3_client.get_file_content( - bucket_name=bucket, full_file_name=file) - if not content: - _LOG.debug(f'Skipping file {file}, no findings content') + self.get_cluster_parents() + # get cluster recommendation + for name, platform in self.cluster_platform_mapping.items(): + key_builder = PlatformReportsBucketKeysBuilder(platform) + file = key_builder.latest_key() + k8s_recommendations = self.get_k8s_recommendations(platform, file) + if not k8s_recommendations: continue - project_id = file.split(prefix)[-1].split('.json')[0] - tenant = next( - self.modular_service.i_get_tenants_by_acc(project_id, True), - None - ) + # path to store /customer/cloud/tenant/timestamp/region.jsonl + tenant = self.modular_client.tenant_service().get(platform.tenant_name) if not tenant: - _LOG.warning( - f'Cannot find tenant with project id {project_id}') continue + self.tenant_obj_mapping[tenant.project] = tenant + tenant_key_builder = TenantReportsBucketKeysBuilder(tenant) + _LOG.debug(f'Get file {tenant_key_builder.latest_key()} content') + collection = self.report_service.tenant_latest_collection( + tenant) + collection.fetch_all() + collection.fetch_meta() + recommendations = self._build_recommendations( + RULE_RECOMMENDATION_MAPPING, collection) + + if not recommendations: + for region, recommend in k8s_recommendations.items(): + _LOG.debug(f'No recommendations based on findings ' + f'{tenant_key_builder.latest_key()}') + content = self._json_to_jsonl(recommend) + self.save_recommendation(region=region, tenant=tenant, + content=content) + self.send_request(tenant) + else: + for region, recommend in recommendations.items(): + if k8s_region_recommend := k8s_recommendations.get(region): + recommend.extend(k8s_region_recommend) + content = self._json_to_jsonl(recommend) + self.save_recommendation(region=region, tenant=tenant, + content=content) + self.send_request(tenant) + + # get tenant recommendations + prefixes = self.s3_client.common_prefixes( + bucket=self.bucket, + delimiter=ReportsBucketKeysBuilder.latest, + prefix=ReportsBucketKeysBuilder.prefix + ) + for prefix in prefixes: + if Cloud.KUBERNETES in prefix: + _LOG.debug('Skipping folder with k8s findings - ' + 'already processed') + continue + _LOG.debug(f'Processing key: {prefix}') + objects = [ + o for o in self.s3_client.list_objects(bucket=self.bucket, + prefix=prefix) + if o.key.endswith('json.gz') or o.key.endswith('json') + ] + + for obj in objects: + project_id = obj.key.split('/')[3] + tenant = self.tenant_obj_mapping.get(project_id) + if tenant: + _LOG.debug( + f'Tenant {tenant.name} have already been processed') + continue + tenant = next(self.modular_client.tenant_service().i_get_by_acc( + project_id, active=True + ), None) + if not tenant: + _LOG.warning( + f'Cannot find tenant with project id {project_id}') + continue + self.tenant_obj_mapping[project_id] = tenant + _LOG.debug(f'Get file {obj.key} content') + collection = self.report_service.tenant_latest_collection( + tenant) + collection.fetch_all() + collection.fetch_meta() + recommendations = self._build_recommendations( + RULE_RECOMMENDATION_MAPPING, collection) + + # path to store /customer/cloud/tenant/timestamp/region.jsonl + for region, recommend in recommendations.items(): + content = self._json_to_jsonl(recommend) + self.save_recommendation(region, tenant, content) + if not recommendations: + _LOG.debug( + f'No recommendations based on findings {obj.key}') + continue + + self.send_request(tenant) + return {} + + def send_request(self, tenant): + customer = tenant.customer_name + if not self.customer_rabbit_mapping.get(customer): + application = self.rabbitmq_service.get_rabbitmq_application( + customer) + if not application: + _LOG.warning( + f'No application with type {RABBITMQ_TYPE} found') + return + self.customer_rabbit_mapping[customer] = \ + self.rabbitmq_service.build_maestro_mq_transport( + application) + + CADF_EVENT['id'] = str(uuid.uuid4().hex) + CADF_EVENT['eventTime'] = self.now.astimezone().isoformat() + CADF_EVENT['attachments'] = [ + { + "contentType": "map", + "content": { + "tenant": tenant.name, + "timestamp": self.timestamp + }, + "name": "collectCustodianRecommendations" + } + ] + code, status, response = self.customer_rabbit_mapping[customer]. \ + send_sync( + command_name=COMMAND_NAME, + parameters={'event': CADF_EVENT, + 'qualifier': 'custodian_data'}, + is_flat_request=False, async_request=False, + secure_parameters=None, compressed=True) + _LOG.debug(f'Response code: {code}, response message: {response}') + return code - recommendations = {} - for rule, resources in json.loads(content).items(): - item = RULE_RECOMMENDATION_MAPPING.get(rule) - if not item: + @staticmethod + def _json_to_jsonl(recommendations: list[dict]) -> str: + buffer = io.StringIO() + for item in recommendations: + buffer.write(json.dumps(item, separators=(',', ':'))) + buffer.write('\n') + content = buffer.getvalue() + buffer.close() + return content + + def get_cluster_parents(self): + """ + Get all parents and applications related to k8s clusters + :return: + """ + for cust in self.modular_client.customer_service().i_get_customer(): + parents = self.modular_client.parent_service().i_get_parent_by_customer( + customer_id=cust.name, + parent_type=ParentType.PLATFORM_K8S.value, + is_deleted=False + ) + for parent in parents: + platform = Platform(parent) + self.platform_service.fetch_application(platform) + self.cluster_platform_mapping[platform.name] = platform + + def get_k8s_recommendations(self, platform: Platform, file): + + _LOG.debug(f'Get file {file} content') + collection = self.report_service.platform_latest_collection(platform) + collection.fetch_all() + collection.fetch_meta() + + recommendations = self._build_k8s_recommendations( + collection, platform.id, platform.region) + return recommendations + + def _build_recommendations(self, rule_mapping: dict, collection) -> dict: + recommendations = {} + for _, shard in collection: + for part in shard: + if not (item := rule_mapping.get(part.policy)): continue - if not self.recommendation_to_article_impact.get(rule): - data = self.mappings_collector.human_data.get(rule, {}) + if not self.recommendation_to_article_impact.get(part.policy): + data = self.mappings_collector.human_data.get(part.policy, {}) article = data.get('article') or '' impact = data.get('impact') or '' - self.recommendation_to_article_impact[rule] = { + resource_type = self.mappings_collector.service.get( + part.policy, '') + self.recommendation_to_article_impact[part.policy] = { ARTICLE_ATTR: article, - IMPACT_ATTR: impact + IMPACT_ATTR: impact, + RESOURCE_TYPE_ATTR: resource_type } item['recommendation'][ARTICLE_ATTR] = \ - self.recommendation_to_article_impact[rule][ARTICLE_ATTR] + self.recommendation_to_article_impact[part.policy][ARTICLE_ATTR] item['recommendation'][IMPACT_ATTR] = \ - self.recommendation_to_article_impact[rule][IMPACT_ATTR] - item['recommendation']['description'] = resources.get( - 'description', 'Description') + self.recommendation_to_article_impact[part.policy][IMPACT_ATTR] + item['recommendation']['description'] = collection.meta.get( + part.policy, {}).get('description', 'Description') _id = item['resource_id'] - for region, resource in resources.get( - 'resources', {}).items(): - for i in resource: - item = item.copy() - item['resource_id'] = _id.format(**i) - recommendations.setdefault(region, []).append(item) + for res in part.resources: + item_copy = item.copy() + item_copy['resource_id'] = _id.format(**res) + recommendations.setdefault(part.location, []).append( + item_copy) + return recommendations - # path to store /customer/cloud/tenant/timestamp/region.jsonl - file = file.split(prefix)[-1] - for region, recommend in recommendations.items(): - contents = self._json_to_jsonl(recommend) - _LOG.debug(f'Saving file {file}, region {region}') - recommendation_bucket = self.environment_service. \ - get_recommendation_bucket() - result = self.assume_role_s3.put_object( - bucket_name=recommendation_bucket, - object_name=f'{tenant.customer_name}/' - f'{tenant.cloud}/{tenant.name}/{timestamp}/' - f'{region}.jsonl', - body=contents.encode(), - is_zipped=False) - if not result: - raise CustodianException( - code=HTTPStatus.INTERNAL_SERVER_ERROR.value, - content='There is no recommendations bucket' - ) - if not recommendations: - _LOG.debug(f'No recommendations based on findings {file}') - continue + def _build_k8s_recommendations(self, collection, application_uuid, + region) -> dict: + recommendations = {} + for _, shard in collection: + for part in shard: + if not self.recommendation_to_article_impact.get(part.policy): + data = self.mappings_collector.human_data.get(part.policy, {}) + article = data.get('article') or '' + impact = data.get('impact') or '' + resource_type = self.mappings_collector.service.get( + part.policy, '') + self.recommendation_to_article_impact[part.policy] = { + ARTICLE_ATTR: article, + IMPACT_ATTR: impact, + RESOURCE_TYPE_ATTR: resource_type + } - customer = tenant.customer_name - if not self.customer_rabbit_mapping.get(customer): - application = self.rabbitmq_service.get_rabbitmq_application( - customer) - if not application: - _LOG.warning( - f'No application with type {RABBITMQ_TYPE} found') - continue - self.customer_rabbit_mapping[customer] = \ - self.rabbitmq_service.build_maestro_mq_transport( - application) - - CADF_EVENT['id'] = str(uuid.uuid4().hex) - CADF_EVENT['eventTime'] = now.astimezone().isoformat() - CADF_EVENT['attachments'] = [ - { - "contentType": "map", - "content": { - "tenant": tenant.name, - "timestamp": timestamp - }, - "name": "collectCustodianRecommendations" - } - ] - code, status, response = self.customer_rabbit_mapping[customer]. \ - send_sync( - command_name=COMMAND_NAME, - parameters={'event': CADF_EVENT, - 'qualifier': 'custodian_data'}, - is_flat_request=False, async_request=False, - secure_parameters=None, compressed=True) - _LOG.debug(f'Response code: {code}, response message: {response}') - return {} + item = deepcopy(K8S_RECOMMENDATION_MODEL) + item['recommendation'][ARTICLE_ATTR] = \ + self.recommendation_to_article_impact[part.policy][ARTICLE_ATTR] + item['recommendation'][IMPACT_ATTR] = \ + self.recommendation_to_article_impact[part.policy][IMPACT_ATTR] + item['recommendation']['description'] = collection.meta.get( + part.policy, {}).get('description', 'Description') + item['recommendation']['resource_type'] = \ + self.recommendation_to_article_impact[part.policy][RESOURCE_TYPE_ATTR] + item['resource_id'] = application_uuid + item['general_actions'] = [K8S_TYPE_ACTION_MAPPING.get( + self.recommendation_to_article_impact[part.policy][RESOURCE_TYPE_ATTR], 'POD')] - @staticmethod - def _json_to_jsonl(recommendations: dict): - with io.StringIO() as body: - for item in recommendations: - body.write(json.dumps(item) + '\n') - body.seek(0) - contents = body.getvalue() - body.close() + _id = item['recommendation']['resource_id'] + for res in part.resources: + item_copy = deepcopy(item) + item_copy['recommendation']['resource_id'] = _id.format(**res) + recommendations.setdefault(region if region else part.location, []).append(item_copy) + return recommendations - return contents + def save_recommendation(self, region, tenant, content): + file_path = f'{tenant.customer_name}/{tenant.cloud}/{tenant.name}/' \ + f'{self.timestamp}/{region}.jsonl' + _LOG.debug(f'Saving file {file_path}, region {region}') + recommendation_bucket = self.environment_service. \ + get_recommendation_bucket() + self.assume_role_s3.put_object( + bucket=recommendation_bucket, + key=file_path, + body=content.encode()) RECOMMENDATION_METRICS = Recommendation( - findings_service=SERVICE_PROVIDER.findings_service(), - environment_service=SERVICE_PROVIDER.environment_service(), - s3_client=SERVICE_PROVIDER.s3(), - modular_service=SERVICE_PROVIDER.modular_service(), - rabbitmq_service=SERVICE_PROVIDER.rabbitmq_service(), - assume_role_s3=SERVICE_PROVIDER.assume_role_s3(), - rule_meta_service=SERVICE_PROVIDER.rule_meta_service(), - mappings_collector=SERVICE_PROVIDER.mappings_collector() + environment_service=SERVICE_PROVIDER.environment_service, + s3_client=SERVICE_PROVIDER.s3, + modular_client=SERVICE_PROVIDER.modular_client, + rabbitmq_service=SERVICE_PROVIDER.rabbitmq_service, + assume_role_s3=SERVICE_PROVIDER.assume_role_s3, + mappings_collector=SERVICE_PROVIDER.mappings_collector, + report_service=SERVICE_PROVIDER.report_service, + platform_service=SERVICE_PROVIDER.platform_service ) diff --git a/src/lambdas/custodian_metrics_updater/processors/tenant_group_metrics_processor.py b/src/lambdas/custodian_metrics_updater/processors/tenant_group_metrics_processor.py index a30f00037..0ff5356fb 100644 --- a/src/lambdas/custodian_metrics_updater/processors/tenant_group_metrics_processor.py +++ b/src/lambdas/custodian_metrics_updater/processors/tenant_group_metrics_processor.py @@ -1,26 +1,21 @@ import calendar -import json -from datetime import datetime, timedelta import copy -from functools import cmp_to_key +from datetime import datetime, timedelta from dateutil.relativedelta import relativedelta, SU +from modular_sdk.modular import Modular -from helpers import get_logger, hashable +from helpers import get_logger, hashable, get_last_element from helpers.constants import CUSTOMER_ATTR, OVERVIEW_TYPE, COMPLIANCE_TYPE, \ RESOURCES_TYPE, DATA_TYPE, ACCOUNT_ID_ATTR, ATTACK_VECTOR_TYPE, END_DATE, \ LAST_SCAN_DATE, SEVERITY_DATA_ATTR, RESOURCE_TYPES_DATA_ATTR, \ TENANT_NAME_ATTR, ID_ATTR, TENANT_DISPLAY_NAME_ATTR, AVERAGE_DATA_ATTR, \ - ACTIVATED_REGIONS_ATTR, FINOPS_TYPE, OUTDATED_TENANTS -from helpers.reports import keep_highest + ACTIVATED_REGIONS_ATTR, FINOPS_TYPE, OUTDATED_TENANTS, ARCHIVE_PREFIX from helpers.time_helper import utc_datetime -from helpers.utils import get_last_element, severity_cmp from services import SERVICE_PROVIDER from services.clients.s3 import S3Client -from services.job_statistics_service import JobStatisticsService from services.environment_service import EnvironmentService -from services.modular_service import ModularService -from services.rule_meta_service import LazyLoadedMappingsCollector +from services.mappings_collector import LazyLoadedMappingsCollector from services.setting_service import SettingsService _LOG = get_logger(__name__) @@ -35,26 +30,22 @@ class TenantGroupMetrics: def __init__(self, s3_client: S3Client, environment_service: EnvironmentService, settings_service: SettingsService, - modular_service: ModularService, - job_statistics_service: JobStatisticsService, + modular_client: Modular, mappings_collector: LazyLoadedMappingsCollector): self.s3_client = s3_client self.environment_service = environment_service self.settings_service = settings_service - self.modular_service = modular_service - self.job_statistics_service = job_statistics_service + self.modular_client = modular_client self.mapping = mappings_collector self.today_date = datetime.utcnow().today() self.today_midnight = datetime.combine(self.today_date, datetime.min.time()) self.yesterday = (self.today_date - timedelta(days=1)).date() - self.next_month_date = (self.today_date.date().replace(day=1) + - relativedelta(months=1)).isoformat() + self.next_month_date = (self.today_date.date() + relativedelta(months=+1, day=1)).isoformat() self.month_first_day = self.today_date.date().replace( day=1).isoformat() - self.prev_month_first_day = ( - self.today_date - relativedelta(months=1)).replace(day=1).date() + self.prev_month_first_day = (self.today_date + relativedelta(months=-1, day=1)).date() self.TO_UPDATE_MARKER = False self._date_marker = self.settings_service.get_report_date_marker() @@ -64,12 +55,11 @@ def __init__(self, s3_client: S3Client, @classmethod def build(cls) -> 'TenantGroupMetrics': return cls( - s3_client=SERVICE_PROVIDER.s3(), - environment_service=SERVICE_PROVIDER.environment_service(), - settings_service=SERVICE_PROVIDER.settings_service(), - modular_service=SERVICE_PROVIDER.modular_service(), - job_statistics_service=SERVICE_PROVIDER.job_statistics_service(), - mappings_collector=SERVICE_PROVIDER.mappings_collector() + s3_client=SERVICE_PROVIDER.s3, + environment_service=SERVICE_PROVIDER.environment_service, + settings_service=SERVICE_PROVIDER.settings_service, + modular_client=SERVICE_PROVIDER.modular_client, + mappings_collector=SERVICE_PROVIDER.mappings_collector ) def _calculate_resources(self, tenant_metrics: dict, cloud: str) -> dict: @@ -147,9 +137,14 @@ def process_data(self, event): if not project_id: _LOG.warning(f'Cannot get project id from file {filename}') continue + elif project_id.startswith(ARCHIVE_PREFIX): + _LOG.warning(f'Skipping archived tenant {filename}') + continue + + tenant_obj = list(self.modular_client.tenant_service().i_get_by_acc( + project_id, attributes_to_get=['dntl', 'n'] + )) - tenant_obj = list(self.modular_service.i_get_tenants_by_acc( - acc=project_id, attrs_to_get=['dntl', 'n'])) if not tenant_obj: _LOG.warning(f'Unknown tenant with project id ' f'{project_id}. Skipping...') @@ -191,9 +186,9 @@ def process_data(self, event): _LOG.debug( f'Processing tenant {get_last_element(tenant, "/")} ' f'within tenant group {tenant_dn}') - tenant_content = self.s3_client.get_json_file_content( - bucket_name=metrics_bucket, full_file_name=tenant) - cloud = tenant_content.get('cloud', 'unknown').lower() + tenant_content = self.s3_client.gz_get_json( + bucket=metrics_bucket, key=tenant) + cloud = tenant_content['cloud'].lower() _id = tenant_content.pop(ID_ATTR, None) tenant_name = tenant_content.pop(TENANT_NAME_ATTR, None) last_scan = tenant_content.get(LAST_SCAN_DATE) @@ -229,7 +224,7 @@ def process_data(self, event): compressed_metrics[COMPLIANCE_TYPE][cloud] = copy.deepcopy( tenant_content[COMPLIANCE_TYPE]) if tenant_content.get(FINOPS_TYPE): - # modifies compressed_metrics, does not change overview + # modifies compressed_metrics, does not change finops compressed_metrics[FINOPS_TYPE][cloud]['service_data'] = self._process_finops_metrics( tenant_content[FINOPS_TYPE]) @@ -261,12 +256,13 @@ def process_data(self, event): FINOPS_TYPE: tenant_group_finops_mapping } - self.s3_client.put_object( - bucket_name=metrics_bucket, - object_name=TENANT_GROUP_METRICS_FILE_PATH.format( + self.s3_client.gz_put_json( + bucket=metrics_bucket, + key=TENANT_GROUP_METRICS_FILE_PATH.format( customer=customer, date=s3_object_date, tenant=tenant_dn), - body=json.dumps(tenant_group_data, separators=(",", ":"))) + obj=tenant_group_data + ) if not event.get(END_DATE) or calendar.monthrange( end_date.year, end_date.month)[1] == end_date.day: @@ -329,7 +325,7 @@ def _process_overview_metrics(cloud: str, overview_data: dict, def _process_attack_vector_metrics(tenant_content: dict): _LOG.debug('Process metrics for attack vector report') reduced_attack_metrics = [] - for tactic in tenant_content.get(ATTACK_VECTOR_TYPE): + for tactic in tenant_content.get(ATTACK_VECTOR_TYPE, []): tactic_name = tactic.get('tactic') tactic_id = tactic.get('tactic_id') tactic_item = { @@ -337,7 +333,6 @@ def _process_attack_vector_metrics(tenant_content: dict): 'tactic': tactic_name, 'techniques_data': [] } - tactic_severity = [] for tech in tactic.get('techniques_data', []): technique_item = { 'technique_id': tech.get('technique_id'), @@ -345,33 +340,19 @@ def _process_attack_vector_metrics(tenant_content: dict): 'regions_data': {} } for region, resource in tech.get('regions_data', {}).items(): - severity_set = {} + severity_list = {} for data in resource.get('resources'): severity = data.get('severity') - severity_set.setdefault(severity, set()).add( - hashable(data['resource'])) - - keep_highest(*[ - severity_set.get(k) for k in - sorted(severity_set.keys(), - key=cmp_to_key(severity_cmp)) - ]) + for _ in range(len(data['sub_techniques']) if len(data['sub_techniques']) != 0 else 1): + severity_list.setdefault(severity, []).append( + data['resource']) - severity_sum = {k: len(v) for k, v in severity_set.items()} + severity_sum = {k: len(v) for k, v in severity_list.items()} technique_item['regions_data'].setdefault( region, {'severity_data': {}})[ 'severity_data'] = severity_sum - tactic_severity.append(severity_set) tactic_item['techniques_data'].append(technique_item) - severity_data = {} - for d in tactic_severity: - for key, value_set in d.items(): - if key not in severity_data: - severity_data[key] = set() - - severity_data[key].update(value_set) - reduced_attack_metrics.append(tactic_item) return reduced_attack_metrics @@ -389,7 +370,7 @@ def _process_finops_metrics(finops_content: dict): for region, data in rule_item['regions_data'].items(): new_item['regions_data'][region].pop('resources', None) new_item['regions_data'][region]['total_violated_resources'] = \ - {'value': len(data.get('resources', 0)), 'diff': None} + {'value': len(data.get('resources', 0))} service_severity_mapping[service_section]['rules_data'].append(new_item) @@ -410,10 +391,11 @@ def _save_monthly_state(self, data: dict, group: str, customer: str): path = f'{customer}/tenants/monthly/{self.next_month_date}/{group}.json' metrics_bucket = self.environment_service.get_metrics_bucket_name() _LOG.debug(f'Save monthly metrics for tenant group {group}') - self.s3_client.put_object( - bucket_name=metrics_bucket, - object_name=path, - body=json.dumps(data, separators=(",", ":"))) + self.s3_client.gz_put_json( + bucket=metrics_bucket, + key=path, + obj=data + ) @staticmethod def deduplication(resources: dict): diff --git a/src/lambdas/custodian_metrics_updater/processors/tenant_metrics_processor.py b/src/lambdas/custodian_metrics_updater/processors/tenant_metrics_processor.py index d3ea4349d..9e810ac96 100644 --- a/src/lambdas/custodian_metrics_updater/processors/tenant_metrics_processor.py +++ b/src/lambdas/custodian_metrics_updater/processors/tenant_metrics_processor.py @@ -1,37 +1,40 @@ import calendar -import json from datetime import datetime, timedelta from functools import cmp_to_key -from typing import List, Dict, Tuple, Any, TypedDict +from typing import List, Dict, TypedDict, Optional from dateutil.relativedelta import relativedelta, SU from modular_sdk.models.tenant import Tenant +from modular_sdk.modular import Modular -from helpers import get_logger, CustodianException +from helpers import get_logger, hashable from helpers.constants import \ RULE_TYPE, OVERVIEW_TYPE, RESOURCES_TYPE, CLOUD_ATTR, CUSTOMER_ATTR, \ - SUCCEEDED_SCANS_ATTR, JOB_SUCCEEDED_STATUS, FAILED_SCANS_ATTR, \ - JOB_FAILED_STATUS, TOTAL_SCANS_ATTR, TENANT_ATTR, LAST_SCAN_DATE, \ - COMPLIANCE_TYPE, TENANT_NAME_ATTR, ID_ATTR, ATTACK_VECTOR_TYPE, \ - DATA_TYPE, TACTICS_ID_MAPPING, MANUAL_TYPE_ATTR, REACTIVE_TYPE_ATTR, \ - AWS_CLOUD_ATTR, AZURE_CLOUD_ATTR, END_DATE, FINOPS_TYPE, OUTDATED_TENANTS -from helpers.reports import hashable, keep_highest + SUCCEEDED_SCANS_ATTR, FAILED_SCANS_ATTR, JobState, \ + TOTAL_SCANS_ATTR, LAST_SCAN_DATE, COMPLIANCE_TYPE, TENANT_NAME_ATTR, \ + ID_ATTR, ATTACK_VECTOR_TYPE, DATA_TYPE, TACTICS_ID_MAPPING, END_DATE, \ + FINOPS_TYPE, ARCHIVE_PREFIX, OUTDATED_TENANTS, KUBERNETES_TYPE, Cloud +from helpers.reports import keep_highest, severity_cmp, merge_dictionaries from helpers.system_customer import SYSTEM_CUSTOMER -from helpers.time_helper import utc_datetime -from helpers.utils import severity_cmp +from helpers.time_helper import utc_datetime, week_number +from models.batch_results import BatchResults from services import SERVICE_PROVIDER -from services.batch_results_service import BatchResultsService +from services import modular_helpers +from services.ambiguous_job_service import AmbiguousJobService, AmbiguousJob from services.clients.s3 import S3Client from services.coverage_service import CoverageService from services.environment_service import EnvironmentService -from services.findings_service import FindingsService -from services.job_service import JobService from services.job_statistics_service import JobStatisticsService +from services.license_service import LicenseService +from services.mappings_collector import LazyLoadedMappingsCollector from services.metrics_service import MetricsService -from services.modular_service import ModularService -from services.rule_meta_service import LazyLoadedMappingsCollector -from services.rule_report_service import RuleReportService +from services.platform_service import PlatformService +from services.report_service import ReportService +from services.reports_bucket import TenantReportsBucketKeysBuilder, \ + PlatformReportsBucketKeysBuilder, StatisticsBucketKeysBuilder from services.setting_service import SettingsService +from services.sharding import (ShardsCollectionFactory, ShardsS3IO, + ShardsCollection) _LOG = get_logger(__name__) @@ -54,29 +57,30 @@ class PrettifiedFinding(TypedDict): class TenantMetrics: - def __init__(self, job_service: JobService, s3_client: S3Client, - batch_results_service: BatchResultsService, - findings_service: FindingsService, + def __init__(self, ambiguous_job_service: AmbiguousJobService, + s3_client: S3Client, environment_service: EnvironmentService, - rule_report_service: RuleReportService, settings_service: SettingsService, - modular_service: ModularService, + modular_client: Modular, coverage_service: CoverageService, metrics_service: MetricsService, mappings_collector: LazyLoadedMappingsCollector, - job_statistics_service: JobStatisticsService): - self.job_service = job_service - self.batch_results_service = batch_results_service + job_statistics_service: JobStatisticsService, + license_service: LicenseService, + report_service: ReportService, + platform_service: PlatformService): + self.ambiguous_job_service = ambiguous_job_service self.s3_client = s3_client - self.findings_service = findings_service self.environment_service = environment_service - self.rule_report_service = rule_report_service self.settings_service = settings_service - self.modular_service = modular_service + self.modular_client = modular_client self.coverage_service = coverage_service self.mappings_collector = mappings_collector self.metrics_service = metrics_service self.job_statistics_service = job_statistics_service + self.license_service = license_service + self.report_service = report_service + self.platform_service = platform_service self.today_date = datetime.today() self.today_midnight = datetime.combine(self.today_date, @@ -85,39 +89,42 @@ def __init__(self, job_service: JobService, s3_client: S3Client, self._date_marker = self.settings_service.get_report_date_marker() self.current_week_date = self._date_marker.get('current_week_date') self.last_week_date = self._date_marker.get('last_week_date') - self.last_week_datetime = utc_datetime( - self.last_week_date if self.last_week_date else - str((self.today_date - timedelta(days=7)).date()), utc=False) self.yesterday = (self.today_date - timedelta(days=1)).date() self.next_month_date = (self.today_date.date().replace(day=1) + relativedelta(months=1)).isoformat() - self.month_first_day = self.today_date.date().replace( - day=1).isoformat() + self.month_first_day = datetime.combine( + self.today_date.replace(day=1), datetime.min.time()) + self.month_first_day_iso = self.month_first_day.date().isoformat() + # won't cross bounds + self.month_last_day = self.today_date.date() + relativedelta(day=31) + + self.end_date = None + self.start_date = None self.TO_UPDATE_MARKER = False self.weekly_scan_statistics = {} @classmethod def build(cls) -> 'TenantMetrics': return cls( - job_service=SERVICE_PROVIDER.job_service(), - environment_service=SERVICE_PROVIDER.environment_service(), - s3_client=SERVICE_PROVIDER.s3(), - batch_results_service=SERVICE_PROVIDER.batch_results_service(), - findings_service=SERVICE_PROVIDER.findings_service(), - rule_report_service=SERVICE_PROVIDER.rule_report_service(), - settings_service=SERVICE_PROVIDER.settings_service(), - modular_service=SERVICE_PROVIDER.modular_service(), - coverage_service=SERVICE_PROVIDER.coverage_service(), - mappings_collector=SERVICE_PROVIDER.mappings_collector(), - metrics_service=SERVICE_PROVIDER.metrics_service(), - job_statistics_service=SERVICE_PROVIDER.job_statistics_service() + environment_service=SERVICE_PROVIDER.environment_service, + s3_client=SERVICE_PROVIDER.s3, + ambiguous_job_service=SERVICE_PROVIDER.ambiguous_job_service, + settings_service=SERVICE_PROVIDER.settings_service, + modular_client=SERVICE_PROVIDER.modular_client, + coverage_service=SERVICE_PROVIDER.coverage_service, + mappings_collector=SERVICE_PROVIDER.mappings_collector, + metrics_service=SERVICE_PROVIDER.metrics_service, + job_statistics_service=SERVICE_PROVIDER.job_statistics_service, + license_service=SERVICE_PROVIDER.license_service, + report_service=SERVICE_PROVIDER.report_service, + platform_service=SERVICE_PROVIDER.platform_service ) class ResourcesAndOverviewCollector: - def __init__(self, findings: dict, + def __init__(self, meta: dict, mappings_collector: LazyLoadedMappingsCollector): - self._findings = findings + self._meta = meta # raw meta from rules self._mappings_collector = mappings_collector self._resources = {} # rule & region to list of unique resources @@ -143,14 +150,15 @@ def add_resource(self, rule: str, region: str, resource: dict): def resources(self) -> List[PrettifiedFinding]: result = [] + service = self._mappings_collector.service + severity = self._mappings_collector.severity + meta = self._meta for rule in self._resources: item = { "policy": rule, - "resource_type": self._mappings_collector.service.get( - rule), - "description": self._findings.get(rule).get( - 'description') or '', - "severity": self._mappings_collector.severity.get(rule), + "resource_type": service.get(rule), + "description": meta.get(rule).get('description') or '', + "severity": severity.get(rule), "regions_data": {} } for region, res in self._resources[rule].items(): @@ -158,6 +166,24 @@ def resources(self) -> List[PrettifiedFinding]: result.append(item) return result + def k8s_resources(self) -> List: + result = [] + service = self._mappings_collector.service + severity = self._mappings_collector.severity + meta = self._meta + for rule in self._resources: + item = { + "policy": rule, + "resource_type": service.get(rule), + "description": meta.get(rule).get('description') or '', + "severity": severity.get(rule), + "resources": [] + } + for region, res in self._resources[rule].items(): + item['resources'].extend(list(res)) + result.append(item) + return result + def region_severity(self, unique: bool = True) -> dict: """ Returns something like this: @@ -216,7 +242,7 @@ def attack_vector(self) -> List[Dict]: continue severity = self._mappings_collector.severity.get(rule) resource_type = self._mappings_collector.service.get(rule) - description = self._findings.get(rule).get('description') or '' + description = self._meta.get(rule).get('description') or '' for region, res in self._resources[rule].items(): for tactic, data in attack_vector.items(): @@ -260,10 +286,60 @@ def attack_vector(self) -> List[Dict]: return resulting_dict + def k8s_attack_vector(self) -> List[Dict]: + # TODO REFACTOR IT + temp = {} + for rule in self._resources: + attack_vector = self._mappings_collector.mitre.get(rule) + if not attack_vector: + _LOG.debug(f'Attack vector not found for {rule}. Skipping') + continue + severity = self._mappings_collector.severity.get(rule) + resource_type = self._mappings_collector.service.get(rule) + description = self._meta.get(rule).get('description') or '' + + for region, res in self._resources[rule].items(): + for tactic, data in attack_vector.items(): + for technique in data: + technique_name = technique.get('tn_name') + technique_id = technique.get('tn_id') + sub_techniques = list( + st['st_name'] for st in technique.get('st', []) + ) + tactics_data = temp.setdefault(tactic, { + 'tactic_id': TACTICS_ID_MAPPING.get(tactic), + 'techniques_data': {} + }) + techniques_data = tactics_data[ + 'techniques_data'].setdefault( + technique_name, { + 'technique_id': technique_id + } + ) + resources_data = techniques_data.setdefault( + 'resources', []) + resources_data.extend([{ + 'resource': r, 'resource_type': resource_type, + 'rule': description, + 'severity': severity, + 'sub_techniques': sub_techniques + } for r in res]) + resulting_dict = [] + + for tactic, techniques in temp.items(): + item = {"tactic_id": techniques['tactic_id'], "tactic": tactic, + "techniques_data": []} + for technique, data in techniques['techniques_data'].items(): + item['techniques_data'].append( + {**data, 'technique': technique}) + resulting_dict.append(item) + + return resulting_dict + def finops(self): service_resource_mapping = {} for rule in self._resources: - category = self._mappings_collector.category.get(rule) + category = self._mappings_collector.category.get(rule, '') if 'FinOps' not in category: continue else: @@ -274,7 +350,7 @@ def finops(self): service_resource_mapping.setdefault(service_section, {'rules_data': []}) - description = self._findings.get(rule).get('description') or '' + description = self._meta.get(rule).get('description') or '' severity = self._mappings_collector.severity.get(rule) service = self._mappings_collector.service.get(rule) resource_type = self._mappings_collector.service.get(rule) @@ -311,17 +387,16 @@ def process_data(self, event): tenant_to_job_mapping = {} tenant_last_job_mapping = {} missing_tenants = {} - metrics_bucket = self.environment_service.get_metrics_bucket_name() - stat_bucket = self.environment_service.get_statistics_bucket_name() - end_date = event.get(END_DATE) + metrics_bucket: str = self.environment_service.get_metrics_bucket_name() + self.end_date: Optional[str] = event.get(END_DATE) - if end_date: - end_datetime = utc_datetime(end_date, utc=False) + if self.end_date: + end_datetime = utc_datetime(self.end_date, utc=False) if end_datetime < self.today_date.astimezone(): end_date_datetime = end_datetime weekday = end_date_datetime.weekday() - s3_object_date = end_date if weekday == 6 else ( + s3_object_date = self.end_date if weekday == 6 else ( end_date_datetime + timedelta(days=6 - weekday)). \ date().isoformat() self.next_month_date = ( @@ -329,38 +404,35 @@ def process_data(self, event): relativedelta(months=1)).date().isoformat() su_number = -1 if weekday != 6 else -2 - start_date = (end_date_datetime + relativedelta( + self.start_date = (end_date_datetime + relativedelta( weekday=SU(su_number))).date() - end_date = end_date_datetime.date() + self.end_date = end_date_datetime.date() else: - start_date, s3_object_date, end_date = self._default_dates() + self.start_date, s3_object_date, self.end_date = self._default_dates() else: - start_date, s3_object_date, end_date = self._default_dates() + self.start_date, s3_object_date, self.end_date = self._default_dates() # get all scans for each of existing customer - _LOG.debug(f'Retrieving jobs between {start_date} and ' - f'{end_date} dates') - for customer in self.modular_service.i_get_customers(): - tenant_objects = {} - if customer == SYSTEM_CUSTOMER: + _LOG.debug(f'Retrieving jobs between {self.start_date} and ' + f'{self.end_date} dates') + for customer in self.modular_client.customer_service().i_get_customer(): + tenant_objects = {} # todo report shadowing another var with this name + if customer == SYSTEM_CUSTOMER: # TODO report different types _LOG.debug('Skipping system customer') continue + jobs = self.ambiguous_job_service.get_by_customer_name( + customer_name=customer.name, + start=datetime.combine(self.start_date, datetime.min.time()), + end=datetime.combine(self.end_date, datetime.now().time()), + limit=100 # TODO report what if there're more + ) + customer_to_tenants_mapping.setdefault( # TODO report customer_to_jobs_mapping + customer.name, []).extend(jobs) + + for customer, jobs in customer_to_tenants_mapping.items(): + current_platforms = {} + current_accounts = set(job.tenant_name for job in jobs) - for job_type in (MANUAL_TYPE_ATTR, REACTIVE_TYPE_ATTR): - jobs = self._retrieve_recent_scans( - start=datetime.combine(start_date, datetime.min.time()), - end=datetime.combine(end_date, datetime.min.time()), - customer_name=customer.name, job_type=job_type) - customer_to_tenants_mapping.setdefault( - customer.name, {}).setdefault(job_type, []).extend(jobs) - - for customer, types in customer_to_tenants_mapping.items(): - current_accounts = set() - for t, jobs in types.items(): - current_accounts.update( - {getattr(j, 'tenant_display_name', None) or - getattr(j, 'tenant_name', None) for j in - jobs if j}) missing = self._check_not_scanned_tenants( prev_key=f'{customer}/accounts/{self.last_week_date}/', current_accounts=current_accounts) @@ -370,8 +442,9 @@ def process_data(self, event): if not project: _LOG.debug(f'Somehow non-existing missing: "{project}"') continue - tenant_obj = list(self.modular_service.i_get_tenants_by_acc( - acc=project, active=True)) + tenant_obj = list(self.modular_client.tenant_service().i_get_by_acc( + project, active=True # todo report limit 1 + )) if not tenant_obj: _LOG.warning(f'Cannot find tenant with id {project}. ' f'Skipping...') @@ -384,84 +457,62 @@ def process_data(self, event): tenant_obj = tenant_obj[0] missing_tenants[project] = tenant_obj - for t, jobs in types.items(): - for job in jobs: - name = job.tenant_display_name if t == MANUAL_TYPE_ATTR \ - else job.tenant_name - result_tenant_data.setdefault(name, {OVERVIEW_TYPE: { - TOTAL_SCANS_ATTR: 0, - FAILED_SCANS_ATTR: 0, - SUCCEEDED_SCANS_ATTR: 0 - } - }) - tenant_overview = result_tenant_data[name][OVERVIEW_TYPE] - tenant_overview[TOTAL_SCANS_ATTR] += 1 - if job.status == JOB_FAILED_STATUS: - tenant_overview[FAILED_SCANS_ATTR] += 1 + for job in jobs: + name = job.tenant_name + if AmbiguousJob(job).is_platform_job: + last_scan = current_platforms.setdefault(name, {}).\ + setdefault(job.platform_id) + if not last_scan or last_scan < job.submitted_at: + current_platforms[name][job.platform_id] = \ + job.submitted_at + + result_tenant_data.setdefault(name, {OVERVIEW_TYPE: { + TOTAL_SCANS_ATTR: 0, + FAILED_SCANS_ATTR: 0, + SUCCEEDED_SCANS_ATTR: 0} + }) + tenant_overview = result_tenant_data[name][OVERVIEW_TYPE] + tenant_overview[TOTAL_SCANS_ATTR] += 1 + if job.status == JobState.FAILED.value: + tenant_overview[FAILED_SCANS_ATTR] += 1 + continue + elif job.status == JobState.SUCCEEDED.value: + tenant_overview[SUCCEEDED_SCANS_ATTR] += 1 + else: + _LOG.warning(f'Unknown scan status: {job.status}; ' + f'scan {job.id}') # TODO report unknown status is still a status. Currently n_failed + n_succeeded != n_total + + if name not in tenant_objects: + tenant_obj = self.modular_client.tenant_service().get(name) + if not tenant_obj or not tenant_obj.project: + _LOG.warning(f'Cannot find tenant {name}. Skipping...') continue - elif job.status == JOB_SUCCEEDED_STATUS: - tenant_overview[SUCCEEDED_SCANS_ATTR] += 1 - else: - _LOG.warning(f'Unknown scan status: {job.status}; ' - f'scan {job.job_id}') - - if name not in tenant_objects: - tenant_obj = self.modular_service.get_tenant( - tenant=name) - if not tenant_obj or not tenant_obj.project: - _LOG.warning( - f'Cannot find tenant {name}. Skipping...') - continue - tenant_objects[name] = tenant_obj - - if t == MANUAL_TYPE_ATTR: - tenant_to_job_mapping.setdefault(name, {}).setdefault( - t, []).append(job) - last_job = tenant_last_job_mapping.setdefault(name, job) - if last_job.submitted_at < job.submitted_at: - tenant_last_job_mapping[name] = job + tenant_objects[name] = tenant_obj + + tenant_to_job_mapping.setdefault(name, []).append(job) + last_job = tenant_last_job_mapping.setdefault(name, job) + if last_job.submitted_at < job.submitted_at: + tenant_last_job_mapping[name] = job today_date = self.today_date.date().isoformat() - if not event.get(END_DATE): - if today_date == self.month_first_day: + if tenant_objects and not event.get(END_DATE): + if today_date == self.month_first_day_iso: self.save_weekly_job_stats(customer, tenant_objects, end_date=today_date) else: self.save_weekly_job_stats(customer, tenant_objects) if not tenant_objects: - _LOG.warning(f'No jobs for period {start_date} to {end_date}') + _LOG.warning( + f'No jobs for period {self.start_date} to {self.end_date}') for name, tenant_obj in tenant_objects.items(): cloud = 'google' if tenant_obj.cloud.lower() == 'gcp' \ else tenant_obj.cloud.lower() identifier = tenant_obj.project - active_regions = list(self.modular_service.get_tenant_regions( - tenant_obj)) + active_regions = list(modular_helpers.get_tenant_regions(tenant_obj)) _LOG.debug(f'Processing \'{name}\' tenant with id {identifier} ' f'and active regions: {", ".join(active_regions)}') - if event.get(END_DATE): - findings = self.findings_service.get_findings_content( - tenant_obj.project, findings_date=end_date.isoformat()) - else: - findings = self.findings_service.get_findings_content( - tenant_obj.project) - if not findings: - _LOG.warning( - f'Cannot find findings for tenant \'{name}\'. Skipping') - continue - - # save for customer metrics - # todo drop after s3 listing with prefix will be tested - if not event.get(END_DATE): - obj_name = f'{self.next_month_date}/{self.findings_service._get_key(tenant_objects.get(name).project)}' - self.s3_client.put_object( - bucket_name=stat_bucket, - object_name=obj_name, - body=json.dumps(findings, separators=(',', ':')) - ) - - col = self._initiate_resource_collector(findings, tenant_obj) # general account info result_tenant_data.setdefault(name, {}).update({ CUSTOMER_ATTR: tenant_obj.customer_name, @@ -469,89 +520,119 @@ def process_data(self, event): ID_ATTR: tenant_obj.account_number or tenant_obj.project, CLOUD_ATTR: cloud, 'activated_regions': active_regions, - 'from': start_date.isoformat(), - 'to': end_date.isoformat(), + 'from': self.start_date.isoformat(), + 'to': self.end_date.isoformat(), OUTDATED_TENANTS: {}, LAST_SCAN_DATE: tenant_last_job_mapping[name].submitted_at }) - # coverage - _LOG.debug('Calculating tenant coverage') - coverage = self._get_tenant_compliance(tenant_obj, findings) - result_tenant_data.get(name).update( - {COMPLIANCE_TYPE: coverage}) - # resources - _LOG.debug('Collecting tenant resources metrics') - result_tenant_data.setdefault(name, {}).update({ - RESOURCES_TYPE: col.resources() - }) - # overview - _LOG.debug('Collecting tenant overview metrics') - result_tenant_data.setdefault(name, {}).setdefault(OVERVIEW_TYPE, - {}).update({ - 'resources_violated': col.len_of_unique(), - 'regions_data': col.region_severity() - }) - # rule - _LOG.debug('Collecting tenant rule metrics') - try: - statistics = self.rule_report_service.attain_referenced_reports( - start_iso=datetime.combine(start_date, - datetime.min.time()), - end_iso=end_date, - cloud_ids=[identifier], - entity_attr=TENANT_ATTR, - source_list=tenant_to_job_mapping.get(name, {}).get( - MANUAL_TYPE_ATTR, []), - list_format=True, - typ=MANUAL_TYPE_ATTR) - except CustodianException as e: - _LOG.error(f'Caught error: {e}\nRule statistic for tenant ' - f'{tenant_obj.name} will be empty') - statistics = {} - result_tenant_data[name].setdefault( - RULE_TYPE, { - 'rules_data': statistics.get(name, []), - 'violated_resources_length': col.len_of_unique() - }) - # attack vector - result_tenant_data[name].setdefault( - ATTACK_VECTOR_TYPE, col.attack_vector() - ) - # finops - result_tenant_data[name].setdefault( - FINOPS_TYPE, col.finops() + + builder = TenantReportsBucketKeysBuilder(tenant_obj) + if event.get(END_DATE): + key = (builder.nearest_snapshot_key(self.end_date) + or builder.latest_key()) + else: + key = builder.latest_key() + + collection = ShardsCollectionFactory.from_tenant(tenant_obj) + collection.io = ShardsS3IO( + bucket=self.environment_service.default_reports_bucket_name(), + key=key, + client=self.s3_client ) + collection.fetch_all() + collection.fetch_meta() + + merge_dictionaries(self._collect_tenant_metrics(collection, tenant_obj), + result_tenant_data[name]) + # k8s cluster + result_tenant_data[name].setdefault(KUBERNETES_TYPE, {}) + platforms = current_platforms.get(tenant_obj.name, {}) + for platform_id, platform_last_scan in platforms.items(): + platform = self.platform_service.get_nullable(platform_id) + if not platform: + _LOG.debug(f'Skipping platform with id {platform_id}: ' + f'cannot find item with such id') + continue + self.platform_service.fetch_application(platform) + + builder = PlatformReportsBucketKeysBuilder(platform) + if event.get(END_DATE): + k8s_key = (builder.nearest_snapshot_key(self.end_date) + or builder.latest_key()) + else: + k8s_key = builder.latest_key() + + k8s_collection = ShardsCollectionFactory.from_cloud( + Cloud.KUBERNETES) + k8s_collection.io = ShardsS3IO( + bucket=self.environment_service.default_reports_bucket_name(), + key=k8s_key, + client=self.s3_client + ) + k8s_collection.fetch_all() + k8s_collection.fetch_meta() + + result_tenant_data[name][KUBERNETES_TYPE].setdefault( + platform.id, self._collect_k8s_metrics(k8s_collection)) + result_tenant_data[name][KUBERNETES_TYPE][platform.id].update({ + 'region': platform.region, + 'last_scan_date': platform_last_scan + }) + # saving to s3 _LOG.debug(f'Saving metrics of {tenant_obj.name} tenant to ' f'{metrics_bucket}') - self.s3_client.put_object( - bucket_name=metrics_bucket, - object_name=TENANT_METRICS_FILE_PATH.format( + if not event.get(END_DATE) and \ + self.today_date.date().isoformat() == self.month_first_day_iso \ + and not self._is_tenant_active(tenant_obj): + identifier = f'{ARCHIVE_PREFIX}-{identifier}' + + self.s3_client.gz_put_json( + bucket=metrics_bucket, + key=TENANT_METRICS_FILE_PATH.format( customer=tenant_obj.customer_name, date=s3_object_date, project_id=identifier), - body=json.dumps(result_tenant_data.get(name), - separators=(",", ":")) + obj=result_tenant_data.get(name) ) - if not event.get(END_DATE) or calendar.monthrange( - end_date.year, end_date.month)[1] == end_date.day: - self._save_monthly_state(result_tenant_data.get(name), - identifier, - tenant_obj.customer_name) - - if self.TO_UPDATE_MARKER: - _LOG.debug(f'Saving metrics of {tenant_obj.name} for current ' - f'date') - s3_object_date = (self.today_date + relativedelta( - weekday=SU(0))).date().isoformat() - self.s3_client.put_object( - bucket_name=metrics_bucket, - object_name=TENANT_METRICS_FILE_PATH.format( + if identifier.startswith(ARCHIVE_PREFIX): + _LOG.debug( + f'Deleting non-archive metrics for tenant {tenant_obj.project}') + self.s3_client.gz_delete_object( + bucket=metrics_bucket, + key=TENANT_METRICS_FILE_PATH.format( customer=tenant_obj.customer_name, date=s3_object_date, - project_id=identifier), - body=json.dumps(result_tenant_data.get(name), - separators=(',', ':')) + project_id=tenant_obj.project + ) ) + + if not identifier.startswith(ARCHIVE_PREFIX): + if not event.get(END_DATE) or calendar.monthrange( + self.end_date.year, self.end_date.month)[1] == \ + self.end_date.day: + self._save_monthly_state(result_tenant_data.get(name), + identifier, + tenant_obj.customer_name) + if self.TO_UPDATE_MARKER: + _LOG.debug(f'Saving metrics of {tenant_obj.name} for current ' + f'date') + s3_object_date = (self.today_date + relativedelta( + weekday=SU(0))).date().isoformat() + self.s3_client.gz_put_json( + bucket=metrics_bucket, + key=TENANT_METRICS_FILE_PATH.format( + customer=tenant_obj.customer_name, + date=s3_object_date, + project_id=identifier), + obj=result_tenant_data.get(name) + ) + + if not event.get(END_DATE) and \ + (self.today_date.date().isoformat() == + self.month_first_day_iso or self.TO_UPDATE_MARKER): + self._save_monthly_rule_statistics( + tenant_obj, + result_tenant_data[name][RULE_TYPE].get('rules_data', [])) # to free memory result_tenant_data.pop(name) @@ -563,15 +644,22 @@ def process_data(self, event): continue dict_to_save['customer_name'] = cid dict_to_save['cloud'] = c - dict_to_save['tenants'] = dict_to_save.get( - 'tenants', {}) - self.job_statistics_service.create(dict_to_save).save() + dict_to_save['tenants'] = dict_to_save.get('tenants', {}) + self.job_statistics_service.save(dict_to_save) _LOG.debug( 'Copy metrics for tenants that haven\'t been scanned this week') for tenant, obj in missing_tenants.items(): - if self.today_date.weekday() != 0: # not Monday - if self.s3_client.file_exists( + tenant_obj = None + filename = obj.project + if not event.get(END_DATE): + if self.today_date.date() == self.month_first_day_iso: + tenant_obj = next(self.modular_client.tenant_service().i_get_by_acc( + obj.project + ), None) + if not self._is_tenant_active(tenant_obj): + filename = f'{ARCHIVE_PREFIX}-{obj.project}' + elif self.today_date.weekday() != 0 and self.s3_client.gz_object_exists( metrics_bucket, TENANT_METRICS_FILE_PATH.format( customer=obj.customer_name, date=self.current_week_date, @@ -580,10 +668,10 @@ def process_data(self, event): file_path = TENANT_METRICS_FILE_PATH.format( customer=obj.customer_name, - date=start_date.isoformat(), project_id=obj.project) - file_content = self.s3_client.get_json_file_content( - bucket_name=metrics_bucket, - full_file_name=file_path + date=self.start_date.isoformat(), project_id=obj.project) + file_content = self.s3_client.gz_get_json( + bucket=metrics_bucket, + key=file_path ) if not file_content: _LOG.warning(f'Cannot find file {file_path}') @@ -592,85 +680,133 @@ def process_data(self, event): required_types = [FINOPS_TYPE, RESOURCES_TYPE, COMPLIANCE_TYPE, ATTACK_VECTOR_TYPE] if any(_type not in file_content for _type in required_types): - tenant_obj = next(self.modular_service.i_get_tenants_by_acc( - obj.project), None) - findings = self.findings_service.get_findings_content( - tenant_obj.project, - findings_date=end_date.isoformat()) + tenant_obj = next(self.modular_client.tenant_service().i_get_by_acc( + obj.project + ), None) if not tenant_obj else tenant_obj + # SHARDS + + collection = ShardsCollectionFactory.from_tenant(tenant_obj) + collection.io = ShardsS3IO( + bucket=self.environment_service.default_reports_bucket_name(), + key=TenantReportsBucketKeysBuilder( + tenant_obj).nearest_snapshot_key(self.end_date), + client=self.s3_client + ) + collection.fetch_all() + collection.fetch_meta() + if COMPLIANCE_TYPE not in file_content: - coverage = self._get_tenant_compliance(tenant_obj, - findings) + coverage = self._get_tenant_compliance( + modular_helpers.tenant_cloud(tenant_obj), + collection + ) file_content[COMPLIANCE_TYPE] = coverage - col = self._initiate_resource_collector(findings, tenant_obj) + collector = self._initiate_resource_collector( + collection, + modular_helpers.get_tenant_regions(tenant_obj) + ) if FINOPS_TYPE not in file_content: - file_content[FINOPS_TYPE] = col.finops() + file_content[FINOPS_TYPE] = collector.finops() if ATTACK_VECTOR_TYPE not in file_content: - file_content[ATTACK_VECTOR_TYPE] = col.attack_vector() + file_content[ + ATTACK_VECTOR_TYPE] = collector.attack_vector() if RESOURCES_TYPE not in file_content: - file_content[RESOURCES_TYPE] = col.resources() - - file_content = self._update_missing_tenant_content( - file_content, start_date) - self.s3_client.put_object( - bucket_name=metrics_bucket, - object_name=TENANT_METRICS_FILE_PATH.format( - customer=obj.customer_name, - date=self.current_week_date, - project_id=obj.project), - body=json.dumps(file_content, separators=(',', ':')) - ) + file_content[RESOURCES_TYPE] = collector.resources() + + file_content = self._update_missing_tenant_content(file_content) + self.s3_client.gz_put_json(bucket=metrics_bucket, + key=TENANT_METRICS_FILE_PATH.format( + customer=obj.customer_name, + date=self.current_week_date, + project_id=filename), + obj=file_content) + if filename.startswith(ARCHIVE_PREFIX): + _LOG.debug( + f'Deleting non-archive metrics for tenant {obj.project}') + self.s3_client.gz_delete_object( + bucket=metrics_bucket, + key=TENANT_METRICS_FILE_PATH.format( + customer=obj.customer_name, + date=self.current_week_date, + project_id=obj.project + ) + ) return {DATA_TYPE: NEXT_STEP, - END_DATE: end_date.isoformat() if event.get( + END_DATE: self.end_date.isoformat() if event.get( END_DATE) else None, 'continuously': event.get('continuously')} + def _save_monthly_rule_statistics(self, tenant_obj, rule_data): + date_to_process = utc_datetime(self.current_week_date).date() + if self.today_date.date().isoformat() == self.month_first_day_iso: # if month ends + self.s3_client.gz_put_json( + bucket=self.environment_service.get_statistics_bucket_name(), + key=StatisticsBucketKeysBuilder.tenant_statistics( + self.today_date.date() - timedelta(days=1), + tenant=tenant_obj), + obj=rule_data) + elif week_number(date_to_process) != 1: + self.s3_client.gz_put_json( + bucket=self.environment_service.get_statistics_bucket_name(), + key=StatisticsBucketKeysBuilder.tenant_statistics( + date_to_process, tenant=tenant_obj), + obj=rule_data) + else: # if week does not fully belong to the current month + jobs = self.ambiguous_job_service.get_by_tenant_name( + tenant_name=tenant_obj.name, + start=self.month_first_day, + end=self.end_date, + status=JobState.SUCCEEDED, + ) + average = self.report_service.average_statistics(*map( + self.report_service.job_statistics, jobs + )) + self.s3_client.gz_put_json( + bucket=self.environment_service.get_statistics_bucket_name(), + key=StatisticsBucketKeysBuilder.tenant_statistics( + date_to_process, tenant=tenant_obj), obj=list(average)) + + def _is_tenant_active(self, tenant: Tenant) -> bool: + _LOG.debug(f'Going to check whether Custodian is activated ' + f'for tenant {tenant.name}') + if not tenant.is_active: + _LOG.debug('Tenant is not active') + return False + lic = self.license_service.get_tenant_license(tenant) + if not lic: + return False + if lic.is_expired(): + _LOG.warning(f'License {lic.license_key} has expired') + return False + last_month_date = datetime.combine( + (self.today_date - relativedelta(months=1)).replace(day=1), + datetime.min.time() + ) + if not list(self.ambiguous_job_service.get_by_tenant_name( + tenant_name=tenant.name, start=last_month_date, limit=1)): + _LOG.warning( + f'Tenant {tenant.name} was inactive more than month (no scans ' + f'for previous month since {last_month_date.isoformat()})') + return False + return True + def _default_dates(self): - start_date = self.last_week_datetime.date() + start_date = utc_datetime(self.last_week_date, utc=False).date() \ + if self.last_week_date else ( + self.today_date - timedelta(days=7)).date() s3_object_date = self.current_week_date end_date = self.today_midnight if self.current_week_date <= self.yesterday.isoformat() else datetime.now() self.TO_UPDATE_MARKER = self.current_week_date <= self.yesterday.isoformat() return start_date, s3_object_date, end_date - def _retrieve_recent_scans( - self, customer_name: str, end: datetime, job_type: str, - start: datetime = None) -> Tuple[Any, List]: - """ - Retrieves all jobs that have been executed in the last week and - extracts the account names and status from them - :return: list of jobs - """ - # todo add xray - if job_type == REACTIVE_TYPE_ATTR: - jobs = self.batch_results_service.get_between_period_by_customer( - customer_name=customer_name, start=start.isoformat(), - end=end.isoformat(), limit=100, only_succeeded=False - ) - else: - if not start: - jobs = self.job_service.get_customer_jobs( - customer_display_name=customer_name, limit=100 - ) - else: - jobs = self.job_service.get_customer_jobs_between_period( - start_period=start, end_period=end, customer=customer_name, - only_succeeded=False, limit=100) - _LOG.debug(f'Retrieved {len(jobs)} {job_type} jobs for customer ' - f'{customer_name}') - return jobs - - def _get_tenant_compliance(self, tenant: Tenant, - findings: dict) -> Dict[str, list]: - points = self.coverage_service.derive_points_from_findings(findings) - if tenant.cloud == AWS_CLOUD_ATTR: - points = self.coverage_service.distribute_multiregion(points) - elif tenant.cloud == AZURE_CLOUD_ATTR: - points = self.coverage_service.congest_to_multiregion(points) - coverage = self.coverage_service.calculate_region_coverages( - points=points, cloud=tenant.cloud + def _get_tenant_compliance(self, cloud: Cloud, + collection: ShardsCollection) -> Dict[str, list]: + coverage = self.coverage_service.coverage_from_collection( + collection, cloud=cloud ) result_coverage = [] summarized_coverage = {} @@ -681,7 +817,7 @@ def _get_tenant_compliance(self, tenant: Tenant, if len(coverage) > 1: summarized_coverage.setdefault(n, []).append(v) result_coverage.append(region_item) - average_coverage = [{'name': k, 'value': sum(v) / len(v)} + average_coverage = [{'name': k, 'value': round(sum(v) / len(v), 2)} for k, v in summarized_coverage.items()] return {'regions_data': result_coverage, 'average_data': average_coverage} @@ -695,13 +831,17 @@ def _check_not_scanned_tenants(self, prev_key, current_accounts) -> set: _LOG.debug(f'Previous files: {previous_files}') prev_accounts = set( f.split('/')[-1].split('.')[0] for f in previous_files + if not f.split('/')[-1].startswith(ARCHIVE_PREFIX) ) _LOG.debug(f'TEMP: prev_accounts: {prev_accounts}') - return prev_accounts - set( - self.modular_service.get_tenant(acc).project for acc in - current_accounts) - def _update_missing_tenant_content(self, file_content: dict, start_date): + ts = self.modular_client.tenant_service() + tenants = (ts.get(acc) for acc in current_accounts) + filtered_tenants = (tenant.project for tenant in tenants if + tenant is not None) + return prev_accounts - set(filtered_tenants) + + def _update_missing_tenant_content(self, file_content: dict): """ Reset overview data for tenant that has not been scanned in a specific period @@ -712,7 +852,8 @@ def _update_missing_tenant_content(self, file_content: dict, start_date): {file_content['tenant_name']: file_content['to']} } - file_content['from'] = (start_date + timedelta(days=1)).isoformat() + file_content['from'] = ( + self.start_date + timedelta(days=1)).isoformat() # just if I forget - lambda will update metrics of tenants with # no scans in this week like outdated. If it’s outdated, duplicate it # once a week to avoid making unnecessary READ/PUT actions. @@ -734,10 +875,11 @@ def _save_monthly_state(self, data: dict, project_id: str, customer: str): path = f'{customer}/accounts/monthly/{self.next_month_date}/{project_id}.json' metrics_bucket = self.environment_service.get_metrics_bucket_name() _LOG.debug(f'Save monthly metrics for account {project_id}') - self.s3_client.put_object( - bucket_name=metrics_bucket, - object_name=path, - body=json.dumps(data, separators=(",", ":"))) + self.s3_client.gz_put_json( + bucket=metrics_bucket, + key=path, + obj=data + ) def save_weekly_job_stats(self, customer, tenant_objects, end_date: str = None): @@ -749,10 +891,10 @@ def append_time_period(new_start_date, new_end_date): start_date = (utc_datetime( self.last_week_date) - timedelta(days=7)).date() else: - start_date = self.last_week_date + start_date = utc_datetime(self.last_week_date).date() time_periods = [] - end_date = utc_datetime(end_date) if end_date else utc_datetime( - self.last_week_date) + end_date = utc_datetime(end_date).date() if end_date else utc_datetime( + self.last_week_date).date() if end_date.month != start_date.month: # split data from previous month and current @@ -774,12 +916,11 @@ def append_time_period(new_start_date, new_end_date): customer, start_date, end_date): continue - ed_scans = self._retrieve_recent_scans( - customer, end=utc_datetime(end_date), - start=utc_datetime(start_date), job_type=REACTIVE_TYPE_ATTR) - manual_scans = self._retrieve_recent_scans( - customer, end=utc_datetime(end_date), - start=utc_datetime(start_date), job_type=MANUAL_TYPE_ATTR) + scans = self.ambiguous_job_service.get_by_customer_name( + customer_name=customer, + start=utc_datetime(start_date), + end=utc_datetime(end_date) + ) self.weekly_scan_statistics.setdefault(customer, {}). \ setdefault('customer_name', customer) @@ -791,27 +932,42 @@ def append_time_period(new_start_date, new_end_date): weekly_stats[c]['failed'] = 0 weekly_stats[c]['succeeded'] = 0 - for scan in ed_scans + manual_scans: - name = getattr(scan, 'tenant_display_name', None) or \ - getattr(scan, 'tenant_name', None) + for scan in scans: + name = scan.tenant_name if not (tenant_obj := tenant_objects.get(name)): - tenant_obj = self.modular_service.get_tenant(tenant=name) + tenant_obj = self.modular_client.tenant_service().get(name) if not tenant_obj or not tenant_obj.project: _LOG.warning(f'Cannot find tenant {name}. Skipping...') continue + cloud = tenant_obj.cloud.lower() + weekly_stats[cloud].setdefault('scanned_regions', {}).\ + setdefault(tenant_obj.project, {}) weekly_stats[cloud].setdefault('tenants', {}).setdefault( tenant_obj.project, {'failed_scans': 0, 'succeeded_scans': 0}) - if scan.status == JOB_FAILED_STATUS: + if scan.status == JobState.FAILED.value: weekly_stats[cloud]['failed'] += 1 weekly_stats[cloud]['tenants'][tenant_obj.project][ 'failed_scans'] += 1 - elif scan.status == JOB_SUCCEEDED_STATUS: + reason = getattr(scan, 'reason', None) + if reason: + weekly_stats[cloud].setdefault('reason', {}).setdefault( + tenant_obj.project, {}).setdefault(reason, 0) + weekly_stats[cloud]['reason'][tenant_obj.project][ + reason] += 1 + elif scan.status == JobState.SUCCEEDED.value: weekly_stats[cloud]['tenants'][tenant_obj.project][ 'succeeded_scans'] += 1 weekly_stats[cloud]['succeeded'] += 1 + if scan.Meta.table_name == BatchResults.Meta.table_name: + regions = list(scan.regions_to_rules().keys()) or [] + else: + regions = scan.regions or [] + for region in regions: + weekly_stats[cloud]['scanned_regions'][tenant_obj.project].setdefault(region, 0) + weekly_stats[cloud]['scanned_regions'][tenant_obj.project][region] += 1 weekly_stats[cloud][LAST_SCAN_DATE] = self._get_last_scan_date( scan.submitted_at, weekly_stats[cloud].get(LAST_SCAN_DATE)) @@ -825,15 +981,77 @@ def _get_last_scan_date(new_scan_date: str, last_scan_date: str = None): return new_scan_date return last_scan_date - def _initiate_resource_collector(self, findings, tenant_obj): + def _initiate_resource_collector(self, collection: ShardsCollection, + regions) -> ResourcesAndOverviewCollector: it = self.metrics_service.create_resources_generator( - findings, tenant_obj.cloud, - self.modular_service.get_tenant_regions(tenant_obj)) - col = self.ResourcesAndOverviewCollector(findings, + collection, regions + ) + col = self.ResourcesAndOverviewCollector(collection.meta, self.mappings_collector) - for rule, region, dto in it: + for rule, region, dto, _ in it: col.add_resource(rule, region, dto) return col + def _collect_tenant_metrics(self, collection, tenant_obj) -> dict: + result = {} + collector = self._initiate_resource_collector( + collection, modular_helpers.get_tenant_regions(tenant_obj) + ) + + # coverage + _LOG.debug(f'Calculating {tenant_obj.name} coverage') + result.update({ + COMPLIANCE_TYPE: self._get_tenant_compliance( + modular_helpers.tenant_cloud(tenant_obj), collection) + }) + # resources + _LOG.debug(f'Collecting {tenant_obj.name} resources metrics') + result[RESOURCES_TYPE] = collector.resources() + # attack vector + result[ATTACK_VECTOR_TYPE] = collector.attack_vector() + # overview + _LOG.debug(f'Collecting {tenant_obj.name} overview metrics') + result[OVERVIEW_TYPE] = { + 'resources_violated': collector.len_of_unique(), + 'regions_data': collector.region_severity() + } + # rule + _LOG.debug(f'Collecting {tenant_obj.name} rule metrics') + jobs = self.ambiguous_job_service.get_by_tenant_name( + tenant_name=tenant_obj.name, + start=datetime.combine(self.start_date, datetime.min.time()), + end=self.end_date, + status=JobState.SUCCEEDED, + ) # TODO report query again? + average = self.report_service.average_statistics(*map( + self.report_service.job_statistics, jobs + )) # todo download in threads? + result[RULE_TYPE] = { + 'rules_data': list(average), + 'violated_resources_length': collector.len_of_unique() + } + # finops + result[FINOPS_TYPE] = collector.finops() + return result + + def _collect_k8s_metrics(self, collection) -> dict: + result = {} + collector = self._initiate_resource_collector(collection, []) + + # coverage + _LOG.debug(f'Calculating k8s coverage') + compliance = self._get_tenant_compliance( + Cloud.KUBERNETES, collection).get('regions_data', []) + if not compliance: + result['compliance_data'] = [] + else: + result['compliance_data'] = compliance[0].get('standards_data', []) + # resources + _LOG.debug(f'Collecting k8s resources metrics') + result['policy_data'] = collector.k8s_resources() + # attack vector + result['mitre_data'] = collector.k8s_attack_vector() + return result + TENANT_METRICS = TenantMetrics.build() diff --git a/src/lambdas/custodian_metrics_updater/processors/top_metrics_processor.py b/src/lambdas/custodian_metrics_updater/processors/top_metrics_processor.py index 20a9c716f..a800b5b06 100644 --- a/src/lambdas/custodian_metrics_updater/processors/top_metrics_processor.py +++ b/src/lambdas/custodian_metrics_updater/processors/top_metrics_processor.py @@ -1,24 +1,23 @@ +import copy import heapq from datetime import datetime, date from functools import cmp_to_key -from typing import List from dateutil.relativedelta import relativedelta +from modular_sdk.modular import Modular from helpers import get_logger, hashable from helpers.constants import CUSTOMER_ATTR, NAME_ATTR, VALUE_ATTR, END_DATE, \ OVERVIEW_TYPE, COMPLIANCE_TYPE, RESOURCES_TYPE, TENANT_ATTR, DATA_TYPE, \ TENANT_DISPLAY_NAME_ATTR, AVERAGE_DATA_ATTR, ATTACK_VECTOR_TYPE, \ FINOPS_TYPE, OUTDATED_TENANTS -from helpers.reports import keep_highest +from helpers.reports import keep_highest, severity_cmp from helpers.time_helper import utc_datetime -from helpers.utils import severity_cmp from services import SERVICE_PROVIDER from services.clients.s3 import S3Client +from services.environment_service import EnvironmentService from services.job_statistics_service import JobStatisticsService from services.metrics_service import CustomerMetricsService -from services.environment_service import EnvironmentService -from services.modular_service import ModularService from services.metrics_service import TenantMetricsService _LOG = get_logger(__name__) @@ -41,13 +40,13 @@ def __init__(self, s3_client: S3Client, environment_service: EnvironmentService, tenant_metrics_service: TenantMetricsService, customer_metrics_service: CustomerMetricsService, - modular_service: ModularService, + modular_client: Modular, job_statistics_service: JobStatisticsService): self.s3_client = s3_client self.environment_service = environment_service self.tenant_metrics_service = tenant_metrics_service self.customer_metrics_service = customer_metrics_service - self.modular_service = modular_service + self.modular_client = modular_client self.job_statistics_service = job_statistics_service self.TOP_RESOURCES_BY_TENANT = [] @@ -79,24 +78,24 @@ def __init__(self, s3_client: S3Client, self.today_date = datetime.utcnow().today() self.month_first_day = self.today_date.date().replace( day=1).isoformat() - self.prev_month_first_day = ( - self.today_date - relativedelta(months=1)).replace(day=1).date() - + self.prev_month_first_day = (self.today_date + relativedelta(months=-1, day=1)).date() self.attack_overall_severity = {} self._tenant_metrics_exists = None self._cust_metrics_exists = None self.tenant_scan_mapping = {} + self.ggl_tenant_obj_mapping = {} self.metrics_bucket = self.environment_service.get_metrics_bucket_name() + self.customer_scanned_tenant_list = {} @classmethod def build(cls) -> 'TopMetrics': return cls( - s3_client=SERVICE_PROVIDER.s3(), - environment_service=SERVICE_PROVIDER.environment_service(), - tenant_metrics_service=SERVICE_PROVIDER.tenant_metrics_service(), - customer_metrics_service=SERVICE_PROVIDER.customer_metrics_service(), - modular_service=SERVICE_PROVIDER.modular_service(), - job_statistics_service=SERVICE_PROVIDER.job_statistics_service() + s3_client=SERVICE_PROVIDER.s3, + environment_service=SERVICE_PROVIDER.environment_service, + tenant_metrics_service=SERVICE_PROVIDER.tenant_metrics_service, + customer_metrics_service=SERVICE_PROVIDER.customer_metrics_service, + modular_client=SERVICE_PROVIDER.modular_client, + job_statistics_service=SERVICE_PROVIDER.job_statistics_service ) def process_data(self, event): @@ -135,13 +134,10 @@ def process_data(self, event): continue _LOG.debug(f'Processing tenant group {filename}') - tenant_group_content = self.s3_client.get_json_file_content( - bucket_name=self.metrics_bucket, full_file_name=filename) + tenant_group_content = self.s3_client.gz_get_json( + bucket=self.metrics_bucket, key=filename) + tenant_group_content = copy.deepcopy(tenant_group_content) customer = tenant_group_content.get(CUSTOMER_ATTR) - outdated_tenants = tenant_group_content.get(OUTDATED_TENANTS, {}) - for c in CLOUDS: - self.CUSTOMER_GENERAL[OUTDATED_TENANTS].setdefault(c, {}).\ - update(outdated_tenants.get(c, {})) resource_data = tenant_group_content.get(RESOURCES_TYPE, {}) attack_data = tenant_group_content.get(ATTACK_VECTOR_TYPE, {}) @@ -236,10 +232,6 @@ def create_top_items(self): def add_department_metrics(self, tenant_group_content, customer, attack_by_tenant): - if self.current_month_tenant_metrics_exist(customer): - _LOG.debug('Department metrics already exist for this month') - return - tenant_dn = tenant_group_content.get('tenant_display_name') general_tenant_info = { CUSTOMER_ATTR: customer, @@ -247,27 +239,22 @@ def add_department_metrics(self, tenant_group_content, customer, 'to': tenant_group_content['to'], 'tenant_display_name': tenant_group_content['tenant_display_name'] } - outdated_tenants = tenant_group_content.get(OUTDATED_TENANTS, {}) self._process_department_overview_metrics(tenant_dn, tenant_group_content, - general_tenant_info, - outdated_tenants) + general_tenant_info) self._process_department_compliance_metrics(tenant_dn, tenant_group_content, - general_tenant_info, - outdated_tenants) + general_tenant_info) self._process_department_attack_metrics(tenant_dn, attack_by_tenant, - general_tenant_info, - outdated_tenants) + general_tenant_info) # self._process_department_finops_metrics(tenant_dn, # tenant_group_content, # general_tenant_info) def _process_department_overview_metrics(self, tenant_dn, metrics, - general_tenant_info, - outdated_tenants): + general_tenant_info): customer = metrics[CUSTOMER_ATTR] resources_sum = 0 for cloud in CLOUDS: @@ -275,6 +262,9 @@ def _process_department_overview_metrics(self, tenant_dn, metrics, continue acc_id = overview_metrics.get('account_id') + if cloud == 'google': + acc_id = self.get_google_project_id(acc_id) or acc_id + if self.tenant_scan_mapping.get(customer, {}).get(acc_id): scans = self.tenant_scan_mapping[customer][acc_id] else: @@ -300,8 +290,7 @@ def _process_department_overview_metrics(self, tenant_dn, metrics, overview_metrics.update(sum_regions_data) if overview_metrics.get('resources_violated'): tenant_data = { - **general_tenant_info, cloud: overview_metrics, - OUTDATED_TENANTS: self._get_actual_outdated_tenants(outdated_tenants, [cloud]) + **general_tenant_info, cloud: overview_metrics } self.add_tenant_to_top_by_resources_by_cloud( tenant_name=tenant_dn, @@ -311,16 +300,14 @@ def _process_department_overview_metrics(self, tenant_dn, metrics, resources_sum += overview_metrics.get('resources_violated', 0) tenant_data = { - **general_tenant_info, **metrics[OVERVIEW_TYPE], - OUTDATED_TENANTS: self._get_actual_outdated_tenants(outdated_tenants) + **general_tenant_info, **metrics[OVERVIEW_TYPE] } self.add_tenant_to_top_by_resources(tenant_display_name=tenant_dn, amount=resources_sum, tenant_data=tenant_data) def _process_department_compliance_metrics(self, tenant_dn, metrics, - general_tenant_info, - outdated_tenants): + general_tenant_info): tenant_mean_coverage = [] for cloud in CLOUDS: if not (compliance_metrics := metrics[COMPLIANCE_TYPE].get(cloud)): @@ -338,13 +325,15 @@ def _process_department_compliance_metrics(self, tenant_dn, metrics, coverages = [percent['value'] for percent in compliance_metrics[AVERAGE_DATA_ATTR]] + if not coverages: + _LOG.warning(f'Skipping {compliance_metrics["tenant_name"]} ' + f'because there is no coverage') + continue tenant_mean_coverage.extend(coverages) tenant_data = { - **general_tenant_info, cloud: compliance_metrics, - OUTDATED_TENANTS: self._get_actual_outdated_tenants( - outdated_tenants, [cloud]) + **general_tenant_info, cloud: compliance_metrics } self.add_tenant_to_top_by_compliance_by_cloud( tenant_name=tenant_dn, @@ -354,8 +343,7 @@ def _process_department_compliance_metrics(self, tenant_dn, metrics, tenant_coverage = sum(tenant_mean_coverage) / len(tenant_mean_coverage) tenant_data = { - **general_tenant_info, **metrics[COMPLIANCE_TYPE], - OUTDATED_TENANTS: self._get_actual_outdated_tenants(outdated_tenants) + **general_tenant_info, **metrics[COMPLIANCE_TYPE] } self.add_tenant_to_top_by_compliance(tenant_display_name=tenant_dn, coverage=tenant_coverage, @@ -383,7 +371,6 @@ def _process_department_finops_metrics(self, tenant_dn, metrics, 'severity_data': severity_sum }) - self.CUSTOMER_FINOPS tenant_data = {**general_tenant_info, cloud: new_finops_metrics[cloud]} self.add_tenant_to_top_by_finops_by_cloud( tenant_name=tenant_dn, amount=resources_cloud_sum, @@ -397,13 +384,10 @@ def _process_department_finops_metrics(self, tenant_dn, metrics, tenant_data=tenant_data) def _process_department_attack_metrics(self, tenant_dn, attack_by_tenant, - general_tenant_info, - outdated_tenants): + general_tenant_info): for cloud in CLOUDS: tenant_data = { - **general_tenant_info, cloud: {'data': attack_by_tenant[cloud]}, - OUTDATED_TENANTS: self._get_actual_outdated_tenants( - outdated_tenants, [cloud]) + **general_tenant_info, cloud: {'data': attack_by_tenant[cloud]} } tenant_attack_severity = {} for tactic in attack_by_tenant[cloud]: @@ -417,8 +401,7 @@ def _process_department_attack_metrics(self, tenant_dn, attack_by_tenant, self.TOP_ATTACK_BY_TENANT.append( {TENANT_ATTR: tenant_dn, **general_tenant_info, 'sort_by': self.attack_overall_severity.copy(), - **attack_by_tenant, - OUTDATED_TENANTS: self._get_actual_outdated_tenants(outdated_tenants)}) + **attack_by_tenant}) @staticmethod def _sort_key(item, key): @@ -567,8 +550,10 @@ def _update_general_customer_data(self, cloud: str, data: dict): """ Updates last scan date and calculates amount of customer tenants """ - if data.get('succeeded', 0) or data.get('failed', 0): - self.CUSTOMER_GENERAL[cloud]['total_scanned_tenants'] += 1 + if data.get('succeeded') or data.get('failed'): + if tenants := data.get('tenants', {}): + self.customer_scanned_tenant_list.setdefault(cloud, set()).\ + update(tenants.as_dict().keys()) last_scan_date = self.CUSTOMER_GENERAL[cloud]['last_scan_date'] self.CUSTOMER_GENERAL[cloud]['last_scan_date'] = \ self._get_last_scan_date(data.get('last_scan_date'), @@ -592,34 +577,28 @@ def create_customer_items(self, customer): if not self._cust_metrics_exists.get(ATTACK_VECTOR_TYPE.upper()): attack_vector_item = self.create_attack_vector_item(customer) if attack_vector_item: - attack_vector_item.update({ - OUTDATED_TENANTS: self._get_actual_outdated_tenants( - self.CUSTOMER_GENERAL[OUTDATED_TENANTS]) - }) items.append(self.customer_metrics_service.create( attack_vector_item)) if not self._cust_metrics_exists.get(COMPLIANCE_TYPE.upper()): compliance_item = self.create_compliance_item(customer) - compliance_item.update({ - OUTDATED_TENANTS: self._get_actual_outdated_tenants( - self.CUSTOMER_GENERAL[OUTDATED_TENANTS]) - }) - for cloud in CLOUDS: - if compliance_item[cloud]: - compliance_item[cloud].update(self.CUSTOMER_GENERAL[cloud]) - items.append(self.customer_metrics_service.create(compliance_item)) + if any(compliance_item[cloud] for cloud in CLOUDS): + for cloud in CLOUDS: + if compliance_item[cloud]: + compliance_item[cloud].update( + self.CUSTOMER_GENERAL[cloud]) + items.append(self.customer_metrics_service.create( + compliance_item)) if not self._cust_metrics_exists.get(OVERVIEW_TYPE.upper()): overview_item = self.create_overview_item(customer) - overview_item.update({ - OUTDATED_TENANTS: self._get_actual_outdated_tenants( - self.CUSTOMER_GENERAL[OUTDATED_TENANTS]) - }) - for cloud in CLOUDS: - if overview_item[cloud]: - overview_item[cloud].update(self.CUSTOMER_GENERAL[cloud]) - items.append(self.customer_metrics_service.create(overview_item)) + if any(overview_item[cloud] for cloud in CLOUDS): + for cloud in CLOUDS: + if overview_item[cloud]: + overview_item[cloud].update( + self.CUSTOMER_GENERAL[cloud]) + items.append( + self.customer_metrics_service.create(overview_item)) # # if not self._cust_metrics_exists.get(FINOPS_TYPE.upper()): # finops_item = self.create_finops_item(customer) @@ -740,6 +719,10 @@ def _get_month_customer_scan_stats(self, customer: str, from_date: date, 'succeeded_scans']) self._update_general_customer_data(i.cloud, i.attribute_values) + + for cloud in CLOUDS: + self.CUSTOMER_GENERAL[cloud]['total_scanned_tenants'] = \ + len(self.customer_scanned_tenant_list.get(cloud, [])) return tenant_scan_mapping def _reset_variables(self): @@ -772,6 +755,7 @@ def _reset_variables(self): self._cust_metrics_exists = None self.mean_coverage = {**{c: [] for c in CLOUDS}} self.attack_overall_severity = {} + self.customer_scanned_tenant_list = {} @staticmethod def _unpack_metrics(data): @@ -826,8 +810,8 @@ def _process_customer_overview(self, overview_data: dict): self.CUSTOMER_OVERVIEW[cloud]['resource_types_data'][ _type] += value - def _process_account_mitre(self, mitre_content: list, cloud: str) -> \ - List[dict]: + def _process_account_mitre(self, mitre_content: list, cloud: str + ) -> list[dict]: """ :param mitre_content: Example: [{ @@ -856,7 +840,6 @@ def _process_account_mitre(self, mitre_content: list, cloud: str) -> \ """ _LOG.debug('Process metrics for attack vector report') reduced_attack_metrics = [] - self.attack_overall_severity = {} for tactic in mitre_content: tactic_name = tactic.get('tactic') @@ -902,16 +885,6 @@ def _is_empty(value): return True return False - def _get_actual_outdated_tenants(self, outdated_tenants: dict, - clouds: list = CLOUDS) -> list: - tenants = [] - for cloud in clouds: - for tenant, last_active_date in outdated_tenants.get( - cloud, {}).items(): - if last_active_date < self.month_first_day: - tenants.append(tenant) - return tenants - def _process_customer_mitre(self, metrics, customer, s3_object_date): attack_by_tenant = {c: [] for c in CLOUDS} for cloud in CLOUDS: @@ -920,16 +893,12 @@ def _process_customer_mitre(self, metrics, customer, s3_object_date): account_id = metrics[cloud].get('account_id') if cloud == 'google': - # todo redo acc to accN in metrics filenames for google accounts - tenant = next(self.modular_service.i_get_tenants_by_accN( - metrics[cloud].get('account_id')), None) - if tenant: - account_id = tenant.project + account_id = self.get_google_project_id(account_id) or account_id account_path = TENANT_METRICS_PATH.format( customer=customer, date=s3_object_date) + '/' + account_id + '.json.gz' - account_data = self.s3_client.get_json_file_content( + account_data = self.s3_client.gz_get_json( self.metrics_bucket, account_path).pop(ATTACK_VECTOR_TYPE, {}) attack_metrics = self._process_account_mitre(account_data, cloud) attack_by_tenant[cloud] = attack_metrics @@ -937,5 +906,15 @@ def _process_customer_mitre(self, metrics, customer, s3_object_date): continue return attack_by_tenant + def get_google_project_id(self, account_number): + if not (tenant := self.ggl_tenant_obj_mapping.get(account_number)): + tenant = next(self.modular_client.tenant_service().i_get_by_accN( + account_number + ), None) + self.ggl_tenant_obj_mapping[account_number] = tenant + if tenant: + return tenant.project + return + CUSTOMER_METRICS = TopMetrics.build() diff --git a/src/lambdas/custodian_metrics_updater/requirements.txt b/src/lambdas/custodian_metrics_updater/requirements.txt index ac442dac9..c0e6ec3fa 100644 --- a/src/lambdas/custodian_metrics_updater/requirements.txt +++ b/src/lambdas/custodian_metrics_updater/requirements.txt @@ -1,2 +1,2 @@ XlsxWriter==3.0.3 -cryptography==3.4.7 \ No newline at end of file +cryptography==42.0.2 \ No newline at end of file diff --git a/src/lambdas/custodian_notification_handler/README.md b/src/lambdas/custodian_notification_handler/README.md deleted file mode 100644 index 7f3fdddc8..000000000 --- a/src/lambdas/custodian_notification_handler/README.md +++ /dev/null @@ -1 +0,0 @@ -## Lambda `custodian-notification-handler` \ No newline at end of file diff --git a/src/lambdas/custodian_notification_handler/__init__.py b/src/lambdas/custodian_notification_handler/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/lambdas/custodian_notification_handler/deployment_resources.json b/src/lambdas/custodian_notification_handler/deployment_resources.json deleted file mode 100644 index 417ecb74f..000000000 --- a/src/lambdas/custodian_notification_handler/deployment_resources.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "notification-handler-policy": { - "resource_type": "iam_policy", - "policy_content": { - "Version": "2012-10-17", - "Statement": [ - { - "Action": [ - "dynamodb:PutItem", - "dynamodb:GetItem", - "dynamodb:BatchGetItem", - "dynamodb:GetRecords", - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:PutLogEvents", - "events:PutTargets" - ], - "Resource": "*", - "Effect": "Allow" - } - ] - } - }, - "notification-handler-role": { - "predefined_policies": [], - "principal_service": "lambda", - "custom_policies": [ - "notification-handler-policy", - "lambda-basic-execution" - ], - "resource_type": "iam_role" - } -} \ No newline at end of file diff --git a/src/lambdas/custodian_notification_handler/handler.py b/src/lambdas/custodian_notification_handler/handler.py deleted file mode 100644 index 628206dc4..000000000 --- a/src/lambdas/custodian_notification_handler/handler.py +++ /dev/null @@ -1,303 +0,0 @@ -import copy -from datetime import datetime, timedelta - -from modular_sdk.commons.error_helper import RESPONSE_SERVICE_UNAVAILABLE_CODE - -from helpers import build_response, RESPONSE_BAD_REQUEST_CODE, get_logger -from helpers.constants import TENANT_NAME_ATTR -from services import SERVICE_PROVIDER -from services.abstract_api_handler_lambda import AbstractApiHandlerLambda -from services.batch_results_service import BatchResultsService -from services.clients.event_bridge import EventBridgeClient, RuleTarget -from services.clients.iam import IAMClient -from services.clients.s3 import S3Client -from services.environment_service import EnvironmentService -from services.modular_service import ModularService -from services.notification_service import NotificationService -from services.setting_service import SettingsService -from helpers.reports import AccountsData -from typing import Optional - -SUBJECT = '{tenant_name} Vulnerabilities report {start_date} - {end_date}' -FUNCTION_NAME = 'custodian-notification-handler' - -_LOG = get_logger(__name__) - -# todo remove this lambda and change it with notification service from Modular - - -class NotificationHandler(AbstractApiHandlerLambda): - def __init__(self, notification_service: NotificationService, - batch_results_service: BatchResultsService, - environment_service: EnvironmentService, - modular_service: ModularService, s3_client: S3Client, - settings_service: SettingsService, iam_client: IAMClient, - event_bridge_client: EventBridgeClient): - self.notification_service = notification_service - self.environment_service = environment_service - self.batch_results_service = batch_results_service - self.modular_service = modular_service - self.s3_client = s3_client - self.settings_service = settings_service - self.iam_client = iam_client - self.event_bridge_client = event_bridge_client - - def validate_request(self, event) -> dict: - pass - - def handle_request(self, event, context): - """ - Sends scheduled notifications to the admins about the results of - event-driven jobs - - Event must contain 'tenant_name' (required), 'submitted_at', - 'rule_name' parameters in order to determine who to send the - notifications and in which time range. That's why EB rules should - contain constant JSON - """ - tenant = event.get(TENANT_NAME_ATTR) - submitted_at = event.get('submitted_at') - rule_name = event.get('rule_name') - start: Optional[datetime] = event.get('start') - end: Optional[datetime] = event.get('end') - if not tenant: - return build_response( - code=RESPONSE_BAD_REQUEST_CODE, - content=f'Invalid tenant name: {tenant}' - ) - - tenant_item = self.modular_service.get_tenant(tenant) - if not rule_name: - _LOG.warning( - 'Cannot get rule name: cannot set new last \'submitted_at\' ' - 'timestamp') - - cloud_data = { - 'all_regions': set(), - 'total_checks': 0, - 'total_successful': 0, - 'total_failed': 0, - 'total_vulnerabilities': 0, - 'total_critical': 0, - 'total_high': 0, - 'total_medium': 0, - 'total_low': 0, - 'total_info': 0, - 'total_unknown': 0, - 'accounts': []} - data = { - 'aws': copy.deepcopy(cloud_data), - 'azure': copy.deepcopy(cloud_data), - 'gcp': copy.deepcopy(cloud_data) - } - - if start or end: # TODO move it to a separate Pydantic validator - if start and end: - if (start - end).days > 31: - return build_response( - code=RESPONSE_BAD_REQUEST_CODE, - content='Invalid time range: must be less than 31 days' - ) - elif start: - end = start + timedelta(days=7) - else: - start = end - timedelta(days=7) - - _LOG.debug( - f'Retrieving data between two dates: ' - f'{start.isoformat()} - {end.isoformat()}' - ) - results = self.batch_results_service.get_between_period_by_tenant( - tenant_name=tenant, - start=str(start.timestamp()), - end=str(end.timestamp()) - ) - - else: - end = datetime.utcnow() - start = end - timedelta(days=7) - _LOG.debug(f'Retrieving data for ' - f'last week = since {start.isoformat()}') - results = self.batch_results_service.get_between_period_by_tenant( - tenant_name=tenant, start=str(start.timestamp()) - ) - # todo return when this lambda will be triggered by cron - # else: - # _LOG.debug(f'Retrieving data from \'submitted_at\' date: ' - # f'{submitted_at}') - # results = self.batch_results_service.get_by_tenant( - # tenant_name=tenant, submitted_at=submitted_at) - if not results: - return build_response( - content='No results found for this period, nothing to report' - ) - account_mapping = self._get_account_mapping(results) - - customer = self.modular_service.get_customer(results[0].customer_name) - if tenant_item.contacts and tenant_item.contacts.primary_contacts: - admins = tenant_item.contacts.primary_contacts - else: - admins = customer.admins - - for account_id, items in account_mapping.items(): - _LOG.debug(f'Processing results within {account_id}') - tenant_obj = next( - self.modular_service.i_get_tenants_by_acc(account_id, True), None - ) - filtered_regions = self.modular_service.get_tenant_regions(tenant) - if tenant_obj: - data[tenant_obj.cloud]['all_regions'].update( - filtered_regions) - - # TODO thread it - findings_content = self.s3_client.get_json_file_content( - bucket_name=self.environment_service.get_statistics_bucket_name(), - full_file_name=f'findings/{account_id}.json') - _LOG.debug('Findings content was downloaded') - severity_mapping = { - 'critical': 0, - 'high': 0, - 'medium': 0, - 'low': 0, - 'unknown': 0, - 'info': 0, - } - for _, item in findings_content.items(): - if any(value for value in item['resources'].values()): - severity_mapping[item['severity'].lower()] += 1 - - succeeded = len([i for i in items if i.status == 'SUCCEEDED']) - failed = len([i for i in items if i.status == 'FAILED']) - vulnerabilities = len(findings_content) - data[tenant_obj.cloud]['total_checks'] += len(items) - data[tenant_obj.cloud]['total_successful'] += succeeded - data[tenant_obj.cloud]['total_failed'] += failed - data[tenant_obj.cloud]['total_vulnerabilities'] += \ - vulnerabilities - data[tenant_obj.cloud]['total_critical'] += \ - severity_mapping['critical'] - data[tenant_obj.cloud]['total_high'] += \ - severity_mapping['high'] - data[tenant_obj.cloud]['total_medium'] += \ - severity_mapping['medium'] - data[tenant_obj.cloud]['total_low'] += \ - severity_mapping['low'] - data[tenant_obj.cloud]['total_info'] += \ - severity_mapping['info'] - data[tenant_obj.cloud]['total_unknown'] += \ - severity_mapping['unknown'] - _LOG.debug('Some data was collected') - resource_type_mapping = self._get_resource_type( - findings_content) - severity_chart = self.notification_service.build_pie_chart( - labels=list(severity_mapping.keys()), - values=list(severity_mapping.values()), - colors=['maroon', 'red', 'darkorange', 'royalblue', - 'slategray', 'g'] - ) - checks_chart = self.notification_service.build_donut_chart( - succeeded=succeeded, failed=failed - ) - resource_type_chart = self.notification_service.build_pie_chart( - labels=list(resource_type_mapping.keys()), - values=list(resource_type_mapping.values()) - ) - _LOG.debug('Some charts were built') - - data[tenant_obj.cloud]['accounts'].append( - AccountsData(**{ - 'regions': filtered_regions, - 'account_name': tenant_obj.display_name, - 'total_checks': len(items), - 'success': succeeded, - 'fail': failed, - 'last_sync': datetime.strptime( - max([i.submitted_at for i in items]), - '%Y-%m-%dT%H:%M:%S.%fZ').strftime( - '%B %d, %Y %H:%M:%S'), - 'vulnerabilities': { - 'length': vulnerabilities, - **severity_mapping - }, - 'charts': { - 'checks_performed': checks_chart, - 'severity': severity_chart, - 'resource_type': resource_type_chart - } - })) - - if rule_name: - submitted_at = sorted(results, key=lambda i: i.submitted_at, - reverse=True)[0] - target = RuleTarget( - _id=FUNCTION_NAME, - arn=self.iam_client.build_lambda_arn( - FUNCTION_NAME, - region=self.environment_service.aws_region(), - alias=self.environment_service.lambdas_alias_name() - ), - _input={ - 'rule_name': rule_name, - 'tenant_name': tenant, - 'submitted_at': submitted_at.submitted_at - } - ) - self.event_bridge_client.put_targets(rule_name, [target]) - - result = self.notification_service.send_event_driven_notification( - recipients=admins, subject=SUBJECT.format(tenant_name=tenant, - start_date=start, - end_date=end), - data={ - 'tenant_name': tenant, - 'date': datetime.utcnow().strftime('%B %d, %Y'), - 'period': f'{start.strftime("%B %d, %Y")} - ' - f'{end.strftime("%B %d, %Y")}', - 'contacts': self.settings_service.get_custodian_contacts(), - 'data': {k: v for k, v in data.items() if v['accounts']} - }) - if result: - return build_response( - content=f'Report was successfully send to {", ".join(admins)}') - else: - return build_response( - content='An error occurred while sending the report.' - 'Please contact the support team.', - code=RESPONSE_SERVICE_UNAVAILABLE_CODE - ) - - @staticmethod - def _get_account_mapping(results: list): - account_mapping = dict() - for item in results: - account_mapping.setdefault(item.cloud_identifier, []).append( - item - ) - return account_mapping - - @staticmethod - def _get_resource_type(findings: dict): - resource_type_mapping = dict() - for content in findings.values(): - resource_type = content.get('resourceType') - if resource_type: - resource_type = resource_type.replace('aws.', '') - resource_type_mapping.setdefault(resource_type, 0) - resource_type_mapping[resource_type] += 1 - return resource_type_mapping - - -HANDLER = NotificationHandler( - notification_service=SERVICE_PROVIDER.notification_service(), - modular_service=SERVICE_PROVIDER.modular_service(), - s3_client=SERVICE_PROVIDER.s3(), - batch_results_service=SERVICE_PROVIDER.batch_results_service(), - environment_service=SERVICE_PROVIDER.environment_service(), - settings_service=SERVICE_PROVIDER.settings_service(), - event_bridge_client=SERVICE_PROVIDER.events(), - iam_client=SERVICE_PROVIDER.iam() -) - - -def lambda_handler(event, context): - return HANDLER.lambda_handler(event=event, context=context) diff --git a/src/lambdas/custodian_notification_handler/lambda_config.json b/src/lambdas/custodian_notification_handler/lambda_config.json deleted file mode 100644 index 61d79727c..000000000 --- a/src/lambdas/custodian_notification_handler/lambda_config.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "version": "1.0", - "name": "caas-notification-handler", - "func_name": "handler.lambda_handler", - "resource_type": "lambda", - "iam_role_name": "notification-handler-role", - "runtime": "python3.8", - "memory": 128, - "timeout": 300, - "lambda_path": "/lambdas/custodian_notification_handler", - "dependencies": [ - { - "resource_name": "CaaSBatchResults", - "resource_type": "dynamodb_table" - } - ], - "event_sources": [ - ], - "env_variables": { - "stats_s3_bucket_name": "${stats_s3_bucket_name}", - "modular_assume_role_arn": "${modular_assume_role_arn}", - "MODULAR_AWS_REGION": "${MODULAR_AWS_REGION}", - "lambdas_alias_name": "${lambdas_alias_name}" - }, - "publish_version": true, - "alias": "${lambdas_alias_name}", - "subnet_ids": [ - "${lambda_private_subnet_id_1}" - ], - "security_group_ids": [ - "${lambda_security_group_1}" - ], - "layers": [ - "custodian_common_dependencies_layer" - ] -} \ No newline at end of file diff --git a/src/lambdas/custodian_notification_handler/local_requirements.txt b/src/lambdas/custodian_notification_handler/local_requirements.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/lambdas/custodian_notification_handler/requirements.txt b/src/lambdas/custodian_notification_handler/requirements.txt deleted file mode 100644 index 67202ac92..000000000 --- a/src/lambdas/custodian_notification_handler/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -# pycryptodome==3.15.0 # not needed, but in case we want to use Licenses to manage notifications... -jinja2==3.1.2 \ No newline at end of file diff --git a/src/lambdas/custodian_report_generation_handler/deployment_resources.json b/src/lambdas/custodian_report_generation_handler/deployment_resources.json index c8745f16c..e086705c2 100644 --- a/src/lambdas/custodian_report_generation_handler/deployment_resources.json +++ b/src/lambdas/custodian_report_generation_handler/deployment_resources.json @@ -21,6 +21,15 @@ ], "Resource": "*", "Effect": "Allow" + }, + { + "Action": [ + "s3:PutObject" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:s3:::${caas_recommendations_bucket}" + ] } ] } @@ -30,6 +39,7 @@ "principal_service": "lambda", "custom_policies": [ "lambda-basic-execution", + "execute-step-function", "report-generation-handler-policy" ], "resource_type": "iam_role" diff --git a/src/lambdas/custodian_report_generation_handler/handler.py b/src/lambdas/custodian_report_generation_handler/handler.py index e7c82656f..c3b971ec8 100644 --- a/src/lambdas/custodian_report_generation_handler/handler.py +++ b/src/lambdas/custodian_report_generation_handler/handler.py @@ -1,64 +1,96 @@ -import json -import os from concurrent.futures import ThreadPoolExecutor, as_completed from datetime import datetime, timedelta -from typing import Union, Dict, List, Tuple -from uuid import uuid4 +import inspect +from functools import cached_property +from http import HTTPStatus +import json +from typing import Mapping, TYPE_CHECKING -from dateutil.relativedelta import relativedelta, SU -from modular_sdk.commons import ModularException from modular_sdk.commons.constants import ApplicationType -from modular_sdk.commons.constants import RABBITMQ_TYPE -from modular_sdk.services.impl.maestro_rabbit_transport_service import \ - MaestroRabbitMQTransport -from http import HTTPStatus -from helpers import raise_error_response, get_logger, build_response, \ - filter_dict -from helpers.constants import HTTP_METHOD_ERROR, CUSTOMER_ATTR, END_DATE, \ - PARAM_REQUEST_PATH, PARAM_HTTP_METHOD, ACTION_PARAM_ERROR, \ - RULE_TYPE, OVERVIEW_TYPE, RESOURCES_TYPE, COMPLIANCE_TYPE, \ - TENANT_DISPLAY_NAME_ATTR, DATA_ATTR, ATTACK_VECTOR_TYPE, START_DATE, \ - TENANT_NAMES_ATTR, TENANT_DISPLAY_NAMES_ATTR, TACTICS_ID_MAPPING, \ - FINOPS_TYPE, HTTPMethod, OUTDATED_TENANTS +from modular_sdk.commons.exception import ModularException +from modular_sdk.models.application import Application +from modular_sdk.models.tenant import Tenant +from modular_sdk.modular import Modular + +from helpers import RequestContext, filter_dict +from helpers.constants import ( + ATTACK_VECTOR_TYPE, + COMPLIANCE_TYPE, + CUSTOMER_ATTR, + CustodianEndpoint, + DATA_ATTR, + END_DATE, + FINOPS_TYPE, + HTTPMethod, + OUTDATED_TENANTS, + OVERVIEW_TYPE, + RESOURCES_TYPE, + ReportDispatchStatus, + RuleDomain, + START_DATE, + TACTICS_ID_MAPPING, + TENANT_DISPLAY_NAME_ATTR, +) from helpers.difference import calculate_dict_diff +from helpers.lambda_response import ( + CustodianException, + ReportNotSendException, + ResponseFactory, + build_response, +) +from helpers.log_helper import get_logger, hide_secret_values from helpers.time_helper import utc_datetime -from models.licenses import License -from models.modular.application import CustodianLicensesApplicationMeta +from lambdas.custodian_report_generation_handler.handlers.diagnostic_handler import ( + DiagnosticHandler, +) +from lambdas.custodian_report_generation_handler.handlers.operational_handler import ( + OperationalHandler, +) +from lambdas.custodian_report_generation_handler.handlers.retry_handler import ( + RetryHandler, +) +from models.batch_results import BatchResults from services import SERVICE_PROVIDER -from services.abstract_api_handler_lambda import AbstractApiHandlerLambda -from services.abstract_lambda import AbstractLambda +from services.abs_lambda import ( + ApiGatewayEventProcessor, + CheckPermissionEventProcessor, + EventProcessorLambdaHandler, + ExpandEnvironmentEventProcessor, + ProcessedEvent, + RestrictCustomerEventProcessor, + RestrictTenantEventProcessor, +) from services.batch_results_service import BatchResultsService from services.clients.s3 import S3Client from services.environment_service import EnvironmentService -from services.license_service import LicenseService +from services.license_service import License, LicenseService +from services.mappings_collector import LazyLoadedMappingsCollector from services.metrics_service import CustomerMetricsService from services.metrics_service import TenantMetricsService -from services.modular_service import ModularService +from services.modular_helpers import get_tenant_regions from services.rabbitmq_service import RabbitMQService -from services.rule_meta_service import LazyLoadedMappingsCollector +from services.report_statistics_service import ReportStatisticsService +from services.reports_bucket import TenantReportsBucketKeysBuilder +from services.ruleset_service import RulesetService from services.setting_service import SettingsService +from services.sharding import ShardsCollection, ShardsCollectionFactory, ShardsS3IO +from validators.registry import permissions_mapping +from validators.swagger_request_models import ( + CLevelGetReportModel, + DepartmentGetReportModel, + ProjectGetReportModel, +) +from validators.utils import validate_kwargs -ACCOUNT_METRICS_PATH = '{customer}/accounts/{date}/{account_id}.json' -TENANT_METRICS_PATH = '{customer}/tenants/{date}/{tenant_dn}.json' +if TYPE_CHECKING: + from scheduler import APJobScheduler +TENANT_METRICS_PATH = '{customer}/tenants/{date}/{tenant_dn}.json' COMMAND_NAME = 'SEND_MAIL' -TYPES_ATTR = 'types' -RECIEVERS_ATTR = 'recievers' +ED_API_ENDPOINT = '/reports/event_driven' EVENT_DRIVEN_TYPE = {'maestro': 'CUSTODIAN_EVENT_DRIVEN_RESOURCES_REPORT', 'custodian': 'EVENT_DRIVEN'} -OVERVIEW_REPORT_TYPE = {'maestro': 'CUSTODIAN_OVERVIEW_REPORT', - 'custodian': 'OVERVIEW'} -RULES_REPORT_TYPE = {'maestro': 'CUSTODIAN_RULES_REPORT', - 'custodian': 'RULE'} -COMPLIANCE_REPORT_TYPE = {'maestro': 'CUSTODIAN_COMPLIANCE_REPORT', - 'custodian': 'COMPLIANCE'} -RESOURCES_REPORT_TYPE = {'maestro': 'CUSTODIAN_RESOURCES_REPORT', - 'custodian': 'RESOURCES'} -ATTACK_REPORT_TYPE = {'maestro': 'CUSTODIAN_ATTACKS_REPORT', - 'custodian': 'ATTACK_VECTOR'} -FINOPS_REPORT_TYPE = {'maestro': 'CUSTODIAN_FINOPS_REPORT', - 'custodian': 'FINOPS'} PROJECT_OVERVIEW_REPORT_TYPE = {'maestro': 'CUSTODIAN_PROJECT_OVERVIEW_REPORT', 'custodian': 'OVERVIEW'} PROJECT_RESOURCES_REPORT_TYPE = { @@ -98,78 +130,188 @@ _LOG = get_logger(__name__) -class ReportGenerator(AbstractApiHandlerLambda): +class ReportGenerator(EventProcessorLambdaHandler): + handlers = ( + OperationalHandler, + DiagnosticHandler, + RetryHandler + ) + processors = ( + ExpandEnvironmentEventProcessor.build(), + ApiGatewayEventProcessor(permissions_mapping), + RestrictCustomerEventProcessor.build(), + CheckPermissionEventProcessor.build(), + RestrictTenantEventProcessor.build() + ) + def __init__(self, s3_service: S3Client, environment_service: EnvironmentService, settings_service: SettingsService, - modular_service: ModularService, + modular_client: Modular, tenant_metrics_service: TenantMetricsService, customer_metrics_service: CustomerMetricsService, license_service: LicenseService, - batch_results_service: BatchResultsService, rabbitmq_service: RabbitMQService, - mappings_collector: LazyLoadedMappingsCollector): + batch_results_service: BatchResultsService, + mappings_collector: LazyLoadedMappingsCollector, + report_statistics_service: ReportStatisticsService, + ap_job_scheduler: 'APJobScheduler', + ruleset_service: RulesetService): self.s3_service = s3_service self.environment_service = environment_service self.settings_service = settings_service - self.modular_service = modular_service + self.modular_client = modular_client self.tenant_metrics_service = tenant_metrics_service self.customer_metrics_service = customer_metrics_service self.license_service = license_service self.batch_results_service = batch_results_service self.rabbitmq_service = rabbitmq_service + self.report_statistics_service = report_statistics_service self.mappings_collector = mappings_collector + self.ap_job_scheduler = ap_job_scheduler + self.ruleset_service = ruleset_service - self.REQUEST_PATH_HANDLER_MAPPING = { - '/reports/operational': { - HTTPMethod.GET: self.generate_operational_reports - }, - '/reports/project': { - HTTPMethod.GET: self.generate_project_reports + self.customer_license_mapping = {} + self.customer_tenant_mapping = {} + self.current_month = datetime.today().replace(day=1).date() + + def lambda_handler(self, event: dict, context: RequestContext): + """ + Overriding lambda handler for this specific lambda because it + requires some additional exceptions handling logic + :param event: + :param context: + :return: + """ + _LOG.info(f'Starting request: {context.aws_request_id}') + # This is the only place where we print the event. Do not print it + # somewhere else + _LOG.debug('Incoming event') + _LOG.debug(json.dumps(hide_secret_values(event))) + + try: + processed, context = self._process_event(event, context) + return self.handle_request(event=processed, context=context) + except ModularException as e: + _LOG.warning('Modular exception occurred', exc_info=True) + return ResponseFactory(int(e.code)).message(e.content).build() + except ReportNotSendException as e: + _LOG.warning('Send report error occurred. ' + 'Re-raising it so that step function could catch', + exc_info=True) + # ReportNotSendException won't be raised in _process_event. + # Can cast, can be sure it'll exist + raise e + except TimeoutError as e: + _LOG.warning('Timeout error occurred. Probably retry') + raise e + except CustodianException as e: + _LOG.warning(f'Application exception occurred: {e}') + return e.build() + except Exception: # noqa + _LOG.exception('Unexpected exception occurred') + return ResponseFactory( + HTTPStatus.INTERNAL_SERVER_ERROR + ).default().build() + + @classmethod + def build(cls) -> 'ReportGenerator': + return cls( + environment_service=SERVICE_PROVIDER.environment_service, + settings_service=SERVICE_PROVIDER.settings_service, + s3_service=SERVICE_PROVIDER.s3, + modular_client=SERVICE_PROVIDER.modular_client, + tenant_metrics_service=SERVICE_PROVIDER.tenant_metrics_service, + customer_metrics_service=SERVICE_PROVIDER.customer_metrics_service, + license_service=SERVICE_PROVIDER.license_service, + batch_results_service=SERVICE_PROVIDER.batch_results_service, + rabbitmq_service=SERVICE_PROVIDER.rabbitmq_service, + mappings_collector=SERVICE_PROVIDER.mappings_collector, + report_statistics_service=SERVICE_PROVIDER.report_statistics_service, + ap_job_scheduler=SERVICE_PROVIDER.ap_job_scheduler, + ruleset_service=SERVICE_PROVIDER.ruleset_service + ) + + @cached_property + def mapping(self) -> Mapping: + data = { + CustodianEndpoint.REPORTS_PROJECT: { + HTTPMethod.POST: self.generate_project_reports }, - '/reports/department': { - HTTPMethod.GET: self.generate_department_reports + CustodianEndpoint.REPORTS_DEPARTMENT: { + HTTPMethod.POST: self.generate_department_reports }, - '/reports/clevel': { - HTTPMethod.GET: self.generate_c_level_reports + CustodianEndpoint.REPORTS_CLEVEL: { + HTTPMethod.POST: self.generate_c_level_reports }, - '/reports/event_driven': { + CustodianEndpoint.REPORTS_EVENT_DRIVEN: { HTTPMethod.GET: self.generate_event_driven_reports - } + }, } - - self.customer_license_mapping = {} - self.customer_tenant_mapping = {} - self.current_month = datetime.today().replace(day=1).date() - - def handle_request(self, event, context): - request_path = event[PARAM_REQUEST_PATH] - method_name = event[PARAM_HTTP_METHOD] - handler_function = self.REQUEST_PATH_HANDLER_MAPPING.get(request_path) - if not handler_function: + for handler in self.handlers: + data.update(handler.build().mapping) + return data + + def handle_request(self, event: ProcessedEvent, context: RequestContext): + func = self.mapping.get(event['resource'], {}).get(event['method']) + if not func: + raise ResponseFactory(HTTPStatus.NOT_FOUND).default().exc() + stat_item = self.report_statistics_service.create_from_processed_event( + event=event + ) + if not self.settings_service.get_send_reports(): + _LOG.debug('Saving report item with PENDING status because ' + 'sending reports is disabled') + stat_item.status = ReportDispatchStatus.PENDING.value + self.report_statistics_service.save(stat_item) return build_response( - code=HTTPStatus.BAD_REQUEST.value, - content=ACTION_PARAM_ERROR.format(endpoint=request_path) - ) - handler_func = handler_function.get(method_name) - response = None - if handler_func: - response = handler_func(event=event) - return response or build_response( - code=HTTPStatus.BAD_REQUEST.value, - content=HTTP_METHOD_ERROR.format( - method=method_name, resource=request_path + code=HTTPStatus.OK, + content='The ability to send reports is disabled. Your ' + 'request has been queued and will be sent after a ' + 'while.' ) - ) - def generate_event_driven_reports(self, event): + match event['method']: + case HTTPMethod.GET: + body = event['query'] + case _: + body = event['body'] + params = dict(event=body, context=context, **event['path_params']) + parameters = inspect.signature(func).parameters + if '_pe' in parameters: + # pe - Processed Event: in case we need to access some raw data + # inside a handler. + _LOG.debug('Expanding handler payload with raw event') + params['_pe'] = event + if '_tap' in parameters: + # _tap - in case we need to know what tenants are allowed + # inside a specific handler + _LOG.debug('Expanding handler payload with tenant access data') + params['_tap'] = event['tenant_access_payload'] + # add event['additional_kwargs'] support if needed + + try: + response = func(**params) + stat_item.status = ReportDispatchStatus.SUCCEEDED.value + self.report_statistics_service.save(stat_item) + return response + except (ReportNotSendException, CustodianException) as e: + self.report_statistics_service.create_failed( + event=event, + exception=e + ) + raise e + except Exception as e: + stat_item.status = ReportDispatchStatus.FAILED.value + stat_item.reason = str(e) + self.report_statistics_service.save(stat_item) + raise e + + def generate_event_driven_reports(self, event, context): licenses = self.license_service.get_event_driven_licenses() - if not licenses: - return build_response('There are no active event-driven licenses. ' - 'Cannot build event-driven report') for l in licenses: _LOG.debug(f'Processing license {l.license_key}') - quota = l.event_driven.quota + quota = l.event_driven.get('quota') if not quota: _LOG.warning( f'There is no quota for ED notifications in license ' @@ -177,15 +319,15 @@ def generate_event_driven_reports(self, event): continue start_date, end_date = self._get_period(quota) if datetime.utcnow().timestamp() < end_date.timestamp() or ( - l.event_driven.last_execution and - start_date.isoformat() <= l.event_driven.last_execution <= end_date.isoformat()): + l.event_driven.get('last_execution') and + start_date.isoformat() <= l.event_driven.get('last_execution') <= end_date.isoformat()): _LOG.debug(f'Skipping ED report for license {l.license_key}: ' f'timestamp now: {datetime.now().isoformat()}; ' f'expected end timestamp: {end_date.isoformat()}') continue _LOG.debug(f'Start timestamp: {start_date.isoformat()}; ' f'end timestamp {end_date.isoformat()}') - for customer, data in l.customers.as_dict().items(): + for customer, data in l.customers.items(): _LOG.debug(f'Processing customer {customer}') tenants = data.get('tenants') if customer not in self.customer_license_mapping: @@ -195,7 +337,7 @@ def generate_event_driven_reports(self, event): set(tenants)) self.customer_license_mapping[customer][ START_DATE] = start_date.replace( - tzinfo=None).isoformat() + tzinfo=None).isoformat() # SHARDS TODO why? self.customer_license_mapping[customer][ END_DATE] = end_date.replace(tzinfo=None).isoformat() self.customer_license_mapping[customer]['license'] = l @@ -203,204 +345,116 @@ def generate_event_driven_reports(self, event): if not self.customer_license_mapping or not any( self.customer_license_mapping.values()): return build_response( - f'There are no any active event-driven licenses') + f'There are no any active event-driven licenses' + ) for customer, info in self.customer_license_mapping.items(): self.customer_tenant_mapping[customer] = {} - results = self.batch_results_service.get_between_period_by_customer( - customer_name=customer, tenants=info['tenants'], - start=info[START_DATE], end=info[END_DATE], limit=100 + results = self.batch_results_service.get_by_customer_name( + customer_name=customer, + start=utc_datetime(info[START_DATE]), + end=utc_datetime(info[END_DATE]), + limit=100, + filter_condition=BatchResults.tenant_name.is_in(*info['tenants']) ) for item in results: self.customer_tenant_mapping[customer].setdefault( - item.tenant_name, []).append(item.id) - + item.tenant_name, []).append(item) if not self.customer_tenant_mapping or not any( self.customer_tenant_mapping.values()): + # probably no need return build_response( f'There are no event-driven jobs for period ' f'{start_date.isoformat()} to {end_date.isoformat()}') for customer, tenants in self.customer_tenant_mapping.items(): - rabbitmq = self.get_customer_rabbitmq(customer) + rabbitmq = self.rabbitmq_service.get_customer_rabbitmq(customer) if not rabbitmq: continue - bucket_name = self.environment_service.default_reports_bucket_name() - with ThreadPoolExecutor() as executor: - for tenant, items in tenants.items(): - _LOG.debug(f'Processing {len(items)} ED scan(s) of ' - f'{tenant} tenant') - futures = [] - tenant_item = next(self.modular_service.i_get_tenant( - [tenant]), None) - futures.append(executor.submit(self._process, bucket_name, - items)) + for tenant_name, results in tenants.items(): + _LOG.debug(f'Processing {len(results)} ED scan(s) of ' + f'{tenant_name} tenant') + tenant_item = self.modular_client.tenant_service().get(tenant_name) + + collections = [] + with ThreadPoolExecutor() as ex: + futures = [ ex.submit(self._fetch_difference, tenant_item, bucket_name, result) for result in results ] for future in as_completed(futures): - new_resources = future.result() - if not new_resources: - _LOG.warning( - f'No new resources for tenant {tenant}') - continue - mitre_data = self._retrieve_mitre_data(new_resources) - - data = { - 'customer': customer, - 'tenant_name': tenant, - 'cloud': tenant_item.cloud, - 'id': tenant_item.project, - 'activated_regions': list( - self.modular_service.get_tenant_regions( - tenant_item)), - 'data': { - 'policy_data': new_resources, - 'mitre_data': mitre_data - }, - 'from': self.customer_license_mapping[customer][ - START_DATE], - 'to': self.customer_license_mapping[customer][ - END_DATE] - } - _LOG.debug(f'Data: {data}') - json_model = self._build_json_model( - EVENT_DRIVEN_TYPE['maestro'], - {**data, - 'report_type': EVENT_DRIVEN_TYPE['custodian']}) - self._send_notification_to_m3(json_model, rabbitmq) - _LOG.debug(f'Notification for {tenant} tenant was ' - f'successfully send') - - customer_license = self.customer_license_mapping[customer] - _LOG.debug( - f'Updating last report execution date for license ' - f'{customer_license["license"].license_key}') - self.license_service.update_last_ed_report_execution( - customer_license['license'], - last_execution_date=customer_license[END_DATE]) - - return build_response( - content='Reports sending was successfully triggered' - ) - - def generate_operational_reports(self, event): - date = self.settings_service.get_report_date_marker().get( - 'current_week_date') - if not date: - _LOG.warning('Missing \'current_week_date\' section in ' - '\'REPORT_DATE_MARKER\' setting.') - date = (datetime.today() + relativedelta( - weekday=SU(0))).date().isoformat() - - metrics_bucket = self.environment_service.get_metrics_bucket_name() - tenant_names = list(filter( - None, event.get(TENANT_NAMES_ATTR, '').split(', '))) - report_types = list(filter( - None, event.get(TYPES_ATTR, '').split(', '))) - receivers = list(filter( - None, event.get(RECIEVERS_ATTR, '').split(', '))) - - if not tenant_names: - return raise_error_response( - code=HTTPStatus.INTERNAL_SERVER_ERROR.value, - content='"tenant_names" parameter cannot be empty' - ) - - json_model = [] - errors = [] - _LOG.debug(f'Report type: {report_types if report_types else "ALL"}') - for tenant_name in tenant_names: - _LOG.debug(f'Retrieving tenant with name {tenant_name}') - tenant = self.modular_service.get_tenant(tenant_name) - if not tenant: - _msg = f'Cannot find tenant with name \'{tenant_name}\'' - errors.append(_msg) - continue - - tenant_metrics = self.s3_service.get_file_content( - bucket_name=metrics_bucket, - full_file_name=ACCOUNT_METRICS_PATH.format( - customer=tenant.customer_name, - date=date, account_id=tenant.project)) - if not tenant_metrics: - _msg = f'There is no data for tenant {tenant_name} for the ' \ - f'last week' - errors.append(_msg) - continue - - _LOG.debug('Retrieving tenant data') - tenant_metrics = json.loads(tenant_metrics) - tenant_metrics[OUTDATED_TENANTS] = self._process_outdated_tenants( - tenant_metrics.pop(OUTDATED_TENANTS, {})) - - overview_data = tenant_metrics.pop(OVERVIEW_TYPE, None) - rule_data = tenant_metrics.pop(RULE_TYPE, None) - resources_data = tenant_metrics.pop(RESOURCES_TYPE, None) - compliance_data = tenant_metrics.pop(COMPLIANCE_TYPE, None) - attack_data = tenant_metrics.pop(ATTACK_VECTOR_TYPE, None) - finops_data = tenant_metrics.pop(FINOPS_TYPE, None) - for _type, data in [(ATTACK_REPORT_TYPE, attack_data), - (COMPLIANCE_REPORT_TYPE, compliance_data), - (OVERVIEW_REPORT_TYPE, overview_data), - (RESOURCES_REPORT_TYPE, resources_data), - (RULES_REPORT_TYPE, rule_data), - (FINOPS_REPORT_TYPE, finops_data)]: - if report_types and _type['custodian'] not in report_types: + collections.append(future.result()) + latest = ShardsCollectionFactory.from_tenant(tenant_item) + latest.io = ShardsS3IO( + bucket=bucket_name, + key=TenantReportsBucketKeysBuilder(tenant_item).latest_key(), + client=self.s3_service + ) + latest.fetch_meta() + new_resources = self.merge_collections( + collections, latest.meta + ) + if not new_resources: + _LOG.warning( + f'No new resources for tenant {tenant_name}') continue - json_model.append(self._build_json_model( - _type['maestro'], {RECIEVERS_ATTR: receivers, - **tenant_metrics, DATA_ATTR: data, - 'report_type': _type['custodian']})) - if errors: - _LOG.error(f"Found errors: {os.linesep.join(errors)}") - if not json_model: - return build_response( - content=os.linesep.join(errors) - ) + mitre_data = self._retrieve_mitre_data(new_resources) + + data = { + 'customer': customer, + 'tenant_name': tenant_name, + 'cloud': tenant_item.cloud, + 'id': tenant_item.project, + 'activated_regions': list(get_tenant_regions(tenant_item)), + 'data': { + 'policy_data': new_resources, + 'mitre_data': mitre_data + }, + 'from': self.customer_license_mapping[customer][ + START_DATE], + 'to': self.customer_license_mapping[customer][ + END_DATE] + } + _LOG.debug(f'Data: {data}') + json_model = self.rabbitmq_service.build_m3_json_model( + EVENT_DRIVEN_TYPE['maestro'], + {**data, + 'report_type': EVENT_DRIVEN_TYPE['custodian']}) + self.rabbitmq_service.send_notification_to_m3( + COMMAND_NAME, json_model, rabbitmq) + _LOG.debug(f'Notification for {tenant_name} tenant was ' + f'successfully send') + + customer_license = self.customer_license_mapping[customer] + _LOG.debug( + f'Updating last report execution date for license ' + f'{customer_license["license"].license_key}') + customer_license['license'].event_driven['last_execution'] = customer_license[END_DATE] - rabbitmq = self.get_customer_rabbitmq(event[CUSTOMER_ATTR]) - if not rabbitmq: - return self.rabbitmq_service.no_rabbit_configuration() - self._send_notification_to_m3(json_model, rabbitmq) return build_response( - content=f'The request to send report' - f'{"s" if not report_types else ""} for ' - f'{tenant_names} tenant' - f'{" was" if report_types else " were"} ' - f'successfully created' + content='Reports sending was successfully triggered' ) - def generate_project_reports(self, event): + @validate_kwargs + def generate_project_reports(self, event: ProjectGetReportModel, + context: RequestContext): if not (date := self.settings_service.get_report_date_marker().get( 'current_week_date')): - return raise_error_response( - code=HTTPStatus.INTERNAL_SERVER_ERROR.value, - content='Cannot send reports: ' - 'missing \'current_week_date\' section in ' - '\'REPORT_DATE_MARKER\' setting.' - ) + raise ResponseFactory(HTTPStatus.INTERNAL_SERVER_ERROR).message( + 'Cannot send reports: missing \'current_week_date\' section ' + 'in \'REPORT_DATE_MARKER\' setting.' + ).exc() metrics_bucket = self.environment_service.get_metrics_bucket_name() - tenant_display_names = list(filter( - None, event.get(TENANT_DISPLAY_NAMES_ATTR, '').split(', '))) - report_types = list(filter( - None, event.get(TYPES_ATTR, '').split(', '))) - receivers = list(filter( - None, event.get(RECIEVERS_ATTR, '').split(', '))) - - if not tenant_display_names: - return raise_error_response( - code=HTTPStatus.INTERNAL_SERVER_ERROR.value, - content='"tenant_display_names" parameter cannot be empty' - ) + tenant_display_names = event.tenant_display_names + report_types = event.types + receivers = event.receivers json_model = [] errors = [] for display_name in tenant_display_names: _LOG.debug( f'Retrieving tenants with display name \'{display_name}\'') - tenants = list( - self.modular_service.i_get_tenant_by_display_name_to_lower( - display_name.lower())) + ts = self.modular_client.tenant_service() + tenants = list(ts.i_get_by_dntl(display_name.lower())) if not tenants: _msg = \ f'Cannot find tenants with display name \'{display_name}\'' @@ -409,11 +463,13 @@ def generate_project_reports(self, event): continue tenant = list(tenants)[0] - tenant_group_metrics = self.s3_service.get_file_content( - bucket_name=metrics_bucket, - full_file_name=TENANT_METRICS_PATH.format( + tenant_group_metrics = self.s3_service.gz_get_object( + bucket=metrics_bucket, + key=TENANT_METRICS_PATH.format( customer=tenant.customer_name, - date=date, tenant_dn=display_name)) + date=date, tenant_dn=display_name + ) + ) if not tenant_group_metrics: _msg = f'There is no data for \'{display_name}\' tenant ' \ f'group for the last week' @@ -421,8 +477,12 @@ def generate_project_reports(self, event): errors.append(_msg) continue - tenant_group_metrics = json.loads(tenant_group_metrics) - tenant_group_metrics[OUTDATED_TENANTS] = self._process_outdated_tenants( + tenant_group_metrics = json.load(tenant_group_metrics) + _LOG.debug('Tenant group metrics') + _LOG.debug(json.dumps(tenant_group_metrics)) + + tenant_group_metrics[ + OUTDATED_TENANTS] = self._process_outdated_tenants( tenant_group_metrics.pop(OUTDATED_TENANTS, {})) overview_data = tenant_group_metrics.pop(OVERVIEW_TYPE, None) @@ -431,33 +491,38 @@ def generate_project_reports(self, event): attack_data = tenant_group_metrics.pop(ATTACK_VECTOR_TYPE, None) finops_data = tenant_group_metrics.pop(FINOPS_TYPE, None) for _type, data in [ - (PROJECT_ATTACK_REPORT_TYPE, attack_data), - (PROJECT_COMPLIANCE_REPORT_TYPE, compliance_data), - (PROJECT_OVERVIEW_REPORT_TYPE, overview_data), - (PROJECT_RESOURCES_REPORT_TYPE, resources_data), - (PROJECT_FINOPS_REPORT_TYPE, finops_data)]: + (PROJECT_ATTACK_REPORT_TYPE, attack_data), + (PROJECT_COMPLIANCE_REPORT_TYPE, compliance_data), + (PROJECT_OVERVIEW_REPORT_TYPE, overview_data), + (PROJECT_RESOURCES_REPORT_TYPE, resources_data), + (PROJECT_FINOPS_REPORT_TYPE, finops_data)]: if report_types and _type['custodian'] not in report_types: continue - json_model.append(self._build_json_model( - _type['maestro'], {RECIEVERS_ATTR: receivers, + json_model.append(self.rabbitmq_service.build_m3_json_model( + _type['maestro'], {'receivers': list(receivers), **tenant_group_metrics, DATA_ATTR: data, 'report_type': _type['custodian']})) if not json_model: return build_response( - code=HTTPStatus.BAD_REQUEST.value, + code=HTTPStatus.BAD_REQUEST, content=';\n'.join(errors) ) - rabbitmq = self.get_customer_rabbitmq(event[CUSTOMER_ATTR]) + rabbitmq = self.rabbitmq_service.get_customer_rabbitmq( + event.customer_id + ) if not rabbitmq: return self.rabbitmq_service.no_rabbit_configuration() - self._send_notification_to_m3(json_model, rabbitmq) + self.rabbitmq_service.send_notification_to_m3( + COMMAND_NAME, json_model, rabbitmq) return build_response( content=f'The request to send reports for {tenant_display_names} ' f'tenant group were successfully created' ) - def generate_department_reports(self, event): + @validate_kwargs + def generate_department_reports(self, event: DepartmentGetReportModel, + context: RequestContext): top_compliance_by_tenant = [] top_compliance_by_cloud = {c: [] for c in CLOUDS} top_resource_by_tenant = [] @@ -498,17 +563,16 @@ def generate_department_reports(self, event): } previous_month = (self.current_month - timedelta(days=1)).replace( day=1) - customer = event[CUSTOMER_ATTR] - report_types = list(filter( - None, event.get(TYPES_ATTR, '').split(', '))) + customer = event.customer_id + report_types = event.types top_tenants = self.tenant_metrics_service.list_by_date_and_customer( date=self.current_month.isoformat(), customer=customer) if len(top_tenants) == 0: return build_response(f'There are no metrics for customer ' f'{customer} for the period from ' - f'{self.current_month.isoformat()} to ' - f'{previous_month}') + f'{previous_month} to ' + f'{self.current_month.isoformat()}') for tenant in top_tenants: tenant = tenant.attribute_values _LOG.debug(f'Retrieving previous item for tenant ' @@ -561,26 +625,25 @@ def generate_department_reports(self, event): 'sort_by': tenant.pop('defining_attribute'), DATA_ATTR: attribute_diff }) - rabbitmq = self.get_customer_rabbitmq(event[CUSTOMER_ATTR]) + rabbitmq = self.rabbitmq_service.get_customer_rabbitmq(event.customer_id) if not rabbitmq: return self.rabbitmq_service.no_rabbit_configuration() for _type, values in report_type_mapping.items(): - if report_types and report_type_mapping[_type]['report_type'][ - 'custodian'] not in report_types: + if report_types and report_type_mapping[_type]['report_type']['custodian'] not in report_types: continue container = values['container'] if (isinstance(container, list) and not container) or ( - isinstance(container, dict) and not any( - container.values())): + isinstance(container, dict) and not any(container.values())): _LOG.warning(f'No data for report type {_type}') continue - json_model = self._build_json_model( + json_model = self.rabbitmq_service.build_m3_json_model( report_type_mapping[_type]['report_type']['maestro'], {CUSTOMER_ATTR: customer, 'from': previous_month.isoformat(), 'to': self.current_month.isoformat(), OUTDATED_TENANTS: values[OUTDATED_TENANTS], 'report_type': _type, DATA_ATTR: values['container']}) - self._send_notification_to_m3(json_model, rabbitmq) + self.rabbitmq_service.send_notification_to_m3( + COMMAND_NAME, json_model, rabbitmq) _LOG.debug(f'Notifications for {customer} customer have been ' f'sent successfully') return build_response( @@ -588,7 +651,9 @@ def generate_department_reports(self, event): f'triggered successfully' ) - def generate_c_level_reports(self, event): + @validate_kwargs + def generate_c_level_reports(self, event: CLevelGetReportModel, + context: RequestContext): report_type_mapping = { OVERVIEW_TYPE.upper(): CUSTOMER_OVERVIEW_REPORT_TYPE, COMPLIANCE_TYPE.upper(): CUSTOMER_COMPLIANCE_REPORT_TYPE, @@ -597,9 +662,8 @@ def generate_c_level_reports(self, event): previous_month = (self.current_month - timedelta(days=1)).replace( day=1) - customer = event[CUSTOMER_ATTR] - report_types = list(filter( - None, event.get(TYPES_ATTR, '').split(', '))) + customer = event.customer_id + report_types = event.types customer_metrics = self.customer_metrics_service.list_by_date_and_customer( date=self.current_month.isoformat(), customer=customer) @@ -608,14 +672,14 @@ def generate_c_level_reports(self, event): if len(customer_metrics) == 0: return build_response(f'There are no metrics for customer ' f'{customer} for the period from ' - f'{self.current_month.isoformat()} to ' - f'{previous_month}') - rabbitmq = self.get_customer_rabbitmq(customer) + f'{previous_month} to ' + f'{self.current_month.isoformat()}') + rabbitmq = self.rabbitmq_service.get_customer_rabbitmq(customer) if not rabbitmq: return self.rabbitmq_service.no_rabbit_configuration() for item in customer_metrics: item = item.attribute_values - if report_types and item.get(TYPES_ATTR) not in report_types: + if report_types and item.get('type') not in report_types: continue if item.get('type') != ATTACK_VECTOR_TYPE.upper(): @@ -645,10 +709,12 @@ def generate_c_level_reports(self, event): attribute_diff = calculate_dict_diff( cloud_attrs, prev_cloud_attrs, exclude=['total_scanned_tenants']) - applications = list(self.modular_service.get_applications( + applications = list(self.modular_client.application_service().list( customer=customer, - _type=ApplicationType.CUSTODIAN_LICENSES.value + _type=ApplicationType.CUSTODIAN_LICENSES.value, + deleted=False )) + self._get_application_info(applications, attribute_diff) else: # attack report should not contain license info cloud_attrs = { @@ -659,9 +725,6 @@ def generate_c_level_reports(self, event): 'google': item.get('google').attribute_values.get( 'data', [])} attribute_diff = cloud_attrs - applications = [] - - self._get_application_info(applications, attribute_diff) model = { DATA_ATTR: attribute_diff, @@ -672,9 +735,12 @@ def generate_c_level_reports(self, event): } model.update({'report_type': item.get('type')}) - json_model = self._build_json_model( + _LOG.debug('Sending clevel model to m3') + _LOG.debug(json.dumps(model)) + json_model = self.rabbitmq_service.build_m3_json_model( report_type_mapping.get(item.get('type')), model) - self._send_notification_to_m3(json_model, rabbitmq) + self.rabbitmq_service.send_notification_to_m3( + COMMAND_NAME, json_model, rabbitmq) _LOG.debug(f'Reports sending for {customer} customer have been ' f'triggered successfully') @@ -684,87 +750,61 @@ def generate_c_level_reports(self, event): ) @staticmethod - def _get_license_info(_license: License) -> Dict: - balance = _license.allowance.attribute_values['job_balance'] - time_range = _license.allowance.attribute_values['time_range'] + def _get_license_info(_license: License) -> dict: + balance = _license.allowance.get('job_balance') + time_range = _license.allowance.get('time_range') scan_frequency = f'{balance} scan{"" if balance == 1 else "s"} per ' \ f'{time_range}' + expiration = None + if exp := _license.expiration: + # the returned object is displayed directly, so we make + # human-formatting here + expiration = exp.strftime('%b %d, %Y %H:%M:%S %Z') return { 'activated': True, 'license_properties': { 'Scans frequency': scan_frequency, - 'Expiration': _license.expiration, + 'Expiration': expiration, 'Event-Driven mode': 'On' if _license.event_driven else 'Off' } } - @staticmethod - def _send_notification_to_m3(json_model: Union[list, dict], - rabbitmq: MaestroRabbitMQTransport) -> None: - try: - code, status, response = rabbitmq.send_sync( - command_name=COMMAND_NAME, - parameters=json_model, - is_flat_request=False, async_request=False, - secure_parameters=None, compressed=True) - _LOG.debug(f'Response code: {code}, response message: {response}') - except ModularException as e: - _LOG.error(f'Modular error: {e}') - return build_response( - code=HTTPStatus.SERVICE_UNAVAILABLE.value, - content='An error occurred while sending the report. ' - 'Please contact the support team.' - ) - except Exception as e: # can occur in case access data is invalid - _LOG.error( - f'An error occurred trying to send a message to rabbit: {e}') - return build_response( - code=HTTPStatus.SERVICE_UNAVAILABLE.value, - content='An error occurred while sending the report. ' - 'Please contact the support team.' - ) + def _fetch_difference(self, tenant: Tenant, bucket_name: str, + result: BatchResults) -> ShardsCollection: + collection = ShardsCollectionFactory.from_tenant(tenant) + collection.io = ShardsS3IO( + bucket=bucket_name, + key=TenantReportsBucketKeysBuilder(tenant).ed_job_difference(result), + client=self.s3_service, # it's a client + ) + collection.fetch_all() + return collection - def _process(self, bucket_name, items): - """Merges differences""" + def merge_collections(self, collections: list[ShardsCollection], + meta: dict) -> list[dict]: content = {} - differences = self.s3_service.get_json_batch( - bucket_name=bucket_name, - keys=[f'{i}/difference.json.gz' for i in items] - ) - for file in differences: - _LOG.debug(f'Processing file {file[0]}') - for rule, resource in file[1].items(): - report_fields = self.mappings_collector.human_data.get( - rule, {}).get('report_fields') or set() - - resource['severity'] = self.mappings_collector.severity.get( - rule, 'Unknown') - resource.pop('report_fields', None) - resource.pop('standard_points', None) - resource.pop('resourceType', None) - resource['regions_data'] = resource.pop('resources', {}) - resource[ - 'resource_type'] = self.mappings_collector.service.get( - rule) - - filtered_resources = {} - for region, data in resource['regions_data'].items(): - filtered_resources.setdefault(region, []).extend( - filter_dict(d, report_fields) for d in data) - if filtered_resources: - resource['regions_data'] = filtered_resources - - if rule not in content: - content[rule] = resource - else: - for region, data in resource['regions_data'].items(): - content[rule]['regions_data'].setdefault( - region, []).extend(data) + human_data = self.mappings_collector.human_data + severity = self.mappings_collector.severity + service = self.mappings_collector.service + for collection in collections: + for _, shard in collection: + for part in shard: + rf = human_data.get(part.policy, {}).get( + 'report_fields') or set() + data = content.setdefault(part.policy, { + 'severity': severity.get(part.policy) or 'Unknown', + 'description': meta.get(part.policy).get('description'), + 'resource_type': service.get(part.policy), + 'regions_data': {} + }) + data['regions_data'].setdefault(part.location, []).extend( + filter_dict(r, rf) for r in part.resources + ) return [{'policy': k, **v} for k, v in content.items()] @staticmethod def _get_period(frequency: int, last_execution: str = None) -> \ - Tuple[datetime, datetime]: + tuple[datetime, datetime]: _LOG.debug('No last execution date') now = utc_datetime() minutes = frequency % 60 @@ -778,7 +818,7 @@ def _get_period(frequency: int, last_execution: str = None) -> \ end = last_execution + timedelta(minutes=frequency) return last_execution, end - def _retrieve_mitre_data(self, resources: list) -> List[dict]: + def _retrieve_mitre_data(self, resources: list) -> list[dict]: mitre = {} result = [] for resource in resources: @@ -798,42 +838,51 @@ def _retrieve_mitre_data(self, resources: list) -> List[dict]: 'tactic': tactic, 'severity_data': data}) return result - def _get_application_info(self, applications: list, attribute_diff: dict): + def _get_application_info(self, applications: list[Application], + attribute_diff: dict): + """ + Previously we could have multiple licenses inside one application + (split by cloud). That division was just verbal because nothing was + preventing us from creating a license that has rulesets for multiple + clouds. Although, we did create only cloud-specific licenses. + Currently, one application is one license, and we have no + straightforward way of knowing the cloud of that license. But I + don't want to change the format of report or whatever this data + is going to. So I just use this workaround. Basically the same, + even better that it was + :param applications: + :param attribute_diff: + :return: + """ + rulesets = {} # cache for ap in applications: - # todo pass several licenses to BE - meta = CustodianLicensesApplicationMeta( - **ap.meta.as_dict() - ) - for cloud in CLOUDS: - if not (l := meta.license_key(cloud)): - continue - if attribute_diff[cloud].get('activated') is True: - continue - license_item = self.license_service.get_license(l) - if not license_item: - _LOG.warning(f'Invalid license key in Application ' - f'meta for cloud {cloud}') - continue - attribute_diff[cloud].update(self._get_license_info( - license_item)) + lic = License(ap) + # here is the faulty thing, but we are not supposed to create + # licenses that contain rulesets of different clouds + ruleset_id = next(iter(lic.ruleset_ids), None) + if not ruleset_id: + continue + if ruleset_id not in rulesets: + rulesets[ruleset_id] = self.ruleset_service.by_lm_id(ruleset_id) + ruleset = rulesets[ruleset_id] + if not ruleset: + continue + if ruleset.cloud == RuleDomain.KUBERNETES: + # skip license because c-level report currently does not + # support k8s + continue + cloud = ruleset.cloud.lower() + if cloud == 'gcp': + cloud = 'google' # need more kludges... + if attribute_diff.setdefault(cloud, {}).get('activated') is True: + continue + attribute_diff[cloud].update(self._get_license_info(lic)) @staticmethod - def _get_attr_values(item, default={}): + def _get_attr_values(item, default: dict = None): + default = default or {} return item.attribute_values if item else default - @staticmethod - def _build_json_model(notification_type, data): - return { - 'viewType': 'm3', - 'model': { - "uuid": str(uuid4()), - "notificationType": notification_type, - "notificationAsJson": json.dumps(data, - separators=(",", ":")), - "notificationProcessorTypes": ["MAIL"] - } - } - def process_department_item_by_cloud(self, item_type, attribute_diff, report_type_mapping, tenant): @@ -877,45 +926,43 @@ def add_other_data(report_type_mapping, tenant, item_type, cloud, data): DATA_ATTR: data }) - def get_customer_rabbitmq(self, customer): - application = self.rabbitmq_service.get_rabbitmq_application( - customer) - if not application: - _LOG.warning(f'No application with type {RABBITMQ_TYPE} found ' - f'for customer {customer}') - return - rabbitmq = self.rabbitmq_service.build_maestro_mq_transport( - application) - if not rabbitmq: - _LOG.warning(f'Could not build rabbit client from application ' - f'for customer {customer}') - return - return rabbitmq - @staticmethod def _process_outdated_tenants(outdated_tenants: dict): tenants = [] for cloud, data in outdated_tenants.items(): - tenants.extend(list(data.keys())) + tenants.extend(data.keys()) return tenants -HANDLER = ReportGenerator( - environment_service=SERVICE_PROVIDER.environment_service(), - settings_service=SERVICE_PROVIDER.settings_service(), - s3_service=SERVICE_PROVIDER.s3(), - modular_service=SERVICE_PROVIDER.modular_service(), - tenant_metrics_service=SERVICE_PROVIDER.tenant_metrics_service(), - customer_metrics_service=SERVICE_PROVIDER.customer_metrics_service(), - license_service=SERVICE_PROVIDER.license_service(), - batch_results_service=SERVICE_PROVIDER.batch_results_service(), - rabbitmq_service=SERVICE_PROVIDER.rabbitmq_service(), - mappings_collector=SERVICE_PROVIDER.mappings_collector() -) +class ReportGeneratorNoProcessors(ReportGenerator): + """ + Only for retry and event-driven + """ + processors = ( + ExpandEnvironmentEventProcessor.build(), # does not change event so can be used + ) + + def handle_request(self, event: dict, context: RequestContext): + resource = event.get('requestContext', {}).get('resourcePath') + if resource == CustodianEndpoint.REPORTS_EVENT_DRIVEN: + _LOG.info('Executing event-driven handler') + return self.generate_event_driven_reports(event, context) + if resource == CustodianEndpoint.REPORTS_RETRY: + _LOG.info('Executing retry handler') + return self.mapping[CustodianEndpoint.REPORTS_RETRY][HTTPMethod.POST](event, context) + raise ResponseFactory(HTTPStatus.NOT_FOUND).default().exc() + + +HANDLER = ReportGenerator.build() +HANDLER_NO_PROCESSORS = ReportGeneratorNoProcessors.build() def lambda_handler(event, context): - if event.get(PARAM_REQUEST_PATH) == '/reports/event_driven': - return AbstractLambda.lambda_handler(HANDLER, event, context) + resource = event.get('requestContext', {}).get('resourcePath') + if resource in (CustodianEndpoint.REPORTS_RETRY.value, + CustodianEndpoint.REPORTS_EVENT_DRIVEN.value): + _LOG.debug('Retry or event driven request came. ' + 'Using handler with no processors') + return HANDLER_NO_PROCESSORS.lambda_handler(event, context) return HANDLER.lambda_handler(event=event, context=context) diff --git a/src/connections/batch_extension/__init__.py b/src/lambdas/custodian_report_generation_handler/handlers/__init__.py similarity index 100% rename from src/connections/batch_extension/__init__.py rename to src/lambdas/custodian_report_generation_handler/handlers/__init__.py diff --git a/src/lambdas/custodian_report_generation_handler/handlers/diagnostic_handler.py b/src/lambdas/custodian_report_generation_handler/handlers/diagnostic_handler.py new file mode 100644 index 000000000..fd0a1d6a6 --- /dev/null +++ b/src/lambdas/custodian_report_generation_handler/handlers/diagnostic_handler.py @@ -0,0 +1,103 @@ +from datetime import datetime, timedelta +from functools import cached_property +from http import HTTPStatus + +from dateutil.relativedelta import relativedelta + +from handlers import AbstractHandler, Mapping +from helpers import RequestContext +from helpers.constants import CUSTOMER_ATTR, CustodianEndpoint, HTTPMethod +from helpers.lambda_response import build_response +from helpers.time_helper import utc_datetime +from services import SERVICE_PROVIDER +from services.clients.s3 import S3Client +from services.environment_service import EnvironmentService +from services.rabbitmq_service import RabbitMQService +from services.reports_bucket import StatisticsBucketKeysBuilder +from services.setting_service import SettingsService +from validators.swagger_request_models import BaseModel +from validators.utils import validate_kwargs + +COMMAND_NAME = 'SEND_MAIL' +DIAGNOSTIC_REPORT_TYPE = {'maestro': 'CUSTODIAN_DIAGNOSTIC_REPORT', + 'custodian': 'DIAGNOSTIC'} + + +class DiagnosticHandler(AbstractHandler): + def __init__(self, environment_service: EnvironmentService, + s3_service: S3Client, settings_service: SettingsService, + rabbitmq_service: RabbitMQService): + self.environment_service = environment_service + self.s3_service = s3_service + self.settings_service = settings_service + self.rabbitmq_service = rabbitmq_service + + self.stat_bucket_name = \ + self.environment_service.get_statistics_bucket_name() + self.today_date = datetime.utcnow().today() + self.today = self.today_date.isoformat() + self.yesterday = ( + utc_datetime() - timedelta(days=1)).date().isoformat() + self.TO_UPDATE_MARKER = False + + self.month = (self.today_date - relativedelta(months=1)).month + self.year = (self.today_date - relativedelta(months=1)).year + self.start_date = self.today_date.replace(day=1, + month=self.month).isoformat() + self.end_date = self.today_date.replace(day=1).isoformat() + self.last_month_date = datetime.combine( + (self.today_date - relativedelta(months=1)).replace(day=1), + datetime.min.time()) + + self._date_marker = self.settings_service.get_report_date_marker() + self.current_week_date = self._date_marker.get('current_week_date') + + @classmethod + def build(cls) -> 'DiagnosticHandler': + return cls( + environment_service=SERVICE_PROVIDER.environment_service, + settings_service=SERVICE_PROVIDER.settings_service, + s3_service=SERVICE_PROVIDER.s3, + rabbitmq_service=SERVICE_PROVIDER.rabbitmq_service + ) + + @cached_property + def mapping(self) -> Mapping: + return { + CustodianEndpoint.REPORTS_DIAGNOSTIC: { + HTTPMethod.GET: self.get, + } + } + + @validate_kwargs + def get(self, event: BaseModel, context: RequestContext): + customer = event.customer_id + json_content = self.s3_service.gz_get_json( + self.stat_bucket_name, + key=StatisticsBucketKeysBuilder.report_statistics( + self.last_month_date, customer=customer) + ) + if not json_content: + return build_response( + code=HTTPStatus.NOT_FOUND, + content=f'No diagnostic report for {customer} customer.' + ) + rabbitmq = self.rabbitmq_service.get_customer_rabbitmq( + event.customer_id) + if not rabbitmq: + return self.rabbitmq_service.no_rabbit_configuration() + json_model = self.rabbitmq_service.build_m3_json_model( + DIAGNOSTIC_REPORT_TYPE['maestro'], json_content + ) + code = self.rabbitmq_service.send_notification_to_m3( + COMMAND_NAME, json_model, rabbitmq) + if code != HTTPStatus.OK: + return build_response( + code=code, + content=f'The request to send report for {customer} customer ' + f'was not triggered.' + ) + return build_response( + content=f'The request to send report for {customer} customer ' + f'was successfully triggered.' + ) diff --git a/src/lambdas/custodian_report_generation_handler/handlers/operational_handler.py b/src/lambdas/custodian_report_generation_handler/handlers/operational_handler.py new file mode 100644 index 000000000..55b294599 --- /dev/null +++ b/src/lambdas/custodian_report_generation_handler/handlers/operational_handler.py @@ -0,0 +1,406 @@ +from datetime import datetime +from functools import cached_property +from http import HTTPStatus +import json +import os +import sys +import tempfile +import uuid + +from dateutil.relativedelta import SU, relativedelta +from modular_sdk.models.tenant import Tenant +from modular_sdk.services.tenant_service import TenantService + +from handlers import AbstractHandler, Mapping +from helpers import RequestContext +from helpers.constants import ( + ATTACK_VECTOR_TYPE, + COMPLIANCE_TYPE, + CUSTOMER_ATTR, + CustodianEndpoint, + DATA_ATTR, + EXTERNAL_DATA_ATTR, + EXTERNAL_DATA_BUCKET_ATTR, + EXTERNAL_DATA_KEY_ATTR, + FINOPS_TYPE, + HTTPMethod, + KUBERNETES_TYPE, + LAST_SCAN_DATE, + OUTDATED_TENANTS, + OVERVIEW_TYPE, + REGION_ATTR, + RESOURCES_TYPE, + RULE_TYPE, +) +from helpers.lambda_response import ResponseFactory, build_response +from helpers.log_helper import get_logger +from services import SERVICE_PROVIDER +from services.clients.s3 import ModularAssumeRoleS3Service, S3Client +from services.environment_service import EnvironmentService +from services.license_service import LicenseService +from services.rabbitmq_service import RabbitMQService +from services.setting_service import SettingsService +from validators.swagger_request_models import OperationalGetReportModel +from validators.utils import validate_kwargs +from services.rbac_service import TenantsAccessPayload + +_LOG = get_logger(__name__) + +OVERVIEW_REPORT_TYPE = {'maestro': 'CUSTODIAN_OVERVIEW_REPORT', + 'custodian': 'OVERVIEW'} +RULES_REPORT_TYPE = {'maestro': 'CUSTODIAN_RULES_REPORT', + 'custodian': 'RULE'} +COMPLIANCE_REPORT_TYPE = {'maestro': 'CUSTODIAN_COMPLIANCE_REPORT', + 'custodian': 'COMPLIANCE'} +RESOURCES_REPORT_TYPE = {'maestro': 'CUSTODIAN_RESOURCES_REPORT', + 'custodian': 'RESOURCES'} +ATTACK_REPORT_TYPE = {'maestro': 'CUSTODIAN_ATTACKS_REPORT', + 'custodian': 'ATTACK_VECTOR'} +FINOPS_REPORT_TYPE = {'maestro': 'CUSTODIAN_FINOPS_REPORT', + 'custodian': 'FINOPS'} +KUBERNETES_REPORT_TYPE = {'maestro': 'CUSTODIAN_K8S_CLUSTER_REPORT', + 'custodian': 'KUBERNETES'} +ACCOUNT_METRICS_PATH = '{customer}/accounts/{date}/{account_id}.json' +COMMAND_NAME = 'SEND_MAIL' + +RULE_PREFIX = 'rule' +SERVICE_PREFIX = 'service' +REGION_PREFIX = 'region' +POLICY_PREFIX = 'policy' +TACTIC_PREFIX = 'tactic' +TECHNIQUE_PREFIX = 'technique' + + +class OperationalHandler(AbstractHandler): + def __init__(self, tenant_service: TenantService, + environment_service: EnvironmentService, + s3_service: S3Client, + settings_service: SettingsService, + rabbitmq_service: RabbitMQService, + license_service: LicenseService, + assume_role_s3: ModularAssumeRoleS3Service): + self.tenant_service = tenant_service + self.environment_service = environment_service + self.s3_service = s3_service + self.settings_service = settings_service + self.rabbitmq_service = rabbitmq_service + self.license_service = license_service + self.assume_role_s3 = assume_role_s3 + + self.recommendation_bucket = self.environment_service.\ + get_recommendation_bucket() + + @classmethod + def build(cls) -> 'OperationalHandler': + return cls( + tenant_service=SERVICE_PROVIDER.modular_client.tenant_service(), + environment_service=SERVICE_PROVIDER.environment_service, + settings_service=SERVICE_PROVIDER.settings_service, + s3_service=SERVICE_PROVIDER.s3, + rabbitmq_service=SERVICE_PROVIDER.rabbitmq_service, + license_service=SERVICE_PROVIDER.license_service, + assume_role_s3=SERVICE_PROVIDER.assume_role_s3 + ) + + @cached_property + def mapping(self) -> Mapping: + return { + CustodianEndpoint.REPORTS_OPERATIONAL: { + HTTPMethod.POST: self.generate_operational_reports, + } + } + + @validate_kwargs + def generate_operational_reports(self, event: OperationalGetReportModel, + context: RequestContext, + _tap: TenantsAccessPayload): + date = self.settings_service.get_report_date_marker().get( + 'current_week_date') + if not date: + _LOG.warning('Missing \'current_week_date\' section in ' + '\'REPORT_DATE_MARKER\' setting.') + date = (datetime.today() + relativedelta( + weekday=SU(0))).date().isoformat() + + metrics_bucket = self.environment_service.get_metrics_bucket_name() + # TODO report move to the pydantic validation because this is stupid + tenant_names = event.tenant_names + report_types = event.types + receivers = event.receivers + + json_model = [] + errors = [] + _LOG.debug(f'Report type: {report_types if report_types else "ALL"}') + for tenant_name in tenant_names: + if not _tap.is_allowed_for(tenant_name): + raise ResponseFactory(HTTPStatus.FORBIDDEN).message( + f'Action is forbidden for tenant {tenant_name}' + ).exc() + _LOG.debug(f'Retrieving tenant with name {tenant_name}') + tenant = self.tenant_service.get(tenant_name) + if not tenant: + _msg = f'Cannot find tenant with name \'{tenant_name}\'' + errors.append(_msg) + continue + if not self._is_tenant_active(tenant): + _msg = f'Custodian is not activated for tenant ' \ + f'\'{tenant_name}\'' + errors.append(_msg) + continue + tenant_metrics = self.s3_service.gz_get_object( + bucket=metrics_bucket, + key=ACCOUNT_METRICS_PATH.format( + customer=tenant.customer_name, + date=date, account_id=tenant.project + ) + ) + if not tenant_metrics: + _msg = f'There is no data for tenant {tenant_name} for the ' \ + f'last week' + errors.append(_msg) + continue + + _LOG.debug('Retrieving tenant data') + # TODO maybe use ijson lib to reduce memory load + tenant_metrics = json.load(tenant_metrics) + tenant_metrics[OUTDATED_TENANTS] = self._process_outdated_tenants( + tenant_metrics.pop(OUTDATED_TENANTS, {})) + + overview_data = tenant_metrics.pop(OVERVIEW_TYPE, None) + rule_data = tenant_metrics.pop(RULE_TYPE, None) + resources_data = tenant_metrics.pop(RESOURCES_TYPE, None) + compliance_data = tenant_metrics.pop(COMPLIANCE_TYPE, None) + attack_data = tenant_metrics.pop(ATTACK_VECTOR_TYPE, None) + finops_data = tenant_metrics.pop(FINOPS_TYPE, None) + k8s_data = tenant_metrics.pop(KUBERNETES_TYPE, None) + for _type, data in [(ATTACK_REPORT_TYPE, attack_data), + (COMPLIANCE_REPORT_TYPE, compliance_data), + (OVERVIEW_REPORT_TYPE, overview_data), + (RESOURCES_REPORT_TYPE, resources_data), + (RULES_REPORT_TYPE, rule_data), + (FINOPS_REPORT_TYPE, finops_data)]: + if report_types and _type['custodian'] not in report_types: + data.clear() + continue + request_data_format = self._collect_json_model_parameters( + data, tenant_metrics[CUSTOMER_ATTR], _type['custodian']) + json_model.append(self.rabbitmq_service.build_m3_json_model( + _type['maestro'], {'receivers': list(receivers), + **tenant_metrics, **request_data_format, + 'report_type': _type['custodian']})) + if not report_types or \ + KUBERNETES_REPORT_TYPE['custodian'] in report_types: + if not k8s_data: + _msg = f'There is no kubernetes data for tenant ' \ + f'{tenant_name}' + _LOG.warning(_msg) + errors.append(_msg) + else: + for cluster, cluster_data in k8s_data.items(): + region = cluster_data.pop(REGION_ATTR) + last_scan_date = cluster_data.pop(LAST_SCAN_DATE) + request_data_format = self._collect_json_model_parameters( + cluster_data, tenant_metrics[CUSTOMER_ATTR], + KUBERNETES_REPORT_TYPE['custodian']) + json_model.append( + self.rabbitmq_service.build_m3_json_model( + KUBERNETES_REPORT_TYPE['maestro'], + {'receivers': list(receivers), + **tenant_metrics, + **request_data_format, + 'cluster_id': cluster, + REGION_ATTR: region, + LAST_SCAN_DATE: last_scan_date, + 'report_type': KUBERNETES_REPORT_TYPE['custodian']} + ) + ) + if errors: + _LOG.warning(f"Found errors: {os.linesep.join(errors)}") + if not json_model: + return build_response( + code=HTTPStatus.NOT_FOUND, + content=os.linesep.join(errors) + ) + + _LOG.debug(f'Going to retrieve rabbit mq application by ' + f'customer: {event.customer_id}') + + rabbitmq = self.rabbitmq_service.get_customer_rabbitmq( + event.customer_id) + if not rabbitmq: + return self.rabbitmq_service.no_rabbit_configuration() + code = self.rabbitmq_service.send_notification_to_m3( + COMMAND_NAME, json_model, rabbitmq) + if code != 200: + return build_response( + code=code, + content=f'The request to send report' + f'{"s" if not report_types else ""} for ' + f'{tenant_names} tenant' + f'{" was" if report_types else " were"} ' + f'not send.' + ) + return build_response( + content=f'The request to send report' + f'{"s" if not report_types else ""} for ' + f'{tenant_names} tenant' + f'{" was" if report_types else " were"} ' + f'successfully created' + ) + + def _is_tenant_active(self, tenant: Tenant) -> bool: + _LOG.debug(f'Going to check whether Custodian is activated ' + f'for tenant {tenant.name}') + if not tenant.is_active: + _LOG.debug('Tenant is not active') + return False + lic = self.license_service.get_tenant_license(tenant) + if not lic: + return False + if lic.is_expired(): + _LOG.warning(f'License {lic.license_key} has expired') + return False + return True + + @staticmethod + def _process_outdated_tenants(outdated_tenants: dict): + tenants = [] + for cloud, data in outdated_tenants.items(): + tenants.extend(data.keys()) + return tenants + + def save_k8s_data_to_s3(self, content: dict, path: str, id_: str) -> str: + with open(f'{tempfile.gettempdir()}{os.sep}{id_}.jsonl', 'w') as file: + for line in content.get('policy_data', {}): + resources = line.pop('resources', []) + file.write(self._build_policy_json_line(line)) + for resource in resources: + file.write(self._build_resource_json_line(resource)) + for tactic in content.get('mitre_data', {}): + techniques = tactic.pop('techniques_data', []) + file.write(self._build_tactic_json_line(tactic)) + for technique in techniques: + resources = technique.pop('resources', []) + file.write(self._build_technique_json_line(technique)) + for resource in resources: + file.write(self._build_resource_json_line(resource)) + content.clear() + _LOG.debug(f'Saving file {path}') + with open(f'{tempfile.gettempdir()}{os.sep}{id_}.jsonl', 'rb') as file: + self.assume_role_s3.put_object(bucket=self.recommendation_bucket, + key=path, body=file.read()) + return path + + def save_mitre_data_to_s3(self, content: dict, path: str, id_: str) -> str: + with open(f'{tempfile.gettempdir()}{os.sep}{id_}.jsonl', 'w') as file: + for tactic in content: + techniques = tactic.pop('techniques_data', []) + file.write(self._build_tactic_json_line(tactic)) + for technique in techniques: + regions = technique.pop('regions_data', {}) + file.write(self._build_technique_json_line(technique)) + for region, resources in regions.items(): + file.write(self._build_region_json_line(region)) + for resource in resources.get('resources', []): + file.write(self._build_resource_json_line(resource)) + content.clear() + _LOG.debug(f'Saving file {path}') + with open(f'{tempfile.gettempdir()}{os.sep}{id_}.jsonl', 'rb') as file: + self.assume_role_s3.put_object(bucket=self.recommendation_bucket, + key=path, body=file.read()) + return path + + def save_policy_data_to_s3(self, content: dict, path: str, + id_: str) -> str: + with open(f'{tempfile.gettempdir()}{os.sep}{id_}.jsonl', 'w') as file: + for line in content: + regions = line.pop('regions_data', {}) + file.write(self._build_policy_json_line(line)) + for region, resources in regions.items(): + file.write(self._build_region_json_line(region)) + for resource in resources.get('resources', []): + file.write(self._build_resource_json_line(resource)) + content.clear() + _LOG.debug(f'Saving file {path}') + with open(f'{tempfile.gettempdir()}{os.sep}{id_}.jsonl', 'rb') as file: + self.assume_role_s3.put_object(bucket=self.recommendation_bucket, + key=path, body=file.read()) + return path + + def save_finops_data_to_s3(self, content: dict, path: str, + id_: str) -> str: + with open(f'{tempfile.gettempdir()}{os.sep}{id_}.jsonl', 'w') as file: + for line in content: + rules = line.pop('rules_data', []) + file.write(self._build_service_json_line(line)) + for rule in rules: + regions = rule.pop('regions_data', {}) + file.write(self._build_rule_json_line(rule)) + for region, resources in regions.items(): + file.write(self._build_region_json_line(region)) + for resource in resources.get('resources', []): + file.write(self._build_resource_json_line(resource)) + content.clear() + _LOG.debug(f'Saving file {path}') + with open(f'{tempfile.gettempdir()}{os.sep}{id_}.jsonl', 'rb') as file: + self.assume_role_s3.put_object(bucket=self.recommendation_bucket, + key=path, body=file.read()) + return path + + @staticmethod + def _build_rule_json_line(line: str) -> str: + return f'{RULE_PREFIX}{json.dumps(line, separators=(",", ":"))}\n' + + @staticmethod + def _build_service_json_line(line: str) -> str: + return f'{SERVICE_PREFIX}{json.dumps(line, separators=(",", ":"))}\n' + + @staticmethod + def _build_region_json_line(region: str) -> str: + return f'{REGION_PREFIX}' \ + f'{json.dumps({"key":region}, separators=(",", ":"))}\n' + + @staticmethod + def _build_policy_json_line(line: dict) -> str: + return f'{POLICY_PREFIX}{json.dumps(line, separators=(",", ":"))}\n' + + @staticmethod + def _build_tactic_json_line(line: dict) -> str: + return f'{TACTIC_PREFIX}{json.dumps(line, separators=(",", ":"))}\n' + + @staticmethod + def _build_technique_json_line(line: dict) -> str: + return f'{TECHNIQUE_PREFIX}{json.dumps(line, separators=(",", ":"))}\n' + + @staticmethod + def _build_resource_json_line(line: dict) -> str: + return f'{json.dumps(line, separators=(",", ":"))}\n' + + def _collect_json_model_parameters(self, data, customer: str, + report_type: str) -> dict: + report_type_method_mapping = { + 'KUBERNETES': self.save_k8s_data_to_s3, + 'FINOPS': self.save_finops_data_to_s3, + 'RESOURCES': self.save_policy_data_to_s3, + 'ATTACK_VECTOR': self.save_mitre_data_to_s3 + } + + if sys.getsizeof(json.dumps(data)) > \ + self.settings_service.get_max_rabbitmq_size() and \ + report_type in report_type_method_mapping: + id_ = str(uuid.uuid4()) + path = f'{customer}/{id_}.jsonl' + data_path = report_type_method_mapping[report_type]( + data, path, id_) + request_data_format = { + EXTERNAL_DATA_ATTR: True, + EXTERNAL_DATA_KEY_ATTR: data_path, + EXTERNAL_DATA_BUCKET_ATTR: self.recommendation_bucket, + DATA_ATTR: {} if isinstance(data, dict) else [] + } + else: + request_data_format = { + DATA_ATTR: data, + EXTERNAL_DATA_ATTR: False + } + return request_data_format diff --git a/src/lambdas/custodian_report_generation_handler/handlers/retry_handler.py b/src/lambdas/custodian_report_generation_handler/handlers/retry_handler.py new file mode 100644 index 000000000..bbbd7386a --- /dev/null +++ b/src/lambdas/custodian_report_generation_handler/handlers/retry_handler.py @@ -0,0 +1,119 @@ +from functools import cached_property +from http import HTTPStatus +import time + +from handlers import AbstractHandler, Mapping +from helpers import RequestContext, batches, to_api_gateway_event +from helpers.constants import ( + ALL_ATTR, + CustodianEndpoint, + HTTPMethod, + ReportDispatchStatus, +) +from helpers.lambda_response import build_response +from helpers.log_helper import get_logger +from models.report_statistics import ReportStatistics +from services import SERVICE_PROVIDER +from services.report_statistics_service import ReportStatisticsService + +SEND_REPORTS_STATE_MACHINE = 'send_reports' +CUSTOMER_NAME_ATTR = 'customer_name' + +_LOG = get_logger(__name__) + + +class RetryHandler(AbstractHandler): + def __init__(self, report_statistics_service: ReportStatisticsService, + step_function_client, setting_service): + self.report_statistics_service = report_statistics_service + self.step_function_client = step_function_client + self.setting_service = setting_service + self.entity_report_mapping = {} # this cache won't work if different lambda executions + + @classmethod + def build(cls) -> 'RetryHandler': + return cls( + report_statistics_service=SERVICE_PROVIDER.report_statistics_service, + step_function_client=SERVICE_PROVIDER.step_function, + setting_service=SERVICE_PROVIDER.settings_service + ) + + @cached_property + def mapping(self) -> Mapping: + return { + CustodianEndpoint.REPORTS_RETRY: { + HTTPMethod.POST: self.post + } + } + + def post(self, event: dict, context: RequestContext): + self.entity_report_mapping.clear() # why? + for batch in batches(self.report_statistics_service.iter_pending(), 10): + self.process_pending_reports(batch, context) + return build_response( + code=HTTPStatus.OK, + content=f'Reports for ' + f'{", ".join(self.entity_report_mapping.keys())} ' + f'were triggered.' + ) + + def process_pending_reports(self, items: list[ReportStatistics], + context: RequestContext): + for item in items: + self.invoke_pending_reports(item) + time.sleep(self.setting_service.get_retry_interval()) + if context.get_remaining_time_in_millis() <= 90: + raise TimeoutError() + + def invoke_pending_reports(self, item: ReportStatistics) -> None: + _LOG.debug(f'Processing {item.id} item') + entity = item.tenant or item.customer_name + report_type = item.type if item.type else ALL_ATTR + level = item.level + if self.entity_report_mapping.get(entity, {}).get(level, {}) and \ + report_type in self.entity_report_mapping[entity][level]: + _LOG.debug(f'{level.capitalize()}-level report for ' + f'{entity} has already been submitted (type: ' + f'{report_type}.') + self.report_statistics_service.update( + item, status=ReportDispatchStatus.DUPLICATE + ) + return + item_event = item.event.as_dict() + if not item_event: + _LOG.warning(f'Failed report with id `{item.id}` does not ' + f'contain event. Cannot resend report.') + self.report_statistics_service.update( + item, + status=ReportDispatchStatus.FAILED, + reason='The report item does not contain an event. ' + 'Unable to resend report' + ) + return + event = to_api_gateway_event(item_event) + + _LOG.info(f'Invoking step function for retry with event: {event}') + is_success = self.step_function_client.invoke( + state_machine_name=SEND_REPORTS_STATE_MACHINE, + event=event + ) + if not is_success: + _LOG.debug(f'No response from step-function ' + f'{SEND_REPORTS_STATE_MACHINE}') + self.report_statistics_service.update( + item, + status=ReportDispatchStatus.FAILED, + reason='Cannot resend report due to step ' + 'function malfunction' + + ) + return + _LOG.debug(f'Submitted {report_type.capitalize()}-level ' + f'report for {entity}.') + self.entity_report_mapping.setdefault(entity, {}).setdefault( + level, set()).add(report_type) + _LOG.debug('Changing status of the old item to \'RETRIED\'') + self.report_statistics_service.update( + item, + status=ReportDispatchStatus.RETRIED + ) diff --git a/src/lambdas/custodian_report_generation_handler/lambda_config.json b/src/lambdas/custodian_report_generation_handler/lambda_config.json index bd9bae306..a93a0432c 100644 --- a/src/lambdas/custodian_report_generation_handler/lambda_config.json +++ b/src/lambdas/custodian_report_generation_handler/lambda_config.json @@ -8,6 +8,7 @@ "memory": 512, "timeout": 300, "lambda_path": "/lambdas/custodian_report_generation_handler", + "logs_expiration": "${logs_expiration}", "dependencies": [ { "resource_name": "CaaSCustomerMetrics", @@ -23,12 +24,14 @@ } ], "env_variables": { - "caas_user_pool_name": "${caas_user_pool_name}", + "CAAS_USER_POOL_NAME": "${caas_user_pool_name}", "modular_assume_role_arn": "${modular_assume_role_arn}", "MODULAR_AWS_REGION": "${MODULAR_AWS_REGION}", - "metrics_bucket_name": "${caas_metrics_bucket_name}", - "reports_bucket_name": "${reports-bucket}", - "caas_rulesets_bucket": "${caas_rulesets_bucket}" + "CAAS_METRICS_BUCKET_NAME": "${caas_metrics_bucket_name}", + "CAAS_REPORTS_BUCKET_NAME": "${reports-bucket}", + "CAAS_RULESETS_BUCKET_NAME": "${caas_rulesets_bucket}", + "CAAS_STATISTICS_BUCKET_NAME": "${stats_s3_bucket_name}", + "CAAS_RECOMMENDATIONS_BUCKET_NAME": "${caas_recommendations_bucket}" }, "publish_version": true, "alias": "${lambdas_alias_name}", @@ -46,5 +49,8 @@ "resource_type": "cloudwatch_rule_trigger", "target_rule": "caas-event-driven-sender-trigger" } + ], + "platforms": [ + "manylinux2014_x86_64" ] } \ No newline at end of file diff --git a/src/lambdas/custodian_report_generation_handler/requirements.txt b/src/lambdas/custodian_report_generation_handler/requirements.txt index 1bec5363b..820289d05 100644 --- a/src/lambdas/custodian_report_generation_handler/requirements.txt +++ b/src/lambdas/custodian_report_generation_handler/requirements.txt @@ -1 +1 @@ -cryptography==3.4.7 \ No newline at end of file +cryptography==42.0.2 \ No newline at end of file diff --git a/src/lambdas/custodian_report_generation_handler/retry_testing.md b/src/lambdas/custodian_report_generation_handler/retry_testing.md new file mode 100644 index 000000000..b51ed7d7e --- /dev/null +++ b/src/lambdas/custodian_report_generation_handler/retry_testing.md @@ -0,0 +1,97 @@ +## SaaS scheme +![SaaS retry scheme](../../../docs/pics/saas_retry.png) +## Onprem scheme (lambda icons = python function) +![OnPrem retry scheme](../../../docs/pics/onprem_retry.png) + +## Testing tips +- To enable/disable setting that blocks reports sending on the Custodian side, use these CLI commands: + `c7n setting report enable_sending --confirm` + `c7n setting report disable_sending --confirm` +- To raise ReportNotSendException to test retry functionality, set environment variable `CAAS_TESTING` as `true` +(for saas: in `custodian_report_generation_handler` lambda) and run any report +Only for onprem: +- To remove all active schedules, set environment variable `TESTING_DROP_SCHEDULES` as `true` and try to trigger any report +- To set max number of crons at the same time, use setting `MAX_CRON_NUMBER` in `CaaSSettings` table (min=2, max=20, default=10) + +### NOTE: +`by any "/reports/" endpoint` I mean any of these endpoints: `/reports/operational`, `/reports/project`, `/reports/department`, `/reports/clevel` + +## Use cases +1. Trigger any `/reports/*` endpoint via CLI or API while setting `SEND_REPORTS`=`false`, request will be saved to `CaaSReportStatistics` table with status `PENDING`. +2. Trigger any `/reports/*` endpoint via CLI or API with setting `SEND_REPORTS`=`true`: + 1. Report was sent successfully and saved to `CaaSReportStatistics` table with `SUCCEEDED` status. + 2. Report was not sent because of any RabbitMQ or Maestro server issues: + - OnPrem: request information is stored to `CaaSReportStatistics` table, cron created that will retry sending request in 15 minutes. Was saved to `CaaSReportStatistics` table with `FAILED` status; + - SaaS: request information is stored to `CaaSReportStatistics` table, step function will retry sending request in 15 minutes. Was saved to `CaaSReportStatistics` table with `FAILED` status; + 3. Report was not sent because of any other issues and saved to `CaaSReportStatistics` table with `FAILED` status. +3. Trigger any `/reports/*` endpoint via cron or step function for the 1-3rd times: + 1. Report was sent successfully and saved to `CaaSReportStatistics` table with `SUCCEEDED` status. + 2. Report was not sent, request information is stored to `CaaSReportStatistics` table with new attempt number and: + - OnPrem: existing cron related to this request has be updated with bigger interval (15 min * attempt number). Item saved to `CaaSReportStatistics` table with `FAILED` status; + - SaaS: step function retrying send the request with a longer interval. Item saved to `CaaSReportStatistics` table with `FAILED` status; +4. Trigger any `/reports/*` endpoint via cron or step function for the 4th time: + 1. Report was sent successfully. + 2. Report was not sent, value of `SEND_REPORTS` setting changed to `false`, all subsequent requests to trigger reports will be saved to `CaaSReportStatistics` table with status `PENDING` and: + - OnPrem: removed cron job of current retry. All subsequent cron jobs (that were created previously) will be removed at the start of execution. +5. Trigger `/reports/retry` endpoint while there are no items with `PENDING` status in `CaaSReportStatistics` table; +6. Trigger `/reports/retry` endpoint while there is one item with `PENDING` status in `CaaSReportStatistics` table: + - OnPrem: the report sending function will run asynchronously; + - SaaS: lambda will trigger the report sending step function; +7. Trigger `/reports/retry` endpoint and multiple items with `PENDING` status in `CaaSReportStatistics` table: + - OnPrem: the report sending function will run asynchronously for each item in loop, excluding duplicates; + - SaaS: lambda will trigger the report sending step function for each item in loop, excluding duplicates; +8. Trigger `/reports/retry` endpoint with setting `SEND_REPORTS`=`false` + + +### Items status: +- `FAILED` - failed to trigger reports +- `PENDING` - the request was created when the `SEND_REPORTS` setting was disabled +- `DUPLICATE` - the request for this specific entity and report type has already been retried within one "retry session" (retry lambda execution) +- `SUCCEEDED` - the report triggered successfully +- `RETRIED` - the report was successfully rerun + + +### Step function input examples: +For retry-send-report: +```json +{ + "requestContext": { + "authorizer": { + "claims": { + "cognito:username":"system_user", + "custom:role":"system_role", + "custom:customer":"CUSTODIAN_SYSTEM" + } + }, + "resourcePath": "/reports/retry", + "path": "/caas/reports/retry" + }, + "headers": { + "Host": "${ID}.execute-api.eu-west-1.amazonaws.com" + }, + "httpMethod": "POST" +} +``` + + +For send-report: +```json +{ + "pathParameters": {}, + "path": "/reports/department", + "httpMethod": "POST", + "headers": { + "Content-Length": "16", "Content-Type": "application/json", "Host": "host", + "User-Agent": "python-requests/2.28.2", "Accept-Encoding": "gzip, deflate", "Accept": "*/*", + "Connection": "keep-alive"}, + "requestContext": { + "stage": "caas", + "resourcePath": "/reports/department", "path": "caas/reports/department", + "authorizer": {"claims": {"cognito:username": "system", "custom:customer": "CUSTODIAN_SYSTEM", + "custom:role": "system_role", "custom:tenants": []} + } + }, + "tenant_names": "NAMES", // only for operational report + "tenant_display_names": "NAMES", // only for project report +} +``` \ No newline at end of file diff --git a/src/lambdas/custodian_report_generator/deployment_resources.json b/src/lambdas/custodian_report_generator/deployment_resources.json index abc8f8aac..ca9ca8251 100644 --- a/src/lambdas/custodian_report_generator/deployment_resources.json +++ b/src/lambdas/custodian_report_generator/deployment_resources.json @@ -10,6 +10,7 @@ "dynamodb:BatchGetItem", "s3:Get*", "s3:List*", + "s3:PutObject", "batch:DescribeJobs", "logs:GetLogEvents", "cognito-idp:AdminInitiateAuth", diff --git a/src/lambdas/custodian_report_generator/handler.py b/src/lambdas/custodian_report_generator/handler.py index 253b9fc35..0a18ba748 100644 --- a/src/lambdas/custodian_report_generator/handler.py +++ b/src/lambdas/custodian_report_generator/handler.py @@ -1,101 +1,60 @@ -from http import HTTPStatus -from typing import List +from functools import cached_property -from handlers.abstracts.abstract_handler import AbstractHandler -from handlers.compliance_handler import JobsComplianceHandler, \ - EntityComplianceHandler -from handlers.details_handler import JobsDetailsHandler, EntityDetailsHandler -from handlers.digest_handler import JobsDigestHandler, EntityDigestHandler -from handlers.errors_handler import JobsErrorsHandler, EntityErrorsHandler +from handlers.compliance_handler import ComplianceReportHandler +from handlers.details_handler import DetailedReportHandler +from handlers.digest_handler import DigestReportHandler +from handlers.errors_handler import ErrorsReportHandler +from handlers.findings_handler import FindingsReportHandler from handlers.push_handler import SiemPushHandler from handlers.resource_report_handler import ResourceReportHandler -from handlers.rules_handler import JobsRulesHandler, EntityRulesHandler -from helpers import build_response -from helpers.constants import ACTION_PARAM_ERROR, HTTP_METHOD_ERROR, \ - PARAM_REQUEST_PATH, PARAM_HTTP_METHOD +from handlers.rules_handler import JobsRulesHandler from helpers.log_helper import get_logger -from services import SERVICE_PROVIDER -from services.abstract_api_handler_lambda import AbstractApiHandlerLambda +from handlers.raw_report_handler import RawReportHandler +from services.abs_lambda import ( + ApiGatewayEventProcessor, + CheckPermissionEventProcessor, + RestrictCustomerEventProcessor, + ExpandEnvironmentEventProcessor, + ApiEventProcessorLambdaHandler, + RestrictTenantEventProcessor +) +from validators.registry import permissions_mapping -_LOG = get_logger('custodian-report-generator') +_LOG = get_logger('caas-report-generator') # TODO merge this lambda with report_generation_handler -class ReportGenerator(AbstractApiHandlerLambda): - - def __init__(self, handlers: List[AbstractHandler]): - self.REQUEST_PATH_HANDLER_MAPPING = {} - for handler in handlers: - self.REQUEST_PATH_HANDLER_MAPPING.update( - handler.define_action_mapping() - ) - - def handle_request(self, event, context): - request_path = event[PARAM_REQUEST_PATH] - method_name = event[PARAM_HTTP_METHOD] - handler_functions = self.REQUEST_PATH_HANDLER_MAPPING.get(request_path) - if not handler_functions: - return build_response( - code=HTTPStatus.BAD_REQUEST, - content=ACTION_PARAM_ERROR.format(endpoint=request_path) - ) - handler_func = handler_functions.get(method_name) - response = None - if handler_func: - response = handler_func(event=event) - return response or build_response( - code=HTTPStatus.BAD_REQUEST, - content=HTTP_METHOD_ERROR.format( - method=method_name, resource=request_path - ) - ) - - -HANDLERS: List[AbstractHandler] = [ - Handler( - ambiguous_job_service=SERVICE_PROVIDER.ambiguous_job_service(), - modular_service=SERVICE_PROVIDER.modular_service(), - report_service=SERVICE_PROVIDER.report_service() +class ReportGenerator(ApiEventProcessorLambdaHandler): + processors = ( + ExpandEnvironmentEventProcessor.build(), + ApiGatewayEventProcessor(permissions_mapping), + RestrictCustomerEventProcessor.build(), + CheckPermissionEventProcessor.build(), + RestrictTenantEventProcessor.build() ) - for Handler in ( - JobsDigestHandler, EntityDigestHandler, - JobsDetailsHandler, EntityDetailsHandler, - JobsErrorsHandler, EntityErrorsHandler, - JobsRulesHandler, EntityRulesHandler + handlers = ( + ComplianceReportHandler, + ResourceReportHandler, + JobsRulesHandler, + DetailedReportHandler, + DigestReportHandler, + ErrorsReportHandler, + SiemPushHandler, + FindingsReportHandler, + RawReportHandler ) -] -HANDLERS.extend( - [ - EntityComplianceHandler( - ambiguous_job_service=SERVICE_PROVIDER.ambiguous_job_service(), - modular_service=SERVICE_PROVIDER.modular_service(), - report_service=SERVICE_PROVIDER.report_service(), - findings_service=SERVICE_PROVIDER.findings_service(), - coverage_service=SERVICE_PROVIDER.coverage_service(), - ), - JobsComplianceHandler( - ambiguous_job_service=SERVICE_PROVIDER.ambiguous_job_service(), - modular_service=SERVICE_PROVIDER.modular_service(), - report_service=SERVICE_PROVIDER.report_service(), - coverage_service=SERVICE_PROVIDER.coverage_service(), - ), - ResourceReportHandler.build() - ] -) + @cached_property + def mapping(self): + res = {} + for h in self.handlers: + res.update(h.build().mapping) + return res -HANDLERS.append( - SiemPushHandler( - ambiguous_job_service=SERVICE_PROVIDER.ambiguous_job_service(), - modular_service=SERVICE_PROVIDER.modular_service(), - report_service=SERVICE_PROVIDER.report_service(), - ssm_client=SERVICE_PROVIDER.ssm() - ) -) -REPORT_GENERATOR = ReportGenerator(handlers=HANDLERS) +REPORT_GENERATOR = ReportGenerator() def lambda_handler(event, context): diff --git a/src/lambdas/custodian_report_generator/lambda_config.json b/src/lambdas/custodian_report_generator/lambda_config.json index 2994dbecf..8ceaa14be 100644 --- a/src/lambdas/custodian_report_generator/lambda_config.json +++ b/src/lambdas/custodian_report_generator/lambda_config.json @@ -5,9 +5,10 @@ "resource_type": "lambda", "iam_role_name": "report-api-handler-role", "runtime": "python3.10", - "memory": 512, + "memory": 1024, "timeout": 300, "lambda_path": "/lambdas/custodian_report_generator", + "logs_expiration": "${logs_expiration}", "dependencies": [ { "resource_name": "CaaSJobs", @@ -19,10 +20,10 @@ } ], "env_variables": { - "caas_rulesets_bucket": "${caas_rulesets_bucket}", - "reports_bucket_name": "${reports-bucket}", - "stats_s3_bucket_name": "${stats_s3_bucket_name}", - "caas_user_pool_name": "${caas_user_pool_name}", + "CAAS_RULESETS_BUCKET_NAME": "${caas_rulesets_bucket}", + "CAAS_REPORTS_BUCKET_NAME": "${reports-bucket}", + "CAAS_STATISTICS_BUCKET_NAME": "${stats_s3_bucket_name}", + "CAAS_USER_POOL_NAME": "${caas_user_pool_name}", "modular_assume_role_arn": "${modular_assume_role_arn}", "MODULAR_AWS_REGION": "${MODULAR_AWS_REGION}" }, @@ -36,5 +37,8 @@ ], "layers": [ "custodian_common_dependencies_layer" + ], + "platforms": [ + "manylinux2014_x86_64" ] } diff --git a/src/lambdas/custodian_report_generator/requirements.txt b/src/lambdas/custodian_report_generator/requirements.txt index 5c2305924..12428cc0b 100644 --- a/src/lambdas/custodian_report_generator/requirements.txt +++ b/src/lambdas/custodian_report_generator/requirements.txt @@ -1,2 +1,2 @@ -XlsxWriter==3.0.3 -# pytablewriter==0.64.2 \ No newline at end of file +XlsxWriter==3.1.9 +tabulate==0.9.0 \ No newline at end of file diff --git a/src/lambdas/custodian_rule_meta_updater/handler.py b/src/lambdas/custodian_rule_meta_updater/handler.py index 3c0da0881..27ed907ed 100644 --- a/src/lambdas/custodian_rule_meta_updater/handler.py +++ b/src/lambdas/custodian_rule_meta_updater/handler.py @@ -1,37 +1,36 @@ +from concurrent.futures import ThreadPoolExecutor import dataclasses -import io +from functools import cached_property import json import os -import tempfile -from concurrent.futures import ThreadPoolExecutor -from functools import cached_property from pathlib import Path -from typing import Generator, Dict, List, Tuple, Optional, Union, TypedDict +import tempfile +from typing import Generator, TypedDict from modular_sdk.commons import DataclassBase from pydantic import ValidationError from ruamel.yaml import YAML, YAMLError, __with_libyaml__ -from helpers import (RequestContext) -from helpers import build_response -from helpers.constants import RuleSourceType, STATUS_SYNCING, \ - STATUS_SYNCING_FAILED, STATUS_SYNCED, \ - KEY_RULES_TO_MITRE, KEY_RULES_TO_SERVICE_SECTION, KEY_RULES_TO_STANDARDS, \ - KEY_RULES_TO_SEVERITY, KEY_CLOUD_TO_RULES, KEY_AWS_EVENTS, \ - KEY_AZURE_EVENTS, KEY_GOOGLE_EVENTS, KEY_AWS_STANDARDS_COVERAGE, \ - KEY_AZURE_STANDARDS_COVERAGE, KEY_GOOGLE_STANDARDS_COVERAGE, \ - KEY_HUMAN_DATA, KEY_RULES_TO_SERVICE, KEY_RULES_TO_CATEGORY +from helpers import RequestContext +from helpers.constants import ( + RuleSourceType, + S3SettingKey, + STATUS_SYNCED, + STATUS_SYNCING, + STATUS_SYNCING_FAILED, +) +from helpers.lambda_response import build_response from helpers.log_helper import get_logger from helpers.time_helper import utc_iso from models.rule import Rule from models.setting import Setting from services import SERVICE_PROVIDER -from services.abstract_lambda import AbstractLambda -from services.clients.git_service_clients import GitLabClient, GitHubClient +from services.abs_lambda import EventProcessorLambdaHandler +from services.clients.git_service_clients import GitHubClient, GitLabClient from services.clients.s3 import S3Client, S3Url from services.clients.xlsx_standard_parser import parse_standards -from services.rule_meta_service import RuleMetaService, RuleService, \ - MappingsCollector, RuleMetaModel, RuleModel +from services.mappings_collector import MappingsCollector +from services.rule_meta_service import RuleMetaModel, RuleModel, RuleService from services.rule_source_service import RuleSourceService from services.s3_settings_service import S3SettingsService from services.setting_service import SettingsService @@ -47,7 +46,7 @@ class MetaAccess(DataclassBase): url: str project: str ref: str - secret: Optional[str] + secret: str | None class StandardsSetting(TypedDict): @@ -56,16 +55,16 @@ class StandardsSetting(TypedDict): value: str -class RuleMetaUpdaterLambdaHandler(AbstractLambda): +class RuleMetaUpdaterLambdaHandler(EventProcessorLambdaHandler): + processors = () + def __init__(self, rule_service: RuleService, - rule_meta_service: RuleMetaService, rule_source_service: RuleSourceService, settings_service: SettingsService, s3_settings_service: S3SettingsService, ssm_service: SSMService, s3_client: S3Client): self._rule_service = rule_service - self._rule_meta_service = rule_meta_service self._rule_source_service = rule_source_service self._settings_service = settings_service self._s3_settings_service = s3_settings_service @@ -75,13 +74,12 @@ def __init__(self, rule_service: RuleService, @classmethod def build(cls): return cls( - rule_service=SERVICE_PROVIDER.rule_service(), - rule_meta_service=SERVICE_PROVIDER.rule_meta_service(), - rule_source_service=SERVICE_PROVIDER.rule_source_service(), - settings_service=SERVICE_PROVIDER.settings_service(), - s3_settings_service=SERVICE_PROVIDER.s3_settings_service(), - ssm_service=SERVICE_PROVIDER.ssm_service(), - s3_client=SERVICE_PROVIDER.s3() + rule_service=SERVICE_PROVIDER.rule_service, + rule_source_service=SERVICE_PROVIDER.rule_source_service, + settings_service=SERVICE_PROVIDER.settings_service, + s3_settings_service=SERVICE_PROVIDER.s3_settings_service, + ssm_service=SERVICE_PROVIDER.ssm_service, + s3_client=SERVICE_PROVIDER.s3 ) def parse_standards(self, setting: Setting) -> dict: @@ -93,23 +91,20 @@ def parse_standards(self, setting: Setting) -> dict: f'Cannot parse standards for {setting.name}') return {} source_s3, notation_s3 = S3Url(source_s3), S3Url(notation_s3) - notation = self._s3_client.get_file_stream( - bucket_name=notation_s3.bucket, full_file_name=notation_s3.key + notation = self._s3_client.get_json( + bucket=notation_s3.bucket, key=notation_s3.key ) if not notation: _LOG.warning(f'Notation not found by path: {notation_s3.url}') return {} notation = json.load(notation) - source = self._s3_client.get_file_stream( - bucket_name=source_s3.bucket, full_file_name=source_s3.key + source = self._s3_client.get_object( + bucket=source_s3.bucket, key=source_s3.key ) if not source: _LOG.warning(f'Source not found by path: {source_s3.url}') return {} - return parse_standards( - io.BytesIO(source.read()), - notation - ) + return parse_standards(source, notation) def update_standards(self): _LOG.info('Generating standards') @@ -117,84 +112,71 @@ def update_standards(self): if aws: _LOG.debug('Updating standards for AWS') self._s3_settings_service.set( - key=KEY_AWS_STANDARDS_COVERAGE, + key=S3SettingKey.AWS_STANDARDS_COVERAGE, data=self.parse_standards(aws) ) - # aws.value['value'] = collector.compressed( - # self.parse_standards(aws)) - # self._settings_service.save(aws) azure = self._settings_service.azure_standards_coverage() if azure: _LOG.debug('Updating standards for AZURE') self._s3_settings_service.set( - key=KEY_AZURE_STANDARDS_COVERAGE, + key=S3SettingKey.AZURE_STANDARDS_COVERAGE, data=self.parse_standards(azure) ) - # azure.value['value'] = collector.compressed( - # self.parse_standards(azure)) - # self._settings_service.save(azure) google = self._settings_service.google_standards_coverage() if google: _LOG.debug('Updating standards for GOOGLE') self._s3_settings_service.set( - key=KEY_GOOGLE_STANDARDS_COVERAGE, + key=S3SettingKey.GOOGLE_STANDARDS_COVERAGE, data=self.parse_standards(google) ) - # google.value['value'] = collector.compressed( - # self.parse_standards(google)) - # self._settings_service.save(google) def save_mappings(self, collector: MappingsCollector): _LOG.debug('Saving mappings to settings') # TODO set in threads? self._s3_settings_service.set( - key=KEY_RULES_TO_STANDARDS, + key=S3SettingKey.RULES_TO_STANDARDS, data=collector.standard ) self._s3_settings_service.set( - key=KEY_RULES_TO_SEVERITY, + key=S3SettingKey.RULES_TO_SEVERITY, data=collector.severity ) self._s3_settings_service.set( - key=KEY_RULES_TO_MITRE, + key=S3SettingKey.RULES_TO_MITRE, data=collector.mitre ) self._s3_settings_service.set( - key=KEY_RULES_TO_SERVICE_SECTION, + key=S3SettingKey.RULES_TO_SERVICE_SECTION, data=collector.service_section ) self._s3_settings_service.set( - key=KEY_CLOUD_TO_RULES, + key=S3SettingKey.CLOUD_TO_RULES, data=collector.cloud_rules ) self._s3_settings_service.set( - key=KEY_AWS_EVENTS, + key=S3SettingKey.AWS_EVENTS, data=collector.aws_events ) self._s3_settings_service.set( - key=KEY_AZURE_EVENTS, + key=S3SettingKey.AZURE_EVENTS, data=collector.azure_events ) self._s3_settings_service.set( - key=KEY_GOOGLE_EVENTS, + key=S3SettingKey.GOOGLE_EVENTS, data=collector.google_events ) self._s3_settings_service.set( - key=KEY_HUMAN_DATA, + key=S3SettingKey.HUMAN_DATA, data=collector.human_data ) self._s3_settings_service.set( - key=KEY_RULES_TO_SERVICE, + key=S3SettingKey.RULES_TO_SERVICE, data=collector.service ) self._s3_settings_service.set( - key=KEY_RULES_TO_CATEGORY, + key=S3SettingKey.RULES_TO_CATEGORY, data=collector.category ) - # self._settings_service.create( - # name=KEY_RULES_TO_STANDARDS, - # value=collector.compressed(collector.standard) - # ).save() _LOG.debug('Mappings were saved') @staticmethod @@ -205,7 +187,7 @@ def is_yaml(filename: str) -> bool: return filename.endswith('.yaml') or filename.endswith('.yml') @staticmethod - def to_rule_name(filename: str) -> str: + def to_rule_name(filepath: str) -> str: """ To get the rule name from metadata file we should adhere to such a protocol: @@ -222,15 +204,16 @@ def to_rule_name(filename: str) -> str: name >>> RuleMetaUpdaterLambdaHandler.to_rule_name('name') name - :param filename: + :param filepath: :return: """ - suffix_to_remove = ['.yaml', '.yml', '_metadata', '_meta'] + suffix_to_remove = ['_metadata', '_meta'] + filename = os.path.basename(filepath) + filename = os.path.splitext(filename)[0] for suffix in suffix_to_remove: if filename.endswith(suffix): filename = filename[:-len(suffix)] - - return filename.rsplit('/', maxsplit=1)[-1] + return filename @cached_property def metadata_key(self) -> str: @@ -241,7 +224,7 @@ def policies_key(self) -> str: return 'policies' def iter_files(self, root: Path - ) -> Generator[Tuple[Path, Dict], None, None]: + ) -> Generator[tuple[Path, dict], None, None]: """ Walks through the given root folder, looks for yaml files, loads them and yields JSONs @@ -263,7 +246,7 @@ def iter_files(self, root: Path _LOG.warning(f'Failed to load rule \'{file}\' ' f'content, skipping') - def get_metadata_data(self) -> List[MetaAccess]: + def get_metadata_data(self) -> list[MetaAccess]: # maybe get from another place. It's a temp solution secret_name = self._settings_service.rules_metadata_repo_access_data() secret_value = self._ssm_service.get_secret_value(secret_name) @@ -274,11 +257,10 @@ def get_metadata_data(self) -> List[MetaAccess]: secret_value = [secret_value] return [MetaAccess.from_dict(item) for item in secret_value] - def pull_meta(self, only_mappings: bool = False): + def pull_meta(self): _LOG.debug('Pulling rules meta') metas = self.get_metadata_data() collector = MappingsCollector() - rule_metas = [] for meta in metas: _LOG.info(f'Pulling meta from {meta.project}{meta.ref}') client = GitLabClient( @@ -289,6 +271,8 @@ def pull_meta(self, only_mappings: bool = False): root = client.clone_project( meta.project, Path(folder), meta.ref, ) + if not root: + continue for filename, content in self.iter_files(root): try: rule_name = self.to_rule_name(str(filename)) @@ -299,19 +283,13 @@ def pull_meta(self, only_mappings: bool = False): ) _LOG.debug( f'Adding meta {item.name}:{item.version}') - rule_metas.append( - self._rule_meta_service.create(**item.dict()) - ) collector.add_meta(item) except ValidationError as e: _LOG.warning(f'Invalid meta: {content}, {e}') continue - if not only_mappings: - _LOG.debug(f'Saving {len(rule_metas)} metas') - self._rule_meta_service.batch_save(rule_metas) self.save_mappings(collector) - def pull_rules(self, ids: List[str]): + def pull_rules(self, ids: list[str]): for rule_source, secret in self._rule_source_service.iter_by_ids(ids): rules = [] self._rule_source_service.update_latest_sync( @@ -351,6 +329,7 @@ def pull_rules(self, ids: List[str]): continue rules.append(self._rule_service.create( customer=rule_source.customer, + cloud=item.cloud.value, path=str(filepath.relative_to(root)), git_project=rule_source.git_project_id, ref=rule_source.git_ref, @@ -415,8 +394,8 @@ def _gl_add_commit_hash(rule: Rule, client: GitLabClient) -> None: return rule.commit_hash = meta['last_commit_id'] - def expand_with_commit_hash(self, rules: List[Rule], - client: Union[GitLabClient, GitHubClient]): + def expand_with_commit_hash(self, rules: list[Rule], + client: GitLabClient | GitHubClient): """ Fetches commit info and set to rules :param client: @@ -443,7 +422,7 @@ def handle_request(self, event: dict, context: RequestContext): if action == 'standards': self.update_standards() elif action == 'mappings': - self.pull_meta(only_mappings=True) + self.pull_meta() elif ids: _LOG.debug(f'Pulling rules for ids: {ids}') self.pull_rules(ids) diff --git a/src/lambdas/custodian_rule_meta_updater/lambda_config.json b/src/lambdas/custodian_rule_meta_updater/lambda_config.json index ade8fff86..581d77d85 100644 --- a/src/lambdas/custodian_rule_meta_updater/lambda_config.json +++ b/src/lambdas/custodian_rule_meta_updater/lambda_config.json @@ -8,6 +8,7 @@ "memory": 512, "timeout": 900, "lambda_path": "/lambdas/custodian_rule_meta_updater", + "logs_expiration": "${logs_expiration}", "dependencies": [ { "resource_name": "CaaSRules", @@ -21,7 +22,7 @@ } ], "env_variables": { - "caas_rulesets_bucket": "${caas_rulesets_bucket}" + "CAAS_RULESETS_BUCKET_NAME": "${caas_rulesets_bucket}" }, "publish_version": true, "alias": "${lambdas_alias_name}", @@ -33,5 +34,8 @@ ], "layers": [ "custodian_common_dependencies_layer" + ], + "platforms": [ + "manylinux2014_x86_64" ] } \ No newline at end of file diff --git a/src/lambdas/layers/common_dependencies_layer/local_requirements.txt b/src/lambdas/layers/common_dependencies_layer/local_requirements.txt index 6af94858b..6e5713cb0 100644 --- a/src/lambdas/layers/common_dependencies_layer/local_requirements.txt +++ b/src/lambdas/layers/common_dependencies_layer/local_requirements.txt @@ -1,7 +1,6 @@ services helpers models -connections -integrations validators -handlers \ No newline at end of file +handlers +scheduler \ No newline at end of file diff --git a/src/lambdas/layers/common_dependencies_layer/requirements.txt b/src/lambdas/layers/common_dependencies_layer/requirements.txt index e7e53c24f..309d311d3 100644 --- a/src/lambdas/layers/common_dependencies_layer/requirements.txt +++ b/src/lambdas/layers/common_dependencies_layer/requirements.txt @@ -3,9 +3,11 @@ botocore==1.29.80 pynamodb==5.3.2 dynamodb-json==1.3 python-dateutil==2.8.2 -typing_extensions==4.8.0 -pydantic==1.10.2 +typing_extensions==4.9.0 +pydantic==2.6.0 requests==2.31.0 cachetools==5.3.1 dacite==1.8.1 -modular-sdk==3.3.2 \ No newline at end of file +modular-sdk==5.1.1 +APScheduler==3.10.4 +msgspec==0.18.6 \ No newline at end of file diff --git a/src/lambdas/layers/matplotlib_layer/lambda_layer_config.json b/src/lambdas/layers/matplotlib_layer/lambda_layer_config.json deleted file mode 100644 index 55d7976fd..000000000 --- a/src/lambdas/layers/matplotlib_layer/lambda_layer_config.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "matplotlib_layer", - "resource_type": "lambda_layer", - "runtimes": [ - "python3.8" - ], - "deployment_package": "matplotlib_layer.zip" -} \ No newline at end of file diff --git a/src/lambdas/layers/matplotlib_layer/local_requirements.txt b/src/lambdas/layers/matplotlib_layer/local_requirements.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/lambdas/layers/matplotlib_layer/requirements.txt b/src/lambdas/layers/matplotlib_layer/requirements.txt deleted file mode 100644 index 1f18cde16..000000000 --- a/src/lambdas/layers/matplotlib_layer/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -matplotlib==3.6.2 \ No newline at end of file diff --git a/src/main.py b/src/main.py index 9142fd4f5..8ff8b088a 100644 --- a/src/main.py +++ b/src/main.py @@ -1,31 +1,56 @@ -""" -On-prem entering point. All the imports are inside functions to make the -helps fast and be safe from importing not existing packages -""" +#!/usr/local/bin/python import argparse -import json -import logging +import base64 import logging.config +import urllib.request +import urllib.error +import json import multiprocessing import os import secrets import string +import sys from abc import ABC, abstractmethod -from datetime import timedelta, datetime -from functools import cached_property +from datetime import datetime from pathlib import Path -from typing import Callable, Optional, Dict, Set, Union, Tuple, List, TypedDict +from typing import Callable, Literal, TYPE_CHECKING -import boto3 +import pymongo from bottle import Bottle -from dateutil.relativedelta import relativedelta, SU +from dateutil.relativedelta import SU, relativedelta from dotenv import load_dotenv -from modular_sdk.models.customer import Customer - -from helpers.time_helper import utc_iso, utc_datetime -from services import SERVICE_PROVIDER -from services.clients.xlsx_standard_parser import \ - main as parse_xlsx_standard, init_parser as init_xlsx_cli_parser +from swagger_ui import api_doc + +from helpers import dereference_json, urljoin +from helpers.__version__ import __version__ +from helpers.constants import ( + CAASEnv, + DOCKER_SERVICE_MODE, + HTTPMethod, + Permission, + SettingKey, + PRIVATE_KEY_SECRET_NAME, + DEFAULT_RULES_METADATA_REPO_ACCESS_SSM_NAME +) +from onprem.api.deployment_resources_parser import \ + DeploymentResourcesApiGatewayWrapper +from onprem.scripts.parse_rule_source import ( + init_parser as init_parser_rule_source_cli_parser, + main as parse_rule_source, +) +from onprem.scripts.rules_table_generator import ( + init_parser as init_rules_table_generator_cli_parser, + main as generate_rules_table, +) +from services import SP +from services.clients.xlsx_standard_parser import ( + init_parser as init_xlsx_cli_parser, + main as parse_xlsx_standard, +) +from services.openapi_spec_generator import OpenApiGenerator + +if TYPE_CHECKING: + from models import BaseModel SRC = Path(__file__).parent.resolve() ROOT = SRC.parent.resolve() @@ -34,45 +59,35 @@ ACTION_DEST = 'action' ENV_ACTION_DEST = 'env_action' -ALL_NESTING: Tuple[str, ...] = (ACTION_DEST, ENV_ACTION_DEST) # important +ALL_NESTING: tuple[str, ...] = (ACTION_DEST, ENV_ACTION_DEST) # important RUN_ACTION = 'run' CREATE_INDEXES_ACTION = 'create_indexes' CREATE_BUCKETS_ACTION = 'create_buckets' +GENERATE_OPENAPI_ACTION = 'generate_openapi' INIT_VAULT_ACTION = 'init_vault' +SET_META_REPOS_ACTION = 'set_meta_repos' UPDATE_API_GATEWAY_MODELS_ACTION = 'update_api_models' PARSE_XLSX_STANDARD_ACTION = 'parse_standards' -ENV_ACTION = 'env' - -UPDATE_SETTINGS_ENV_ACTION = 'update_settings' -CREATE_SYSTEM_USER_ENV_ACTION = 'create_system_user' -CREATE_CUSTOMER_ACTION = 'create_customer' -CREATE_TENANT_ACTION = 'create_tenant' -CREATE_USER_ACTION = 'create_user' +PARSE_RULE_SOURCE_ACTION = 'parse_rule_source' +GENERATE_RULES_TABLE_ACTION = 'generate_rules_table' +SHOW_PERMISSIONS_ACTION = 'show_permissions' +INIT_ACTION = 'init' DEFAULT_HOST = '0.0.0.0' DEFAULT_PORT = 8000 -DEFAULT_SCHEDULE_HOURS = 3 DEFAULT_NUMBER_OF_WORKERS = (multiprocessing.cpu_count() * 2) + 1 -DEFAULT_ON_PREM_API_LINK = f'http://{DEFAULT_HOST}:{str(DEFAULT_PORT)}/caas' DEFAULT_API_GATEWAY_NAME = 'custodian-as-a-service-api' +DEFAULT_SWAGGER_PREFIX = '/api/doc' -API_GATEWAY_LINK = 'https://{id}.execute-api.{region}.amazonaws.com/{stage}' - -SYSTEM_ROLE, ADMIN_ROLE, USER_ROLE = 'system_role', 'admin_role', 'user_role' -SYSTEM_POLICY, ADMIN_POLICY, USER_POLICY = 'system_policy', 'admin_policy', \ - 'user_policy' - -C7N_CONFIGURE_COMMAND = 'c7n configure --api_link {api_link}' -C7N_LOGIN_COMMAND = 'c7n login --username {username} --password ' \ - '\'{password}\'' +SYSTEM_USER = 'system_user' +SYSTEM_CUSTOMER = 'CUSTODIAN_SYSTEM' def gen_password(digits: int = 20) -> str: - allowed_punctuation = ''.join(set(string.punctuation) - {'"', "'", "!"}) - chars = string.ascii_letters + string.digits + allowed_punctuation + chars = string.ascii_letters + string.digits while True: - password = ''.join(secrets.choice(chars) for _ in range(digits)) + '=' + password = ''.join(secrets.choice(chars) for _ in range(digits)) if (any(c.islower() for c in password) and any(c.isupper() for c in password) and sum(c.isdigit() for c in password) >= 3): @@ -80,21 +95,22 @@ def gen_password(digits: int = 20) -> str: return password -def get_logger(): - config = { - 'version': 1, - 'disable_existing_loggers': True +logging.config.dictConfig({ + 'version': 1, + 'formatters': { + 'console_formatter': {'format': '%(levelname)s - %(message)s'}, + }, + 'handlers': { + 'console_handler': { + 'class': 'logging.StreamHandler', + 'formatter': 'console_formatter' + }, + }, + 'loggers': { + '__main__': {'level': 'DEBUG', 'handlers': ['console_handler']}, } - logging.config.dictConfig(config) - logger = logging.getLogger() - handler = logging.StreamHandler() - handler.setFormatter(logging.Formatter('%(levelname)s - %(message)s')) - logger.addHandler(handler) - logger.setLevel(logging.INFO) - return logger - - -_LOG = get_logger() +}) +_LOG = logging.getLogger(__name__) def build_parser() -> argparse.ArgumentParser: @@ -112,6 +128,23 @@ def build_parser() -> argparse.ArgumentParser: INIT_VAULT_ACTION, help='Enables secret engine and crated a necessary token in Vault' ) + set_meta_parser = sub_parsers.add_parser( + SET_META_REPOS_ACTION, + help='Sets rules metadata gitlab repositories to vault' + ) + + class MetaAccessType: + def __call__(self, item: str) -> tuple[str, str]: + res = item.strip().split(':', maxsplit=1) + if len(res) != 2: + raise ValueError('Invalid value. Must be :') + return res[0], res[1] + + set_meta_parser.add_argument( + '--repositories', nargs='+', required=True, type=MetaAccessType(), + help='List of repositories to set for meta: ' + '--repositories : :' + ) _ = sub_parsers.add_parser( CREATE_BUCKETS_ACTION, help='Creates necessary buckets in Minio' @@ -120,10 +153,32 @@ def build_parser() -> argparse.ArgumentParser: UPDATE_API_GATEWAY_MODELS_ACTION, help='Regenerates API Gateway models from existing pydantic validators' ) + _ = sub_parsers.add_parser( + SHOW_PERMISSIONS_ACTION, + help='Dumps existing permissions to stdout. ' + 'By default, dumps only user permission. ' + 'Use flags to dump admin permissions as well ' + ) + _ = sub_parsers.add_parser( + INIT_ACTION, + help='Creates system user and sets up some base settings' + ) + _ = sub_parsers.add_parser( + GENERATE_OPENAPI_ACTION, + help='Generates Open API spec for Rule Engine API' + ) init_xlsx_cli_parser(sub_parsers.add_parser( PARSE_XLSX_STANDARD_ACTION, help='Parses Custom Core\'s xlsx with standards' )) + init_parser_rule_source_cli_parser(sub_parsers.add_parser( + PARSE_RULE_SOURCE_ACTION, + help='Parses Rule source and extracts some data' + )) + init_rules_table_generator_cli_parser(sub_parsers.add_parser( + GENERATE_RULES_TABLE_ACTION, + help='Generates xlsx table with rules data from local dir with rules' + )) parser_run = sub_parsers.add_parser(RUN_ACTION, help='Run on-prem server') parser_run.add_argument( '-g', '--gunicorn', action='store_true', default=False, @@ -133,74 +188,18 @@ def build_parser() -> argparse.ArgumentParser: help='Number of gunicorn workers. Must be specified only ' 'if --gunicorn flag is set' ) + parser_run.add_argument( + '-sw', '--swagger', action='store_true', default=False, + help='Specify the flag is you want to enable swagger' + ) + parser_run.add_argument( + '-swp', '--swagger-prefix', type=str, default=DEFAULT_SWAGGER_PREFIX, + help='Swagger path prefix, (default: %(default)s)' + ) parser_run.add_argument('--host', default=DEFAULT_HOST, type=str, help='IP address where to run the server') parser_run.add_argument('--port', default=DEFAULT_PORT, type=int, help='IP Port to run the server on') - parser_run.add_argument( - '-hours', '--schedule_hours', default=DEFAULT_SCHEDULE_HOURS, type=int, - help='License synchronization schedule (in hours). Default value: ' - '3 hours. To disable synchronization, set this value to 0.') - # ------- - - # env sub-action - env_parser = sub_parsers.add_parser( - ENV_ACTION, help='Sub-group to configure an existing env' - ) - env_sub_parsers = env_parser.add_subparsers( - dest=ENV_ACTION_DEST, required=True, help='Configure an existing env' - ) - env_parser_update_settings = env_sub_parsers.add_parser( - UPDATE_SETTINGS_ENV_ACTION, help='Actualizes existing settings' - ) - env_parser_update_settings.add_argument( - '--rulesets_bucket', type=str, required=False, - help='Reports bucket name to put some settings\' data in. ' - 'If not specified, value from env will be used' - ) - env_parser_update_settings.add_argument( - '--lm_api_link', type=str, required=False, - help='Api link to Custodian license manager' - ) - env_parser_create_system = env_sub_parsers.add_parser( - CREATE_SYSTEM_USER_ENV_ACTION, - help='Creates system role, policy and user in case they do not exist' - ) - env_parser_create_system.add_argument( - '--username', type=str, required=True, - help='Username name of root admin' - ) - env_parser_create_system.add_argument( - '--api_link', type=str, required=False, - help='Link to api of the server. If not specified, ' - 'it will be resolved automatically' - ) - env_parser_create_customer = env_sub_parsers.add_parser( - CREATE_CUSTOMER_ACTION, - help='Creates a standard customer, and its role and policy' - ) - env_parser_create_customer.add_argument( - '--customer_name', type=str, required=True, help='Customer\'s name') - env_parser_create_customer.add_argument( - '--admins', type=str, nargs='+', help='Customer\'s owner(s) emails') - - env_parser_create_user = env_sub_parsers.add_parser( - CREATE_USER_ACTION, - help='Creates user with for given entities' - ) - env_parser_create_user.add_argument( - '--username', type=str, required=True, help='The name of the user') - env_parser_create_user.add_argument( - '--customer_name', type=str, required=True, - help='Customer name to create the user in') - env_parser_create_user.add_argument( - '--tenant_names', type=str, nargs='+', - help='Tenants names to create the user for' - ) - env_parser_create_user.add_argument( - '--role_name', type=str, required=False, default=ADMIN_ROLE, - help='Role name within a customer to give ' - 'to the user (default: %(default)s)') return parser @@ -210,98 +209,276 @@ class ActionHandler(ABC): def is_docker() -> bool: # such a kludge due to different envs that points to on-prem env in # LM and Modular - lm_docker = SERVICE_PROVIDER.environment_service().is_docker() - modular_docker = SERVICE_PROVIDER.modular_client().environment_service(). \ - is_docker() + lm_docker = SP.environment_service.is_docker() + modular_docker = SP.modular_client.environment_service().is_docker() return lm_docker or modular_docker + @staticmethod + def load_api_dr() -> dict: + with open(SRC / DEPLOYMENT_RESOURCES_FILENAME, 'r') as f: + data1 = json.load(f).get(DEFAULT_API_GATEWAY_NAME) or {} + with open(SRC / 'validators' / DEPLOYMENT_RESOURCES_FILENAME, + 'r') as f: + data2 = json.load(f).get(DEFAULT_API_GATEWAY_NAME) or {} + data1['models'] = data2.get('models') or {} + return data1 + @abstractmethod def __call__(self, **kwargs): ... -class InitSubService(ActionHandler): - """ - Dynamic. Just add a method which starts from prefix - """ +class InitVault(ActionHandler): + @staticmethod + def generate_private_key(kty: Literal['EC', 'RSA'] = 'EC', + crv='P-521', size: int = 4096, + ) -> str: + """ + Generates a private key and exports PEM to str encoding it to base64 + :param kty: + :param crv: + :param size: + :return: + """ + from jwcrypto import jwk + match kty: + case 'EC': + key = jwk.JWK.generate(kty=kty, crv=crv) + case _: # RSA + key = jwk.JWK.generate(kty=kty, size=size) + return base64.b64encode(key.export_to_pem(private_key=True, + password=None)).decode() + + def __call__(self): + ssm = SP.ssm_service + if ssm.enable_secrets_engine(): + _LOG.info('Vault engine was enabled') + else: + _LOG.info('Vault engine has been already enabled') + if ssm.get_secret_value(PRIVATE_KEY_SECRET_NAME): + _LOG.info('Token inside Vault already exists. Skipping...') + return - def __init__(self, services: Set[str]): - assert services.issubset(self.available_services) - self._services = services + ssm.create_secret_value( + secret_name=PRIVATE_KEY_SECRET_NAME, + secret_value=self.generate_private_key() + ) + _LOG.info('Private token was generated and set to vault') - @cached_property - def prefix(self) -> str: - return 'init_' - @cached_property - def available_services(self) -> Set[str]: - return { - attr[len(self.prefix):] for attr in dir(self) - if attr.startswith(self.prefix) and callable(getattr(self, attr)) - } +class InitMinio(ActionHandler): + @staticmethod + def buckets() -> tuple[str, ...]: + environment = SP.environment_service + return ( + environment.get_statistics_bucket_name(), + environment.get_rulesets_bucket_name(), + environment.default_reports_bucket_name(), + environment.get_metrics_bucket_name() + ) - def get_method(self, name: str) -> Callable: - """ - For scripting purposes this is exactly what I need - """ - return getattr(self, self.prefix + name, lambda **kwargs: None) + @staticmethod + def create_bucket(name: str) -> None: + client = SP.s3 + if client.bucket_exists(bucket=name): + _LOG.info(f'Bucket {name} already exists') + return + client.create_bucket( + bucket=name, + region=SP.environment_service.aws_region() or 'us-east-1' + ) + _LOG.info(f'Bucket {name} was created') - def init_vault(self, **kwargs): - from exported_module.scripts.init_vault import init_vault as \ - _init_vault - _init_vault() + @staticmethod + def put_lifecycle(name: str, prefix): + days = 7 + _LOG.info(f'Setting {days} days expiration for s3://{name}/{prefix}') + SP.s3.put_path_expiration(bucket=name, key=prefix, days=days) + + def __call__(self): + from services.reports_bucket import ReportsBucketKeysBuilder + + for name in self.buckets(): + self.create_bucket(name) + self.put_lifecycle( + SP.environment_service.default_reports_bucket_name(), + ReportsBucketKeysBuilder.on_demand + ) - def init_minio(self, **kwargs): - from exported_module.scripts.init_minio import init_minio as \ - _init_minio - _init_minio() - def init_mongo(self, **kwargs): - from exported_module.scripts.init_mongo import init_mongo as \ - _init_mongo - _init_mongo() +class InitMongo(ActionHandler): + @staticmethod + def convert_index(key_schema: dict) -> str | list[tuple]: + if len(key_schema) == 1: + _LOG.debug('Only hash key found for the index') + return key_schema[0]['AttributeName'] + elif len(key_schema) == 2: + _LOG.debug('Both hash and range keys found for the index') + result = [None, None] + for key in key_schema: + if key['KeyType'] == 'HASH': + _i = 0 + elif key['KeyType'] == 'RANGE': + _i = 1 + else: + raise ValueError(f'Unknown key type: {key["KeyType"]}') + result[_i] = (key['AttributeName'], pymongo.DESCENDING) + return result + else: + raise ValueError(f'Unknown key schema: {key_schema}') + + @staticmethod + def create_indexes_for_model(model: 'BaseModel'): + table_name = model.Meta.table_name + _LOG.info(f'Creating indexes for {table_name}') + collection = model.mongodb_handler().mongodb.collection(table_name) + collection.drop_indexes() + + hash_key = getattr(model._hash_key_attribute(), 'attr_name', None) + range_key = getattr(model._range_key_attribute(), 'attr_name', None) + _LOG.debug('Creating the main index') + if hash_key and range_key: + collection.create_index([(hash_key, pymongo.ASCENDING), + (range_key, pymongo.ASCENDING)], + name='main') + elif hash_key: + collection.create_index(hash_key, name='main') + else: + _LOG.error(f'Table has no hash_key and range_key') + + indexes = model._get_schema() # GSIs & LSIs, # only PynamoDB 5.2.1+ + gsi = indexes.get('global_secondary_indexes') + lsi = indexes.get('local_secondary_indexes') + if gsi: + for i in gsi: + index_name = i['index_name'] + _LOG.debug(f'Creating index \'{index_name}\'') + collection.create_index( + InitMongo.convert_index(i['key_schema']), + name=index_name + ) + if lsi: + pass # write this part if at least one LSI is used - def __call__(self, **kwargs): - for service in self._services: - method = self.get_method(service) - _LOG.info(f'Initializing {service}') - method(**kwargs) + @staticmethod + def models(): + from modular_sdk.models.application import Application + from modular_sdk.models.customer import Customer + from modular_sdk.models.job import Job as ModularJob + from modular_sdk.models.region import RegionModel + from modular_sdk.models.tenant import Tenant + from modular_sdk.models.tenant_settings import TenantSettings + from modular_sdk.models.parent import Parent + + from models.batch_results import BatchResults + from models.customer_metrics import CustomerMetrics + from models.event import Event + from models.job import Job + from models.job_statistics import JobStatistics + from models.policy import Policy + from models.role import Role + from models.rule import Rule + from models.rule_source import RuleSource + from models.ruleset import Ruleset + from models.scheduled_job import ScheduledJob + from models.setting import Setting + from models.tenant_metrics import TenantMetrics + from models.user import User + + modular_models = [ + Customer, Tenant, Parent, RegionModel, TenantSettings, Application, + ModularJob + ] + custodian_models = [ + Job, ScheduledJob, + Policy, Role, Rule, RuleSource, Ruleset, Setting, + User, Event, BatchResults, TenantMetrics, JobStatistics, + CustomerMetrics, + ] + return modular_models + custodian_models + + def __call__(self): + for model in self.models(): + self.create_indexes_for_model(model) class Run(ActionHandler): @staticmethod - def make_app() -> Bottle: + def make_app(dp_wrapper: DeploymentResourcesApiGatewayWrapper) -> Bottle: """For gunicorn""" - from exported_module.api.deployment_resources_parser import \ - DeploymentResourcesParser - from exported_module.api.app import DynamicAPI - api = DynamicAPI(dr_parser=DeploymentResourcesParser( - SRC / DEPLOYMENT_RESOURCES_FILENAME - )) - return api.app - - def __call__(self, host: str = DEFAULT_HOST, port: str = DEFAULT_PORT, - schedule_hours: int = DEFAULT_SCHEDULE_HOURS, - gunicorn: bool = False, workers: Optional[int] = None): + from onprem.api.app import OnPremApiBuilder + builder = OnPremApiBuilder(dp_wrapper=dp_wrapper) + return builder.build() + + def _resolve_urls(self) -> set[str]: + """ + Builds some additional urls for swagger ui + :return: + """ + urls = {f'http://127.0.0.1:{self._port}'} + try: + with urllib.request.urlopen( + 'http://169.254.169.254/latest/meta-data/public-ipv4', + timeout=1) as resp: + urls.add(f'http://{resp.read().decode()}:{self._port}') + except urllib.error.URLError: + _LOG.warning('Cannot resolve public-ipv4 from instance metadata') + return urls + + def _init_swagger(self, app: Bottle, + dp_wrapper: DeploymentResourcesApiGatewayWrapper, + prefix: str) -> None: + from validators import registry + url = f'http://{self._host}:{self._port}' + urls = self._resolve_urls() + urls.add(url) + _LOG.debug('Generating swagger spec') + generator = OpenApiGenerator( + title='Rule Engine - OpenAPI 3.0', + description='Rule engine rest api', + url=sorted(urls), + stages=dp_wrapper.stage, + version=__version__, + endpoints=registry.iter_all() + ) + if not prefix.startswith('/'): + prefix = f'/{prefix}' + api_doc( + app, + config=generator.generate(), + url_prefix=prefix, + title='Rule engine' + ) + _LOG.info(f'Serving swagger on {urljoin(url, prefix)}') + + def __call__(self, host: str = DEFAULT_HOST, port: int = DEFAULT_PORT, + gunicorn: bool = False, workers: int | None = None, + swagger: bool = False, + swagger_prefix: str = DEFAULT_SWAGGER_PREFIX): + self._host = host + self._port = port + if not gunicorn and workers: - print( - '--workers is ignored because it you are not running Gunicorn') - - from exported_module.api.license_sync import ensure_license_sync_job - from helpers.constants import ENV_SERVICE_MODE, DOCKER_SERVICE_MODE - if os.getenv(ENV_SERVICE_MODE) != DOCKER_SERVICE_MODE: - print(f'Env \'{ENV_SERVICE_MODE}\' is not equal to ' - f'\'{DOCKER_SERVICE_MODE}\' but You are executing the ' - f'on-prem server. Setting ' - f'{ENV_SERVICE_MODE}={DOCKER_SERVICE_MODE} forcefully') - os.environ[ENV_SERVICE_MODE] = DOCKER_SERVICE_MODE - - app = self.make_app() - SERVICE_PROVIDER.ap_job_scheduler().start() - ensure_license_sync_job(schedule_hours) + _LOG.warning( + '--workers is ignored because you are not running Gunicorn' + ) + + from onprem.api.cron_jobs import ensure_all + if os.getenv(CAASEnv.SERVICE_MODE) != DOCKER_SERVICE_MODE: + os.environ[CAASEnv.SERVICE_MODE] = DOCKER_SERVICE_MODE + + dr_wrapper = DeploymentResourcesApiGatewayWrapper(self.load_api_dr()) + app = self.make_app(dr_wrapper) + if swagger: + self._init_swagger(app, dr_wrapper, swagger_prefix) + del dr_wrapper + + SP.ap_job_scheduler.start() + ensure_all() + # ensure_retry_job() if gunicorn: workers = workers or DEFAULT_NUMBER_OF_WORKERS - from exported_module.api.app_gunicorn import \ + from onprem.api.app_gunicorn import \ CustodianGunicornApplication options = { 'bind': f'{host}:{port}', @@ -313,15 +490,23 @@ def __call__(self, host: str = DEFAULT_HOST, port: str = DEFAULT_PORT, class UpdateApiGatewayModels(ActionHandler): + """ + Updates ./validators/deployment_resources.json and + ./deployment_resources.json + """ @property def validators_module(self) -> str: return 'validators' @property - def deployment_resources_file(self) -> Path: + def models_deployment_resources(self) -> Path: return SRC / self.validators_module / DEPLOYMENT_RESOURCES_FILENAME + @property + def mail_deployment_resources(self) -> Path: + return SRC / DEPLOYMENT_RESOURCES_FILENAME + @property def custodian_api_gateway_name(self) -> str: return "custodian-as-a-service-api" @@ -338,338 +523,181 @@ def custodian_api_definition(self) -> dict: } def __call__(self, **kwargs): - from validators.request_validation import ALL_MODELS_WITHOUT_GET - from validators.response_validation import ALL_MODELS + from validators import registry api_def = self.custodian_api_definition - for model in set(ALL_MODELS_WITHOUT_GET) | set(ALL_MODELS): + for model in registry.iter_models(without_get=True): + schema = model.model_json_schema() + dereference_json(schema) + schema.pop('$defs', None) api_def[self.custodian_api_gateway_name]['models'].update({ model.__name__: { "content_type": "application/json", - "schema": model.schema() + "schema": schema } }) - - with open(self.deployment_resources_file, 'w') as file: - json.dump(api_def, file, indent=2) - _LOG.info(f'{self.deployment_resources_file} has been updated') - - -class UpdateSettings(ActionHandler): - - @cached_property - def access_data_lm(self) -> dict: - from services.setting_service import KEY_ACCESS_DATA_LM - return { - "name": KEY_ACCESS_DATA_LM, - "value": { - "host": None, - "port": None, - "version": "1" - } - } - - @cached_property - def system_customer_name(self) -> dict: - from services.setting_service import KEY_SYSTEM_CUSTOMER - return { - "name": KEY_SYSTEM_CUSTOMER, - "value": "CUSTODIAN_SYSTEM" - } - - @cached_property - def report_date_marker(self) -> dict: - from services.setting_service import KEY_REPORT_DATE_MARKER - return { - "name": KEY_REPORT_DATE_MARKER, - "value": { - "last_week_date": (datetime.today() + relativedelta( - weekday=SU(-1))).date().isoformat(), - "current_week_date": (datetime.today() + relativedelta( - weekday=SU(0))).date().isoformat() - } - } - - def set_access_data_lm_setting(self, lm_api_link, lm_api_port=443): - from models.setting import Setting - from modular_sdk.services.impl.maestro_credentials_service import \ - AccessMeta - setting = self.access_data_lm - model = AccessMeta.from_dict({}) - model.update_host(host=lm_api_link) - setting['value'] = model.dict() - Setting(**setting).save() - - def set_system_customer_name_setting(self): - from models.setting import Setting - Setting(**self.system_customer_name).save() - - def set_report_date_marker_setting(self): - from models.setting import Setting - Setting(**self.report_date_marker).save() - - def __call__(self, rulesets_bucket: Optional[str] = None, - lm_api_link: Optional[str] = None): - if lm_api_link: - _LOG.info('LM API link was given. Setting lm access data') - self.set_access_data_lm_setting(lm_api_link) - _LOG.info('Setting system customer name') - self.set_system_customer_name_setting() - _LOG.info('Setting report date marker') - self.set_report_date_marker_setting() - - -class EntitiesRelatedActions: - class RoleItem(TypedDict): - customer: Optional[str] - expiration: Optional[str] - name: Optional[str] - policies: List[str] - # resource: List[str] # not used - - class PolicyItem(TypedDict): - customer: Optional[str] - name: Optional[str] - permissions: List - - @property - def blank_role(self) -> RoleItem: - return { - "customer": None, - "expiration": None, - "name": None, - "policies": [], - # "resource": ["*"] - } - - @property - def blank_policy(self) -> PolicyItem: - return { - "customer": None, - "name": None, - "permissions": [] - } - - @staticmethod - def create_customer(customer_name: str, - admins: Optional[List[str]] = None): - """ - Creates a customer with given params. Is the customer already exists, - the creation will be skipped, no attributes will be changed. - """ - _service = SERVICE_PROVIDER.modular_client().customer_service() - if _service.get(customer_name): - _LOG.warning(f"\'{customer_name}'\' customer already " - f"exists. His attributes won`t be changed") - return - customer = Customer( - name=customer_name, - display_name=customer_name.title().replace('_', ' '), - admins=admins or [] - ) - customer.save() - _LOG.info(f'Customer "{customer_name}" created..') - - def create_policy(self, customer_name: str, policy_name: str, - permissions: list): - from models.policy import Policy - policy = self.blank_policy - policy['customer'] = customer_name - policy['name'] = policy_name - policy['permissions'].extend(permissions) - Policy(**policy).save() - _LOG.info(f'Policy "{policy_name}" was created') - - def create_role(self, customer_name: str, role_name: str, - policy_names: list): - from models.role import Role - role = self.blank_role - - role['customer'] = customer_name - role['name'] = role_name - role['policies'].extend(policy_names) - role['expiration'] = utc_iso(utc_datetime() + timedelta(days=6 * 30)) - Role(**role).save() - _LOG.info(f'Role "{role_name}" was created') - - @staticmethod - def create_user(username: str, customer_name: str, role_name: str, - tenants: Optional[List[str]] = None - ) -> Tuple[str, Optional[str]]: - user_service = SERVICE_PROVIDER.user_service() - - if user_service.is_user_exists(username): - _LOG.warning(f'User with username {username} already exists. ' - f'Skipping...') - return username, None - - password = gen_password() - - user_service.save(username=username, password=password, - customer=customer_name, role=role_name, - tenants=tenants) - _LOG.info(f'User \'{username}\' with customer \'{customer_name}\' ' - f'was created') - return username, password - - -class CreateSystemUser(EntitiesRelatedActions, ActionHandler): - - def create_system_user(self, username: str, role_name: str - ) -> Tuple[Optional[str], Optional[str]]: - from helpers.system_customer import SYSTEM_CUSTOMER - user_service = SERVICE_PROVIDER.user_service() - if user_service.is_system_user_exists(): - system_username = user_service.get_system_user() - _LOG.warning(f'System user already exists. ' - f'It\'s name: \'{system_username}\'. Skipping...') - return None, None - else: - return self.create_user( - username=username, - customer_name=SYSTEM_CUSTOMER, - role_name=role_name - ) - - def resolve_api_link(self, - api_name: Optional[str] = DEFAULT_API_GATEWAY_NAME, - ) -> Optional[str]: - if self.is_docker(): - _LOG.warning('Not going to try to resolve api link on on-prem. ' - 'Default will do') - return DEFAULT_ON_PREM_API_LINK - client = boto3.client(service_name='apigateway') - rest_apis = client.get_rest_apis() - api = next( - (api for api in rest_apis.get('items') if api['name'] == api_name), - None - ) + path = self.models_deployment_resources + _LOG.info(f'Updating {path}') + with open(path, 'w') as file: + json.dump(api_def, file, indent=2, sort_keys=True) + _LOG.info(f'{path} has been updated') + + # here we update api gateway inside main deployment resources. + # We don't remove existing endpoints, only add new in case they are + # defined in RequestModelRegistry and are absent inside deployment + # resources. Also, we update request and response models. Default + # lambda is configuration-api-handler. Change it if it's wrong + path = self.mail_deployment_resources + _LOG.info(f'Updating {path}') + with open(path, 'r') as file: + deployment_resources = json.load(file) + api = deployment_resources.get(self.custodian_api_gateway_name) if not api: - _LOG.warning('Could not resolve api link') + _LOG.warning('Api gateway not found in deployment_resources') return - rest_api_id = api['id'] - stages = client.get_stages(restApiId=rest_api_id).get('item') - stage = '' - if len(stages) == 0: - _LOG.warning('Api gateway has no stages') + resources = api.setdefault('resources', {}) + for item in registry.iter_all(): + # if endpoint & method are defined, just update models. + # otherwise add configuration + data = resources.setdefault(item.path, { + 'policy_statement_singleton': True, + 'enable_cors': True, + }).setdefault(item.method.value, { + 'integration_type': 'lambda', + 'enable_proxy': True, + 'lambda_alias': '${lambdas_alias_name}', + 'authorization_type': 'authorizer' if item.auth else 'NONE', + 'lambda_name': 'caas-configuration-api-handler', + }) + data.pop('method_request_models', None) + data.pop('responses', None) + data.pop('method_request_parameters', None) + if model := item.request_model: + match item.method: + case HTTPMethod.GET: + params = {} + for name, info in model.model_fields.items(): + params[ + f'method.request.querystring.{name}'] = info.is_required() + data['method_request_parameters'] = params + case _: + data['method_request_models'] = { + 'application/json': model.__name__ + } + responses = [] + for st, m, description in item.responses: + resp = {'status_code': str(st.value)} + if m: + resp['response_models'] = {'application/json': m.__name__} + responses.append(resp) + data['responses'] = responses + + with open(path, 'w') as file: + json.dump(deployment_resources, file, indent=2) + _LOG.info(f'{path} has been updated') + + +class InitAction(ActionHandler): + def __call__(self): + from models.setting import Setting + if not Setting.get_nullable(SettingKey.SYSTEM_CUSTOMER): + _LOG.info('Setting system customer name') + Setting( + name=CAASEnv.SYSTEM_CUSTOMER_NAME, + value=SYSTEM_CUSTOMER + ).save() + if not Setting.get_nullable(SettingKey.REPORT_DATE_MARKER): + _LOG.info('Setting report date marker') + Setting( + name=SettingKey.REPORT_DATE_MARKER, + value={ + 'last_week_date': (datetime.today() + + relativedelta( + weekday=SU(-1))).date().isoformat(), + 'current_week_date': (datetime.today() + + relativedelta(weekday=SU( + 0))).date().isoformat() + } + ).save() + users_client = SP.users_client + if not users_client.get_user_by_username(SYSTEM_USER): + _LOG.info('Creating a system user') + password = os.getenv(CAASEnv.SYSTEM_USER_PASSWORD) + from_env = bool(password) + if not from_env: + password = gen_password() + + users_client.signup_user( + username=SYSTEM_USER, + password=password, + customer=SYSTEM_CUSTOMER, + ) + if not from_env: + print(f'System ({SYSTEM_USER}) password: {password}') + else: + print(f'System ({SYSTEM_USER}) was created') else: - stage = stages[0]['stageName'] - return API_GATEWAY_LINK.format( - id=rest_api_id, - region=client.meta.region_name, - stage=stage - ) - - def __call__(self, username: str, api_link: Optional[str] = None): - from helpers.system_customer import SYSTEM_CUSTOMER - access_control_service = SERVICE_PROVIDER.access_control_service() - _LOG.info(f'Creating \'{SYSTEM_CUSTOMER}\' customer...') - # self.create_customer(SYSTEM_CUSTOMER) - - _LOG.info('Creating policy...') - self.create_policy( - customer_name=SYSTEM_CUSTOMER, - policy_name=SYSTEM_POLICY, - permissions=list(access_control_service.all_permissions) - ) - - _LOG.info('Creating role...') - self.create_role( - customer_name=SYSTEM_CUSTOMER, role_name=SYSTEM_ROLE, - policy_names=[SYSTEM_POLICY, ] - ) - - _LOG.info('Creating user...') - - username, password = self.create_system_user( - username=username, role_name=SYSTEM_ROLE) - if not api_link: - _LOG.info('Api link was not specified. Resolving the api link') - api_link = self.resolve_api_link() - _LOG.info('Environment was successfully configured! Use commands to ' - 'configure CLI:') - print(C7N_CONFIGURE_COMMAND.format(api_link=api_link or '[api_link]')) - print(C7N_LOGIN_COMMAND.format(username=username, - password=password or '[password]')) - - -class CreateCustomer(EntitiesRelatedActions, ActionHandler): - """ - Creates an admin customer and his admin role and policy and additionally - creates so-called user policy and user role within the customer which - contains a more restricted set of permissions - """ - - def __call__(self, customer_name: str, admins: List[str]): - acs = SERVICE_PROVIDER.access_control_service() - _LOG.info(f'Creating admin customer "{customer_name}"') - self.create_customer(customer_name, admins) - - _LOG.info('Creating admin policy') - self.create_policy( - customer_name=customer_name, policy_name=ADMIN_POLICY, - permissions=list(acs.admin_permissions) + _LOG.info('System user already exists') + _LOG.info('Done') + + +class GenerateOpenApi(ActionHandler): + def __call__(self): + from validators import registry + data = self.load_api_dr() + generator = OpenApiGenerator( + title='Rule Engine - OpenAPI 3.0', + description='Rule engine rest api', + url=f'http://{DEFAULT_HOST}:{DEFAULT_PORT}', + stages=DeploymentResourcesApiGatewayWrapper(data).stage, + version=__version__, + endpoints=registry.iter_all() ) - - _LOG.info('Creating admin role') - self.create_role( - customer_name=customer_name, - role_name=ADMIN_ROLE, - policy_names=[ADMIN_POLICY, ] + json.dump(generator.generate(), sys.stdout, separators=(',', ':')) + + +class ShowPermissions(ActionHandler): + def __call__(self): + json.dump(sorted(Permission.iter_enabled()), sys.stdout, indent=4) + + +class SetMetaRepos(ActionHandler): + def __call__(self, repositories: list[tuple[str, str]]): + ssm = SP.ssm_service + ssm.create_secret_value( + secret_name=DEFAULT_RULES_METADATA_REPO_ACCESS_SSM_NAME, + secret_value=[ + { + 'project': i[0], + 'ref': 'main', + 'secret': i[1], + 'url': 'https://git.epam.com' + } for i in repositories + ] ) + _LOG.info('Repositories were set') - _LOG.info('Creating user policy') - self.create_policy( - customer_name=customer_name, - policy_name=USER_POLICY, - permissions=list(acs.user_permissions) - ) - _LOG.info('Create user role') - self.create_role( - customer_name=customer_name, - role_name=USER_ROLE, - policy_names=[USER_POLICY] - ) - -class CreateTenant(ActionHandler): - - def __call__(self, **kwargs): - pass - - -class CreateUser(EntitiesRelatedActions, ActionHandler): - def __call__(self, username: str, customer_name: str, - tenant_names: List[str], role_name: str = USER_ROLE): - username, password = self.create_user( - username=username, - customer_name=customer_name, - role_name=role_name, - tenants=tenant_names - ) - print(C7N_LOGIN_COMMAND.format(username=username, - password=password or '[password]')) - - -def main(args: Optional[List[str]] = None): +def main(args: list[str] | None = None): parser = build_parser() arguments = parser.parse_args(args) key = tuple( getattr(arguments, dest) for dest in ALL_NESTING if hasattr(arguments, dest) ) - mapping: Dict[Tuple[str, ...], Union[Callable, ActionHandler]] = { - (INIT_VAULT_ACTION,): InitSubService({'vault'}), - (CREATE_INDEXES_ACTION,): InitSubService({'mongo'}), - (CREATE_BUCKETS_ACTION,): InitSubService({'minio'}), + mapping: dict[tuple[str, ...], Callable | ActionHandler] = { + (INIT_VAULT_ACTION,): InitVault(), + (SET_META_REPOS_ACTION,): SetMetaRepos(), + (CREATE_INDEXES_ACTION,): InitMongo(), + (CREATE_BUCKETS_ACTION,): InitMinio(), + (GENERATE_OPENAPI_ACTION,): GenerateOpenApi(), (RUN_ACTION,): Run(), (PARSE_XLSX_STANDARD_ACTION,): parse_xlsx_standard, + (PARSE_RULE_SOURCE_ACTION,): parse_rule_source, + (GENERATE_RULES_TABLE_ACTION,): generate_rules_table, (UPDATE_API_GATEWAY_MODELS_ACTION,): UpdateApiGatewayModels(), - (ENV_ACTION, UPDATE_SETTINGS_ENV_ACTION): UpdateSettings(), - (ENV_ACTION, CREATE_SYSTEM_USER_ENV_ACTION): CreateSystemUser(), - (ENV_ACTION, CREATE_CUSTOMER_ACTION): CreateCustomer(), - (ENV_ACTION, CREATE_USER_ACTION): CreateUser() - + (SHOW_PERMISSIONS_ACTION,): ShowPermissions(), + (INIT_ACTION,): InitAction() } func: Callable = mapping.get(key) or (lambda **kwargs: _LOG.error('Hello')) for dest in ALL_NESTING: diff --git a/src/models/__init__.py b/src/models/__init__.py index e69de29bb..669f789b1 100644 --- a/src/models/__init__.py +++ b/src/models/__init__.py @@ -0,0 +1,47 @@ +import os + +from modular_sdk.connections.mongodb_connection import MongoDBConnection +from modular_sdk.models.pynamodb_extension.base_model import \ + ABCMongoDBHandlerMixin, \ + RawBaseModel, RawBaseGSI +from modular_sdk.models.pynamodb_extension.base_safe_update_model import \ + BaseSafeUpdateModel as ModularSafeUpdateModel +from modular_sdk.models.pynamodb_extension.pynamodb_to_pymongo_adapter import \ + PynamoDBToPyMongoAdapter + +from helpers.constants import CAASEnv, DOCKER_SERVICE_MODE + +ADAPTER = None +MONGO_CLIENT = None +if os.getenv(CAASEnv.SERVICE_MODE) == DOCKER_SERVICE_MODE: + uri = os.getenv(CAASEnv.MONGO_URI) + db = os.getenv(CAASEnv.MONGO_DATABASE) + assert uri and db, 'Mongo uri and db must be specified for on-prem' + ADAPTER = PynamoDBToPyMongoAdapter( + mongodb_connection=MongoDBConnection( + mongo_uri=uri, + default_db_name=db + ) + ) + MONGO_CLIENT = ADAPTER.mongodb.client + + +class CustodianMongoDBHandlerMixin(ABCMongoDBHandlerMixin): + @classmethod + def mongodb_handler(cls): + if not cls._mongodb: + cls._mongodb = ADAPTER + return cls._mongodb + + +class BaseModel(CustodianMongoDBHandlerMixin, RawBaseModel): + pass + + +class BaseGSI(CustodianMongoDBHandlerMixin, RawBaseGSI): + pass + + +class BaseSafeUpdateModel(CustodianMongoDBHandlerMixin, + ModularSafeUpdateModel): + pass diff --git a/src/models/batch_results.py b/src/models/batch_results.py index 82bce5ff7..1f41f8da2 100644 --- a/src/models/batch_results.py +++ b/src/models/batch_results.py @@ -1,12 +1,11 @@ import os -from typing import Dict, Set from pynamodb.attributes import UnicodeAttribute, MapAttribute from pynamodb.indexes import AllProjection -from helpers.constants import ENV_VAR_REGION -from models.modular import BaseModel, BaseGSI -from models.pynamodb_extension.index import ThreadedGSI +from helpers.constants import CAASEnv, JobState +from helpers.time_helper import utc_iso +from models import BaseModel, BaseGSI BR_ID = 'id' BR_JOB_ID = 'jid' @@ -25,100 +24,57 @@ BR_CREDENTIALS_KEY = 'cr' -class JobIdIndex(BaseGSI): +class CustomerNameSubmittedAtIndex(BaseGSI): class Meta: - index_name = f'{BR_JOB_ID}-index' - read_capacity_units = 1 - write_capacity_units = 1 - projection = AllProjection() - - job_id = UnicodeAttribute(hash_key=True, attr_name=BR_JOB_ID) - - -class CustomerNameRegistrationStartIndex(BaseGSI): - class Meta: - index_name = f'{BR_CUSTOMER_NAME}-{BR_EVENT_REGISTRATION_START}-index' + index_name = f'{BR_CUSTOMER_NAME}-{BR_JOB_SUBMITTED_AT}-index' read_capacity_units = 1 write_capacity_units = 1 projection = AllProjection() customer_name = UnicodeAttribute(hash_key=True, attr_name=BR_CUSTOMER_NAME) - registration_start = UnicodeAttribute( - range_key=True, attr_name=BR_EVENT_REGISTRATION_START - ) + submitted_at = UnicodeAttribute(attr_name=BR_JOB_SUBMITTED_AT) -class TenantNameRegistrationStartIndex(ThreadedGSI): +class TenantNameSubmittedAtIndex(BaseGSI): class Meta: - index_name = f'{BR_TENANT_NAME}-{BR_EVENT_REGISTRATION_START}-index' + index_name = f'{BR_TENANT_NAME}-{BR_JOB_SUBMITTED_AT}-index' read_capacity_units = 1 write_capacity_units = 1 projection = AllProjection() tenant_name = UnicodeAttribute(hash_key=True, attr_name=BR_TENANT_NAME) - registration_start = UnicodeAttribute( - range_key=True, attr_name=BR_EVENT_REGISTRATION_START - ) - - -class CustomerNameStoppedIndex(ThreadedGSI): - class Meta: - index_name = f'{BR_CUSTOMER_NAME}-{BR_JOB_STOPPED_AT}-index' - read_capacity_units = 1 - write_capacity_units = 1 - projection = AllProjection() - - customer_name = UnicodeAttribute(hash_key=True, attr_name=BR_CUSTOMER_NAME) - stopped_at = UnicodeAttribute(range_key=True, attr_name=BR_JOB_STOPPED_AT) - - -class CloudIdRegistrationStartIndex(ThreadedGSI): - class Meta: - index_name = f'{BR_CLOUD_ID}-{BR_EVENT_REGISTRATION_START}-index' - read_capacity_units = 1 - write_capacity_units = 1 - projection = AllProjection() - - cloud_id = UnicodeAttribute(hash_key=True, attr_name=BR_CLOUD_ID) - registration_start = UnicodeAttribute( - range_key=True, attr_name=BR_EVENT_REGISTRATION_START - ) - # # todo resolve this use-case - # submitted_at = UnicodeAttribute(range_key=True, - # attr_name=BR_JOB_SUBMITTED_AT) + submitted_at = UnicodeAttribute(attr_name=BR_JOB_SUBMITTED_AT) class BatchResults(BaseModel): class Meta: table_name = "CaaSBatchResults" - region = os.environ.get(ENV_VAR_REGION) + region = os.environ.get(CAASEnv.AWS_REGION) id = UnicodeAttribute(hash_key=True, attr_name=BR_ID) # uuid job_id = UnicodeAttribute(attr_name=BR_JOB_ID, null=True) # Batch job id rules = MapAttribute(default=dict, attr_name=BR_RULES_TO_SCAN, null=True) credentials_key = UnicodeAttribute(null=True, attr_name=BR_CREDENTIALS_KEY) - status = UnicodeAttribute(null=True, + status = UnicodeAttribute(default=JobState.SUBMITTED.value, attr_name=BR_STATUS) # the same as in CaaSJobs reason = UnicodeAttribute(null=True, attr_name=BR_FAILURE_REASON) cloud_identifier = UnicodeAttribute(null=True, attr_name=BR_CLOUD_ID) # AWS:account_id AZURE:subscription_id - tenant_name = UnicodeAttribute(null=True, attr_name=BR_TENANT_NAME) - customer_name = UnicodeAttribute(null=True, attr_name=BR_CUSTOMER_NAME) + tenant_name = UnicodeAttribute(attr_name=BR_TENANT_NAME) + customer_name = UnicodeAttribute(attr_name=BR_CUSTOMER_NAME) registration_start = UnicodeAttribute(null=True, attr_name=BR_EVENT_REGISTRATION_START) registration_end = UnicodeAttribute(null=True, attr_name=BR_EVENT_REGISTRATION_END) - submitted_at = UnicodeAttribute(null=True, attr_name=BR_JOB_SUBMITTED_AT) + submitted_at = UnicodeAttribute(attr_name=BR_JOB_SUBMITTED_AT, + default=utc_iso) stopped_at = UnicodeAttribute(null=True, attr_name=BR_JOB_STOPPED_AT) - job_id_index = JobIdIndex() - cid_rs_index = CloudIdRegistrationStartIndex() - tn_rs_index = TenantNameRegistrationStartIndex() - cn_rs_index = CustomerNameRegistrationStartIndex() - cn_jsta_index = CustomerNameStoppedIndex() + customer_name_submitted_at_index = CustomerNameSubmittedAtIndex() + tenant_name_submitted_at_index = TenantNameSubmittedAtIndex() - def regions_to_rules(self) -> Dict[str, Set[str]]: + def regions_to_rules(self) -> dict[str, set[str]]: """ Retrieves rules attribute from self and transforms in to a mapping: { @@ -132,7 +88,7 @@ def regions_to_rules(self) -> Dict[str, Set[str]]: ref.setdefault(region, set()).update(rules) return ref - def rules_to_regions(self) -> Dict[str, Set[str]]: + def rules_to_regions(self) -> dict[str, set[str]]: """ Retrieves rules attribute from self and transforms in to a mapping: { diff --git a/src/models/credentials_manager.py b/src/models/credentials_manager.py deleted file mode 100644 index 859d7f67f..000000000 --- a/src/models/credentials_manager.py +++ /dev/null @@ -1,61 +0,0 @@ -import os - -from pynamodb.attributes import ( - UnicodeAttribute, BooleanAttribute, NumberAttribute -) - -from pynamodb.indexes import AllProjection -from models.modular import BaseModel, BaseGSI -from helpers.constants import ENV_VAR_REGION - -CM_CLOUD_IDENTIFIER = 'cid' -CM_CLOUD = 'c' -CM_ROLE_ARN = 'ra' -CM_ENABLED = 'e' -CM_EXPIRATION = 'ex' -CM_CREDENTIALS_KEY = 'ck' -CM_CUSTOMER = 'cn' -CM_TENANT = 'tn' - - -class TenantCloudIdentifierIndex(BaseGSI): - class Meta: - index_name = f'{CM_TENANT}-{CM_CLOUD}-index' - read_capacity_units = 1 - write_capacity_units = 1 - projection = AllProjection() - - tenant = UnicodeAttribute(hash_key=True, attr_name=CM_TENANT) - cloud = UnicodeAttribute(range_key=True, attr_name=CM_CLOUD) - - -class CustomerCloudIdentifierIndex(BaseGSI): - class Meta: - index_name = f'{CM_CUSTOMER}-{CM_CLOUD}-index' - read_capacity_units = 1 - write_capacity_units = 1 - projection = AllProjection() - - customer = UnicodeAttribute(hash_key=True, attr_name=CM_CUSTOMER) - cloud = UnicodeAttribute(range_key=True, attr_name=CM_CLOUD) - - -class CredentialsManager(BaseModel): - class Meta: - table_name = 'CaaSCredentialsManager' - region = os.environ.get(ENV_VAR_REGION) - - cloud_identifier = UnicodeAttribute(hash_key=True, - attr_name=CM_CLOUD_IDENTIFIER) - cloud = UnicodeAttribute(range_key=True, attr_name=CM_CLOUD) - trusted_role_arn = UnicodeAttribute(null=True, attr_name=CM_ROLE_ARN) - enabled = BooleanAttribute(null=True, default=False, attr_name=CM_ENABLED) - expiration = NumberAttribute(null=True, - attr_name=CM_EXPIRATION) # timestamp - credentials_key = UnicodeAttribute(null=True, attr_name=CM_CREDENTIALS_KEY) - - customer = UnicodeAttribute(null=True, attr_name=CM_CUSTOMER) - tenant = UnicodeAttribute(null=True, attr_name=CM_TENANT) - - customer_cloud_identifier_index = CustomerCloudIdentifierIndex() - tenant_cloud_identifier_index = TenantCloudIdentifierIndex() diff --git a/src/models/customer_metrics.py b/src/models/customer_metrics.py index 486ab7761..297a26441 100644 --- a/src/models/customer_metrics.py +++ b/src/models/customer_metrics.py @@ -3,8 +3,8 @@ from pynamodb.attributes import UnicodeAttribute, MapAttribute, ListAttribute from pynamodb.indexes import AllProjection -from helpers.constants import ENV_VAR_REGION -from models.modular import BaseModel, BaseGSI +from helpers.constants import CAASEnv +from models import BaseModel, BaseGSI TM_ID_ATTR = 'id' TM_DATE_ATTR = 'd' @@ -28,7 +28,7 @@ class Meta: class CustomerMetrics(BaseModel): class Meta: table_name = 'CaaSCustomerMetrics' - region = os.environ.get(ENV_VAR_REGION) + region = os.environ.get(CAASEnv.AWS_REGION) id = UnicodeAttribute(hash_key=True, attr_name=TM_ID_ATTR) customer = UnicodeAttribute(attr_name=TM_CUSTOMER_ATTR) date = UnicodeAttribute(attr_name=TM_DATE_ATTR) # ISO8601 diff --git a/src/models/event.py b/src/models/event.py index d86f4a347..b4eb70e45 100644 --- a/src/models/event.py +++ b/src/models/event.py @@ -3,8 +3,8 @@ from pynamodb.attributes import UnicodeAttribute, MapAttribute, \ ListAttribute, NumberAttribute -from helpers.constants import ENV_VAR_REGION -from models.modular import BaseModel +from helpers.constants import CAASEnv +from models import BaseModel E_PARTITION_ATTR = 'p' E_TIMESTAMP_ATTR = 't' @@ -20,7 +20,7 @@ class Event(BaseModel): class Meta: table_name = "CaaSEvents" - region = os.environ.get(ENV_VAR_REGION) + region = os.environ.get(CAASEnv.AWS_REGION) partition = NumberAttribute(hash_key=True, attr_name=E_PARTITION_ATTR) # random 0..N diff --git a/src/models/event_stats.py b/src/models/event_stats.py deleted file mode 100644 index a7ac7b121..000000000 --- a/src/models/event_stats.py +++ /dev/null @@ -1,34 +0,0 @@ -import os - -from pynamodb.attributes import UnicodeAttribute, MapAttribute - -from helpers.constants import ENV_VAR_REGION -from helpers.time_helper import utc_iso -from models.modular import BaseModel - -ES_TENANT = 't' -ES_TIMESTAMP_START = 'ets' -ES_TIMESTAMP_END = 'ete' -ES_STATISTICS = 'st' -ES_COLLECTED_AT = 'c' - -ES_STATUS_COLLECTED = 'COL' - - -class EventStatistics(BaseModel): - """ - Persistence Model that represents registry of events. - """ - - class Meta: - table_name = "CaaSEventStatistics" - region = os.environ.get(ENV_VAR_REGION) - - tenant = UnicodeAttribute(hash_key=True, attr_name=ES_TENANT) - start = UnicodeAttribute( - range_key=True, attr_name=ES_TIMESTAMP_START - ) # epoch timestamp - end = UnicodeAttribute(attr_name=ES_TIMESTAMP_END) - - statistics = MapAttribute(default=dict, attr_name=ES_STATISTICS) - collected_at = UnicodeAttribute(default=utc_iso, attr_name=ES_COLLECTED_AT) diff --git a/src/models/job.py b/src/models/job.py index 9760265dc..1d6ae3f6a 100644 --- a/src/models/job.py +++ b/src/models/job.py @@ -3,62 +3,87 @@ from pynamodb.attributes import UnicodeAttribute, ListAttribute, TTLAttribute from pynamodb.indexes import AllProjection -from helpers.constants import ENV_VAR_REGION -from models.modular import BaseModel, BaseGSI +from helpers.constants import CAASEnv, JobState +from helpers.time_helper import utc_iso +from models import BaseModel, BaseGSI -TENANT_DISPLAY_NAME_ATTR = 'tenant_display_name' -SUBMITTED_AT_ATTR = 'submitted_at' +JOB_ID = 'i' +JOB_BATCH_JOB_ID = 'b' +JOB_TENANT_NAME = 't' +JOB_CUSTOMER_NAME = 'c' +JOB_STATUS = 's' +JOB_SUBMITTED_AT = 'sa' +JOB_CREATED_AT = 'cr' +JOB_STARTED_AT = 'sta' +JOB_STOPPED_AT = 'sto' +JOB_QUEUE = 'q' +JOB_DEFINITION = 'd' +JOB_OWNER = 'o' +JOB_REGIONS = 'rg' +JOB_RULESETS = 'rs' +JOB_REASON = 'r' +JOB_SCHEDULED_RULE_NAME = 'sr' +JOB_RULES_TO_SCAN = 'ru' +JOB_PLATFORM_ID = 'p' +JOB_TTL = 'ttl' -class TenantDisplayNameSubmittedAtIndex(BaseGSI): +class TenantNameSubmittedAtIndex(BaseGSI): class Meta: - index_name = f'{TENANT_DISPLAY_NAME_ATTR}-{SUBMITTED_AT_ATTR}-index' + index_name = f'{JOB_TENANT_NAME}-{JOB_SUBMITTED_AT}-index' read_capacity_units = 1 write_capacity_units = 1 projection = AllProjection() - tenant_display_name = UnicodeAttribute(hash_key=True) - submitted_at = UnicodeAttribute(range_key=True) + tenant_name = UnicodeAttribute(hash_key=True, attr_name=JOB_TENANT_NAME) + submitted_at = UnicodeAttribute(range_key=True, attr_name=JOB_SUBMITTED_AT) -class CustomerDisplayNameIndex(BaseGSI): +class CustomerNameSubmittedAtIndex(BaseGSI): class Meta: - index_name = "customer-display-name" + index_name = f'{JOB_CUSTOMER_NAME}-{JOB_SUBMITTED_AT}-index' read_capacity_units = 1 write_capacity_units = 1 projection = AllProjection() - customer_display_name = UnicodeAttribute(hash_key=True) - submitted_at = UnicodeAttribute(range_key=True) + customer_name = UnicodeAttribute(hash_key=True, + attr_name=JOB_CUSTOMER_NAME) + submitted_at = UnicodeAttribute(range_key=True, attr_name=JOB_SUBMITTED_AT) class Job(BaseModel): - """ - Model that represents job entity. - """ - class Meta: - table_name = "CaaSJobs" - region = os.environ.get(ENV_VAR_REGION) - - job_id = UnicodeAttribute(hash_key=True) - tenant_display_name = UnicodeAttribute(null=True) - customer_display_name = UnicodeAttribute(null=True) - created_at = UnicodeAttribute(null=True) - started_at = UnicodeAttribute(null=True) - stopped_at = UnicodeAttribute(null=True) - submitted_at = UnicodeAttribute(null=True) - status = UnicodeAttribute(null=True) - job_queue = UnicodeAttribute(null=True) - job_definition = UnicodeAttribute(null=True) - job_owner = UnicodeAttribute(null=True) - scan_regions = ListAttribute(null=True, default=list) - scan_rulesets = ListAttribute(null=True, default=list) - reason = UnicodeAttribute(null=True) - scheduled_rule_name = UnicodeAttribute(null=True) - ttl = TTLAttribute(null=True) - rules_to_scan = ListAttribute(default=list) - platform_id = UnicodeAttribute(null=True) - - customer_display_name_index = CustomerDisplayNameIndex() - tenant_display_name_index = TenantDisplayNameSubmittedAtIndex() + table_name = 'CaaSJobs' + region = os.environ.get(CAASEnv.AWS_REGION) + + id = UnicodeAttribute(hash_key=True, attr_name=JOB_ID) + batch_job_id = UnicodeAttribute(null=True, attr_name=JOB_BATCH_JOB_ID) + tenant_name = UnicodeAttribute(attr_name=JOB_TENANT_NAME) + customer_name = UnicodeAttribute(attr_name=JOB_CUSTOMER_NAME) + + submitted_at = UnicodeAttribute(attr_name=JOB_SUBMITTED_AT, + default=utc_iso) + status = UnicodeAttribute(attr_name=JOB_STATUS, + default=JobState.SUBMITTED.value) + + created_at = UnicodeAttribute(null=True, attr_name=JOB_CREATED_AT) + started_at = UnicodeAttribute(null=True, attr_name=JOB_STARTED_AT) + stopped_at = UnicodeAttribute(null=True, attr_name=JOB_STOPPED_AT) + + queue = UnicodeAttribute(null=True, attr_name=JOB_QUEUE) + definition = UnicodeAttribute(null=True, attr_name=JOB_DEFINITION) + owner = UnicodeAttribute(null=True, attr_name=JOB_OWNER) + + regions = ListAttribute(default=list, attr_name=JOB_REGIONS) + rulesets = ListAttribute(default=list, attr_name=JOB_RULESETS) + reason = UnicodeAttribute(null=True, attr_name=JOB_REASON) + scheduled_rule_name = UnicodeAttribute(null=True, + attr_name=JOB_SCHEDULED_RULE_NAME) + rules_to_scan = ListAttribute(default=list, attr_name=JOB_RULES_TO_SCAN, + of=UnicodeAttribute) + platform_id = UnicodeAttribute(null=True, attr_name=JOB_PLATFORM_ID) + + ttl = TTLAttribute(null=True, attr_name=JOB_TTL) + + customer_name_submitted_at_index = CustomerNameSubmittedAtIndex() + tenant_name_submitted_at_index = TenantNameSubmittedAtIndex() diff --git a/src/models/job_statistics.py b/src/models/job_statistics.py index c0b47575e..721deab0f 100644 --- a/src/models/job_statistics.py +++ b/src/models/job_statistics.py @@ -4,8 +4,8 @@ MapAttribute from pynamodb.indexes import AllProjection -from helpers.constants import ENV_VAR_REGION -from models.modular import BaseModel, BaseGSI +from helpers.constants import CAASEnv +from models import BaseModel, BaseGSI class CustomerNameFromDateIndex(BaseGSI): @@ -26,7 +26,7 @@ class JobStatistics(BaseModel): class Meta: table_name = "CaaSJobStatistics" - region = os.environ.get(ENV_VAR_REGION) + region = os.environ.get(CAASEnv.AWS_REGION) id = UnicodeAttribute(hash_key=True) cloud = UnicodeAttribute() @@ -36,6 +36,8 @@ class Meta: succeeded = NumberAttribute(null=True, default=0) failed = NumberAttribute(null=True, default=0) last_scan_date = UnicodeAttribute(null=True) - tenants = MapAttribute(null=True) + tenants = MapAttribute(default=dict) + reason = MapAttribute(default=dict) + scanned_regions = MapAttribute(default=dict) customer_name_from_date_index = CustomerNameFromDateIndex() diff --git a/src/models/licenses.py b/src/models/licenses.py deleted file mode 100644 index b3b7de587..000000000 --- a/src/models/licenses.py +++ /dev/null @@ -1,40 +0,0 @@ -import os - -from pynamodb.attributes import UnicodeAttribute, ListAttribute, \ - NumberAttribute, MapAttribute, BooleanAttribute - -from helpers.constants import ENV_VAR_REGION -from models.modular import BaseModel - -PERMITTED_ATTACHMENT = 'permitted' -PROHIBITED_ATTACHMENT = 'prohibited' -ALLOWED_ATTACHMENT_MODELS = (PERMITTED_ATTACHMENT, PERMITTED_ATTACHMENT) - - -class AllowanceAttribute(MapAttribute): - time_range = UnicodeAttribute(null=True) - job_balance = NumberAttribute(null=True) - balance_exhaustion_model = UnicodeAttribute(null=True) - - -class EventDriven(MapAttribute): - active = BooleanAttribute(null=True) - quota = NumberAttribute(null=True) # in minutes - last_execution = UnicodeAttribute(null=True) - - -class License(BaseModel): - class Meta: - table_name = 'CaaSLicenses' - region = os.environ.get(ENV_VAR_REGION) - - license_key = UnicodeAttribute(hash_key=True) - # you do not use DynamicMapAttribute - it contains a bug in pynamodb==5.2.1 - # customers = DynamicMapAttribute(default=dict) - customers = MapAttribute(default=dict) - expiration = UnicodeAttribute(null=True) # ISO8601 - ruleset_ids = ListAttribute(null=True, default=list) - latest_sync = UnicodeAttribute(null=True) # ISO8601 - # applied_only_for_descendants = ListAttribute(null=True) - allowance = AllowanceAttribute(null=True, default=dict) - event_driven = EventDriven(default=dict, null=True) diff --git a/src/models/modular/__init__.py b/src/models/modular/__init__.py deleted file mode 100644 index 2ce44df07..000000000 --- a/src/models/modular/__init__.py +++ /dev/null @@ -1,40 +0,0 @@ -import os - -from modular_sdk.models.pynamodb_extension.base_model import ABCMongoDBHandlerMixin, \ - build_mongodb_uri, RawBaseModel, RawBaseGSI -from modular_sdk.models.pynamodb_extension.base_safe_update_model import \ - BaseSafeUpdateModel as ModularSafeUpdateModel -from modular_sdk.models.pynamodb_extension.pynamodb_to_pymongo_adapter import \ - PynamoDBToPyMongoAdapter - -from helpers.constants import ENV_MONGODB_USER, ENV_MONGODB_PASSWORD, \ - ENV_MONGODB_URL, ENV_MONGODB_DATABASE - - -class CustodianMongoDBHandlerMixin(ABCMongoDBHandlerMixin): - @classmethod - def mongodb_handler(cls): - if not cls._mongodb: - from modular_sdk.connections.mongodb_connection import MongoDBConnection - user = os.environ.get(ENV_MONGODB_USER) - password = os.environ.get(ENV_MONGODB_PASSWORD) - url = os.environ.get(ENV_MONGODB_URL) - db = os.environ.get(ENV_MONGODB_DATABASE) - cls._mongodb = PynamoDBToPyMongoAdapter( - mongodb_connection=MongoDBConnection( - build_mongodb_uri(user, password, url), db - ) - ) - return cls._mongodb - - -class BaseModel(CustodianMongoDBHandlerMixin, RawBaseModel): - pass - - -class BaseGSI(CustodianMongoDBHandlerMixin, RawBaseGSI): - pass - - -class BaseSafeUpdateModel(CustodianMongoDBHandlerMixin, ModularSafeUpdateModel): - pass diff --git a/src/models/modular/application.py b/src/models/modular/application.py deleted file mode 100644 index 0cff04420..000000000 --- a/src/models/modular/application.py +++ /dev/null @@ -1,51 +0,0 @@ -from typing import Optional, Dict - -from modular_sdk.models.application import Application -from pydantic import BaseModel - -from helpers.enums import RuleDomain - -Application = Application - - -class CustodianLicensesApplicationMeta(BaseModel): - """ - Application with type 'CUSTODIAN_LICENSES' meta - """ - - class Config: - extra = 'allow' - - awsAid: Optional[str] - azureAid: Optional[str] - googleAid: Optional[str] - kubernetesAid: Optional[str] - awsLk: Optional[str] - azureLk: Optional[str] - googleLk: Optional[str] - kubernetesLk: Optional[str] - - @staticmethod - def _license_key_attr(cloud: str) -> str: - return f'{cloud.lower()}Lk' - - @staticmethod - def _access_application_id_attr(cloud: str) -> str: - return f'{cloud.lower()}Aid' - - def update_access_application_id(self, cloud: str, aid: str): - setattr(self, self._access_application_id_attr(cloud), aid) - - def update_license_key(self, cloud: str, lk: Optional[str] = None): - setattr(self, self._license_key_attr(cloud), lk) - - def license_key(self, cloud: str) -> Optional[str]: - return getattr(self, self._license_key_attr(cloud), None) - - def access_application_id(self, cloud: str) -> Optional[str]: - return getattr(self, self._access_application_id_attr(cloud), None) - - def cloud_to_license_key(self) -> Dict[str, Optional[str]]: - return { - cloud: self.license_key(cloud) for cloud in RuleDomain.iter() - } diff --git a/src/models/modular/customer.py b/src/models/modular/customer.py deleted file mode 100644 index f3315747c..000000000 --- a/src/models/modular/customer.py +++ /dev/null @@ -1 +0,0 @@ -from modular_sdk.models.customer import Customer diff --git a/src/models/modular/jobs.py b/src/models/modular/jobs.py deleted file mode 100644 index 5be1f9519..000000000 --- a/src/models/modular/jobs.py +++ /dev/null @@ -1 +0,0 @@ -from modular_sdk.models.job import Job diff --git a/src/models/modular/parents.py b/src/models/modular/parents.py deleted file mode 100644 index 27a374fc6..000000000 --- a/src/models/modular/parents.py +++ /dev/null @@ -1,23 +0,0 @@ -import dataclasses -from typing import List, Dict - -from modular_sdk.commons import DataclassBase -from modular_sdk.models.parent import Parent - -Parent = Parent - - -@dataclasses.dataclass() -class ParentMeta(DataclassBase): - """ - Parent with type CUSTODIAN_LICENSES meta - """ - rules_to_exclude: List[str] - - -@dataclasses.dataclass() -class DefectDojoParentMeta(DataclassBase): - entities_mapping: Dict[str, str] = dataclasses.field(default_factory=dict) - display_all_fields: bool = False - upload_files: bool = False - resource_per_finding: bool = False diff --git a/src/models/modular/tenant_settings.py b/src/models/modular/tenant_settings.py deleted file mode 100644 index 3d439dfe1..000000000 --- a/src/models/modular/tenant_settings.py +++ /dev/null @@ -1 +0,0 @@ -from modular_sdk.models.tenant_settings import TenantSettings \ No newline at end of file diff --git a/src/models/modular/tenants.py b/src/models/modular/tenants.py deleted file mode 100644 index f71598d44..000000000 --- a/src/models/modular/tenants.py +++ /dev/null @@ -1 +0,0 @@ -from modular_sdk.models.tenant import Tenant diff --git a/src/models/policy.py b/src/models/policy.py index ce25d3d42..b1492a1a3 100644 --- a/src/models/policy.py +++ b/src/models/policy.py @@ -2,15 +2,24 @@ from pynamodb.attributes import UnicodeAttribute, ListAttribute -from models.modular import BaseModel -from helpers.constants import ENV_VAR_REGION +from models import BaseModel +from enum import Enum +from helpers.constants import CAASEnv + + +class PolicyEffect(str, Enum): + ALLOW = 'allow' + DENY = 'deny' class Policy(BaseModel): class Meta: table_name = 'CaaSPolicies' - region = os.environ.get(ENV_VAR_REGION) + region = os.environ.get(CAASEnv.AWS_REGION) - customer = UnicodeAttribute(hash_key=True) + customer = UnicodeAttribute(hash_key=True) # todo hot partition? name = UnicodeAttribute(range_key=True) - permissions = ListAttribute(null=True, default=list) + description = UnicodeAttribute(null=True) + permissions = ListAttribute(default=list, of=UnicodeAttribute) + tenants = ListAttribute(default=list, of=UnicodeAttribute) + effect = UnicodeAttribute(default=PolicyEffect.DENY.value) diff --git a/src/models/pynamodb_extension/__init__.py b/src/models/pynamodb_extension/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/models/pynamodb_extension/index.py b/src/models/pynamodb_extension/index.py deleted file mode 100644 index de6a91f65..000000000 --- a/src/models/pynamodb_extension/index.py +++ /dev/null @@ -1,114 +0,0 @@ -from pynamodb.expressions.condition import Condition -from pynamodb.models import _KeyType - -from models.pynamodb_extension.pagination import IResultIterator, \ - ComposableResultIterator - -from models.modular import BaseGSI -from typing import Optional, List, Dict, Any, TypedDict, Callable -from concurrent.futures import ThreadPoolExecutor, as_completed - -from helpers.log_helper import get_logger - -_LOG = get_logger(__name__) - - -class QueryParams(TypedDict): - range_key_condition: Optional[Condition] - filter_condition: Optional[Condition] - consistent_read: Optional[bool] - scan_index_forward: Optional[bool] - limit: Optional[int] - last_evaluated_key: Optional[Dict[str, Dict[str, Any]]] - attributes_to_get: Optional[List[str]] - rate_limit: Optional[float] - - page_size: Optional[int] # DynamoDB - limit: Optional[str] # MongoDB - - -class ThreadedGSI(BaseGSI): - - @classmethod - def batch_query( - cls, hash_key_query_ref: Dict[_KeyType, QueryParams], - limit: Optional[int] = None, scan_index_forward: bool = True, - workers: Optional[int] = None - ) -> IResultIterator: - """ - Mediates a thread-based index-queries, based on a given reference map - of a hash_key and query parameters. - Note: given an index maintains a sort-key, ResultIterators are - Composed in a respective, sorted range. - - :param hash_key_query_ref: Dict[_KeyType, QueryParams] - :param limit: Optional[int] - :param scan_index_forward: Optional[bool, def=True], denotes - ascending order to be True or False - :param workers: Optional[int] - :return: IResultIterator[_M] - """ - - range_key_attr = cls._range_key_attribute() - - query = cls.query - if cls.is_docker: - for hash_key in [*hash_key_query_ref]: - params = hash_key_query_ref[hash_key].copy() # Consistency - hash_key_query_ref[hash_key] = params - params['model_class'] = cls - query = cls.mongodb_handler().query - - hash_key_ri_reference = cls._inquire_indexes( - query=query, hash_key_query_ref=hash_key_query_ref, workers=workers - ) - - return ComposableResultIterator( - limit=limit, - hash_key_result_iterator_ref=hash_key_ri_reference, - sort_key_attr=range_key_attr.attr_name if range_key_attr else None, - ascending_order=scan_index_forward - ) - - @classmethod - def _inquire_indexes( - cls, query: Callable, hash_key_query_ref: Dict[_KeyType, QueryParams], - sort_key_attr: Optional[str] = None, workers: Optional[int] = None - ): - """ - Mandates thread-based index queries, based on hash-keys of a given - list, as well as Key and Filter Expression conditions. - """ - - index_name = cls.Meta.index_name - - with ThreadPoolExecutor(max_workers=workers) as executor: - futures = { - executor.submit( - query, - hash_key=hash_key, - **hash_key_query_ref[hash_key] - ): hash_key - - for hash_key in hash_key_query_ref - } - - iterator_map: Dict[str, IResultIterator] = {} - - for future in as_completed(futures): - hash_key = futures[future] - try: - res: IResultIterator = future.result() - except (TimeoutError, Exception) as e: - _locals, _ignore = locals(), ( - 'query', 'hash_key_query_ref', 'workers' - ) - tuple(map(_locals.pop, _ignore)) - _LOG.error(f'Index:\'{index_name}\' query of ' - f'{hash_key} hash-key value, and {_locals}' - f' has run into an issue: {e}.') - continue - - iterator_map[hash_key] = res - - return iterator_map diff --git a/src/models/pynamodb_extension/pagination.py b/src/models/pynamodb_extension/pagination.py deleted file mode 100644 index 23273cb5c..000000000 --- a/src/models/pynamodb_extension/pagination.py +++ /dev/null @@ -1,162 +0,0 @@ -# todo update within MODULAR - -from collections.abc import Iterator -from operator import le, ge -from typing import Optional, List, Dict, Union, Any, Tuple, Set - -from pynamodb.indexes import _M, _KeyType - -_Lek = Union[int, Dict[str, Any]] - - -class IResultIterator(Iterator): - - @property - def last_evaluated_key(self): - raise NotImplemented - - -class ComposableResultIterator(IResultIterator): - - def __init__( - self, hash_key_result_iterator_ref: Dict[_KeyType, IResultIterator], - sort_key_attr: Optional[str] = None, ascending_order: bool = True, - limit: Optional[int] = None - ): - - self._hk_ri_ref = hash_key_result_iterator_ref - - self._sk_attr = sort_key_attr - self._op = ge if ascending_order else le - self._limit = limit - - # Queue of N Iterators. - self._queue: List[Tuple[_KeyType, IResultIterator]] = [ - *self._hk_ri_ref.items() - ] - - # Retains a sorted list of Tuples[item, hash-key, last-evaluated-key] - self._items: List[Tuple[_M, _KeyType, _Lek]] = [] - - # Explicitly allows to provide $hash-key: None, to search on the - # index with no explicit beginning. - self._evaluated_key: Dict[_KeyType, Optional[_Lek]] = {} - - self._return_count: Optional[int] = 0 - - - @property - def last_evaluated_key(self) -> Optional[ - Dict[_KeyType, Union[int, Dict[str, Any]]] - ]: - return self._evaluated_key if self._evaluated_key else None - - def __iter__(self): - return self - - def __next__(self): - - # Shift per iterator. - _shifted: Set[IResultIterator] = set() - # Merged flag - meant to trigger a search. - _m = False - - if not self._items: - - while True: - - _len = len(self._queue) - - # Based on N sorted result iterators within a queue, - # merges N items - - if not self._queue or ( - self._limit is not None - and self._return_count == self._limit - ): - self._queue = [] - break - - _hk, _ri = self._queue[0] - # Picks at the fifo queue, enqueuing the iterator to the back. - _item = self._pick(queue=self._queue) - - if not _item: - continue - - index = self._get_index(item=_item) - if self._items and index < len(self._items): - # Order must be shifted - amending iterator priority. - # Prepending, the iterator back into the front. - self._queue.insert(0, (_hk, _ri)) - # Flag the priority-trigger - _m = True - else: - _shifted.add(_ri) - # Unsets the priority-triggered search. - _m = False - - self._items.insert(index, (_item, _hk, _ri.last_evaluated_key)) - - # Stores the first evaluated key of items - providing reference - # to those, which have not been yielded, but consumed. - if _hk not in self._evaluated_key: - # Explicitly mention the key. - self._evaluated_key[_hk] = None - - # Breaks searching loop, given the queue has been seen at - # least twice - if not _m and len(_shifted) >= _len*2: - break - - if self._items and ( - self._limit is None or self._return_count < self._limit - ): - - self._return_count += 1 - - # Update the last evaluated key store. - item, hk, lek = self._items.pop(0) - # Store a last evaluated key of the next non-mentioned hk, as well. - self._evaluated_key[hk] = lek - if not lek: - self._evaluated_key.pop(hk) - return item - - raise StopIteration - - @staticmethod - def _pick(queue: List[Tuple[_KeyType, IResultIterator]]) -> Optional[_M]: - _, ri = queue.pop(0) - try: - item = next(ri) - queue.append((_, ri)) - except StopIteration: - item = None - return item - - def _get_index(self, item: _M): - """ - Returns an index value, to insert an item within already merged, - pending item list. - :param item: _M - :return: int - """ - - if not self._sk_attr: - return len(self._items) - - index = 0 - item_sort_value = getattr(item, self._sk_attr, None) - for each, _, _ in self._items: - _sort_value = getattr(each, self._sk_attr, None) - try: - if not self._op(item_sort_value, _sort_value): - break - except (TypeError, Exception): - # Improper values to compare. - ... - - index += 1 - - return index diff --git a/src/models/report_statistics.py b/src/models/report_statistics.py new file mode 100644 index 000000000..80863770d --- /dev/null +++ b/src/models/report_statistics.py @@ -0,0 +1,49 @@ +import os + +from pynamodb.attributes import UnicodeAttribute, MapAttribute, NumberAttribute +from pynamodb.indexes import AllProjection + +from models import BaseModel, BaseGSI +from helpers.constants import CAASEnv + + +class CustomerNameTriggeredAtIndex(BaseGSI): + class Meta: + index_name = 'customer_name-triggered_at-index' + read_capacity_units = 1 + write_capacity_units = 1 + projection = AllProjection() + + customer_name = UnicodeAttribute(hash_key=True) + triggered_at = UnicodeAttribute(range_key=True) + + +class StatusIndex(BaseGSI): + class Meta: + index_name = 'status-index' + read_capacity_units = 1 + write_capacity_units = 1 + projection = AllProjection() + + status = UnicodeAttribute(hash_key=True) + + +class ReportStatistics(BaseModel): + class Meta: + table_name = 'CaaSReportStatistics' + region = os.environ.get(CAASEnv.AWS_REGION) + + id = UnicodeAttribute(hash_key=True) + triggered_at = UnicodeAttribute(range_key=True) + attempt = NumberAttribute(null=True, default=1) + user = UnicodeAttribute(null=True) + level = UnicodeAttribute(null=True) + type = UnicodeAttribute(null=True) + status = UnicodeAttribute(null=True) # FAILED, SUCCEEDED, PENDING, RETRIED, DUPLICATE + customer_name = UnicodeAttribute(null=True) + tenant = UnicodeAttribute(null=True) + reason = UnicodeAttribute(null=True) + event = MapAttribute(default=dict) + + customer_name_triggered_at_index = CustomerNameTriggeredAtIndex() + status_index = StatusIndex() diff --git a/src/models/retries.py b/src/models/retries.py index ff00be138..f0d67e3d2 100644 --- a/src/models/retries.py +++ b/src/models/retries.py @@ -2,14 +2,14 @@ from pynamodb.attributes import UnicodeAttribute, ListAttribute -from models.modular import BaseModel -from helpers.constants import ENV_VAR_REGION +from models import BaseModel +from helpers.constants import CAASEnv class Retries(BaseModel): class Meta: table_name = 'CaaSRetries' - region = os.environ.get(ENV_VAR_REGION) + region = os.environ.get(CAASEnv.AWS_REGION) id = UnicodeAttribute(hash_key=True) name = UnicodeAttribute(range_key=True) diff --git a/src/models/role.py b/src/models/role.py index 6e686c0f7..c7ef535ab 100644 --- a/src/models/role.py +++ b/src/models/role.py @@ -2,17 +2,23 @@ from pynamodb.attributes import UnicodeAttribute, ListAttribute -from models.modular import BaseModel, BaseGSI -from helpers.constants import ENV_VAR_REGION +from helpers.constants import CAASEnv +from helpers.time_helper import utc_datetime +from models import BaseModel class Role(BaseModel): class Meta: table_name = 'CaaSRoles' - region = os.environ.get(ENV_VAR_REGION) + region = os.environ.get(CAASEnv.AWS_REGION) customer = UnicodeAttribute(hash_key=True) name = UnicodeAttribute(range_key=True) expiration = UnicodeAttribute(null=True) # ISO8601, valid to date - policies = ListAttribute(null=True, default=list) - resource = ListAttribute(null=True, default=list) + policies = ListAttribute(default=list) + description = UnicodeAttribute(null=True) + + def is_expired(self) -> bool: + if not self.expiration: + return False + return utc_datetime() >= utc_datetime(self.expiration) diff --git a/src/models/rule.py b/src/models/rule.py index 1e26baf01..154acdaf4 100644 --- a/src/models/rule.py +++ b/src/models/rule.py @@ -1,13 +1,13 @@ import os -from itertools import islice +from itertools import count from typing import Optional from pynamodb.attributes import UnicodeAttribute, MapAttribute, ListAttribute from pynamodb.indexes import AllProjection -from helpers.constants import ENV_VAR_REGION, COMPOUND_KEYS_SEPARATOR, \ - RuleSourceType -from models.modular import BaseModel, BaseGSI +from helpers.constants import (COMPOUND_KEYS_SEPARATOR, RuleSourceType, Cloud, + CAASEnv) +from models import BaseModel, BaseGSI R_ID_ATTR = 'id' R_CUSTOMER_ATTR = 'c' @@ -42,48 +42,189 @@ class RuleIndex: """ https://github.com/epam/ecc-kubernetes-rulepack/wiki/Rule-Index-(Comment)-Structure """ + @staticmethod + def _it(d: int): + f = f'0{d}d' + for i in count(): + yield format(i, f) + + _cloud_map = dict(zip(_it(2), [ + None, + 'AWS', + 'AZURE', + 'GCP' + ])) + + _platform_map = dict(zip(_it(2), [ + None, + 'Kubernetes', + 'OpenShift', + 'Kubernetes and OpenShift' + ])) + _category_map = dict(zip(_it(2), [ + 'FinOps', + 'Lifecycle management', + 'Unutilized Resources', + 'Idle and underutilized resources', + 'Rightsizing', + 'Autoscaling', + 'Computing resources optimization', + 'Storage optimization', + 'Reserved instances and savings plan usage', + 'Other cost optimization checks', + 'Tagging', + 'Security', + 'Detect', + 'Identify', + 'Protect', + 'Recover', + 'Detection services', + 'Secure access management', + 'Inventory', + 'Logging', + 'Resource configuration', + 'Vulnerability, patch, and version management', + 'Secure access management', + 'Secure configuration', + 'Secure network configuration', + 'Data protection', + 'API protection', + 'Protective services', + 'Secure development', + 'Key, Secrets, and Certificate management', + 'Network security', + 'Resilience', + 'Monitoring', + 'Access control', + 'Passwordless authentication', + 'Root user access restrictions', + 'MFA enabled', + 'Sensitive API actions restricted', + 'Resource policy configuration', + 'API private access', + 'Resources not publicly accessible', + 'Resources within VPC', + 'Security group configuration', + 'Encryption of data at rest', + 'Encryption of data in transit', + 'Encryption of data at rest and in transit', + 'Data integrity', + 'Data deletion protection', + 'Credentials not hardcoded', + 'Backups enabled', + 'High availability' + ])) + _service_section_map = dict(zip(_it(2), [ + 'Identity and Access Management', + 'Logging and Monitoring', + 'Networking & Content Delivery', + 'Compute', + 'Storage', + 'Analytics', + 'Databases', + 'Kubernetes Engine', + 'Containers', + 'Security & Compliance', + 'Cryptography & PKI', + 'Machine learning', + 'End User Computing', + 'Developer Tools', + 'Application Integration', + 'Dataproc', + 'App Engine', + 'AppService', + 'Microsoft Defender for Cloud', + 'API Server', + 'Controller Manager', + 'etcd', + 'General Policies', + 'Pod Security Standards', + 'RBAC and Service Accounts', + 'Scheduler', + 'Secrets Management' + ])) + _source_map = dict(zip(_it(2), [ + 'Azure Security Benchmark (V3)', + 'CIS Amazon Web Services Foundations Benchmark v1.2.0', + 'CIS Amazon Web Services Foundations Benchmark v1.4.0', + 'CIS Amazon Web Services Foundations Benchmark v1.5.0', + 'CIS AWS Compute Services Benchmark v1.0.0', + 'CIS AWS EKS Benchmark 1.1.0', + 'CIS AWS End User Compute Services Benchmark v1.0.0', + 'CIS Benchmark Google Cloud Platform Foundation v1.0.0', + 'CIS Benchmark Google Cloud Platform Foundation v1.2.0', + 'CIS Benchmark Google Cloud Platform Foundation v1.3.0', + 'CIS Benchmark Google Cloud Platform Foundation v2.0.0', + 'CIS Google Kubernetes Engine (GKE) Benchmark v1.3.0', + 'CIS Google Kubernetes Engine (GKE) Benchmark v1.4.0', + 'CIS Microsoft Azure Foundations Benchmark v1.4.0', + 'CIS Microsoft Azure Foundations Benchmark v1.5.0', + 'CIS Microsoft Azure Foundations Benchmark v2.0.0', + 'CIS MySQL Enterprise Edition 8.0 Benchmark v1.2.0', + 'CIS Oracle Database 19 Benchmark v1.0.0', + 'CIS Oracle MySQL Community Server 5.7 Benchmark v2.0.0', + 'CIS PostgreSQL 11 Benchmark 1.0.0', + 'EPAM', + 'NIST SP 800-53 Rev. 5', + 'PCI DSS', + 'CIS Kubernetes Benchmark v1.7.0', + 'CIS RedHat OpenShift Container Platform Benchmark v1.4.0', + 'CIS Amazon Web Services Foundations Benchmark v2.0.0', + ])) + + __slots__ = ( + '_comment', '_cloud', '_platform', '_category', '_service_section', + '_source', '_customization', '_multiregional' + ) + def __init__(self, comment: str): self._comment = comment or '' - it = iter(self._comment) - self._cloud = ''.join(islice(it, 2)) - self._platform = ''.join(islice(it, 2)) - self._category = ''.join(islice(it, 2)) - self._service_section = ''.join(islice(it, 2)) - self._source = ''.join(islice(it, 2)) - self._customization = ''.join(islice(it, 1)) - self._multiregional = ''.join(islice(it, 1)) + self._cloud = comment[0:2] + self._platform = comment[2:4] + self._category = comment[4:6] + self._service_section = comment[6:8] + self._source = comment[8:10] + self._customization = comment[10:11] + self._multiregional = comment[11:12] - @staticmethod - def _cloud_map() -> dict: - return { - '00': None, - '01': 'AWS', - '02': 'AZURE', - '03': 'GCP' - } + @property + def raw_cloud(self) -> Optional[str]: + return self._cloud_map.get(self._cloud) - @staticmethod - def _platform_map() -> dict: - return { - '00': None, - '01': 'Kubernetes', - '02': 'OpenShift', - '03': 'Kubernetes and OpenShift' - } + @property + def raw_platform(self) -> Optional[str]: + return self._platform_map.get(self._platform) + + @property + def cloud(self) -> Cloud: + pl = self.raw_platform + if pl: + return Cloud.KUBERNETES + return Cloud[self.raw_cloud] + + @property + def category(self) -> Optional[str]: + return self._category_map.get(self._category) + + @property + def service_section(self) -> Optional[str]: + return self._service_section_map.get(self._service_section) @property - def cloud(self) -> Optional[str]: - return self._cloud_map().get(self._cloud) + def source(self) -> Optional[str]: + return self._source_map.get(self._source) @property - def platform(self) -> Optional[str]: - return self._platform_map().get(self._platform) + def has_customization(self) -> bool: + if not self._customization: + return False + return not not int(self._customization) @property - def multiregional(self) -> bool: + def is_global(self) -> bool: if not self._multiregional: - return True # most rules are multiregional - return bool(int(self._multiregional)) + return True # most rules are global + return not not int(self._multiregional) class Rule(BaseModel): @@ -100,15 +241,14 @@ def latest_version_tag(cls) -> str: class Meta: table_name = 'CaaSRules' - region = os.environ.get(ENV_VAR_REGION) + region = os.environ.get(CAASEnv.AWS_REGION) # "customer#cloud#name#version" id = UnicodeAttribute(hash_key=True, attr_name=R_ID_ATTR) customer = UnicodeAttribute(attr_name=R_CUSTOMER_ATTR) resource = UnicodeAttribute(attr_name=R_RESOURCE_ATTR) description = UnicodeAttribute(attr_name=R_DESCRIPTION_ATTR) - filters = ListAttribute(default=list, of=MapAttribute, - attr_name=R_FILTERS_ATTR) + filters = ListAttribute(default=list, attr_name=R_FILTERS_ATTR) # list of either strings or maps comment = UnicodeAttribute(null=True, attr_name=R_COMMENT_ATTR) # "project#ref#path" diff --git a/src/models/rule_meta.py b/src/models/rule_meta.py deleted file mode 100644 index 148fa1dc3..000000000 --- a/src/models/rule_meta.py +++ /dev/null @@ -1,58 +0,0 @@ -import os - -from modular_sdk.models.pynamodb_extension.base_model import BaseModel -from pynamodb.attributes import UnicodeAttribute, MapAttribute, \ - ListAttribute, BooleanAttribute - -from helpers.constants import ENV_VAR_REGION, AZURE_CLOUD_ATTR, GCP_CLOUD_ATTR - -RM_NAME_ATTR = 'n' -RM_VERSION_ATTR = 'v' -RM_CLOUD_ATTR = 'c' -RM_SOURCE_ATTR = 's' -RM_ARTICLE_ATTR = 'a' -RM_SERVICE_SECTION_ATTR = 'ss' -RM_IMPACT_ATTR = 'i' -RM_SEVERITY_ATTR = 'se' -RM_MIN_CORE_VERSION_ATTR = 'mcv' -RM_REPORT_FIELDS_ATTR = 'r' -RM_MULTIREGIONAL_ATTR = 'm' -RM_EVENTS_ATTR = 'e' -RM_STANDARD_ATTR = 'st' -RM_MITRE_ATTR = 'mi' -RM_REMEDIATION_ATTR = 're' - - -class RuleMeta(BaseModel): - class Meta: - table_name = 'CaaSRulesMeta' - region = os.environ.get(ENV_VAR_REGION) - - name = UnicodeAttribute(hash_key=True, attr_name=RM_NAME_ATTR) - version = UnicodeAttribute(range_key=True, attr_name=RM_VERSION_ATTR) - # meta - cloud = UnicodeAttribute(attr_name=RM_CLOUD_ATTR) # it's more a domain - source = UnicodeAttribute(attr_name=RM_SOURCE_ATTR) - article = UnicodeAttribute(attr_name=RM_ARTICLE_ATTR, null=True) - service_section = UnicodeAttribute(attr_name=RM_SERVICE_SECTION_ATTR) - impact = UnicodeAttribute(attr_name=RM_IMPACT_ATTR) - severity = UnicodeAttribute(attr_name=RM_SEVERITY_ATTR) - min_core_version = UnicodeAttribute(attr_name=RM_MIN_CORE_VERSION_ATTR) - report_fields = ListAttribute(default=list, of=UnicodeAttribute, - attr_name=RM_REPORT_FIELDS_ATTR) - multiregional = BooleanAttribute(default=True, - attr_name=RM_MULTIREGIONAL_ATTR) - events = MapAttribute(default=dict, attr_name=RM_EVENTS_ATTR) - standard = MapAttribute(default=dict, attr_name=RM_STANDARD_ATTR) - mitre = MapAttribute(default=dict, attr_name=RM_MITRE_ATTR) - remediation = UnicodeAttribute(attr_name=RM_REMEDIATION_ATTR) - - def is_multiregional(self) -> bool: - """ - AWS rules can be multiregional or region-dependent whereas AZURE and - GCP rules are always multiregional - :return: - """ - if self.cloud == AZURE_CLOUD_ATTR or self.cloud == GCP_CLOUD_ATTR: - return True - return self.multiregional diff --git a/src/models/rule_source.py b/src/models/rule_source.py index ca736e7c1..530a14950 100644 --- a/src/models/rule_source.py +++ b/src/models/rule_source.py @@ -4,9 +4,9 @@ from pynamodb.attributes import UnicodeAttribute, MapAttribute, ListAttribute from pynamodb.indexes import AllProjection -from helpers.constants import ENV_VAR_REGION, CUSTOMER_ATTR, \ +from helpers.constants import CAASEnv, CUSTOMER_ATTR, \ GIT_PROJECT_ID_ATTR, RuleSourceType -from models.modular import BaseModel, BaseGSI +from models import BaseModel, BaseGSI class LatestSyncAttribute(MapAttribute): @@ -30,7 +30,7 @@ class Meta: class RuleSource(BaseModel): class Meta: table_name = 'CaaSRuleSources' - region = os.environ.get(ENV_VAR_REGION) + region = os.environ.get(CAASEnv.AWS_REGION) id = UnicodeAttribute(hash_key=True) customer = UnicodeAttribute() diff --git a/src/models/ruleset.py b/src/models/ruleset.py index ea642d9ae..b5c1aae85 100644 --- a/src/models/ruleset.py +++ b/src/models/ruleset.py @@ -4,9 +4,8 @@ ListAttribute, MapAttribute from pynamodb.indexes import AllProjection -from helpers.constants import COMPOUND_KEYS_SEPARATOR -from helpers.constants import ENV_VAR_REGION -from models.modular import BaseModel, BaseGSI +from helpers.constants import CAASEnv, COMPOUND_KEYS_SEPARATOR +from models import BaseModel, BaseGSI RULESET_LICENSES = 'L' RULESET_STANDARD = 'S' @@ -47,7 +46,7 @@ class Meta: class Ruleset(BaseModel): class Meta: table_name = 'CaaSRulesets' - region = os.environ.get(ENV_VAR_REGION) + region = os.environ.get(CAASEnv.AWS_REGION) id = UnicodeAttribute(hash_key=True) # "customer#L|S#name#version" customer = UnicodeAttribute() diff --git a/src/models/scheduled_job.py b/src/models/scheduled_job.py index e5784738c..db7299d2a 100644 --- a/src/models/scheduled_job.py +++ b/src/models/scheduled_job.py @@ -1,20 +1,19 @@ import os +from typing import Optional from pynamodb.attributes import UnicodeAttribute, ListAttribute, \ - MapAttribute, BinaryAttribute, BooleanAttribute + MapAttribute, BooleanAttribute from pynamodb.indexes import AllProjection -from typing import Optional -from helpers.constants import ENV_VAR_REGION, CUSTODIAN_TYPE, \ +from helpers.constants import CAASEnv, CUSTODIAN_TYPE, \ SCHEDULED_JOB_TYPE from helpers.time_helper import utc_iso -from models.modular import BaseGSI, BaseSafeUpdateModel +from models import BaseGSI, BaseSafeUpdateModel SCHEDULED_JOBS_TABLE_NAME = 'CaaSScheduledJobs' SJ_ID_ATTR = 'id' SJ_TYPE_ATTR = 'type' -# SJ_PRINCIPAL_ATTR = 'principal' SJ_TENANT_NAME = 'tenant_name' SJ_CREATION_DATE_ATTR = 'creation_date' SJ_LAST_EXECUTION_TIME_ATTR = 'last_execution_time' @@ -50,7 +49,7 @@ class ScheduledJob(BaseSafeUpdateModel): class Meta: table_name = SCHEDULED_JOBS_TABLE_NAME - region = os.environ.get(ENV_VAR_REGION) + region = os.environ.get(CAASEnv.AWS_REGION) id = UnicodeAttribute(hash_key=True, attr_name=SJ_ID_ATTR) type = UnicodeAttribute(range_key=True, default=default_type, @@ -71,23 +70,3 @@ def get_nullable(cls, hash_key, range_key=None, attributes_to_get=None ) -> Optional['ScheduledJob']: return super().get_nullable( hash_key, range_key or cls.default_type, attributes_to_get) - - def update_with(self, customer: str = None, tenant: str = None, - schedule: str = None, scan_regions: list = None, - scan_rulesets: list = None, - is_enabled: bool = True): - """ - Sets some common parameters to the object - """ - if customer: - self.customer_name = customer - if tenant: - self.tenant_name = tenant - if schedule: - self.context.schedule = schedule - if scan_regions: - self.context.scan_regions = scan_regions - if scan_rulesets: - self.context.scan_rulesets = scan_rulesets - if is_enabled is not None: - self.context.is_enabled = is_enabled diff --git a/src/models/setting.py b/src/models/setting.py index 2afc75cbf..c00c575b8 100644 --- a/src/models/setting.py +++ b/src/models/setting.py @@ -3,14 +3,14 @@ from modular_sdk.models.pynamodb_extension.base_model import DynamicAttribute from pynamodb.attributes import UnicodeAttribute -from helpers.constants import ENV_VAR_REGION -from models.modular import BaseModel +from helpers.constants import CAASEnv +from models import BaseModel class Setting(BaseModel): class Meta: table_name = 'CaaSSettings' - region = os.environ.get(ENV_VAR_REGION) + region = os.environ.get(CAASEnv.AWS_REGION) max_retry_attempts = 5 name = UnicodeAttribute(hash_key=True) diff --git a/src/models/tenant_metrics.py b/src/models/tenant_metrics.py index f6040c2b7..ba3e081db 100644 --- a/src/models/tenant_metrics.py +++ b/src/models/tenant_metrics.py @@ -4,8 +4,8 @@ JSONAttribute, ListAttribute from pynamodb.indexes import AllProjection -from helpers.constants import ENV_VAR_REGION -from models.modular import BaseModel, BaseGSI +from helpers.constants import CAASEnv +from models import BaseModel, BaseGSI TM_ID_ATTR = 'id' TM_TENANT_ATTR = 'tn' @@ -42,7 +42,7 @@ class Meta: class TenantMetrics(BaseModel): class Meta: table_name = 'CaaSTenantMetrics' - region = os.environ.get(ENV_VAR_REGION) + region = os.environ.get(CAASEnv.AWS_REGION) id = UnicodeAttribute(hash_key=True, attr_name=TM_ID_ATTR) tenant_display_name = UnicodeAttribute(range_key=True, attr_name=TM_TENANT_ATTR) diff --git a/src/models/user.py b/src/models/user.py index 14d7f441a..05294f22b 100644 --- a/src/models/user.py +++ b/src/models/user.py @@ -2,14 +2,15 @@ from pynamodb.attributes import UnicodeAttribute -from helpers.constants import ENV_VAR_REGION -from models.modular import BaseModel +from helpers.constants import CAASEnv +from models import BaseModel +# used only for on-prem class User(BaseModel): class Meta: table_name = 'CaaSUsers' - region = os.environ.get(ENV_VAR_REGION) + region = os.environ.get(CAASEnv.AWS_REGION) user_id = UnicodeAttribute(hash_key=True) tenants = UnicodeAttribute(null=True) @@ -17,4 +18,5 @@ class Meta: role = UnicodeAttribute(null=True) password = UnicodeAttribute(null=True) latest_login = UnicodeAttribute(null=True) + created_at = UnicodeAttribute(null=True) diff --git a/src/exported_module/Dockerfile b/src/onprem/Dockerfile similarity index 54% rename from src/exported_module/Dockerfile rename to src/onprem/Dockerfile index 18178750b..6eca102cc 100644 --- a/src/exported_module/Dockerfile +++ b/src/onprem/Dockerfile @@ -4,10 +4,9 @@ FROM public.ecr.aws/docker/library/python:3.10 as executor-compile-image # root and execute build with build context pointing to Custodian's root: # pwd # ../custodian-as-a-service -# docker build -f executor/Dockerfile . +# docker build -f src/executor/Dockerfile . ARG custodian=. ARG core=custodian-custom-core -# ARG mcdm=maestro-common-domain-model # it's not enough to just add a provider here, you must copy its files below ARG providers="gcp azure kube" @@ -15,8 +14,7 @@ ARG POETRY_VERSION="1.4.0" # to import c7n_azure.resources.insights.DiagnosticSettings ENV AZURE_SUBSCRIPTION_ID=" " -RUN pip install "poetry==$POETRY_VERSION" && \ - python -m venv /root/.local +RUN pip install "poetry==$POETRY_VERSION" && python -m venv /root/.local WORKDIR /build/custodian-custom-core @@ -31,9 +29,13 @@ COPY $core/tools/c7n_kube/pyproject.toml $core/tools/c7n_kube/poetry.lock tools/ RUN for pkg in $providers; do . /root/.local/bin/activate && cd tools/c7n_$pkg && poetry install --no-interaction --without dev --no-root && cd ../..; done +# server requirements +COPY $custodian/src/onprem/requirements.txt ../src/onprem/requirements.txt +RUN . /root/.local/bin/activate && pip install -r ../src/onprem/requirements.txt + # executor requirements -COPY $custodian/executor/requirements.txt ../executor/requirements.txt -RUN . /root/.local/bin/activate && pip install -r ../executor/requirements.txt +COPY $custodian/src/executor/requirements.txt ../src/executor/requirements.txt +RUN . /root/.local/bin/activate && pip install -r ../src/executor/requirements.txt COPY $core/c7n c7n/ @@ -50,51 +52,22 @@ COPY $core/tools/c7n_kube tools/c7n_kube/ RUN for pkg in $providers; do . /root/.local/bin/activate && cd tools/c7n_$pkg && pip install --no-deps . && cd ../..; done -# COPY $mcdm/mcdm_sdk ../mcdm_sdk -# RUN . /root/.local/bin/activate && pip install ../mcdm_sdk - -COPY $custodian/executor ../executor -# by here we have executor built in /root/.local - - -# building API -FROM public.ecr.aws/docker/library/python:3.10 as api-compile-image - -ARG custodian=. -# ARG mcdm=maestro-common-domain-model +COPY $custodian/src ../src +# by here we have executor and api built in /root/.local -RUN python -m venv /root/.local -WORKDIR /build/custodian-as-a-service - -# copying and installing API requirements -COPY $custodian/src/exported_module/requirements.txt src/exported_module/requirements.txt -RUN . /root/.local/bin/activate && pip install -r src/exported_module/requirements.txt - -# copying api and mcdm_sdk sources -COPY $custodian/src src -# COPY $mcdm/mcdm_sdk ../mcdm_sdk/ - -# RUN . /root/.local/bin/activate && pip install ../mcdm_sdk/ -# by here api image is built FROM public.ecr.aws/docker/library/python:3.10-slim AS build-image - -COPY --from=api-compile-image /root/.local /root/.local -COPY --from=api-compile-image /build/custodian-as-a-service /custodian-as-a-service - -COPY --from=executor-compile-image /build/executor /custodian-as-a-service/executor -COPY --from=executor-compile-image /root/.local /custodian-as-a-service/executor/.executor_venv +COPY --from=executor-compile-image /root/.local /root/.local +COPY --from=executor-compile-image /build/src/ /src/ ENV AWS_REGION=eu-central-1 \ - SERVICE_MODE=docker \ - VENV_PATH=/custodian-as-a-service/executor/.executor_venv/bin/python \ - EXECUTOR_PATH=/custodian-as-a-service/executor/executor.py \ + CAAS_SERVICE_MODE=docker \ PATH=/root/.local/bin:$PATH \ modular_service_mode=docker -WORKDIR /custodian-as-a-service/src +WORKDIR /src EXPOSE 8000 -RUN chmod +x exported_module/scripts/run.sh -CMD ["exported_module/scripts/run.sh"] +RUN chmod +x onprem/scripts/entrypoint.sh main.py +ENTRYPOINT ["onprem/scripts/entrypoint.sh"] diff --git a/src/onprem/Dockerfile-opensource b/src/onprem/Dockerfile-opensource new file mode 100644 index 000000000..b52daaac0 --- /dev/null +++ b/src/onprem/Dockerfile-opensource @@ -0,0 +1,33 @@ +FROM public.ecr.aws/docker/library/python:3.10-slim as compile-image + +ARG CUSTODIAN_SERVICE_PATH=. +ARG CLOUD_CUSTODIAN_PROVIDERS="gcp azure kube" + +# some dependency error occurred when installing all with one pip install command. So +RUN pip install --user c7n && for pkg in $CLOUD_CUSTODIAN_PROVIDERS; do pip install --user c7n-$pkg; done + +COPY $CUSTODIAN_SERVICE_PATH/src/executor/requirements.txt /src/executor/requirements.txt +RUN pip install --user -r /src/executor/requirements.txt + +COPY $CUSTODIAN_SERVICE_PATH/src/onprem/requirements.txt /src/onprem/requirements.txt +RUN pip install --user -r /src/onprem/requirements.txt + +COPY $CUSTODIAN_SERVICE_PATH/src /src + +RUN rm -rf $(find /root/.local/lib -name "*.dist-info") && rm -rf $(find /root/.local/lib/ -name "__pycache__") + + +FROM public.ecr.aws/docker/library/python:3.10-slim AS build-image + +COPY --from=compile-image /root/.local /root/.local +COPY --from=compile-image /src /src + +ENV AWS_REGION=us-east-1 \ + CAAS_SERVICE_MODE=docker \ + PATH=/root/.local/bin:$PATH \ + modular_service_mode=docker + +WORKDIR /src +EXPOSE 8000 +RUN chmod +x onprem/scripts/entrypoint.sh main.py +ENTRYPOINT ["onprem/scripts/entrypoint.sh"] diff --git a/src/exported_module/README.md b/src/onprem/README.md similarity index 86% rename from src/exported_module/README.md rename to src/onprem/README.md index c2086ef98..7b92034c6 100644 --- a/src/exported_module/README.md +++ b/src/onprem/README.md @@ -7,7 +7,7 @@ it locally to make the development easier ### Installation ```bash -pip install -r src/exported_module/requirements.txt +pip install -r src/onprem/requirements.txt ``` @@ -23,19 +23,15 @@ Create `.env` file with such contents: SERVICE_MODE=docker # -reports_bucket_name=reports # reports-bucket -caas_rulesets_bucket=rulesets -caas_ssm_backup_bucket=ssm-backup -stats_s3_bucket_name=statistics -templates_s3_bucket_name=templates +CAAS_REPORTS_BUCKET_NAME=reports # reports-bucket +CAAS_RULESETS_BUCKET_NAME=rulesets +CAAS_STATISTICS_BUCKET_NAME=statistics +CAAS_METRICS_BUCKET_NAME=metrics # # -last_scan_threshold=0 feature_skip_cloud_identifier_validation=false batch_job_log_level=DEBUG -feature_allow_only_temp_aws_credentials=false -not_invoke_ruleset_compiler=false # batch_job_def_name=caas-job-definition # reports-submit-job-definition # batch_job_queue_name=caas-job-queue # reports-submit-job-queue # event_bridge_service_role_to_invoke_batch= # event-bridge-service-role-to-invoke-batch diff --git a/src/connections/logs_extension/__init__.py b/src/onprem/__init__.py similarity index 100% rename from src/connections/logs_extension/__init__.py rename to src/onprem/__init__.py diff --git a/src/exported_module/__init__.py b/src/onprem/api/__init__.py similarity index 100% rename from src/exported_module/__init__.py rename to src/onprem/api/__init__.py diff --git a/src/onprem/api/app.py b/src/onprem/api/app.py new file mode 100644 index 000000000..e4f0022ff --- /dev/null +++ b/src/onprem/api/app.py @@ -0,0 +1,198 @@ +import inspect +import json +import re +from http import HTTPStatus +from typing import Callable + +from bottle import Bottle, HTTPResponse, request + +from helpers import RequestContext +from helpers.lambda_response import CustodianException, LambdaResponse, \ + ResponseFactory +from helpers.log_helper import get_logger +from lambdas.custodian_api_handler.handler import \ + lambda_handler as api_handler_lambda +from lambdas.custodian_configuration_api_handler.handler import ( + lambda_handler as configuration_api_handler_lambda, +) +from lambdas.custodian_report_generation_handler.handler import ( + lambda_handler as report_generation_handler, +) +from lambdas.custodian_report_generator.handler import ( + lambda_handler as report_generator_lambda, +) +from onprem.api.deployment_resources_parser import DeploymentResourcesApiGatewayWrapper +from services import SERVICE_PROVIDER +from services.clients.mongo_ssm_auth_client import UNAUTHORIZED_MESSAGE + +_LOG = get_logger(__name__) + + +class AuthPlugin: + """ + Authenticates the user + """ + + def __init__(self): + self.name = 'custodian-auth' + + @staticmethod + def get_token_from_header(header: str) -> str | None: + if not header or not isinstance(header, str): + return + parts = header.split() + if len(parts) == 1: + return parts[0] + if len(parts) == 2 and parts[0].lower() == 'bearer': + return parts[1] + + @staticmethod + def _to_bottle_resp(resp: LambdaResponse) -> HTTPResponse: + built = resp.build() + return HTTPResponse( + body=built['body'], + status=built['statusCode'], + headers=built['headers'] + ) + + def __call__(self, callback: Callable): + def wrapper(*args, **kwargs): + header = (request.headers.get('Authorization') or + request.headers.get('authorization')) + token = self.get_token_from_header(header) + + if not token: + resp = ResponseFactory(HTTPStatus.UNAUTHORIZED).message( + UNAUTHORIZED_MESSAGE + ) + return self._to_bottle_resp(resp) + + try: + decoded = SERVICE_PROVIDER.onprem_users_client.decode_token( + token + ) + except CustodianException as e: + return self._to_bottle_resp(e.response) + + sign = inspect.signature(callback) + if 'decoded_token' in sign.parameters: + _LOG.debug('Expanding callback with decoded token') + kwargs['decoded_token'] = decoded + return callback(*args, **kwargs) + + return wrapper + + +class OnPremApiBuilder: + dynamic_resource_regex = re.compile(r'([^{/]+)(?=})') + lambda_name_to_handler = { + 'caas-api-handler': api_handler_lambda, + 'caas-configuration-api-handler': configuration_api_handler_lambda, + 'caas-report-generator': report_generator_lambda, + 'caas-report-generation-handler': report_generation_handler + } + + def __init__(self, dp_wrapper: DeploymentResourcesApiGatewayWrapper): + self._dp_wrapper = dp_wrapper + + self._endpoint_to_lambda = {} + + @staticmethod + def _register_errors(app: Bottle) -> None: + @app.error(404) + def not_found(error): + return json.dumps({'message': HTTPStatus.NOT_FOUND.phrase}, + separators=(',', ':')) + + @app.error(500) + def internal(error): + return json.dumps( + {'message': HTTPStatus.INTERNAL_SERVER_ERROR.phrase}, + separators=(',', ':') + ) + + def build(self) -> Bottle: + self._endpoint_to_lambda.clear() + app = Bottle() + self._register_errors(app) + + custodian_app = Bottle() + it = self._dp_wrapper.iter_path_method_lambda() + auth_plugin = AuthPlugin() + for path, method, ln, has_auth in it: + path = self.to_bottle_route(path) + method = method.value + + self._endpoint_to_lambda[(path, method)] = ln + params = dict( + path=path, + method=method, + callback=self._callback + ) + if has_auth: + params['apply'] = [auth_plugin] + custodian_app.route(**params) + + app.mount(self._dp_wrapper.stage.strip('/'), custodian_app) + return app + + @classmethod + def to_bottle_route(cls, resource: str) -> str: + """ + Returns a proxied resource path, compatible with Bottle. + >>> OnPremApiBuilder.to_bottle_route('/path/{id}') + '/path/' + >>> OnPremApiBuilder.to_bottle_route('/some/data/{test}') + /some/data/ + :return: str + """ + for match in re.finditer(cls.dynamic_resource_regex, resource): + suffix = resource[match.end() + 1:] + resource = resource[:match.start() - 1] + path_input = match.group() + path_input = path_input.strip('{+') + resource += f'<{path_input}>' + suffix + return resource + + def _callback(self, decoded_token: dict | None = None, **path_params): + + method = request.method + path = request.route.rule + ln = self._endpoint_to_lambda[(path, method)] + handler = self.lambda_name_to_handler[ln] + event = { + 'httpMethod': request.method, + 'path': request.path, + 'headers': dict(request.headers), + 'requestContext': { + 'stage': self._dp_wrapper.stage, + 'resourcePath': path.replace('<', '{').replace('>', '}').replace('proxy', 'proxy+'), # kludge + 'path': request.fullpath + }, + 'pathParameters': path_params + } + if decoded_token: + event['requestContext']['authorizer'] = { + 'claims': { + 'cognito:username': decoded_token.get('cognito:username'), + 'sub': decoded_token.get('sub'), + 'custom:customer': decoded_token.get( + 'custom:customer'), + 'custom:role': decoded_token.get('custom:role'), + 'custom:tenants': decoded_token.get('custom:tenants') or '' + } + } + + if method == 'GET': + event['queryStringParameters'] = dict(request.query) + else: + event['body'] = request.body.read().decode() + event['isBase64Encoded'] = False + + response = handler(event, RequestContext()) + + return HTTPResponse( + body=response['body'], + status=response['statusCode'], + headers=response['headers'] + ) diff --git a/src/exported_module/api/app_gunicorn.py b/src/onprem/api/app_gunicorn.py similarity index 100% rename from src/exported_module/api/app_gunicorn.py rename to src/onprem/api/app_gunicorn.py diff --git a/src/onprem/api/cron_jobs.py b/src/onprem/api/cron_jobs.py new file mode 100644 index 000000000..86260ad04 --- /dev/null +++ b/src/onprem/api/cron_jobs.py @@ -0,0 +1,58 @@ +import importlib +from typing import Callable + +from apscheduler.triggers.interval import IntervalTrigger + +from helpers import RequestContext +from helpers.log_helper import get_logger +from services import SERVICE_PROVIDER + +_LOG = get_logger(__name__) + + +def sync_license(): + """ + This is a scheduled job itself. It must be in a separate module + from __main__ in case we have multiple possible __main__ (s). And we do + """ + license_module = importlib.import_module( + 'lambdas.custodian_license_updater.handler') + license_module.lambda_handler(event={}, context=RequestContext()) + + +def make_findings_snapshot(): + module = importlib.import_module( + 'lambdas.custodian_metrics_updater.processors.findings_processor' + ) + module.FINDINGS_UPDATER.process_data( + event={'data_type': 'findings'}, + ) + + +def ensure_job(name: str, method: Callable, hours: int): + from helpers.system_customer import SYSTEM_CUSTOMER + scheduler = SERVICE_PROVIDER.ap_job_scheduler.scheduler + _job = scheduler.get_job(name) + if not _job: + _LOG.info(f'Job {name} not found, registering') + scheduler.add_job(method, id=name, + trigger=IntervalTrigger(hours=hours)) + else: + _LOG.info(f'Job {name} already registered') + from models.scheduled_job import ScheduledJob + _item = ScheduledJob(id=name) + _item.update(actions=[ + ScheduledJob.customer_name.set(SYSTEM_CUSTOMER), + ScheduledJob.context.schedule.set( + f'rate({hours} {"hour" if hours == 1 else "hours"})' + ) + ]) + + +def ensure_all(): + jobs = { + 'custodian-system-license-sync-job': (sync_license, 3), + 'custodian-system-snapshot-findings': (make_findings_snapshot, 2) + } + for name, data in jobs.items(): + ensure_job(name, data[0], data[1]) diff --git a/src/onprem/api/deployment_resources_parser.py b/src/onprem/api/deployment_resources_parser.py new file mode 100644 index 000000000..f972b99c1 --- /dev/null +++ b/src/onprem/api/deployment_resources_parser.py @@ -0,0 +1,104 @@ +from http import HTTPStatus +from typing import Generator, Literal + +from typing_extensions import TypedDict, NotRequired + +from helpers.constants import HTTPMethod + + +class ResponseData(TypedDict): + status_code: str + response_models: NotRequired[dict[str, str]] + + +class MethodData(TypedDict): + integration_type: Literal['lambda',] + enable_proxy: bool + authorization_type: Literal['NONE', 'authorizer',] + method_request_parameters: NotRequired[dict[str, bool]] + method_request_models: NotRequired[dict[str, str]] + responses: list[ResponseData] + lambda_name: NotRequired[str] + + +class DeploymentResourcesApiGatewayWrapper: + """ + Syndicate api gateway deployment resources parser + """ + __slots__ = ('_data',) + + def __init__(self, data: dict): + assert data.get('resource_type') == 'api_gateway', \ + 'Invalid deployment resources' + self._data = data + + def iter_path_method_data( + self + ) -> Generator[tuple[str, HTTPMethod, MethodData], None, None]: + for path, path_data in (self._data.get('resources') or {}).items(): + _any = path_data.get('ANY') + if _any: + for method in HTTPMethod: + yield path, method, _any + else: + for method, method_data in path_data.items(): + try: + method = HTTPMethod(method) + except ValueError: + continue + yield path, method, method_data + + def iter_path_method_lambda( + self + ) -> Generator[tuple[str, HTTPMethod, str, bool], None, None]: + """ + Iterates over tuples (resource, method, lambda, has_auth) + :return: + """ + for path, method, data in self.iter_path_method_data(): + lambda_name = data.get('lambda_name') + if not lambda_name: + continue + yield path, method, lambda_name, self.has_auth(data) + + @staticmethod + def has_auth(data: MethodData) -> bool: + return data.get('authorization_type') != 'NONE' + + @staticmethod + def query_parameters(data: MethodData + ) -> Generator[tuple[str, bool], None, None]: + params = data.get('method_request_parameters') or {} + for param, required in params.items(): + yield param.split('.')[-1], required + + def json_schema(self, data: MethodData + ) -> tuple[str, dict] | None: + name = data.get('method_request_models', {}).get('application/json') + if not name: + return None + schema = self.get_schema(name) + if not schema: + return None + return name, schema + + def get_schema(self, name: str) -> dict | None: + models = self._data.get('models') or {} + model = models.get(name) + if not model: + return None + return model.get('schema') + + @staticmethod + def iter_response_models( + data: MethodData + ) -> Generator[tuple[HTTPStatus, str | None], None, None]: + for response in data.get('responses') or []: + code = HTTPStatus(int(response['status_code'])) + name = (response.get('response_models') or {}).get( + 'application/json') + yield code, name + + @property + def stage(self) -> str: + return self._data.get('deploy_stage') or '' diff --git a/src/onprem/api/schedule_retry.py b/src/onprem/api/schedule_retry.py new file mode 100644 index 000000000..d16c4caa4 --- /dev/null +++ b/src/onprem/api/schedule_retry.py @@ -0,0 +1,60 @@ +from helpers import get_logger + +_LOG = get_logger(__name__) +LIMIT = 30 +RETRY_CRON_NAME_PATTERN = 'report_retry_' + + +# def create_retry_job(event): +# report_module = importlib.import_module( +# 'lambdas.custodian_report_generation_handler.handler') +# report_module.lambda_handler(event=event, context=RequestContext()) + + +# def ensure_retry_job(): +# """ +# Make sure you've started the scheduler and set envs before invoking +# this function +# """ +# scheduler: BackgroundScheduler = SERVICE_PROVIDER.ap_job_scheduler(). \ +# scheduler +# _settings_service = SERVICE_PROVIDER.settings_service() +# _report_statistics_service = SERVICE_PROVIDER.report_statistics_service() +# _entity_schedule_mapping = {} +# +# if not _settings_service.get_send_reports(): +# _LOG.info('Send report setting is disabled. Cannot run retry jobs.') +# return +# +# _item, cursor = _report_statistics_service.get_all_not_sent_retries( +# limit=LIMIT, only_without_status=True) +# while cursor: +# new_items, cursor = _report_statistics_service.get_all_not_sent_retries( +# limit=LIMIT, last_evaluated_key=cursor, only_without_status=True) +# _item.append(new_items) +# if not _item: +# _LOG.info('No need to run retry jobs.') +# else: +# for i in _item: +# entity = f'{i.customer}_{i.tenant}_{i.report_type}' +# if scheduler.get_job(job_id=i.id): +# _LOG.debug(f'Schedule job `{i.id}` already exists') +# _entity_schedule_mapping[entity] = i.id +# else: +# if entity in _entity_schedule_mapping: +# _LOG.debug(f'There is already a cron for entity {entity}') +# elif _settings_service.max_cron_number() > len(scheduler.get_jobs()): +# _LOG.debug('Maximum number of crons reached!') +# break +# else: +# hours = (i.attempt * 15) // 60 +# params = { +# 'kwargs': {'event': i.get_json().get('event', {})}, +# 'id': i.id, 'name': RETRY_CRON_NAME_PATTERN + i.id, +# 'trigger': 'interval', +# 'minutes': (i.attempt * 15) % 60 +# } +# if hours > 0: +# params.update(hours=hours) +# scheduler.add_job(create_retry_job, **params) +# _entity_schedule_mapping[entity] = i.id diff --git a/src/onprem/requirements.txt b/src/onprem/requirements.txt new file mode 100644 index 000000000..e9db84ff0 --- /dev/null +++ b/src/onprem/requirements.txt @@ -0,0 +1,28 @@ +pynamodb==5.3.2 +boto3==1.26.80 +botocore==1.29.80 +python-dateutil==2.8.2 +requests==2.31.0 +dynamodb-json==1.3 +pymongo==4.5.0 +bcrypt==4.0.1 +bottle==0.12.25 +hvac==1.2.1 +XlsxWriter==3.1.9 +typing_extensions==4.9.0 +ruamel.yaml==0.17.16 +ruamel.yaml.clib==0.2.6 +gunicorn==20.1.0 +APScheduler==3.10.4 +pydantic==2.6.0 +cachetools==5.3.1 +python-dotenv==1.0.0 +dacite==1.8.1 +et-xmlfile==1.1.0 +openpyxl==3.0.10 +cryptography==42.0.2 +modular-sdk==5.1.1 +swagger-ui-py==23.9.23 +jwcrypto==1.5.3 +msgspec==0.18.6 +tabulate==0.9.0 \ No newline at end of file diff --git a/src/requirements.txt b/src/requirements.txt deleted file mode 100644 index 020a9d870..000000000 --- a/src/requirements.txt +++ /dev/null @@ -1,15 +0,0 @@ --r lambdas/layers/common_dependencies_layer/requirements.txt -bottle==0.12.25 -python-dotenv==1.0.0 -APScheduler==3.10.4 -bcrypt==4.0.1 -PyJWT==2.8.0 -pycryptodome==3.19.0 -et-xmlfile==1.1.0 -openpyxl==3.0.10 -ruamel.yaml==0.17.16 -ruamel.yaml.clib==0.2.6 -gunicorn==20.1.0 -XlsxWriter==3.0.3 -hvac==1.2.1 -psutil==5.8.0 \ No newline at end of file diff --git a/src/run.py b/src/run.py new file mode 100644 index 000000000..180fd402e --- /dev/null +++ b/src/run.py @@ -0,0 +1,1302 @@ +""" +[Up-to-date description] + +Available environment variables: +- ... +- ... + +Usage: python executor.py + +Exit codes: +- 0: success; +- 1: unexpected system error; +- 2: Job execution is not granted by the License Manager; +- 126: Job is event-driven and cannot be executed in consequence of invalid + credentials or conceivably some other temporal reason. Retry is allowed. +""" +from abc import ABC, abstractmethod +from concurrent.futures import Future, ThreadPoolExecutor, as_completed +from datetime import datetime, timedelta +import io +from itertools import chain +import operator +from pathlib import Path +import sys +import tempfile +import threading +import time +import traceback +from typing import Generator, Optional, cast + +from botocore.exceptions import ClientError +from c7n.config import Config +from c7n.exceptions import PolicyValidationError +from c7n.policy import Policy, PolicyCollection +from c7n.provider import clouds +from c7n.resources import load_resources +from google.auth.exceptions import GoogleAuthError +from googleapiclient.errors import HttpError +from modular_sdk.commons.constants import ENV_KUBECONFIG, ParentType +from modular_sdk.models.parent import Parent +from modular_sdk.models.tenant import Tenant +from modular_sdk.services.environment_service import EnvironmentContext +from msrestazure.azure_exceptions import CloudError + +from executor.helpers.constants import ( + ACCESS_DENIED_ERROR_CODE, + AWS_DEFAULT_REGION, + CACHE_FILE, + ExecutorError, + ExecutorMode, + INVALID_CREDENTIALS_ERROR_CODES, + ENV_AWS_DEFAULT_REGION, +) +from executor.helpers.profiling import BytesEmitter, xray_recorder as _XRAY +from executor.services import BSP +from executor.services.license_manager_service import ( + BalanceExhaustion, + InaccessibleAssets, +) +from executor.services.policy_service import PolicyDict +from executor.services.report_service import JobResult +from helpers.constants import ( + BatchJobEnv, + BatchJobType, + Cloud, + GLOBAL_REGION, + JobState, + PlatformType, + PolicyErrorType, + TS_EXCLUDED_RULES_KEY, +) +from helpers.log_helper import get_logger +from helpers.time_helper import utc_datetime, utc_iso +from models.batch_results import BatchResults +from models.job import Job +from models.scheduled_job import ScheduledJob +from services import SP +from services.ambiguous_job_service import AmbiguousJob +from services.clients import Boto3ClientFactory +from services.clients.dojo_client import DojoV2Client +from services.clients.eks_client import EKSClient +from services.clients.sts import TokenGenerator, StsClient +from services.job_lock import TenantSettingJobLock +from services.job_service import JobUpdater, NullJobUpdater +from services.platform_service import K8STokenKubeconfig, Kubeconfig, Platform +from services.report_convertors import ShardCollectionDojoConvertor +from services.reports_bucket import ( + PlatformReportsBucketKeysBuilder, + StatisticsBucketKeysBuilder, + TenantReportsBucketKeysBuilder, +) +from services.sharding import ShardsCollection, ShardsCollectionFactory, ShardsS3IO + +_LOG = get_logger(__name__) + + +class ExecutorException(Exception): + def __init__(self, error: ExecutorError): + self.error = error + + +def get_time_left() -> float: + _LOG.debug('Retrieving job time threshold') + if BSP.environment_service.is_docker(): + _LOG.debug('On prem job - using current timestamp as start time') + started_at = utc_datetime().timestamp() * 1e3 + else: + _LOG.debug('Saas - using AWS Batch startedAt attribute') + job = SP.batch.get_job(BSP.environment_service.batch_job_id()) + started_at = job.get('startedAt') or utc_datetime().timestamp() * 1e3 + + threshold = datetime.timestamp( + datetime.fromtimestamp(started_at / 1e3) + timedelta( + minutes=BSP.environment_service.job_lifetime_min()) + ) + _LOG.debug(f'Threshold: {threshold}, {datetime.fromtimestamp(threshold)}') + return threshold + + +TIME_THRESHOLD: float = get_time_left() + + +class PoliciesLoader: + __slots__ = ('_cloud', '_output_dir', '_regions', '_cache', + '_cache_period') + + def __init__(self, cloud: Cloud, output_dir: Path, + regions: set[str] | None = None, cache: str = CACHE_FILE, + cache_period: int = 30): + """ + + :param cloud: + :param output_dir: + :param regions: + :param cache: + :param cache_period: + """ + self._cloud = cloud + self._output_dir = output_dir + self._regions = regions or set() + if self._cloud != Cloud.AWS and self._regions: + _LOG.warning(f'Given regions will be ignored because the cloud is ' + f'{self._cloud}') + self._cache = cache + self._cache_period = cache_period + + def set_global_output(self, policy: Policy) -> None: + policy.options.output_dir = str( + (self._output_dir / GLOBAL_REGION).resolve() + ) + + def set_regional_output(self, policy: Policy) -> None: + policy.options.output_dir = str( + (self._output_dir / policy.options.region).resolve() + ) + + @staticmethod + def is_global(policy: Policy) -> bool: + """ + Tells whether this policy must be executed only once ignoring its + region + :param policy: + :return: + """ + if policy.provider_name != 'aws': + return True + rt = policy.resource_manager.resource_type + # s3 has one endpoint for all regions + return rt.global_resource or rt.service == 's3' + + @staticmethod + def get_policy_region(policy: Policy) -> str: + if PoliciesLoader.is_global(policy): + return GLOBAL_REGION + return policy.options.region + + def _base_config(self) -> Config: + """ + Probably, most fields not even used when we load, but just keep them + :return: + """ + match self._cloud: + case Cloud.AWS: + # load for all and just keep necessary. It does not provide + # much overhead but more convenient + regions = ['all'] + case Cloud.AZURE: + regions = ['AzureCloud'] + case _: + regions = [] + return Config.empty( + regions=regions, + cache=self._cache, + cache_period=self._cache_period, + command='c7n.commands.run', + config=None, + configs=[], + output_dir=str(self._output_dir), + subparser='run', + policy_filters=[], + resource_types=[], + verbose=None, + quiet=False, + debug=False, + skip_validation=False, + vars=None + ) + + @staticmethod + def _get_resource_types(policies: list[PolicyDict]) -> set[str]: + res = set() + for pol in policies: + rtype = pol['resource'] + if isinstance(rtype, list): + res.update(rtype) + elif '.' not in rtype: + rtype = f'aws.{rtype}' + res.add(rtype) + return res + + def prepare_policies(self, policies: list[Policy], + ) -> Generator[Policy, None, None]: + """ + Keeps only policies with regions that must be scanned. Also keeps + global policies and changes their output_dir to global. + Exceptions: + - s3, region-dependent, but API is global. So treat like global. + No need to make requests multiple times + - waf, global but its + resource_manager.resource_type.global_resource == False. + Though it's not global in resource_type class, Cloud Custodian + treats it like global and loads only once because + boto3.Session().get_available_regions('waf') returns an empty list + :param policies: + :return: + """ + global_yielded = set() + n_global, n_not_global = 0, 0 + for policy in policies: + if self.is_global(policy): + if policy.name in global_yielded: + # custom core loads all the global policies only once + # (except S3 which technically is not global). + continue + _LOG.debug(f'Global policy found: {policy.name}') + self.set_global_output(policy) + policy.options.region = AWS_DEFAULT_REGION + policy.session_factory.region = AWS_DEFAULT_REGION + global_yielded.add(policy.name) + n_global += 1 + elif not self._regions or policy.options.region in self._regions: + _LOG.debug(f'Not global policy found: {policy.name}') + n_not_global += 1 + # self.set_regional_output(policy) # Cloud Custodian does it + else: + continue + yield policy + _LOG.debug(f'Global policies: {n_global}') + _LOG.debug(f'Not global policies: {n_not_global}') + + def _load(self, policies: list[PolicyDict], + options: Config | None = None) -> list[Policy]: + """ + Unsafe load using internal CLoud Custodian API: + - does not load file, we already have policies list + - does not check duplicates, can be sure there are no them + - we almost can be sure there are all 100% valid. We should just skip + invalid instead of throwing SystemError + - don't need Structure parser from Cloud Custodian + - don't need schema validation + - don't need filters from config and some other things + :param policies: + :return: + """ + if not options: + options = self._base_config() + options.region = '' + load_resources(self._get_resource_types(policies)) + # here we should probably validate schema, but it's too time-consuming + provider_policies = {} + for policy in policies: + try: + pol = Policy(policy, options) + except PolicyValidationError: + _LOG.warning(f'Cannot load policy {policy["name"]} ' + f'dict to object. Skipping', exc_info=True) + continue + provider_policies.setdefault(pol.provider_name, []).append(pol) + + # initialize providers (copied from Cloud Custodian) + collection = PolicyCollection.from_data({}, options) + for provider_name in provider_policies: + provider = clouds[provider_name]() + p_options = provider.initialize(options) + collection += provider.initialize_policies( + PolicyCollection(provider_policies[provider_name], p_options), + p_options + ) + + # Variable expansion and non schema validation + result = [] + for p in collection: + p.expand_variables(p.get_variables()) + try: + p.validate() + except PolicyValidationError as e: + _LOG.warning(f'Policy {p.name} validation failed', + exc_info=True) + continue + except (ValueError, Exception): + _LOG.warning('Unexpected error occurred validating policy', + exc_info=True) + continue + result.append(p) + return result + + def load_from_policies(self, policies: list[PolicyDict]) -> list[Policy]: + """ + This functionality is already present inside Cloud Custodian but that + is that part of private python API, besides it does more that we need. + So, this is our small implementation which does exactly what we need + here. + :param policies: + :return: + """ + _LOG.info('Loading policies') + items = self._load(policies) + match self._cloud: + case Cloud.AWS: + items = list(self.prepare_policies(items)) + case _: + for pol in items: + self.set_global_output(pol) + _LOG.info('Policies were loaded') + return items + + def load_from_regions_to_rules(self, policies: list[PolicyDict], + mapping: dict[str, set[str]] + ) -> list[Policy]: + """ + Expected mapping: + { + 'eu-central-1': {'epam-aws-005..', 'epam-aws-006..'}, + 'eu-west-1': {'epam-aws-006..', 'epam-aws-007..'} + } + :param policies: + :param mapping: + :return: + """ + rules = set(chain.from_iterable(mapping.values())) # all rules + if self._cloud != Cloud.AWS: + # load all policies ignoring region and set global to all + items = self._load(policies) + items = list(filter(lambda p: p.name in rules, items)) + for policy in items: + self.set_global_output(policy) + return items + # self._cloud == Cloud.AWS + # first -> I load all the rules for regions that came + us-east-1. + # second -> execute self.prepare_policies in order to set global + # third -> For each region I keep only necessary rules + config = self._base_config() + config.regions = [*mapping.keys(), AWS_DEFAULT_REGION] + items = [] + for policy in self.prepare_policies(self._load(policies, config)): + if self.is_global(policy) and policy.name in rules: + items.append(policy) + elif policy.name in (mapping.get(policy.options.region) or ()): + items.append(policy) + return items + + +class Runner(ABC): + FailedPolicies = dict[ + tuple[str, str], + tuple[PolicyErrorType, Optional[str], list[str]] + ] + cloud: Cloud | None = None + + def __init__(self, policies: list[Policy]): + self._policies = policies + + self._failed: Runner.FailedPolicies = {} + + self._is_ongoing = False + self._error_type: PolicyErrorType = PolicyErrorType.SKIPPED # default + self._message = None + self._exception = None + + self._lock = threading.Lock() + + @classmethod + def factory(cls, cloud: Cloud, policies: list[Policy]) -> 'Runner': + """ + Builds a necessary runner instance based on cloud. + :param cloud: + :param policies: + :return: + """ + # TODO refactor, make runner not abstract and move policy + # error handling (depending on policy's cloud) to a separate class + _class = next( + filter(lambda sub: sub.cloud == cloud, cls.__subclasses__()) + ) + return _class(policies) + + @property + def failed(self) -> FailedPolicies: + return self._failed + + @_XRAY.capture('Run policies consistently') + def start(self): + self._is_ongoing = True + for policy in self._policies: + self._handle_errors(policy=policy) + self._is_ongoing = False + + @_XRAY.capture('Run policies concurrently ') + def start_threads(self): + self._is_ongoing = True + with ThreadPoolExecutor() as executor: + future_policy = { + executor.submit(self._call_policy, policy): policy + for policy in self._policies + } + for future in as_completed(future_policy): + self._handle_errors(policy=future_policy[future], + future=future) + self._is_ongoing = False + + def _call_policy(self, policy: Policy): + if TIME_THRESHOLD <= utc_datetime().timestamp(): + if self._is_ongoing: + _LOG.warning('Job time threshold has been exceeded. ' + 'All the consequent rules will be skipped.') + self._is_ongoing = False + self._error_type = PolicyErrorType.SKIPPED + self._message = ('Job time exceeded the maximum ' + 'possible execution time') + if not self._is_ongoing: + self._add_failed( + region=PoliciesLoader.get_policy_region(policy), + policy=policy.name, + error_type=self._error_type, + message=self._message, + exception=self._exception + ) + return + policy() + + def _add_failed(self, region: str, policy: str, + error_type: PolicyErrorType, + exception: Exception | None = None, + message: str | None = None): + tb = [] + if exception: + te = traceback.TracebackException.from_exception(exception) + tb.extend(te.format()) + if not message: + message = ''.join(te.format_exception_only()) + with self._lock: + self._failed[(region, policy)] = (error_type, message, tb) + + @abstractmethod + def _handle_errors(self, policy: Policy, future: Future | None = None): + ... + + +class AWSRunner(Runner): + cloud = Cloud.AWS + + def _handle_errors(self, policy: Policy, future: Future | None = None): + name, region = policy.name, PoliciesLoader.get_policy_region(policy) + try: + future.result() if future else self._call_policy(policy) + except ClientError as error: + error_code = error.response['Error']['Code'] + error_reason = error.response['Error']['Message'] + + if error_code in ACCESS_DENIED_ERROR_CODE.get(self.cloud): + _LOG.warning(f'Policy \'{name}\' is skipped. ' + f'Reason: \'{error_reason}\'') + self._add_failed( + region=region, policy=name, + error_type=PolicyErrorType.ACCESS, + message=error_reason + ) + elif error_code in INVALID_CREDENTIALS_ERROR_CODES.get(self.cloud): + _LOG.warning( + f'Policy \'{name}\' is skipped due to invalid ' + f'credentials. All the subsequent rules will be skipped') + self._add_failed( + region=region, policy=name, + error_type=PolicyErrorType.CREDENTIALS, + message=error_reason + ) + self._is_ongoing = False + self._error_type = PolicyErrorType.CREDENTIALS + self._message = error_reason + else: + _LOG.warning(f'Policy \'{name}\' has failed. ' + f'Client error occurred. ' + f'Code: \'{error_code}\'. ' + f'Reason: {error_reason}') + self._add_failed( + region=region, policy=name, + error_type=PolicyErrorType.CLIENT, + exception=error + ) + except Exception as error: + _LOG.exception(f'Policy {name} has failed with unexpected error') + self._add_failed( + region=region, policy=name, + error_type=PolicyErrorType.INTERNAL, + exception=error + ) + + +class AZURERunner(Runner): + cloud = Cloud.AZURE + + def _handle_errors(self, policy: Policy, future: Future | None = None): + name, region = policy.name, PoliciesLoader.get_policy_region(policy) + try: + future.result() if future else self._call_policy(policy) + except CloudError as error: + error_code = error.error + error_reason = error.message.split(':')[-1].strip() + if error_code in INVALID_CREDENTIALS_ERROR_CODES.get(self.cloud): + _LOG.warning( + f'Policy \'{name}\' is skipped due to invalid ' + f'credentials. All the subsequent rules will be skipped') + self._add_failed( + region=region, policy=name, + error_type=PolicyErrorType.CREDENTIALS, + message=error_reason + ) + self._is_ongoing = False + self._error_type = PolicyErrorType.CREDENTIALS + self._message = error_reason + else: + _LOG.warning(f'Policy \'{name}\' has failed. ' + f'Client error occurred. ' + f'Code: \'{error_code}\'. ' + f'Reason: {error_reason}') + self._add_failed( + region=region, policy=name, + error_type=PolicyErrorType.CLIENT, + exception=error + ) + except Exception as error: + _LOG.exception(f'Policy {name} has failed with unexpected error') + self._add_failed( + region=region, policy=name, + error_type=PolicyErrorType.INTERNAL, + exception=error + ) + + +class GCPRunner(Runner): + cloud = Cloud.GOOGLE + + def _handle_errors(self, policy: Policy, future: Future | None = None): + name, region = policy.name, PoliciesLoader.get_policy_region(policy) + try: + future.result() if future else self._call_policy(policy) + except GoogleAuthError as error: + error_reason = str(error.args[-1]) + _LOG.warning( + f'Policy \'{name}\' is skipped due to invalid ' + f'credentials. All the subsequent rules will be skipped') + self._add_failed( + region=region, policy=name, + error_type=PolicyErrorType.CREDENTIALS, + message=error_reason + ) + self._is_ongoing = False + self._error_type = PolicyErrorType.CREDENTIALS + self._message = error_reason + except HttpError as error: + if error.status_code == 403: + self._add_failed( + region=region, policy=name, + error_type=PolicyErrorType.ACCESS, + message=error.reason + ) + else: + self._add_failed( + region=region, policy=name, + error_type=PolicyErrorType.CLIENT, + exception=error + ) + except Exception as error: + _LOG.exception(f'Policy {name} has failed with unexpected error') + self._add_failed( + region=region, policy=name, + error_type=PolicyErrorType.INTERNAL, + exception=error + ) + + +class K8SRunner(Runner): + cloud = Cloud.KUBERNETES + + def _handle_errors(self, policy: Policy, future: Future | None = None): + name, region = policy.name, PoliciesLoader.get_policy_region(policy) + try: + future.result() if future else self._call_policy(policy) + except Exception as error: + _LOG.exception(f'Policy {name} has failed with unexpected error') + self._add_failed( + region=region, + policy=name, + error_type=PolicyErrorType.INTERNAL, + exception=error + ) + + +def fetch_licensed_ruleset_list(tenant: Tenant, licensed: dict): + """ + Designated to execute preliminary licensed Job instantiation, which + verifies permissions to create a demanded entity. + :parameter tenant: Tenant of the issuer + :parameter licensed: Dict - non-empty collection of licensed rulesets + :raises: ExecutorException - given parameter absence or prohibited action + :return: List[Dict] + """ + job_id = BSP.environment_service.job_id() + + payload = dict( + job_id=job_id, + customer=tenant.customer_name, + tenant=tenant.name, + ruleset_map=licensed + ) + _LOG.debug(f'Going to license a Job:\'{job_id}\'.') + + licensed_job, issue = None, '' + + try: + licensed_job = BSP.license_manager_service.instantiate_licensed_job_dto( + **payload + ) + except BalanceExhaustion as fj: + issue = str(fj) + + except InaccessibleAssets as ij: + issue = str(ij) + rulesets = list(ij) + + customer_name = tenant.customer_name + customer = SP.modular_client.customer_service().get(customer_name) + + scheduled_job_name = BSP.environment_service.scheduled_job_name() + mail_configuration = SP.setting_service.get_mail_configuration() + + if scheduled_job_name and mail_configuration and rulesets and customer: + header = f'Scheduled-Job:\'{scheduled_job_name}\' of ' \ + f'\'{customer_name}\' customer' + + _LOG.info(f'{header} - is going to be retrieved.') + job = SP.scheduler_service.get( + name=scheduled_job_name, customer=customer_name + ) + if not job: + _LOG.error(f'{header} - could not be found.') + + if not SP.scheduler_service.update_job(item=job, is_enabled=False): + _LOG.error(f'{header} - could not be deactivated.') + else: + _LOG.info(f'{header} - has been deactivated') + subject = f'{tenant.name} job-rescheduling notice' + if not BSP.notification_service.send_rescheduling_notice_notification( + recipients=customer.admins, subject=subject, + tenant=tenant, scheduled_job_name=scheduled_job_name, + ruleset_list=rulesets, + customer=customer_name): + _LOG.error('Job-Rescheduling notice was not sent.') + else: + _LOG.info('Job-Rescheduling notice has been sent.') + + elif not mail_configuration: + _LOG.warning( + 'No mail configuration has been attached, skipping ' + f' job-rescheduling notice of \'{scheduled_job_name}\'.' + ) + + if not licensed_job: + reason = 'Job execution could not be granted.' + if issue: + reason += f' {issue}' + _LOG.error(reason) + raise ExecutorException(ExecutorError.LM_DID_NOT_ALLOW) + + _LOG.info(f'Job {job_id} has been permitted to be commenced.') + return BSP.license_manager_service.instantiate_job_sourced_ruleset_list( + licensed_job_dto=licensed_job + ) + + +@_XRAY.capture('Fetch licensed ruleset') +def get_licensed_ruleset_dto_list(tenant: Tenant) -> list: + """ + Preliminary step, given an affected license and respective ruleset(s), + can raise + """ + affected_license = BSP.environment_service.affected_licenses() + licensed_rulesets = BSP.environment_service.licensed_ruleset_map( + license_key_list=affected_license + ) + licensed_ruleset_dto_list = [] + if affected_license and licensed_rulesets: + licensed_ruleset_dto_list = fetch_licensed_ruleset_list( + tenant=tenant, licensed=licensed_rulesets + ) + return licensed_ruleset_dto_list + + +@_XRAY.capture('Upload to SIEM') +def upload_to_siem(tenant: Tenant, collection: ShardsCollection, + job: AmbiguousJob, platform: Platform | None = None): + it = SP.integration_service.get_dojo_adapters(tenant, True) + for dojo, configuration in it: + convertor = ShardCollectionDojoConvertor.from_scan_type( + configuration.scan_type + ) + configuration = configuration.substitute_fields(job, platform) + client = DojoV2Client( + url=dojo.url, + api_key=SP.defect_dojo_service.get_api_key(dojo) + ) + try: + client.import_scan( + scan_type=configuration.scan_type, + scan_date=utc_datetime(), + product_type_name=configuration.product_type, + product_name=configuration.product, + engagement_name=configuration.engagement, + test_title=configuration.test, + data=convertor.convert(collection), + tags=SP.integration_service.job_tags_dojo(job) + ) + except Exception: + _LOG.exception('Unexpected error occurred pushing to dojo') + + +@_XRAY.capture('Get credentials') +def get_credentials(tenant: Tenant, + batch_results: BatchResults | None = None) -> dict: + """ + Tries to retrieve credentials to scan the given tenant with such + priorities: + 1. env "CREDENTIALS_KEY" - gets key name and then gets credentials + from SSM. This is the oldest solution, in can sometimes be used if + the job is standard and a user has given credentials directly; + The SSM parameter is removed after the creds are received. + 2. Only for event-driven jobs. Gets credentials_key (SSM parameter name) + from "batch_result.credentials_key". Currently, the option is obsolete. + API in no way will set credentials key there. But maybe some time... + 3. 'CUSTODIAN_ACCESS' key in the tenant's parent_map. It points to the + parent with type 'CUSTODIAN_ACCESS' as well. That parent is linked + to an application with credentials + 4. Maestro management_parent_id -> management creds. Tries to resolve + management parent from tenant and then management credentials. This + option can be used only if the corresponding env is set to 'true'. + Must be explicitly allowed because the option is not safe. + 5. Checks whether instance by default has access to the given tenant + If not credentials are found, ExecutorException is raised + """ + mcs = SP.modular_client.maestro_credentials_service() + _log_start = 'Trying to get credentials from ' + credentials = {} + # 1. + if not credentials: + _LOG.info(_log_start + '\'CREDENTIALS_KEY\' env') + credentials = BSP.credentials_service.get_credentials_from_ssm() + if credentials and tenant.cloud == Cloud.GOOGLE: + credentials = BSP.credentials_service.google_credentials_to_file( + credentials) + # 2. + if not credentials and batch_results and batch_results.credentials_key: + _LOG.info(_log_start + 'batch_results.credentials_key') + credentials = BSP.credentials_service.get_credentials_from_ssm( + batch_results.credentials_key) + if credentials and tenant.cloud == Cloud.GOOGLE: + credentials = BSP.credentials_service.google_credentials_to_file( + credentials) + # 3. + if not credentials: + _LOG.info(_log_start + '`CUSTODIAN_ACCESS` parent') + parent = SP.modular_client.parent_service().get_linked_parent_by_tenant( + tenant=tenant, + type_=ParentType.CUSTODIAN_ACCESS + ) + if parent: + application = SP.modular_client.application_service().get_application_by_id(parent.application_id) + if application: + _creds = mcs.get_by_application(application, tenant) + if _creds: + credentials = _creds.dict() + # 4. + if not credentials and BSP.environment_service.is_management_creds_allowed(): + _LOG.info(_log_start + 'Maestro management parent & application') + _creds = mcs.get_by_tenant(tenant=tenant) + if _creds: # not a dict + credentials = _creds.dict() + # 5 + if not credentials: + _LOG.info(_log_start + 'instance profile') + # TODO refactor + do the same for other clouds. Try to resolve + # from envs + try: + aid = StsClient.factory().build().get_caller_identity()['Account'] + _LOG.debug('Instance profile found') + if aid == tenant.project: + _LOG.info('Instance profile credentials match to tenant id') + return {} + except (Exception, ClientError) as e: + _LOG.warning(f'No instance credentials found: {e}') + + if credentials: + credentials = mcs.complete_credentials_dict( + credentials=credentials, + tenant=tenant + ) + return credentials + raise ExecutorException(ExecutorError.NO_CREDENTIALS) + + +def get_platform_credentials(platform: Platform) -> dict: + """ + Credentials for platform (k8s) only. This should be refactored somehow. + Raises ExecutorException if not credentials are found + :param platform: + :return: + """ + # TODO K8S request a short-lived token here from long-lived in case + # it's possible + # if creds.token: + # _LOG.info('Request a temp token') + # conf = kubernetes.client.Configuration() + # conf.host = creds.endpoint + # conf.api_key['authorization'] = creds.token + # conf.api_key_prefix['authorization'] = 'Bearer' + # conf.ssl_ca_cert = creds.ca_file() + # kubernetes.client.AuthenticationV1TokenRequest() + # with kubernetes.client.ApiClient(conf) as client: + # kubernetes.client.CoreV1Api(client).create_namespaced_service_account_token('readonly-user') + app = SP.modular_client.application_service().get_application_by_id( + platform.parent.application_id + ) + token = BSP.credentials_service.get_credentials_from_ssm() + kubeconfig = {} + if app.secret: + kubeconfig = SP.modular_client.assume_role_ssm_service().get_parameter( + app.secret) or {} # noqa + + if kubeconfig and token: + _LOG.debug('Kubeconfig and custom token are provided. ' + 'Combining both') + config = Kubeconfig(kubeconfig) + session = str(int(time.time())) + user = f'user-{session}' + context = f'context-{session}' + cluster = next(config.cluster_names()) # always should be 1 at least + + config.add_user(user, token) + config.add_context(context, cluster, user) + config.current_context = context + return {ENV_KUBECONFIG: str(config.to_temp_file())} + elif kubeconfig: + _LOG.debug('Only kubeconfig is provided') + config = Kubeconfig(kubeconfig) + return {ENV_KUBECONFIG: str(config.to_temp_file())} + if platform.type != PlatformType.EKS: + _LOG.warning('No kubeconfig provided and platform is not EKS') + raise ExecutorException(ExecutorError.NO_CREDENTIALS) + _LOG.debug('Kubeconfig and token are not provided. ' + 'Using management creds for EKS') + tenant = SP.modular_client.tenant_service().get(platform.tenant_name) + parent = SP.modular_client.parent_service().get_linked_parent_by_tenant( + tenant=tenant, type_=ParentType.AWS_MANAGEMENT + ) + if not parent: + _LOG.warning('Parent AWS_MANAGEMENT not found') + raise ExecutorException(ExecutorError.NO_CREDENTIALS) + application = SP.modular_client.application_service().get_application_by_id(parent.application_id) + if not application: + _LOG.warning('Management application is not found') + raise ExecutorException(ExecutorError.NO_CREDENTIALS) + creds = SP.modular_client.maestro_credentials_service().get_by_application( + application, tenant + ) + if not creds: + _LOG.warning(f'No credentials in ' + f'application: {application.application_id}') + raise ExecutorException(ExecutorError.NO_CREDENTIALS) + cluster = EKSClient.factory().from_keys( + aws_access_key_id=creds.AWS_ACCESS_KEY_ID, + aws_secret_access_key=creds.AWS_SECRET_ACCESS_KEY, + aws_session_token=creds.AWS_SESSION_TOKEN, + region_name=platform.region + ).describe_cluster(platform.name) + if not cluster: + _LOG.error(f'No cluster with name: {platform.name} ' + f'in region: {platform.region}') + raise ExecutorException(ExecutorError.NO_CREDENTIALS) + sts = Boto3ClientFactory('sts').from_keys( + aws_access_key_id=creds.AWS_ACCESS_KEY_ID, + aws_secret_access_key=creds.AWS_SECRET_ACCESS_KEY, + aws_session_token=creds.AWS_SESSION_TOKEN, + region_name=AWS_DEFAULT_REGION + ) + token_config = K8STokenKubeconfig( + endpoint=cluster['endpoint'], + ca=cluster['certificateAuthority']['data'], + token=TokenGenerator(sts).get_token(platform.name) + ) + return {ENV_KUBECONFIG: str(token_config.to_temp_file())} + + +@_XRAY.capture('Get rules to exclude') +def get_rules_to_exclude(tenant: Tenant) -> set[str]: + """ + Takes into consideration rules that are excluded for that specific tenant + and for its customer + :param tenant: + :return: + """ + _LOG.info('Querying excluded rules') + excluded = set() + ts = SP.modular_client.tenant_settings_service().get( + tenant_name=tenant.name, + key=TS_EXCLUDED_RULES_KEY + ) + if ts: + _LOG.info('Tenant setting with excluded rules is found') + excluded.update(ts.value.as_dict().get('rules') or ()) + cs = SP.modular_client.customer_settings_service().get_nullable( + customer_name=tenant.customer_name, + key=TS_EXCLUDED_RULES_KEY + ) + if cs: + _LOG.info('Customer setting with excluded rules is found') + excluded.update(cs.value.get('rules') or ()) + return excluded + + +@_XRAY.capture('Batch results job') +def batch_results_job(batch_results: BatchResults): + _XRAY.put_annotation('batch_results_id', batch_results.id) + + temp_dir = tempfile.TemporaryDirectory() + work_dir = Path(temp_dir.name) + + tenant: Tenant = SP.modular_client.tenant_service().get(batch_results.tenant_name) + cloud = Cloud[tenant.cloud.upper()] + credentials = get_credentials(tenant, batch_results) + + policies = BSP.policies_service.separate_ruleset( + from_=BSP.policies_service.ensure_event_driven_ruleset(cloud), + exclude=get_rules_to_exclude(tenant), + keep=set( + chain.from_iterable(batch_results.regions_to_rules().values()) + ) + ) + loader = PoliciesLoader( + cloud=cloud, + output_dir=work_dir, + regions=BSP.environment_service.target_regions() + ) + with EnvironmentContext(credentials, reset_all=False): + runner = Runner.factory(cloud, loader.load_from_regions_to_rules( + policies, + batch_results.regions_to_rules() + )) + match BSP.environment_service.executor_mode(): + case ExecutorMode.CONSISTENT: + runner.start() + case ExecutorMode.CONCURRENT: + runner.start_threads() + + result = JobResult(work_dir, cloud) + keys_builder = TenantReportsBucketKeysBuilder(tenant) + collection = ShardsCollectionFactory.from_cloud(cloud) + collection.put_parts(result.iter_shard_parts()) + meta = result.rules_meta() + collection.meta = meta + + _LOG.info('Going to upload to SIEM') + upload_to_siem( + tenant=tenant, + collection=collection, + job=AmbiguousJob(batch_results), + ) + + collection.io = ShardsS3IO( + bucket=SP.environment_service.default_reports_bucket_name(), + key=keys_builder.ed_job_result(batch_results), + client=SP.s3 + ) + _LOG.debug('Writing job report') + collection.write_all() # writes job report + + latest = ShardsCollectionFactory.from_cloud(cloud) + latest.io = ShardsS3IO( + bucket=SP.environment_service.default_reports_bucket_name(), + key=keys_builder.latest_key(), + client=SP.s3 + ) + _LOG.debug('Pulling latest state') + latest.fetch_by_indexes(collection.shards.keys()) + latest.fetch_meta() + + difference = collection - latest + + _LOG.debug('Writing latest state') + latest.update(collection) + latest.update_meta(meta) + latest.write_all() + latest.write_meta() + + _LOG.debug('Writing difference') + difference.io = ShardsS3IO( + bucket=SP.environment_service.default_reports_bucket_name(), + key=keys_builder.ed_job_difference(batch_results), + client=SP.s3 + ) + difference.write_all() + + _LOG.info('Writing statistics') + SP.s3.gz_put_json( + bucket=SP.environment_service.get_statistics_bucket_name(), + key=StatisticsBucketKeysBuilder.job_statistics(batch_results), + obj=result.statistics(tenant, runner.failed) + ) + temp_dir.cleanup() + + +def multi_account_event_driven_job() -> int: + for br_uuid in BSP.environment_service.batch_results_ids(): + _LOG.info(f'Processing batch results with id {br_uuid}') + actions = [] + batch_results = BatchResults.get_nullable(br_uuid) + if not batch_results: + _LOG.warning('Somehow batch results item does not exist. Skipping') + continue + if batch_results.status == JobState.SUCCEEDED.value: + _LOG.info('Batch results already succeeded. Skipping') + continue + try: + _LOG.info(f'Starting job for batch result') + batch_results_job(batch_results) + _LOG.info(f'Job for batch result {br_uuid} has finished') + actions.append(BatchResults.status.set(JobState.SUCCEEDED.value)) + except ExecutorException as e: + _LOG.exception(f'Executor exception {e.error} occurred') + actions.append(BatchResults.status.set(JobState.FAILED.value)) + actions.append(BatchResults.reason.set(traceback.format_exc())) + except Exception: + _LOG.exception('Unexpected exception occurred') + actions.append(BatchResults.status.set(JobState.FAILED.value)) + actions.append(BatchResults.reason.set(traceback.format_exc())) + actions.append(BatchResults.stopped_at.set(utc_iso())) + _LOG.info('Saving batch results item') + batch_results.update(actions=actions) + Path(CACHE_FILE).unlink(missing_ok=True) + return 0 + + +def update_scheduled_job() -> None: + """ + Updates 'last_execution_time' in scheduled job item if + this is a scheduled job. + """ + _LOG.info('Updating scheduled job item in DB') + scheduled_job_name = BSP.env.scheduled_job_name() + if not scheduled_job_name: + _LOG.info('The job is not scheduled. No scheduled job ' + 'item to update. Skipping') + return + _LOG.info('The job is scheduled. Updating the ' + '\'last_execution_time\' in scheduled job item') + item = ScheduledJob(id=scheduled_job_name, type=ScheduledJob.default_type) + item.update(actions=[ScheduledJob.last_execution_time.set(utc_iso())]) + + +def single_account_standard_job() -> int: + # in case it's a standard job , tenant_name will always exist + tenant_name = cast(str, BSP.env.tenant_name()) + tenant = cast(Tenant, SP.modular_client.tenant_service().get(tenant_name)) + + if job_id := BSP.env.job_id(): + # custodian job id, present only for standard jobs + job = cast(Job, SP.job_service.get_nullable(job_id)) + if BSP.env.is_docker(): + updater = JobUpdater(job) + else: + updater = NullJobUpdater(job) # updated in caas-job-updater + else: # scheduled job, generating it dynamically + updater = JobUpdater.from_batch_env(BSP.env.environment) + updater.save() + job = updater.job + BSP.env.override_environment({BatchJobEnv.CUSTODIAN_JOB_ID: job.id}) + + if BSP.env.is_scheduled(): # locking scanned regions + TenantSettingJobLock(tenant_name).acquire( + job_id=job.id, + regions=BSP.env.target_regions() or {GLOBAL_REGION} + ) + update_scheduled_job() + + updater.created_at = utc_iso() + updater.started_at = utc_iso() + updater.status = JobState.RUNNING + updater.update() + + temp_dir = tempfile.TemporaryDirectory() + + try: + standard_job(job, tenant, Path(temp_dir.name)) + updater.status = JobState.SUCCEEDED + updater.stopped_at = utc_iso() + code = 0 + except ExecutorException as e: + _LOG.exception(f'Executor exception {e.error} occurred') + # in case the job has failed, we should update it here even if it's + # saas installation because we cannot retrieve traceback from + # caas-job-updater lambda + updater = JobUpdater.from_job_id(job.id) + updater.status = JobState.FAILED + updater.stopped_at = utc_iso() + updater.reason = e.error.value + match e.error: + case ExecutorError.LM_DID_NOT_ALLOW: + code = 2 + case _: + code = 1 + except Exception: + _LOG.exception('Unexpected error occurred') + updater = JobUpdater.from_job_id(job.id) + updater.status = JobState.FAILED + updater.stopped_at = utc_iso() + updater.reason = ExecutorError.INTERNAL + code = 1 + finally: + Path(CACHE_FILE).unlink(missing_ok=True) + TenantSettingJobLock(tenant_name).release(job.id) + temp_dir.cleanup() + + updater.update() + + if BSP.env.is_docker() and BSP.env.is_licensed_job(): + _LOG.info('The job is licensed on premises. Updating in LM') + BSP.license_manager_service.update_job_in_license_manager( + job_id=job.id, + created_at=job.created_at, + started_at=job.started_at, + stopped_at=job.stopped_at, + status=job.status + ) + return code + + +@_XRAY.capture('Standard job') +def standard_job(job: Job, tenant: Tenant, work_dir: Path): + cloud: Cloud # not cloud but rather domain + platform: Platform | None = None + if pid := BSP.env.platform_id(): + parent = cast( + Parent, + SP.modular_client.parent_service().get_parent_by_id(pid) + ) + platform = Platform(parent) + cloud = Cloud.KUBERNETES + else: + cloud = Cloud[tenant.cloud.upper()] + + _LOG.info(f'{BSP.env.job_type().capitalize()} job \'{job.id}\' ' + f'has started: {cloud=}, {tenant.name=}, {platform=}') + _LOG.debug(f'Entire sys.argv: {sys.argv}') + _LOG.debug(f'Environment: {BSP.env}') + _XRAY.put_annotation('job_id', job.id) + _XRAY.put_annotation('tenant_name', tenant.name) + _XRAY.put_metadata('cloud', cloud.value) + + licensed_urls = map(operator.itemgetter('s3_path'), + get_licensed_ruleset_dto_list(tenant)) + standard_urls = map(SP.ruleset_service.download_url, + BSP.policies_service.get_standard_rulesets()) + + if platform: + credentials = get_platform_credentials(platform) + else: + credentials = get_credentials(tenant) + + policies = BSP.policies_service.get_policies( + urls=chain(licensed_urls, standard_urls), + keep=set(job.rules_to_scan), + exclude=get_rules_to_exclude(tenant) + ) + + loader = PoliciesLoader( + cloud=cloud, + output_dir=work_dir, + regions=BSP.env.target_regions() + ) + + with EnvironmentContext(credentials, reset_all=False): + runner = Runner.factory(cloud, loader.load_from_policies(policies)) + match BSP.environment_service.executor_mode(): + case ExecutorMode.CONSISTENT: + runner.start() + case ExecutorMode.CONCURRENT: + runner.start_threads() + result = JobResult(work_dir, cloud) + if platform: + keys_builder = PlatformReportsBucketKeysBuilder(platform) + else: + keys_builder = TenantReportsBucketKeysBuilder(tenant) + + collection = ShardsCollectionFactory.from_cloud(cloud) + collection.put_parts(result.iter_shard_parts()) + meta = result.rules_meta() + collection.meta = meta + + _LOG.info('Going to upload to SIEM') + upload_to_siem(tenant=tenant, collection=collection, + job=AmbiguousJob(job), platform=platform) + + collection.io = ShardsS3IO( + bucket=SP.environment_service.default_reports_bucket_name(), + key=keys_builder.job_result(job), + client=SP.s3 + ) + + _LOG.debug('Writing job report') + collection.write_all() # writes job report + + latest = ShardsCollectionFactory.from_cloud(cloud) + latest.io = ShardsS3IO( + bucket=SP.environment_service.default_reports_bucket_name(), + key=keys_builder.latest_key(), + client=SP.s3 + ) + + _LOG.debug('Pulling latest state') + latest.fetch_by_indexes(collection.shards.keys()) + latest.fetch_meta() + + _LOG.debug('Writing latest state') + latest.update(collection) + latest.update_meta(meta) + latest.write_all() + latest.write_meta() + + _LOG.info('Writing statistics') + SP.s3.gz_put_json( + bucket=SP.environment_service.get_statistics_bucket_name(), + key=StatisticsBucketKeysBuilder.job_statistics(job), + obj=result.statistics(tenant, runner.failed) + ) + _LOG.info(f'Job \'{job.id}\' has ended') + + +def main(command: list[str] | None = None, environment: dict | None = None): + env = environment or {} + env.setdefault(ENV_AWS_DEFAULT_REGION, AWS_DEFAULT_REGION) + BSP.environment_service.override_environment(env) + + buffer = io.BytesIO() + _XRAY.configure(emitter=BytesEmitter(buffer)) # noqa + + _XRAY.begin_segment('AWS Batch job') + sampled = _XRAY.is_sampled() + _LOG.info(f'Batch job is {"" if sampled else "NOT "}sampled') + _XRAY.put_annotation('batch_job_id', BSP.env.batch_job_id()) + + match BSP.environment_service.job_type(): + case BatchJobType.EVENT_DRIVEN: + _LOG.info('Starting event driven job') + code = multi_account_event_driven_job() + case _: # BatchJobType.STANDARD | BatchJobType.SCHEDULED + _LOG.info('Starting standard job') + code = single_account_standard_job() + + _XRAY.end_segment() + + if sampled: + _LOG.debug('Writing xray data to S3') + buffer.seek(0) + SP.s3.gz_put_object( + bucket=SP.environment_service.get_statistics_bucket_name(), + key=StatisticsBucketKeysBuilder.xray_log(BSP.env.batch_job_id()), + body=buffer + ) + _LOG.info('Finished') + sys.exit(code) + + +if __name__ == '__main__': + main(command=sys.argv) diff --git a/src/scheduler/__init__.py b/src/scheduler/__init__.py index 51437fa8d..14bf0168e 100644 --- a/src/scheduler/__init__.py +++ b/src/scheduler/__init__.py @@ -1 +1 @@ -from .ap_job_scheduler import APJobScheduler +from scheduler.ap_job_scheduler import APJobScheduler diff --git a/src/scheduler/ap_job_scheduler.py b/src/scheduler/ap_job_scheduler.py index 1901db213..7d352198f 100644 --- a/src/scheduler/ap_job_scheduler.py +++ b/src/scheduler/ap_job_scheduler.py @@ -1,4 +1,6 @@ +import json import os +from functools import cached_property from http import HTTPStatus from typing import Optional @@ -10,24 +12,25 @@ from apscheduler.triggers.interval import IntervalTrigger from apscheduler.util import datetime_to_utc_timestamp from bson.binary import Binary -from modular_sdk.models.pynamodb_extension.base_model import build_mongodb_uri from modular_sdk.models.tenant import Tenant from pymongo import ASCENDING, MongoClient from pymongo.collection import Collection from pymongo.errors import DuplicateKeyError -from connections.batch_extension.base_job_client import SUBPROCESS_HANDLER -from helpers import build_response -from helpers.constants import BATCH_ENV_SCHEDULED_JOB_NAME -from helpers.constants import CUSTODIAN_TYPE, SCHEDULED_JOB_TYPE -from helpers.constants import ENV_MONGODB_DATABASE, ENV_MONGODB_USER, \ - ENV_MONGODB_PASSWORD, ENV_MONGODB_URL +from helpers import RequestContext +from helpers.constants import CAASEnv, CUSTODIAN_TYPE, SCHEDULED_JOB_TYPE, \ + BatchJobEnv, ReportDispatchStatus +from helpers.lambda_response import ResponseFactory from helpers.log_helper import get_logger from helpers.time_helper import utc_iso +from models.report_statistics import ReportStatistics from models.scheduled_job import SJ_ID_ATTR, SJ_CREATION_DATE_ATTR, \ SJ_TYPE_ATTR, SJ_CONTEXT_ATTR, SJ_LAST_EXECUTION_TIME_ATTR from models.scheduled_job import ScheduledJob, SCHEDULED_JOBS_TABLE_NAME +from services.clients.batch import SubprocessBatchClient from services.clients.scheduler import AbstractJobScheduler +from services.report_statistics_service import ReportStatisticsService +from services.setting_service import SettingsService try: import cPickle as pickle @@ -40,6 +43,7 @@ 'minute', 'minutes', 'hour', 'hours', 'day', 'days', 'week', 'weeks', 'second', 'seconds', } +RETRY_CRON_NAME_PATTERN = 'report_retry_' class CustomMongoDBJobStore(MongoDBJobStore): @@ -130,27 +134,22 @@ def _get_jobs(self, conditions): class APJobScheduler(AbstractJobScheduler): - def __init__(self): - self._scheduler = None + def __init__(self, batch_client: SubprocessBatchClient, + setting_service: SettingsService): + self._batch_client = batch_client + self.setting_service = setting_service - @property + @cached_property def scheduler(self) -> BackgroundScheduler: - """ - Mock this in order not to init MONGODB_HANDLER in tests - """ - if not self._scheduler: - self._scheduler = BackgroundScheduler() - self._scheduler.configure(jobstores={ - 'default': CustomMongoDBJobStore( - database=os.getenv(ENV_MONGODB_DATABASE), - collection=SCHEDULED_JOBS_TABLE_NAME, - client=MongoClient(build_mongodb_uri( - os.getenv(ENV_MONGODB_USER), - os.getenv(ENV_MONGODB_PASSWORD), - os.getenv(ENV_MONGODB_URL) - ))) # or BaseModel.mongodb_handler().mongodb.client - }) - return self._scheduler + scheduler = BackgroundScheduler() + scheduler.configure(jobstores={ + 'default': CustomMongoDBJobStore( + database=os.getenv(CAASEnv.MONGO_DATABASE), + collection=SCHEDULED_JOBS_TABLE_NAME, + client=MongoClient(os.getenv(CAASEnv.MONGO_URI)) + ) # or BaseModel.mongodb_handler().mongodb.client + }) + return scheduler def start(self): self.scheduler.start() @@ -161,7 +160,8 @@ def _valid_cron(schedule: str) -> CronTrigger: May raise ValueError """ return CronTrigger.from_crontab( - schedule.strip().replace('cron', '').strip('()')) + schedule.strip().replace('cron', '').strip('()') + ) @staticmethod def _valid_interval(schedule: str) -> IntervalTrigger: @@ -192,29 +192,42 @@ def derive_trigger(self, schedule: str) -> BaseTrigger: if error: _LOG.warning(f'User has sent invalid schedule ' f'expression: \'{schedule}\'') - return build_response( - code=HTTPStatus.BAD_REQUEST, - content=f'Schedule expression validation error: {error}') + raise ResponseFactory(HTTPStatus.BAD_REQUEST).message( + f'Schedule expression validation error: {error}' + ).exc() def register_job(self, tenant: Tenant, schedule: str, environment: dict, name: Optional[str] = None) -> ScheduledJob: _id = self.safe_name(name) if name else \ self.safe_name_from_tenant(tenant) - environment[BATCH_ENV_SCHEDULED_JOB_NAME] = _id + environment[BatchJobEnv.SCHEDULED_JOB_NAME] = _id _LOG.info(f'Registering new scheduled job with id \'{_id}\'') self.scheduler.add_job( - SUBPROCESS_HANDLER().submit_job, id=_id, kwargs={ - 'environment_variables': environment - }, trigger=self.derive_trigger(schedule)) + func=self._batch_client.submit_job, + id=_id, + trigger=self.derive_trigger(schedule), + kwargs=dict( + environment_variables=environment, + job_name=_id, # not so important + ) + ) # here it's not a mistake. self.scheduler.add_job above creates an # item in MongoDB, so we just query it _job = ScheduledJob.get_nullable(hash_key=_id) _LOG.info('Updating the created job item with some ' 'Custodian`s required attributes') - self._update_job_obj_with(_job, tenant, schedule, environment) - _job.save() - _LOG.debug('Scheduled job`s data was saved to Dynamodb') + actions = [ + ScheduledJob.tenant_name.set(tenant.name), + ScheduledJob.customer_name.set(tenant.customer_name), + ScheduledJob.context['schedule'].set(schedule), + ScheduledJob.context['scan_regions'].set( + self._scan_regions_from_env(environment)), + ScheduledJob.context['scan_rulesets'].set( + self._scan_rulesets_from_env(environment)), + ScheduledJob.context['is_enabled'].set(True) + ] + _job.update(actions=actions) _LOG.info(f'Scheduled job with name \'{_id}\' was added') return _job @@ -232,15 +245,13 @@ def update_job(self, item: ScheduledJob, is_enabled: Optional[bool] = None, _id = item.id _LOG.info(f'Updating scheduled job with id \'{_id}\'') job = self.scheduler.get_job(_id) - if not job: - _LOG.warning(f'Job with id \'{_id}\' was not found') - return build_response( - code=HTTPStatus.NOT_FOUND, - content=f'Cannot find rule for scheduled job \'{_id}\'. ' - f'Recreate the job') # first update item in DB and second scheduler item. Not vice versa - item.update_with(is_enabled=is_enabled, schedule=schedule) - item.save() + actions = [] + if isinstance(is_enabled, bool): + actions.append(ScheduledJob.context['is_enabled'].set(is_enabled)) + if schedule: + actions.append(ScheduledJob.context['schedule'].set(schedule)) + item.update(actions=actions) if isinstance(is_enabled, bool): job.resume() if is_enabled else job.pause() @@ -248,3 +259,99 @@ def update_job(self, item: ScheduledJob, is_enabled: Optional[bool] = None, job.reschedule(trigger=self.derive_trigger(schedule)) _LOG.info( f'Scheduled job with name \'{_id}\' was successfully updated') + + def register_retry_job(self, event: dict, function): + _LOG.error('Registering retry job. Not implemented. Nothing happens') + + # def register_retry_job(self, event: dict, function): + # # todo rewrite this mess + # job_id = event.get('schedule_job_id') + # retry_scheduler = self.scheduler.get_job( + # job_id=job_id) if job_id else None + # quota = 15 + # + # if self.setting_service.max_cron_number() <= len( + # self.scheduler.get_jobs()): + # _LOG.warning(f'Max cron number achieved: ' + # f'{self.setting_service.max_cron_number()}') + # elif not retry_scheduler: + # attempt = int(event.get('attempt', 1)) + 1 + # path = event.pop('path') + # http_method = event.pop('httpMethod') + # for param in ('user_id', 'user_role', 'user_customer'): + # event.pop(param, None) + # new_event = {'attempt': attempt, 'headers': {'Host': ''}, + # 'httpMethod': http_method, + # 'requestContext': { + # 'resourcePath': path, 'path': path}, + # 'body': json.dumps(event)} + # hours = quota // 60 if attempt * quota >= 60 else None + # params = {'kwargs': {'event': new_event, + # 'context': RequestContext()}, + # 'trigger': 'interval', 'minutes': quota % 60} + # if hours: + # params.update(hours=hours) + # retry_scheduler = self.scheduler.add_job(function, **params) + # new_event.update({'schedule_job_id': retry_scheduler.id}) + # retry_scheduler.modify( + # name=RETRY_CRON_NAME_PATTERN + retry_scheduler.id, + # kwargs={'event': new_event, 'context': RequestContext()}) + # + # self.report_statistics_service.create( + # event, attempt=attempt, _id=retry_scheduler.id).save() + # _LOG.info( + # f'Scheduled job with id \'{retry_scheduler.id}\' ' + # f'was successfully created. Time interval: {quota} minutes') + # elif retry_scheduler.kwargs['event'].get('attempt') == \ + # self.setting_service.get_max_attempt_number(): + # retries = self.report_statistics_service.get_by_id_and_no_status( + # retry_scheduler.id) + # with ReportStatistics.batch_write() as writer: + # for r in retries: + # r.status = ReportDispatchStatus.PENDING + # writer.save(r) + # for cron in self.scheduler.get_jobs(): + # if cron.name.startswith(RETRY_CRON_NAME_PATTERN): + # try: + # self.scheduler.remove_job(cron.id) + # except JobLookupError: + # _LOG.warning( + # f'Job {retry_scheduler.id} is already deleted') + # _LOG.debug('Disabling ability to send reports') + # self.setting_service.disable_send_reports() + # else: + # attempt = int(retry_scheduler.kwargs['event'].get( + # 'attempt', 1)) + 1 + # + # retries = list(ReportStatistics.query( + # hash_key=retry_scheduler.id, + # range_key_condition=ReportStatistics.status.does_not_exist(), + # filter_condition=ReportStatistics.tenant == event.get( + # 'tenant_names') if event.get( + # 'tenant_names') else event.get( + # 'tenant_display_names')) + # ) + # self.report_statistics_service.batch_update_status( + # 'FAILED', retries) + # self.report_statistics_service.create( + # event, attempt=attempt, _id=retry_scheduler.id).save() + # + # path = event.pop('path') + # event.pop('attempt', None) + # new_event = {'attempt': attempt, 'headers': {'Host': ''}, + # 'httpMethod': event.pop('httpMethod', None), + # 'requestContext': { + # 'resourcePath': path, 'path': path}, + # 'body': json.dumps(event)} + # hours = attempt * quota // 60 if attempt * quota >= 60 else None + # params = {'trigger': 'interval', 'minutes': attempt * quota % 60} + # if hours: + # params.update(hours=hours) + # + # retry_scheduler = retry_scheduler.reschedule(**params) + # retry_scheduler.modify(kwargs={'event': new_event, + # 'context': RequestContext()}) + # _LOG.info( + # f'Scheduled job with id \'{retry_scheduler.id}\' ' + # f'was successfully updated. New time interval: ' + # f'{attempt * quota} minutes') diff --git a/src/services/abs_lambda.py b/src/services/abs_lambda.py new file mode 100644 index 000000000..b9e0077c2 --- /dev/null +++ b/src/services/abs_lambda.py @@ -0,0 +1,615 @@ +from abc import ABC, abstractmethod +from http import HTTPStatus +import inspect +import json +import msgspec +from typing import MutableMapping, TypedDict, cast, TYPE_CHECKING + +from modular_sdk.commons.exception import ModularException +from modular_sdk.services.customer_service import CustomerService + +from helpers import RequestContext, deep_get +from helpers.constants import CAASEnv, CustodianEndpoint, HTTPMethod, Permission +from helpers.lambda_response import ( + CustodianException, + LambdaOutput, + MetricsUpdateException, + ResponseFactory, +) +from helpers.log_helper import get_logger, hide_secret_values +from helpers.system_customer import SYSTEM_CUSTOMER +from services.environment_service import EnvironmentService +from services.job_service import JobService +from services.batch_results_service import BatchResultsService +from services.platform_service import PlatformService +from services.license_service import LicenseService +from services import SP +from services.rbac_service import ( + PolicyService, + PolicyStruct, + RoleService, + TenantAccess, + TenantsAccessPayload, +) +if TYPE_CHECKING: + from handlers import Mapping + +_LOG = get_logger(__name__) + + +class AbstractEventProcessor(ABC): + __slots__ = () + + @abstractmethod + def __call__(self, event: dict, context: RequestContext + ) -> tuple[dict, RequestContext]: + """ + Returns somehow changed dict + """ + + +class ProcessedEvent(TypedDict): + """ + This is a processed event that contains all kinds of useful information. + By default, each handler receives only body (query if GET) as a single + kwargs and all the path params as other kwargs. + """ + method: HTTPMethod + resource: CustodianEndpoint | None # our resource if it can be matched: /jobs/{id} + path: str # real path without stage: /jobs/123 + fullpath: str # full real path with stage /dev/jobs/123 + cognito_username: str | None + cognito_customer: str | None + cognito_user_id: str | None + cognito_user_role: str | None + permission: Permission | None # permissions for this endpoint + is_system: bool + body: dict # maybe better str in order not to bind to json + query: dict + path_params: dict + tenant_access_payload: TenantsAccessPayload + additional_kwargs: dict # additional kwargs to path to a handler + + +class ExpandEnvironmentEventProcessor(AbstractEventProcessor): + __slots__ = '_env', + + def __init__(self, environment_service: EnvironmentService): + self._env = environment_service + + @classmethod + def build(cls) -> 'ExpandEnvironmentEventProcessor': + return cls( + environment_service=SP.environment_service + ) + + def __call__(self, event: dict, context: RequestContext + ) -> tuple[dict, RequestContext]: + """ + Adds some useful data to internal environment variables + """ + envs = {CAASEnv.INVOCATION_REQUEST_ID: context.aws_request_id} + if host := deep_get(event, ('headers', 'Host')): + envs[CAASEnv.API_GATEWAY_HOST] = host + # we could've got stage from requestContext.stage, but it always points + # to api gw stage. That value if wrong for us in case we use a domain + # name with prefix. So we should resolve stage as difference between + # requestContext.path and requestContext.resourcePath + _path = deep_get(event, ('requestContext', 'path')) + _resource = deep_get(event, ('requestContext', 'resourcePath')) + envs[CAASEnv.API_GATEWAY_STAGE] = _path[:-len(_resource)].strip('/') + + if arn := context.invoked_function_arn: + envs[CAASEnv.ACCOUNT_ID] = arn.split(':')[4] + self._env.override_environment(envs) + return event, context + + +class ApiGatewayEventProcessor(AbstractEventProcessor): + __slots__ = '_mapping', + _decoder = msgspec.json.Decoder(type=dict) + + def __init__(self, mapping: dict[tuple[CustodianEndpoint, HTTPMethod], Permission | None]): + """ + :param mapping: permissions mapping. Currently, we have one inside + validators.registry + """ + self._mapping = mapping + + @staticmethod + def _is_from_step_function(event: dict) -> bool: + """ + Standard api gateway event does not contain `execution_job_id`. + Historically we can have a situation when the lambda is invoked by + step function with an event which mocks api gateway's event but still + is a bit different. + :param event: + :return: + """ + return 'execution_job_id' in event + + @staticmethod + def _prepare_step_function_event(event: dict): + """ + Such an event has `execution_job_id` and `attempt` in his root. Also, + its body is already an object, not a string. It does not have `path` + :param event: + :return: + """ + assert isinstance(event.get('body'), dict), 'A bug found' + event['body']['execution_job_id'] = event.pop('execution_job_id', None) + event['body']['attempt'] = event.pop('attempt', 0) + p = event['requestContext']['path'] + # removes stage + event['path'] = '/' + p.strip('/').split('/', maxsplit=1)[-1] + + def __call__(self, event: dict, context: RequestContext + ) -> tuple[ProcessedEvent, RequestContext]: + """ + Accepts API GW lambda proxy integration event + """ + if self._is_from_step_function(event): + _LOG.debug('Event came from step function. Preprocessing') + self._prepare_step_function_event(event) + + body = event.get('body') or '{}' + if isinstance(body, str): + try: + body = self._decoder.decode(body) + except msgspec.ValidationError as e: + _LOG.info('Invalid body type came. Returning 400') + raise ResponseFactory(HTTPStatus.BAD_REQUEST).message( + str(e) + ).exc() + except msgspec.DecodeError as e: + _LOG.info('Invalid incoming json. Returning 400') + raise ResponseFactory(HTTPStatus.BAD_REQUEST).message( + str(e) + ).exc() + rc = event.get('requestContext') or {} + return { + 'method': (method := HTTPMethod(event['httpMethod'])), + 'resource': (res := CustodianEndpoint.match(rc['resourcePath'])), + 'path': event['path'], # todo may be wrong if we use custom domain + 'fullpath': rc['path'], + 'cognito_username': deep_get(rc, ('authorizer', 'claims', + 'cognito:username')), + 'cognito_customer': (cst := deep_get(rc, ('authorizer', 'claims', + 'custom:customer'))), + 'cognito_user_id': deep_get(rc, ('authorizer', 'claims', 'sub')), + 'cognito_user_role': deep_get(rc, ('authorizer', 'claims', + 'custom:role')), + 'permission': self._mapping.get((res, method)), + 'is_system': cst == SYSTEM_CUSTOMER, + 'body': body, + 'query': dict(event.get('queryStringParameters') or {}), + 'path_params': dict(event.get('pathParameters') or {}), + 'tenant_access_payload': TenantsAccessPayload.build_denying_all(), + 'additional_kwargs': dict() + }, context + + +class RestrictCustomerEventProcessor(AbstractEventProcessor): + """ + Each user has its own customer but a system user should be able to + perform actions on behalf of any customer. Every request model has + customer_id attribute that is used by handlers to manage entities of + that customer. This processor inserts user's customer to each event body. + Allows to provide customer_id only for system users + """ + __slots__ = '_cs', + + # TODO organize this collection somehow else + can_work_without_customer_id = { + (CustodianEndpoint.CUSTOMERS, HTTPMethod.GET), + + (CustodianEndpoint.METRICS_UPDATE, HTTPMethod.POST), + (CustodianEndpoint.METRICS_STATUS, HTTPMethod.GET), + + (CustodianEndpoint.META_STANDARDS, HTTPMethod.POST), + (CustodianEndpoint.META_MAPPINGS, HTTPMethod.POST), + (CustodianEndpoint.META_META, HTTPMethod.POST), + + (CustodianEndpoint.ED_RULESETS, HTTPMethod.GET), + (CustodianEndpoint.ED_RULESETS, HTTPMethod.POST), + (CustodianEndpoint.ED_RULESETS, HTTPMethod.DELETE), + (CustodianEndpoint.RULESETS, HTTPMethod.GET), + (CustodianEndpoint.RULESETS, HTTPMethod.POST), + (CustodianEndpoint.RULESETS, HTTPMethod.PATCH), + (CustodianEndpoint.RULESETS, HTTPMethod.DELETE), + (CustodianEndpoint.RULESETS_CONTENT, HTTPMethod.GET), + + (CustodianEndpoint.RULE_SOURCES, HTTPMethod.GET), + (CustodianEndpoint.RULE_SOURCES, HTTPMethod.POST), + (CustodianEndpoint.RULE_SOURCES, HTTPMethod.DELETE), + (CustodianEndpoint.RULE_SOURCES, HTTPMethod.PATCH), + + (CustodianEndpoint.RULES, HTTPMethod.GET), + (CustodianEndpoint.RULES, HTTPMethod.DELETE), + (CustodianEndpoint.RULE_META_UPDATER, HTTPMethod.POST), + + (CustodianEndpoint.LICENSES_LICENSE_KEY_SYNC, HTTPMethod.POST), + + (CustodianEndpoint.SETTINGS_MAIL, HTTPMethod.GET), + (CustodianEndpoint.SETTINGS_MAIL, HTTPMethod.POST), + (CustodianEndpoint.SETTINGS_MAIL, HTTPMethod.DELETE), + (CustodianEndpoint.SETTINGS_SEND_REPORTS, HTTPMethod.POST), + (CustodianEndpoint.SETTINGS_LICENSE_MANAGER_CLIENT, HTTPMethod.POST), + (CustodianEndpoint.SETTINGS_LICENSE_MANAGER_CLIENT, HTTPMethod.GET), + (CustodianEndpoint.SETTINGS_LICENSE_MANAGER_CLIENT, HTTPMethod.DELETE), + (CustodianEndpoint.SETTINGS_LICENSE_MANAGER_CONFIG, HTTPMethod.POST), + (CustodianEndpoint.SETTINGS_LICENSE_MANAGER_CONFIG, HTTPMethod.GET), + (CustodianEndpoint.SETTINGS_LICENSE_MANAGER_CONFIG, HTTPMethod.DELETE), + + (CustodianEndpoint.EVENT, HTTPMethod.POST), + + (CustodianEndpoint.USERS_WHOAMI, HTTPMethod.GET), + (CustodianEndpoint.USERS_RESET_PASSWORD, HTTPMethod.POST), + (CustodianEndpoint.USERS, HTTPMethod.GET), + (CustodianEndpoint.USERS_USERNAME, HTTPMethod.PATCH), + (CustodianEndpoint.USERS_USERNAME, HTTPMethod.DELETE), + (CustodianEndpoint.USERS_USERNAME, HTTPMethod.GET), + } + + def __init__(self, customer_service: CustomerService): + self._cs = customer_service + + @classmethod + def build(cls) -> 'RestrictCustomerEventProcessor': + return cls( + customer_service=SP.modular_client.customer_service() + ) + + @staticmethod + def _get_cid(event: ProcessedEvent) -> str | None: + one = event['query'].get('customer_id') + two = event['query'].get('customer') + three = event['body'].get('customer_id') + four = event['body'].get('customer') + return one or two or three or four + + def __call__(self, event: ProcessedEvent, context: RequestContext + ) -> tuple[ProcessedEvent, RequestContext]: + if not event['cognito_username']: + _LOG.info('Event does not contain username. No auth') + # endpoint without auth + return event, context + if event['is_system']: + # if system user is making a request he should provide customer_id + # as a parameter to make a request on that customer's behalf. + cid = self._get_cid(event) + if cid and cid != SYSTEM_CUSTOMER and not self._cs.get(cid): + raise ResponseFactory(HTTPStatus.BAD_REQUEST).message( + f'Customer {cid} does not exist. You cannot make a request' + f' on his behalf' + ).exc() + event['tenant_access_payload'] = TenantsAccessPayload.build_allowing_all() + + if (event['resource'], event['method']) in self.can_work_without_customer_id: # noqa + return event, context + if not cid: + raise ResponseFactory(HTTPStatus.BAD_REQUEST).message( + 'Please, provide customer_id param to make a request on ' + 'his behalf' + ).exc() + return event, context + # override customer attribute for standard users with their customer + cust = event['cognito_customer'] + match event['method']: + case HTTPMethod.GET: + event['query']['customer_id'] = cust + case _: + event['body']['customer_id'] = cust + return event, context + + +class CheckPermissionEventProcessor(AbstractEventProcessor): + """ + Processor that restricts rbac permission + """ + __slots__ = '_rs', '_ps', '_env' + + def __init__(self, role_service: RoleService, + policy_service: PolicyService, + environment_service: EnvironmentService): + self._rs = role_service + self._ps = policy_service + self._env = environment_service + + @classmethod + def build(cls) -> 'CheckPermissionEventProcessor': + return cls( + role_service=SP.role_service, + policy_service=SP.policy_service, + environment_service=SP.environment_service + ) + + @staticmethod + def _not_allowed_message(permission: Permission) -> str: + if permission.depends_on_tenant: + return (f'Permission \'{permission.value}\' is not allowed ' + f'for any tenant by your user role') + else: + return (f'Permission \'{permission.value}\' is not allowed ' + f'by your user role') + + def _check_permission(self, customer: str, role_name: str, + permission: Permission) -> TenantsAccessPayload: + """ + Checks users role in order to make immediate 403 in case the permission + is not allowed. This method raises 403 in case the permission is not + allowed. + :param customer: + :param role_name: + :param permission: + :return: TenantAccessPayload + """ + factory = ResponseFactory(HTTPStatus.FORBIDDEN).message + # todo cache role and policies? + role = self._rs.get_nullable(customer, role_name) + if not role: + raise factory('Your user role was removed').exc() + if role.is_expired(): + raise factory('Your user role has expired').exc() + + it = map(PolicyStruct.from_model, self._ps.iter_role_policies(role)) + ta = TenantAccess() + is_allowed = False + for policy in it: + if policy.forbids(permission): + raise factory(self._not_allowed_message(permission)).exc() + is_allowed |= policy.allows(permission) + ta.add(policy) + if not is_allowed: + raise factory(self._not_allowed_message(permission)).exc() + return ta.resolve_payload(permission) + + def __call__(self, event: ProcessedEvent, context: RequestContext + ) -> tuple[ProcessedEvent, RequestContext]: + if event['is_system']: # do not check any permissions for system + return event, context + username = event['cognito_username'] + if not username: + # no username -> means no auth on endpoint. No permission to check + return event, context + if not event['resource']: + # will be aborted with 404 + _LOG.warning('A request for not known resource') + return event, context + permission = event['permission'] + if not permission: + _LOG.info('No permission exist for endpoint, allowing') + return event, context + if not self._env.allow_disabled_permissions() and permission.is_disabled: + raise ResponseFactory(HTTPStatus.FORBIDDEN).message( + 'Action is allowed only for system user' + ).exc() + + # if cognito_username exists, cognito_customer & cognito_user_role + # exist as well + event['tenant_access_payload'] = self._check_permission( + customer=cast(str, event['cognito_customer']), + role_name=cast(str, event['cognito_user_role']), + permission=permission + ) + return event, context + + +class RestrictTenantEventProcessor(AbstractEventProcessor): + """ + Restricting endpoints based on tenant permissions is kind of a + difficult task. In general, it would mean that we should adjust each + single endpoint so that it could do its logic taking allowed tenants + into consideration. We can do that but that is a lot of work and repeating. + This processor will intercept some common cases like endpoints with + {tenant_name} or {job_id} in path and will check tenant permissions + immediately. Additionally, it will insert some data into + event['additional_kwargs']. I know it all seems quite confusing. + Behold example: + imagine we have and an endpoint `GET /jobs/{job_id}`. It describes a + job by the given job_id. Each job belongs to a specific tenant so if I + make say this request: `GET /jobs/1` and the job with id `1` belongs to + the tenant I have no rights to access - I should receive 403. More or less + the same logic can be applied to a lot of other endpoints ({tenant_name}, + {platform_id}, etc.). + So, this method will catch such endpoints, will query the request item + (job for the example above) if necessary and perform restriction here. + Then it will add this item to + additional_kwargs so that it could be used in the actual handler + without querying again. + Although it will cover the majority of cases we still need to implement + custom restriction for some specific endpoints. For example `GET /jobs` + should query jobs that belong to tenants a user have access to + """ + __slots__ = '_js', '_brs', '_ps', '_ls' + + def __init__(self, job_service: JobService, + batch_results_service: BatchResultsService, + platform_service: PlatformService, + license_service: LicenseService): + self._js = job_service + self._brs = batch_results_service + self._ps = platform_service + self._ls = license_service + + @classmethod + def build(cls) -> 'RestrictTenantEventProcessor': + return cls( + job_service=SP.job_service, + batch_results_service=SP.batch_results_service, + platform_service=SP.platform_service, + license_service=SP.license_service + ) + + def _restrict_tenant_name(self, event: ProcessedEvent, + context: RequestContext + ) -> tuple[ProcessedEvent, RequestContext]: + tenant_name = cast(str, event['path_params'].get('tenant_name')) + if not event['tenant_access_payload'].is_allowed_for(tenant_name): + raise ResponseFactory(HTTPStatus.FORBIDDEN).message( + f'The request tenant \'{tenant_name}\' is not found' + ).exc() + return event, context + + def _restrict_job_id(self, event: ProcessedEvent, context: RequestContext + ) -> tuple[ProcessedEvent, RequestContext]: + job_id = cast(str, event['path_params'].get('job_id')) + job = self._js.get_nullable(hash_key=job_id) + if not job or not event['tenant_access_payload'].is_allowed_for(job.tenant_name): + raise ResponseFactory(HTTPStatus.NOT_FOUND).message( + self._js.not_found_message(job_id) + ).exc() + event['additional_kwargs']['job_obj'] = job + return event, context + + def _restrict_batch_results(self, event: ProcessedEvent, + context: RequestContext + ) -> tuple[ProcessedEvent, RequestContext]: + br_id = cast(str, event['path_params'].get('batch_results_id')) + job = self._brs.get_nullable(hash_key=br_id) + if not job or not event['tenant_access_payload'].is_allowed_for(job.tenant_name): + raise ResponseFactory(HTTPStatus.NOT_FOUND).message( + self._brs.not_found_message(br_id) + ).exc() + event['additional_kwargs']['br_obj'] = job + return event, context + + def _restrict_platform_id(self, event: ProcessedEvent, + context: RequestContext + ) -> tuple[ProcessedEvent, RequestContext]: + platform_id = cast(str, event['path_params'].get('platform_id')) + platform = self._ps.get_nullable(hash_key=platform_id) + if not platform or not event['tenant_access_payload'].is_allowed_for(platform.tenant_name): + raise ResponseFactory(HTTPStatus.NOT_FOUND).message( + self._ps.not_found_message(platform_id) + ).exc() + event['additional_kwargs']['platform_obj'] = platform + return event, context + + def _restrict_license_key(self, event: ProcessedEvent, + context: RequestContext + ) -> tuple[ProcessedEvent, RequestContext]: + # todo check if license is applicable at least for one allowed tenant + return event, context + + def __call__(self, event: ProcessedEvent, context: RequestContext + ) -> tuple[ProcessedEvent, RequestContext]: + res = event['resource'] + perm = event['permission'] + if not res or not perm or not perm.depends_on_tenant: + return event, context + + if '{tenant_name}' in res: + return self._restrict_tenant_name(event, context) + if '{job_id}' in res: + return self._restrict_job_id(event, context) + if '{batch_results_id}' in res: + return self._restrict_batch_results(event, context) + if '{platform_id}' in res: + return self._restrict_platform_id(event, context) + if '{license_key}' in res: + return self._restrict_license_key(event, context) + return event, context + + +class AbstractLambdaHandler(ABC): + @abstractmethod + def handle_request(self, event: MutableMapping, context: RequestContext + ) -> LambdaOutput: + """ + Should be implemented. May raise TelegramBotException or any + other kind of exception + """ + + @abstractmethod + def lambda_handler(self, event: dict, context: RequestContext + ) -> LambdaOutput: + """ + Main lambda's method that is executed + """ + + +class EventProcessorLambdaHandler(AbstractLambdaHandler): + processors: tuple[AbstractEventProcessor, ...] = () + + def _process_event(self, event: dict, context: RequestContext + ) -> tuple[dict, RequestContext]: + for processor in self.processors: + _LOG.debug(f'Processing event: {processor.__class__.__name__}') + event, context = processor(event, context) + return event, context + + @abstractmethod + def handle_request(self, event: MutableMapping, + context: RequestContext) -> LambdaOutput: + ... + + def lambda_handler(self, event: dict, context: RequestContext + ) -> LambdaOutput: + _LOG.info(f'Starting request: {context.aws_request_id}') + # This is the only place where we print the event. Do not print it + # somewhere else + _LOG.debug('Incoming event') + _LOG.debug(json.dumps(hide_secret_values(event))) + try: + processed, context = self._process_event(event, context) + return self.handle_request(event=processed, context=context) + except MetricsUpdateException as e: + # todo 5.0.0 refactor metrics-updater lambda to remove this except + # from here + _LOG.warning('Metrics update exception occurred', exc_info=True) + raise e # needed for ModularJobs to track failed jobs + except CustodianException as e: + _LOG.warning(f'Application exception occurred: {e}') + return e.build() + except ModularException as e: + _LOG.warning('Modular exception occurred', exc_info=True) + return ResponseFactory(int(e.code)).message(e.content).build() + except Exception: # noqa + _LOG.exception('Unexpected exception occurred') + return ResponseFactory( + HTTPStatus.INTERNAL_SERVER_ERROR + ).default().build() + + +class ApiEventProcessorLambdaHandler(EventProcessorLambdaHandler): + mapping: 'Mapping' + + def handle_request(self, event: ProcessedEvent, + context: RequestContext) -> LambdaOutput: + """ + It resolves handler from the mapping using method and resource. It + always passes query or body inside `event` kwargs (depending on + method). Also, if a handler has some specific reserved kwargs this + method will add additional values. Also, all `additional_kwargs` from + event will be passes to handlers if those have that kwargs + :param event: + :param context: + :return: + """ + handler = self.mapping.get(event['resource'], {}).get(event['method']) + if not handler: + raise ResponseFactory(HTTPStatus.NOT_FOUND).default().exc() + match event['method']: + case HTTPMethod.GET: + body = event['query'] + case _: + body = event['body'] + params = dict(event=body, **event['path_params']) + parameters = inspect.signature(handler).parameters + if '_pe' in parameters: + # pe - Processed Event: in case we need to access some raw data + # inside a handler. + _LOG.debug('Expanding handler payload with raw event') + params['_pe'] = event + if '_tap' in parameters: + # _tap - in case we need to know what tenants are allowed + # inside a specific handler + _LOG.debug('Expanding handler payload with tenant access data') + params['_tap'] = event['tenant_access_payload'] + additional = event['additional_kwargs'] + for p in parameters: + if p in additional: + _LOG.debug(f'Expanding handler payload with {p}') + params[p] = additional[p] + return handler(**params) diff --git a/src/services/abstract_api_handler_lambda.py b/src/services/abstract_api_handler_lambda.py deleted file mode 100644 index 8b6de4dac..000000000 --- a/src/services/abstract_api_handler_lambda.py +++ /dev/null @@ -1,258 +0,0 @@ -import json -from abc import abstractmethod -from http import HTTPStatus -from typing import Optional - -from modular_sdk.commons.exception import ModularException - -from helpers import build_response, CustodianException, deep_get -from helpers.constants import PARAM_USER_ID, PARAM_USER_ROLE, \ - PARAM_REQUEST_PATH, PARAM_HTTP_METHOD, PARAM_USER_CUSTOMER, \ - PARAM_RESOURCE_PATH, ENV_API_GATEWAY_STAGE, ENV_API_GATEWAY_HOST, \ - HTTPMethod -from helpers.log_helper import get_logger -from services import SERVICE_PROVIDER - -_LOG = get_logger(__name__) - -# remove pathParams -UNNECESSARY_EVENT_PARAMS = { - 'resource', 'multiValueHeaders', 'multiValueQueryStringParameters', - 'stageVariables', 'requestContext', 'headers', 'isBase64Encoded' -} -REQUEST_CONTEXT = None - -NOT_ALLOWED_TO_ACCESS_ENTITY = 'You are not allowed to access this entity' -NOT_ENOUGH_DATA = 'Not enough data to proceed the request' - - -class AbstractApiHandlerLambda: - @abstractmethod - def handle_request(self, event, context): - """ - Inherited lambda function code - :param event: lambda event - :param context: lambda context - :return: - """ - pass - - @staticmethod - def _resolve_stage(resource_path: str, path: str) -> str: - """ - :param resource_path: path without stage, cannot contain "{}" - /applications/58da234b-f85b-4a8b-810a-c201f3c55e5f - :param path: path with stage - /caas/applications/58da234b-f85b-4a8b-810a-c201f3c55e5f - :return: str, 'caas' in the example - """ - resource_path = resource_path.strip('/') - path = path.strip('/') - return path[:len(path) - len(resource_path)].strip('/') - - def lambda_handler(self, event, context): - global REQUEST_CONTEXT - REQUEST_CONTEXT = context - _env = SERVICE_PROVIDER.environment_service() - # These overriden envs used only for POST, PATCH /applications/access - path_params = event.get('pathParameters') or {} - _env.override_environment({ - ENV_API_GATEWAY_HOST: deep_get(event, ('headers', 'Host')), - ENV_API_GATEWAY_STAGE: self._resolve_stage( - deep_get(event, ('requestContext', 'resourcePath')).format_map( - path_params), - deep_get(event, ('requestContext', 'path')).format_map( - path_params) - ) - }) - try: - _LOG.debug(f'Request: {json.dumps(event)}, ' - f'request id: \'{context.aws_request_id}\'') - _LOG.debug('Checking user permissions') - - # _, request_path = self._split_stage_and_resource( - # deep_get(event, ['requestContext', 'path'])) - - # todo consider using `resourcePath` convention, instead of - # request one. - - request_path = resource_path = deep_get( - event, ['requestContext', PARAM_RESOURCE_PATH] - ) - event[PARAM_REQUEST_PATH] = self._floor_request_path( - request_path=resource_path - ) - request_method = event.get(PARAM_HTTP_METHOD) - target_permission = self._get_target_permission( - request_path, request_method) - _request_context = event.get('requestContext') or {} - - _LOG.debug('Removing unnecessary attrs from event') - self._pop_excessive_attributes(event) - _LOG.debug(f'Event after removing unnecessary attrs: {event}') - - _LOG.debug('Formatting and validating event`s body') - self._format_event(event) - _LOG.debug(f'Event after validating and formatting: {event}') - - user_id = deep_get( - _request_context, ['authorizer', 'claims', - 'cognito:username']) - user_customer = deep_get( - _request_context, ['authorizer', 'claims', - 'custom:customer']) - user_role = deep_get( - _request_context, ['authorizer', 'claims', 'custom:role']) - user_tenants = deep_get( - _request_context, ['authorizer', 'claims', - 'custom:tenants']) - if user_id and user_customer and user_role: # endpoint with cognito - event[PARAM_USER_ID] = user_id - event[PARAM_USER_ROLE] = user_role - event[PARAM_USER_CUSTOMER] = user_customer - if target_permission: - _LOG.info('Restricting access by RBAC') - if not SERVICE_PROVIDER.access_control_service().is_allowed_to_access( # noqa - customer=user_customer, role_name=user_role, - target_permission=target_permission): - message = f'User \'{event.get(PARAM_USER_ID)}\' ' \ - f'is not allowed to access the resource: ' \ - f'\'{target_permission}\'' - _LOG.info(message) - return build_response( - code=HTTPStatus.FORBIDDEN.value, - content=message - ) - _LOG.info( - f'Restricting access by entities: ' - f'user_customer={user_customer}, ' - f'user_tenants={user_tenants}') - restriction_service = SERVICE_PROVIDER.restriction_service() - restriction_service.set_endpoint(request_path, request_method) - restriction_service.set_user_entities( - user_customer, - user_tenants=user_tenants.replace(' ', '').split(',') - if user_tenants else []) - restriction_service.update_event(event) - else: # for example /signin - _LOG.debug(f'Authorizer is not provided for ' - f'endpoint: {request_path}. Allowing...') - - execution_result = self.handle_request( - event=event, - context=context - ) - _LOG.debug(f'Response: {execution_result}') - return execution_result - except ModularException as e: - _LOG.warning(f'Modular exception occurred: {e}') - return CustodianException( - code=e.code, - content=e.content - ).response() - except CustodianException as e: - _LOG.warning(f'Custodian exception occurred: {e}') - return e.response() - except Exception: - _LOG.exception('Unexpected error occurred') - return CustodianException( - code=HTTPStatus.INTERNAL_SERVER_ERROR.value, - content='Internal server error' - ).response() - - def _get_target_permission(self, request_path: str, - http_method: str) -> Optional[str]: - from services.rbac.endpoint_to_permission_mapping import \ - ENDPOINT_PERMISSION_MAPPING, PERMISSIONS - return ENDPOINT_PERMISSION_MAPPING.get( - self._reformat_request_path(request_path), {} - ).get(http_method, {}).get(PERMISSIONS) - - def _validate_event(self, request_path: str, http_method: str, - body: dict) -> dict: - """ - Validates an input payload using endpoint's validator and in case - the validation has passed returns validated dict. If the validation - fails, raises 400 - :param request_path: str - :param http_method: str - :param body: dict - :return: dict - """ - from services.rbac.endpoint_to_permission_mapping import \ - ENDPOINT_PERMISSION_MAPPING, VALIDATION - from validators.utils import validate_pydantic - validation = ENDPOINT_PERMISSION_MAPPING.get( - self._reformat_request_path(request_path), {} - ).get(http_method, {}).get(VALIDATION) - - if not validation: - _LOG.warning(f'Validator is not found for endpoint: ' - f'{http_method}, {request_path}. Skipping...') - return body - return validate_pydantic( - model=validation, - value=body - ).dict(exclude_none=True) - - @staticmethod - def _split_stage_and_resource(path: str) -> tuple: - """/caas/account/region -> ("/caas", "/account/region")""" - path = path.rstrip('/') - path = path.lstrip('/') - first_slash = path.index('/') - return f'/{path[:first_slash]}', path[first_slash:] - - def _format_event(self, event: dict) -> None: - """ - Unpacks query params and body in the root of the event. - Event must contain: - - httpMethod; - - path; - ..in order to retrieve a correct validator. - Raises 400 in case body's json is invalid or validation has not passed - """ - method, path = (event.get(PARAM_HTTP_METHOD), - event.get(PARAM_REQUEST_PATH)) - try: - body = json.loads(event.pop('body', None) or '{}') - except json.JSONDecodeError as e: - return build_response(code=HTTPStatus.BAD_REQUEST.value, - content=f'Invalid request body: \'{e}\'') - if method == HTTPMethod.GET or method == HTTPMethod.HEAD: - body.update(event.pop('queryStringParameters', None) or {}) - body.update(event.pop('pathParameters', None) or {}) - event.update(self._validate_event(path, method, body)) - - @staticmethod - def _pop_excessive_attributes(event: dict): - """ - Removes the attributes we don't need from event - """ - [event.pop(attr, None) for attr in UNNECESSARY_EVENT_PARAMS] - - @staticmethod - def _reformat_request_path(request_path: str): - """ - /hello -> /hello/ - hello/ -> /hello/ - hello -> /hello/ - """ - if not request_path.startswith('/'): - request_path = '/' + request_path - if not request_path.endswith('/'): - request_path += '/' - return request_path - - @staticmethod - def _floor_request_path(request_path: str): - """ - Given an ambiguously deep child-resource - safe to floor out the - `name`, as such child-resource, does not have any siblings. - Example: /path/{child+} -> /path/{child} - :return: str - """ - if '+' in request_path: - index = request_path.index('+') - request_path = request_path[:index] + request_path[index + 1:] - return request_path diff --git a/src/services/abstract_lambda.py b/src/services/abstract_lambda.py deleted file mode 100644 index 8a09271ce..000000000 --- a/src/services/abstract_lambda.py +++ /dev/null @@ -1,60 +0,0 @@ -import json -from abc import abstractmethod -from http import HTTPStatus - -from modular_sdk.commons.exception import ModularException - -from helpers import CustodianException -from helpers.exception import MetricsUpdateException -from helpers.log_helper import get_logger - -PARAM_NATIVE_JOB_ID = 'jobId' -PARAM_ROLE = 'role' - -_LOG = get_logger(__name__) - -REQUEST_CONTEXT = None - - -class AbstractLambda: - - @abstractmethod - def handle_request(self, event, context): - """ - Inherited lambda function code - :param event: lambda event - :param context: lambda context - :return: - """ - pass - - def lambda_handler(self, event, context): - global REQUEST_CONTEXT - REQUEST_CONTEXT = context - try: - _LOG.debug(f'Request: {json.dumps(event)}, ' - f'request id: \'{context.aws_request_id}\'') - execution_result = self.handle_request( - event=event, - context=context - ) - _LOG.debug(f'Response: {execution_result}') - return execution_result - except MetricsUpdateException as e: - _LOG.warning(f'Metrics update exception occurred: {e}') - raise e # needed for ModularJobs to track failed jobs - except ModularException as e: - _LOG.warning(f'Modular exception occurred: {e}') - return CustodianException( - code=e.code, - content=e.content - ).response() - except CustodianException as e: - _LOG.warning(f'Custodian exception occurred: {e}') - return e.response() - except Exception: - _LOG.exception('Unexpected error occurred.') - return CustodianException( - code=HTTPStatus.INTERNAL_SERVER_ERROR.value, - content='Internal server error' - ).response() diff --git a/src/services/abstract_rule_service.py b/src/services/abstract_rule_service.py deleted file mode 100644 index 0fef89ebe..000000000 --- a/src/services/abstract_rule_service.py +++ /dev/null @@ -1,68 +0,0 @@ -from abc import ABC, abstractmethod -from functools import wraps -from typing import Iterable, Generator, Union, Callable, List, Set, Optional - -from helpers.constants import ALLOWED_FOR_ATTR, CUSTOMER_ATTR -from helpers.system_customer import SYSTEM_CUSTOMER -from models.rule_source import RuleSource -from models.ruleset import Ruleset -from models.rule import Rule -from services.rbac.restriction_service import RestrictionService - -Entity = Union[RuleSource, Ruleset, Rule] -RulesContainer = Union[RuleSource, Ruleset] - - -class AbstractRuleService(ABC): - def __init__(self, restriction_service: RestrictionService): - self._restriction_service = restriction_service - - def filter_by_tenants(self, entities: Iterable[RulesContainer], - tenants: Optional[Set[str]] = None, - ) -> Generator[RulesContainer, None, None]: - """ - Filters the given iterable of entities by the list of allowed - tenants using `allowed_for` attribute - """ - _tenants = tenants or self._restriction_service.user_tenants - if not _tenants: - yield from entities - return - for entity in entities: - allowed_for = list(entity.allowed_for) - if not allowed_for or allowed_for & _tenants: - yield entity - - @staticmethod - def system_payload(params: dict) -> dict: - """ - Adjusts the params somehow to make the request return SYSTEM - entities - Make sure to use `super(Class, Class)` if you are going to use super - in an inherited staticmethod. - """ - return {**params, CUSTOMER_ATTR: SYSTEM_CUSTOMER} - - @classmethod - def derive_from_system(cls, func: Callable): - """ - A decorator to expand customer's entities with system entities. - Make sure to use only keyword parameters - """ - @wraps(func) - def wrapper(*args, **kwargs): - customer_entities = func(*args, **kwargs) - kwargs = cls.system_payload(kwargs) - system_entities = func(*args, **kwargs) - return cls.expand_systems( - system_entities, customer_entities) - return wrapper - - @staticmethod - @abstractmethod - def expand_systems(system_entities: List[Entity], - customer_entities: List[Entity]) -> List[Entity]: - """ - Updates two lists of objects by certain objects' attributes - """ - diff --git a/src/services/ambiguous_job_service.py b/src/services/ambiguous_job_service.py index 1114d4e42..0d40832b0 100644 --- a/src/services/ambiguous_job_service.py +++ b/src/services/ambiguous_job_service.py @@ -1,32 +1,105 @@ -from services.job_service import JobService, Job -from services.batch_results_service import BatchResultsService, BatchResults -from models.pynamodb_extension.index import IResultIterator -from helpers.log_helper import get_logger -from helpers.time_helper import utc_datetime, ts_datetime, datetime -from helpers.constants import TENANT_ATTR, TENANT_DISPLAY_NAME_ATTR, \ - TENANT_NAME_ATTR, ID_ATTR, CUSTOMER_ATTR, CUSTOMER_NAME_ATTR, \ - CUSTOMER_DISPLAY_NAME_ATTR, MANUAL_TYPE_ATTR -from typing import Union, Dict, Optional, List, Callable -from concurrent.futures import ThreadPoolExecutor, as_completed +import heapq +import operator +from datetime import datetime from functools import cached_property +from typing import Optional, Callable, TypeVar, Iterator, Iterable, Union + +from helpers.constants import JobType, JobState +from helpers.log_helper import get_logger +from services.batch_results_service import BatchResultsService, BatchResults +from services.job_service import JobService, Job -JOB_ID_ATTR = 'job_id' +_LOG = get_logger(__name__) -REACTIVE_TYPE_ATTR = 'reactive' +Source = Job | BatchResults -# Sort attributes. -REACTIVE_SORT_KEY_ATTR = 'registration_start' -MANUAL_SORT_KEY_ATTR = 'submitted_at' +J = TypeVar('J', Job, BatchResults) -STARTED_AT_ATTR = 'started_at' -SUBMITTED_AT = 'submitted_at' -_LOG = get_logger(__name__) +class AmbiguousJob: + """ + Resembles our base job interface + """ + __slots__ = ('job', ) + + def __init__(self, job: J): + self.job: J = job -# Establish an ambiguous source type -Source = Union[BatchResults, Job] + def __repr__(self) -> str: + return f'{self.class_.__name__}<{self.id}>' + + def __hash__(self): + return self.job.__hash__() + + def __eq__(self, other: Union['AmbiguousJob', Job | BatchResults]) -> bool: + if type(other) is AmbiguousJob: + return self.job == other.job + return type(other) is self.class_ and self.job == other + + @property + def class_(self) -> J: + return type(self.job) + + @property + def type(self) -> JobType: + if self.is_ed_job: + return JobType.REACTIVE + return JobType.MANUAL + + @property + def is_ed_job(self) -> bool: + return self.class_ is BatchResults + + @property + def is_platform_job(self) -> bool: + return not self.is_ed_job and bool(self.platform_id) -_DEF_ITEMS_PER_QUERY = 100 + @property + def id(self) -> str: + return self.job.id + + @property + def batch_job_id(self) -> str: + if self.is_ed_job: + return self.job.job_id + return self.batch_job_id + + @property + def tenant_name(self) -> str: + return self.job.tenant_name + + @property + def customer_name(self) -> str: + return self.job.customer_name + + @property + def status(self) -> JobState | None: + raw = self.job.status + if not raw: + return + return JobState[raw] + + @property + def reason(self) -> str | None: + return self.job.reason + + @property + def submitted_at(self) -> str: + return self.job.submitted_at + + @property + def stopped_at(self) -> str | None: + return self.job.stopped_at + + @property + def is_succeeded(self) -> bool: + return self.status == JobState.SUCCEEDED.value + + def __getattr__(self, item): + """ + All the other attributes can be accessed directly + """ + return getattr(self.job, item, None) class AmbiguousJobService: @@ -43,34 +116,14 @@ def job_service(self) -> JobService: def batch_results_service(self) -> BatchResultsService: return self._reactive_source_service - def dto(self, entity: Source) -> dict: - extractor: Optional[Callable] = None - output = {} - common = set(BatchResults.get_attributes()) & set(Job.get_attributes()) - manual = isinstance(entity, Job) - if manual: - _extractor = self._manual_source_service.get_job_dto - elif isinstance(entity, BatchResults): - _extractor = self._reactive_source_service.dto - - if extractor: - _output = extractor(entity) - for key in common: - if key in _output: - output[key] = _output[key] - if not manual: - output[JOB_ID_ATTR] = entity.id - - return output - @cached_property - def typ_job_getter_ref(self) -> Dict[str, Callable]: + def typ_job_getter_ref(self) -> dict[JobType, Callable]: return { - MANUAL_TYPE_ATTR: self._manual_source_service.get_job, - REACTIVE_TYPE_ATTR: self._reactive_source_service.get + JobType.MANUAL: self._manual_source_service.get_nullable, + JobType.REACTIVE: self._reactive_source_service.get_nullable } - def get(self, uid: str, typ: Optional[str] = None) -> Optional[Source]: + def get(self, uid: str, typ: Optional[JobType] = None) -> Optional[Source]: ref = self.typ_job_getter_ref if typ: assert typ in ref, 'Invalid type provided' @@ -78,255 +131,89 @@ def get(self, uid: str, typ: Optional[str] = None) -> Optional[Source]: source = filter(lambda x: x, (get(uid) for get in ref.values())) return next(source, None) - def batch_list( - self, typ_params_map: Dict[str, List[Dict]], - customer: Optional[str] = None, - start: Optional[datetime] = None, - end: Optional[datetime] = None, - sort: bool = True, ascending: bool = False - ) -> List[Source]: - """ - Returns a list of type-aggregated jobs, based on a type-specific - list of payloads, to run in threads. - :param typ_params_map: Dict[str, List[Dict]] - :param customer: Optional[str] = None - :param start: Optional[str] = None, lower bound - :param end: Optional[str] = None, upper bound - :param sort: bool = True - :param ascending: bool = False - :return: List[Union[BatchResults, Job]] - """ - output = [] - - typ_action_ref = { - REACTIVE_TYPE_ATTR: self.list_reactive, - MANUAL_TYPE_ATTR: self.list_manual - } + def get_job(self, job_id: str, typ: Optional[JobType] = None, + tenant: Optional[str] = None, + customer: Optional[str] = None + ) -> AmbiguousJob | None: + item = self.get(job_id, typ) + if not item: + return + item = AmbiguousJob(item) + if tenant and item.tenant_name != tenant: + return + if customer and item.customer_name != customer: + return + return item - with ThreadPoolExecutor() as executor: - futures = { - executor.submit( - typ_action_ref[typ], - customer=customer, start=start, end=end, **kwargs - ): (typ, kwargs) - for typ, params in typ_params_map.items() - for kwargs in params - if typ in typ_action_ref - } - for future in as_completed(futures): - _typ, _kwargs = futures[future] - _head = f'{_typ.capitalize()} job, based on - {_kwargs}' - try: - iterator = future.result() - output.extend(iterator) - if sort: - output.sort( - reverse=not ascending, - key=self.sort_value_into_datetime - ) - except (Exception, BaseException) as e: - _LOG.warning(_head + f', has run into an issue - {e}.') - - return output - - def list_reactive( - self, customer: Optional[str] = None, - tenants: Optional[List[str]] = None, - cloud_ids: Optional[List[str]] = None, - start: Optional[datetime] = None, - end: Optional[datetime] = None, - limit: Optional[int] = None, - last_evaluated_key: Optional[str] = None, - items_per_query: Optional[int] = _DEF_ITEMS_PER_QUERY - ): - """ - Obtains Batch Result entities, based on a provided customer, tenant - view scope. - :return: Iterable[BatchResults] - """ - _service = self._reactive_source_service - - rk_condition = _service.get_registered_scope_condition( - start=str(start.timestamp()), end=str(end.timestamp()) - ) - f_condition = _service.get_succeeded_condition(True) - if not any((cloud_ids, tenants, customer)): - # Issued scan. - f_condition = rk_condition & f_condition - rk_condition = None - - params = dict( - customer=customer, tenants=tenants, cloud_ids=cloud_ids, - ascending=False, range_condition=rk_condition, - filter_condition=f_condition - ) - return self._size_restrictive_query( - query=_service.inquery, params=params, - items_per_query=items_per_query, items_to_retrieve=limit, - last_evaluated_key=last_evaluated_key + @staticmethod + def merged(jobs: Iterator[Job], brs: Iterator[BatchResults], + ascending: bool = False) -> Iterable[Source]: + key = operator.attrgetter('submitted_at') + return heapq.merge(jobs, brs, key=key, reverse=not ascending) + + def get_by_customer_name(self, customer_name: str, + job_type: JobType = None, status: JobState = None, + start: datetime = None, end: datetime = None, + ascending: bool = False, limit: int = None, + ) -> Iterable[Source]: + cursor1 = self.job_service.get_by_customer_name( + customer_name=customer_name, + status=status, + start=start, + end=end, + ascending=ascending, + limit=limit ) - - def list_manual( - self, customer: Optional[str] = None, - tenants: Optional[List[str]] = None, - start: Optional[datetime] = None, end: Optional[datetime] = None, - limit: Optional[int] = None, - last_evaluated_key: Optional[str] = None, - items_per_query: Optional[int] = _DEF_ITEMS_PER_QUERY - ): - """ - Obtains Job entities, based on a provided customer, tenant - view scope. - :return: List[Job] - """ - _service = self._manual_source_service - - f_condition = _service.get_succeeded_condition(True) - rk_condition = _service.get_submitted_scope_condition( - start=start.isoformat(), end=end.isoformat() + cursor2 = self.batch_results_service.get_by_customer_name( + customer_name=customer_name, + status=status, + start=start, + end=end, + ascending=ascending, + limit=limit ) - - if not any((tenants, customer)): - # Issued scan. - f_condition = rk_condition & f_condition - rk_condition = None - - params = dict( - customer=customer, tenants=tenants, - ascending=False, range_condition=rk_condition, - filter_condition=f_condition + match job_type: + case JobType.MANUAL: + return cursor1 + case JobType.REACTIVE: + return cursor2 + case _: + return self.merged(cursor1, cursor2, ascending) + + def get_by_tenant_name(self, tenant_name: str, + job_type: JobType = None, status: JobState = None, + start: datetime = None, end: datetime = None, + ascending: bool = False, limit: int = None, + ) -> Iterable[Source]: + cursor1 = self.job_service.get_by_tenant_name( + tenant_name=tenant_name, + status=status, + start=start, + end=end, + ascending=ascending, + limit=limit ) - - return self._size_restrictive_query( - query=_service.inquery, params=params, - items_to_retrieve=limit, last_evaluated_key=last_evaluated_key, - items_per_query=items_per_query + cursor2 = self.batch_results_service.get_by_tenant_name( + tenant_name=tenant_name, + status=status, + start=start, + end=end, + ascending=ascending, + limit=limit ) + match job_type: + case JobType.MANUAL: + return cursor1 + case JobType.REACTIVE: + return cursor2 + case _: + return self.merged(cursor1, cursor2, ascending) @staticmethod - def sort_value_into_datetime(item: Source): - if isinstance(item, Job): - return utc_datetime(getattr(item, MANUAL_SORT_KEY_ATTR)) - else: - return ts_datetime(float(getattr(item, REACTIVE_SORT_KEY_ATTR))) - - @staticmethod - def get_attribute(item: Source, attr: str): - ref = { - Job: { - TENANT_ATTR: TENANT_DISPLAY_NAME_ATTR, - ID_ATTR: JOB_ID_ATTR, - CUSTOMER_ATTR: CUSTOMER_DISPLAY_NAME_ATTR - }, - BatchResults: { - TENANT_ATTR: TENANT_NAME_ATTR, - CUSTOMER_ATTR: CUSTOMER_NAME_ATTR, - STARTED_AT_ATTR: SUBMITTED_AT - } - } - if not hasattr(item, attr): - attr = ref.get(item.__class__, {}).get(attr, None) - return getattr(item, attr) if attr else None - - @staticmethod - def get_type(item: Source): - ref = { - Job: MANUAL_TYPE_ATTR, BatchResults: REACTIVE_TYPE_ATTR - } - return ref.get(item.__class__, None) - - @staticmethod - def _expand_reactive_attainment_params( - tenants: Optional[List[str]] = None, - cloud_ids: Optional[List[str]] = None - ): - param_list = [] - # Preference priority of cloud-id(s). - if cloud_ids: - attr = 'cloud_ids' - args = cloud_ids - elif tenants: - attr = 'tenants' - args = tenants - else: - return [{}] - - for arg in args or (None,): - param_list.append({attr: [arg] if arg else None}) - - return param_list - - @staticmethod - def _expand_manual_attainment_params( - tenants: Optional[List[str]] = None, - cloud_ids: Optional[List[str]] = None): - param_list = [] - kwargs = dict() - for tn in tenants or (None,): - _kwargs = kwargs.copy() - _kwargs.update(tenants=[tn] if tn else None) - param_list.append(_kwargs) - return param_list - - @classmethod - def derive_typ_param_map( - cls, typ: Optional[str] = None, - tenants: Optional[List[str]] = None, - cloud_ids: Optional[List[str]] = None, - ) -> Dict[str, List[Dict]]: - - output = {} - _expand_reactive = cls._expand_reactive_attainment_params - _expand_manual = cls._expand_manual_attainment_params - - typ_arg_expander_ref = { - (REACTIVE_TYPE_ATTR, tuple(cloud_ids)): _expand_reactive, - (MANUAL_TYPE_ATTR, tuple(cloud_ids)): _expand_manual - } - for ta, expander in typ_arg_expander_ref.items(): - _typ, arg = ta - if not typ or _typ == typ: - _LOG.info(f'Preparing {_typ} job-type batch-inquery payload.') - output[_typ] = expander(tenants, arg) - - return output - - @staticmethod - def _size_restrictive_query( - query: Callable, params: Dict, items_per_query: int, - items_to_retrieve: Optional[int] = None, - last_evaluated_key: Optional[str] = None - ): - listed = [] - - # Establish pending amount of items to retrieve. - pending = items_to_retrieve - if pending and pending > items_per_query: - pending -= items_per_query - - _LOG.info(f'Querying for {items_per_query} item(s), using {params}.') - iterator: IResultIterator = query( - limit=items_per_query, last_evaluated_key=last_evaluated_key, - **params - ) - listed.extend(list(iterator)) - - while iterator.last_evaluated_key: - - last_evaluated_key = iterator.last_evaluated_key - _limit = items_per_query - - if pending: - pending -= _limit - if pending < 0: - # Limit is too large, cutting down on the page size. - _limit -= pending - - _LOG.info(f'Querying for {_limit} item(s) more, using {params}.') - iterator: IResultIterator = query( - limit=_limit, last_evaluated_key=last_evaluated_key, **params - ) - listed.extend(list(iterator)) - - return listed + def to_ambiguous(it: Iterable[Source]) -> Iterator[AmbiguousJob]: + return map(AmbiguousJob, it) + def dto(self, job: Source) -> dict: + if isinstance(job, BatchResults): + return self.batch_results_service.dto(job) + return self.job_service.dto(job) diff --git a/src/services/assemble_service.py b/src/services/assemble_service.py index c194c46e7..aa471ac42 100644 --- a/src/services/assemble_service.py +++ b/src/services/assemble_service.py @@ -1,71 +1,57 @@ -from typing import List, Optional, Tuple +from typing import TYPE_CHECKING from modular_sdk.models.tenant import Tenant -from helpers import get_logger -from helpers.constants import ( - BATCH_STANDARD_JOB_TYPE, BATCH_ENV_TARGET_REGIONS, - BATCH_ENV_DEFAULT_REPORTS_BUCKET_NAME, BATCH_ENV_AWS_REGION, - BATCH_ENV_CREDENTIALS_KEY, BATCH_ENV_JOB_LIFETIME_MIN, - BATCH_ENV_JOB_TYPE, BATCH_ENV_LOG_LEVEL, BATCH_ENV_SUBMITTED_AT, - BATCH_ENV_AFFECTED_LICENSES, BATCH_ENV_LICENSED_RULESETS, - BATCH_ENV_TARGET_RULESETS_VIEW, BATCH_ENV_TARGET_RULESETS, - BATCH_ENV_TENANT_NAME, BATCH_ENV_STATS_S3_BUCKET_NAME, - BATCH_ENV_VAR_RULESETS_BUCKET_NAME, BATCH_ENV_PLATFORM_ID) -from helpers.time_helper import utc_iso -from services.environment_service import EnvironmentService +from helpers import sifted +from helpers.constants import CAASEnv, BatchJobEnv, BatchJobType +from helpers.log_helper import get_logger + +if TYPE_CHECKING: + from services.environment_service import EnvironmentService _LOG = get_logger(__name__) class AssembleService: - def __init__(self, environment_service: EnvironmentService): + def __init__(self, environment_service: 'EnvironmentService'): self.environment_service = environment_service - def build_job_envs(self, tenant: Tenant, - platform_id: Optional[str] = None, - target_regions: Optional[List[str]] = None, - target_rulesets: Optional[ - List[Tuple[str, str, str]]] = None, - affected_licenses: Optional[List[str]] = None, - licensed_rulesets: Optional[List[str]] = None, - job_type: Optional[str] = BATCH_STANDARD_JOB_TYPE, - credentials_key: Optional[str] = None - ) -> dict: + def build_job_envs(self, tenant: Tenant, job_id: str = None, + platform_id: str = None, + target_regions: list[str] = None, + target_rulesets: list[tuple[str, str, str]] = None, + affected_licenses: list[str] = None, + licensed_rulesets: list[str] = None, + job_type: BatchJobType = BatchJobType.STANDARD, + credentials_key: str = None) -> dict: # TODO +- duplicate in event_assembler_handler._build_common_envs envs = { - BATCH_ENV_DEFAULT_REPORTS_BUCKET_NAME: + CAASEnv.REPORTS_BUCKET_NAME: self.environment_service.default_reports_bucket_name(), - BATCH_ENV_STATS_S3_BUCKET_NAME: + CAASEnv.STATISTICS_BUCKET_NAME: self.environment_service.get_statistics_bucket_name(), - BATCH_ENV_VAR_RULESETS_BUCKET_NAME: self.environment_service.get_rulesets_bucket_name(), - BATCH_ENV_AWS_REGION: self.environment_service.aws_region(), - BATCH_ENV_JOB_LIFETIME_MIN: + CAASEnv.RULESETS_BUCKET_NAME: + self.environment_service.get_rulesets_bucket_name(), + CAASEnv.AWS_REGION: self.environment_service.aws_region(), + CAASEnv.BATCH_JOB_LIFETIME_MINUTES: self.environment_service.get_job_lifetime_min(), - BATCH_ENV_LOG_LEVEL: - self.environment_service.batch_job_log_level(), - BATCH_ENV_SUBMITTED_AT: utc_iso(), - BATCH_ENV_TENANT_NAME: tenant.name, - BATCH_ENV_PLATFORM_ID: platform_id, - BATCH_ENV_CREDENTIALS_KEY: credentials_key, - BATCH_ENV_JOB_TYPE: job_type, - BATCH_ENV_TARGET_REGIONS: ','.join(target_regions or []), + CAASEnv.LM_TOKEN_LIFETIME_MINUTES: + str(self.environment_service.lm_token_lifetime_minutes()), + 'LOG_LEVEL': self.environment_service.batch_job_log_level(), + BatchJobEnv.TENANT_NAME: tenant.name, + BatchJobEnv.PLATFORM_ID: platform_id, + BatchJobEnv.CREDENTIALS_KEY: credentials_key, + BatchJobEnv.JOB_TYPE: job_type.value if isinstance(job_type, BatchJobType) else job_type, # noqa + BatchJobEnv.TARGET_REGIONS: ','.join(target_regions or []), + BatchJobEnv.CUSTODIAN_JOB_ID: job_id, + } if target_rulesets: - envs[BATCH_ENV_TARGET_RULESETS] = ','.join( + envs[BatchJobEnv.TARGET_RULESETS] = ','.join( t[0] for t in target_rulesets) - envs[BATCH_ENV_TARGET_RULESETS_VIEW] = ','.join( - f'{t[1]}:{t[2]}' for t in target_rulesets - ) if affected_licenses and licensed_rulesets: envs.update({ - BATCH_ENV_AFFECTED_LICENSES: ','.join(affected_licenses), - BATCH_ENV_LICENSED_RULESETS: ','.join(licensed_rulesets) + BatchJobEnv.AFFECTED_LICENSES: ','.join(affected_licenses), + BatchJobEnv.LICENSED_RULESETS: ','.join(licensed_rulesets) }) - return self.sifted(envs) - - @staticmethod - def sifted(dct: dict) -> dict: - return { - k: v for k, v in dct.items() if isinstance(v, (bool, int)) or v - } + return sifted(envs) diff --git a/src/services/azure_subscriptions_service.py b/src/services/azure_subscriptions_service.py deleted file mode 100644 index 91d079d33..000000000 --- a/src/services/azure_subscriptions_service.py +++ /dev/null @@ -1,49 +0,0 @@ -# from azure.mgmt.resource import SubscriptionClient -# from azure.common.credentials import ServicePrincipalCredentials -# from msrest.exceptions import AuthenticationError -# from msrestazure.azure_exceptions import CloudError -# -# from helpers import CustodianException, RESPONSE_BAD_REQUEST_CODE, \ -# RESPONSE_INTERNAL_SERVER_ERROR -# from helpers.log_helper import get_logger -# -# _LOG = get_logger(__name__) -# -# -# class AzureSubscriptionsService: -# -# @staticmethod -# def validate_credentials(tenant_id, client_id, client_secret, -# subscription_id): -# _LOG.debug(f'Validating azure credentials') -# try: -# credential = ServicePrincipalCredentials(tenant=tenant_id, -# client_id=client_id, -# secret=client_secret) -# subscription_client = SubscriptionClient(credential) -# subscription_client.subscriptions.get( -# subscription_id=subscription_id) -# except AuthenticationError as e: -# _LOG.error(f'Invalid credentials: {str(e)}') -# raise CustodianException( -# code=RESPONSE_BAD_REQUEST_CODE, -# content=f'Invalid auth credentials provided' -# ) -# -# except CloudError as e: -# _LOG.debug(f'Error occurred on subscription id ' -# f'\'{subscription_id}\' validation: {e.error.message}') -# raise CustodianException( -# code=RESPONSE_BAD_REQUEST_CODE, -# content=f'Invalid subscription id provided: ' -# f'\'{subscription_id}\'' -# ) -# except Exception as e: -# _LOG.debug(f'Unexpected error occurred on azure credentials ' -# f'validation: {str(e)}') -# raise CustodianException( -# code=RESPONSE_INTERNAL_SERVER_ERROR, -# content=f'Unexpected error occurred on azure credentials ' -# f'validation. Please check your credentials and' -# f'try again.' -# ) diff --git a/src/services/base_data_service.py b/src/services/base_data_service.py index fbbfc626f..348f6070c 100644 --- a/src/services/base_data_service.py +++ b/src/services/base_data_service.py @@ -1,10 +1,11 @@ -import copy -from typing import TypeVar, Generic, Optional, Dict, Any, Iterable +from typing import TypeVar, Generic, Any, Iterable T = TypeVar('T') class BaseDataService(Generic[T]): + __slots__ = '_model_class', + def __init__(self): self._model_class = self.__orig_bases__[0].__args__[0] @@ -14,12 +15,13 @@ def save(self, item: T): def delete(self, item: T): item.delete() - def get_nullable(self, *args, **kwargs) -> Optional[T]: + def get_nullable(self, *args, **kwargs) -> T | None: return self._model_class.get_nullable(*args, **kwargs) - def dto(self, item: T) -> Dict[str, Any]: - # TODO correct special cases (datetime, etc) if necessary - return copy.deepcopy(item.attribute_values) + def dto(self, item: T) -> dict[str, Any]: + # i dont think we will save item after mangling its dto + return item.attribute_values + # return copy.deepcopy(item.attribute_values) def create(self, **kwargs) -> T: return self._model_class(**{ @@ -31,7 +33,7 @@ def create(self, **kwargs) -> T: def model_class(self) -> T: return self._model_class - def not_found_message(self, _id: Optional[str] = None) -> str: + def not_found_message(self, _id: str | None = None) -> str: """ Let it be here currently :return: diff --git a/src/services/batch_results_service.py b/src/services/batch_results_service.py index 2cbae0956..352245da5 100644 --- a/src/services/batch_results_service.py +++ b/src/services/batch_results_service.py @@ -1,327 +1,107 @@ import uuid from datetime import datetime -from typing import Optional, List, Union +from typing import Optional, Any -from pynamodb.exceptions import QueryError from pynamodb.expressions.condition import Condition +from pynamodb.pagination import ResultIterator -from helpers import time_helper -from helpers.constants import JOB_SUCCEEDED_STATUS -from models.batch_results import BatchResults -from modular_sdk.models.pynamodb_extension.base_model import LastEvaluatedKey as Lek -from helpers.time_helper import ts_datetime, utc_iso - +from helpers.constants import JobState from helpers.log_helper import get_logger +from helpers.time_helper import utc_iso +from models.batch_results import BatchResults +from services.base_data_service import BaseDataService _LOG = get_logger(__name__) -ATTRS_TO_CONCEAL = ('rules', 'bucket_name', 'bucket_path', 'rulesets', 'credentials_key') -SUCCEEDED_STATUS = 'SUCCEEDED' -REG_START_ATTR = 'registration_start' -REG_END_ATTR = 'registration_end' - - -class BatchResultsService: - - @staticmethod - def create(data: dict) -> BatchResults: - results_data = {} - for attribute in BatchResults.get_attributes(): - value = data.get(attribute) - if value is None: - continue - results_data[attribute] = value - if not results_data.get('id'): - results_data['id'] = str(uuid.uuid4()) - return BatchResults(**results_data) - - @staticmethod - def get(batch_results: str) -> Optional[BatchResults]: - return BatchResults.get_nullable(batch_results) - - @staticmethod - def list() -> list: - batch_results = [] - response = BatchResults.scan() - batch_results.extend(list(response)) - last_evaluated_key = response.last_evaluated_key - - while last_evaluated_key: - response = BatchResults.scan( - last_evaluated_key=last_evaluated_key - ) - batch_results.extend(list(batch_results)) - last_evaluated_key = response.last_evaluated_key - return batch_results - - @staticmethod - def batch_save(entity_list: List[BatchResults]): - with BatchResults.batch_write() as writer: - for batch_result in entity_list: - writer.save(batch_result) - @classmethod - def get_between_period_by_tenant( - cls, tenant_name: str, start: str = None, end: str = None, - ascending: bool = False - ) -> list: - range_key_condition = cls.get_registered_scope_condition( - start=start, end=end +class BatchResultsService(BaseDataService[BatchResults]): + def create(self, customer_name: str, tenant_name: str, + rules: dict, cloud_identifier: str) -> BatchResults: + return super().create( + id=str(uuid.uuid4()), + customer_name=customer_name, + tenant_name=tenant_name, + rules=rules, + cloud_identifier=cloud_identifier ) - _cursor = BatchResults.tn_rs_index.query( - hash_key=tenant_name, range_key_condition=range_key_condition, - scan_index_forward=ascending - ) - items = list(_cursor) - last_evaluated_key = _cursor.last_evaluated_key - while last_evaluated_key: - _cursor = BatchResults.tn_rs_index.query( - hash_key=tenant_name, - last_evaluated_key=last_evaluated_key, - range_key_condition=range_key_condition, - scan_index_forward=ascending - ) - items.append(list(_cursor)) - last_evaluated_key = _cursor.last_evaluated_key - return items - @classmethod - def get_between_period_by_customer( - cls, customer_name: str, tenants: list = None, start: str = None, - end: str = None, ascending: bool = False, limit: int = None, - only_succeeded: bool = True, attributes_to_get: list = None) -> \ - List[BatchResults]: - filter_condition = None - range_key_condition = None + def update(self, job: BatchResults, batch_job_id: str = None, + reason: str = None, + status: JobState = None, stopped_at: str = None): + actions = [] + if batch_job_id: + actions.append(BatchResults.job_id.set(batch_job_id)) + if reason: + actions.append(BatchResults.reason.set(reason)) + if status: + actions.append(BatchResults.status.set(status.value)) + if stopped_at: + actions.append(BatchResults.stopped_at.set(stopped_at)) + if actions: + job.update(actions) + + def get_by_customer_name(self, customer_name: str, status: JobState = None, + start: datetime = None, end: datetime = None, + filter_condition: Optional[Condition] = None, + ascending: bool = False, limit: int = None, + last_evaluated_key: dict = None, + ) -> ResultIterator[BatchResults]: if start and end: - range_key_condition = BatchResults.stopped_at.between( - lower=start, upper=end + rkc = BatchResults.submitted_at.between( + lower=utc_iso(start), + upper=utc_iso(end) ) elif start: - range_key_condition = BatchResults.stopped_at >= start + rkc = (BatchResults.submitted_at >= utc_iso(start)) elif end: - range_key_condition = BatchResults.stopped_at <= end - - if tenants: - filter_condition &= BatchResults.tenant_name.is_in(*tenants) - if only_succeeded: - filter_condition &= BatchResults.status == SUCCEEDED_STATUS - _cursor = BatchResults.cn_jsta_index.query( - hash_key=customer_name, range_key_condition=range_key_condition, - scan_index_forward=ascending, attributes_to_get=attributes_to_get, - filter_condition=filter_condition, limit=limit - ) - items = list(_cursor) - last_evaluated_key = _cursor.last_evaluated_key - while last_evaluated_key: - try: - _cursor = BatchResults.cn_jsta_index.query( - hash_key=customer_name, - last_evaluated_key=last_evaluated_key, - range_key_condition=range_key_condition, - scan_index_forward=ascending, limit=limit, - attributes_to_get=attributes_to_get - ) - items.extend(list(_cursor)) - last_evaluated_key = _cursor.last_evaluated_key - except QueryError as e: - if e.cause_response_code == \ - 'ProvisionedThroughputExceededException': - _LOG.warning('Request rate on BatchResults table is too ' - 'high!') - time_helper.wait(1) - else: - raise e - - return items - - @classmethod - def get_between_period(cls, start: datetime = None, end: datetime = None, - attributes_to_get: list = None) -> list: - filter_condition = None - if start: - filter_condition &= \ - (BatchResults.submitted_at >= start.isoformat()) - if end: - filter_condition &= \ - (BatchResults.submitted_at <= end.isoformat()) - - _cursor = BatchResults.scan(filter_condition=filter_condition, - attributes_to_get=attributes_to_get) - items = list(_cursor) - last_evaluated_key = _cursor.last_evaluated_key - while last_evaluated_key: - _cursor = BatchResults.scan(filter_condition=filter_condition, - attributes_to_get=attributes_to_get, - last_evaluated_key=last_evaluated_key) - items.extend(list(_cursor)) - last_evaluated_key = _cursor.last_evaluated_key - return items - - @classmethod - def get_latest_by_account(cls, cloud_id: str, - succeeded_only: bool = True) -> list: - filter_condition = None - if succeeded_only: - filter_condition &= BatchResults.status == JOB_SUCCEEDED_STATUS - results = list(BatchResults.cid_rs_index.query( - hash_key=cloud_id, - scan_index_forward=True, - limit=1, - filter_condition=filter_condition)) - if results: - return results[0] - - @classmethod - def inquery( - cls, - customer: Optional[str] = None, - tenants: Optional[List[str]] = None, - cloud_ids: Optional[List[str]] = None, - range_condition: Optional[Condition] = None, - filter_condition: Optional[Condition] = None, - ascending: Optional[bool] = False, - last_evaluated_key: Optional[Union[Lek, str]] = None, - limit: Optional[str] = None, - attributes_to_get: Optional[List[str]] = None - ): - # cloud_ids now are excessive here, it's obsolete logic, - # but it's too scary to change something here.. - - action = BatchResults.scan - - last_evaluated_key = last_evaluated_key or '' - if isinstance(last_evaluated_key, str): - last_evaluated_key = Lek.deserialize(s=last_evaluated_key) - - params = dict( - filter_condition=filter_condition, - last_evaluated_key=last_evaluated_key, - attributes_to_get=attributes_to_get, - limit=limit - ) - - if any((cloud_ids, tenants, customer)): - params.update( - range_key_condition=range_condition, - scan_index_forward=ascending - ) - - if cloud_ids and len(cloud_ids) != 1: - # Digests composable lek: Dict[str, Union[int, Dict[str, Any]]] - _params = cls._get_query_hash_key_ref_params( - last_evaluated_key=(last_evaluated_key.value or {}), - partition_key_list=cloud_ids, params=params - ) - - _scope = ', '.join(map("'{}'".format, _params)) - _LOG.info(f'Collecting items of {_scope} account(s).') - params = dict( - hash_key_query_ref=_params, limit=limit, - scan_index_forward=ascending - ) - action = BatchResults.cid_rs_index.batch_query - - elif cloud_ids: - # len(cloud_ids) == 1. - cloud_id = cloud_ids[0] - _LOG.info(f'Collecting items of a \'{cloud_id}\' account.') - params.update(hash_key=cloud_id) - action = BatchResults.cid_rs_index.query - - elif tenants and len(tenants) != 1: - # Digests composable lek: Dict[str, Union[int, Dict[str, Any]]] - _params = cls._get_query_hash_key_ref_params( - last_evaluated_key=(last_evaluated_key.value or {}), - partition_key_list=tenants, params=params - ) - _scope = ', '.join(map("'{}'".format, _params)) - _LOG.info(f'Collecting items of {_scope} tenant(s).') - params = dict( - hash_key_query_ref=_params, limit=limit, - scan_index_forward=ascending - ) - action = BatchResults.tn_rs_index.batch_query - - elif tenants: - # len(tenants) == 1 - tenant = tenants[0] - _LOG.info(f'Collecting items of a \'{tenant}\' tenant.') - params.update(hash_key=tenant) - action = BatchResults.tn_rs_index.query - - elif customer: - _LOG.info(f'Collecting items of a \'{customer}\' customer.') - params.update(hash_key=customer) - action = BatchResults.cn_rs_index.query + rkc = (BatchResults.submitted_at < utc_iso(end)) else: - # Scan - params.update(limit=limit) - - return action(**params) - - @staticmethod - def timerange_captured_between(since: str, until: str): - return BatchResults.registration_start.between( - lower=since, upper=until + rkc = None + if status: + filter_condition &= (BatchResults.status == status.value) + return BatchResults.customer_name_submitted_at_index.query( + hash_key=customer_name, + range_key_condition=rkc, + filter_condition=filter_condition, + scan_index_forward=ascending, + limit=limit, + last_evaluated_key=last_evaluated_key ) - @staticmethod - def get_registered_scope_condition( - start: Optional[str] = None, end: Optional[str] = None - ): - """ - :param start: Optional[str], the lower bound - :param end: Optional[str], the upper bound - """ - cdn = None + def get_by_tenant_name(self, tenant_name: str, status: JobState = None, + start: datetime = None, end: datetime = None, + filter_condition: Optional[Condition] = None, + ascending: bool = False, limit: int = None, + last_evaluated_key: dict = None, + ) -> ResultIterator[BatchResults]: if start and end: - cdn = BatchResults.registration_start.between( - lower=start, upper=end + rkc = BatchResults.submitted_at.between( + lower=utc_iso(start), + upper=utc_iso(end) ) elif start: - cdn = BatchResults.registration_start >= start + rkc = (BatchResults.submitted_at >= utc_iso(start)) elif end: - cdn = BatchResults.registration_start <= end - return cdn - - @staticmethod - def get_succeeded_condition(succeeded: bool): - # todo query optimization: $status#$submitted-at - _status = BatchResults.status - op = _status.__eq__ if succeeded else _status.__ne__ - return op(SUCCEEDED_STATUS) - - @staticmethod - def dto(entity: BatchResults): - data = { - k: v for k, v in entity.get_json().items() - if k not in ATTRS_TO_CONCEAL - } - attrs = (REG_START_ATTR, REG_END_ATTR) - for each in attrs: - value = data.get(each) - if value: - data[each] = utc_iso(ts_datetime(float(value))) - if data.get('status') == SUCCEEDED_STATUS: - data.pop('job_id', None) - return data + rkc = (BatchResults.submitted_at < utc_iso(end)) + else: + rkc = None + if status: + filter_condition &= (BatchResults.status == status.value) + return BatchResults.tenant_name_submitted_at_index.query( + hash_key=tenant_name, + range_key_condition=rkc, + filter_condition=filter_condition, + scan_index_forward=ascending, + limit=limit, + last_evaluated_key=last_evaluated_key, + ) - @staticmethod - def _get_query_hash_key_ref_params( - last_evaluated_key: dict, partition_key_list: List[str], - params: dict - ): - """ - Returns `hash_key_ref` payload, digesting a last_evaluated_key, - presumably composed out of partition key pointers, reference to - which is stored within the respective list. - """ - output = {} - last_evaluated_key = last_evaluated_key or {} - for partition_key in partition_key_list: - _output = params.copy() - if partition_key in last_evaluated_key: - _output.update(last_evaluated_key=last_evaluated_key) - output[partition_key] = _output - return output + def dto(self, item: BatchResults) -> dict[str, Any]: + raw = super().dto(item) + raw.pop('rules', None) + raw.pop('job_id', None) + raw.pop('credentials_key', None) + raw.pop('reason', None) + raw.pop('registration_start', None) + raw.pop('registration_end', None) + return raw diff --git a/src/services/cache.py b/src/services/cache.py index daeb0c4a8..f5803cf44 100644 --- a/src/services/cache.py +++ b/src/services/cache.py @@ -1,6 +1,6 @@ from typing import Callable, Any -from cachetools import TLRUCache, cachedmethod # noqa +from cachetools import TLRUCache, TTLCache, cachedmethod # noqa from services import SP @@ -14,7 +14,7 @@ def _expiration(key: Any, value: Any, now: float) -> float: :param now: :return: """ - return now + SP.environment_service().inner_cache_ttl_seconds() + return now + SP.environment_service.inner_cache_ttl_seconds() def factory(maxsize=50, ttu: Callable[[Any, Any, float], float] = _expiration diff --git a/src/services/clients/__init__.py b/src/services/clients/__init__.py index e69de29bb..45ad586fe 100644 --- a/src/services/clients/__init__.py +++ b/src/services/clients/__init__.py @@ -0,0 +1,119 @@ +from abc import ABC +from typing import Optional, Generic, TypeVar + +from boto3.resources.base import ServiceResource +from boto3.session import Session +from botocore.client import BaseClient +from botocore.config import Config +from typing_extensions import Self + + +class Boto3ClientFactory: + _session = Session() # class variable + + def __init__(self, service: str): + self._service = service + + def build(self, region_name: str = None, endpoint_url: str = None, + aws_access_key_id: str = None, aws_secret_access_key: str = None, + aws_session_token: str = None, config: Config = None, + ) -> BaseClient: + return self._session.client( + service_name=self._service, + region_name=region_name, + endpoint_url=endpoint_url, + aws_access_key_id=aws_access_key_id, + aws_secret_access_key=aws_secret_access_key, + aws_session_token=aws_session_token, + config=config + ) + + def build_resource(self, region_name: str = None, endpoint_url: str = None, + aws_access_key_id: str = None, + aws_secret_access_key: str = None, + aws_session_token: str = None, config: Config = None, + ) -> ServiceResource: + return self._session.resource( + service_name=self._service, + region_name=region_name, + endpoint_url=endpoint_url, + aws_access_key_id=aws_access_key_id, + aws_secret_access_key=aws_secret_access_key, + aws_session_token=aws_session_token, + config=config + ) + + def from_keys(self, aws_access_key_id: str, aws_secret_access_key: str, + aws_session_token: Optional[str] = None, + region_name: Optional[str] = None) -> BaseClient: + return self.build( + aws_access_key_id=aws_access_key_id, + aws_secret_access_key=aws_secret_access_key, + aws_session_token=aws_session_token, + region_name=region_name + ) + + +T = TypeVar('T') + + +class Boto3ClientWrapperFactory(Generic[T]): + """ + Client wrapper means a class wrapper over raw boto3 client + """ + + def __init__(self, client_class: T): + self._wrapper = client_class + + def build(self, region_name: Optional[str] = None) -> T: + instance = self._wrapper.build() + instance.client = Boto3ClientFactory(instance.service_name).build( + region_name=region_name + ) + return instance + + def from_keys(self, aws_access_key_id: str, aws_secret_access_key: str, + aws_session_token: Optional[str] = None, + region_name: Optional[str] = None) -> T: + instance = self._wrapper.build() + instance.client = Boto3ClientFactory(instance.service_name).from_keys( + aws_access_key_id=aws_access_key_id, + aws_secret_access_key=aws_secret_access_key, + aws_session_token=aws_session_token, + region_name=region_name + ) + return instance + + +class Boto3ClientWrapper(ABC): + _client = None + _resource = None + service_name: str = None + + @classmethod + def build(cls) -> Self: + return cls() + + @property + def client(self) -> BaseClient: + return self._client + + @client.setter + def client(self, value: BaseClient): + assert isinstance(value, BaseClient) and \ + value.meta.service_model.service_name == self.service_name + self._client = value + + @property + def resource(self) -> ServiceResource: + return self._resource + + @resource.setter + def resource(self, value: ServiceResource): + assert isinstance(value, ServiceResource) and \ + value.meta.service_name == self.service_name + self._resource = value + + @classmethod + def factory(cls) -> Boto3ClientWrapperFactory[Self]: + return Boto3ClientWrapperFactory(cls) diff --git a/src/services/clients/abstract_key_management.py b/src/services/clients/abstract_key_management.py deleted file mode 100644 index 9ee3b49ec..000000000 --- a/src/services/clients/abstract_key_management.py +++ /dev/null @@ -1,98 +0,0 @@ -from abc import ABC, abstractmethod -from typing import Union, Optional, TypeVar, Generic, Dict -from re import match -from helpers.log_helper import get_logger - -K = TypeVar('K') - -KEY_TYPE_ATTR = 'key_typ' -KEY_STD_ATTR = 'key_std' -SIG_SCHEME_ATTR = 'sig_scheme' -HASH_TYPE_ATTR = 'hash_type' -HASH_STD_ATTR = 'hash_std' - -ALG_PATTERN = f'(?P<{KEY_TYPE_ATTR}>.+):(?P<{KEY_STD_ATTR}>.+)_' \ - f'(?P<{SIG_SCHEME_ATTR}>.+)_' \ - f'(?P<{HASH_TYPE_ATTR}>.+):(?P<{HASH_STD_ATTR}>.+)' - - -_LOG = get_logger(__name__) - - -class IKey(Generic[K]): - - @classmethod - def import_key(cls, encoded: Union[str, bytes], **kwargs) -> K: - ... - - def export_key(self, format: str, **kwargs) -> Union[str, bytes]: - ... - - def public_key(self) -> K: - ... - - -class AbstractKeyManagementClient(ABC): - - @abstractmethod - def sign(self, key_id, message: Union[str, bytes], algorithm: str, - encoding='utf-8') -> Optional[bytes]: - raise NotImplementedError() - - @abstractmethod - def verify(self, key_id: str, message: Union[str, bytes], algorithm: str, - signature: bytes, encoding='utf-8') -> bool: - raise NotImplementedError() - - @abstractmethod - def generate(self, key_type: str, key_std: str, **data) -> IKey: - raise NotImplementedError() - - @abstractmethod - def save(self, key_id: str, key: IKey, key_format: str, **data): - raise NotImplementedError() - - @abstractmethod - def delete(self, key_id: str): - raise NotImplementedError() - - @abstractmethod - def get_key(self, key_type: str, key_std: str, key_data: dict) -> \ - Optional[IKey]: - raise NotImplementedError() - - @abstractmethod - def get_key_data(self, key_id: str) -> Optional[dict]: - raise NotImplementedError() - - @classmethod - @abstractmethod - def construct(cls, key_type: str, key_std: str, key_value: str, **data): - raise NotImplementedError() - - @classmethod - @abstractmethod - def is_signature_scheme_accessible( - cls, sig_scheme: str, key_type: str, key_std: str, hash_type: str, - hash_std: str - ): - raise NotImplemented() - - @staticmethod - def dissect_alg(alg: str) -> Optional[Dict[str, str]]: - """ - Returns `alg` value dissected dict-object, with the following keys: - - key_type: str - - key_std: str - - sig_scheme: str - - hash_type: str - - hash_std: str - :return: Optional[Dict[str, str]] - """ - mapped = None - try: - m = match(pattern=ALG_PATTERN, string=alg) - mapped = m.groupdict() - except (ValueError, Exception) as e: - _LOG.warning(f'Alg:\'{alg}\' could not be dissected due to "{e}".') - return mapped diff --git a/src/services/clients/batch.py b/src/services/clients/batch.py index 74ab3f10c..cca060f21 100644 --- a/src/services/clients/batch.py +++ b/src/services/clients/batch.py @@ -1,102 +1,53 @@ -import boto3 -from typing import Union +import dataclasses +import os +import subprocess +import sys +import uuid +from pathlib import Path +from typing import TypedDict -from helpers.log_helper import get_logger from helpers import title_keys -from services.environment_service import EnvironmentService +from helpers.constants import BatchJobEnv +from helpers.log_helper import get_logger +from helpers.time_helper import utc_iso, utc_datetime +from services import SP +from services.clients import Boto3ClientWrapper from services.clients.sts import StsClient +from services.environment_service import EnvironmentService _LOG = get_logger(__name__) -class BatchClient: +class BatchJob(TypedDict, total=False): + jobArn: str + jobName: str + jobId: str + jobQueue: str + status: str + createdAt: int + startedAt: int + stoppedAt: int + # ... and other params + + +class BatchClient(Boto3ClientWrapper): + service_name = 'batch' def __init__(self, environment_service: EnvironmentService, sts_client: StsClient): self._environment = environment_service self._sts_client = sts_client - self._client = None - - @property - def client(self): - if not self._client: - self._client = boto3.client( - 'batch', self._environment.aws_region()) - return self._client - - def submit_job(self, job_name: str, job_queue: str, job_definition: str, - command: str, size: int = None, depends_on: list = None, - parameters=None, retry_strategy: int = None, - timeout: int = None, environment_variables: dict = None): - params = { - 'jobName': job_name, - 'jobQueue': job_queue, - 'jobDefinition': job_definition, - } - if size: - params['arrayProperties'] = {'size': size} - if depends_on: - params['dependsOn'] = depends_on - if parameters: - params['parameters'] = parameters - if retry_strategy: - params['retryStrategy'] = {'attempts': retry_strategy} - if timeout: - params['timeout'] = {'attemptDurationSeconds': timeout} - container_overrides = self.build_container_overrides( - command, environment_variables) - params.update(container_overrides) - response = self.client.submit_job(**params) - return response - - def terminate_job(self, job_id: str, reason: str = 'Terminating job.'): - params = { - 'jobId': job_id, - 'reason': reason - } - response = self.client.terminate_job(**params) - return response - - def describe_jobs(self, jobs: list): - response = self.client.describe_jobs(jobs=jobs) - if response: - return response.get('jobs', []) - return [] - - def get_job_definition_by_name(self, job_def_name): - _LOG.debug(f'Retrieving last job definition with name {job_def_name}') - return self.client.describe_job_definitions( - jobDefinitionName=job_def_name, status='ACTIVE', - maxResults=1)['jobDefinitions'] - - def create_job_definition(self, job_def_name, image_url, command, platform, - job_role_arn=None, resource_requirements=None): - return self.client.register_job_definition( - jobDefinitionName=job_def_name, type='container', - containerProperties={ - 'image': image_url, 'command': command, - 'jobRoleArn': job_role_arn, - 'resourceRequirements': resource_requirements, - }, - platformCapabilities=platform + @classmethod + def build(cls) -> 'BatchClient': + return cls( + environment_service=SP.environment_service, + sts_client=SP.sts ) - def create_job_definition_from_existing_one(self, job_def, image_url): - job_def_name = job_def['jobDefinitionName'] - properties = job_def['containerProperties'] - if not properties: - _LOG.debug('No containerProperties field in the last job ' - 'definition - cannot specify command, jobRoleArn') - return None - command = properties['command'] - job_role_arn = properties['jobRoleArn'] - resource_requirements = properties['resourceRequirements'] - platform = job_def['platformCapabilities'] - return self.create_job_definition( - job_def_name=job_def_name, image_url=image_url, command=command, - platform=platform, job_role_arn=job_role_arn, - resource_requirements=resource_requirements) + def get_job(self, job_id: str) -> BatchJob | None: + response = self.client.describe_jobs(jobs=[job_id]) + return next(iter(response.get('jobs') or []), None) def build_queue_arn(self, queue_name: str) -> str: """ @@ -122,8 +73,21 @@ def get_custodian_job_definition_arn(self) -> str: # the service is not properly configured return job_definitions[0]['jobDefinitionArn'] + def get_job_definition_by_name(self, job_def_name): + _LOG.debug(f'Retrieving last job definition with name {job_def_name}') + return self.client.describe_job_definitions( + jobDefinitionName=job_def_name, status='ACTIVE', + maxResults=1)['jobDefinitions'] + + def terminate_job(self, job_id: str, reason: str = 'Terminating job.'): + response = self.client.terminate_job( + jobId=job_id, + reason=reason + ) + return response + @staticmethod - def build_container_overrides(command: Union[str, list] = None, + def build_container_overrides(command: str | list = None, environment: dict = None, titled: bool = False) -> dict: """ @@ -145,3 +109,93 @@ def build_container_overrides(command: Union[str, list] = None, if titled: result = title_keys(result) return result + + def submit_job(self, job_name: str, job_queue: str, job_definition: str, + command: str = None, size: int = None, + depends_on: list = None, + parameters=None, retry_strategy: int = None, + timeout: int = None, environment_variables: dict = None + ) -> BatchJob: + + params = { + 'jobName': job_name, + 'jobQueue': job_queue, + 'jobDefinition': job_definition, + } + if size: + params['arrayProperties'] = {'size': size} + if depends_on: + params['dependsOn'] = depends_on + if parameters: + params['parameters'] = parameters + if retry_strategy: + params['retryStrategy'] = {'attempts': retry_strategy} + if timeout: + params['timeout'] = {'attemptDurationSeconds': timeout} + container_overrides = self.build_container_overrides( + command=command, + environment=environment_variables + ) + params.update(container_overrides) + return self.client.submit_job(**params) + + +@dataclasses.dataclass(slots=True, repr=False, frozen=True) +class _Job: + id: str + name: str + process: subprocess.Popen + started_at: int = dataclasses.field( + default_factory=lambda: utc_datetime().timestamp() * 1e3 + ) + + def serialize(self) -> BatchJob: + return { + 'jobId': self.id, + 'startedAt': self.started_at, + 'jobName': self.name + } + + +class SubprocessBatchClient: + def __init__(self): + self._jobs: dict[str, _Job] = {} # job_id to Job + + @classmethod + def build(cls) -> 'SubprocessBatchClient': + return cls() + + def submit_job(self, environment_variables: dict = None, + job_name: str = None, **kwargs + ) -> BatchJob: + environment_variables = environment_variables or {} + # self.check_ability_to_start_job() + + # Popen raises TypeError in case there is an env where value is None + job_id = str(uuid.uuid4()) + environment_variables[BatchJobEnv.JOB_ID] = job_id + environment_variables[ + BatchJobEnv.SUBMITTED_AT] = utc_iso() # for scheduled jobs + env = {**os.environ, **environment_variables} + process = subprocess.Popen([ + sys.executable, + (Path(__file__).parent.parent.parent / 'run.py').resolve() + ], env=env, shell=False) + job = _Job( + id=job_id, + process=process, + name=job_name + ) + self._jobs[job_id] = job # MOVE TODO think how to gc + return job.serialize() + + def terminate_job(self, job_id: str, **kwargs): + if job_id not in self._jobs: + return + job = self._jobs.pop(job_id) + job.process.kill() + + def get_job(self, job_id: str) -> BatchJob | None: + job = self._jobs.get(job_id) + if job: + return job.serialize() diff --git a/src/services/clients/cloudwatch.py b/src/services/clients/cloudwatch.py deleted file mode 100644 index 4577b9e69..000000000 --- a/src/services/clients/cloudwatch.py +++ /dev/null @@ -1,34 +0,0 @@ -import boto3 - - -class CloudWatchClient: - - def __init__(self, region): - self._region = region - self._client = None - - @property - def client(self): - if not self._client: - self._client = boto3.client('logs', self._region) - return self._client - - def get_log_events(self, log_group_name, log_stream_name, - start: int, end: int): - response = self.client.get_log_events( - logGroupName=log_group_name, - logStreamName=log_stream_name, - startTime=start, - endTime=end - ) - next_token = response.get('nextToken') - while next_token: - response['events'].append(self.client.get_log_events( - logGroupName=log_group_name, - logStreamName=log_stream_name, - startTime=start, - endTime=end, - nextToken=next_token) - ).get('events') - next_token = response.get('nextToken') - return response['events'] diff --git a/src/services/clients/cognito.py b/src/services/clients/cognito.py index 4779fd308..90fda75a3 100644 --- a/src/services/clients/cognito.py +++ b/src/services/clients/cognito.py @@ -1,296 +1,409 @@ +from abc import ABC, abstractmethod +from datetime import datetime +from functools import cached_property from http import HTTPStatus -from typing import Optional, Union +from typing import Generator, Iterator, TYPE_CHECKING +from typing_extensions import NotRequired, Self, TypedDict import boto3 - -from connections.auth_extension.base_auth_client import BaseAuthClient -from helpers import CustodianException -from helpers.constants import CUSTOM_CUSTOMER_ATTR, CUSTOM_ROLE_ATTR, \ - CUSTOM_TENANTS_ATTR, CUSTOM_LATEST_LOGIN_ATTR +from botocore.exceptions import ClientError +from botocore.paginate import PageIterator + +from helpers.constants import ( + CUSTOM_CUSTOMER_ATTR, + CUSTOM_LATEST_LOGIN_ATTR, + CUSTOM_ROLE_ATTR, +) +from helpers.lambda_response import ResponseFactory from helpers.log_helper import get_logger -from helpers.system_customer import SYSTEM_CUSTOMER -from helpers.time_helper import utc_iso, utc_datetime +from helpers.time_helper import utc_datetime, utc_iso from services.environment_service import EnvironmentService +if TYPE_CHECKING: + from models.user import User + _LOG = get_logger(__name__) -PARAM_USER_POOLS = 'UserPools' + +class _CognitoUserAttr(TypedDict): + Name: str + Value: str + + +class CognitoUserModel(TypedDict): + Username: str + UserAttributes: NotRequired[list[_CognitoUserAttr]] # either this one + Attributes: NotRequired[list[_CognitoUserAttr]] # or this one + UserCreateDate: datetime + UserLastModifiedDate: datetime + Enabled: bool + + +class UserWrapper: + __slots__ = ('id', 'username', 'customer', 'role', 'latest_login', + 'created_at') + + def __init__(self, username: str, customer: str | None = None, + role: str | None = None, latest_login: datetime | None = None, + created_at: datetime | None = None, sub: str | None = None): + """ + Sub is not used currently, so it's not important. Username represents + user id + :param username: + :param customer: + :param role: + :param latest_login: + :param created_at: + :param sub: + """ + self.username = username + self.customer = customer + self.role = role + self.latest_login = latest_login + self.created_at = created_at + self.id = sub + + @classmethod + def from_user_model(cls, user: 'User') -> Self: + ll = None + if user.latest_login: + ll = utc_datetime(user.latest_login) + ca = None + if user.created_at: + ca = utc_datetime(user.created_at) + return cls( + sub=str(user.mongo_id), # noqa valid for onprem + username=user.user_id, + customer=user.customer, + role=user.role, + latest_login=ll, + created_at=ca + ) + + @classmethod + def from_cognito_model(cls, model: CognitoUserModel) -> Self: + attrs = model.get('UserAttributes') or model.get('Attributes') or () + attributes = {a['Name']: a['Value'] for a in attrs} + ll = None + if item := attributes.get(CUSTOM_LATEST_LOGIN_ATTR): + ll = utc_datetime(item) + return cls( + sub=attributes.get('sub'), # valid for onprem + username=model['Username'], + customer=attributes.get(CUSTOM_CUSTOMER_ATTR), + role=attributes.get(CUSTOM_ROLE_ATTR), + latest_login=ll, + created_at=model['UserCreateDate'] + ) + + def get_dto(self) -> dict: + return { + 'username': self.username, + 'customer': self.customer, + 'role': self.role, + 'latest_login': utc_iso( + self.latest_login) if self.latest_login else None, + 'created_at': utc_iso(self.created_at) if self.created_at else None + } + + +class UsersIterator(Iterator[UserWrapper]): + next_token: str | int | None = None + + def __iter__(self): + return self + + def __next__(self) -> UserWrapper: + raise NotImplementedError + + +class CognitoUsersIterator(UsersIterator): + def __init__(self, page_iterator: PageIterator, + customer: str | None = None): + """ + :param page_iterator: cognito-idp list_users pages iterator + :param customer: allows to filter the result additionally by custom + customer attr + """ + self._page_iterable = page_iterator + self._customer = customer + + def __iter__(self): + self._it = iter(self._page_iterable) + self._page = None + return self + + def __next__(self) -> UserWrapper: + while 1: + if not self._page: + dct = self._it.__next__() # getting next page + self.next_token = dct.get('PaginationToken') + _users = map(UserWrapper.from_cognito_model, + dct.get('Users') or ()) + if self._customer: + self._page = filter( + lambda u: u.customer == self._customer, _users + ) + else: + self._page = _users + try: + return self._page.__next__() + except StopIteration: + self._page = None + + +class AuthenticationResult(TypedDict): + id_token: str + refresh_token: str | None + expires_in: int + + +class BaseAuthClient(ABC): + @abstractmethod + def get_user_by_username(self, username: str) -> UserWrapper | None: + pass + + @abstractmethod + def query_users(self, customer: str | None = None, + limit: int | None = None, + next_token: str | dict | None = None) -> UsersIterator: + pass + + @abstractmethod + def set_user_password(self, username: str, password: str) -> bool: + pass + + @abstractmethod + def update_user_attributes(self, user: UserWrapper): + """ + Updates all the attributes that are not equal to None in user wrapper + :param user: + :return: + """ + + @abstractmethod + def delete_user(self, username: str) -> None: + pass + + @abstractmethod + def authenticate_user(self, username: str, password: str + ) -> AuthenticationResult | None: + pass + + @abstractmethod + def refresh_token(self, refresh_token: str) -> AuthenticationResult | None: + pass + + @abstractmethod + def signup_user(self, username: str, password: str, + customer: str | None = None, role: str | None = None) -> UserWrapper: + pass + + def does_user_exist(self, username: str) -> bool: + """ + Use only if you don't need the user's data + :param username: + :return: + """ + return not not self.get_user_by_username(username) class CognitoClient(BaseAuthClient): def __init__(self, environment_service: EnvironmentService): - self._environment = environment_service - self._client = None - self._user_pool_id, self._client_id = None, None + self._env = environment_service - @property + @cached_property def client(self): - if not self._client: - self._client = boto3.client('cognito-idp', - self._environment.aws_region()) - return self._client + return boto3.client('cognito-idp', region_name=self._env.aws_region()) @property def user_pool_name(self) -> str: - return self._environment.get_user_pool_name() + return self._env.get_user_pool_name() - @property + @cached_property def user_pool_id(self) -> str: - if not self._user_pool_id: - _LOG.info('Retrieving user pool id') - _id = self._environment.get_user_pool_id() - if not _id: - _LOG.warning('User pool id is not found in envs. ' - 'Scanning all the available pools to get the id') - _id = self._pool_id_from_name(self.user_pool_name) - if not _id: - _message = 'Application Authentication Service is ' \ - 'not configured properly.' - _LOG.error(f'User pool \'{self.user_pool_name}\' does ' - f'not exists. {_message}') - raise CustodianException(code=HTTPStatus.INTERNAL_SERVER_ERROR, - content=_message) - self._user_pool_id = _id - return self._user_pool_id + _LOG.info('Retrieving user pool id') + _id = self._env.get_user_pool_id() + if not _id: + _LOG.warning('User pool id is not found in envs. ' + 'Scanning all the available pools to get the id') + _id = self._pool_id_from_name(self.user_pool_name) + if not _id: + _message = 'Application Authentication Service is ' \ + 'not configured properly.' + _LOG.error(f'User pool \'{self.user_pool_name}\' does ' + f'not exists. {_message}') + raise ResponseFactory(HTTPStatus.SERVICE_UNAVAILABLE).message( + _message).exc() + return _id @property def client_id(self) -> str: - if not self._client_id: - client = self.client.list_user_pool_clients( - UserPoolId=self.user_pool_id, MaxResults=1)['UserPoolClients'] - if not client: - _message = 'Application Authentication Service is not ' \ - 'configured properly: no client applications found' - _LOG.error(_message) - raise CustodianException( - code=HTTPStatus.INTERNAL_SERVER_ERROR, - content=_message) - self._client_id = client[0]['ClientId'] - return self._client_id - - def _pool_id_from_name(self, name: str) -> Optional[str]: + client = self.client.list_user_pool_clients( + UserPoolId=self.user_pool_id, MaxResults=1)['UserPoolClients'] + if not client: + _message = 'Application Authentication Service is not ' \ + 'configured properly: no client applications found' + _LOG.error(_message) + raise ResponseFactory(HTTPStatus.SERVICE_UNAVAILABLE).message( + _message + ).exc() + return client[0]['ClientId'] + + def _pool_id_from_name(self, name: str) -> str | None: """ Since AWS Cognito can have two different pools with equal names, this method returns the first pool id which will be found. """ - pools = (p for p in self._list_user_pools() if p['Name'] == name) + for pool in self._list_user_pools(): + if pool['Name'] == name: + return pool['Id'] + + def _list_user_pools(self) -> Generator[dict, None, None]: + first = True + params = dict(MaxResults=10) + while params.get('NextToken') or first: + pools = self.client.list_user_pools(**params) + yield from pools.get('UserPools') or [] + params['NextToken'] = pools.get('NextToken') + if first: + first = False + + def get_user_by_username(self, username: str) -> UserWrapper | None: try: - return next(pools)['Id'] - except StopIteration: + item = self.client.admin_get_user( + UserPoolId=self.user_pool_id, + Username=username + ) + _LOG.debug(f'Result of admin_get_user: {item}') + return UserWrapper.from_cognito_model(item) + except ClientError: + _LOG.warning('ClientError occurred querying a user', exc_info=True) return - def _list_user_pools(self) -> list: - list_user_pools = self.client.list_user_pools(MaxResults=10) - pools = [] - while list_user_pools[PARAM_USER_POOLS]: - pools.extend(list_user_pools[PARAM_USER_POOLS]) - next_token = list_user_pools.get('NextToken') - if next_token: - list_user_pools = self.client.list_user_pools( - MaxResults=10, NextToken=next_token) - else: - break - return pools - - def _list_users(self, attributes_to_get=None): - params = dict(UserPoolId=self.user_pool_id) - if attributes_to_get: - params['AttributesToGet'] = attributes_to_get - return self.client.list_users(**params) - - def admin_initiate_auth(self, username, password): - """ - Initiates the authentication flow. Returns AuthenticationResult if - the caller does not need to pass another challenge. If the caller - does need to pass another challenge before it gets tokens, - ChallengeName, ChallengeParameters, and Session are returned. - """ - auth_params = { - 'USERNAME': username, - 'PASSWORD': password - } - if self.is_user_exists(username): - try: - result = self.client.admin_initiate_auth( - UserPoolId=self.user_pool_id, ClientId=self.client_id, - AuthFlow='ADMIN_NO_SRP_AUTH', AuthParameters=auth_params) - self.update_latest_login(username) - return result - except self.client.exceptions.NotAuthorizedException: - return None - - def admin_delete_user(self, username: str): - self.client.admin_delete_user(UserPoolId=self.user_pool_id, - Username=username) - - def respond_to_auth_challenge(self, challenge_name): - """ - Responds to an authentication challenge. - """ - self.client.respond_to_auth_challenge(ClientId=self.client_id, - ChallengeName=challenge_name) - - def sign_up(self, username, password, customer, role, tenants=None): - custom_attr = [{ - 'Name': 'name', - 'Value': username - }, { - 'Name': CUSTOM_CUSTOMER_ATTR, - 'Value': customer - }, { - 'Name': CUSTOM_ROLE_ATTR, - 'Value': role - }] - if tenants: - custom_attr.append({ - 'Name': CUSTOM_TENANTS_ATTR, - 'Value': tenants - }) - validation_data = [ - { - 'Name': 'name', - 'Value': username - } - ] - return self.client.sign_up(ClientId=self.client_id, - Username=username, - Password=password, - UserAttributes=custom_attr, - ValidationData=validation_data) - - def set_password(self, username, password, permanent=True): - return self.client.admin_set_user_password( - UserPoolId=self.user_pool_id, Username=username, - Password=password, Permanent=permanent) - - def _get_user(self, username) -> Optional[dict]: - users = self.client.list_users( + def query_users(self, customer: str | None = None, + limit: int | None = None, + next_token: str | dict | None = None) -> UsersIterator: + conf = {} + if limit: + conf['MaxItems'] = limit + if next_token: + conf['StartingToken'] = next_token + it = self.client.get_paginator('list_users').paginate( UserPoolId=self.user_pool_id, - Limit=1, - Filter=f'username = "{username}"')['Users'] - if len(users) >= 1: - return users[0] - - def is_user_exists(self, username) -> bool: - return bool(self._get_user(username)) - - def _get_user_attr(self, user, attr_name, query_user=True): - """user attribute can be either a 'username' or a user dict object - already fetched from AWS Cognito""" - if query_user: - user = self._get_user(username=user) - for attr in user['Attributes']: - if attr['Name'] == attr_name: - return attr['Value'] - - def get_user_role(self, username): - return self._get_user_attr(username, CUSTOM_ROLE_ATTR) - - def get_user_customer(self, username): - return self._get_user_attr(username, CUSTOM_CUSTOMER_ATTR) - - def get_user_tenants(self, username): - return self._get_user_attr(username, CUSTOM_TENANTS_ATTR) or '' - - def update_role(self, username, role): - role_attribute = [ - { - 'Name': CUSTOM_ROLE_ATTR, - 'Value': role - } - ] - self.client.admin_update_user_attributes(UserPoolId=self.user_pool_id, - Username=username, - UserAttributes=role_attribute) - - def get_user_latest_login(self, username): - return self._get_user_attr(username, CUSTOM_LATEST_LOGIN_ATTR) - - def update_latest_login(self, username: str): - latest_login_attribute = [ - { - 'Name': CUSTOM_LATEST_LOGIN_ATTR, - 'Value': utc_iso() - } - ] - self.client.admin_update_user_attributes( - UserPoolId=self.user_pool_id, Username=username, - UserAttributes=latest_login_attribute) - - def update_customer(self, username, customer): - customer_attribute = [ - { - 'Name': CUSTOM_CUSTOMER_ATTR, - 'Value': customer + PaginationConfig=conf + ) + return CognitoUsersIterator(it, customer=customer) + + def set_user_password(self, username: str, password: str) -> bool: + try: + self.client.admin_set_user_password( + UserPoolId=self.user_pool_id, + Username=username, + Password=password, + Permanent=True + ) + return True + except ClientError: + _LOG.warning('Could not set user password due to client error', + exc_info=True) + return False + + def update_user_attributes(self, user: UserWrapper): + def attr(n, v): + return dict(Name=n, Value=v) + + attributes = [] + if user.customer: + attributes.append(attr(CUSTOM_CUSTOMER_ATTR, user.customer)) + if user.role: + attributes.append(attr(CUSTOM_ROLE_ATTR, user.customer)) + if user.latest_login: + attributes.append(attr(CUSTOM_LATEST_LOGIN_ATTR, + utc_iso(user.latest_login))) + if attributes: + self.client.admin_update_user_attributes( + UserPoolId=self.user_pool_id, + Username=user.username, + UserAttributes=attributes + ) + + def delete_user(self, username: str) -> None: + try: + self.client.admin_delete_user( + UserPoolId=self.user_pool_id, + Username=username + ) + except ClientError as e: + if e.response['Error']['Code'] == 'UserNotFoundException': + pass + raise e + + def authenticate_user(self, username: str, password: str + ) -> AuthenticationResult | None: + try: + r = self.client.admin_initiate_auth( + UserPoolId=self.user_pool_id, + ClientId=self.client_id, + AuthFlow='ADMIN_NO_SRP_AUTH', + AuthParameters={'USERNAME': username, 'PASSWORD': password} + ) + return { + 'id_token': r['AuthenticationResult']['IdToken'], + 'refresh_token': r['AuthenticationResult'].get('RefreshToken'), + 'expires_in': r['AuthenticationResult']['ExpiresIn'] } - ] - self.client.admin_update_user_attributes( - UserPoolId=self.user_pool_id, Username=username, - UserAttributes=customer_attribute) - - def update_tenants(self, username: str, tenants: Union[str, list]): - if isinstance(tenants, list): - tenants = ','.join(tenants) - tenants_attribute = [ - { - 'Name': CUSTOM_TENANTS_ATTR, - 'Value': tenants + except self.client.exceptions.UserNotFoundException: + return + except self.client.exceptions.NotAuthorizedException: + return + + def refresh_token(self, refresh_token: str) -> AuthenticationResult | None: + try: + r = self.client.admin_initiate_auth( + UserPoolId=self.user_pool_id, + ClientId=self.client_id, + AuthFlow='REFRESH_TOKEN_AUTH', + AuthParameters={'REFRESH_TOKEN': refresh_token} + ) + return { + 'id_token': r['AuthenticationResult']['IdToken'], + 'refresh_token': r['AuthenticationResult'].get('RefreshToken'), + 'expires_in': r['AuthenticationResult']['ExpiresIn'] } - ] - self.client.admin_update_user_attributes( - UserPoolId=self.user_pool_id, Username=username, - UserAttributes=tenants_attribute) - - def delete_role(self, username): - self.client.admin_delete_user_attributes( - UserPoolId=self.user_pool_id, Username=username, - UserAttributeNames=[CUSTOM_ROLE_ATTR]) - - def delete_customer(self, username): - self.client.admin_delete_user_attributes( - UserPoolId=self.user_pool_id, Username=username, - UserAttributeNames=[CUSTOM_CUSTOMER_ATTR]) - - def delete_tenants(self, username): - self.client.admin_delete_user_attributes( - UserPoolId=self.user_pool_id, Username=username, - UserAttributeNames=[CUSTOM_TENANTS_ATTR]) - - def is_system_user_exists(self): - """Checks whether user with customer=$SYSTEM_CUSTOMER already exists""" - users = self._list_users(attributes_to_get=[CUSTOM_CUSTOMER_ATTR, ]) - for user in users['Users']: - if self._get_user_attr(user, CUSTOM_CUSTOMER_ATTR, - query_user=False) == SYSTEM_CUSTOMER: - return True - return False - - def get_system_user(self): - """ - Returns the user with customer=$SYSTEM_CUSTOMER - if exists, else - None - """ - users = self._list_users(attributes_to_get=[CUSTOM_CUSTOMER_ATTR, ]) - for user in users['Users']: - if self._get_user_attr(user, CUSTOM_CUSTOMER_ATTR, - query_user=False) == SYSTEM_CUSTOMER: - return user['Username'] - - def get_customers_latest_logins(self, customers=None): - customers = customers or [] - result = {} - users = self._list_users() - for user in users.get('Users', []): - customer = self._get_user_attr( - user, CUSTOM_CUSTOMER_ATTR, query_user=False) - # may be either with military prefix or without - latest_login = self._get_user_attr( - user, CUSTOM_LATEST_LOGIN_ATTR, query_user=False) - if not result.get(customer): - result[customer] = latest_login - elif latest_login and utc_datetime(result[customer]) < \ - utc_datetime(latest_login): - result[customer] = latest_login - if customers: - result = {k: v for k, v in result.items() if k in customers} - return result + except ClientError: + _LOG.warning('Client error occurred trying to refresh token', + exc_info=True) + + def signup_user(self, username: str, password: str, + customer: str | None = None, role: str | None = None + ) -> UserWrapper: + def attr(n, v): + return dict(Name=n, Value=v) + + attrs = [attr('name', username)] + if customer: + attrs.append(attr(CUSTOM_CUSTOMER_ATTR, customer)) + if role: + attrs.append(attr(CUSTOM_ROLE_ATTR, role)) + validation_data = [attr('name', username)] + res = self.client.sign_up( + ClientId=self.client_id, + Username=username, + Password=password, + UserAttributes=attrs, + ValidationData=validation_data + ) + self.client.admin_set_user_password( + UserPoolId=self.user_pool_id, + Username=username, + Password=password, + Permanent=True + ) + return UserWrapper( + username=username, + customer=customer, + role=role, + created_at=utc_datetime(), + sub=res['UserSub'], + ) diff --git a/src/services/clients/dojo_client.py b/src/services/clients/dojo_client.py index e3675f384..894f6d4c8 100644 --- a/src/services/clients/dojo_client.py +++ b/src/services/clients/dojo_client.py @@ -1,121 +1,84 @@ -import io -from http import HTTPStatus +from datetime import datetime import requests +import msgspec +from helpers.constants import HTTPMethod from helpers.log_helper import get_logger -from helpers.time_helper import utc_datetime - -DOJO_CAAS_SCAN_TYPE = 'Cloud Custodian Scan' _LOG = get_logger(__name__) -ENGAGEMENT_NAME_TEMPLATE = '{target_start} - {target_end} scan' -ENGAGEMENT_START_END_FORMAT = '%Y-%m-%d' -POST_METHOD = 'POST' -DELETE_METHOD = 'DELETE' -GET_METHOD = 'GET' +class DojoV2Client: + __slots__ = ('_url', '_session') -class DojoClient: - def __init__(self, host: str, api_key: str): + def __init__(self, url: str, api_key: str): """ - :param host: assuming that host came from Modular AccessMeta + :param url: http://127.0.0.1:8080/api/v2 :param api_key: """ - _LOG.info('Init Dojo client') - self.host = host - self.api_key = api_key - self.headers = {'Authorization': "Token " + self.api_key} - self._check_connection() + url.strip('/') + if 'api/v2' not in url: + url = url + '/api/v2' - def _check_connection(self): - _LOG.info('Checking DefectDojo\'s connection') - self._request( - method=GET_METHOD, - url='/user_profile/', - timeout=4 - ) - _LOG.info('Connected successfully') + self._url = url + self._session = requests.Session() + self._session.headers.update({'Authorization': f'Token {api_key}'}) + + def __del__(self): + self._session.close() + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self._session.close() + + def user_profile(self) -> dict | None: + resp = self._request(path='user_profile', method=HTTPMethod.GET) + if not resp or not resp.ok: + return + return resp.json() - def create_context(self, product_type_name, product_name, engagement_name, - scan_date=None): - """Created necessary entities without actually importing findings""" - response = self._request( - method=POST_METHOD, - url='/import-scan/', + def import_scan(self, scan_type: str, scan_date: datetime, + product_type_name: str, + product_name: str, engagement_name: str, test_title: str, + data: dict, auto_create_context: bool = True, + tags: list[str] | None = None, reimport: bool = True, + ) -> requests.Response | None: + return self._request( + path='/reimport-scan/' if reimport else '/import-scan/', + method=HTTPMethod.POST, data={ - "product_type_name": product_type_name, - "product_name": product_name, - "engagement_name": engagement_name, - "scan_type": DOJO_CAAS_SCAN_TYPE, - "auto_create_context": True, - "scan_date": scan_date or utc_datetime().date().isoformat() + 'product_type_name': product_type_name, + 'product_name': product_name, + 'engagement_name': engagement_name, + 'test_title': test_title, + 'auto_create_context': auto_create_context, + 'tags': tags or [], + 'scan_type': scan_type, + 'scan_date': scan_date.date().isoformat() }, files={ - 'file': ('report.json', io.BytesIO(b'{"findings": []}')) + 'file': ('report.json', msgspec.json.encode(data)) } ) - self._request( - method=DELETE_METHOD, - url=f'/tests/{response.get("test_id")}' - ) - - def import_scan(self, product_type_name, product_name, engagement_name, - buffer: io.BytesIO, scan_type, reimport=False, - auto_create_context=True, test_title=None, scan_date=None): - data = { - "product_type_name": product_type_name, - "product_name": product_name, - "engagement_name": engagement_name, - "scan_type": scan_type, - "auto_create_context": auto_create_context, - "scan_date": scan_date or utc_datetime().date().isoformat() - } - if test_title: - data['test_title'] = test_title - files = { - "file": ("report.json", buffer) - } - url = '/reimport-scan/' if reimport else 'import-scan/' - return self._request( - method=POST_METHOD, - url=url, - data=data, - files=files - ) - def _request(self, method, url, params=None, data=None, files=None, - timeout=None): - url = self.host + url - _LOG.info(f"Making a request to DefectDojo's url: {url}, " - f"prodiving: params={params}, data={data}, " - f"files={files}.") + def _request(self, path: str, method: HTTPMethod, + params: dict | None = None, data: dict | None = None, + files: dict | None = None, timeout: int | None = None + ) -> requests.Response | None: + _LOG.info(f'Making dojo request {method.value} {path}') try: - response = requests.request( - method=method, - url=url, + resp = self._session.request( + method=method.value, + url=self._url + path, params=params, data=data, files=files, - headers=self.headers, timeout=timeout ) - response.raise_for_status() - _LOG.info('The request to DefectDojo was successful!') - return response.json() - except ValueError: # JsonDecodeError (either simplejson or json) - _LOG.warning('The request is successful but without JSON') - return {} - except requests.HTTPError as e: - _LOG.error(e) - error = f'An error {e.response.status_code} occurred ' \ - f'while making a request to DefectDojo server.' - if e.response.status_code == HTTPStatus.FORBIDDEN: - error = f'{error} Forbidden.' - raise requests.RequestException(error) - except requests.exceptions.ConnectionError as e: - error = f'Could not connect to the DefectDojo ' \ - f'server {self.host}' - _LOG.error(f'{error}: {e}') - raise requests.RequestException(error) + _LOG.info(f'Response status code: {resp.status_code}') + return resp + except requests.RequestException: + _LOG.exception('Error occurred making request to dojo') diff --git a/src/services/clients/ecr.py b/src/services/clients/ecr.py deleted file mode 100644 index 13a2e81e5..000000000 --- a/src/services/clients/ecr.py +++ /dev/null @@ -1,54 +0,0 @@ -import boto3 - - -class ECRClient: - def __init__(self, region): - self._region = region - self._client = None - - @property - def client(self): - if not self._client: - self._client = boto3.client('ecr', self._region) - return self._client - - def describe_images(self, repository_name, tag): - try: - response = self.client.describe_images( - repositoryName=repository_name, - imageIds=[ - { - 'imageTag': tag - } - ] - ) - return response['imageDetails'] - except self.client.exceptions.ImageNotFoundException: - return None - - def is_image_with_tag_exists(self, repository_name, tag): - response = self.client.list_images( - repositoryName=repository_name, - filter={ - 'tagStatus': 'TAGGED' - } - ) - ids = response.get('imageIds') - for _id in ids: - if _id['imageTag'].startswith(tag): - return True - - while response.get('nextToken'): - token = response.get('nextToken') - response = self.client.list_images( - repositoryName=repository_name, - nextToken=token, - filter={ - 'tagStatus': 'TAGGED' - } - ) - ids = response.get('imageIds') - for _id in ids: - if _id['imageTag'].startswith(tag): - return True - return False diff --git a/src/services/clients/eks_client.py b/src/services/clients/eks_client.py new file mode 100644 index 000000000..b91e0f37f --- /dev/null +++ b/src/services/clients/eks_client.py @@ -0,0 +1,29 @@ +from typing import TypedDict + +from botocore.exceptions import ClientError + +from services.clients import Boto3ClientWrapper + + +class _CertificateAuthority(TypedDict): + data: str + + +class Cluster(TypedDict): + name: str + arn: str + certificateAuthority: _CertificateAuthority + endpoint: str + # and other params + + +class EKSClient(Boto3ClientWrapper): + service_name = 'eks' + + def describe_cluster(self, name: str) -> Cluster | None: + try: + return self._client.describe_cluster(name=name)['cluster'] + except ClientError as e: + if e.response['Error']['Code'] == 'ResourceNotFoundException': + return + raise e diff --git a/src/services/clients/event_bridge.py b/src/services/clients/event_bridge.py index 85fe439c0..034af637a 100644 --- a/src/services/clients/event_bridge.py +++ b/src/services/clients/event_bridge.py @@ -1,10 +1,11 @@ import json import uuid -from typing import List -import boto3 from botocore.exceptions import ClientError - +from typing import TypedDict +from helpers import sifted +from services import SP +from services.clients import Boto3ClientWrapper from services.clients.sts import StsClient from services.environment_service import EnvironmentService @@ -75,36 +76,45 @@ def serialize(self) -> dict: return result -class EventBridgeClient: +class _Rule(TypedDict): + Name: str + Arn: str + EventPattern: str + ScheduleExpression: str + State: str # enum + Description: str + RoleArn: str + ManagedBy: str + EventBusName: str + CreatedBy: str + + +class EventBridgeClient(Boto3ClientWrapper): + service_name = 'events' + def __init__(self, environment_service: EnvironmentService, sts_client: StsClient): self._environment = environment_service self._sts_client = sts_client - self._client = None - - @staticmethod - def sifted(request: dict) -> dict: - return {k: v for k, v in request.items() if isinstance( - v, (bool, int)) or v} - @property - def client(self): - if not self._client: - self._client = boto3.client( - 'events', self._environment.aws_region()) - return self._client + @classmethod + def build(cls) -> 'EventBridgeClient': + return cls( + environment_service=SP.environment_service, + sts_client=SP.sts + ) def put_rule(self, rule_name: str, schedule: str, state: str = 'ENABLED', description: str = 'Custodian managed rule') -> str: params = dict(Name=rule_name, ScheduleExpression=schedule, State=state, Description=description) - response = self.client.put_rule(**self.sifted(params)) + response = self.client.put_rule(**sifted(params)) return response['RuleArn'] def delete_rule(self, rule_name: str) -> dict: return self.client.delete_rule(Name=rule_name) - def put_targets(self, rule_name: str, targets: List[RuleTarget]) -> dict: + def put_targets(self, rule_name: str, targets: list[RuleTarget]) -> dict: params = dict(Rule=rule_name, Targets=[t.serialize() for t in targets]) return self.client.put_targets(**params) @@ -126,7 +136,7 @@ def build_rule_arn(self, rule_name: str) -> str: return f'arn:aws:events:{self._environment.aws_region()}:' \ f'{self._sts_client.get_account_id()}:rule/{rule_name}' - def enable_rule(self, rule_name) -> bool: + def enable_rule(self, rule_name: str) -> bool: params = dict(Name=rule_name) try: self.client.enable_rule(**params) @@ -136,7 +146,7 @@ def enable_rule(self, rule_name) -> bool: return False raise - def disable_rule(self, rule_name) -> bool: + def disable_rule(self, rule_name: str) -> bool: params = dict(Name=rule_name) try: self.client.disable_rule(**params) @@ -146,11 +156,11 @@ def disable_rule(self, rule_name) -> bool: return False raise - def describe_rule(self, rule_name) -> dict: + def describe_rule(self, rule_name) -> _Rule | None: params = dict(Name=rule_name) try: return self.client.describe_rule(**params) except ClientError as e: if e.response['Error']['Code'] == 'ResourceNotFoundException': - return {} + return raise diff --git a/src/services/clients/git_service_clients.py b/src/services/clients/git_service_clients.py index 74a0dec26..358ae8c58 100644 --- a/src/services/clients/git_service_clients.py +++ b/src/services/clients/git_service_clients.py @@ -2,7 +2,7 @@ import tempfile from functools import cached_property from pathlib import Path -from typing import Optional, Union, TypedDict, List +from typing import TypedDict from urllib.parse import urljoin import requests @@ -82,8 +82,8 @@ def get_project(self, pid: int): _LOG.warning('Error occurred trying to get project') return - def clone_project(self, project: Union[str, int], to: Path, - ref: Optional[str] = None) -> Optional[Path]: + def clone_project(self, project: str | int, to: Path, + ref: str | None = None) -> Path | None: """ In case the project was cloned, returns a path to its root >>> with tempfile.TemporaryDirectory() as folder: @@ -132,8 +132,8 @@ class _GitLabFileMeta(TypedDict): _session: requests.Session = None - def __init__(self, url: Optional[str] = 'https://git.epam.com', - private_token: Optional[str] = None): + def __init__(self, url: str | None = 'https://git.epam.com', + private_token: str | None = None): self._url = self.extract_netloc(url) self._private_token = private_token @@ -177,7 +177,7 @@ def close(cls): if isinstance(cls._session, requests.Session): cls._session.close() - def get_project(self, project: Union[int, str]) -> Optional[dict]: + def get_project(self, project: int | str) -> dict | None: resp = self.session().get( url=self._api_url(f'projects/{project}'), headers={ @@ -188,8 +188,8 @@ def get_project(self, project: Union[int, str]) -> Optional[dict]: return return resp.json() - def clone_project(self, project: Union[int, str], to: Path, - ref: Optional[str] = None) -> Optional[Path]: + def clone_project(self, project: str | int, to: Path, + ref: str | None = None) -> Path | None: """ In case the project was cloned, returns a path to its root >>> with tempfile.TemporaryDirectory() as folder: @@ -222,8 +222,8 @@ def clone_project(self, project: Union[int, str], to: Path, _LOG.debug('Repository was cloned') return next(Path(extracted).iterdir()) - def get_file_meta(self, project: Union[int, str], filepath: str, - ref: Optional[str] = None) -> Optional[_GitLabFileMeta]: + def get_file_meta(self, project: int | str, filepath: str, + ref: str | None = None) -> _GitLabFileMeta | None: """ Makes just HEAD to /api/v4/projects/:id/repository/files/:path. Main purpose to retrieve commit_hash and updated_date for a file @@ -285,8 +285,8 @@ def extract_netloc(path: str) -> str: scheme = 'https' return scheme + '://' + parsed.netloc - def __init__(self, url: Optional[str] = 'https://api.github.com', - private_token: Optional[str] = None): + def __init__(self, url: str | None = 'https://api.github.com', + private_token: str | None = None): self._url = self.extract_netloc(url) self._private_token = private_token @@ -309,15 +309,15 @@ def close(cls): if isinstance(cls._session, requests.Session): cls._session.close() - def get_project(self, project: str) -> Optional[dict]: + def get_project(self, project: str) -> dict | None: project = project.strip('/') resp = self.session().get(url=urljoin(self._url, f'/repos/{project}')) if not resp.ok: return return resp.json() - def clone_project(self, project: str, to: Path, ref: Optional[str] = None, - ) -> Optional[Path]: + def clone_project(self, project: str, to: Path, ref: str | None = None, + ) -> Path | None: """ :param project: GitHub project full name: '[owner]/[repo]' :param ref: @@ -349,7 +349,7 @@ def clone_project(self, project: str, to: Path, ref: Optional[str] = None, return next(Path(extracted).iterdir()) def get_file_blame(self, project: str, filepath: str, ref: str - ) -> List[_GitHubBlameRange]: + ) -> list[_GitHubBlameRange]: project = project.strip('/') owner, repo = project.split('/') variables = { @@ -366,12 +366,12 @@ def get_file_blame(self, project: str, filepath: str, ref: str ) or [] @staticmethod - def most_reset_blame(blames: List[_GitHubBlameRange]) -> _GitHubBlameRange: + def most_reset_blame(blames: list[_GitHubBlameRange]) -> _GitHubBlameRange: assert blames, 'At least one blame dict must be provided' # assuming if there is no age, it is old return min(blames, key=lambda x: x.get('age') or 10) - def run_query(self, query: str, variables: dict) -> Optional[dict]: + def run_query(self, query: str, variables: dict) -> dict | None: """ Runs the provided query and returns a raw response :param variables: @@ -392,5 +392,5 @@ def run_query(self, query: str, variables: dict) -> Optional[dict]: return resp.json() def get_file_meta(self, project: str, filepath: str, - ref: Optional[str] = None): + ref: str | None = None): return diff --git a/src/services/clients/iam.py b/src/services/clients/iam.py index 2d082733c..927ca62bb 100644 --- a/src/services/clients/iam.py +++ b/src/services/clients/iam.py @@ -1,5 +1,4 @@ import re -from typing import Optional from services.clients.sts import StsClient @@ -9,7 +8,7 @@ def __init__(self, sts_client: StsClient): self._sts_client = sts_client def build_role_arn(self, maybe_arn: str, - account_id: Optional[str] = None) -> str: + account_id: str | None = None) -> str: if self.is_role_arn(maybe_arn): return maybe_arn account_id = account_id or self._sts_client.get_account_id() diff --git a/src/services/clients/jwt_management_client.py b/src/services/clients/jwt_management_client.py new file mode 100644 index 000000000..3d71353d6 --- /dev/null +++ b/src/services/clients/jwt_management_client.py @@ -0,0 +1,157 @@ +import base64 +import json +import time +from typing import Literal +from datetime import timedelta, datetime + +from jwcrypto import jwk, jwt + +from helpers.log_helper import get_logger + +_LOG = get_logger(__name__) + + +class JWTManagementClient: + kty_to_encrypt_alg = { # $kty: ($alg, $enc) + 'RSA': ('RSA-OAEP-256', 'A256CBC-HS512'), + 'EC': ('ECDH-ES+A256KW', 'A256GCM') + } + + def __init__(self, key: jwk.JWK): + """ + :param key: private key + """ + # self._key.key_type + assert key and key.get('kty') in ('EC', 'RSA'), \ + 'EC and RSA keys only allowed' + self._key = key + + @property + def key_type(self) -> Literal['EC', 'RSA']: + return self._key.get('kty') # self._key.key_type + + @property + def key_alg(self) -> str: + """ + To be used in JWT header + :return: + """ + if self.key_type == 'RSA': + return 'PS256' # TODO get some specific + curve = self._key.get('crv') # self._key.key_curve + match curve: + case 'P-256': + return 'ES256' + case 'P-521': + return 'ES512' + case _: + return 'ES256' # default + + @property + def jwk(self) -> jwk.JWK: + return self._key + + @classmethod + def from_pem(cls, pem: str | bytes): + if isinstance(pem, str): + pem = pem.encode() + return cls(jwk.JWK.from_pem(pem)) + + @classmethod + def from_b64_pem(cls, pem: str | bytes): + return cls.from_pem(base64.b64decode(pem)) + + def _sign_header(self, **kwargs) -> dict: + return { + 'alg': self.key_alg, + # 'typ': 'JWS', # I don't know whether it's JWT or JWS? Do u know? + 'kid': self._key.thumbprint(), + 'kty': self.key_type, + **kwargs + } + + def _encrypt_header(self) -> dict: + kty = self._key.get('kty') + return { + 'alg': self.kty_to_encrypt_alg[kty][0], + 'typ': 'JWE', + 'enc': self.kty_to_encrypt_alg[kty][1], + 'kid': self._key.thumbprint(), + 'kty': kty + } + + @staticmethod + def _normalize_exp(exp: datetime | timedelta | int) -> int: + if isinstance(exp, (int, float)): + return int(exp) + if isinstance(exp, datetime): + return int(exp.timestamp()) + if isinstance(exp, timedelta): + return int(time.time() + exp.seconds) + + def sign(self, claims: dict | str, + exp: datetime | timedelta | int = None, + iss: str | None = None, headers: dict = None) -> str: + """ + + :param claims: + :param exp: + :param iss: + :param headers: + :return: + """ + if isinstance(claims, dict): # adding custom params to claims + if exp: + claims['exp'] = self._normalize_exp(exp) + claims['iat'] = int(time.time()) # issued at + if isinstance(iss, str): + claims['iss'] = iss + + headers = headers or {} + token = jwt.JWT( + header=self._sign_header(**headers), + claims=claims + ) + token.make_signed_token(self._key) + return token.serialize() + + def verify(self, token: str | jwt.JWT) -> jwt.JWT: + """ + Can raise + :param token: + :return: + """ + return jwt.JWT( + key=self._key, + jwt=token.claims if isinstance(token, jwt.JWT) else token, + expected_type='JWS' + ) + + def encrypt(self, token: str | jwt.JWT) -> str: + e_token = jwt.JWT( + header=self._encrypt_header(), + claims=token.claims if isinstance(token, jwt.JWT) else token + ) + e_token.make_encrypted_token(self._key) + return e_token.serialize() + + def decrypt(self, token: str) -> jwt.JWT | None: + try: + return jwt.JWT(key=self._key, jwt=token, expected_type="JWE") + except Exception as e: + _LOG.warning(f'Cloud not decrypt JWE: {str(e)}') + return + + def encrypt_dict(self, dct: dict) -> str: + s = json.dumps(dct, sort_keys=True, separators=(',', ':')) + return self.encrypt(s) + + def decrypt_dict(self, token: str) -> dict | None: + """ + Retrieves dict from token received by encrypt_dict + :param token: + :return: + """ + decrypted = self.decrypt(token) + if decrypted: + return json.loads(decrypted.claims) diff --git a/src/services/clients/kms.py b/src/services/clients/kms.py deleted file mode 100644 index df24e029f..000000000 --- a/src/services/clients/kms.py +++ /dev/null @@ -1,36 +0,0 @@ -import boto3 - -from typing import Union - -SIGNATURE_ATTR = 'Signature' - - -class KMSClient: - def __init__(self, region): - self._region = region - self._client = None - - @property - def client(self): - if not self._client: - self._client = boto3.client('kms', self._region) - return self._client - - def encrypt(self, key_id, value): - response = self.client.encrypt(KeyId=key_id, Plaintext=value) - return response['CiphertextBlob'] - - def decrypt(self, value, key_id=None): - if key_id: - response = self.client.decrypt(CiphertextBlob=value, KeyId=key_id) - else: - response = self.client.decrypt(CiphertextBlob=value) - return response['Plaintext'].decode('utf-8') - - def sign(self, key_id: str, message: Union[str, bytes], algorithm: str, - encoding='utf-8') -> bytes: - is_bytes = isinstance(message, bytes) - message = message if is_bytes else bytes(message, encoding) - return self.client.sign( - KeyId=key_id, Message=message, SigningAlgorithm=algorithm - ).get(SIGNATURE_ATTR) diff --git a/src/services/clients/lambda_func.py b/src/services/clients/lambda_func.py index d01306def..5d17630e9 100644 --- a/src/services/clients/lambda_func.py +++ b/src/services/clients/lambda_func.py @@ -6,7 +6,7 @@ import boto3 from helpers import RequestContext -from helpers.exception import CustodianException +from helpers.lambda_response import CustodianException from helpers.log_helper import get_logger from services.environment_service import EnvironmentService @@ -110,7 +110,8 @@ def _invoke_function_docker(self, function_name, event=None, wait=False): @staticmethod def _handle_execution(handler: Callable, *args): try: - _response = handler(*args) - except CustodianException as _ce: - _response = dict(code=_ce.code, body=dict(message=_ce.content)) - return _response + response = handler(*args) + except CustodianException as e: + resp = e.response.build() + response = dict(code=resp['statusCode'], body=resp['body']) + return response diff --git a/src/services/clients/license_manager.py b/src/services/clients/license_manager.py index 76e7cda05..62f95298d 100644 --- a/src/services/clients/license_manager.py +++ b/src/services/clients/license_manager.py @@ -1,34 +1,23 @@ import dataclasses import json from abc import ABC, abstractmethod -from typing import List, Optional import requests from modular_sdk.services.impl.maestro_credentials_service import AccessMeta from helpers.constants import HTTPMethod, \ - LICENSE_KEY_ATTR, STATUS_ATTR, TENANT_LICENSE_KEY_ATTR, \ + LICENSE_KEY_ATTR, TENANT_LICENSE_KEY_ATTR, \ AUTHORIZATION_PARAM, CUSTOMER_ATTR, TENANT_LICENSE_KEYS_ATTR, \ - TENANTS_ATTR, TENANT_ATTR + TENANTS_ATTR, TENANT_ATTR, JobState from helpers.log_helper import get_logger from services.setting_service import SettingsService -SET_CUSTOMER_ACTIVATION_DATE_PATH = '/customers/set-activation-date' -JOB_CHECK_PERMISSION_PATH = '/jobs/check-permission' -SYNC_LICENSE_PATH = '/license/sync' -JOBS_PATH = '/jobs' - -JOB_ID = 'job_id' -CREATED_AT_ATTR = 'created_at' -STARTED_AT_ATTR = 'started_at' -STOPPED_AT_ATTR = 'stopped_at' - _LOG = get_logger(__name__) @dataclasses.dataclass() class LMAccessData(AccessMeta): - api_version: Optional[str] + api_version: str | None class LicenseManagerClientInterface(ABC): @@ -46,7 +35,7 @@ def client_key_data(self): @abstractmethod def license_sync(self, license_key: str, auth: str - ) -> Optional[requests.Response]: + ) -> requests.Response | None: """ Delegated to commence license-synchronization, bound to a list of tenant licenses accessible to a client, authorized by a respective @@ -59,7 +48,7 @@ def license_sync(self, license_key: str, auth: str @abstractmethod def job_check_permission(self, customer: str, tenant: str, - tenant_license_keys: List[str], auth: str + tenant_license_keys: list[str], auth: str ) -> bool: """ Delegated to check for permission to license Job, @@ -72,8 +61,24 @@ def job_check_permission(self, customer: str, :return: bool """ - def update_job(self, job_id: str, created_at: str, started_at: str, - stopped_at: str, status: str, auth: str): + def post_job(self, job_id: str, customer: str, tenant: str, + ruleset_map: dict[str, list[str]], auth: str + ) -> requests.Response | None: + """ + Delegated to instantiate a licensed Job, bound to a tenant within a + customer utilizing rulesets which are grouped by tenant-license-keys, + allowing to request for a ruleset-content-source collection. + :parameter job_id: str + :parameter customer: str + :parameter tenant: str + :parameter auth: str, authorization token + :parameter ruleset_map: Dict[str, List[str]] + :return: Union[Response, Type[None]] + """ + + def update_job(self, job_id: str, auth: str, created_at: str = None, + started_at: str = None, stopped_at: str = None, + status: JobState = None): """ Delegated to update an id-derivable licensed Job entity, providing necessary state data. @@ -87,15 +92,19 @@ def update_job(self, job_id: str, created_at: str, started_at: str, """ def activate_customer(self, customer: str, tlk: str, auth: str - ) -> Optional[requests.Response]: - pass + ) -> requests.Response | None: + """ + Activates customer for the first time + :param customer: + :param tlk: + :param auth: + :return: + """ @classmethod - def _send_request(cls, url: str, method: str, - params: Optional[dict] = None, - payload: Optional[dict] = None, - headers: Optional[dict] = None - ) -> Optional[requests.Response]: + def _send_request(cls, url: str, method: str, params: dict = None, + payload: dict = None, headers: dict = None + ) -> requests.Response | None: try: _LOG.debug(f'Going to send \'{method}\' request to \'{url}\'') response = requests.request( @@ -109,7 +118,7 @@ def _send_request(cls, url: str, method: str, return @staticmethod - def retrieve_json(response: requests.Response) -> Optional[dict]: + def retrieve_json(response: requests.Response) -> dict | None: try: return response.json() except json.JSONDecodeError as je: @@ -125,21 +134,20 @@ class LicenseManagerClientMain(LicenseManagerClientInterface): """ def license_sync(self, license_key: str, auth: str - ) -> Optional[requests.Response]: + ) -> requests.Response | None: if not self.host: _LOG.warning('CustodianLicenceManager access data has not been' ' provided.') return None return self._send_request( - url=self.host + SYNC_LICENSE_PATH, + url=self.host + '/license/sync', method=HTTPMethod.POST, payload={LICENSE_KEY_ATTR: license_key}, headers={AUTHORIZATION_PARAM: auth} ) - def job_check_permission(self, customer: str, - tenant: str, - tenant_license_keys: List[str], auth: str + def job_check_permission(self, customer: str, tenant: str, + tenant_license_keys: list[str], auth: str ) -> bool: """ Currently only one tenant_license_key valid for business logic @@ -156,7 +164,7 @@ def job_check_permission(self, customer: str, return False resp = self._send_request( - url=host.strip('/') + JOB_CHECK_PERMISSION_PATH, + url=host.strip('/') + '/jobs/check-permission', method=HTTPMethod.POST, payload={ CUSTOMER_ATTR: customer, @@ -170,39 +178,68 @@ def job_check_permission(self, customer: str, return tenant in \ resp.json().get('items')[0][tenant_license_keys[0]]['allowed'] - def update_job(self, job_id: str, created_at: str, started_at: str, - stopped_at: str, status: str, auth: str): + def update_job(self, job_id: str, auth: str, created_at: str = None, + started_at: str = None, stopped_at: str = None, + status: JobState = None): host = self.host if not host: _LOG.error('CustodianLicenceManager access data has not been' ' provided.') return None + payload = { + 'job_id': job_id, + 'created_at': created_at, + 'started_at': started_at, + 'stopped_at': stopped_at, + 'status': status + } return self._send_request( - url=host.strip('/') + JOBS_PATH, + url=host.strip('/') + '/jobs', method=HTTPMethod.PATCH, - payload={ - JOB_ID: job_id, - CREATED_AT_ATTR: created_at, - STARTED_AT_ATTR: started_at, - STOPPED_AT_ATTR: stopped_at, - STATUS_ATTR: status - }, + payload={k: v for k, v in payload.items() if v is not None}, headers={AUTHORIZATION_PARAM: auth} ) def activate_customer(self, customer: str, tlk: str, auth: str - ) -> Optional[requests.Response]: + ) -> requests.Response | None: if not self.host: _LOG.warning('CustodianLicenceManager access data has not been' ' provided.') - return None + return return self._send_request( - url=self.host.strip('/') + SET_CUSTOMER_ACTIVATION_DATE_PATH, + url=self.host.strip('/') + '/customers/set-activation-date', method=HTTPMethod.POST, payload={CUSTOMER_ATTR: customer, TENANT_LICENSE_KEY_ATTR: tlk}, headers={AUTHORIZATION_PARAM: auth} ) + def post_job(self, job_id: str, customer: str, tenant: str, + ruleset_map: dict[str, list[str]], auth: str + ) -> requests.Response | None: + host = self.host + if not host: + _LOG.error('CustodianLicenceManager access data has not been' + ' provided.') + return + + host = host.strip('/') + url = host + '/jobs' + + payload = { + 'service_type': 'CUSTODIAN', + 'job_id': job_id, + 'customer': customer, + 'tenant': tenant, + 'rulesets': ruleset_map + } + + return self._send_request( + url=url, + method=HTTPMethod.POST, + payload=payload, + headers={AUTHORIZATION_PARAM: auth} + ) + class LicenseManagerClientLess2p7(LicenseManagerClientMain): """ @@ -210,9 +247,8 @@ class LicenseManagerClientLess2p7(LicenseManagerClientMain): endpoint slightly differ """ - def job_check_permission(self, customer: str, - tenant: str, - tenant_license_keys: List[str], auth: str + def job_check_permission(self, customer: str, tenant: str, + tenant_license_keys: list[str], auth: str ) -> bool: host = self.host if not host: @@ -221,7 +257,7 @@ def job_check_permission(self, customer: str, return False return bool(self._send_request( - url=host.strip('/') + JOB_CHECK_PERMISSION_PATH, + url=host.strip('/') + '/jobs/check-permission', method=HTTPMethod.POST, payload={ CUSTOMER_ATTR: customer, diff --git a/src/services/clients/mongo_ssm_auth_client.py b/src/services/clients/mongo_ssm_auth_client.py new file mode 100644 index 000000000..a9e77fafe --- /dev/null +++ b/src/services/clients/mongo_ssm_auth_client.py @@ -0,0 +1,250 @@ +from datetime import timedelta +from http import HTTPStatus +import json +import os +import secrets +from typing import cast + +import bcrypt +from jwcrypto import jwt +from pymongo import MongoClient +from pynamodb.pagination import ResultIterator + +from helpers.constants import ( + CAASEnv, + COGNITO_SUB, + COGNITO_USERNAME, + CUSTOM_CUSTOMER_ATTR, + CUSTOM_LATEST_LOGIN_ATTR, + CUSTOM_ROLE_ATTR, + CUSTOM_TENANTS_ATTR, + PRIVATE_KEY_SECRET_NAME, +) +from helpers.lambda_response import ResponseFactory +from helpers.log_helper import get_logger +from helpers.time_helper import utc_datetime, utc_iso +from models import MONGO_CLIENT +from models.user import User +from services.clients.cognito import ( + AuthenticationResult, + BaseAuthClient, + UserWrapper, + UsersIterator, +) +from services.clients.jwt_management_client import JWTManagementClient +from services.clients.ssm import AbstractSSMClient + +_LOG = get_logger(__name__) + +EXPIRATION_IN_MINUTES = 60 + +TOKEN_EXPIRED_MESSAGE = 'The incoming token has expired' +UNAUTHORIZED_MESSAGE = 'Unauthorized' + + +class MongoAndSSMUsersIterator(UsersIterator): + __slots__ = '_it', + + def __init__(self, it: ResultIterator[User]): + self._it = it + + @property + def next_token(self): + return self._it.last_evaluated_key + + def __next__(self) -> UserWrapper: + return UserWrapper.from_user_model(self._it.__next__()) + + +class MongoAndSSMAuthClient(BaseAuthClient): + __slots__ = '_ssm', '_jwt_client', '_refresh_col' + + def __init__(self, ssm_client: AbstractSSMClient): + self._ssm = ssm_client + self._jwt_client = None + self._refresh_col = cast(MongoClient, MONGO_CLIENT).get_database( + os.getenv(CAASEnv.MONGO_DATABASE) + ).get_collection('CaaSRefreshTokenChains') + + @property + def jwt_client(self) -> JWTManagementClient: + if self._jwt_client: + return self._jwt_client + jwk_pem = self._ssm.get_secret_value(PRIVATE_KEY_SECRET_NAME) + unavailable = ResponseFactory(HTTPStatus.SERVICE_UNAVAILABLE).default() + + if not jwk_pem or not isinstance(jwk_pem, str): + _LOG.error('Can not find jwt-secret') + raise unavailable.exc() + try: + cl = JWTManagementClient.from_b64_pem(jwk_pem) + self._jwt_client = cl + return cl + except ValueError: + raise unavailable.exc() + + def get_user_by_username(self, username: str) -> UserWrapper | None: + item = User.get_nullable(hash_key=username) + if not item: + return + return UserWrapper.from_user_model(item) + + def query_users(self, customer: str | None = None, + limit: int | None = None, + next_token: str | dict | None = None) -> UsersIterator: + fc = None + if customer: + fc = (User.customer == customer) + it = User.scan( + limit=limit, + last_evaluated_key=next_token, + filter_condition=fc + ) + return MongoAndSSMUsersIterator(it) + + def set_user_password(self, username: str, password: str) -> bool: + User(user_id=username).update(actions=[ + User.password.set( + bcrypt.hashpw(password.encode(), bcrypt.gensalt())) + ]) + return True + + @staticmethod + def _update_password_attr(user: User, password: str): + user.password = bcrypt.hashpw(password.encode(), bcrypt.gensalt()) + + def update_user_attributes(self, user: UserWrapper): + actions = [] + if user.customer: + actions.append(User.customer.set(user.customer)) + if user.role: + actions.append(User.role.set(user.role)) + if user.latest_login: + actions.append(User.latest_login.set(utc_iso(user.latest_login))) + if actions: + User(user_id=user.username).update(actions=actions) + + def delete_user(self, username: str) -> None: + User(user_id=username).delete() + + @staticmethod + def _gen_refresh_token_version() -> str: + return secrets.token_hex() + + def _gen_refresh_token(self, username: str, version: str) -> str: + t = self.jwt_client.sign({'username': username, 'version': version}) + return self.jwt_client.encrypt(t) + + def _decrypt_refresh_token(self, token: str) -> tuple[str, str] | None: + t = self.jwt_client.decrypt(token) + if not t: + return + try: + t = self.jwt_client.verify(t.claims) + except Exception: + return + dct = json.loads(t.claims) + return dct['username'], dct['version'] + + def _gen_access_token(self, user: User) -> str: + return self.jwt_client.sign( + claims={ + COGNITO_USERNAME: user.user_id, + COGNITO_SUB: str(user.mongo_id), + CUSTOM_CUSTOMER_ATTR: user.customer, + CUSTOM_TENANTS_ATTR: user.tenants or '', + CUSTOM_ROLE_ATTR: user.role, + CUSTOM_LATEST_LOGIN_ATTR: user.latest_login, + }, + exp=timedelta(minutes=EXPIRATION_IN_MINUTES) + ) + + def authenticate_user(self, username: str, password: str + ) -> AuthenticationResult | None: + user_item = User.get_nullable(hash_key=username) + if not user_item: + return + check = bcrypt.checkpw( + password=password.encode(), + hashed_password=user_item.password + ) + if not check: + _LOG.info('Invalid password provided by user') + return + token = self._gen_access_token(user_item) + + rt_version = self._gen_refresh_token_version() + refresh_token = self._gen_refresh_token(username, rt_version) + self._refresh_col.replace_one({'_id': username}, { + 'v': rt_version # latest version for user + }, upsert=True) + + # that id_token is actually used as access_token. But because Api Gw + # required cognito id_token to be passed, we keep here the similar + # interface to the client inside ./cognito.py + return { + 'id_token': token, + 'refresh_token': refresh_token, + 'expires_in': EXPIRATION_IN_MINUTES * 60 + } + + def refresh_token(self, refresh_token: str) -> AuthenticationResult | None: + _LOG.info('Starting on-prem refresh token flow') + tpl = self._decrypt_refresh_token(refresh_token) + if not tpl: + _LOG.info('Invalid refresh token provided. Cannot refresh') + return + username, rt_version = tpl + latest = self._refresh_col.find_one({'_id': username}) + if not latest or not latest.get('v'): + _LOG.warning('Latest version of token not found in DB ' + 'but valid token was received. Cannot refresh') + return + correct_version = latest['v'] + if rt_version != correct_version: + _LOG.warning('Valid token received but its version and one from ' + 'DB do not match. Stolen refresh token or user ' + 'reused one. Invalidating existing version') + self._refresh_col.delete_one({'_id': username}) + return + rt_version = self._gen_refresh_token_version() + self._refresh_col.replace_one({'_id': username}, { + 'v': rt_version # latest version for user + }, upsert=True) + + user_item = User.get_nullable(hash_key=username) + return { + 'id_token': self._gen_access_token(user_item), + 'refresh_token': self._gen_refresh_token(username, rt_version), + 'expires_in': EXPIRATION_IN_MINUTES * 60 + } + + def signup_user(self, username: str, password: str, + customer: str | None = None, role: str | None = None + ) -> UserWrapper: + created_at = utc_datetime() + user = User( + user_id=username, + customer=customer, + role=role, + created_at=utc_iso(created_at) + ) + self._update_password_attr(user, password) + user.save() + return UserWrapper( + username=username, + customer=customer, + role=role, + created_at=created_at + ) + + def decode_token(self, token: str) -> dict: + try: + verified = self.jwt_client.verify(token) + except jwt.JWTExpired: + raise ResponseFactory(HTTPStatus.UNAUTHORIZED).message( + TOKEN_EXPIRED_MESSAGE).exc() + except (jwt.JWException, ValueError, Exception): + raise ResponseFactory(HTTPStatus.UNAUTHORIZED).message( + UNAUTHORIZED_MESSAGE).exc() + return json.loads(verified.claims) diff --git a/src/services/clients/s3.py b/src/services/clients/s3.py index ef4f3ee13..2a3069cb2 100644 --- a/src/services/clients/s3.py +++ b/src/services/clients/s3.py @@ -1,40 +1,31 @@ +import gzip import io -import ipaddress -import json -import os.path +import mimetypes +import os import re -from concurrent.futures import ThreadPoolExecutor, as_completed -from datetime import datetime -from gzip import GzipFile -from subprocess import PIPE, Popen -from typing import Union, Generator, Iterable, Dict, Tuple, Optional, \ - TypedDict, List -from urllib.parse import urlparse -from urllib3.util import Url, parse_url +import shutil +from datetime import datetime, timedelta +from typing import Generator, Iterable, Optional, TypedDict, BinaryIO, cast -import boto3 +import msgspec from botocore.config import Config from botocore.exceptions import ClientError -from botocore.response import StreamingBody -from botocore.utils import IMDSFetcher, _RetriesExceededError +from modular_sdk.services.aws_creds_provider import ModularAssumeRoleClient +from urllib3.util import Url, parse_url -from helpers import coroutine -from helpers.constants import ENV_SERVICE_MODE, ENV_MINIO_HOST, \ - ENV_MINIO_PORT, DOCKER_SERVICE_MODE, \ - ENV_MINIO_ACCESS_KEY, ENV_MINIO_SECRET_ACCESS_KEY +from helpers.constants import CAASEnv from helpers.log_helper import get_logger - -UTF_8_ENCODING = 'utf-8' -MINIKUBE_IP_PARAM = 'MINIKUBE_IP' -DEFAULT_MINIO_PORT = 30103 # hard-coded from minio-config.yaml -GZIP_EXTENSION = '.gz' +from services.clients import (Boto3ClientWrapperFactory, Boto3ClientFactory, + Boto3ClientWrapper) _LOG = get_logger(__name__) -S3_NOT_AVAILABLE = re.compile(r'[^a-zA-Z0-9!-_.*()]') +Json = dict | list | str | int | float | tuple class S3Url: + __slots__ = ('_parsed',) + def __init__(self, s3_url: str): self._parsed: Url = parse_url(s3_url) @@ -51,409 +42,416 @@ def url(self) -> str: return self._parsed.url -class S3Client: - IS_DOCKER = os.getenv(ENV_SERVICE_MODE) == DOCKER_SERVICE_MODE +class S3ClientWrapperFactory(Boto3ClientWrapperFactory['S3Client']): + @classmethod + def _base_config(cls) -> Config: + return Config(retries={ + 'max_attempts': 10, + 'mode': 'standard' + }) - class ObjectMetadata(TypedDict): - Key: str - LastModified: datetime - ETag: str - ChecksumAlgorithm: List[str] - Size: int - StorageClass: str - Owner: Dict[str, str] - RestoreStatus: Dict + @classmethod + def _minio_config(cls) -> Config: + return cls._base_config().merge(Config(s3={ + 'signature_version': 's3v4', + 'addressing_style': 'path' + })) + + def build_s3(self, region_name: str) -> 'S3Client': + instance = self._wrapper.build() + instance.resource = Boto3ClientFactory( + instance.service_name).build_resource( + region_name=region_name, + config=self._base_config() + ) + instance.client = instance.resource.meta.client + _LOG.info('S3 connection was successfully initialized') + return instance + + def build_minio(self) -> 'S3Client': + endpoint = os.getenv(CAASEnv.MINIO_ENDPOINT) + access_key = os.getenv(CAASEnv.MINIO_ACCESS_KEY_ID) + secret_key = os.getenv(CAASEnv.MINIO_SECRET_ACCESS_KEY) + assert endpoint and access_key and secret_key, \ + ('Minio endpoint, access key and secret key must be ' + 'provided for on-prem') + + instance = self._wrapper.build() + instance.resource = Boto3ClientFactory( + instance.service_name).build_resource( + aws_access_key_id=access_key, + aws_secret_access_key=secret_key, + endpoint_url=endpoint, + config=self._minio_config() + ) + instance.client = instance.resource.meta.client + _LOG.info('Minio connection was successfully initialized') + return instance - @staticmethod - def safe_key(key: str) -> str: - return re.sub(S3_NOT_AVAILABLE, '-', key) - def __init__(self, region): - self.region = region - self._client = None +class S3Client(Boto3ClientWrapper): + """ + Most methods have their gz equivalent with prefix gz_. Such methods + add .gz to bucket key, compress/decompress content (if the method + interacts with content) and add gzip ContentEncoding to metadata + """ + service_name = 's3' + s3_not_available = re.compile(r'[^a-zA-Z0-9!-_.*()]') - def build_config(self) -> Config: - config = Config(retries={ - 'max_attempts': 10, - 'mode': 'standard' - }) - if self.IS_DOCKER: - config = config.merge(Config(s3={ - 'signature_version': 's3v4', - 'addressing_style': 'path' - })) - return config - - def _init_clients(self): - config = self.build_config() - if self.IS_DOCKER: - host, port = os.getenv(ENV_MINIO_HOST), os.getenv(ENV_MINIO_PORT) - access_key = os.getenv(ENV_MINIO_ACCESS_KEY) - secret_access_key = os.getenv(ENV_MINIO_SECRET_ACCESS_KEY) - assert (host and port and access_key and secret_access_key), \ - f"\'{ENV_MINIO_HOST}\', \'{ENV_MINIO_PORT}\', " \ - f"\'{ENV_MINIO_ACCESS_KEY}\', " \ - f"\'{ENV_MINIO_SECRET_ACCESS_KEY}\' envs must be specified " \ - f"for on-prem" - url = f'http://{host}:{port}' - session = boto3.Session( - aws_access_key_id=access_key, - aws_secret_access_key=secret_access_key - ) - self._client = session.client('s3', endpoint_url=url, - config=config) - _LOG.info('Minio connection was successfully initialized') - else: # saas - self._client = boto3.client('s3', self.region, config=config) - _LOG.info('S3 connection was successfully initialized') + def __init__(self): + self._enc = msgspec.json.Encoder() + self._dec = msgspec.json.Decoder() - @property - def client(self): - if not self._client: - self._init_clients() - return self._client + class Bucket(TypedDict): + Name: str + CreationDate: datetime + + @classmethod + def safe_key(cls, key: str) -> str: + return re.sub(cls.s3_not_available, '-', key) @staticmethod - def _gz_key(key: str, is_zipped: bool = True) -> str: - if not is_zipped: - return key - if not key.endswith(GZIP_EXTENSION): - key = key.strip('.') + GZIP_EXTENSION + def _gz_key(key: str) -> str: + if not key.endswith('.gz'): + key = key.strip('.') + '.gz' return key - def create_bucket(self, bucket_name, region=None): - region = region or self.region - self.client.create_bucket( - Bucket=bucket_name, CreateBucketConfiguration={ - 'LocationConstraint': region - } - ) - - def file_exists(self, bucket_name, key): - """Checks if object with the given key exists in bucket, if not - compressed version exists, compresses and rewrites""" + @classmethod + def factory(cls) -> S3ClientWrapperFactory: + return S3ClientWrapperFactory(cls) - if self._file_exists(bucket_name, self._gz_key(key)): - _LOG.info(f'Gzipped version of the file \'{key}\' exists') - return True - elif self._file_exists(bucket_name, key): - _LOG.warning(f'Not gzipped version of file \'{key}\' exists. ' - f'Compressing') - self._gzip_object(bucket_name, key) - return True - else: - return False - - def _file_exists(self, bucket_name: str, key: str) -> bool: - obj = next( - self.list_objects(bucket_name=bucket_name, prefix=key, max_keys=1), - None + @staticmethod + def _resolve_content_type(key: str, ct: str = None, ce: str = None + ) -> tuple[str | None, str | None]: + """ + Returns user provided content type and encoding. If something is not + provided -> tries to resolve from key + :param key: + :param ct: + :param ce: + :return: (content_type, content_encoding) + """ + if ct and ce: + return ct, ce + resolved_ct, resolved_ce = mimetypes.guess_type(key) + ct = ct or resolved_ct + ce = ce or resolved_ce + return ct, ce + + def put_object(self, bucket: str, key: str, body: bytes | BinaryIO, + content_type: str = None, content_encoding: str = None): + """ + Uploads the provided stream of bytes or raw bytes. + :param bucket: + :param key: + :param body: + :param content_type: + :param content_encoding: + :return: + """ + ct, ce = self._resolve_content_type(key, content_type, + content_encoding) + params = {} + if ct: + params.update(ContentType=ct) + if ce: + params.update(ContentEncoding=ce) + if isinstance(body, bytes): + body = io.BytesIO(body) + return self.resource.Bucket(bucket).upload_fileobj( + Fileobj=body, Key=key, ExtraArgs=params ) - if not obj: - return False - return obj['Key'] == key - def _gzip_object(self, bucket_name: str, key: str) -> io.BytesIO: - """Replaces a file with gzipped version. - And incidentally returns file's content""" + def gz_put_object(self, bucket: str, key: str, body: bytes | BinaryIO, + gz_buffer: BinaryIO = None, content_type: str = None, + content_encoding: str = None): + """ + Uploads the file adding .gz to the file extension and compressing the + body + :param bucket: + :param key: + :param body: + :param gz_buffer: optional buffer to use for compression. + Otherwise - in memory. Argument can be used for large files + :param content_type: + :param content_encoding: + :return: + """ + if not gz_buffer: + gz_buffer = io.BytesIO() + with gzip.GzipFile(fileobj=gz_buffer, mode='wb') as gz: + if isinstance(body, bytes): + gz.write(body) + else: + shutil.copyfileobj(body, gz) + gz_buffer.seek(0) + return self.put_object(bucket, self._gz_key(key), gz_buffer, + content_type, content_encoding) + + def get_object(self, bucket: str, key: str, + buffer: BinaryIO = None) -> BinaryIO | None: + """ + Downloads object to memory by default. Optional buffer can be provided. + In case the key does not exist, None is returned + :param bucket: + :param key: + :param buffer: + :return: + """ + if not buffer: + buffer = io.BytesIO() try: - response = self.client.get_object( - Bucket=bucket_name, - Key=key + self.resource.Bucket(bucket).download_fileobj( + Key=key, Fileobj=buffer ) except ClientError as e: - if isinstance(e, ClientError) and \ - e.response['Error']['Code'] == 'NoSuchKey': - _LOG.warning( - f'There is no \'{key}\' file in bucket {bucket_name}') + if e.response['Error']['Code'] in ('NoSuchKey', '404'): return + _LOG.exception(f'Unexpected error occurred in ' + f'get_object: s3://{bucket}/{key}') raise e - buf = io.BytesIO(response.get('Body').read()) - _LOG.info(f'Putting the compressed version of file \'{key}\'') - self.put_object(bucket_name, key, buf.read()) - _LOG.info(f'Removing the old not compressed version of the ' - f'file \'{key}\'') - self.client.delete_object(Bucket=bucket_name, Key=key) - buf.seek(0) - return buf - - def put_object(self, bucket_name: str, object_name: str, - body: Union[str, bytes], is_zipped: bool = True): - object_name = self._gz_key(object_name, is_zipped) - try: - if not is_zipped: - return self.client.put_object( - Body=body, Bucket=bucket_name, Key=object_name, - ContentEncoding='utf-8') - - buf = io.BytesIO() - with GzipFile(fileobj=buf, mode='wb') as f: - f.write(body.encode() if not isinstance(body, bytes) else body) - buf.seek(0) - return self.client.put_object(Body=buf, Bucket=bucket_name, - Key=object_name) - except (Exception, BaseException) as e: - _LOG.error(f'Putting data inside of an {object_name} object, ' - f'within the {bucket_name} bucket, has triggered an' - f' exception - {e}.') - - def is_bucket_exists(self, bucket_name: str) -> bool: - """ - Check if specified bucket exists. - :param bucket_name: name of the bucket to check; - :return: True is exists, otherwise - False - """ - try: - self.client.head_bucket(Bucket=bucket_name) - return True - except ClientError as e: - if e.response['Error']['Code'] != '404': - raise e - return False + buffer.seek(0) + return buffer - def list_buckets(self): - response = self.client.list_buckets() - return [bucket['Name'] for bucket in response.get("Buckets")] - - def get_json_file_content(self, bucket_name: str, - full_file_name: str) -> dict: + def gz_get_object(self, bucket: str, key: str, + buffer: BinaryIO = None, + gz_buffer: BinaryIO = None) -> BinaryIO | None: """ - Returns content of the object. - :param bucket_name: name of the bucket. - :param full_file_name: name of the file including its folders. - Example: /folder1/folder2/file_name.json - :return: content of the file loaded to json + :param bucket: + :param key: + :param buffer: + :param gz_buffer: can be optionally provided to use as a buffer for + compression. You can provide some temp file in case the size of body + is expected to be large + :return: """ - content = self.get_file_content( - bucket_name, full_file_name, decode=True) - return json.loads(content) if content else {} + if not gz_buffer: + gz_buffer = io.BytesIO() + stream = self.get_object(bucket, self._gz_key(key), gz_buffer) + if not stream: + return + gz_buffer.seek(0) + if not buffer: + buffer = io.BytesIO() + with gzip.GzipFile(fileobj=gz_buffer, mode='rb') as gz: + shutil.copyfileobj(gz, buffer) + buffer.seek(0) + return buffer + + def put_json(self, bucket: str, key: str, obj: Json): + return self.put_object( + bucket=bucket, + key=key, + body=self._enc.encode(obj), + content_type='application/json' + ) - def get_file_content(self, bucket_name: str, full_file_name: str, - decode: bool = False) -> Union[str, bytes]: - """ - Returns content of the object. - :param bucket_name: name of the bucket. - :param full_file_name: name of the file including its folders. - Example: /folder1/folder2/file_name.json - :param decode: flag - :return: content of the file - """ - response_stream = self.get_decompressed_stream(bucket_name, - full_file_name) - if not response_stream: - _LOG.warning( - f'No gzip file found for \'{self._gz_key(full_file_name)}\'. ' - f'Trying to reach not the gzipped version') - response_stream = self._gzip_object(bucket_name, full_file_name) - if not response_stream: - return - _LOG.info(f'Resource stream of {full_file_name} within ' - f'{bucket_name} has been established.') - if decode: - return response_stream.read().decode(UTF_8_ENCODING) - return response_stream.read() - - def get_file_stream(self, bucket_name: str, - full_file_name: str) -> Union[StreamingBody, None]: - """Returns boto3 stream with file content. If the file is not found, - returns None. The given file name is not converted to gz. - Use this method as a raw version based on which you build your - own logic""" + def gz_put_json(self, bucket: str, key: str, obj: Json): + # ignoring key, cause you specifically used this method. + # So it must be json and gzip + return self.gz_put_object( + bucket=bucket, + key=key, + body=self._enc.encode(obj), + content_type='application/json', + content_encoding='gzip' + ) + + def get_json(self, bucket: str, key: str) -> Json: + body = self.get_object(bucket, key) + if not body: + return {} + result = self._dec.decode(cast(io.BytesIO, body).getvalue()) + body.close() + return result + + def gz_get_json(self, bucket: str, key: str) -> Json: + body = self.gz_get_object(bucket, key) + if not body: + return {} + result = self._dec.decode(cast(io.BytesIO, body).getvalue()) + body.close() + return result + + def delete_object(self, bucket: str, key: str): + self.client.delete_object(Bucket=bucket, Key=key) + + def gz_delete_object(self, bucket: str, key: str): + self.delete_object(bucket, self._gz_key(key)) + + def object_meta(self, bucket: str, key: str): + obj = self.resource.Object(bucket, key) try: - response = self.client.get_object( - Bucket=bucket_name, - Key=full_file_name - ) - return response.get('Body') + obj.load() + return obj except ClientError as e: - if e.response['Error']['Code'] == 'NoSuchKey': - return None + if e.response['Error']['Code'] == '404': + return raise e - def get_decompressed_stream(self, bucket_name: str, - full_file_name: str) -> Union[GzipFile, None]: - stream = self.get_file_stream(bucket_name, - self._gz_key(full_file_name)) - if not stream: - return - return GzipFile(fileobj=stream) + def object_exists(self, bucket: str, key: str) -> bool: + # or better use list_objects with limit 1 + return bool(self.object_meta(bucket, key)) + + def gz_object_exists(self, bucket: str, key: str) -> bool: + return self.object_exists(bucket, self._gz_key(key)) - def list_objects(self, bucket_name: str, prefix: Optional[str] = None, - max_keys: Optional[int] = None, - delimiter: Optional[str] = None, + def list_objects(self, bucket: str, prefix: Optional[str] = None, + page_size: Optional[int] = None, + limit: Optional[int] = None, start_after: Optional[str] = None, - ) -> Generator[ObjectMetadata, None, None]: - params = dict(Bucket=bucket_name) - if max_keys: - params.update(MaxKeys=max_keys) + ) -> Iterable: + params = dict() if prefix: params.update(Prefix=prefix) - if delimiter: - params.update(Delimiter=delimiter) if start_after: - params.update(StartAfter=start_after) - is_truncated = True - while is_truncated: - _LOG.debug('Making list_objects_v2 request') - response = self.client.list_objects_v2(**params) - params.pop('StartAfter', None) - yield from response.get('Contents') or [] - limit = params.get('MaxKeys') - n_returned = len(response.get('Contents') or []) - if limit and n_returned == limit: - return - # either no limit or n_returned < limit - if limit: - params.update(MaxKeys=limit - n_returned) - is_truncated = response['IsTruncated'] - params['ContinuationToken'] = response.get('NextContinuationToken') - - def delete_file(self, bucket_name: str, file_key: str): - # TODO https://github.com/boto/boto3/issues/759, when the bug is - # fixed, use response statusCode instead of client.file_exists - gzip_file_key = self._gz_key(file_key) - if self._file_exists(bucket_name, gzip_file_key): - self.client.delete_object(Bucket=bucket_name, Key=gzip_file_key) - else: - _LOG.warning(f'File {gzip_file_key} was not found during ' - f'removing. Maybe it has not been compressed yet. ' - f'Trying to remove the not compressed version') - self.client.delete_object(Bucket=bucket_name, Key=gzip_file_key) - - def generate_presigned_url(self, bucket_name, full_file_name, - client_method='get_object', http_method='GET', - expires_in_sec=300, force_private_ip=False): - """Do not forget to use client.file_exists before using this method""" - url = self.client.generate_presigned_url( - ClientMethod=client_method, - Params={ - 'Bucket': bucket_name, - 'Key': self._gz_key(full_file_name), - }, - ExpiresIn=expires_in_sec, - HttpMethod=http_method - ) - if self.IS_DOCKER and not force_private_ip: - ipv4 = self._get_public_ipv4() - minikube_ip = self._get_minikube_ipv4() - if ipv4: - _LOG.info(f'Public ip: {ipv4} was received. Replacing the ' - f'domain in the presigned url') - parsed = urlparse(url) - return parsed._replace(netloc=parsed.netloc.replace( - parsed.hostname, ipv4)).geturl() - elif minikube_ip: - _LOG.info(f'Minikube ip: {minikube_ip} was received. ' - f'Replacing the domain in the presigned url') - # return url - parsed = urlparse(url) - return parsed._replace(netloc=minikube_ip).geturl() - return url + params.update(Marker=start_after) + it = self.resource.Bucket(bucket).objects.filter(**params) + if page_size is not None: + it = it.page_size(page_size) + if limit is not None: + it = it.limit(limit) + return it def list_dir(self, bucket_name: str, key: Optional[str] = None, - max_keys: Optional[int] = None, - delimiter: Optional[str] = None, + page_size: Optional[int] = None, + limit: Optional[int] = None, start_after: Optional[str] = None ) -> Generator[str, None, None]: """ Yields just keys - :param max_keys: :param bucket_name: :param key: - :param delimiter: + :param page_size: + :param limit: :param start_after: :return: """ - yield from (obj['Key'] for obj in self.list_objects( - bucket_name=bucket_name, + yield from (obj.key for obj in self.list_objects( + bucket=bucket_name, prefix=key, - max_keys=max_keys, - delimiter=delimiter, + page_size=page_size, + limit=limit, start_after=start_after )) - @staticmethod - def _get_public_ipv4(): - """Tries to retrieve a public ipv4 from EC2 instance metadata""" + def common_prefixes(self, bucket: str, delimiter: str, + prefix: Optional[str] = None, + start_after: Optional[str] = None + ) -> Generator[str, None, None]: + paginator = self.client.get_paginator('list_objects_v2') + params = dict(Bucket=bucket, Delimiter=delimiter) + if prefix: + params.update(Prefix=prefix) + if start_after: + params.update(StartAfter=start_after) + for item in paginator.paginate(**params): + for prefix in item.get('CommonPrefixes') or []: + yield prefix.get('Prefix') + + def create_bucket(self, bucket: str, region: str): + self.client.create_bucket( + Bucket=bucket, + CreateBucketConfiguration={'LocationConstraint': region} + ) + + def bucket_exists(self, bucket: str) -> bool: try: - _LOG.info('Trying to receive a public IP v4 from EC2 metadata') - return IMDSFetcher(timeout=0.5)._get_request( - "/latest/meta-data/public-ipv4", None).text - except (_RetriesExceededError, Exception) as e: - _LOG.warning(f'An IP v4 from EC2 metadata was not received: {e}') - # try: - # from requests import get, RequestException - # return get('https://api.ipify.org').content.decode('utf8') - # except RequestException: - # pass - _LOG.info('No public IP was received. Returning None...') - return + self.client.head_bucket(Bucket=bucket) + return True + except ClientError as e: + if e.response['Error']['Code'] != '404': + raise e + return False - @staticmethod - def _get_minikube_ipv4(port=None): - """Tries to retrieve the minikubes's api address and port""" - port = port or DEFAULT_MINIO_PORT - _LOG.info('Trying to receive the minikube`s ip') - ip = os.getenv(MINIKUBE_IP_PARAM) - if ip: - return f'{ip}:{port}' - _LOG.info('Minikube ip not found in environ. ' - 'Maybe the service is running beyond the Cluster?') - process = Popen('minikube ip'.split(), stdout=PIPE) - output, error = process.communicate() - output = output.decode().strip() - if not error and ipaddress.ip_address(output): - _LOG.info('`minikube ip` has executed successfully. Return result') - return f'{output}:{port}' - - @coroutine - def get_json_batch(self, bucket_name: str, keys: Iterable[str] - ) -> Generator[Tuple[str, Dict], None, None]: + def list_buckets(self) -> Generator[Bucket, None, None]: + yield from (self.client.list_buckets().get('Buckets') or []) + + def copy(self, bucket: str, key: str, destination_bucket: str, + destination_key: str): + self.client.copy( + CopySource=dict(Bucket=bucket, Key=key), + Bucket=destination_bucket, + Key=destination_key + ) + + def download_url(self, bucket: str, key: str, + expires_in: timedelta = timedelta(seconds=300), + filename: Optional[str] = None, + response_encoding: Optional[str] = None) -> str: """ - When you create this generator object it immediately starts - downloading items, and you can iterate over results when you need. + :param bucket: + :param key: + :param expires_in: + :param filename: custom filename for the file that will be downloaded + :param response_encoding: by default uses encoding from object meta. + :return: + """ + disposition = 'attachment;' + if filename: + disposition += f';filename="{filename}"' + params = { + 'Bucket': bucket, 'Key': key, + 'ResponseContentDisposition': disposition, + } + if response_encoding: + params['ResponseContentEncoding'] = response_encoding + return self.client.generate_presigned_url( + ClientMethod='get_object', + Params=params, + ExpiresIn=expires_in.seconds, + ) - :param bucket_name: - :param keys: + def gz_download_url(self, bucket: str, key: str, + expires_in: timedelta = timedelta(seconds=300), + filename: Optional[str] = None, + response_encoding: str = None) -> str: + """ + Prefix gz only impact the key here + :param bucket: + :param key: + :param expires_in: + :param filename: + :param response_encoding: :return: """ + return self.download_url(bucket, self._gz_key(key), expires_in, + filename, response_encoding) - def _process(*args, **kwargs): - try: - return self.get_json_file_content(*args, **kwargs) - except ClientError as e: - _LOG.warning(f'A client error was caught while getting the ' - f'content of a file: {e}') - return {} - - with ThreadPoolExecutor() as executor: - futures = { - executor.submit(_process, bucket_name, key): key - for key in keys - } - yield # remove this and coroutine decorator in case something wrong - for future in as_completed(futures): - yield futures[future], future.result() - - def put_objects_batch(self, bucket_name: str, - key_body: Iterable[Tuple[str, Union[str, bytes]]]): - with ThreadPoolExecutor() as executor: - futures = { - executor.submit(self.put_object, bucket_name, *pair): pair[0] - for pair in key_body - } - for future in as_completed(futures): - key = futures[future] - try: - future.result() - except (ClientError, Exception) as e: - _LOG.warning(f'Cloud not upload file \'{key}\': {e}') + def put_path_expiration(self, bucket: str, key: str, days: int): + """ + Creates a lifecycle rule with expiration for the given prefix + :param bucket: + :param key: + :param days: + :return: + """ + return self.client.put_bucket_lifecycle_configuration( + Bucket=bucket, + LifecycleConfiguration={'Rules': [{ + 'Expiration': {'Days': days}, + 'Filter': { + 'Prefix': key + }, + 'Status': 'Enabled' + }]} + ) class ModularAssumeRoleS3Service(S3Client): - from modular_sdk.services.aws_creds_provider import ModularAssumeRoleClient client = ModularAssumeRoleClient('s3') - def __init__(self, region): - super().__init__(region=region) + # probably actions that require resource won't work + + # the implementation in S3Client uses s3 resource to handle multipart + # upload if necessary. Currently, we can access only client here, so + # this implementation + def put_object(self, bucket: str, key: str, body: bytes | BinaryIO): + ct, ce = mimetypes.guess_type(key) + params = dict(Bucket=bucket, Key=key, Body=body) + if ct: + params.update(ContentType=ct) + if ce: + params.update(ContentEncoding=ce) + return self.client.put_object(**params) diff --git a/src/services/clients/scheduler.py b/src/services/clients/scheduler.py index 611d1bb30..85adbdf25 100644 --- a/src/services/clients/scheduler.py +++ b/src/services/clients/scheduler.py @@ -7,14 +7,11 @@ from botocore.exceptions import ClientError from modular_sdk.models.tenant import Tenant -from connections.batch_extension.base_job_client import BaseBatchClient -from helpers import build_response -from helpers.constants import BATCH_ENV_SUBMITTED_AT, \ - BATCH_ENV_TARGET_REGIONS, BATCH_ENV_SCHEDULED_JOB_NAME, \ - BATCH_ENV_TARGET_RULESETS_VIEW, \ - BATCH_ENV_LICENSED_RULESETS, ALL_ATTR +from helpers.lambda_response import ResponseFactory +from helpers.constants import ALL_ATTR, BatchJobEnv from helpers.log_helper import get_logger from models.scheduled_job import ScheduledJob +from services.clients.batch import BatchClient from services.clients.event_bridge import EventBridgeClient, BatchRuleTarget from services.clients.iam import IAMClient from services.environment_service import EnvironmentService @@ -42,7 +39,7 @@ def safe_name(name: str) -> str: def safe_name_from_tenant(self, tenant: Tenant) -> str: return self.safe_name( - f'custodian-job-{tenant.customer_name}_{tenant.name}' + f'custodian-job-{tenant.customer_name}-{tenant.name}' ) @abstractmethod @@ -68,17 +65,16 @@ def update_job(self, item: ScheduledJob, is_enabled: Optional[bool] = None, """ @staticmethod - def _update_job_obj_with(obj: ScheduledJob, tenant: Tenant, - schedule: str, envs: dict): - """ - Retrieves some necessary attributes from account obj and job - envs and sets them to the scheduled_job obj - """ + def _scan_regions_from_env(envs: dict) -> list[str]: + return envs.get(BatchJobEnv.TARGET_REGIONS, '').split(',') + + @staticmethod + def _scan_rulesets_from_env(envs: dict) -> list[str]: rule_sets = [] - standard = envs.get(BATCH_ENV_TARGET_RULESETS_VIEW) + standard = envs.get(BatchJobEnv.TARGET_RULESETS) if standard and isinstance(standard, str): rule_sets.extend(standard.split(',')) - licensed = envs.get(BATCH_ENV_LICENSED_RULESETS) + licensed = envs.get(BatchJobEnv.LICENSED_RULESETS) if licensed and isinstance(licensed, str): rule_sets.extend( each.split(':', maxsplit=1)[-1] @@ -86,13 +82,7 @@ def _update_job_obj_with(obj: ScheduledJob, tenant: Tenant, ) if not rule_sets: rule_sets.append(ALL_ATTR) - obj.update_with( - customer=tenant.customer_name, - tenant=tenant.name, - schedule=schedule, - scan_regions=envs.get(BATCH_ENV_TARGET_REGIONS, '').split(','), - scan_rulesets=rule_sets - ) + return rule_sets class EventBridgeJobScheduler(AbstractJobScheduler): @@ -100,7 +90,7 @@ class EventBridgeJobScheduler(AbstractJobScheduler): def __init__(self, client: EventBridgeClient, environment_service: EnvironmentService, iam_client: IAMClient, - batch_client: BaseBatchClient): + batch_client: BatchClient): self._client = client self._environment = environment_service self._iam_client = iam_client @@ -133,8 +123,7 @@ def _put_rule_asserting_valid_schedule_expression(self, *args, **kwargs): message = e.response['Error']['Message'] _LOG.warning(f'User has sent invalid schedule ' f'expression: {args}, {kwargs}') - return build_response(code=HTTPStatus.BAD_REQUEST, - content=f'Validation error: {message}') + raise ResponseFactory(HTTPStatus.BAD_REQUEST).errors([message]).exc() raise def register_job(self, tenant: Tenant, schedule: str, @@ -154,8 +143,8 @@ def register_job(self, tenant: Tenant, schedule: str, role_arn=self._iam_client.build_role_arn( self._environment.event_bridge_service_role()), ) - environment[BATCH_ENV_SUBMITTED_AT] = '' - environment[BATCH_ENV_SCHEDULED_JOB_NAME] = _id + environment[BatchJobEnv.SUBMITTED_AT] = '' + environment[BatchJobEnv.SCHEDULED_JOB_NAME] = _id target.set_input_transformer( {'submitted_at': '$.time'}, self._batch_client.build_container_overrides( @@ -167,10 +156,18 @@ def register_job(self, tenant: Tenant, schedule: str, ) _ = self._client.put_targets(_id, [target, ]) _LOG.debug('Batch queue target was added to the created rule') - _job = ScheduledJob(id=_id) - self._update_job_obj_with(_job, tenant, schedule, environment) + _job = ScheduledJob( + id=_id, + customer_name=tenant.customer_name, + tenant_name=tenant.name, + context=dict( + schedule=schedule, + scan_regions=self._scan_regions_from_env(environment), + scan_rulesets=self._scan_rulesets_from_env(environment), + is_enabled=True + ), + ) _job.save() - _LOG.debug('Scheduled job`s data was saved to Dynamodb') _LOG.info(f'Scheduled job with name \'{_id}\' was added') return _job @@ -189,35 +186,27 @@ def deregister_job(self, _id: str): def update_job(self, item: ScheduledJob, is_enabled: Optional[bool] = None, schedule: Optional[str] = None): - _id = item.id - enabling_rule_map = { - True: self._client.enable_rule, - False: self._client.disable_rule - } - _LOG.info(f'Updating scheduled job with id \'{_id}\'') - params = dict(rule_name=_id) - - existing_rule = self._client.describe_rule(**params) - if not existing_rule: - _LOG.error('The EventBridge rule somehow disparaged') - return build_response( - code=HTTPStatus.NOT_FOUND, - content=f'Cannot find rule for scheduled job \'{_id}\'. ' - f'Please recreate the job') - + if not isinstance(is_enabled, bool) and not schedule: + return + rule = self._client.describe_rule(rule_name=item.id) + params = dict( + rule_name=item.id, + schedule=rule['ScheduleExpression'], + state=rule['State'] + ) + actions = [] if isinstance(is_enabled, bool): - enabling_rule_map.get(is_enabled)(**params) - + params['state'] = 'ENABLED' if is_enabled else 'DISABLED' + actions.append(ScheduledJob.context['is_enabled'].set(is_enabled)) if schedule: - new_description = self._rule_description_from_existing_one( - existing_rule.get('Description'), schedule) - params.update(schedule=schedule, description=new_description, - state=existing_rule.get('State')) - self._put_rule_asserting_valid_schedule_expression(**params) - _LOG.debug('EventBridge rule was updated') - - item.update_with(is_enabled=is_enabled, schedule=schedule) - item.save() - _LOG.debug('Scheduled job`s data was updated in Dynamodb') - _LOG.info( - f'Scheduled job with name \'{_id}\' was successfully updated') + params['schedule'] = schedule + params['description'] = self._rule_description_from_existing_one( + rule['Description'], + schedule + ) + actions.append(ScheduledJob.context['schedule'].set(schedule)) + self._put_rule_asserting_valid_schedule_expression(**params) + _LOG.debug('EventBridge rule was updated') + item.update(actions=actions) + + _LOG.info(f'Scheduled job \'{item.id}\' was successfully updated') diff --git a/src/services/clients/smtp.py b/src/services/clients/smtp.py index 718e68f97..e679b2daa 100644 --- a/src/services/clients/smtp.py +++ b/src/services/clients/smtp.py @@ -1,15 +1,12 @@ from smtplib import SMTP, SMTPConnectError, SMTPException -from dataclasses import dataclass from typing import Optional, List from helpers.log_helper import get_logger - _LOG = get_logger(__name__) class SMTPClient: - host: Optional[str] port: Optional[int] diff --git a/src/services/clients/ssm.py b/src/services/clients/ssm.py index 8e59f0990..cc4675922 100644 --- a/src/services/clients/ssm.py +++ b/src/services/clients/ssm.py @@ -1,19 +1,17 @@ import json import os from abc import ABC, abstractmethod -from typing import Union, Optional, List, Dict import boto3 from botocore.client import ClientError -from helpers.constants import ENV_VAULT_HOST, ENV_VAULT_PORT, \ - ENV_VAULT_TOKEN, ENV_SERVICE_MODE, DOCKER_SERVICE_MODE +from helpers.constants import CAASEnv from helpers.log_helper import get_logger from services.environment_service import EnvironmentService _LOG = get_logger(__name__) -SecretValue = Union[Dict, List, str] +SecretValue = list | dict | str class AbstractSSMClient(ABC): @@ -21,7 +19,7 @@ def __init__(self, environment_service: EnvironmentService): self._environment_service = environment_service @abstractmethod - def get_secret_value(self, secret_name: str) -> Optional[SecretValue]: + def get_secret_value(self, secret_name: str) -> SecretValue | None: ... @abstractmethod @@ -33,11 +31,6 @@ def create_secret(self, secret_name: str, secret_value: SecretValue, def delete_parameter(self, secret_name: str) -> bool: ... - @abstractmethod - def get_secret_values(self, secret_names: List[str] - ) -> Optional[Dict[str, SecretValue]]: - ... - @abstractmethod def enable_secrets_engine(self, mount_point=None): ... @@ -56,17 +49,13 @@ def __init__(self, environment_service: EnvironmentService): self._client = None # hvac.Client def _init_client(self): - assert os.getenv(ENV_SERVICE_MODE) == DOCKER_SERVICE_MODE, \ - "You can init vault handler only if SERVICE_MODE=docker" import hvac - vault_token = os.getenv(ENV_VAULT_TOKEN) - vault_host = os.getenv(ENV_VAULT_HOST) - vault_port = os.getenv(ENV_VAULT_PORT) + token = os.getenv(CAASEnv.VAULT_TOKEN) + endpoint = os.getenv(CAASEnv.VAULT_ENDPOINT) + assert token and endpoint, ('Vault endpoint and token must ' + 'be specified for on-prem') _LOG.info('Initializing hvac client') - self._client = hvac.Client( - url=f'http://{vault_host}:{vault_port}', - token=vault_token - ) + self._client = hvac.Client(url=endpoint, token=token) _LOG.info('Hvac client was initialized') @property @@ -75,7 +64,7 @@ def client(self): self._init_client() return self._client - def get_secret_value(self, secret_name: str) -> Optional[SecretValue]: + def get_secret_value(self, secret_name: str) -> SecretValue | None: try: response = self.client.secrets.kv.v2.read_secret_version( path=secret_name, mount_point=self.mount_point) or {} @@ -95,10 +84,6 @@ def delete_parameter(self, secret_name: str) -> bool: return bool(self.client.secrets.kv.v2.delete_metadata_and_all_versions( path=secret_name, mount_point=self.mount_point)) - def get_secret_values(self, secret_names: List[str] - ) -> Optional[Dict[str, SecretValue]]: - return {name: self.get_secret_value(name) for name in secret_names} - def enable_secrets_engine(self, mount_point=None): try: self.client.sys.enable_secrets_engine( @@ -128,7 +113,7 @@ def client(self): 'ssm', self._environment_service.aws_region()) return self._client - def get_secret_value(self, secret_name): + def get_secret_value(self, secret_name: str): try: response = self.client.get_parameter( Name=secret_name, @@ -144,23 +129,8 @@ def get_secret_value(self, secret_name): _LOG.error(f'Can\'t get secret for name \'{secret_name}\', ' f'error code: \'{error_code}\'') - def get_secret_values(self, secret_names: List[str] - ) -> Optional[Dict[str, SecretValue]]: - try: - response = self.client.get_parameters( - Names=secret_names, - WithDecryption=True - ) - parameters = {item.get('Name'): item.get('Value') for item in - response.get('Parameters')} - return parameters - except ClientError as e: - error_code = e.response['Error']['Code'] - _LOG.error(f'Can\'t get secret for names \'{secret_names}\', ' - f'error code: \'{error_code}\'') - def create_secret(self, secret_name: str, - secret_value: Union[str, list, dict], + secret_value: str | list | dict, secret_type='SecureString') -> bool: try: if isinstance(secret_value, (list, dict)): @@ -190,7 +160,7 @@ def delete_parameter(self, secret_name: str) -> bool: return False def enable_secrets_engine(self, mount_point=None): - """No need to implement""" + pass def is_secrets_engine_enabled(self, mount_point=None) -> bool: - """No need to implement""" + return True diff --git a/src/services/clients/standalone_key_management.py b/src/services/clients/standalone_key_management.py deleted file mode 100644 index 4c755d5b7..000000000 --- a/src/services/clients/standalone_key_management.py +++ /dev/null @@ -1,537 +0,0 @@ -from services.clients.abstract_key_management import \ - AbstractKeyManagementClient, IKey, \ - KEY_TYPE_ATTR, KEY_STD_ATTR, HASH_TYPE_ATTR, HASH_STD_ATTR, SIG_SCHEME_ATTR - -from typing import Union, Callable, Optional, Dict - -from services.clients.ssm import SSMClient - -from Crypto import Hash -from Crypto.PublicKey import ECC -from Crypto.Signature import DSS - -from helpers.log_helper import get_logger - -ECC_KEY_ATTR = 'ECC' -SHA_HASH_ATTR = 'SHA' -DSS_SIGN_ATTR = 'DSS' - -VALUE_ATTR = 'value' - -# SHA attributes -TFS_SHA_BIT_MODE = 256 -BIT_MODE_ATTR = 'bit_mode' - -ATTR_DELIMITER = '_' - -_LOG = get_logger(__name__) - -# DSS attributes -DSS_MODE_ATTR = 'mode' -DSS_FIPS_MODE = 'fips-186-3' -DSS_RFC6979_MODE = 'deterministic-rfc6979' - -KEY_ATTR = 'key' -HASH_ATTR = 'hash' - -# Key standard attr(s): -# .Ecc: -ECC_NIST_CURVES = ('p521', 'p384', 'p256', 'p224') -ECDSA_NIST_CURVES = ('p521', 'p384', 'p256', 'p224') - -# Hash standard attr(s): -# .Sha: -SHA_BIT_MODES = ('256', '512') -SHA2_MODES = ('256', '512') - - -class StandaloneKeyManagementClient(AbstractKeyManagementClient): - """ - Provides necessary cryptographic behaviour for the following actions: - - signature verification - - signature production - - key construction - - key generation - - key persistence - adhering to the self-declared algorithm(s), which is bound to the next - format: - `$key-type:$key-standard-label`_`$scheme`_`$hash-type:$hash-standard-label` - Note: - Such extended formatting approach would allow to provide stateless, - verification if required. - """ - def __init__(self, ssm_client: SSMClient): - self._ssm_client = ssm_client - - def sign(self, key_id: str, message: Union[str, bytes], algorithm: str, - encoding='utf-8') -> Optional[bytes]: - """ - Mandates signature production computed using a private-key, retrieved - from a manager store, and an algorithm string, segments of which, - split by `_`, explicitly state: - - key-type standard`:`standard-data-label - - signature-scheme standard - - hashing mode`:`standard-data-label - - Note: standard-data-label is meant to provide stateless - configuration of standards, denoting labels, which are supported - i.e. key data - ECC:p521. - - :parameter key_id: str - :parameter message: Union[str, bytes] - :parameter algorithm: str - :parameter encoding: str - :return: Union[bytes, Type[None]] - """ - is_bytes = isinstance(message, bytes) - message = message if is_bytes else bytes(message, encoding) - - _LOG.debug(f'Going to split \'{algorithm}\' algorithm into standards.') - alg: Optional[Dict[str, str]] = self.dissect_alg(alg=algorithm) - if not algorithm: - return - - # Retrieve type and standard data of a key, hash and signature scheme. - key_type, key_std, hash_type, hash_std, sig_scheme = map( - alg.get, ( - KEY_TYPE_ATTR, KEY_STD_ATTR, HASH_TYPE_ATTR, HASH_STD_ATTR, - SIG_SCHEME_ATTR - ) - ) - - _LOG.debug(f'Checking \'{algorithm}\' signature protocol support.') - if key_type not in self._key_construction_map(): - _LOG.warning(f'\'{key_type}\' construction is not supported') - return None - - if not self.is_signature_scheme_accessible( - sig_scheme=sig_scheme, key_type=key_type, key_std=key_std, - hash_type=hash_type, hash_std=hash_std - ): - _LOG.warning( - f'\'{algorithm}\' signature-protocol is not supported.' - ) - return None - - _LOG.debug(f'Going to retrieve raw \'{key_id}\' key data.') - key_data: dict = self.get_key_data(key_id=key_id) - if not key_data: - return None - - key = self.get_key( - key_type=key_type, key_std=key_std, key_data=key_data - ) - if not key: - return None - - hash_obj = self._get_hash_client( - message=message, hash_type=hash_type, hash_std=hash_std, - **(key_data.get(hash_type) or {}) - ) - if not hash_obj: - return None - - signer = self._get_signature_client( - key=key, sig_scheme=sig_scheme, **(key_data.get(sig_scheme) or {}) - ) - if not signer: - return None - - return signer.sign(hash_obj) - - def verify(self, key_id: str, message: Union[str, bytes], algorithm: str, - signature: bytes, encoding='utf-8') -> bool: - """ - Mandates signature verification computed using a public-key, retrieved - from a manager store, and an algorithm string, segments of which - explicitly state: - - key-type standard`:`standard-data-label - - signature-scheme standard - - hashing mode`:`standard-data-label - :parameter key_id: str - :parameter message: Union[str, bytes] - :parameter algorithm: str - :parameter signature: bytes - :parameter encoding: str - :return: bool - """ - # Currently obsolete. - is_bytes = isinstance(message, bytes) - message = message if is_bytes else bytes(message, encoding) - - _LOG.debug(f'Going to split \'{algorithm}\' algorithm into standards.') - alg: Optional[Dict[str, str]] = self.dissect_alg(alg=algorithm) - if not algorithm: - return False - - # Retrieve type and standard data of a key, hash and signature scheme. - key_type, key_std, hash_type, hash_std, sig_scheme = map( - alg.get, ( - KEY_TYPE_ATTR, KEY_STD_ATTR, HASH_TYPE_ATTR, HASH_STD_ATTR, - SIG_SCHEME_ATTR - ) - ) - - _LOG.debug(f'Checking \'{algorithm}\' verification protocol support.') - if key_type not in self._key_construction_map(): - _LOG.warning(f'\'{key_type}\' construction is not supported') - return False - - if not self.is_signature_scheme_accessible( - sig_scheme=sig_scheme, key_type=key_type, key_std=key_std, - hash_type=hash_type, hash_std=hash_std - ): - _LOG.warning( - f'\'{algorithm}\' verification-protocol is not supported.' - ) - return False - - _LOG.debug(f'Going to retrieve raw \'{key_id}\' key data.') - key_data: dict = self.get_key_data(key_id=key_id) - if not key_data: - return False - - key = self.get_key( - key_type=key_type, key_std=key_std, key_data=key_data - ) - if not key: - return False - - hash_obj = self._get_hash_client( - message=message, hash_type=hash_type, hash_std=hash_std, - **(key_data.get(hash_type) or {}) - ) - if not hash_obj: - return False - - verifier = self._get_signature_client( - key=key, sig_scheme=sig_scheme, **(key_data.get(sig_scheme) or {}) - ) - if not verifier: - return False - try: - verifier.verify(hash_obj, signature) - _LOG.debug(f'Signature verification, based on {key_id}, has been ' - f'asserted as valid.') - return True - except ValueError as _ve: - _LOG.debug(f'Signature verification, based on {key_id} has been ' - f'asserted as invalid: {_ve}.') - return False - - def generate(self, key_type: str, key_std: str, **data): - """ - Produces a random key, based on a given key-type and respective - standard-label. - :param key_type: str - :param key_std: str - :return: Optional[IKey] - """ - reference = self._key_generation_map() - generator = reference.get(key_type, {}).get(key_std) - if not generator: - _LOG.warning(f'\'{key_type}\':{key_std} generator is not' - f' supported') - return - try: - return generator(key_std, **data) - except (TypeError, Exception) as e: - _LOG.warning(f'\'{key_type}\' generator could not be invoked, ' - f'due to: "{e}".') - return - - def save( - self, key_id: str, key: IKey, key_format: str = 'PEM', **data - ) -> bool: - """ - Persists given key within a parameter store, under a given key_id - label. - :parameter key_id: str - :parameter key: IKey - :parameter key_format: str - """ - try: - exported: Union[str, bytes] = key.export_key(format=key_format) - except (ValueError, Exception) as e: - _LOG.warning(f'Key:\'{key_id}\' could not be exported into ' - f'{key_format} format, due to: "{e}".') - return False - - if isinstance(exported, bytes): - exported = exported.decode('utf-8') - - mapped = data.copy() - mapped.update({VALUE_ATTR: exported}) - return self._ssm_client.create_secret( - secret_name=key_id, secret_value=mapped - ) - - def delete(self, key_id: str): - return self._ssm_client.delete_parameter(secret_name=key_id) - - def get_key(self, key_type: str, key_std: str, key_data: dict): - """ - Mediates cryptographic key instance derivation, based on a key_type - and a respective key_data. - :parameter key_type: str - :parameter key_std: str, type-respective standard data label - :parameter key_data: dict, any store-persisted key data - :return: Optional[IKey] - """ - key_value = key_data.pop(VALUE_ATTR) - - try: - key = self.construct( - key_type=key_type, key_std=key_std, key_value=key_value, - **key_data - ) - except (ValueError, Exception) as e: - _LOG.error( - f'Could not instantiate {key_type}:{key_std} due to "{e}".') - return None - - return key - - def get_key_data(self, key_id: str) -> Optional[dict]: - """ - Mandates raw cryptographic-key retrieval referencing management store. - :parameter key_id: str - :return: Union[dict, Type[None]] - """ - item = self._ssm_client.get_secret_value(secret_name=key_id) - item = _load_json(item) if isinstance(item, str) else item - is_dict = isinstance(item, dict) - predicate = not is_dict or VALUE_ATTR not in item - if predicate: - header = f'\'{key_id}\' key: {item}' - _LOG.error(f'{header} ' + 'is not a dictionary' if not is_dict - else 'does not contain a \'value\' key.') - return None - return item - - @classmethod - def construct( - cls, key_type: str, key_std: str, key_value: str, **data - ): - """ - Head cryptographic key construction mediator, which derives a - key type - raw value type constructor, given one has been found. - :parameter key_type: str, cryptographic key-type - :parameter key_std: str, cryptographic key-type standard label - :parameter key_value: str, raw key value - :parameter data: dict, any store-persisted data, related to the key. - :return: Union[object, Type[None]] - """ - mediator_map = cls._key_construction_map() - _map: dict = mediator_map.get(key_type, {}) - if not _map: - _LOG.warning(f'No {key_type} key constructor could be found.') - return None - - mediator: Callable = _map.get(key_std) - if not mediator: - _LOG.warning(f'{key_type} key does not support {key_std} ' - 'construction.') - return None - - try: - built = mediator(value=key_value, key_std=key_std, **data) - except (ValueError, Exception) as e: - _LOG.warning(f'Key of {key_type}:{key_std} standard ' - f'could not be constructed due to: "{e}".') - built = None - return built - - @classmethod - def is_signature_scheme_accessible( - cls, sig_scheme: str, key_type: str, key_std: str, hash_type: str, - hash_std: str - ): - ref = cls._signature_scheme_reference().get(sig_scheme, {}) - return hash_std in ref.get(key_type, {}).get(key_std, {}).get( - hash_type, [] - ) - - @classmethod - def _get_hash_client( - cls, message: bytes, hash_type: str, hash_std: str, **data - ): - """ - Mandates message-hash resolution based on provided type and - optional standard data. - :parameter message: bytes - :parameter hash_type: str - :parameter hash_std: str, cryptographic hash-type wise standard label - :parameter data: dict, any store-persisted data, related to the hash. - :return: Type[object, None] - """ - resolver = cls._hash_construction_map().get(hash_type, []).get( - hash_std - ) - return resolver(message, hash_std, **data) if resolver else None - - @classmethod - def _get_signature_client(cls, key, sig_scheme: str, **data): - """ - Resolves key signature actor based on provided type and optional - standard data. - :parameter key: object - :parameter sig_scheme: str, cryptographic signature scheme label - :parameter data: dict, any persisted data, related to the hash. - :return: Type[object, None] - """ - resolver = cls._signature_construction_map().get(sig_scheme) - return resolver(key, **data) if resolver else None - - @classmethod - def _key_construction_map(cls): - """ - Declares a construction key-map, which follows the structure: - { - $key_type: { - $key_std: Callable[value: str, key_std: std, **kwargs] - } - } - :return: Dict[str, Dict[str, Callable]] - """ - reference = {ECC_KEY_ATTR: {}} - for curve in ECC_NIST_CURVES: - reference[ECC_KEY_ATTR][curve] = cls._import_ecc_key - return reference - - @classmethod - def _key_generation_map(cls) -> Dict[str, Dict[str, Callable]]: - reference = {ECC_KEY_ATTR: {}} - for curve in ECC_NIST_CURVES: - reference[ECC_KEY_ATTR][curve] = cls._generate_ecc_key - return reference - - @classmethod - def _hash_construction_map(cls): - reference = {SHA_HASH_ATTR: {}} - for bit_mode in SHA_BIT_MODES: - reference[SHA_HASH_ATTR][bit_mode] = cls._get_sha - return reference - - @classmethod - def _signature_construction_map(cls): - return { - DSS_SIGN_ATTR: cls._get_dss - } - - @staticmethod - def _signature_scheme_reference(): - """ - Returns a reference map accessible signature schemes, based on the - following structure: - { - $signature_scheme: { - $key_type: { - $key_std: { - $hash_type: Iterable[$hash_standard] - } - } - } - } - :return: Dict[str, Dict[str, Dict[str, Dict[str, Iterable[str]]]]] - """ - # Declares DSS scheme accessible protocols. - dss = dict() - dss[ECC_KEY_ATTR] = { - curve: { - SHA_HASH_ATTR: SHA2_MODES - } - for curve in ECDSA_NIST_CURVES - } - return { - DSS_SIGN_ATTR: dss - } - - @staticmethod - def _import_ecc_key(value: str, key_std: str, **kwargs): - """ - Delegated to import an Elliptic Curve key. - :parameter value: str - :parameter key_std: standard-label of an ECC key - :parameter kwargs: dict - :return: EccKey - """ - # Declares optional parameters - parameters = ['passphrase'] - payload = _filter_items(source=kwargs, include_list=parameters) - payload['curve_name'] = key_std - payload['encoded'] = value - return ECC.import_key(**payload) - - @staticmethod - def _generate_ecc_key(key_std: str, **kwargs): - """ - Delegated to construct an Elliptic Curve key, based on a - given standard-curve. - :param key_std: str, standard-label, which denotes curve on default - :param kwargs: dict, any additionally allowed data to inject - :return: EccKey - """ - parameters = ['curve', 'rand_func'] - payload = _filter_items(source=kwargs, include_list=parameters) - payload['curve'] = key_std or payload.get('curve') - return ECC.generate(**payload) - - @staticmethod - def _get_sha(message: bytes, hash_std: str, **data): - """ - Delegated to instantiate a hasher bound to the SHA standard, - deriving a bit-mode-parameter, which by default is set to 256-bits. - :parameter message: bytes - :param hash_std: cryptographic type-wise standard label - - denotes bit-mode - :parameter data: dict, any store-persisted data, related to the hash. - :return: Union[object, Type[None]] - """ - bit_mode = hash_std or data.get(BIT_MODE_ATTR) - module = Hash.__dict__.get(SHA_HASH_ATTR + bit_mode) - if not module: - _LOG.warning(f'SHA does not support {bit_mode} mode.') - return None - return module.new(message) - - @classmethod - def _get_dss(cls, key, **data): - """ - Delegated to instantiate a signer bound to the Digital Signature - standard, deriving optional bit-mode-attribute, which by default is - set to deterministic-rfc6979. - :parameter key: Union[DsaKey, EccKey] - :parameter data: dict - :return: Union[object, Type[None]] - """ - parameters = dict(key=key) - - default = DSS_RFC6979_MODE - raw_mode = data.get(DSS_MODE_ATTR, default) - try: - parameters['mode'] = str(raw_mode) - except (ValueError, Exception) as e: - _LOG.warning(f'Improper DSS mode value: \'{raw_mode}\'.') - return None - - try: - signer = DSS.new(**parameters) - except (TypeError, ValueError, Exception) as e: - _LOG.warning(f'Could not instantiate a DSS signer: {e}') - signer = None - - return signer - - -def _filter_items(source: dict, include_list: list) -> dict: - return {key: value for key, value in source.items() if key in include_list} - - -def _load_json(data: str): - from json import loads, JSONDecodeError - try: - loaded = loads(data) - except (ValueError, JSONDecodeError): - loaded = data - return loaded diff --git a/src/services/clients/step_function.py b/src/services/clients/step_function.py new file mode 100644 index 000000000..888640759 --- /dev/null +++ b/src/services/clients/step_function.py @@ -0,0 +1,93 @@ +import json +from abc import ABC +from functools import cached_property + +import boto3 +from botocore.exceptions import ClientError + +from helpers.log_helper import get_logger +from services.environment_service import EnvironmentService + +_LOG = get_logger(__name__) + +# STEP_FUNCTION_TO_PACKAGE_MAPPING = { +# RETRY_REPORT_STATE_MACHINE: 'custodian_report_generation_handler', +# SEND_REPORTS_STATE_MACHINE: 'custodian_report_generation_handler' +# } + + +class AbstractStepFunctionClient(ABC): + def __init__(self, environment_service: EnvironmentService): + self._environment_service = environment_service + + def invoke(self, state_machine_name: str, event: dict, + job_id: str | None = None) -> bool: + pass + + +class ScriptClient(AbstractStepFunctionClient): + def invoke(self, state_machine_name, event: dict, job_id: str = None): + _LOG.warning('Step function client is not implemented for on-prem') + return False + # handler = self._derive_handler(state_machine_name) + # if handler: + # _LOG.debug(f'Handler: {handler}') + # args = [{}, RequestContext()] + # if event: + # args[0] = event + # Thread(target=self._handle_execution, args=( + # handler.lambda_handler, *args)).start() + # response = dict(StatusCode=202) + # return response + + # @staticmethod + # def _handle_execution(handler: Callable, *args): + # try: + # response = handler(*args) + # except CustodianException as e: + # resp = e.response.build() + # response = dict(code=resp['statusCode'], body=resp['body']) + # return response + # + # @staticmethod + # def _derive_handler(function_name): + # _LOG.debug(f'Importing lambda \'{function_name}\'') + # package_name = STEP_FUNCTION_TO_PACKAGE_MAPPING.get(function_name) + # if not package_name: + # return + # return getattr( + # import_module(f'lambdas.{package_name}.handler'), 'HANDLER' + # ) + + +class StepFunctionClient(AbstractStepFunctionClient): + @cached_property + def client(self): + return boto3.client('stepfunctions', + region_name=self._environment_service.aws_region()) + + @staticmethod + def build_step_function_arn(region: str, account_id: str, name: str + ) -> str: + return f'arn:aws:states:{region}:{account_id}:statemachine:{name}' + + def invoke(self, state_machine_name: str, event: dict, + job_id: str | None = None) -> bool: + _LOG.debug( + f'Invoke step function {state_machine_name} with event: {event}') + params = { + 'stateMachineArn': self.build_step_function_arn( + self._environment_service.aws_region(), + self._environment_service.account_id(), + state_machine_name + ), + 'input': json.dumps(event, separators=(',', ':')) + } + if job_id: + params.update(name=job_id) + try: + self.client.start_execution(**params) + return True # todo look at status code + except ClientError: + _LOG.warning('Could not invoke step function', exc_info=True) + return False diff --git a/src/services/clients/sts.py b/src/services/clients/sts.py index e9d184132..93e62498e 100644 --- a/src/services/clients/sts.py +++ b/src/services/clients/sts.py @@ -1,27 +1,58 @@ -from functools import lru_cache +import base64 +import time +from datetime import datetime +from typing import TypedDict -import boto3 +from botocore.client import BaseClient +from helpers.constants import CAASEnv from helpers.log_helper import get_logger +from services import SP +from services.clients import Boto3ClientWrapper, Boto3ClientFactory from services.environment_service import EnvironmentService _LOG = get_logger(__name__) +TOKEN_PREFIX = 'k8s-aws-v1.' +CLUSTER_NAME_HEADER = 'x-k8s-aws-id' -class StsClient: + +class _AssumeRoleCredentials(TypedDict): + AccessKeyId: str + SecretAccessKey: str + SessionToken: str + Expiration: datetime + + +class AssumeRoleResult(TypedDict): + Credentials: _AssumeRoleCredentials + AssumedRoleUser: dict + PackedPolicySize: int + SourceIdentity: str + + +class CallerIdentity(TypedDict): + UserId: str + Account: str + Arn: str + + +class StsClient(Boto3ClientWrapper): + service_name = 'sts' def __init__(self, environment_service: EnvironmentService): self._environment = environment_service - self._client = None - @property - def client(self): - if not self._client: - self._client = boto3.client('sts', self._environment.aws_region()) - return self._client + @classmethod + def build(cls) -> 'StsClient': + return cls( + environment_service=SP.environment_service + ) def assume_role(self, role_arn: str, duration: int = 3600, - role_session_name: str = 'custodian_scan') -> dict: + role_session_name: str = 'custodian-service' + ) -> AssumeRoleResult: + role_session_name = role_session_name or f'Custodian-scan-{time.time()}' params = { 'RoleArn': role_arn, 'RoleSessionName': role_session_name, @@ -29,16 +60,63 @@ def assume_role(self, role_arn: str, duration: int = 3600, } return self.client.assume_role(**params) - def get_caller_identity(self, credentials: dict = None) -> dict: + def get_caller_identity(self, credentials: dict = None) -> CallerIdentity: if credentials: - return boto3.client('sts', **credentials).get_caller_identity() + return Boto3ClientFactory('sts').from_keys( + **credentials).get_caller_identity() return self.client.get_caller_identity() - @lru_cache() def get_account_id(self) -> str: _id = self._environment.account_id() if not _id: _LOG.warning('Valid account id not found in envs. ' 'Calling \'get_caller_identity\'') _id = self.get_caller_identity()['Account'] + self._environment.override_environment({CAASEnv.ACCOUNT_ID: _id}) return _id + + +class TokenGenerator: + """ + From python AWS CLI + """ + + def __init__(self, sts_client: BaseClient): + self._sts_client = sts_client + self._register_cluster_name_handlers(self._sts_client) + + def get_token(self, cluster_name: str): + """ + Generate a presigned url token to pass to kubectl + """ + url = self._get_presigned_url(cluster_name) + token = TOKEN_PREFIX + base64.urlsafe_b64encode( + url.encode('utf-8')).decode('utf-8').rstrip('=') + return token + + def _get_presigned_url(self, cluster_name: str): + return self._sts_client.generate_presigned_url( + 'get_caller_identity', + Params={'ClusterName': cluster_name}, + ExpiresIn=60, + HttpMethod='GET', + ) + + def _register_cluster_name_handlers(self, sts_client): + sts_client.meta.events.register( + 'provide-client-params.sts.GetCallerIdentity', + self._retrieve_cluster_name + ) + sts_client.meta.events.register( + 'before-sign.sts.GetCallerIdentity', + self._inject_cluster_name_header + ) + + def _retrieve_cluster_name(self, params, context, **kwargs): + if 'ClusterName' in params: + context['eks_cluster'] = params.pop('ClusterName') + + def _inject_cluster_name_header(self, request, **kwargs): + if 'eks_cluster' in request.context: + request.headers[ + CLUSTER_NAME_HEADER] = request.context['eks_cluster'] diff --git a/src/services/coverage_service.py b/src/services/coverage_service.py index 38079d7c2..662f6fed3 100644 --- a/src/services/coverage_service.py +++ b/src/services/coverage_service.py @@ -1,28 +1,26 @@ -import io -import json +from enum import Enum from statistics import mean -from typing import Union, Dict, List, Set, Optional -from helpers import adjust_cloud -from helpers.constants import AWS_CLOUD_ATTR, GCP_CLOUD_ATTR, \ - AZURE_CLOUD_ATTR, MULTIREGION +from helpers.constants import GLOBAL_REGION, Cloud from helpers.log_helper import get_logger from helpers.reports import Standard -from services.rule_meta_service import LazyLoadedMappingsCollector +from services.mappings_collector import LazyLoadedMappingsCollector +from services.sharding import ShardsCollection -COVERAGE_POINTS_KEY, COVERAGE_PERCENTAGES_KEY = 'P', '%' -_LOG = get_logger(__name__) -# region to standard to set of points +class CoverageKey(str, Enum): + POINTS = 'P' + PERCENTAGES = '%' + -Points = Dict[Standard, Set[str]] # standard to set of points -RegionPoints = Dict[str, Points] +_LOG = get_logger(__name__) -Coverage = Dict[Union[Standard, str], float] -RegionCoverage = Dict[str, Coverage] +Points = dict[Standard, set[str]] +RegionPoints = dict[str, Points] +Coverage = dict[Standard, float] -class CoverageCalculator: +class CoverageCalculator: # todo test def __init__(self, standards_coverage: dict): self._standards_coverage = standards_coverage @@ -49,25 +47,23 @@ def _calculate(cls, coverage: dict, points: set) -> float: """ if not coverage: return 0 - percents: List[float] = [] + percents: list[float] = [] for point, data in coverage.items(): if point in points: - percents.append(data.get(COVERAGE_PERCENTAGES_KEY, 0)) - elif data.get(COVERAGE_PERCENTAGES_KEY, 0): + percents.append(data.get(CoverageKey.PERCENTAGES, 0)) + elif data.get(CoverageKey.PERCENTAGES, 0): percents.append( - cls._calculate(data.get(COVERAGE_POINTS_KEY, {}), points)) + cls._calculate(data.get(CoverageKey.POINTS, {}), points)) else: percents.append(0) return mean(percents) - def get_coverage(self, points: Points, - to_percents: bool) -> Coverage: + def get_coverage(self, points: Points) -> Coverage: """ Accepts a dict where keys are Standard instances and values are sets of points within this standard. Returns a dict of standard instances to calculated coverages :param points: - :param to_percents: indicates to parse coverage results as percents: [0 - 100] :return: """ @@ -81,8 +77,6 @@ def get_coverage(self, points: Points, continue standard_coverage = self._calculate( self._coverages_value(standard_params), standard_points) - if to_percents: - standard_coverage = round(standard_coverage * 100, 2) coverages[standard] = standard_coverage return coverages @@ -94,10 +88,10 @@ def _coverages_value(d: dict): """ keys = list(d) if len(keys) == 0 or \ - (len(keys) == 1 and keys[0] == COVERAGE_PERCENTAGES_KEY): + (len(keys) == 1 and keys[0] == CoverageKey.PERCENTAGES): return {} for key in keys: - if key != COVERAGE_PERCENTAGES_KEY: + if key != CoverageKey.PERCENTAGES: return d[key] @@ -105,89 +99,45 @@ class CoverageService: def __init__(self, mappings_collector: LazyLoadedMappingsCollector): self._mappings_collector = mappings_collector - @staticmethod - def _load_if_stream(obj: Union[io.IOBase, Dict]) -> dict: - if isinstance(obj, io.IOBase): - _LOG.debug('The given object is stream. Loading to json') - return json.load(obj) - return obj - - def derive_points_from_detailed_report( - self, detailed_report: Union[io.IOBase, Dict], - regions: Optional[List[str]] = None) -> RegionPoints: - """ - Derives points from detailed_report format into a dict with - the following format: - {'region': {Standard: {'point1', 'point2', 'point3'}}} - :param detailed_report: Union[io.IOBase, Dict] - :param regions: Optional[List[str]], denotes regions to derive for, - given None - assumes to calculate for any region. - :return: Points - """ - _LOG.info('Deriving points from detailed_report') - detailed_report = self._load_if_stream(detailed_report) - points = {} - for region, region_policies in detailed_report.items(): - if region != MULTIREGION and regions and region not in regions: - continue - - points.setdefault(region, {}) - for policy in region_policies: - if policy.get('resources'): - continue - name = policy.get('policy', {}).get('name') - policy_standards = Standard.deserialize( - self._mappings_collector.standard.get(name) or {} - ) - for standard in policy_standards: - points[region].setdefault( - standard, set() - ).update(standard.points) - return points - - def derive_points_from_findings(self, findings: Dict, - regions: Optional[List[str]] = None - ) -> RegionPoints: + def points_from_collection(self, collection: ShardsCollection + ) -> RegionPoints: """ - Derives points from findings format into a dict with - the following format: + Derives points from shards collection to the following format: {'region': {Standard: {'point1', 'point2', 'point3'}}} - :param findings: Dict - :param regions: Optional[List[str]], denotes regions to derive for, - given None - assumes to calculate for any region. + :param collection: :return: Points """ - _LOG.info('Loading points from findings') + remapped = {} # policy to its parts + for part in collection.iter_parts(): + remapped.setdefault(part.policy, []).append(part) points = {} - for name, policy_data in findings.items(): - for region, resources in policy_data.get('resources', {}).items(): - if resources: - continue - if regions and region != MULTIREGION and region not in regions: - continue - points.setdefault(region, {}) - policy_standards = Standard.deserialize( - self._mappings_collector.standard.get(name) or {} - ) - for standard in policy_standards: - points[region].setdefault(standard, set()).update( - standard.points) + for policy, parts in remapped.items(): + standards = Standard.deserialize( + self._mappings_collector.standard.get(policy) or {} + ) + for part in parts: + points.setdefault(part.location, {}) + for standard in standards: + points[part.location].setdefault(standard, set()) + if not part.resources: + points[part.location][standard].update(standard.points) return points - def standards_coverage(self, cloud: str) -> dict: - cloud = adjust_cloud(cloud) - if cloud == AWS_CLOUD_ATTR: - return self._mappings_collector.aws_standards_coverage - if cloud == AZURE_CLOUD_ATTR: - return self._mappings_collector.azure_standards_coverage - if cloud == GCP_CLOUD_ATTR: - return self._mappings_collector.google_standards_coverage - raise AssertionError(f'Not available cloud: {cloud}') + def standards_coverage(self, cloud: Cloud) -> dict: + match cloud: + case Cloud.AWS: + return self._mappings_collector.aws_standards_coverage + case Cloud.AZURE: + return self._mappings_collector.azure_standards_coverage + case Cloud.GOOGLE: + return self._mappings_collector.google_standards_coverage + case _: + return {} @staticmethod - def distribute_multiregion(points: RegionPoints) -> RegionPoints: + def distribute_global(points: RegionPoints) -> RegionPoints: """ - Updates each region's points with multi-region's points. + Updates each region's points with global points. The method updates the given object and returns it as well, (just to keep +- similar interface) :param points: @@ -195,7 +145,7 @@ def distribute_multiregion(points: RegionPoints) -> RegionPoints: """ if len(points) == 1: # only one region in dict return points - multi_region = points.pop(MULTIREGION, None) + multi_region = points.pop(GLOBAL_REGION, None) if not multi_region: return points for region, region_result in points.items(): @@ -204,27 +154,43 @@ def distribute_multiregion(points: RegionPoints) -> RegionPoints: return points @staticmethod - def congest_to_multiregion(points: RegionPoints) -> RegionPoints: + def congest_to_global(points: RegionPoints) -> RegionPoints: """ - Merges all the points to multi-region + Merges all the points to global ones :param points: :return: """ - standards: Dict[Standard, Set] = {} + standards = {} for region_data in points.values(): for standard, points in region_data.items(): standards.setdefault(standard, set()).update(points) - return {MULTIREGION: standards} - - def calculate_region_coverages(self, points: RegionPoints, cloud: str, - to_percents: bool = True) -> RegionCoverage: - calculator = CoverageCalculator(self.standards_coverage(cloud)) - result = {} - for region, region_points in points.items(): - result[region] = { - st.full_name: cov - for st, cov in calculator.get_coverage( - points=region_points, - to_percents=to_percents).items() - } - return result + return {GLOBAL_REGION: standards} + + @staticmethod + def format_coverage(coverage: Coverage) -> dict[str, float]: + """ + Replaces Standard instances with strings, transforms 0-1 to percents + """ + return {k.full_name: round(v * 100, 2) for k, v in coverage.items()} + + def calculate_region_coverages(self, points: RegionPoints, cloud: Cloud, + ) -> dict[str, Coverage]: + calc = CoverageCalculator(self.standards_coverage(cloud)) + return { + region: calc.get_coverage(region_points) + for region, region_points in points.items() + } + + def format_region_coverages(self, coverages: dict[str, Coverage] + ) -> dict[str, float]: + return {k: self.format_coverage(v) for k, v in coverages.items()} + + def coverage_from_collection(self, collection: ShardsCollection, + cloud: Cloud) -> dict: + points = self.points_from_collection(collection) + if cloud == Cloud.AWS: + points = self.distribute_global(points) + if cloud == Cloud.AZURE: + points = self.congest_to_global(points) + coverages = self.calculate_region_coverages(points, cloud) + return self.format_region_coverages(coverages) diff --git a/src/services/credentials_manager_service.py b/src/services/credentials_manager_service.py deleted file mode 100644 index d78b09193..000000000 --- a/src/services/credentials_manager_service.py +++ /dev/null @@ -1,158 +0,0 @@ -from typing import Optional, List - -from pynamodb.exceptions import DoesNotExist -from pynamodb.expressions.condition import Condition -from pynamodb.indexes import GlobalSecondaryIndex - -from helpers.constants import CLOUD_IDENTIFIER_ATTR, TRUSTED_ROLE_ARN, ENABLED, \ - CLOUD_ATTR -from helpers.log_helper import get_logger -from models.credentials_manager import CredentialsManager - -_ATTRS_TO_GET = {CLOUD_IDENTIFIER_ATTR, TRUSTED_ROLE_ARN, ENABLED, CLOUD_ATTR} - -MA_SSM_CLIENT_ID, MA_DYNAMODB_CLIENT_ID = 'client_id', 'clientId' -MA_SSM_API_KEY = 'api_key' -MA_SSM_TENANT_ID, MA_DYNAMODB_TENANT_ID = 'tenant_id', 'tenantId' - -MA_SSM_ACCESS_KEY_ID = 'accessKeyId' -MA_SSM_SECRET_ACCESS_KEY = 'secretAccessKey' -MA_SSM_SESSION_TOKEN = 'sessionToken' # not sure, it's a guess -MA_SSM_DEFAULT_REGION = 'defaultRegion' -MA_DYNAMODB_ROLE_NAME = 'roleName' -MA_DYNAMODB_ACCOUNT_NUMBER = 'accountNumber' - -MA_AWS, MA_AZURE, MA_GOOGLE = 'AWS', 'AZURE', 'GOOGLE' - -_LOG = get_logger(__name__) - - -class CredentialsManagerService: - """ - Manage Credentials Manager - """ - - @staticmethod - def get_credentials_configuration(cloud_identifier: str, cloud: str - ) -> Optional[CredentialsManager]: - return CredentialsManager.get_nullable(hash_key=cloud_identifier, - range_key=cloud.lower()) - - @classmethod - def inquire( - cls, - cloud: Optional[str] = None, - cloud_identifier: Optional[str] = None, - customer: Optional[str] = None, tenants: Optional[List[str]] = None - ): - if cloud: - cloud = cloud.lower() - index_payload = dict() - - if customer: - index_payload.update( - index=CredentialsManager.customer_cloud_identifier_index, - hash_key=customer - ) - - if tenants is not None: - - if len(tenants) > 1 and customer: - - fc = None - for _tenant in tenants: - _fc = CredentialsManager.tenant == _tenant - fc = (fc | _fc) if fc is not None else _fc - - # Inherits the customer-based payload. - index_payload.update(filter_condition=fc) - - elif len(tenants) == 1: - tenant = tenants[0] - index_payload.update( - index=CredentialsManager.tenant_cloud_identifier_index, - hash_key=tenant - ) - - # Otherwise, query is not tenant/customer biased. - - # Establish a default output. - output = iter([]) - - if index_payload: - index_payload.update(cloud=cloud, cid=cloud_identifier) - output = cls._index_inquery(**index_payload) - - elif cloud and cloud_identifier: - entity = cls.get_credentials_configuration( - cloud=cloud, cloud_identifier=cloud_identifier - ) - if entity: - output = iter([entity]) - - # todo swap hash(cloud) and sort(cid) keys. - elif cloud: - fc = CredentialsManager.cloud == cloud - output = CredentialsManager.scan(filter_condition=fc) - - elif cloud_identifier: - output = CredentialsManager.query(hash_key=cloud_identifier) - else: - output = CredentialsManager.scan() - - return output - - @staticmethod - def _index_inquery( - index: GlobalSecondaryIndex, hash_key: str, - cloud: Optional[str] = None, cid: Optional[str] = None, - filter_condition: Optional[Condition] = None - ): - # Note: consider using a compound attr as sort-key, i.e. '$cloud#$cid'. - if cloud: - cloud = cloud.lower() - fc = filter_condition - if cid: - fc = fc & (CredentialsManager.cloud_identifier == cid) - - rkc = CredentialsManager.cloud == cloud if cloud else None - return index.query( - hash_key=hash_key, - range_key_condition=rkc, - filter_condition=fc - ) - - @staticmethod - def save(credentials_manager: CredentialsManager): - credentials_manager.save() - - @staticmethod - def create_credentials_configuration( - configuration_data: dict) -> CredentialsManager: - credentials_manager = CredentialsManager(**configuration_data) - return credentials_manager - - @staticmethod - def credentials_configuration_exists(cloud: str, - cloud_identifier: str) -> bool: - try: - CredentialsManager.get( - hash_key=cloud_identifier, - range_key=cloud.lower(), - attributes_to_get=[CredentialsManager.cloud_identifier] - # no use since we pay the same as when query the whole obj. God bless Dynamodb - ) - return True - except DoesNotExist: - return False - - @staticmethod - def remove_entity(credentials_manager: CredentialsManager): - credentials_manager.delete() - - @staticmethod - def get_credentials_manager_dto( - credentials_manager: CredentialsManager - ): - data = credentials_manager.get_json() - return {k: v for k, v in data.items() if k in _ATTRS_TO_GET} diff --git a/src/services/defect_dojo_service.py b/src/services/defect_dojo_service.py new file mode 100644 index 000000000..0475c0b3e --- /dev/null +++ b/src/services/defect_dojo_service.py @@ -0,0 +1,267 @@ +from typing import Any, Iterable, Iterator, Literal +from typing_extensions import Self + +from modular_sdk.commons.constants import ApplicationType +from modular_sdk.models.application import Application +from modular_sdk.models.parent import Parent +from modular_sdk.services.application_service import ApplicationService +from modular_sdk.services.impl.maestro_credentials_service import ( + DefectDojoApplicationMeta, + DefectDojoApplicationSecret, +) +from modular_sdk.services.ssm_service import SSMService + +from helpers.log_helper import get_logger +from services.ambiguous_job_service import AmbiguousJob +from services.base_data_service import BaseDataService +from services.platform_service import Platform + +_LOG = get_logger(__name__) + + +class DefectDojoConfiguration: + __slots__ = ('_app', '_meta') + + def __init__(self, app: Application): + self._app = app + self._meta = DefectDojoApplicationMeta.from_dict(app.meta.as_dict()) + + @property + def application(self) -> Application: + """ + Meta will be set when you request the application + :return: + """ + self._app.meta = self._meta.dict() + return self._app + + @property + def id(self) -> str: + return self._app.application_id + + @property + def customer(self) -> str: + return self._app.customer_id + + @property + def description(self) -> str: + return self._app.description + + @description.setter + def description(self, value: str): + self._app.description = value + + @property + def host(self) -> str | None: + return self._meta.host + + @property + def stage(self) -> str | None: + return self._meta.stage + + @property + def port(self) -> int | None: + return self._meta.port + + @property + def protocol(self) -> Literal['HTTP', 'HTTPS'] | None: + return self._meta.protocol + + @property + def url(self) -> str: + return self._meta.url + + def update_host(self, host: str | None, port: int | None, + protocol: str | None, stage: str | None): + self._meta.update_host( + host=host, + port=port, + protocol=protocol, + stage=stage + ) + + @property + def secret_name(self) -> str | None: + return self._app.secret + + @secret_name.setter + def secret_name(self, value: str | None): + self._app.secret = value + + +class DefectDojoParentMeta: + """ + Defect Dojo configuration for specific set of tenants + """ + + class SkipKeyErrorDict(dict): + def __missing__(self, key): + return '{' + key + '}' + + __slots__ = ('scan_type', 'product_type', 'product', 'engagement', + 'test', 'send_after_job', 'attachment') + + def __init__(self, scan_type: str, product_type: str, product: str, + engagement: str, test: str, send_after_job: bool, + attachment: Literal['json', 'xlsx', 'csv'] | None = None): + self.scan_type = scan_type + self.product_type = product_type + self.product = product + self.engagement = engagement + self.test = test + self.send_after_job = send_after_job + self.attachment = attachment + + def dto(self) -> dict: + """ + Human-readable dict + :return: + """ + return {k: getattr(self, k) for k in self.__slots__} + + def dict(self) -> dict: + """ + Dict that is stored to DB + :return: + """ + return { + 'st': self.scan_type, + 'pt': self.product_type, + 'p': self.product, + 'e': self.engagement, + 't': self.test, + 'saj': self.send_after_job, + 'at': self.attachment + } + + @classmethod + def from_dict(cls, dct: dict) -> Self: + return cls( + scan_type=dct['st'], + product_type=dct['pt'], + product=dct['p'], + engagement=dct['e'], + test=dct['t'], + send_after_job=dct.get('saj') or False, + attachment=dct.get('at') + ) + + @classmethod + def from_parent(cls, parent: Parent): + return cls.from_dict(parent.meta.as_dict()) + + def substitute_fields(self, job: AmbiguousJob, + platform: Platform | None = None + ) -> 'DefectDojoParentMeta': + """ + Changes this dict in place. + Available keys: + - tenant_name (works also for platform name if the job is platform) + - customer_name + - job_id + :param job: + :param platform: + :return: + """ + tenant_name = job.tenant_name + if job.is_platform_job and platform: + tenant_name = platform.name + dct = DefectDojoParentMeta.SkipKeyErrorDict( + tenant_name=tenant_name, + job_id=job.id, + customer_name=job.customer_name + ) + + return DefectDojoParentMeta( + scan_type=self.scan_type, + product_type=self.product_type.format_map(dct), + product=self.product.format_map(dct), + engagement=self.engagement.format_map(dct), + test=self.test.format_map(dct), + send_after_job=self.send_after_job, + attachment=self.attachment + ) + + +class DefectDojoService(BaseDataService[DefectDojoConfiguration]): + def __init__(self, application_service: ApplicationService, + ssm_service: SSMService): + super().__init__() + self._aps = application_service + self._ssm = ssm_service + + @staticmethod + def to_dojos(it: Iterable[Application] + ) -> Iterator[DefectDojoConfiguration]: + return map(DefectDojoConfiguration, it) + + def dto(self, item: DefectDojoConfiguration) -> dict[str, Any]: + return { + 'id': item.application.application_id, + 'description': item.description, + 'host': item.host, + 'port': item.port, + 'stage': item.stage, + 'protocol': item.protocol + } + + def create(self, customer: str, description: str, + created_by: str) -> DefectDojoConfiguration: + app = self._aps.build( + customer_id=customer, + description=description, + type=ApplicationType.DEFECT_DOJO.value, + created_by=created_by, + is_deleted=False, + meta={}, + ) + return DefectDojoConfiguration(app) + + def get_nullable(self, id: str) -> DefectDojoConfiguration | None: + app = self._aps.get_application_by_id(id) + if not app: + return + return DefectDojoConfiguration(app) + + def save(self, item: DefectDojoConfiguration): + self._aps.save(item.application) + + def delete(self, item: DefectDojoConfiguration): + if item.secret_name: + self._ssm.delete_parameter(item.secret_name) + self._aps.force_delete(item.application) + + def set_dojo_api_key(self, dojo: DefectDojoConfiguration, api_key: str): + """ + Modifies the incoming application with secret + :param dojo: + :param api_key: + """ + secret_name = dojo.secret_name + if not secret_name: + secret_name = self._ssm.safe_name( + name=dojo.customer, + prefix='m3.custodian.dojo', + ) + _LOG.debug('Saving dojo api key to SSM') + secret = self._ssm.put_parameter( + name=secret_name, + value=DefectDojoApplicationSecret(api_key=api_key).dict() + ) + if not secret: + _LOG.warning('Something went wrong trying to save api key ' + 'to ssm. Keeping application.secret empty') + else: + _LOG.debug('Dojo api key was saved to SSM') + dojo.secret_name = secret + + def get_api_key(self, dojo: DefectDojoConfiguration) -> str: + value = self._ssm.get_parameter(dojo.secret_name) + return DefectDojoApplicationSecret.from_dict(value).api_key + + def batch_delete(self, items: Iterable[DefectDojoConfiguration]): + raise NotImplementedError() + + def batch_save(self, items: Iterable[DefectDojoConfiguration]): + raise NotImplementedError() + diff --git a/src/services/environment_service.py b/src/services/environment_service.py index 6857fb7e2..0f648871e 100644 --- a/src/services/environment_service.py +++ b/src/services/environment_service.py @@ -1,35 +1,42 @@ import os import re -import tempfile -from typing import Optional - -from helpers.constants import ENV_SERVICE_MODE, DOCKER_SERVICE_MODE, \ - ENV_VAR_REGION, TESTING_MODE_ENV, TESTING_MODE_ENV_TRUE, \ - ENV_VAR_JOBS_TIME_TO_LIVE_DAYS, ENV_NUMBER_OF_EVENTS_IN_EVENT_ITEM, \ - DEFAULT_NUMBER_OF_EVENTS_IN_EVENT_ITEM, ENV_VAR_EVENTS_TTL, \ - EVENT_STATISTICS_TYPE_VERBOSE, COMPONENT_NAME_ATTR, \ - EVENT_STATISTICS_TYPE_SHORT, ENV_EVENT_STATISTICS_TYPE, \ - AZURE_CLOUD_ATTR, ENV_API_GATEWAY_HOST, ENV_API_GATEWAY_STAGE, \ - AWS_CLOUD_ATTR, GOOGLE_CLOUD_ATTR, DEFAULT_STATISTICS_BUCKET_NAME, \ - DEFAULT_REPORTS_BUCKET_NAME, DEFAULT_RULESETS_BUCKET_NAME, \ - DEFAULT_SSM_BACKUP_BUCKET_NAME, DEFAULT_TEMPLATES_BUCKET_NAME, \ - DEFAULT_METRICS_BUCKET_NAME, ENV_ALLOW_SIMULTANEOUS_JOBS_FOR_ONE_TENANT, \ - DEFAULT_EVENTS_TTL_HOURS, ENV_VAR_NUMBER_OF_PARTITIONS_FOR_EVENTS, \ - DEFAULT_NUMBER_OF_PARTITIONS_FOR_EVENTS, \ - DEFAULT_RECOMMENDATION_BUCKET_NAME, DEFAULT_INNER_CACHE_TTL_SECONDS, \ - ENV_INNER_CACHE_TTL_SECONDS - -ALLOWED_CLOUDS = {AWS_CLOUD_ATTR, AZURE_CLOUD_ATTR, GOOGLE_CLOUD_ATTR} - -ENV_TRUE = {'1', 'true', 'yes', 'y'} +from typing import Mapping + +from helpers.constants import ( + CAASEnv, + DEFAULT_EVENTS_TTL_HOURS, + DEFAULT_INNER_CACHE_TTL_SECONDS, + DEFAULT_LM_TOKEN_LIFETIME_MINUTES, + DEFAULT_METRICS_BUCKET_NAME, + DEFAULT_NUMBER_OF_EVENTS_IN_EVENT_ITEM, + DEFAULT_NUMBER_OF_PARTITIONS_FOR_EVENTS, + DEFAULT_RECOMMENDATION_BUCKET_NAME, + DEFAULT_REPORTS_BUCKET_NAME, + DEFAULT_RULESETS_BUCKET_NAME, + DEFAULT_STATISTICS_BUCKET_NAME, + DOCKER_SERVICE_MODE, + ENV_TRUE, +) class EnvironmentService: - def __init__(self): self._environment = os.environ - def override_environment(self, environs: dict) -> None: + def ensure_env(self, env_name: str) -> str: + env = self._environment.get(env_name) + if not env: + raise RuntimeError( + f'Environment variable {env_name} is required for ' + f'service to work properly' + ) + return env + + @property + def environment(self): + return self._environment + + def override_environment(self, environs: Mapping) -> None: self._environment.update(environs) def aws_region(self) -> str: @@ -37,53 +44,50 @@ def aws_region(self) -> str: caas-api-handler, caas-event-handler to build envs for jobs. All the lambdas to init connections to clients. """ - return self._environment.get(ENV_VAR_REGION) + return self._environment.get(CAASEnv.AWS_REGION) - def temp_folder_path(self): + def system_customer(self) -> str | None: """ - This env var used to store temp files. - Lambdas: - caas-rule-meta-updater - caas-ruleset-compiler + Currently used only for event-driven scans in order to retrieve + ED system rulesets. """ - return self._environment.get( - 'TEMP_FOLDER_PATH') or tempfile.gettempdir() + return self._environment.get(CAASEnv.SYSTEM_CUSTOMER_NAME) - def default_reports_bucket_name(self): + def default_reports_bucket_name(self) -> str: """ Lambdas: - caas-event-handler - caas-api-handler - caas-report-generator """ - return self._environment.get('reports_bucket_name') or \ - DEFAULT_REPORTS_BUCKET_NAME + return (self._environment.get(CAASEnv.REPORTS_BUCKET_NAME) or + DEFAULT_REPORTS_BUCKET_NAME) - def batch_job_log_level(self): + def batch_job_log_level(self) -> str: """ Lambdas: caas-api-handler caas-event-handler """ - return self._environment.get('batch_job_log_level') or 'DEBUG' + return self._environment.get(CAASEnv.BATCH_JOB_LOG_LEVEL) or 'DEBUG' - def get_batch_job_queue(self): + def get_batch_job_queue(self) -> str | None: """ Lambdas: caas-api-handler caas-event-handler """ - return self._environment.get('batch_job_queue_name') + return self._environment.get(CAASEnv.BATCH_JOB_QUEUE_NAME) - def get_batch_job_def(self): + def get_batch_job_def(self) -> str | None: """ Lambdas: caas-api-handler caas-event-handler """ - return self._environment.get('batch_job_def_name') + return self._environment.get(CAASEnv.BATCH_JOB_DEF_NAME) - def get_rulesets_bucket_name(self): + def get_rulesets_bucket_name(self) -> str: """ Lambdas: caas-api-handler @@ -94,32 +98,8 @@ def get_rulesets_bucket_name(self): caas-report-generator caas-ruleset-compiler """ - return self._environment.get('caas_rulesets_bucket') or \ - DEFAULT_RULESETS_BUCKET_NAME - - def get_ssm_backup_bucket(self): - """ - Lambdas: - caas-configuration-updater - caas-configuration-backupper - """ - return self._environment.get('caas_ssm_backup_bucket') or \ - DEFAULT_SSM_BACKUP_BUCKET_NAME - - def get_ssm_backup_kms_key_id(self): - """ - Lambdas: - caas-configuration-updater - caas-configuration-backupper - """ - return self._environment.get('caas_ssm_backup_kms_key_id') - - def get_templates_bucket_name(self): - """ - - """ - return self._environment.get('templates_s3_bucket_name') or \ - DEFAULT_TEMPLATES_BUCKET_NAME + return (self._environment.get(CAASEnv.RULESETS_BUCKET_NAME) or + DEFAULT_RULESETS_BUCKET_NAME) def get_metrics_bucket_name(self) -> str: """ @@ -127,19 +107,19 @@ def get_metrics_bucket_name(self) -> str: caas-metrics-updater caas-report-generator-handler """ - return self._environment.get('metrics_bucket_name') or \ - DEFAULT_METRICS_BUCKET_NAME + return (self._environment.get(CAASEnv.METRICS_BUCKET_NAME) or + DEFAULT_METRICS_BUCKET_NAME) - def get_user_pool_name(self): + def get_user_pool_name(self) -> str | None: """ Api lambdas: caas-api-handler caas-configuration-api-handler caas-report-generator """ - return self._environment.get('caas_user_pool_name') + return self._environment.get(CAASEnv.USER_POOL_NAME) - def get_user_pool_id(self): + def get_user_pool_id(self) -> str | None: """ It's optional but is preferred to use this instead of user_pool_name Api lambdas: @@ -147,142 +127,103 @@ def get_user_pool_id(self): caas-configuration-api-handler caas-report-generator """ - return self._environment.get('caas_user_pool_id') - - def get_last_scan_threshold(self) -> int: - """ - Threshold in seconds - caas-api-handler - """ - from_env = str(self._environment.get('last_scan_threshold')) - if from_env.isdigit(): - return int(from_env) - return 0 + return self._environment.get(CAASEnv.USER_POOL_ID) def get_job_lifetime_min(self) -> str: - return self._environment.get('job_lifetime_min') or '120' - - def get_statistics_bucket_name(self): - return self._environment.get('stats_s3_bucket_name') or \ - DEFAULT_STATISTICS_BUCKET_NAME - - def allowed_clouds_to_scan(self) -> set: - """ - Filter jobs for clouds. - Clouds names must be separated by commas in env. - """ - # "None" - env = str(self._environment.get('feature_filter_jobs_request')) - clouds = {cl.upper() for cl in env.split(',')} & ALLOWED_CLOUDS - if not clouds: - return ALLOWED_CLOUDS - return clouds - - def get_image_folder_url(self): - return self._environment.get('image_folder_url') - - def get_feature_update_ccc_version(self) -> bool: - """ - caas-api-handler - """ - return str(self._environment.get( - 'feature_update_ccc_version')).lower() in ENV_TRUE + return (self._environment.get(CAASEnv.BATCH_JOB_LIFETIME_MINUTES) or + '120') - def get_feature_allow_only_temp_aws_credentials(self): - value = str( - self._environment.get('feature_allow_only_temp_aws_credentials')) - return value.strip().lower() in ENV_TRUE + def get_statistics_bucket_name(self) -> str: + return (self._environment.get(CAASEnv.STATISTICS_BUCKET_NAME) or + DEFAULT_STATISTICS_BUCKET_NAME) def skip_cloud_identifier_validation(self) -> bool: """ caas-api-handler """ from_env = str( - self._environment.get('feature_skip_cloud_identifier_validation')) + self._environment.get(CAASEnv.SKIP_CLOUD_IDENTIFIER_VALIDATION)) return from_env.lower() in ENV_TRUE def is_docker(self) -> bool: - return self._environment.get(ENV_SERVICE_MODE) == DOCKER_SERVICE_MODE + return (self._environment.get(CAASEnv.SERVICE_MODE) == + DOCKER_SERVICE_MODE) - def event_bridge_service_role(self): - return self._environment.get( - 'event_bridge_service_role_to_invoke_batch') + def event_bridge_service_role(self) -> str | None: + return self._environment.get(CAASEnv.EB_SERVICE_ROLE_TO_INVOKE_BATCH) - def lambdas_alias_name(self) -> Optional[str]: + def lambdas_alias_name(self) -> str | None: """ To be able to trigger the valid lambda :return: """ - return self._environment.get('lambdas_alias_name') + return self._environment.get(CAASEnv.LAMBDA_ALIAS_NAME) - def account_id(self) -> Optional[str]: - maybe_id = self._environment.get('account_id') or '' - res = re.search(r'\d{12}', maybe_id) - return res.group() if res else None + def account_id(self) -> str | None: + # resolved from lambda context + return self._environment.get(CAASEnv.ACCOUNT_ID) def is_testing(self) -> bool: - return self._environment.get(TESTING_MODE_ENV) == TESTING_MODE_ENV_TRUE - - def jobs_time_to_live_days(self) -> Optional[int]: - """ + return (str(self._environment.get(CAASEnv.TESTING_MODE)).lower() in + ENV_TRUE) + + def mock_rabbitmq_s3_url(self) -> tuple[str, float] | None: + data = self._environment.get(CAASEnv.MOCKED_RABBIT_MQ_S3) + if not data: + return + url, rate = data.split(',') + return url, float(rate) + + def jobs_time_to_live_days(self) -> int | None: + """live_days Lambdas: - caas-api-handler """ - from_env = str(self._environment.get(ENV_VAR_JOBS_TIME_TO_LIVE_DAYS)) + from_env = str(self._environment.get(CAASEnv.JOBS_TIME_TO_LIVE_DAYS)) if from_env.isdigit(): return int(from_env) return - def events_ttl_hours(self) -> Optional[int]: + def events_ttl_hours(self) -> int | None: """ Lambdas: - caas-api-handler """ - from_env = self._environment.get(ENV_VAR_EVENTS_TTL) + from_env = self._environment.get(CAASEnv.EVENTS_TTL_HOURS) if from_env: return int(from_env) return DEFAULT_EVENTS_TTL_HOURS - def event_assembler_pull_item_limit(self): + def event_assembler_pull_item_limit(self) -> int: """ Lambdas: - caas-event-handler """ - return self._environment.get('event_assembler_pull_item_limit') or 100 + env = self._environment.get( + CAASEnv.EVENT_ASSEMBLER_PULL_EVENTS_PAGE_SIZE) + if env: + return int(env) + return 100 def number_of_native_events_in_event_item(self) -> int: """ Lambdas: - caas-api-handler """ - from_env = self._environment.get(ENV_NUMBER_OF_EVENTS_IN_EVENT_ITEM) + from_env = self._environment.get(CAASEnv.NATIVE_EVENTS_PER_ITEM) if from_env: return int(from_env) return DEFAULT_NUMBER_OF_EVENTS_IN_EVENT_ITEM - def event_statistics_type(self) -> str: - """ - Lambdas: - caas-event-handler - """ - return self._environment.get( - ENV_EVENT_STATISTICS_TYPE) or EVENT_STATISTICS_TYPE_VERBOSE - - def component_name(self) -> str: - return self._environment.get(COMPONENT_NAME_ATTR) - - def is_event_statistics_verbose(self) -> bool: - return self.event_statistics_type() == EVENT_STATISTICS_TYPE_SHORT + def api_gateway_host(self) -> str | None: + return self._environment.get(CAASEnv.API_GATEWAY_HOST) - def api_gateway_host(self) -> Optional[str]: - return self._environment.get(ENV_API_GATEWAY_HOST) + def api_gateway_stage(self) -> str | None: + return self._environment.get(CAASEnv.API_GATEWAY_STAGE) - def api_gateway_stage(self) -> Optional[str]: - return self._environment.get(ENV_API_GATEWAY_STAGE) - - def get_recommendation_bucket(self) -> Optional[str]: - return self._environment.get('caas_recommendations_bucket') or \ - DEFAULT_RECOMMENDATION_BUCKET_NAME + def get_recommendation_bucket(self) -> str | None: + return (self._environment.get(CAASEnv.RECOMMENDATIONS_BUCKET_NAME) or + DEFAULT_RECOMMENDATION_BUCKET_NAME) def allow_simultaneous_jobs_for_one_tenant(self) -> bool: """ @@ -291,7 +232,8 @@ def allow_simultaneous_jobs_for_one_tenant(self) -> bool: :return: """ return str( - self._environment.get(ENV_ALLOW_SIMULTANEOUS_JOBS_FOR_ONE_TENANT) + self._environment.get( + CAASEnv.ALLOW_SIMULTANEOUS_JOBS_FOR_ONE_TENANT) ).lower() in ENV_TRUE def number_of_partitions_for_events(self) -> int: @@ -304,7 +246,7 @@ def number_of_partitions_for_events(self) -> int: :return: """ from_env = self._environment.get( - ENV_VAR_NUMBER_OF_PARTITIONS_FOR_EVENTS) + CAASEnv.NUMBER_OF_PARTITIONS_FOR_EVENTS) if from_env: return int(from_env) return DEFAULT_NUMBER_OF_PARTITIONS_FOR_EVENTS @@ -314,7 +256,20 @@ def inner_cache_ttl_seconds(self) -> int: Used for time to live cache :return: """ - from_env = str(self._environment.get(ENV_INNER_CACHE_TTL_SECONDS)) + from_env = str(self._environment.get(CAASEnv.INNER_CACHE_TTL_SECONDS)) if from_env.isdigit(): return int(from_env) return DEFAULT_INNER_CACHE_TTL_SECONDS + + def lm_token_lifetime_minutes(self): + try: + return int(self._environment.get( + CAASEnv.LM_TOKEN_LIFETIME_MINUTES)) + except (TypeError, ValueError): + return DEFAULT_LM_TOKEN_LIFETIME_MINUTES + + def allow_disabled_permissions(self) -> bool: + env = str(self._environment.get( + CAASEnv.ALLOW_DISABLED_PERMISSIONS_FOR_STANDARD_USERS + )) + return env.lower() in ENV_TRUE diff --git a/src/services/event_processor_service.py b/src/services/event_processor_service.py index 5e9708eb6..af974813e 100644 --- a/src/services/event_processor_service.py +++ b/src/services/event_processor_service.py @@ -2,21 +2,17 @@ from abc import ABC from functools import cached_property from typing import Optional, Dict, Iterator, List, Generator, Tuple, Set, \ - Union, Iterable + Iterable from helpers import deep_get, deep_set from helpers.constants import AWS_VENDOR, MAESTRO_VENDOR, AZURE_CLOUD_ATTR, \ - AZURE_ULTIMATE_REGION, GOOGLE_CLOUD_ATTR, GOOGLE_ULTIMATE_REGION + S3SettingKey, GOOGLE_CLOUD_ATTR, GLOBAL_REGION from helpers.log_helper import get_logger from services.clients.sts import StsClient from services.environment_service import EnvironmentService -from services.rule_meta_service import LazyLoadedMappingsCollector +from services.mappings_collector import LazyLoadedMappingsCollector from services.s3_settings_service import S3SettingsService, \ - S3_KEY_EVENT_BRIDGE_EVENT_SOURCE_TO_RULES_MAPPING, \ - S3_KEY_MAESTRO_SUBGROUP_ACTION_TO_AZURE_EVENTS_MAPPING, \ - S3SettingsServiceLocalWrapper, \ - S3_KEY_MAESTRO_SUBGROUP_ACTION_TO_GOOGLE_EVENTS_MAPPING - + S3SettingsServiceLocalWrapper _LOG = get_logger(__name__) # CloudTrail event @@ -48,18 +44,6 @@ MA_REGION_NAME = 'regionName' MA_REQUEST = 'request' -# EventBridge does not have "Records" but we still use it to keep a list -# of EventBridge events. -RECORDS = CT_RECORDS - -STATISTICS_NUMBER_OF_PROCESSED = 'n_pr' -STATISTICS_NUMBER_OF_SKIPPED = 'n_sk' -STATISTICS_PROCESSED = 'pr' -STATISTICS_SKIPPED = 'sk' -STATISTICS_ACTIVITY = 'a' -STATISTICS_MAPPING = 's' -STATISTICS_UNKNOWN = 'unknown' - # --AWS-- RegionRuleMap = Dict[str, Set[str]] AccountRegionRuleMap = Dict[str, RegionRuleMap] # Account means Tenant.project @@ -69,9 +53,6 @@ CloudTenantRegionRulesMap = Dict[str, Dict[str, Dict[str, Set[str]]]] # --MAESTRO-- - -Stats = Dict[str, Dict[str, Dict[str, Union[int, Dict, List]]]] - DEV = '323549576358' @@ -117,7 +98,6 @@ def __init__(self, s3_settings_service: S3SettingsService, self.mappings_collector = mappings_collector self._events: List[Dict] = [] - self._stats: Stats = {} @property def ct_mapping(self) -> dict: @@ -130,48 +110,11 @@ def eb_mapping(self) -> dict: :return: """ return self.s3_settings_service.get( - S3_KEY_EVENT_BRIDGE_EVENT_SOURCE_TO_RULES_MAPPING + S3SettingKey.EVENT_BRIDGE_EVENT_SOURCE_TO_RULES_MAPPING ) - @property - def stats(self) -> Stats: - return self._stats - - def update_stats(self, account_id: Optional[str] = None, - region: Optional[str] = None, - processed: Optional[List[dict]] = None, - skipped: Optional[List[dict]] = None) -> None: - processed = processed or [] - skipped = skipped or [] - account_id = account_id or STATISTICS_UNKNOWN - region = region or STATISTICS_UNKNOWN - self._stats.setdefault(account_id, {}) - _region_scope = self._stats[account_id].setdefault(region, { - STATISTICS_NUMBER_OF_SKIPPED: 0, STATISTICS_NUMBER_OF_PROCESSED: 0, - STATISTICS_SKIPPED: [], STATISTICS_PROCESSED: {} - }) - _region_scope[STATISTICS_NUMBER_OF_PROCESSED] += len(processed) - _region_scope[STATISTICS_NUMBER_OF_SKIPPED] += len(skipped) - if self.environment_service.is_event_statistics_verbose(): - _region_scope[STATISTICS_SKIPPED].extend(skipped) - - # TODO rethink the code below in order to make a more humane structure ? - for record in processed: - key = None - if EventProcessorService.is_cloudtrail_record(record): - key = f'ct#{record.get(CT_EVENT_SOURCE)}:{record.get(CT_EVENT_NAME)}' - elif EventProcessorService.is_eventbridge_record(record): - if CloudTrail.is_cloudtrail_api_call(record): - key = f'eb:ct#{deep_get(record, (EB_DETAIL, CT_EVENT_SOURCE))}:{deep_get(record, (EB_DETAIL, CT_EVENT_NAME))}' - else: - key = f'eb#{record.get(EB_EVENT_SOURCE)}:{record.get(EB_DETAIL_TYPE)}' - if key: - _region_scope[STATISTICS_PROCESSED].setdefault(key, 0) - _region_scope[STATISTICS_PROCESSED][key] += 1 - def clear(self): self._events = [] - self._stats.clear() @property def events(self) -> List[Dict]: @@ -235,7 +178,6 @@ def without_duplicates(cls, it: Iterable[Dict]) -> Generator[ for i in it: d = cls.digest(i) if d in emitted: - _LOG.warning(f'Omitting the {i} because it`s a duplicate ') continue emitted.add(d) yield i @@ -333,7 +275,7 @@ def azure_mapping(self) -> dict: @property def maestro_azure_mapping(self) -> dict: return self.s3_settings_service.get( - S3_KEY_MAESTRO_SUBGROUP_ACTION_TO_AZURE_EVENTS_MAPPING + S3SettingKey.MAESTRO_SUBGROUP_ACTION_TO_AZURE_EVENTS_MAPPING ) @property @@ -343,7 +285,7 @@ def google_mapping(self) -> dict: @property def maestro_google_mapping(self) -> dict: return self.s3_settings_service.get( - S3_KEY_MAESTRO_SUBGROUP_ACTION_TO_GOOGLE_EVENTS_MAPPING + S3SettingKey.MAESTRO_SUBGROUP_ACTION_TO_GOOGLE_EVENTS_MAPPING ) def cloud_tenant_region_rules( @@ -362,13 +304,12 @@ def cloud_tenant_region_rules( # to process AWS maestro audit events we should remap the # maestro region to native name. For AZURE we can just ignore it if cloud == AZURE_CLOUD_ATTR: - region = AZURE_ULTIMATE_REGION + region = GLOBAL_REGION elif cloud == GOOGLE_CLOUD_ATTR: - region = GOOGLE_ULTIMATE_REGION + region = GLOBAL_REGION else: region = deep_get(event, (MA_REGION_NAME,)) - if not all([cloud, tenant, region]): - _LOG.warning(f'Skipping event: {event}') + if not all((cloud, tenant, region)): continue rules = self.get_rules(event, cloud.upper()) yield cloud.upper(), tenant, region, rules @@ -465,14 +406,10 @@ def account_region_rule_map(self, account_id = self.get_account_id(record=event) region = self.get_region(record=event) rules = self.get_rules(record=event) - if not all([account_id, region, rules]): - self.update_stats(account_id=account_id, region=region, - skipped=[event]) + if not all((account_id, region, rules)): continue _account_scope = ref.setdefault(account_id, {}) _account_scope.setdefault(region, set()).update(rules) - self.update_stats(account_id=account_id, region=region, - processed=[event]) return ref @staticmethod @@ -501,64 +438,3 @@ def get_rules(self, record: dict) -> set: # make, but it would be better to use EB detail-type here source = record.get(EB_EVENT_SOURCE) return set(self.eb_mapping.get(source) or []) - - -class CloudTrailEventProcessor(BaseEventProcessor): - params_to_keep = ( - (CT_REGION,), - (CT_USER_IDENTITY, CT_ACCOUNT_ID), - (CT_EVENT_SOURCE,), - (CT_EVENT_NAME,), - (CT_RESOURCES,) - ) - - def __init__(self, s3_settings_service: S3SettingsService, - environment_service: EnvironmentService, - sts_client: StsClient, - mappings_collector: LazyLoadedMappingsCollector): - super().__init__( - s3_settings_service, - environment_service, - sts_client, - mappings_collector - ) - self.keep_where = { - (CT_EVENT_SOURCE,): set(self.ct_mapping.keys()), - (CT_EVENT_NAME,): { - name for values in self.ct_mapping.values() for name in values - } - } - account_id = self.sts_client.get_account_id() - self.skip_where = { - ('readOnly',): {True, }, - # here we put event sources and names which we cannot process - } - if account_id != DEV: - self.skip_where[(CT_USER_IDENTITY, CT_ACCOUNT_ID)] = {account_id, } - - @property - def account_region_rule_map(self) -> AccountRegionRuleMap: - ref = {} - for record in self.i_records: - _skip_record = False - account_id = CloudTrail.get_account_id(record=record) - if not account_id: - _skip_record = True - - region = CloudTrail.get_region(record=record) - if not region: - _skip_record = True - - rules = CloudTrail.get_rules(record=record, - mapping=self.ct_mapping) - if not rules: - _skip_record = True - if _skip_record: - self.update_stats(account_id=account_id, region=region, - skipped=[record]) - continue - _account_scope = ref.setdefault(account_id, {}) - _account_scope.setdefault(region, set()).update(rules) - self.update_stats(account_id=account_id, region=region, - processed=[record]) - return ref diff --git a/src/services/event_service.py b/src/services/event_service.py index a1d7dd2ac..dbb6682b6 100644 --- a/src/services/event_service.py +++ b/src/services/event_service.py @@ -39,7 +39,7 @@ def get_events(self, partition: int, since: Optional[float] = None, :param till: :return: """ - limit = self._environment_service.event_assembler_pull_item_limit() + page_size = self._environment_service.event_assembler_pull_item_limit() rkc = None if since and till: rkc = Event.timestamp.between(since, till) @@ -47,49 +47,22 @@ def get_events(self, partition: int, since: Optional[float] = None, rkc = (Event.timestamp > since) elif till: rkc = (Event.timestamp <= till) - cursor = Event.query(hash_key=partition, - range_key_condition=rkc, - scan_index_forward=True, - limit=limit) - events = list(cursor) - lek = cursor.last_evaluated_key - while lek: - _LOG.info(f'Going to query for {limit} events more.') - cursor = Event.query( - hash_key=partition, - range_key_condition=rkc, - scan_index_forward=True, - limit=limit, - last_evaluated_key=lek, - ) - events.extend(cursor) - lek = cursor.last_evaluated_key - return events - - @staticmethod - def get_dto(entity: Event): - _json = entity.get_json() - _json.pop('partition', None) - _json.pop('ttl', None) - return _json + return Event.query( + hash_key=partition, + range_key_condition=rkc, + scan_index_forward=True, + page_size=page_size + ) @classmethod def save(cls, event: Event): - try: - event.save() - return True - except (Exception, BaseException) as e: - _LOG.warning(f'{event} could not be persisted, due to: {e}.') - return False + event.save() + return True @classmethod def delete(cls, event: Event): - try: - event.delete() - return True - except (Exception, BaseException) as e: - _LOG.warning(f'{event} could not be removed, due to: {e}.') - return False + event.delete() + return True @classmethod def batch_save(cls, events: Iterable[Event]): diff --git a/src/services/findings_service.py b/src/services/findings_service.py deleted file mode 100644 index b778ba131..000000000 --- a/src/services/findings_service.py +++ /dev/null @@ -1,488 +0,0 @@ -import os -from datetime import timedelta, datetime, date -from json import JSONDecodeError, dumps -from pathlib import PurePosixPath -from re import compile, escape, error, Pattern -from typing import Dict, Union, Callable, Iterator, Type, List, Optional, \ - Generator - -from botocore.exceptions import ClientError - -from helpers.constants import RULE_ID_ATTR, DESCRIPTION_ATTR -from helpers.log_helper import get_logger -from helpers.time_helper import utc_datetime -from services.clients.s3 import S3Client -from services.environment_service import EnvironmentService - -_LOG = get_logger(__name__) - -FINDINGS_KEY = 'findings' -RESOURCE_TYPE_ATTR = 'resourceType' -SEVERITY_ATTR = 'severity' -REGION_ATTR = 'region' -RESOURCES_ATTR = 'resources' - -LIST_TYPE_ATTR = 'list_type' -MAP_TYPE_ATTR = 'map_type' -MAP_KEY_ATTR = 'map_key' - - -class FindingsService: - RULE_ITEM_KEYS: set = {DESCRIPTION_ATTR, RESOURCE_TYPE_ATTR, SEVERITY_ATTR} - FILTER_KEYS: tuple = (RULE_ID_ATTR, REGION_ATTR, - RESOURCE_TYPE_ATTR, SEVERITY_ATTR) - - def __init__(self, environment_service: EnvironmentService, - s3_client: S3Client): - self._environment_service = environment_service - self._s3_client = s3_client - - @property - def _bucket_name(self) -> str: - """ - Retrieves name of Findings bucket, from the settings service. - :return:str - """ - return self._environment_service.get_statistics_bucket_name() - - def get_findings_keys(self, from_: Optional[date] = None, - until: Optional[date] = None, - days: Optional[int] = 365 - ) -> Generator[str, None, None]: - """ - Return an iterator over all the finding's keys: - from <= [keys] < until - If not parameters are specified, a year back is returned - :param from_: - :param until: - :param days: - :return: - """ - if from_ and until: - start, end = from_.isoformat(), until.isoformat() - elif from_: - start = from_.isoformat() - end = (from_ + timedelta(days=days)).isoformat() - elif until: - start = (until - timedelta(days=days)).isoformat() - end = until.isoformat() - else: # nothing - start = (date.today() - timedelta(days=days)).isoformat() - end = date.today().isoformat() - _LOG.debug(f'Retrieving findings keys from {start} until {end}') - start_after = str(PurePosixPath(FINDINGS_KEY, start)) - end_when = str(PurePosixPath(FINDINGS_KEY, end)) - - prefix = str(PurePosixPath(FINDINGS_KEY, - os.path.commonprefix([start, end]))) - keys = self._s3_client.list_dir( - bucket_name=self._bucket_name, - key=prefix, - start_after=start_after, - ) # list_dir performs pagination - - def _check(key: str) -> bool: - """Checks whether the folder is vaid""" - try: - datetime.fromisoformat(key.strip('/').split('/')[1]) - return True - except (ValueError, IndexError): - return False - - for k in filter(_check, keys): - if k >= end_when: # we've reached the latest - break - yield k - - def get_findings_for_period(self, until_date, days, account_id) -> \ - Optional[dict]: - """ - Retrieves last available findings for a specific period - """ - it = self.get_findings_keys( - until=until_date, # not including - days=days - ) - key = self.get_latest_key(it, account_id) - if not key: - return - return self._s3_client.get_json_file_content( - bucket_name=self._environment_service.get_statistics_bucket_name(), - full_file_name=key - ) - - @staticmethod - def get_latest_key(it: Iterator[str], identifier: str) -> Optional[str]: - for key in reversed(list(it)): - _json = key.endswith(f'{identifier}.json.gz') - _gz = key.endswith(f'{identifier}.json') - if _json or _gz: - return key - - def get_latest_findings_key(self, identifier: str) -> Optional[str]: - """ - Findings are updated daily. The structure is: - finding/2023-08-17/.json.gz - finding/2023-08-18/.json.gz - In case today's findings have not been created yet, we must return - the nearest old finding. And here we have a problem: AWS Api does - not allow to list keys in descending order - (https://github.com/boto/boto3/issues/2248) (we could've requested a - list in descending order and limit 1), but we cannot. - Considering that s3 listing api can return up to 1000 keys per request - and since we save folders daily, we can return up to 1000 previous - days using only one request. After that we look for the latest day, - where identifier exists - Also, findings folder can contain some other junk except data - folders. We should skip them - :param identifier: - :return: - """ - return self.get_latest_key(self.get_findings_keys(), identifier) - - def get_findings_content( - self, identifier: str, path: str = '', - findings_date: str = utc_datetime().date().isoformat()) -> Dict: - """ - Retrieves the content of a Findings file, which maintains - the latest, respective vulnerability state, bound to the account. - Given that no Findings state has been previously assigned, could not - be sourced out or simply does not exist, returns an empty Dict. - :parameter identifier: str - :parameter path: str - :parameter findings_date: str - :returns: Dict - """ - _findings: Dict = {} - _name, _key = self._bucket_name, self._get_key( - identifier, FINDINGS_KEY, findings_date, path - ) - if not self._s3_client.file_exists(bucket_name=_name, key=_key): - _LOG.info(f'Findings for the account {identifier} for ' - f'{findings_date} were not found. Looking for older') - _key = self.get_latest_findings_key(identifier) - if not _key: - _LOG.warning(f'Old findings for {identifier} not found') - return _findings - try: - _LOG.debug(f'Pulling Findings state, from \'{_name}\' bucket ' - f'sourced by \'{_key}\' key.') - _findings = self._s3_client.get_json_file_content( - bucket_name=_name, full_file_name=_key - ) - _LOG.info('Findings state has been pulled ' - f'from the \'{_name}/{_key}\' bucket source.') - except JSONDecodeError as _je: - _LOG.warning(f'Content of a Findings file from a \'{_name}\' ' - f'bucket and sourced by a \'{_key}\' key could' - f' not be decoded.') - except (ClientError, Exception) as _e: - _LOG.warning(f'No Findings bound to an account:\'{identifier}\',' - f' have been found. An exception has occurred: {_e}.') - return _findings - - def get_findings_url(self, identifier: str, path: str = '') -> \ - Union[str, Type[None]]: - """ - Retrieves the access URL of a Findings file, which maintains - the latest, respective vulnerability state, bound to the account. - Relative persistence path is derived by a given `path` parameter and - the respective `identifier`, stored inside a delegated storage. - :parameter identifier: str - :parameter path: str - :returns: Union[str, Type[None]] - """ - _name, _key = self._bucket_name, self._get_key( - identifier, FINDINGS_KEY, utc_datetime().date().isoformat(), path) - _url = None - if not self._s3_client.file_exists(bucket_name=_name, key=_key): - _key = self._get_key(identifier, FINDINGS_KEY, path) - - if not self._s3_client.file_exists(bucket_name=_name, key=_key): - _LOG.warning(f'Presigned Findings state URL' - f' bound to an account:\'{identifier}\',' - f' could not be generated for the \'{_name}/{_key}\' ' - f'bucket source.') - return None - - _LOG.debug(f'Generating presigned Findings state URL, for a ' - f'\'{_name}\' bucket driven by \'{_key}\' key.') - _url = self._s3_client.generate_presigned_url( - bucket_name=_name, full_file_name=_key, expires_in_sec=3600) - _LOG.info(f'Presigned Findings state URL has been generated for a ' - f'\'{_name}\' bucket driven by \'{_key}\' key.') - return _url - - def delete_findings(self, identifier: str, path: str = '') -> bool: - """ - Removes a Findings state file, bound to an account, driven by a - provided identifier. Relative persistence path is derived by a given - `path` parameter and the respective `identifier`, which is stored - inside a delegated storage. - :parameter identifier: str - :parameter path:str - :returns bool: - """ - _name, _key = self._bucket_name, self._get_key( - identifier, FINDINGS_KEY, utc_datetime().date().isoformat(), path) - _response = False - try: - _LOG.debug(f'Removing Findings state, from \'{_name}\' bucket ' - f'sourced by \'{_key}\' key.') - _findings = self._s3_client.delete_file(bucket_name=_name, - file_key=_key) - _LOG.info('Findings state has been removed ' - f'from the \'{_name}/{_key}\' bucket source.') - _response = True - - except (ClientError, Exception) as _e: - _LOG.warning(f'No Findings bound to an account:\'{identifier}\',' - f' have been found. An exception has occurred: {_e}.') - return _response - - def put_findings(self, content: Union[Dict, List], - identifier: str, path: str = ''): - """ - Puts a Findings state content, bound to an account, driven by a - provided identifier. Relative persistence path is derived by a given - `path` parameter and the respective `identifier`, which is stored - inside a delegated storage. - :parameter identifier: str - :parameter path:str - :returns bool: - """ - _name = self._bucket_name - _key = self._get_key(identifier, FINDINGS_KEY, - utc_datetime().date().isoformat(), path) - _response = False - try: - _LOG.debug(f'Putting Findings state, inside of \'{_name}\' bucket ' - f'driven by \'{_key}\' key.') - self._s3_client.put_object( - bucket_name=_name, object_name=_key, body=dumps(content) - ) - _LOG.info('Findings state has been put ' - f'into \'{_name}/{_key}\' bucket source.') - _response = True - except (ClientError, BaseException) as _e: - _LOG.warning(f'Findings bound to an account:\'{identifier}\',' - f' could not be put into \'{_name}/{_key}\' ' - f'bucket source. An exception has occurred: {_e}.') - return _response - - @classmethod - def expand_content(cls, content: Dict, key: Union[RESOURCES_ATTR]): - """ - Inverts Findings collection content into a sequence of vulnerability - items, production of which is delegated to a respective method, - derived from a map, by a given key. - :parameter content:Dict - :parameter key:Union[RESOURCES_ATTR] - :return:Iterator - """ - _reference_map: Dict = cls._get_expansion_map() - _expansion: Callable[[Dict], Iterator] = _reference_map.get( - key, cls._i_default_expansion - ) - return _expansion(content) - - @classmethod - def _i_resource_expansion(cls, content: Dict) -> Iterator: - """ - Inverts the Findings content into a sequence of vulnerability - states of each resource, providing an iterator to filter on. - :parameter content: Dict - :return: Iterator - """ - for rule_id, rule_content in content.items(): - _output_resource_dict = {} - _rule_dict = cls._default_instance(rule_content, dict) - _resource_region_dict = cls._default_instance( - _rule_dict.get(RESOURCES_ATTR, None), dict - ) - for _region, _resource_list in _resource_region_dict.items(): - for _resource_dict in _resource_list: - _rule_outsourced = { - each: _rule_dict[each] for each in cls.RULE_ITEM_KEYS - if each in _rule_dict - } - _item = {RULE_ID_ATTR: rule_id, REGION_ATTR: _region} - _item.update(_rule_outsourced) - _item.update(_resource_dict) - yield _item - - @classmethod - def _i_default_expansion(cls, content: Dict) -> Iterator: - """ - Inverts the Findings content into a default sequence of vulnerability - states nesting the rule id, providing an iterator to filter on. - :parameter content: Dict - :return Iterator: - """ - for rule_id, rule_content in content.items(): - _output_resource_dict = {} - _rule_dict = cls._default_instance(rule_content, dict) - _rule_dict.update({RULE_ID_ATTR: rule_id}) - yield _rule_dict - - @classmethod - def extractive_iterator(cls, iterator: Iterator, key: str) -> Iterator: - """ - Yields each item out of the iterator, by extracting the provided `key` - from aforementioned items and mapping other content to it. Such head - key is afterwards mapped to a label, which defaults to 'map_key'. - :parameter iterator:Iterator - :parameter key:str - :return:Iterator - """ - label = MAP_KEY_ATTR - for item in iterator: - _item = cls._default_instance(item, dict) - _value = cls._default_instance(_item.get(key), str) - if not _value: - continue - yield { - label: _value, _value: { - k: v for k, v in _item.items() if k != key - } - } - - @classmethod - def format_iterator(cls, iterator: Iterator, - key: Union[LIST_TYPE_ATTR, MAP_TYPE_ATTR]): - """ - Inverts Findings collection content into a sequence of vulnerability - items, production of which is delegated to a respective method, - derived from a map, by a given key. - :parameter iterator:Iterator - :parameter key:Union[LIST_TYPE_ATTR, MAP_TYPE_ATTR] - :return:Union[List, Dict] - """ - _reference_map: Dict = cls._get_format_map() - _format: Callable[[Iterator], Union[Dict, List]] = _reference_map.get( - key, cls._i_list_formatter - ) - return _format(iterator) - - @staticmethod - def _i_list_formatter(iterator: Iterator): - return list(iterator) - - @classmethod - def _i_map_formatter(cls, iterator: Iterator) -> Dict: - """ - Returns a mapped collection, which - """ - collection: Dict[str, List[Dict]] = {} - for item in iterator: - _item = cls._default_instance(item, dict) - _key: str = cls._default_instance(_item.get(MAP_KEY_ATTR), str) - _value: Dict = cls._default_instance(_item.get(_key), dict) - if not all((_key, _value)): - continue - collection.setdefault(_key, []) - collection[_key].append(_value) - return collection - - @classmethod - def filter_iterator(cls, iterator: Iterator, dependent: bool, - **filters) -> Iterator: - """ - Yields each item out of the iterator, given that provided filters - predicate so, by referencing the `_apply_filters` method. - :parameter iterator:Iterator - :parameter dependent:bool - :parameter filters:Dict - :return:Iterator - """ - for _item in iterator: - _item: Dict = cls._default_instance(_item, dict) - _apply = cls._apply_filters - if not filters or filters and _apply(_item, dependent, **filters): - yield _item - - @classmethod - def _apply_filters(cls, _item: Dict, dependent: bool, **filters) -> bool: - """ - Predicates whether a given `_item` contains key-value pairs, pattern - requirement of which are respectively denoted in provided filters. - Given that action is meant to depend the predicates upon each other, - one may provide `dependent` value. - :parameter _item:Dict - :parameter dependent:bool - :parameter filters:Dict - :return:bool - """ - _adheres = 0 - for key, each in filters.items(): - _each = cls._default_instance(each, str) - pattern = cls._get_include_pattern(_each) - _subject = cls._default_instance(_item.get(key, ''), str) - _encountered = cls._encounter_pattern(pattern, _subject) - if _encountered is not None and not dependent: - break - elif dependent: - _adheres += int(bool(_encountered)) - else: - return _adheres == len(filters) - - return True - - @classmethod - def _get_expansion_map(cls): - return { - RESOURCES_ATTR: cls._i_resource_expansion - } - - @classmethod - def _get_format_map(cls): - return { - LIST_TYPE_ATTR: cls._i_list_formatter, - MAP_TYPE_ATTR: cls._i_map_formatter - } - - @staticmethod - def _get_include_pattern(string: str) -> Union[Pattern, Type[None]]: - try: - return compile(f'^.*{escape(string)}.*$') - except error: - return None - - @staticmethod - def _encounter_pattern(pattern: Pattern, subject: str): - return next(pattern.finditer(subject), None) - - @classmethod - def _get_key(cls, identifier: str, *keys) -> str: - """ - Retrieves bucket key path bound to an account, by concatenating - a reference path, derived from given keys. - I.e. : "findings/$identifier.json" - :parameter identifier:str - :parameter keys:Tuple[str] - :return: str - """ - return str(PurePosixPath(*keys, f'{identifier}.json')) - - @staticmethod - def get_demand_folder(): - return 'on-demand' - - @staticmethod - def _default_instance(value, _type: type, *args, **kwargs): - return value if isinstance(value, _type) else _type(*args, **kwargs) - - @staticmethod - def unique_resources_from_raw_findings(content: dict) -> set: - result = set() - for _, policy in content.items(): - for region, resources in policy.get('resources', {}).items(): - if not resources: - continue - for i in resources: - name = '' - for k, v in i.items(): - name += f'{k}:{v}:' - if name: - result.add(name) - return result diff --git a/src/services/health_check_service.py b/src/services/health_check_service.py index 4b55d34d9..b2abde1ee 100644 --- a/src/services/health_check_service.py +++ b/src/services/health_check_service.py @@ -1,37 +1,29 @@ -import json from abc import ABC, abstractmethod from functools import cached_property -from typing import Dict, Optional, Type, Tuple, List +from typing import Type import requests from botocore.exceptions import ClientError -from modular_sdk.commons.constants import RABBITMQ_TYPE, SIEM_DEFECT_DOJO_TYPE +from modular_sdk.commons.constants import ApplicationType +from modular_sdk.modular import Modular from modular_sdk.services.impl.maestro_credentials_service import \ - DefectDojoApplicationMeta, DefectDojoApplicationSecret, AccessMeta -from pydantic import BaseModel, Field, ValidationError + AccessMeta +from pydantic import BaseModel, Field, ValidationError, ConfigDict from helpers.constants import AWS_CLOUD_ATTR, AZURE_CLOUD_ATTR, \ GCP_CLOUD_ATTR, DEFAULT_SYSTEM_CUSTOMER, HOST_ATTR, CUSTOMER_ATTR, \ ED_AWS_RULESET_NAME, \ - ED_AZURE_RULESET_NAME, ED_GOOGLE_RULESET_NAME, \ - KEY_RULES_TO_SERVICE_SECTION, KEY_RULES_TO_SEVERITY, \ - KEY_RULES_TO_STANDARDS, KEY_RULES_TO_MITRE, KEY_CLOUD_TO_RULES, \ - KEY_HUMAN_DATA, KEY_AWS_STANDARDS_COVERAGE, KEY_AZURE_STANDARDS_COVERAGE, \ - KEY_GOOGLE_STANDARDS_COVERAGE, KEY_AWS_EVENTS, KEY_AZURE_EVENTS, \ - KEY_GOOGLE_EVENTS, HealthCheckStatus + ED_AZURE_RULESET_NAME, ED_GOOGLE_RULESET_NAME, HealthCheckStatus, \ + SettingKey, S3SettingKey, PRIVATE_KEY_SECRET_NAME from helpers.log_helper import get_logger from helpers.system_customer import SYSTEM_CUSTOMER -from integrations.defect_dojo_adapter import DefectDojoAdapter -from models.modular.application import Application from services import SERVICE_PROVIDER from services.clients.s3 import S3Client from services.clients.ssm import VaultSSMClient from services.environment_service import EnvironmentService from services.license_manager_service import LicenseManagerService -from services.modular_service import ModularService from services.ruleset_service import RulesetService -from services.setting_service import SettingsService, KEY_SYSTEM_CUSTOMER, \ - KEY_REPORT_DATE_MARKER +from services.setting_service import SettingsService from services.ssm_service import SSMService _LOG = get_logger(__name__) @@ -40,12 +32,11 @@ class CheckResult(BaseModel): id: str status: HealthCheckStatus = HealthCheckStatus.OK - details: Optional[Dict] = Field(default_factory=dict) - remediation: Optional[str] - impact: Optional[str] + details: dict = Field(default_factory=dict) + remediation: str | None = None + impact: str | None = None - class Config: - use_enum_values = True + model_config = ConfigDict(use_enum_values=True) def is_ok(self) -> bool: return self.status == HealthCheckStatus.OK @@ -68,7 +59,7 @@ def identifier(cls) -> str: """ @classmethod - def remediation(cls) -> Optional[str]: + def remediation(cls) -> str | None: """ Actions in case the check is failed :return: @@ -76,34 +67,34 @@ def remediation(cls) -> Optional[str]: return @classmethod - def impact(cls) -> Optional[str]: + def impact(cls) -> str | None: """ Harm in case the check is failed :return: """ return - def ok_result(self, details: Optional[dict] = None) -> CheckResult: + def ok_result(self, details: dict | None = None) -> CheckResult: return CheckResult( id=self.identifier(), status=HealthCheckStatus.OK, - details=details, + details=details or {} ) - def not_ok_result(self, details: Optional[dict] = None) -> CheckResult: + def not_ok_result(self, details: dict | None = None) -> CheckResult: return CheckResult( id=self.identifier(), status=HealthCheckStatus.NOT_OK, - details=details, + details=details or {}, remediation=self.remediation(), impact=self.impact() ) - def unknown_result(self, details: Optional[dict] = None) -> CheckResult: + def unknown_result(self, details: dict | None = None) -> CheckResult: return CheckResult( id=self.identifier(), status=HealthCheckStatus.UNKNOWN, - details=details + details=details or {} ) @abstractmethod @@ -114,49 +105,6 @@ def check(self, **kwargs) -> CheckResult: """ -# class CoveragesSetToS3SettingsCheck(AbstractHealthCheck): -# @classmethod -# def remediation(cls) -> Optional[str]: -# return 'Parse coverages mappings from excell and set them ' \ -# 'using `main.py env update_settings`' -# -# @classmethod -# def impact(cls) -> Optional[str]: -# return 'You will not be able to generate compliance report for ' \ -# 'the cloud for which coverages mapping is missing' -# -# @classmethod -# def identifier(cls) -> str: -# return 'coverages_setting' -# -# def __init__(self, s3_settings_service: S3SettingsService): -# self._s3_settings_service = s3_settings_service -# -# @classmethod -# def build(cls) -> 'CoveragesSetToS3SettingsCheck': -# return cls( -# s3_settings_service=SERVICE_PROVIDER.s3_settings_service() -# ) -# -# def check(self, **kwargs) -> CheckResult: -# details = { -# AWS_CLOUD_ATTR: True, -# AZURE_CLOUD_ATTR: True, -# GCP_CLOUD_ATTR: True -# } -# for cloud in details: -# key = S3_KEY_SECURITY_STANDARDS_COVERAGE.format(cloud=cloud) -# coverage = self._s3_settings_service.get(key) -# if not coverage: -# details[cloud] = False -# -# if all(details.values()): -# return self.ok_result(details=details) -# return self.not_ok_result( -# details=details -# ) - - class SystemCustomerSettingCheck(AbstractHealthCheck): def __init__(self, settings_service: SettingsService): self._settings_service = settings_service @@ -164,7 +112,7 @@ def __init__(self, settings_service: SettingsService): @classmethod def build(cls) -> 'SystemCustomerSettingCheck': return cls( - settings_service=SERVICE_PROVIDER.settings_service() + settings_service=SERVICE_PROVIDER.settings_service ) @classmethod @@ -172,7 +120,7 @@ def identifier(cls) -> str: return 'system_customer_setting' def check(self, **kwargs) -> CheckResult: - name = (self._settings_service.get(KEY_SYSTEM_CUSTOMER) or + name = (self._settings_service.get(SettingKey.SYSTEM_CUSTOMER) or DEFAULT_SYSTEM_CUSTOMER) return self.ok_result(details={ 'name': name @@ -186,16 +134,16 @@ def __init__(self, settings_service: SettingsService): @classmethod def build(cls) -> 'LicenseManagerIntegrationCheck': return cls( - settings_service=SERVICE_PROVIDER.settings_service() + settings_service=SERVICE_PROVIDER.settings_service ) @classmethod - def remediation(cls) -> Optional[str]: + def remediation(cls) -> str | None: return 'Make sure License Manager is running. Execute ' \ '`c7n setting lm config add --host` to set license manager link' @classmethod - def impact(cls) -> Optional[str]: + def impact(cls) -> str | None: return 'The installation of service does not have ' \ 'access to License Manager API' @@ -230,18 +178,18 @@ def __init__(self, settings_service: SettingsService, @classmethod def build(cls) -> 'LicenseManagerClientKeyCheck': return cls( - settings_service=SERVICE_PROVIDER.settings_service(), - license_manager_service=SERVICE_PROVIDER.license_manager_service(), - ssm_service=SERVICE_PROVIDER.ssm_service() + settings_service=SERVICE_PROVIDER.settings_service, + license_manager_service=SERVICE_PROVIDER.license_manager_service, + ssm_service=SERVICE_PROVIDER.ssm_service ) @classmethod - def remediation(cls) -> Optional[str]: + def remediation(cls) -> str | None: return 'Create client on LM side and rotate keys. Then execute ' \ '`c7n setting lm client add` to import the rotated key' @classmethod - def impact(cls) -> Optional[str]: + def impact(cls) -> str | None: return 'License manager does not know about this custodian ' \ 'installation. It will no allow to sync licenses' @@ -278,7 +226,7 @@ def __init__(self, settings_service: SettingsService): @classmethod def build(cls) -> 'ReportDateMarkerSettingCheck': return cls( - settings_service=SERVICE_PROVIDER.settings_service() + settings_service=SERVICE_PROVIDER.settings_service ) @classmethod @@ -286,11 +234,11 @@ def identifier(cls) -> str: return 'report_date_marker_setting' @classmethod - def impact(cls) -> Optional[str]: + def impact(cls) -> str | None: return 'Metric collecting will not work properly' @classmethod - def remediation(cls) -> Optional[str]: + def remediation(cls) -> str | None: return 'Execute `main.py env update_settings`' @cached_property @@ -305,13 +253,15 @@ def check(self, **kwargs) -> CheckResult: setting = self._settings_service.get_report_date_marker() if not setting: return self.not_ok_result({ - 'error': f'setting \'{KEY_REPORT_DATE_MARKER}\' is not set' + 'error': f'setting \'{SettingKey.REPORT_DATE_MARKER}\' ' + f'is not set' }) try: self.model(**setting) except ValidationError as e: return self.not_ok_result({ - 'error': f'setting \'{KEY_REPORT_DATE_MARKER}\' is invalid' + 'error': f'setting \'{SettingKey.REPORT_DATE_MARKER}\' ' + f'is invalid' }) return self.ok_result() @@ -319,17 +269,17 @@ def check(self, **kwargs) -> CheckResult: class RabbitMQConnectionCheck(AbstractHealthCheck): def __init__(self, settings_service: SettingsService, ssm_service: SSMService, - modular_service: ModularService): + modular_client: Modular): self._settings_service = settings_service self._ssm_service = ssm_service - self._modular_service = modular_service + self._modular_client = modular_client @classmethod def build(cls) -> 'RabbitMQConnectionCheck': return cls( - settings_service=SERVICE_PROVIDER.settings_service(), - ssm_service=SERVICE_PROVIDER.ssm_service(), - modular_service=SERVICE_PROVIDER.modular_service() + settings_service=SERVICE_PROVIDER.settings_service, + ssm_service=SERVICE_PROVIDER.ssm_service, + modular_client=SERVICE_PROVIDER.modular_client ) @classmethod @@ -337,35 +287,36 @@ def identifier(cls) -> str: return 'rabbitmq_connection' @classmethod - def impact(cls) -> Optional[str]: + def impact(cls) -> str | None: return 'Customer won`t be able to send messages to Maestro' @classmethod - def remediation(cls) -> Optional[str]: + def remediation(cls) -> str | None: return 'Setup RabbitMQ configuration for your customer' - def _check_customer(self, customer: str) -> Tuple[dict, bool]: + def _check_customer(self, customer: str) -> tuple[dict, bool]: """ Returns details for one customer and boolean whether the check is successful :param customer: :return: """ - app = next(self._modular_service.get_applications( + app = next(self._modular_client.application_service().list( customer=customer, - _type=RABBITMQ_TYPE, + _type=ApplicationType.RABBITMQ.value, limit=1, deleted=False ), None) if not app: return { - customer: f'Application with type {RABBITMQ_TYPE} not found' + customer: f'Application with type ' + f'{ApplicationType.RABBITMQ} not found' }, False - creds = self._modular_service.modular_client.maestro_credentials_service(). \ + creds = self._modular_client.maestro_credentials_service(). \ get_by_application(app) if not creds: return { - customer: 'Could not resolve rabbitmq creds from application' + customer: 'Could not resolve RabbitMQ creds from application' }, False return {customer: 'OK'}, True @@ -373,8 +324,8 @@ def check(self, **kwargs) -> CheckResult: if kwargs.get(CUSTOMER_ATTR): customers = iter([kwargs[CUSTOMER_ATTR]]) else: - customers = (c.name for c in - self._modular_service.i_get_customers()) + cs = self._modular_client.customer_service() + customers = (c.name for c in cs.i_get_customer()) details = {} ok = True for customer in customers: @@ -394,7 +345,7 @@ def __init__(self, ruleset_service: RulesetService): @classmethod def build(cls) -> 'AbstractHealthCheck': return cls( - ruleset_service=SERVICE_PROVIDER.ruleset_service() + ruleset_service=SERVICE_PROVIDER.ruleset_service ) @classmethod @@ -441,7 +392,7 @@ def check(self, **kwargs) -> CheckResult: ) @classmethod - def remediation(cls) -> Optional[str]: + def remediation(cls) -> str | None: """ Actions in case the check is failed :return: @@ -450,7 +401,7 @@ def remediation(cls) -> Optional[str]: '"c7n ruleset eventdriven add" for all three clouds' @classmethod - def impact(cls) -> Optional[str]: + def impact(cls) -> str | None: """ Harm in case the check is failed :return: @@ -458,75 +409,6 @@ def impact(cls) -> Optional[str]: return 'Event-driven scans won`t work' -class DefectDojoCheck(AbstractHealthCheck): - def __init__(self, modular_service: ModularService): - self._modular_service = modular_service - - @classmethod - def build(cls) -> 'DefectDojoCheck': - return cls( - modular_service=SERVICE_PROVIDER.modular_service() - ) - - @classmethod - def identifier(cls) -> str: - return 'defect_dojo_connection' - - def _check_application(self, application: Application) -> Tuple[str, bool]: - raw_secret = self._modular_service.modular_client.assume_role_ssm_service().get_parameter( - application.secret) - if not raw_secret or not isinstance(raw_secret, dict): - _LOG.debug(f'SSM Secret by name {application.secret} not found') - return 'Application secret not found', False - meta = DefectDojoApplicationMeta.from_dict(application.meta.as_dict()) - secret = DefectDojoApplicationSecret.from_dict(raw_secret) - try: - _LOG.info('Initializing dojo client') - DefectDojoAdapter( - host=meta.url, - api_key=secret.api_key, - # todo get other configuration from parent meta - ) - return 'OK', True - except requests.RequestException as e: - return str(e), False - - def _check_customer(self, customer: str) -> Tuple[dict, bool]: - parents = self._modular_service.get_customer_bound_parents( - customer=customer, - parent_type=SIEM_DEFECT_DOJO_TYPE, - is_deleted=False - ) - result = {} - ok = True - for aid in (parent.application_id for parent in parents): - app = self._modular_service.get_application(aid) - if not app or app.is_deleted: - result[aid], ok = 'Application not found', False - - else: - result[aid], _ok = self._check_application(app) - ok &= _ok - return {customer: result}, ok - - def check(self, **kwargs) -> CheckResult: - if kwargs.get(CUSTOMER_ATTR): - customers = iter([kwargs[CUSTOMER_ATTR]]) - else: - customers = (c.name for c in - self._modular_service.i_get_customers()) - details = {} - ok = True - for customer in customers: - data, success = self._check_customer(customer) - details.update(data) - if not success: - ok = False - if ok: - return self.ok_result(details) - return self.not_ok_result(details) - - class RulesMetaAccessDataCheck(AbstractHealthCheck): def __init__(self, settings_service: SettingsService, ssm_service: SSMService): @@ -536,8 +418,8 @@ def __init__(self, settings_service: SettingsService, @classmethod def build(cls) -> 'RulesMetaAccessDataCheck': return cls( - settings_service=SERVICE_PROVIDER.settings_service(), - ssm_service=SERVICE_PROVIDER.ssm_service() + settings_service=SERVICE_PROVIDER.settings_service, + ssm_service=SERVICE_PROVIDER.ssm_service ) @classmethod @@ -545,12 +427,12 @@ def identifier(cls) -> str: return 'rules_meta_access_data' @classmethod - def remediation(cls) -> Optional[str]: + def remediation(cls) -> str | None: return 'Admin should set secret with access data to ' \ 'git repo containing rules meta' @classmethod - def impact(cls) -> Optional[str]: + def impact(cls) -> str | None: return 'Custodian will not pull rules meta. ' \ 'Some features will be unavailable' @@ -578,16 +460,16 @@ def __init__(self, s3_settings_service): @classmethod def build(cls) -> 'RulesMetaCheck': return cls( - s3_settings_service=SERVICE_PROVIDER.s3_settings_service() + s3_settings_service=SERVICE_PROVIDER.s3_settings_service ) @classmethod - def remediation(cls) -> Optional[str]: + def remediation(cls) -> str | None: return 'Configure rule-meta access data and invoke rule-meta-updater ' \ 'with an empty event to wait for cron invoke' @classmethod - def impact(cls) -> Optional[str]: + def impact(cls) -> str | None: return 'Some features will not be available' @classmethod @@ -597,11 +479,18 @@ def identifier(cls) -> str: def check(self, **kwargs) -> CheckResult: _all = set(self._s3_settings_service.ls()) _required = { - KEY_RULES_TO_SERVICE_SECTION, KEY_RULES_TO_SEVERITY, - KEY_RULES_TO_STANDARDS, KEY_RULES_TO_MITRE, KEY_CLOUD_TO_RULES, - KEY_HUMAN_DATA, KEY_AWS_STANDARDS_COVERAGE, - KEY_AZURE_STANDARDS_COVERAGE, KEY_GOOGLE_STANDARDS_COVERAGE, - KEY_AWS_EVENTS, KEY_AZURE_EVENTS, KEY_GOOGLE_EVENTS + S3SettingKey.RULES_TO_SERVICE_SECTION.value, + S3SettingKey.RULES_TO_SEVERITY.value, + S3SettingKey.RULES_TO_STANDARDS.value, + S3SettingKey.RULES_TO_MITRE.value, + S3SettingKey.CLOUD_TO_RULES.value, + S3SettingKey.HUMAN_DATA.value, + S3SettingKey.AWS_STANDARDS_COVERAGE.value, + S3SettingKey.AZURE_STANDARDS_COVERAGE.value, + S3SettingKey.GOOGLE_STANDARDS_COVERAGE.value, + S3SettingKey.AWS_EVENTS.value, + S3SettingKey.AZURE_EVENTS.value, + S3SettingKey.GOOGLE_EVENTS.value } missing = _required - _all present = _required & _all @@ -620,16 +509,16 @@ def __init__(self, ssm_service: SSMService): @classmethod def build(cls) -> 'VaultConnectionCheck': return cls( - ssm_service=SERVICE_PROVIDER.ssm_service() + ssm_service=SERVICE_PROVIDER.ssm_service ) @classmethod - def remediation(cls) -> Optional[str]: + def remediation(cls) -> str | None: return 'Make sure, vault required envs are set to .env file. ' \ 'In case k8s installation check whether Vault k8s service is up' @classmethod - def impact(cls) -> Optional[str]: + def impact(cls) -> str | None: return 'On-prem will not work properly' @classmethod @@ -662,7 +551,7 @@ def __init__(self, s3_client: S3Client): @classmethod def build(cls) -> 'AbstractHealthCheck': return cls( - s3_client=SERVICE_PROVIDER.s3() + s3_client=SERVICE_PROVIDER.s3 ) @classmethod @@ -672,7 +561,7 @@ def identifier(cls) -> str: def check(self, **kwargs) -> CheckResult: # TODO check host and port try: - self._s3_client.list_buckets() + next(self._s3_client.list_buckets()) except ClientError as e: if e.response['Error']['Code'] in ('SignatureDoesNotMatch', 'InvalidAccessKeyId'): @@ -705,16 +594,16 @@ def __init__(self, s3_client: S3Client, @classmethod def build(cls) -> 'AllS3BucketsExist': return cls( - s3_client=SERVICE_PROVIDER.s3(), - environment_service=SERVICE_PROVIDER.environment_service() + s3_client=SERVICE_PROVIDER.s3, + environment_service=SERVICE_PROVIDER.environment_service ) @classmethod - def impact(cls) -> Optional[str]: + def impact(cls) -> str | None: return 'Depending on missing buckets some features may not work' @classmethod - def remediation(cls) -> Optional[str]: + def remediation(cls) -> str | None: return 'Set bucket names to .env and execute `main.py ' \ 'create_buckets`. For saas deploy the buckets' @@ -723,20 +612,18 @@ def identifier(cls) -> str: return 'buckets_exist' @cached_property - def bucket_names(self) -> List[str]: + def bucket_names(self) -> list[str]: return [name for name in ( self._environment_service.get_statistics_bucket_name(), - self._environment_service.get_ssm_backup_bucket(), self._environment_service.get_rulesets_bucket_name(), self._environment_service.default_reports_bucket_name(), - self._environment_service.get_templates_bucket_name(), self._environment_service.get_metrics_bucket_name() ) if name] def check(self, **kwargs) -> CheckResult: availability = {} for name in self.bucket_names: - availability[name] = self._s3_client.is_bucket_exists(name) + availability[name] = self._s3_client.bucket_exists(name) if all(availability.values()): return self.ok_result() return self.not_ok_result(availability) @@ -749,15 +636,15 @@ def __init__(self, ssm_service: SSMService): @classmethod def build(cls) -> 'VaultAuthTokenIsSetCheck': return cls( - ssm_service=SERVICE_PROVIDER.ssm_service() + ssm_service=SERVICE_PROVIDER.ssm_service ) @classmethod - def remediation(cls) -> Optional[str]: + def remediation(cls) -> str | None: return 'Execute `main.py init_vault`' @classmethod - def impact(cls) -> Optional[str]: + def impact(cls) -> str | None: return 'On-prem authentication will not work' @classmethod @@ -765,31 +652,17 @@ def identifier(cls) -> str: return 'vault_auth_token' def check(self, **kwargs) -> CheckResult: - from connections.auth_extension.cognito_to_jwt_adapter import \ - AUTH_TOKEN_NAME - # lambda package does not include `exported_module` + # lambda package does not include `onprem` if not self._ssm_service.is_secrets_engine_enabled(): return self.not_ok_result(details={ - 'token': False, + 'private_key': False, 'secrets_engine': False }) - token = self._ssm_service.get_secret_value(AUTH_TOKEN_NAME) - if isinstance(token, str): - try: - payload = json.loads(token) - except json.JSONDecodeError as e: - return self.not_ok_result(details={ - 'token': True, - 'secrets_engine': True, - 'error': 'Invalid token JSON' - }) - else: # isinstance(token, dict) - payload = token - phrase = payload.get('phrase') - if not phrase: + token = self._ssm_service.get_secret_value(PRIVATE_KEY_SECRET_NAME) + if not token or not isinstance(token, str): return self.not_ok_result(details={ - 'token': True, + 'private_key': False, 'secrets_engine': True, - 'error': '`phrase` key is missing' + 'error': 'Private key does not exist or invalid' }) return self.ok_result(details={'token': True, 'secrets_engine': True}) diff --git a/src/services/integration_service.py b/src/services/integration_service.py new file mode 100644 index 000000000..872ba1f17 --- /dev/null +++ b/src/services/integration_service.py @@ -0,0 +1,50 @@ +from typing import Generator + +from modular_sdk.commons.constants import ParentType +from modular_sdk.models.tenant import Tenant +from modular_sdk.services.parent_service import ParentService + +from services.ambiguous_job_service import AmbiguousJob +from services.defect_dojo_service import DefectDojoConfiguration, \ + DefectDojoParentMeta, DefectDojoService + + +class IntegrationService: + def __init__(self, parent_service: ParentService, + defect_dojo_service: DefectDojoService): + self._ps = parent_service + self._dds = defect_dojo_service + + def get_dojo_adapters(self, tenant: Tenant, + send_after_job: bool | None = None + ) -> Generator[tuple[DefectDojoConfiguration, DefectDojoParentMeta], None, None]: + """ + Yields dojo configurations for the given tenant. Currently, can yield + only one, but maybe in future need multiple + :param tenant: + :param send_after_job: + :return: + """ + parent = self._ps.get_linked_parent_by_tenant( + tenant=tenant, type_=ParentType.SIEM_DEFECT_DOJO + ) + if not parent: + return + configuration = DefectDojoParentMeta.from_parent(parent) + if (isinstance(send_after_job, bool) and + configuration.send_after_job != send_after_job): + return + + dojo = self._dds.get_nullable(parent.application_id) + if not dojo: + return + yield dojo, configuration + + @staticmethod + def job_tags_dojo(job: AmbiguousJob) -> list[str]: + return list(filter(None, [ + job.owner, + job.type.value, + job.scheduled_rule_name, + *(job.rulesets or []), + ])) diff --git a/src/services/job_lock.py b/src/services/job_lock.py new file mode 100644 index 000000000..ad403ea59 --- /dev/null +++ b/src/services/job_lock.py @@ -0,0 +1,172 @@ +import time +import uuid +from abc import ABC, abstractmethod +from typing import Generator, TYPE_CHECKING + +from modular_sdk.models.tenant_settings import TenantSettings + +from helpers.constants import TS_JOB_LOCK_KEY +from services import SP + +if TYPE_CHECKING: + from modular_sdk.services.tenant_settings_service import \ + TenantSettingsService + + +class AbstractJobLock(ABC): + + @abstractmethod + def acquire(self, *args, **kwargs): + pass + + @abstractmethod + def release(self, *args, **kwargs): + pass + + @abstractmethod + def locked(self, *args, **kwargs) -> bool: + pass + + +class JobPayload: + __slots__ = ('expiration', 'regions') + + def __init__(self, expiration: float | None = None, + regions: set[str] | None = None): + if not expiration: + expiration = time.time() + 3600 * 1.5 + + self.expiration: float = expiration + self.regions: set[str] = regions or set() + + def serialize(self) -> dict: + return { + 'e': self.expiration, + 'r': list(self.regions) + } + + @classmethod + def deserialize(cls, data: dict) -> 'JobPayload': + return cls( + expiration=data.get('e'), + regions=set(data.get('r') or []) + ) + + def intersected(self, regions: set[str]) -> set[str]: + return self.regions & regions + + def is_locked(self, regions: set[str]) -> bool: + _intersection = bool(self.intersected(regions)) + _expired = self.expiration < time.time() + return not _expired and _intersection + + +class TenantSettingJobLock(AbstractJobLock): + """ + { + "k": "CUSTODIAN_JOB_LOCK", + "t": "EXAMPLE-TENANT", + "v": { + "job-id-1": { + "e": 1231231, + "r": [] + } + } + } + """ + + def __init__(self, tenant_name: str): + """ + >>> lock = TenantSettingJobLock('MY_TENANT') + >>> lock.locked({'eu-central-1'}) + False + >>> lock.acquire('job-1') + >>> lock.locked({'eu-central-1'}) + True + >>> lock.locked({'eu-west-1'}) + False + >>> lock.release() + >>> lock.locked({'eu-central-1'}) + False + :param tenant_name: + """ + self._tenant_name = tenant_name + + @property + def tss(self) -> 'TenantSettingsService': + """ + Tenant settings service + :return: + """ + return SP.modular_client.tenant_settings_service() + + @property + def tenant_name(self) -> str: + return self._tenant_name + + def acquire(self, job_id: str, regions: set[str]): + """ + You must check whether the lock is locked before calling acquire(). + """ + payload = JobPayload(regions=regions).serialize() + item = self.tss.get(tenant_name=self._tenant_name, key=TS_JOB_LOCK_KEY) + if not item: + # unfortunately we must create an item before we can update + # nested attributes. + # in other words, update fails in case you do it on the nested + # attribute of not existing item. + self.tss.create( + tenant_name=self._tenant_name, + key=TS_JOB_LOCK_KEY, + value={job_id: payload} + ).save() + return + # item found + self.tss.update(item, actions=[ + TenantSettings.value[job_id].set(payload) + ]) + + def release(self, job_id: str): + item = self.tss.create( + tenant_name=self._tenant_name, + key=TS_JOB_LOCK_KEY + ) + try: + self.tss.update(item, actions=[ + TenantSettings.value[job_id].remove() + ]) + except Exception: # noqa + # update will fail in case item does not exist in db + pass + + @staticmethod + def _iter_payloads(data: dict + ) -> Generator[tuple[str, JobPayload], None, None]: + """ + Iterates over setting value and yields valid payloads, skipping + invalid ones + """ + for key, value in data.items(): + try: + uuid.UUID(key) + payload = JobPayload.deserialize(value) + except Exception: # noqa + continue + # key is a valid uuid thus a valid job id + # payload is also valid + yield key, payload + + def locked(self, regions: set[str]) -> bool: + return bool(self.locked_for(regions)) + + def locked_for(self, regions: set[str]) -> str | None: + """ + The same as above but allows to get the job that is running + """ + item = self.tss.get(self._tenant_name, TS_JOB_LOCK_KEY) + if not item: + return + value = item.value.as_dict() + for key, payload in self._iter_payloads(value): + if payload.is_locked(regions): + return key diff --git a/src/services/job_service.py b/src/services/job_service.py index af08e6019..c7ec13aae 100644 --- a/src/services/job_service.py +++ b/src/services/job_service.py @@ -1,517 +1,250 @@ -import datetime -import time -from abc import ABC, abstractmethod -from typing import Iterable, Optional, List, Union, Generator, Callable - -from botocore.exceptions import ClientError -from modular_sdk.models.pynamodb_extension.base_model import \ - LastEvaluatedKey as Lek -from modular_sdk.models.pynamodb_extension.pynamodb_to_pymongo_adapter import \ - Result -from pymongo.collection import Collection -from modular_sdk.models.tenant_settings import TenantSettings -from modular_sdk.services.tenant_settings_service import TenantSettingsService -from pynamodb.indexes import Condition +import uuid +from datetime import timedelta, datetime +from typing import Optional, Any + +from pynamodb.expressions.condition import Condition from pynamodb.pagination import ResultIterator -from pynamodb.exceptions import UpdateError -from helpers import time_helper -from helpers.constants import JOB_STARTED_STATUS, JOB_RUNNABLE_STATUS, \ - JOB_RUNNING_STATUS, JOB_SUCCEEDED_STATUS, JOB_FAILED_STATUS + +from helpers.constants import JobState, BatchJobEnv, ALL_ATTR from helpers.log_helper import get_logger -from helpers.time_helper import utc_iso +from helpers.time_helper import utc_iso, utc_datetime from models.job import Job from services import SP -from services.rbac.restriction_service import RestrictionService - -JOB_PENDING_STATUSES = (JOB_STARTED_STATUS, JOB_RUNNABLE_STATUS, - JOB_RUNNING_STATUS) -JOB_DTO_SKIP_ATTRS = {'job_definition', 'job_queue', 'reason', 'created_at', - 'rules_to_scan', 'ttl'} -DEFAULT_LIMIT = 30 +from services.base_data_service import BaseDataService _LOG = get_logger(__name__) -class AbstractJobLock(ABC): +class JobService(BaseDataService[Job]): + def create(self, customer_name: str, tenant_name: str, regions: list[str], + rulesets: list[str], rules_to_scan: list[str] | None = None, + platform_id: str | None = None, ttl: timedelta | None = None, + owner: str | None = None) -> Job: + return super().create( + id=str(uuid.uuid4()), + customer_name=customer_name, + tenant_name=tenant_name, + regions=regions, + rulesets=rulesets, + rules_to_scan=rules_to_scan or [], + platform_id=platform_id, + ttl=ttl, + owner=owner + ) - @abstractmethod - def acquire(self, *args, **kwargs): - pass + def update(self, job: Job, batch_job_id: str = None, reason: str = None, + status: JobState = None, created_at: str = None, + started_at: str = None, stopped_at: str = None, + queue: str = None, definition: str = None): + actions = [] + if batch_job_id: + actions.append(Job.batch_job_id.set(batch_job_id)) + if reason: + actions.append(Job.reason.set(reason)) + if status: + actions.append(Job.status.set(status.value)) + if created_at: + actions.append(Job.created_at.set(created_at)) + if started_at: + actions.append(Job.started_at.set(started_at)) + if stopped_at: + actions.append(Job.stopped_at.set(stopped_at)) + if queue: + actions.append(Job.queue.set(queue)) + if definition: + actions.append(Job.definition.set(definition)) + if actions: + job.update(actions) + + def get_by_customer_name(self, customer_name: str, status: JobState = None, + start: datetime = None, end: datetime = None, + filter_condition: Optional[Condition] = None, + ascending: bool = False, limit: int = None, + last_evaluated_key: dict = None, + ) -> ResultIterator[Job]: + if start and end: + rkc = Job.submitted_at.between( + lower=utc_iso(start), + upper=utc_iso(end) + ) + elif start: + rkc = (Job.submitted_at >= utc_iso(start)) + elif end: + rkc = (Job.submitted_at < utc_iso(end)) + else: + rkc = None + if status: + filter_condition &= (Job.status == status.value) + return Job.customer_name_submitted_at_index.query( + hash_key=customer_name, + range_key_condition=rkc, + filter_condition=filter_condition, + scan_index_forward=ascending, + limit=limit, + last_evaluated_key=last_evaluated_key + ) + + def get_by_tenant_name(self, tenant_name: str, status: JobState = None, + start: datetime = None, end: datetime = None, + filter_condition: Optional[Condition] = None, + ascending: bool = False, limit: int = None, + last_evaluated_key: dict = None, + ) -> ResultIterator[Job]: + if start and end: + rkc = Job.submitted_at.between( + lower=utc_iso(start), + upper=utc_iso(end) + ) + elif start: + rkc = (Job.submitted_at >= utc_iso(start)) + elif end: + rkc = (Job.submitted_at < utc_iso(end)) + else: + rkc = None + if status: + filter_condition &= (Job.status == status.value) + return Job.tenant_name_submitted_at_index.query( + hash_key=tenant_name, + range_key_condition=rkc, + filter_condition=filter_condition, + scan_index_forward=ascending, + limit=limit, + last_evaluated_key=last_evaluated_key + ) - @abstractmethod - def release(self): + def dto(self, item: Job) -> dict[str, Any]: + raw = super().dto(item) + raw.pop('batch_job_id', None) + raw.pop('queue', None) + raw.pop('definition', None) + raw.pop('owner', None) + raw.pop('rules_to_scan', None) + raw.pop('ttl', None) + return raw + + +class NullJobUpdater: + """ + For standard jobs (not scheduled and not event-driven). Standard jobs + are updated by caas-job-updater + """ + def __init__(self, job: Job): + self._job = job + + def save(self): pass - @abstractmethod - def locked(self) -> bool: + def update(self): pass + @property + def job(self) -> Job: + return self._job -class TenantSettingJobLock(AbstractJobLock): - TYPE = 'CUSTODIAN_JOB_LOCK' # tenant_setting type - EXPIRATION = 3600 * 1.5 # in seconds, 1.5h - def __init__(self, tenant_name: str): - """ - >>> lock = TenantSettingJobLock('MY_TENANT') - >>> lock.locked() - False - >>> lock.acquire('job-1') - >>> lock.locked() - True - >>> lock.job_id - 'job-1' - >>> lock.release() - >>> lock.locked() - False - >>> lock.release() - >>> lock.locked() - False - :param tenant_name: - """ - self._tenant_name = tenant_name +class JobUpdater: + """ + Allows to update job attributes more easily + """ + __slots__ = ('_job', '_actions') - self._item = None # just cache + def __init__(self, job: Job): + self._job = job - @property - def tss(self) -> TenantSettingsService: - """ - Tenant settings service - :return: - """ - return SP.modular_service().modular_client.tenant_settings_service() + self._actions = [] - @property - def job_id(self) -> Optional[str]: - """ - ID of a job the lock is locked with - :return: - """ - if not self._item: - return - return self._item.value.as_dict().get('jid') - - @property - def tenant_name(self) -> str: - return self._tenant_name - - def acquire(self, job_id: str): + @classmethod + def from_batch_env(cls, environment: dict[str, str]) -> 'JobUpdater': """ - You must check whether the lock is locked before calling acquire(). - :param job_id: + A situation when the job does not exist in db is possible + :param environment: :return: """ - item = self.tss.create( - tenant_name=self._tenant_name, - key=self.TYPE - ) - self.tss.update(item, actions=[ - TenantSettings.value.set({ - 'exp': time.time() + self.EXPIRATION, - 'jid': job_id, - 'locked': True - }) - ]) - self._item = item - - def release(self): - item = self.tss.create( - tenant_name=self._tenant_name, - key=self.TYPE - ) - try: - self.tss.update(item, actions=[ - TenantSettings.value['locked'].set(False) - ]) - except UpdateError: - # it's normal. It means that item.value['locked'] simply - # does not exist and update action cannot perform its update. - # DynamoDB raises UpdateError if you try to update not existing - # nested key - pass - self._item = item - - def locked(self) -> bool: - item = self.tss.get(self._tenant_name, self.TYPE) - if not item: - return False - self._item = item - value = item.value.as_dict() - if not value.get('locked'): - return False - # locked = True - if not value.get('exp'): - return True # no expiration, we locked - return value.get('exp') > time.time() - - -class JobService: - def __init__(self, restriction_service: RestrictionService): - self._restriction_service = restriction_service - - def get_last_tenant_job(self, tenant_name: str, - status: Optional[Iterable] = None - ) -> Optional[Job]: - """ - Returns the latest job made by tenant - """ - condition = None - if isinstance(status, Iterable): - condition = Job.status.is_in(*list(status)) - return next( - Job.tenant_display_name_index.query( - hash_key=tenant_name, - scan_index_forward=False, - limit=1, - filter_condition=condition - ), None + tenant = SP.modular_client.tenant_service().get( + environment[BatchJobEnv.TENANT_NAME] ) - - @staticmethod - def create(data: dict) -> Job: - job_data = {} - for attribute in Job.get_attributes(): - value = data.get(attribute) - if value is None: - continue - job_data[attribute] = value - return Job(**job_data) - - @staticmethod - def get_job(job_id: str) -> Optional[Job]: - return Job.get_nullable(job_id) - - @classmethod - def inquery( - cls, customer: Optional[str] = None, - tenants: Optional[List[str]] = None, limit: Optional[int] = 10, - last_evaluated_key: Optional[Union[str, dict]] = None, - attributes_to_get: Optional[list] = None, - filter_condition: Optional[Condition] = None, - range_condition: Optional[Condition] = None, - ascending: bool = False - ): - query: Callable = Job.scan - last_evaluated_key = last_evaluated_key or '' - if isinstance(last_evaluated_key, str): - last_evaluated_key = Lek.deserialize(s=last_evaluated_key) - - params: dict = dict( - filter_condition=filter_condition, - last_evaluated_key=last_evaluated_key, - attributes_to_get=attributes_to_get, - limit=limit - ) - - if any((tenants, customer)): - params.update( - range_key_condition=range_condition, - scan_index_forward=ascending + submitted_at = utc_iso() + if BatchJobEnv.SUBMITTED_AT in environment: + submitted_at = utc_iso( + utc_datetime(environment[BatchJobEnv.SUBMITTED_AT])) + + rule_sets = [] + standard = environment.get(BatchJobEnv.TARGET_RULESETS) + if standard and isinstance(standard, str): + rule_sets.extend(standard.split(',')) + licensed = environment.get(BatchJobEnv.LICENSED_RULESETS) + if licensed and isinstance(licensed, str): + rule_sets.extend( + each.split(':', maxsplit=1)[-1] + for each in licensed.split(',') if ':' in each ) + if not rule_sets: + rule_sets.append(ALL_ATTR) + return JobUpdater(Job( + id=environment.get(BatchJobEnv.CUSTODIAN_JOB_ID) or str( + uuid.uuid4()), + batch_job_id=environment.get(BatchJobEnv.JOB_ID), + tenant_name=tenant.name, + customer_name=tenant.customer_name, + submitted_at=submitted_at, + status=JobState.SUBMITTED.value, + owner=tenant.customer_name, + regions=environment.get(BatchJobEnv.TARGET_REGIONS, '').split(','), + rulesets=rule_sets, + scheduled_rule_name=environment.get( + BatchJobEnv.SCHEDULED_JOB_NAME), + )) - if tenants and len(tenants) != 1: - - _params = {} - - # Digests composable lek: Dict[str, Union[int, Dict[str, Any]]] - _params = cls._get_query_hash_key_ref_params( - last_evaluated_key=(last_evaluated_key.value or {}), - partition_key_list=tenants, params=params - ) - - _scope = ', '.join(map("'{}'".format, _params)) - _LOG.info(f'Collecting job-items of {_scope} tenants.') - params = dict( - hash_key_query_ref=_params, limit=limit, - scan_index_forward=ascending - ) - query = Job.tenant_display_name_index.batch_query - - elif tenants: - # len(tenants) == 1 - tenant = tenants[0] - - _LOG.info(f'Collecting job-items of a \'{tenant}\' tenant.') - params.update(hash_key=tenant) - query = Job.tenant_display_name_index.query - - elif customer: - _LOG.info( - f'Collecting job-items of a \'{customer}\' customer.') - params.update(hash_key=customer) - query = Job.customer_display_name_index.query - else: - # Scan - params.update(limit=limit) - - return query(**params) + @classmethod + def from_job_id(cls, job_id) -> 'JobUpdater': + return JobUpdater(Job(id=job_id)) - @staticmethod - def get_succeeded_condition(succeeded: bool): - # todo query optimization: $status#$submitted-at - op = Job.status.__eq__ if succeeded else Job.status.__ne__ - return op(JOB_SUCCEEDED_STATUS) + def save(self): + self._job.save() - @staticmethod - def get_submitted_scope_condition(start: Optional[str] = None, - end: Optional[str] = None): - """ - :param start: Optional[str], the lower bound - :param end: Optional[str], the upper bound - """ - if start and end: - return Job.submitted_at.between(lower=start, upper=end) - elif start: - return Job.submitted_at >= start - else: # only end - return Job.submitted_at <= end - - @staticmethod - def get_tenant_related_condition(tenant: str): - return Job.tenant_display_name == tenant - - @staticmethod - def get_tenants_related_condition(tenants: List[str]): - return Job.tenant_display_name.is_in(*tenants) - - @staticmethod - def _get_query_hash_key_ref_params( - last_evaluated_key: dict, partition_key_list: List[str], - params: dict - ): - """ - Returns `hash_key_ref` payload, digesting a last_evaluated_key, - presumably composed out of partition key pointers, reference to - which is stored within the respective list. - """ - output = {} - last_evaluated_key = last_evaluated_key or {} - for partition_key in partition_key_list: - _output = params.copy() - if partition_key in last_evaluated_key: - _output.update(last_evaluated_key=last_evaluated_key) - output[partition_key] = _output - return output - - def list(self, tenants: Optional[list] = None, - customer: Optional[str] = None, - succeeded: bool = None, - attributes_to_get: Optional[list] = None, - limit: Optional[int] = 10, - lek: Optional[Union[str, dict]] = None) -> Union[ResultIterator, - Result]: - """ - Describes all the jobs based on the given params. Make sure that - the given tenants are available for the user making the request - :parameter tenants: Optional[list] - list of tenant names which jobs - to describe. If the list is empty, all jobs of all the tenants - within the customer are described. Make sure the param is - restricted. - :parameter customer: Optional[str] - :parameter succeeded: bool - :parameter attributes_to_get: Optional[list] - :parameter limit: Optional[int] - :parameter lek: Optional[Union[str, dict]] - last evaluated key. - For DynamoDB it's a dict, for MondoDB it's an integer. - :return: Union[ResultIterator, Result] - """ - _condition = None - if isinstance(succeeded, bool): - _condition &= Job.status == JOB_SUCCEEDED_STATUS - - _c_condition = _condition - if customer: - _c_condition &= Job.customer_display_name == customer - params = dict(scan_index_forward=False, limit=limit, - last_evaluated_key=lek, - filter_condition=_c_condition, - attributes_to_get=attributes_to_get) # commons - if tenants and len(tenants) == 1: - _tenant = tenants[0] - params.update(hash_key=_tenant) - _cursor = Job.tenant_display_name_index.query(**params) - elif customer: # and maybe multiple tenants - if tenants: - _condition &= Job.tenant_display_name.is_in(*tenants) - params.update(hash_key=customer, filter_condition=_condition) - _cursor = Job.customer_display_name_index.query(**params) - else: # not account & not customer & maybe multiple tenants. - # Apparently the action is restricted by handler -> there - # will be no possibility the customer is not given. - if tenants: - _c_condition &= Job.tenant_display_name.is_in(*tenants) - params.pop('scan_index_forward') - params.update(filter_condition=_c_condition) - _cursor = Job.scan(**params) - return _cursor - - def filter_by_tenants(self, entities: Iterable[Job] - ) -> Generator[Job, None, None]: - _tenants = self._restriction_service.user_tenants - if not _tenants: - yield from entities + def update(self): + if not self._actions: return - for entity in entities: - if entity.tenant_display_name in _tenants: - yield entity - - @staticmethod - def jobs_between(jobs: Iterable[Job], - start: Optional[datetime.datetime], - end: Optional[datetime.datetime]) -> Iterable[Job]: - start_s = utc_iso(start) if start else None - end_s = utc_iso(end) if end else None - for job in jobs: - start_condition = job.submitted_at >= start_s if start_s else True - end_condition = job.submitted_at <= end_s if end_s else True - if start_condition and end_condition: - yield job - - @staticmethod - def is_allowed(entity: Job, customer: Optional[str] = None, - tenants: Optional[List] = None) -> bool: - if customer and entity.customer_display_name != customer: - return False - if tenants and entity.tenant_display_name not in tenants: - return False - return True - - @staticmethod - def get_customer_jobs(customer_display_name, limit=None, offset=None): - jobs = [] - if limit and offset: - limit = limit + offset - elif not limit and offset: - limit = DEFAULT_LIMIT + offset - else: - offset = 0 - - condition = None + self._job.update(actions=self._actions) + self._actions.clear() - response = Job.customer_display_name_index.query( - scan_index_forward=False, - hash_key=customer_display_name, - limit=limit, - filter_condition=condition) - jobs.extend(list(response)) - last_evaluated_key = response.last_evaluated_key - - while last_evaluated_key: - if limit and len(jobs) >= limit: - return jobs[offset:] - try: - response = Job.customer_display_name_index.query( - scan_index_forward=False, - last_evaluated_key=last_evaluated_key, - hash_key=customer_display_name, - limit=limit, - filter_condition=condition) - jobs.extend(list(response)) - last_evaluated_key = response.last_evaluated_key - except ClientError as e: - if e.response['Error']['Code'] == \ - 'ProvisionedThroughputExceededException': - _LOG.warning('Request rate on CaaSJobs table is too ' - 'high!') - time_helper.wait(5) - else: - raise e - - if limit and len(jobs) >= limit: - return jobs[offset:] - return jobs - - @staticmethod - def save(job: Job): - job.save() - - @staticmethod - def get_job_dto(job: Job, params_to_exclude: set = None): - params_to_exclude = params_to_exclude or set() - job_data = job.get_json() - job_dto = {} - params_to_exclude |= JOB_DTO_SKIP_ATTRS - for attr_name, attr_value in job_data.items(): - if attr_name in params_to_exclude: - continue - else: - job_dto[attr_name] = attr_value - return job_dto - - @staticmethod - def get_customer_jobs_between_period( - start_period=None, end_period=None, customer=None, - tenant=None, only_succeeded: bool = True, limit: int = None, - attributes_to_get: list = None): - conditions = None - range_key_condition = None - if only_succeeded: - conditions = (Job.status == JOB_SUCCEEDED_STATUS) - if tenant: - conditions &= (Job.tenant_display_name == tenant) - if customer: - if start_period and end_period: - range_key_condition &= ( - Job.submitted_at.between(start_period.isoformat(), - end_period.isoformat())) - elif end_period: - range_key_condition &= ( - Job.submitted_at <= end_period.isoformat()) - elif start_period: - range_key_condition &= ( - Job.submitted_at >= start_period.isoformat()) - _cursor = Job.customer_display_name_index.query( - hash_key=customer, range_key_condition=range_key_condition, - filter_condition=conditions, limit=limit, - attributes_to_get=attributes_to_get) - items = list(_cursor) - last_evaluated_key = _cursor.last_evaluated_key - while last_evaluated_key: - try: - _cursor = Job.customer_display_name_index.query( - hash_key=customer, - last_evaluated_key=last_evaluated_key, - range_key_condition=range_key_condition, - filter_condition=conditions, limit=limit, - attributes_to_get=attributes_to_get - ) - items.extend(list(_cursor)) - last_evaluated_key = _cursor.last_evaluated_key - except ClientError as e: - if e.response['Error']['Code'] == \ - 'ProvisionedThroughputExceededException': - _LOG.warning( - 'Request rate on CaaSJobs table is too ' - 'high!') - time_helper.wait(5) - else: - raise e - else: - _cursor = Job.scan(filter_condition=conditions, limit=limit, - attributes_to_get=attributes_to_get) - items = list(_cursor) - last_evaluated_key = _cursor.last_evaluated_key - while last_evaluated_key: - try: - _cursor = Job.scan(filter_condition=conditions, - limit=limit, - attributes_to_get=attributes_to_get, - last_evaluated_key=last_evaluated_key) - items.extend(list(_cursor)) - last_evaluated_key = _cursor.last_evaluated_key - except ClientError as e: - if e.response['Error']['Code'] == \ - 'ProvisionedThroughputExceededException': - _LOG.warning( - 'Request rate on CaaSJobs table is too ' - 'high!') - time_helper.wait(5) - else: - raise e - return items - - @staticmethod - def set_job_failed_status(job, reason='Terminating job.'): - job.status = JOB_FAILED_STATUS - job.stopped_at = utc_iso() - job.reason = reason + @property + def job(self) -> Job: + return self._job + + def status(self, status: str | JobState): + if isinstance(status, str): + status = JobState(status) + self._actions.append(Job.status.set(status.value)) + + def reason(self, reason: str | None): + self._actions.append(Job.reason.set(reason)) + + def created_at(self, created_at: datetime | str): + if isinstance(created_at, datetime): + created_at = utc_iso(created_at) + self._actions.append(Job.created_at.set(created_at)) + + def started_at(self, started_at: datetime | str): + if isinstance(started_at, datetime): + started_at = utc_iso(started_at) + self._actions.append(Job.started_at.set(started_at)) + + def stopped_at(self, stopped_at: datetime | str): + if isinstance(stopped_at, datetime): + stopped_at = utc_iso(stopped_at) + self._actions.append(Job.stopped_at.set(stopped_at)) + + def queue(self, queue: str): + self._actions.append(Job.queue.set(queue)) + + def definition(self, definition: str): + self._actions.append(Job.definition.set(definition)) + + status = property(None, status) + reason = property(None, reason) + created_at = property(None, created_at) + started_at = property(None, started_at) + stopped_at = property(None, stopped_at) + queue = property(None, queue) + definition = property(None, definition) diff --git a/src/services/job_statistics_service.py b/src/services/job_statistics_service.py index 8632e9d26..48f2f64af 100644 --- a/src/services/job_statistics_service.py +++ b/src/services/job_statistics_service.py @@ -10,7 +10,7 @@ class JobStatisticsService: @staticmethod - def create(data: dict) -> JobStatistics: + def save(data: dict): result_data = {} for attribute in JobStatistics.get_attributes(): value = data.get(attribute) @@ -19,7 +19,7 @@ def create(data: dict) -> JobStatistics: result_data[attribute] = value if not result_data.get('id'): result_data['id'] = str(uuid.uuid4()) - return JobStatistics(**result_data) + JobStatistics(**result_data).save() @staticmethod def get(item_id: str) -> Optional[JobStatistics]: diff --git a/src/services/key_management_service.py b/src/services/key_management_service.py deleted file mode 100644 index 51b6c44a0..000000000 --- a/src/services/key_management_service.py +++ /dev/null @@ -1,203 +0,0 @@ -from services.clients.abstract_key_management import \ - AbstractKeyManagementClient, IKey, KEY_TYPE_ATTR, KEY_STD_ATTR, \ - HASH_TYPE_ATTR, HASH_STD_ATTR, SIG_SCHEME_ATTR - -from helpers.constants import KEY_ID_ATTR, ALGORITHM_ATTR, VALUE_ATTR, \ - B64ENCODED_ATTR -from helpers.log_helper import get_logger -from base64 import standard_b64encode -from typing import Optional - -PUBLIC_KEY_ATTR = 'puk' -PRIVATE_KEY_ATTR = 'prk' -ALG_ATTR = 'alg' -KID_ATTR = 'kid' -FORMAT_ATTR = 'format' - - -_LOG = get_logger(__name__) - - -class ManagedKey: - def __init__(self, kid: str, alg: str, key: IKey): - self.kid = kid - self.alg = alg - self.key = key - - def export_key(self, frmt: str, base64encode: bool = False): - try: - value = self.key.export_key(format=frmt) - except (TypeError, Exception) as e: - _LOG.warning(f'Key:\'{self.kid}\' could not be exported into ' - f'{frmt} format, due to: "{e}".') - value = None - - base = { - KEY_ID_ATTR: self.kid, - ALGORITHM_ATTR: self.alg - } - - pending = {} - - if value: - pending[FORMAT_ATTR] = frmt - pending[VALUE_ATTR] = value - - if pending[VALUE_ATTR] and base64encode: - pending[B64ENCODED_ATTR] = True - pending[VALUE_ATTR] = standard_b64encode( - value if isinstance(value, bytes) else value.encode('utf-8') - ) - elif pending[VALUE_ATTR] and not base64encode: - pending[B64ENCODED_ATTR] = False - - if pending[VALUE_ATTR] and isinstance(value, bytes): - - try: - pending[VALUE_ATTR] = value.decode() - except (TypeError, Exception) as e: - _LOG.warning(f'Key:\'{self.kid}\' could not be decoded into' - f' a string, due to: "{e}".') - pending = {} - - base.update(pending) - return base - - -class KeyPair: - def __init__(self, prk: IKey, typ: str, std: str): - self.prk: IKey = prk - self.puk: IKey = prk.public_key() - self.typ = typ - self.std = std - - -class KeyManagementService: - - def __init__( - self, key_management_client: AbstractKeyManagementClient - ): - self._key_management_client = key_management_client - - def get_key(self, kid: str, alg: str) -> Optional[ManagedKey]: - _alg = alg - alg = self._key_management_client.dissect_alg(alg=alg) - if not alg: - return - - # Retrieve type and standard data of a key, hash and signature scheme. - key_type, key_std = map( - alg.get, (KEY_TYPE_ATTR, KEY_STD_ATTR) - ) - - data = self._key_management_client.get_key_data( - key_id=kid - ) - if not data: - return - - key = self._key_management_client.get_key( - key_type=key_type, key_std=key_std, key_data=data - ) - if key: - return self.instantiate_managed_key(kid=kid, alg=_alg, key=key) - - def import_key(self, alg: str, key_value: str) -> Optional[IKey]: - alg = self._key_management_client.dissect_alg(alg=alg) - if not alg: - return - - # Retrieve type and standard data of a key, hash and signature scheme. - key_type, key_std, hash_type, hash_std, sig_scheme = map( - alg.get, ( - KEY_TYPE_ATTR, KEY_STD_ATTR, HASH_TYPE_ATTR, HASH_STD_ATTR, - SIG_SCHEME_ATTR - ) - ) - - if not self._key_management_client.is_signature_scheme_accessible( - sig_scheme=sig_scheme, key_type=key_type, key_std=key_std, - hash_type=hash_type, hash_std=hash_std - ): - return - return self._key_management_client.construct( - key_type=key_type, key_std=key_std, key_value=key_value - ) - - def import_key_pair(self, alg: str, private_key: str) -> Optional[KeyPair]: - alg = self._key_management_client.dissect_alg(alg=alg) - if not alg: - return - - # Retrieve type and standard data of a key, hash and signature scheme. - key_type, key_std, hash_type, hash_std, sig_scheme = map( - alg.get, ( - KEY_TYPE_ATTR, KEY_STD_ATTR, HASH_TYPE_ATTR, HASH_STD_ATTR, - SIG_SCHEME_ATTR - ) - ) - - if not self._key_management_client.is_signature_scheme_accessible( - sig_scheme=sig_scheme, key_type=key_type, key_std=key_std, - hash_type=hash_type, hash_std=hash_std - ): - return - prk = self._key_management_client.construct( - key_type=key_type, key_std=key_std, key_value=private_key - ) - if prk: - return KeyPair(prk=prk, typ=key_type, std=key_std) - - def create_key_pair(self, key_type: str, key_std: str) -> \ - Optional[KeyPair]: - prk = self._key_management_client.generate( - key_type=key_type, key_std=key_std - ) - if not prk: - return - - try: - return KeyPair(prk=prk, typ=key_type, std=key_std) - except (TypeError, Exception) as e: - _LOG.warning(f'KeyPair of {key_type}:{key_std} standard' - f' could not be instantiated, due to "{e}".') - return - - def save_key(self, kid: str, key: IKey, frmt: str) -> bool: - _log = _LOG.info - action = ' has been persisted.' - head = f'Key:\'{kid}\'' - persisted = self._key_management_client.save( - key_id=kid, key=key, key_format=frmt - ) - if not persisted: - _log = _LOG.warning - action = ' could not be persisted.' - _log(head + action) - return persisted - - def delete_key(self, kid: str) -> bool: - _log = _LOG.info - action = ' has been removed.' - head = f'Key:\'{kid}\'' - removed = self._key_management_client.delete(key_id=kid) - if not removed: - _log = _LOG.warning - action = ' could not be removed.' - _log(head + action) - return removed - - def derive_alg( - self, key_type: str, key_std: str, hash_type: str, hash_std: str, - sig_scheme: str - ) -> Optional[str]: - if self._key_management_client.is_signature_scheme_accessible( - sig_scheme=sig_scheme, hash_type=hash_type, hash_std=hash_std, - key_type=key_type, key_std=key_std - ): - return f'{key_type}:{key_std}_{sig_scheme}_{hash_type}:{hash_std}' - - @staticmethod - def instantiate_managed_key(kid: str, alg: str, key: IKey): - return ManagedKey(kid=kid, alg=alg, key=key) - diff --git a/src/services/license_manager_service.py b/src/services/license_manager_service.py index 207797d85..6cde2fb2b 100644 --- a/src/services/license_manager_service.py +++ b/src/services/license_manager_service.py @@ -1,35 +1,41 @@ -from datetime import timedelta +import re +import time +from datetime import datetime from functools import cached_property -from typing import Optional, List +from http import HTTPStatus from requests.exceptions import RequestException, ConnectionError, Timeout -from http import HTTPStatus -from helpers.constants import KID_ATTR, ALG_ATTR, CLIENT_TOKEN_ATTR + +from helpers.constants import KID_ATTR, ALG_ATTR, TOKEN_ATTR, EXPIRATION_ATTR from helpers.log_helper import get_logger -from helpers.time_helper import utc_datetime from services.clients.license_manager import LicenseManagerClientInterface, \ LicenseManagerClientFactory +from services.environment_service import EnvironmentService from services.setting_service import SettingsService -from services.token_service import TokenService +from services.ssm_service import SSMService CONNECTION_ERROR_MESSAGE = 'Can\'t establish connection with ' \ 'License Manager. Please contact the support team.' +SSM_LM_TOKEN_KEY = 'caas_lm_auth_token_{customer}' +DEFAULT_CUSTOMER = 'default' _LOG = get_logger(__name__) class LicenseManagerService: def __init__(self, settings_service: SettingsService, - token_service: TokenService): + ssm_service: SSMService, + environment_service: EnvironmentService): self.settings_service = settings_service - self.token_service = token_service + self.ssm_service = ssm_service + self.environment_service = environment_service @cached_property def client(self) -> LicenseManagerClientInterface: _LOG.debug('Creating license manager client inside LM service') return LicenseManagerClientFactory(self.settings_service).create() - def synchronize_license(self, license_key: str, expires: dict = None): + def synchronize_license(self, license_key: str): """ Mandates License synchronization request, delegated to prepare a custodian service-token, given the Service is the SaaS installation. @@ -40,7 +46,7 @@ def synchronize_license(self, license_key: str, expires: dict = None): :parameter expires: Optional[dict] :return: Union[Response, ConnectionError, RequestException] """ - auth = self._get_client_token(expires or dict(hours=1)) + auth = self._get_client_token() if not auth: _LOG.warning('Client authorization token could be established.') return None @@ -56,14 +62,13 @@ def synchronize_license(self, license_key: str, expires: dict = None): response = ConnectionError(CONNECTION_ERROR_MESSAGE) except RequestException as _re: - _LOG.warning(f'An exception occurred, during the request: {_re}.') + _LOG.exception('An exception occurred') response = RequestException(CONNECTION_ERROR_MESSAGE) return response def is_allowed_to_license_a_job(self, customer: str, tenant: str, - tenant_license_keys: List[str], - expires: dict = None) -> bool: + tenant_license_keys: list[str]) -> bool: """ License manager allows to check whether the job is allowed for multiple tenants. But currently for custodian we just need to check @@ -71,10 +76,9 @@ def is_allowed_to_license_a_job(self, customer: str, tenant: str, :param customer: :param tenant: :param tenant_license_keys: - :param expires: :return: """ - auth = self._get_client_token(expires or dict(hours=1)) + auth = self._get_client_token(customer=customer) if not auth: _LOG.warning('Client authorization token could be established.') return False @@ -85,10 +89,8 @@ def is_allowed_to_license_a_job(self, customer: str, tenant: str, ) def update_job_in_license_manager(self, job_id, created_at, started_at, - stopped_at, status, - expires: dict = None) -> Optional[int]: - - auth = self._get_client_token(expires or dict(hours=1)) + stopped_at, status) -> int | None: + auth = self._get_client_token() if not auth: _LOG.warning('Client authorization token could be established.') return None @@ -99,9 +101,8 @@ def update_job_in_license_manager(self, job_id, created_at, started_at, ) return getattr(response, 'status_code', None) - def activate_customer(self, customer: str, tlk: str, expires: dict = None - ) -> Optional[dict]: - auth = self._get_client_token(expires or dict(hours=1)) + def activate_customer(self, customer: str, tlk: str) -> dict | None: + auth = self._get_client_token(customer=customer) if not auth: _LOG.warning('Client authorization token could be established.') return None @@ -117,53 +118,75 @@ def activate_customer(self, customer: str, tlk: str, expires: dict = None response = _json.get('items') or [] return response[0] if len(response) == 1 else {} - def _get_client_token(self, expires: dict, **payload): + def _get_client_token(self, customer: str = None): + secret_name = self.get_ssm_auth_token_name(customer=customer) + cached_auth = self.ssm_service.get_secret_value( + secret_name=secret_name) or {} + cached_token = cached_auth.get(TOKEN_ATTR) + cached_token_expiration = cached_auth.get(EXPIRATION_ATTR) + + if (cached_token and cached_token_expiration and + not self.is_expired(expiration=cached_token_expiration)): + _LOG.debug(f'Using cached lm auth token.') + return cached_token + _LOG.debug(f'Cached lm auth token are not found or expired. ' + f'Generating new token.') + lifetime_minutes = self.environment_service.lm_token_lifetime_minutes() + token = self._generate_client_token( + lifetime=lifetime_minutes, + customer=customer + ) + + _LOG.debug(f'Updating lm auth token in SSM.') + secret_data = { + EXPIRATION_ATTR: int(time.time()) + lifetime_minutes * 60, + TOKEN_ATTR: token + } + self.ssm_service.create_secret_value( + secret_name=secret_name, + secret_value=secret_data + ) + return token + + @staticmethod + def is_expired(expiration: int): + now = int(datetime.utcnow().timestamp()) + return now >= expiration + + def _generate_client_token(self, lifetime: int, customer: str): """ Delegated to derive a custodian-service-token, encoding any given payload key-value pairs into the claims. - :parameter expires: dict, meant to store timedelta kwargs - :parameter payload: dict + :parameter lifetime: token lifetime in minutes + :parameter customer: str :return: Union[str, Type[None]] """ - token_type = CLIENT_TOKEN_ATTR + # not to bring cryptography to global + from services.license_manager_token import LicenseManagerToken key_data = self.client.client_key_data kid, alg = key_data.get(KID_ATTR), key_data.get(ALG_ATTR) if not (kid and alg): _LOG.warning('LicenseManager Client-Key data is missing.') return - - t_head = f'\'{token_type}\'' - encoder = self.token_service.derive_encoder( - token_type=CLIENT_TOKEN_ATTR, **payload + pem = self.ssm_service.get_secret_value( + secret_name=self.derive_client_private_key_id(kid) + ).get('value') + token = LicenseManagerToken( + customer=customer, + lifetime=lifetime, + kid=kid, + private_pem=pem.encode() ) - - if not encoder: - return None - - # Establish a kid reference to a key. - encoder.prk_id = self.derive_client_private_key_id( - kid=kid - ) - _LOG.info(f'{t_head} - {encoder.prk_id} private-key id has been ' - f'assigned.') - - encoder.kid = kid - _LOG.info(f'{t_head} - {encoder.kid} token \'kid\' has been assigned.') - - encoder.alg = alg - _LOG.info(f'{t_head} - {encoder.alg} token \'alg\' has been assigned.') - - encoder.expire(utc_datetime() + timedelta(**expires)) - try: - token = encoder.product - except (Exception, BaseException) as e: - _LOG.error(f'{t_head} could not be encoded, due to: {e}.') - token = None - - if not token: - _LOG.warning(f'{t_head} token could not be encoded.') - return token + return token.produce() @staticmethod def derive_client_private_key_id(kid: str): return f'cs_lm_client_{kid}_prk' + + @staticmethod + def get_ssm_auth_token_name(customer: str = None): + if customer: + customer = re.sub(r"[\s-]", '_', customer.lower()) + else: + customer = DEFAULT_CUSTOMER + return SSM_LM_TOKEN_KEY.format(customer=customer) diff --git a/src/services/license_manager_token.py b/src/services/license_manager_token.py new file mode 100644 index 000000000..bcd9a51b5 --- /dev/null +++ b/src/services/license_manager_token.py @@ -0,0 +1,105 @@ +# separate module in order not to require Crytography for all lambdas +import base64 +import json +import time + +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives.asymmetric.utils import \ + decode_dss_signature +from cryptography.hazmat.primitives.serialization import load_pem_private_key + +try: + from jwcrypto.jwa import _encode_int +except ImportError: + from binascii import unhexlify + + + def _encode_int(n, bits): + e = '{:x}'.format(n) + ilen = ((bits + 7) // 8) * 2 + return unhexlify(e.rjust(ilen, '0')[:ilen]) + + +class LicenseManagerToken: + """ + The thing is: license manager API is authenticated via JWT tokens, but + those are - custom tokens: they do not follow JOSE standards. They require + something like that: "ECC:p521_DSS_SHA:256" to be set to `alg` header, so + neither PyJWT nor jwcrypto support such tokens (as far as I investigated). + + Example header + + .. code-block:: json + + { + "typ": "client-token", + "alg": "ECC:p521_DSS_SHA:256", + "kid": "f20e1939-7d37-48ad-a9eb-35db6723b013" + } + + Example payload + + .. code-block:: json + + { + "customer": "EPAM Systems", + "exp": 1707502018.614004 + } + + We do not require PyJWT lib for application, but jwcrypto is used, though + not for this token. + Another thing: initially these tokens were implemented simultaneously by + one person both for Rule engine and for License Manager. Pycryptodome lib + is used by LM and was used by Rule Engine (before this implementation). + I did not like that 'cause we needed to bring Pycryptodome to lambda + bundles and that lib is huge. Besides, we already had Cryptography as + requirements. So, to sum up: LM uses Pycryptodome and to hell with that + (maybe will be rewritten someday). But for Rule engine I removed + Pycryptodome and use this custom token with Cryptography for signing. + + Currently, LM supports only one type of key-pairs: Elliptic Curve P-521, + so don't burden myself with other implementations. + + """ + __slots__ = ('kid', 'key', 'customer', 'lifetime') + + def __init__(self, kid: str, private_pem: bytes, customer: str, + lifetime: int): + """ + :param customer: + :param lifetime: lifetime in minutes + """ + self.kid = kid + self.key = load_pem_private_key(private_pem, None) + self.customer = customer + self.lifetime = lifetime + + @staticmethod + def _encode(data: bytes): + return base64.urlsafe_b64encode(data).replace(b'=', b'') + + def produce(self) -> str: + header = { + 'typ': 'client-token', + 'alg': 'ECC:p521_DSS_SHA:256', + 'kid': self.kid + } + payload = { + 'customer': self.customer, + 'exp': int(time.time()) + self.lifetime * 60 + } + message = b'.'.join([ + self._encode(json.dumps(part, separators=(',', ':')).encode()) + for part in (header, payload) + ]) + der_signature = self.key.sign( + message, + ec.ECDSA(hashes.SHA256()) + ) + # gives signature in DER format, need to convert to binary, because + # pycryptodome on LM side requires binary (ECDSA) format + size = self.key.key_size + r, s = decode_dss_signature(der_signature) + signature = _encode_int(r, size) + _encode_int(s, size) + return b'.'.join((message, self._encode(signature))).decode() diff --git a/src/services/license_service.py b/src/services/license_service.py index 2a31e5366..d065616f2 100644 --- a/src/services/license_service.py +++ b/src/services/license_service.py @@ -1,142 +1,238 @@ -from typing import Iterable, Union +import operator +from datetime import datetime +from itertools import chain +from typing import Iterable, TypedDict, Literal, Iterator, Any, Generator + +from modular_sdk.models.parent import Parent +from modular_sdk.commons.constants import ApplicationType, ParentType +from modular_sdk.models.application import Application +from modular_sdk.models.tenant import Tenant +from modular_sdk.services.application_service import ApplicationService +from modular_sdk.services.customer_service import CustomerService +from modular_sdk.services.parent_service import ParentService +from typing_extensions import NotRequired -from helpers.constants import CUSTOMERS_ATTR -from helpers.constants import TENANTS_ATTR, ATTACHMENT_MODEL_ATTR from helpers.log_helper import get_logger -from helpers.time_helper import utc_iso -from models.licenses import License -from models.licenses import PROHIBITED_ATTACHMENT, \ - PERMITTED_ATTACHMENT +from helpers.time_helper import utc_iso, utc_datetime from models.ruleset import Ruleset -from services.setting_service import SettingsService from services import SERVICE_PROVIDER +from services.modular_helpers import LinkedParentsIterator +from services.base_data_service import BaseDataService _LOG = get_logger(__name__) +PERMITTED_ATTACHMENT = 'permitted' +PROHIBITED_ATTACHMENT = 'prohibited' +ALLOWED_ATTACHMENT_MODELS = (PERMITTED_ATTACHMENT, PERMITTED_ATTACHMENT) -class LicenseService: - def __init__(self, settings_service: SettingsService): - self.settings_service = settings_service - @staticmethod - def get_license(license_id): - return License.get_nullable(hash_key=license_id) +class Allowance(TypedDict): + balance_exhaustion_model: Literal['collective', 'independent'] + job_balance: int + time_range: Literal['DAY', 'WEEK', 'MONTH'] - @staticmethod - def dto(_license: License) -> dict: - data = _license.get_json() - data.pop(CUSTOMERS_ATTR, None) - return data - @staticmethod - def scan(): - return License.scan() +class EventDriven(TypedDict, total=False): + active: bool + quota: int + last_execution: NotRequired[str] - @staticmethod - def list_licenses(license_key: str = None): - if license_key: - license_ = LicenseService.get_license(license_key) - return iter([license_, ]) if license_ else [] - return LicenseService.scan() - @staticmethod - def get_all_non_expired_licenses(): - return list(License.scan( - filter_condition=License.expiration > utc_iso() - )) +class Tenants(TypedDict, total=False): + tenant_license_key: str + tenants: list[str] + attachment_model: Literal['permitted', 'prohibited'] - @staticmethod - def get_event_driven_licenses(): - filter_condition = None - filter_condition &= License.expiration > utc_iso() - filter_condition &= License.event_driven.active == True - return list(License.scan( - filter_condition=filter_condition - )) - @staticmethod - def update_last_ed_report_execution(license: License, - last_execution_date: str): - license.event_driven.last_execution = last_execution_date - license.save() +class License: + _allowance = 'a' + _customers = 'c' + _event_driven = 'ed' + _ruleset_ids = 'r' + _expiration = 'e' + _latest_sync = 's' + __slots__ = ('_app', '_meta') - @staticmethod - def validate_customers(_license: License, allowed_customers: list): - license_customers = list(_license.customers) - if not allowed_customers: - return license_customers - return list(set(license_customers) & set(allowed_customers)) + def __init__(self, app: Application): + self._app = app + self._meta = app.meta.as_dict() - @staticmethod - def create(configuration): - return License(**configuration) + @property + def customer(self) -> str: + return self._app.customer_id + + @property + def application(self) -> Application: + """ + Meta will be set when you request the application + :return: + """ + self._app.meta = self._meta + return self._app + + @property + def license_key(self) -> str: + return self._app.application_id + + @property + def description(self) -> str: + return self._app.description + + @description.setter + def description(self, value: str): + self._app.description = value + + @property + def allowance(self) -> Allowance: + return self._meta.setdefault(self._allowance, {}) + + @allowance.setter + def allowance(self, value: Allowance): + self._meta[self._allowance] = value + + @property + def customers(self) -> dict[str, Tenants]: + return self._meta.setdefault(self._customers, {}) + + @customers.setter + def customers(self, value: dict[str, Tenants]): + self._meta[self._customers] = value + + @property + def event_driven(self) -> EventDriven: + return self._meta.setdefault(self._event_driven, {}) + + @event_driven.setter + def event_driven(self, value: EventDriven): + self._meta[self._event_driven] = value + + @property + def ruleset_ids(self) -> list[str]: + return self._meta.setdefault(self._ruleset_ids, []) + + @ruleset_ids.setter + def ruleset_ids(self, value: list[str]): + self._meta[self._ruleset_ids] = value + + @property + def expiration(self) -> datetime | None: + exp = self._meta.get(self._expiration) + if exp: + return utc_datetime(exp) + + @expiration.setter + def expiration(self, value: str | datetime): + if isinstance(value, datetime): + value = utc_iso(value) + self._meta[self._expiration] = value + + @property + def latest_sync(self) -> datetime | None: + exp = self._meta.get(self._latest_sync) + if exp: + return utc_datetime(exp) + + @latest_sync.setter + def latest_sync(self, value: str | datetime): + if isinstance(value, datetime): + value = utc_iso(value) + self._meta[self._latest_sync] = value + + def is_expired(self) -> bool: + exp = self.expiration + if not exp: + return True + return exp <= utc_datetime() - @staticmethod - def delete(license_obj: License): - return license_obj.delete() - def is_applicable_for_customer(self, license_key, customer): - license_ = self.get_license(license_id=license_key) - if not license_: - return False - return customer in license_.customers +class LicenseService(BaseDataService[License]): + def __init__(self, application_service: ApplicationService, + parent_service: ParentService, + customer_service: CustomerService): + super().__init__() + self._aps = application_service + self._ps = parent_service + self._cs = customer_service @staticmethod - def is_subject_applicable( - entity: License, customer: str, tenant: str = None - ): - """ - Predicates whether a subject, such a customer or a tenant within - said customer has access to provided license entity. - - Note: one must verify whether provided tenant belongs to the - provided customer, beforehand. - :parameter entity: License - :parameter customer: str - :parameter tenant: Optional[str] - :return: bool - """ - customers = entity.customers.as_dict() - scope: dict = customers.get(customer, dict()) + def to_licenses(it: Iterable[Application]) -> Iterator[License]: + return map(License, it) - model = scope.get(ATTACHMENT_MODEL_ATTR) - tenants = scope.get(TENANTS_ATTR, []) - retained, _all = tenant in tenants, not tenants - attachment = ( - model == PERMITTED_ATTACHMENT and (retained or _all), - model == PROHIBITED_ATTACHMENT and not (retained or _all) + def create(self, license_key: str, description: str, customer: str, + created_by: str, customers: dict | None = None, + expiration: str | None = None, + ruleset_ids: list[str] | None = None, + allowance: Allowance | None = None, + event_driven: EventDriven | None = None, + ) -> License: + app = self._aps.build( + customer_id=customer, + type=ApplicationType.CUSTODIAN_LICENSES.value, + description=description, + created_by=created_by, + application_id=license_key, + is_deleted=False, + meta={} ) - return (not tenant) or (tenant and any(attachment)) if scope else False + lic = License(app) + if expiration: + lic.expiration = expiration + if customers: + lic.customers = customers + if ruleset_ids: + lic.ruleset_ids = ruleset_ids + if allowance: + lic.allowance = allowance + if event_driven: + lic.event_driven = event_driven + return lic - @staticmethod - def is_expired(entity: License) -> bool: - if not entity.expiration: - return True - return entity.expiration <= utc_iso() + def dto(self, item: License) -> dict[str, Any]: + ls = item.latest_sync + ex = item.expiration + return { + 'license_key': item.license_key, + 'expiration': utc_iso(ex) if ex else None, + 'latest_sync': utc_iso(ls) if ls else None, + 'description': item.description, + 'ruleset_ids': item.ruleset_ids, + 'event_driven': item.event_driven, + 'allowance': item.allowance + } + + def get_nullable(self, license_key: str) -> License | None: + app = self._aps.get_application_by_id(license_key) + if not app: + return + return License(app) - def remove_rulesets_for_license(self, rulesets_ids: Iterable[str], - license_key: str): + def save(self, lic: License): + self._aps.save(lic.application) + + def delete(self, lic: License): + self._aps.force_delete(lic.application) + + @staticmethod + def remove_rulesets_for_license(lic: License): """ Removes rulesets items from DB completely only if license by key `license_key` is the only license by which there rulesets were received. In other case just removes the given license_key from `license_keys` list - :parameter rulesets_ids: List[str] - :parameter license_key: str """ - ruleset_service = SERVICE_PROVIDER.ruleset_service() # circular import + ruleset_service = SERVICE_PROVIDER.ruleset_service # circular import delete, update = [], [] - for _id in rulesets_ids: + for _id in lic.ruleset_ids: item = ruleset_service.by_lm_id(_id) if not item: _LOG.warning('Strangely enough -> ruleset by lm id not found') continue if len(item.license_keys) == 1 and \ - item.license_keys[0] == license_key: + item.license_keys[0] == lic.license_key: delete.append(item) else: item.license_keys = list(set(item.license_keys) - - {license_key}) + {lic.license_key}) update.append(item) with Ruleset.batch_write() as batch: for item in delete: @@ -144,35 +240,79 @@ def remove_rulesets_for_license(self, rulesets_ids: Iterable[str], for item in update: batch.save(item) - def remove_for_customer(self, _license: Union[License, str], - customer: str) -> None: + def batch_delete(self, items: Iterable[License]): + raise NotImplementedError() + + def batch_save(self, items: Iterable[License]): + raise NotImplementedError() + + def get_tenant_license(self, tenant: Tenant) -> License | None: """ - Performs dynamodb writes. - It handles both "old" and "new" business logic. - Currently, a license is supposed to have only one customer. In - such a case, the method will remove the license and its rule-sets - from DB whatsoever. But according to old logic, a license can be - given to multiple customers. If such a case happens, this method will - just remove the given customer from the given license. + Retrieves only one license, even though the model allows to have + multiple such linked licenses + (see services.modular_helpers.LinkedParentsIterator) + :param tenant: + :return: """ - _LOG.info(f'Removing license: {_license} for customer') - license_obj = _license if isinstance(_license, License) \ - else self.get_license(_license) - if not license_obj: - return - license_key = license_obj.license_key - customers = license_obj.customers.as_dict() - customers.pop(customer, None) - if not customers: # "new" logic - _LOG.info('No customers left in license. Removing it ') - self.delete(license_obj) - self.remove_rulesets_for_license( - rulesets_ids=list(license_obj.ruleset_ids or []), - license_key=license_key - ) - return - _LOG.warning(f'Somehow the license: {license_key} ' - f'has multiple customers. Keeping it..') - license_obj.customers = customers # "old" logic - license_obj.save() - return + pair = next(self.iter_tenant_licenses(tenant, limit=1), None) + if pair: + return pair[1] + + def iter_tenant_licenses(self, tenant: Tenant, limit: int | None = None + ) -> Generator[tuple[Parent, License], None, None]: + """ + Iterates over all licenses that are active for tenant + :param tenant: + :param limit: + :return: + """ + it = LinkedParentsIterator( + parent_service=self._ps, + tenant=tenant, + type_=ParentType.CUSTODIAN_LICENSES, + limit=limit + ) + yielded = set() + for parent in it: + aid = parent.application_id + if aid in yielded: + continue + app = self._aps.get_application_by_id(aid) + if app: + yield parent, License(app) + yielded.add(aid) + + @staticmethod + def is_subject_applicable(lic: License, customer: str, + tenant_name: str | None = None): + customers = lic.customers + scope: dict = customers.get(customer) or {} + + model = scope.get('attachment_model') + tenants = scope.get('tenants') or [] + retained, _all = tenant_name in tenants, not tenants + attachment = ( + model == PERMITTED_ATTACHMENT and (retained or _all), + model == PROHIBITED_ATTACHMENT and not (retained or _all) + ) + return (not tenant_name) or (tenant_name and any(attachment)) if scope else False + + def get_event_driven_licenses(self) -> Iterator[License]: + """ + I strongly object to such a method, i think the whole thing should be + refactored. + :return: + """ + names = map(operator.attrgetter('name'), self._cs.i_get_customer()) + licenses = self.to_licenses(chain.from_iterable( + self._aps.i_get_application_by_customer( + customer_id=name, + application_type=ApplicationType.CUSTODIAN_LICENSES, + deleted=False + ) for name in names + )) + now = utc_datetime() + return filter( + lambda lic: lic.event_driven.get('active') and lic.expiration and lic.expiration > now, + licenses + ) diff --git a/src/services/mappings_collector.py b/src/services/mappings_collector.py new file mode 100644 index 000000000..5e9d88379 --- /dev/null +++ b/src/services/mappings_collector.py @@ -0,0 +1,291 @@ +from functools import cached_property +from typing import TypedDict, TYPE_CHECKING + +from helpers.constants import RuleDomain +from services import SP + +if TYPE_CHECKING: + from services.rule_meta_service import RuleMetaModel + from services.s3_settings_service import S3SettingsService + + +class HumanData(TypedDict): + """ + Human-targeted info about rule + """ + article: str | None + impact: str + report_fields: list[str] + remediation: str + multiregional: bool + + +SeverityType = dict[str, str] # rule to severity +StandardType = dict[str, dict] # rule to standards map +MitreType = dict[str, dict] # rule to mitre map +ServiceSectionType = dict[str, str] # rule to service section +ServiceType = dict[str, str] # rule to service section +CategoryType = dict[str, str] # rule to category +CloudRulesType = dict[str, set[str]] # cloud to rules +Events = dict[str, dict[str, list[str]]] +HumanDataType = dict[str, HumanData] + + +class MappingsCollector: + """ + This class helps to retrieve specific projections from rule's meta and + keep them as mappings to allow more cost-effective access + """ + + def __init__(self): + """ + Compressor must implement compress and decompress. Use something from + standard library + """ + self._severity = {} + self._standard = {} + self._mitre = {} + self._service_section = {} + self._cloud_rules = {} + self._human_data = {} + self._category = {} + self._service = {} + + self._aws_standards_coverage = {} + self._azure_standards_coverage = {} + self._google_standards_coverage = {} + + self._aws_events = {} + self._azure_events = {} + self._google_events = {} + + def event_map(self, cloud: str) -> dict | None: + if cloud == RuleDomain.AWS: + return self._aws_events + if cloud == RuleDomain.AZURE: + return self._azure_events + if cloud == RuleDomain.GCP: + return self._google_events + + def add_meta(self, meta: 'RuleMetaModel'): + self._severity[meta.name] = meta.severity + self._mitre[meta.name] = meta.mitre + self._service_section[meta.name] = meta.service_section + self._category[meta.name] = meta.category + self._service[meta.name] = meta.service + self._standard[meta.name] = meta.standard + domain = meta.get_domain() + if domain: + self._cloud_rules.setdefault(domain.value, []).append(meta.name) + if meta.cloud: + self._cloud_rules.setdefault(meta.cloud, []).append(meta.name) + self._human_data[meta.name] = { + 'article': meta.article, + 'impact': meta.impact, + 'report_fields': meta.report_fields, + 'remediation': meta.remediation, + 'multiregional': meta.multiregional, + 'service': meta.service + } + + _map = self.event_map(domain) + if isinstance(_map, dict): + for source, names in meta.events.items(): + _map.setdefault(source, {}) + for name in names: # here already parsed, without ',' + _map[source].setdefault(name, []).append(meta.name) + + @property + def severity(self) -> SeverityType: + return self._severity + + @severity.setter + def severity(self, value: SeverityType): + self._severity = value + + @property + def standard(self) -> StandardType: + return self._standard + + @standard.setter + def standard(self, value: StandardType): + self._standard = value + + @property + def mitre(self) -> MitreType: + return self._mitre + + @mitre.setter + def mitre(self, value: MitreType): + self._mitre = value + + @property + def service_section(self) -> ServiceSectionType: + return self._service_section + + @service_section.setter + def service_section(self, value: ServiceSectionType): + self._service_section = value + + @property + def cloud_rules(self) -> CloudRulesType: + return self._cloud_rules + + @cloud_rules.setter + def cloud_rules(self, value: CloudRulesType): + self._cloud_rules = value + + @property + def human_data(self) -> HumanDataType: + return self._human_data + + @human_data.setter + def human_data(self, value: HumanDataType): + self._human_data = value + + @property + def service(self) -> ServiceType: + return self._service + + @service.setter + def service(self, value: ServiceType): + self._service = value + + @property + def category(self) -> CategoryType: + return self._category + + @category.setter + def category(self, value: CategoryType): + self._category = value + + @property + def aws_standards_coverage(self) -> dict: + return self._aws_standards_coverage + + @aws_standards_coverage.setter + def aws_standards_coverage(self, value: dict): + self._aws_standards_coverage = value + + @property + def azure_standards_coverage(self) -> dict: + return self._azure_standards_coverage + + @azure_standards_coverage.setter + def azure_standards_coverage(self, value: dict): + self._azure_standards_coverage = value + + @property + def google_standards_coverage(self) -> dict: + return self._google_standards_coverage + + @google_standards_coverage.setter + def google_standards_coverage(self, value: dict): + self._google_standards_coverage = value + + @property + def aws_events(self) -> Events: + return self._aws_events + + @aws_events.setter + def aws_events(self, value: Events): + self._aws_events = value + + @property + def azure_events(self) -> Events: + return self._azure_events + + @azure_events.setter + def azure_events(self, value: Events): + self._azure_events = value + + @property + def google_events(self) -> Events: + return self._google_events + + @google_events.setter + def google_events(self, value: Events): + self._google_events = value + + +class LazyLoadedMappingsCollector: + """ + Read only class which allows to load mappings lazily. Currently, it + loads them from S3. Readonly class + """ + + def __init__(self, collector: MappingsCollector, + s3_settings_service: 'S3SettingsService'): + self._collector = collector + self._s3_settings_service = s3_settings_service + + @classmethod + def build(cls) -> 'LazyLoadedMappingsCollector': + return cls( + collector=MappingsCollector(), + s3_settings_service=SP.s3_settings_service, + ) + + @cached_property + def category(self) -> CategoryType: + return self._s3_settings_service.rules_to_category() or {} + + @cached_property + def service(self) -> ServiceType: + return self._s3_settings_service.rules_to_service() or {} + + @cached_property + def severity(self) -> SeverityType: + return self._s3_settings_service.rules_to_severity() or {} + + @cached_property + def service_section(self) -> ServiceSectionType: + return self._s3_settings_service.rules_to_service_section() or {} + + @cached_property + def standard(self) -> StandardType: + return self._s3_settings_service.rules_to_standards() or {} + + @cached_property + def mitre(self) -> MitreType: + return self._s3_settings_service.rules_to_mitre() or {} + + @cached_property + def cloud_rules(self) -> CloudRulesType: + """ + This mapping is kind of special. It's just auxiliary. It means that + the functions, this mapping is designed for, can be used without the + map (obviously they will work worse, but still, they will work), + whereas all the other mappings are required for the functional they + provided for + :return: + """ + return self._s3_settings_service.cloud_to_rules() or {} + + @cached_property + def human_data(self) -> HumanDataType: + return self._s3_settings_service.human_data() or {} + + @cached_property + def aws_standards_coverage(self) -> dict: + return self._s3_settings_service.aws_standards_coverage() or {} + + @cached_property + def azure_standards_coverage(self) -> dict: + return self._s3_settings_service.azure_standards_coverage() or {} + + @cached_property + def google_standards_coverage(self) -> dict: + return self._s3_settings_service.google_standards_coverage() or {} + + @cached_property + def aws_events(self) -> Events: + return self._s3_settings_service.aws_events() or {} + + @cached_property + def azure_events(self) -> Events: + return self._s3_settings_service.azure_events() or {} + + @cached_property + def google_events(self) -> Events: + return self._s3_settings_service.google_events() or {} diff --git a/src/services/metrics_service.py b/src/services/metrics_service.py index e85e67fab..6755f3bdd 100644 --- a/src/services/metrics_service.py +++ b/src/services/metrics_service.py @@ -1,15 +1,19 @@ -from typing import List, Union, Iterable, Generator, Tuple, Set +import time +import uuid +from typing import List, Union, Iterable, Generator from pynamodb.exceptions import QueryError, PutError + from helpers import batches -from helpers import get_logger, generate_id, time_helper, hashable, filter_dict -from helpers.constants import MULTIREGION, AZURE_CLOUD_ATTR +from helpers import get_logger, hashable, filter_dict +from helpers.constants import GLOBAL_REGION, REPORT_FIELDS from models.customer_metrics import CustomerMetrics from models.tenant_metrics import TenantMetrics -from services.rule_meta_service import LazyLoadedMappingsCollector +from services.mappings_collector import LazyLoadedMappingsCollector +from services.sharding import ShardsCollection, BaseShardPart _LOG = get_logger(__name__) -ResourcesGenerator = Generator[Tuple[str, str, dict], None, None] +ResourcesGenerator = Generator[tuple[str, str, dict, float], None, None] class MetricsService: @@ -38,7 +42,7 @@ def batch_save(self, metrics: List[Union[CustomerMetrics, TenantMetrics]], 'ProvisionedThroughputExceededException': _LOG.warning('Request rate on CaaSTenantMetrics ' 'table is too high!') - time_helper.wait(retry_delay) + time.sleep(retry_delay) else: raise e @@ -50,10 +54,6 @@ def _batch_write(metrics: List[Union[CustomerMetrics, TenantMetrics]]): for m in metrics: batch.save(m) - def is_multiregional(self, rule: str) -> bool: - return self.mappings_collector.human_data.get( - rule, {}).get('multiregional') - @staticmethod def custom_attr(name: str) -> str: """ @@ -68,55 +68,28 @@ def is_custom_attr(name: str) -> bool: return name.startswith('c7n-service:') @staticmethod - def iter_resources(findings: dict - ) -> ResourcesGenerator: - """ - This generator goes through findings and yields a resource, it's rule - and region where it was found. - :param findings: raw findings - :yield: (rule, region, res dto) - """ - for rule, data in findings.items(): - for region, resources in (data.get('resources') or {}).items(): - for res in resources: - yield rule, region, res + def allow_only_regions(it: ResourcesGenerator, regions: set[str] + ) -> ResourcesGenerator: + for rule, region, dto, ts in it: + if region in regions: + yield rule, region, dto, ts - def expose_multiregional(self, it: ResourcesGenerator - ) -> ResourcesGenerator: - """ - This generator yields multiregional region in case the rule is - multiregional - :param it: - :return: - """ - for rule, region, dto in it: - if self.is_multiregional(rule): - _LOG.debug(f'Rule {rule} is multiregional. ' - f'Yielding multiregional region') - yield rule, MULTIREGION, dto - else: - yield rule, region, dto + def allow_only_resource_type(self, it: ResourcesGenerator, meta: dict, + resource_type: str + ) -> ResourcesGenerator: + for rule, region, dto, ts in it: + rt = self.adjust_resource_type(meta.get(rule, {}).get('resource')) + if rt == self.adjust_resource_type(resource_type): + yield rule, region, dto, ts @staticmethod - def allow_only_regions(it: ResourcesGenerator, regions: Set[str] - ) -> Iterable[Tuple[str, str, dict]]: - return filter( - lambda item: item[1] in regions, it - ) - - def allow_only_resource_type(self, it: ResourcesGenerator, findings: dict, - resource_type: str - ) -> Iterable[Tuple[str, str, dict]]: - def _check(item) -> bool: - rt = self.adjust_resource_type( - findings.get(item[0], {}).get('resourceType') - ) - return rt == resource_type - return filter(_check, it) + def iter_resources(it: Iterable[BaseShardPart]) -> ResourcesGenerator: + for part in it: + for res in part.resources: + yield part.policy, part.location, res, part.timestamp @staticmethod - def deduplicated(it: ResourcesGenerator, - ) -> ResourcesGenerator: + def deduplicated(it: ResourcesGenerator) -> ResourcesGenerator: """ This generator goes through resources and yields only unique ones within rule and region @@ -124,17 +97,16 @@ def deduplicated(it: ResourcesGenerator, :return: """ emitted = {} - for rule, region, dto in it: + for rule, region, dto, ts in it: _emitted = emitted.setdefault((rule, region), set()) _hashable = hashable(dto) if _hashable in _emitted: _LOG.debug(f'Duplicate found for {rule}:{region}') continue - yield rule, region, dto + yield rule, region, dto, ts _emitted.add(_hashable) - def custom_modify(self, it: ResourcesGenerator, - findings: dict + def custom_modify(self, it: ResourcesGenerator, meta: dict ) -> ResourcesGenerator: """ Some resources require special treatment. @@ -144,29 +116,29 @@ def custom_modify(self, it: ResourcesGenerator, 252 and other glue-catalog rules are not multiregional, but they also do not return unique information within region. :param it: - :param findings: + :param meta: :return: """ # TODO in case we need more business logic here, redesign this # solution. Maybe move this logic to a separate class - for rule, region, dto in it: - rt = findings.get(rule).get('resourceType') + for rule, region, dto, ts in it: + rt = meta.get(rule).get('resource') rt = self.adjust_resource_type(rt) if rt in ('glue-catalog', 'account'): _LOG.debug(f'Rule with type {rt} found. Adding region ' f'attribute to make its dto differ from ' f'other regions') dto[self.custom_attr('region')] = region - yield rule, region, dto elif rt == 'cloudtrail': - _region = region if dto.get('IsMultiRegionTrail'): _LOG.debug('Found multiregional trail. ' 'Moving it to multiregional region') - _region = MULTIREGION - yield rule, _region, dto - else: # no changes required - yield rule, region, dto + region = GLOBAL_REGION + yield rule, region, dto, ts + + def report_fields(self, rule: str) -> set[str]: + rf = set(self.mappings_collector.human_data.get(rule, {}).get('report_fields') or []) # noqa + return rf | REPORT_FIELDS def keep_report_fields(self, it: ResourcesGenerator) -> ResourcesGenerator: """ @@ -175,51 +147,31 @@ def keep_report_fields(self, it: ResourcesGenerator) -> ResourcesGenerator: :param it: :return: """ - for rule, region, dto in it: - report_fields = self.mappings_collector.human_data.get( - rule, {}).get('report_fields') or set() - filtered = filter_dict(dto, report_fields) + for rule, region, dto, ts in it: + filtered = filter_dict(dto, self.report_fields(rule)) filtered.update({ k: v for k, v in dto.items() if self.is_custom_attr(k) }) - yield rule, region, filtered + yield rule, region, filtered, ts - def create_resources_generator(self, findings: dict, cloud: str, + def create_resources_generator(self, collection: ShardsCollection, active_regions: Union[set, list] - ) -> Iterable[Tuple[str, str, dict]]: + ) -> ResourcesGenerator: # just iterate over resources - resources = self.iter_resources(findings) - - # change region to multiregional in case the rule is multiregional - if cloud != AZURE_CLOUD_ATTR: - # All the AZURE and GCP rules are technically multiregional. - # It means that one rule scan all the regions at once whereas - # one AWS rule must be executed separately for each region - # (except multiregional). So, after the following generator: - # - GCP, all the resources become multiregional because GCP meta - # contains multiregional=True - # - AZURE is exception, we distribute resources to regions - # manually based on 'location' attribute (this logic is - # currently inside executor). So here we don't need to make - # any changes with azure regions. - # - AWS - can contain both global and region-dependent resources. - # In case a global rule was scanned on different regions, the - # resources it finds will be duplicates. So here we must use - # multiregional from meta and perform additional processing - resources = self.expose_multiregional(resources) + resources = self.iter_resources(collection.iter_parts()) # modify dto for some exceptional rules, see generator's description - resources = self.custom_modify(resources, findings) + resources = self.custom_modify(resources, collection.meta) # keeping only report fields resources = self.keep_report_fields(resources) - # removing duplicates within rule-region + # removing duplicates within rule-region (probably no need) resources = self.deduplicated(resources) - # keeping only active regions and multiregion + # keeping only active regions and global return self.allow_only_regions(resources, - {MULTIREGION, *active_regions}) + {GLOBAL_REGION, *active_regions}) class CustomerMetricsService(MetricsService): @@ -230,8 +182,7 @@ def get_all_metrics(): @staticmethod def create(data) -> CustomerMetrics: - _id = generate_id() - return CustomerMetrics(**data, id=_id) + return CustomerMetrics(**data, id=str(uuid.uuid4())) @staticmethod def save(metrics: CustomerMetrics): @@ -295,7 +246,7 @@ def list_by_date_and_customer(date: str, customer: str, limit: int = 50): 'ProvisionedThroughputExceededException': _LOG.warning('Request rate on CaaSCustomerMetrics table ' 'is too high!') - time_helper.wait(10) + time.sleep(10) else: raise e return result @@ -325,8 +276,7 @@ def get_all_metrics(): @staticmethod def create(data) -> TenantMetrics: - _id = generate_id() - return TenantMetrics(**data, id=_id) + return TenantMetrics(**data, id=str(uuid.uuid4())) @staticmethod def save(metrics: TenantMetrics): @@ -363,7 +313,7 @@ def list_by_date_and_customer(date: str, customer: str, limit: int = 50): 'ProvisionedThroughputExceededException': _LOG.warning('Request rate on CaaSJobs table is too ' 'high!') - time_helper.wait(10) + time.sleep(10) else: raise e return result @@ -384,7 +334,7 @@ def get_by_tenant_date_type(tenant: str, date: str, 'ProvisionedThroughputExceededException': _LOG.warning('Request rate on CaaSCustomerMetrics table ' 'is too high!') - time_helper.wait(5) + time.sleep(5) else: raise e if metrics: diff --git a/src/services/modular_helpers.py b/src/services/modular_helpers.py new file mode 100644 index 000000000..effa5817c --- /dev/null +++ b/src/services/modular_helpers.py @@ -0,0 +1,331 @@ +""" +Provides some modular sdk helper functions and classes +""" + +from http import HTTPStatus +from typing import Iterable, Iterator, Literal +from typing_extensions import Self + +from modular_sdk.commons.constants import ParentScope, ParentType +from modular_sdk.models.parent import Parent +from modular_sdk.models.tenant import Tenant +from modular_sdk.services.parent_service import ParentService + +from helpers.constants import Cloud +from helpers import MultipleCursorsWithOneLimitIterator +from helpers.lambda_response import ResponseFactory + + +class ResolveParentsPayload: + __slots__ = ('parents', 'tenant_names', 'exclude_tenants', 'clouds', + 'all_tenants') + + def __init__(self, parents: list[Parent], tenant_names: set[str], + exclude_tenants: set[str], clouds: set[str], + all_tenants: bool): + self.parents = parents + self.tenant_names = tenant_names + self.exclude_tenants = exclude_tenants + self.clouds = clouds + self.all_tenants = all_tenants + + def __repr__(self) -> str: + inner = ', '.join( + f'{sl}={getattr(self, sl)}' + for sl in self.__slots__ if sl not in ('parents', ) + ) + return f'{self.__class__.__name__}({inner})' + + @classmethod + def from_parents_list(cls, parents: list[Parent]) -> Self: + """ + Makes parents payload from existing list of parents in such way that + this payload is complete. If split_into_to_keep_to_delete accepts an + objected built by this function it should always return an empty + to_delete set and all the parents inside to_keep. After that method + payload.tenant_names & payload.clouds & payload.exclude_tenants all + should be equal to set(), payload.all_tenants should be False + :param parents: + :return: + """ + tenant_names = set() + exclude_tenants = set() + clouds = set() + all_tenants = False + for parent in parents: + match parent.scope: + case ParentScope.SPECIFIC: + tenant_names.add(parent.tenant_name) + case ParentScope.DISABLED: + exclude_tenants.add(parent.tenant_name) + case _: # ParentScope.ALL + all_tenants = True + if parent.cloud: + clouds.add(parent.cloud) + return cls( + parents=parents, + tenant_names=tenant_names, + exclude_tenants=exclude_tenants, + clouds=clouds, + all_tenants=all_tenants + ) + + +def get_main_scope(parents: list[Parent] + ) -> Literal[ParentScope.ALL, ParentScope.SPECIFIC]: + """ + Currently we can have either ALL with disabled or SPECIFIC. + """ + if not parents: + return ParentScope.SPECIFIC + match parents[0].scope: + case ParentScope.ALL | ParentScope.DISABLED: + return ParentScope.ALL + case _: + return ParentScope.SPECIFIC + + +def split_into_to_keep_to_delete(payload: ResolveParentsPayload + ) -> tuple[set[Parent], set[Parent]]: + """ + It distributes the given parents list into two groups: parents that should + be kept and parents that should be removed (based on provided params). + After executing this method the payload will contain only those tenant + names for which parents should be created + Changes the payload in place + :param payload: + :return: + """ + to_delete = set() + to_keep = set() + + while payload.parents: + parent = payload.parents.pop() + if parent.scope == ParentScope.SPECIFIC and parent.tenant_name in payload.tenant_names: + to_keep.add(parent) + payload.tenant_names.remove(parent.tenant_name) + elif parent.scope == ParentScope.DISABLED and parent.tenant_name in payload.exclude_tenants: + to_keep.add(parent) + payload.exclude_tenants.remove(parent.tenant_name) + elif parent.scope == ParentScope.ALL and not parent.cloud and payload.all_tenants and not payload.clouds: + to_keep.add(parent) + payload.all_tenants = False + elif parent.scope == ParentScope.ALL and parent.cloud in payload.clouds and payload.all_tenants: + to_keep.add(parent) + payload.clouds.remove(parent.cloud) + if not payload.clouds: + payload.all_tenants = False + else: + to_delete.add(parent) + return to_keep, to_delete + + +def build_parents(payload: ResolveParentsPayload, + parent_service: ParentService, application_id: str, + customer_id: str, type_: ParentType, created_by: str, + description: str = 'Rule Engine auto-created parent', + meta: dict | None = None) -> set[Parent]: + """ + + :param payload: + :param parent_service: + :param application_id: + :param customer_id: + :param type_: + :param created_by: + :param description: + :param meta: + :return: + """ + meta = meta or {} + ps = parent_service + + to_create = set() + for tenant in payload.tenant_names: + to_create.add(ps.create_tenant_scope( + application_id=application_id, + customer_id=customer_id, + type_=type_, + tenant_name=tenant, + disabled=False, + created_by=created_by, + is_deleted=False, + description=description, + meta=meta, + )) + for tenant in payload.exclude_tenants: + to_create.add(ps.create_tenant_scope( + application_id=application_id, + customer_id=customer_id, + type_=type_, + tenant_name=tenant, + disabled=True, + created_by=created_by, + is_deleted=False, + description=description, + meta=meta, + )) + if payload.all_tenants: + if payload.clouds: + for cloud in payload.clouds: + to_create.add(ps.create_all_scope( + application_id=application_id, + customer_id=customer_id, + type_=type_, + created_by=created_by, + is_deleted=False, + description=description, + meta=meta, + cloud=cloud + )) + else: + to_create.add(ps.create_all_scope( + application_id=application_id, + customer_id=customer_id, + type_=type_, + created_by=created_by, + is_deleted=False, + description=description, + meta=meta, + )) + return to_create + + +def get_activation_dto(parents: Iterable[Parent]) -> dict: + result = { + 'activated_for_all': False, + 'within_clouds': [], + 'excluding': [], + 'activated_for': [] + } + for parent in parents: + match parent.scope: + case ParentScope.SPECIFIC: + result['activated_for'].append(parent.tenant_name) + case ParentScope.DISABLED: + result['excluding'].append(parent.tenant_name) + case _: # ALL + result['activated_for_all'] = True + if parent.cloud: + result['within_clouds'].append(parent.cloud) + if result['activated_for_all']: + result.pop('activated_for') + if not result['within_clouds']: + result.pop('within_clouds') + return result + + +class LinkedParentsIterator(Iterator[Parent]): + """ + Iterates over SPECIFIC and then ALL -scoped parents for specific tenant + and parent type. For each application checks whether there is a DISABLED + parent + """ + + # TODO, maybe move to modular sdk + + def __init__(self, parent_service: ParentService, tenant: Tenant, + type_: ParentType, limit: int | None = None, + check_disabled_for_specific: bool = False): + self._ps = parent_service + self._tenant = tenant + self._type = type_ + self._limit = limit + self._check_disabled_for_specific = check_disabled_for_specific + + # dict[application_id, disabled parent for tenant] + self._applications: dict[str, bool] = {} + + def __iter__(self) -> Iterator[Parent]: + self._applications.clear() + + def i_specific(limit): + return self._ps.get_by_tenant_scope( + customer_id=self._tenant.customer_name, + type_=self._type, + tenant_name=self._tenant.name, + disabled=False, + limit=limit + ) + + def i_cloud(limit): + return self._ps.get_by_all_scope( + customer_id=self._tenant.customer_name, + type_=self._type, + cloud=self._tenant.cloud, + limit=limit + ) + + def i_all(limit): + return self._ps.get_by_all_scope( + customer_id=self._tenant.customer_name, + type_=self._type, + limit=limit + ) + + self._it = iter(MultipleCursorsWithOneLimitIterator( + self._limit, i_specific, i_cloud, i_all + )) + return self + + def __next__(self) -> Parent: + _applications = self._applications + while True: + parent = next(self._it) + if (parent.scope == ParentScope.SPECIFIC.value + and not self._check_disabled_for_specific): + # return immediately ignoring disabled + return parent + + application_id = parent.application_id + if application_id not in _applications: + # looking for disabled + disabled = next(self._ps.i_list_application_parents( + application_id=application_id, + type_=self._type, + scope=ParentScope.DISABLED, + tenant_or_cloud=self._tenant.name, + limit=1 + ), None) + _applications[application_id] = not disabled + if _applications[application_id]: # if enabled + return parent + + +def is_tenant_valid(tenant: Tenant | None = None, + customer: str | None = None) -> bool: + if not tenant or (customer and tenant.customer_name != customer + or not tenant.is_active): + return False + return True + + +def assert_tenant_valid(tenant: Tenant | None = None, + customer: str | None = None): + if not is_tenant_valid(tenant, customer): + generic = 'No active tenant could be found.' + template = 'Active tenant \'{tdn}\' not found' + issue = template.format( + tdn=tenant.name) if tenant else generic + raise ResponseFactory(HTTPStatus.NOT_FOUND).message(issue).exc() + + +def get_tenant_regions(tenant: Tenant) -> set[str]: + """ + Returns active tenant's regions + """ + # Maestro's regions in tenants have attribute "is_active" ("act"). + # But currently (22.06.2023) they ignore it. They deem all the + # regions listed in an active tenant to be active as well. So do we + tenant_json = tenant.get_json() + return { + r.get('native_name') for r in tenant_json.get('regions') or [] + if r.get('is_hidden') is not True + } + + +def tenant_cloud(tenant: Tenant) -> Cloud | None: + try: + return Cloud[tenant.cloud.upper()] + except KeyError: + return diff --git a/src/services/modular_service.py b/src/services/modular_service.py deleted file mode 100644 index cc595bc78..000000000 --- a/src/services/modular_service.py +++ /dev/null @@ -1,1319 +0,0 @@ -from functools import cached_property -from http import HTTPStatus -from typing import Union, Type, Any, Set, Callable, Iterator, Dict, Optional, \ - List - -from modular_sdk.commons.constants import CUSTODIAN_TYPE, ParentType -from modular_sdk.commons.exception import ModularException -from modular_sdk.models.customer import Customer -from modular_sdk.models.tenant import Tenant -from modular_sdk.models.tenant_settings import TenantSettings -from pynamodb.expressions.condition import Condition - -from helpers import raise_error_response -from helpers.constants import ( - META_ATTR, ACTIVATION_DATE_ATTR, INHERIT_ATTR, VALUE_ATTR, - MODULAR_MANAGEMENT_ID_ATTR, MODULAR_CLOUD_ATTR, MODULAR_DISPLAY_NAME_ATTR, - MODULAR_READ_ONLY_ATTR, MODULAR_DISPLAY_NAME_TO_LOWER, MODULAR_CONTACTS, - MODULAR_SECRET, MODULAR_IS_DELETED, MODULAR_TYPE, MODULAR_DELETION_DATE, - MODULAR_PARENT_MAP, TENANT_ENTITY_TYPE -) -from helpers.log_helper import get_logger -from helpers.time_helper import utc_iso -from models.modular import BaseModel -from models.modular.application import Application -from models.modular.parents import Parent -from services.clients.modular import ModularClient - -_LOG = get_logger(__name__) - -PUBLIC_COMPLEMENT_ATTR = 'complement' -PROTECTED_COMPLEMENT_ATTR = '_complement' -PROTECTED_ENTITY_ATTR = '_entity' - -INITIALIZED_ATTR = '_initialized' -ENTITY_TYPE_ATTR = '_entity_type' -COMPLEMENT_TYPE_ATTR = '_complement_type' -DATA_AGGREGATE_ATTR = '_data_aggregate_attr' - -COMPLETE_ENTITY = '{entity} Complement' -PERSISTENCE_ERROR = ' does not exist' -INSTANTIATED = ' has been instantiated' -RETRIEVED = ' has been retrieved' - -METHOD_ATTR = 'method' -REFERENCE_ATTR = 'reference' - -ENTITY = 'an entity' -ENTITY_TEMPLATE = '{type}:\'{id}\'' -FETCHED_TEMPLATE = ' has fetched {}' -FAILED_TO_DERIVE = ' has failed to derive {}' -FAILED_TO_PERSIST = ' has failed to persist {}' -FAILED_TO_CEASE_PERSISTENCE = ' has failed to delete' -FAILED_TO_INSTANTIATE = ' could not instantiate a {}' -REASON = ', due to the following: "{}"' - -APPLICATION_ABSENCE = ' related Application absence.' -REMOVED = ' {} being removed' - -SAMPLE_PARENT_DESCRIPTION = 'Custodian \'{}\' Parent entity complement.' - -DEFAULT_CUSTOMER_INHERIT = False -DEFAULT_APPLICATION_ID = 'APPLICATION_ID_PLACEHOLDER' - -# Tenant region attrs -NATIVE_NAME_ATTR = 'native_name' -IS_ACTIVE_ATTR = 'is_active' - - -class Complemented: - """ - Mandates MaestroCommonDomainModel Entity infusion. - """ - - def __init__(self, entity: Optional[Union[Customer, Tenant]] = None, - complement: Optional[Union[Parent, TenantSettings]] = None): - """ - Either entity or complement must be provided - """ - if self._initialize(entity=entity, complement=complement): - self.__dict__[INITIALIZED_ATTR] = True - - def _initialize(self, entity: Optional[Union[Customer, Tenant]] = None, - complement: Optional[Union[Parent, TenantSettings]] = None - ) -> bool: - """ - Constructs a Complemented entity instance, initializing: - 1. Attributes to contain entity data: - - _entity - maintaining content of either Customer, Tenant - - _complement - maintaining content of either Parent, TenantSettings - 2. Attribute to contain types: - - _entity_type - maintaining either Customer, Tenant class - - _complement_type - maintaining either Parent, TenantSettings class - 3. Attribute `_data_aggregate_attr`, meant to reference data - aggregation in respect to the complement type. - - :parameter entity: Union[Customer, Tenant] - :parameter complement: Union[Parent, TenantSettings] - :return: bool - """ - if not (entity or complement): - return False - self._initialize_inner_attributes() - if entity: - _entity_type = entity.__class__ - assert _entity_type in self._infusion_reference - self.__setattr__(PROTECTED_ENTITY_ATTR, entity.get_json()) - self.__setattr__(ENTITY_TYPE_ATTR, _entity_type) - if complement: - _complement_type = complement.__class__ - assert _complement_type in self._infusion_reference.values() - self.__setattr__(PROTECTED_COMPLEMENT_ATTR, complement.get_json()) - - _aggregate_attr_reference = self._data_aggregate_attr_reference - _aggregate_atr = _aggregate_attr_reference.get(_complement_type) - - if _aggregate_atr is not None: # no need if properly configured - self.__setattr__(COMPLEMENT_TYPE_ATTR, _complement_type) - self.__setattr__(DATA_AGGREGATE_ATTR, _aggregate_atr) - return True - - def _initialize_inner_attributes(self): - """ - So that __getattr__ and existing properties do not cause recursion - """ - self.__setattr__(PROTECTED_ENTITY_ATTR, dict()) - self.__setattr__(ENTITY_TYPE_ATTR, None) - self.__setattr__(PROTECTED_COMPLEMENT_ATTR, dict()) - self.__setattr__(COMPLEMENT_TYPE_ATTR, None) - self.__setattr__(DATA_AGGREGATE_ATTR, None) - - @property - def entity(self) -> Optional[Union[Customer, Tenant]]: - """ - References the initialized entity, attached to the Parent. - :return: Type[BaseModel] - """ - _type = getattr(self, ENTITY_TYPE_ATTR) - if not _type: - return - return _type(**getattr(self, PROTECTED_ENTITY_ATTR)) - - @property - def complement(self) -> Optional[Union[Parent, TenantSettings]]: - """ - Produces a MaestroCommonDomainModel: Complement aggregated with - attached custodian:entity data, respectively stored under the - `meta` attribute. - :return: Parent - """ - _type = getattr(self, COMPLEMENT_TYPE_ATTR) - if not _type: - return - return _type(**getattr(self, PROTECTED_COMPLEMENT_ATTR)) - - @property - def initialized(self): - return self.__dict__.get(INITIALIZED_ATTR, False) - - @cached_property - def _infusion_reference(self): - return { - Customer: Parent, Tenant: TenantSettings - } - - @cached_property - def _data_aggregate_attr_reference(self): - return { - Parent: META_ATTR, TenantSettings: VALUE_ATTR - } - - def get_json(self): - """ - Produces a Data Transfer object of an Entity and complemented - Parent, adhering to the assigned access priority. - Note: look out for collisions. - :return: dict - """ - # Retrieve content of entities. - entity_d, complement_d = {}, {} - entity, complement = self.entity, self.complement - if entity: - entity_d.update(entity.get_json()) - if complement: - complement_d.update(complement.get_json()) - attr = getattr(self, DATA_AGGREGATE_ATTR) - metadata: dict = complement_d.get(attr, dict()) - entity_d.update(metadata) - return entity_d - - def __setattr__(self, name: str, value: Any): - """ - Alters Modular metadata, providing a new key-value pair. - :parameter name:str, attribute to store into the metadata. - :parameter value:Any, respective attribute value. - :return: Type[None] - """ - if self.initialized: - - complement: dict = getattr(self, PROTECTED_COMPLEMENT_ATTR) - attr = getattr(self, DATA_AGGREGATE_ATTR) - if complement and attr: - metadata: dict = complement.setdefault(attr, dict()) - metadata.update({name: value}) - else: # entity - entity: dict = getattr(self, PROTECTED_ENTITY_ATTR) - entity.update({name: value}) - else: - super().__setattr__(name, value) - - def __getattr__(self, attribute: str): - """ - Retrieves shared Parent-Entity attribute adhering - to the respective `access priority`. - :parameter attribute: str - :raises: AttributeError, given object has not been `initialized` - :return: Any - """ - if not self.initialized: - issue = f'\'{self.__class__.__name__}\' object has no attribute ' - issue += '\'{}\'' - raise AttributeError(issue.format(attribute)) - - complement: dict = getattr(self, PROTECTED_COMPLEMENT_ATTR) - attr = getattr(self, DATA_AGGREGATE_ATTR) - metadata: dict = complement.get(attr, dict()) - entity: dict = getattr(self, PROTECTED_ENTITY_ATTR) - - # Establish attribute reference. - order = (metadata, entity, complement) - for each in order: - if attribute in each: - break - else: - each = dict() - return each.get(attribute) - - -class ModularService: - """ - Mandates persistence of MaestroCommonDomainModel entities: - - Application, providing bare entity `query`; - - Customer, providing bare and complemented entity `query`; - - Tenant, providing bare entity `query`; - - Parent, providing bare entity `query` and complementing `update`. - - TenantSettings, providing complementing `update`. - """ - - def __init__(self, client: ModularClient): - self._client: ModularClient = client - - @property - def modular_client(self) -> ModularClient: - return self._client - - # Public Entity related actions - def get_dto(self, entity: Union[BaseModel, Complemented], *attributes): - """ - Retrieves Data Transfer Object of an entity, including provided - iterable name sequence of `attributes`. Given no preference - all retained attributes are retrieved. - :parameter entity: Union[BaseModel, Complemented] - :parameter attributes: Tuple[str] - :return: dict - """ - to_include = attributes or None - to_conceal = self._get_concealed_attributes(entity=entity) - source = self._get_entity_dto(entity=entity) - dto = dict() - for attribute in source: - approved = to_conceal and attribute not in to_conceal - demanded = to_include and attribute in to_include - if (approved or not to_conceal) and (demanded or not to_include): - dto[attribute] = source[attribute] - return dto - - def save(self, - entity: Union[Parent, TenantSettings, Application, Complemented]): - """ - Mandates persistence of Maestro Common Domain Model complementary - entities such as Parent and TenantSettings. - :parameter entity: Union[Parent, TenantSettings, Complemented] - :return: bool - """ - retainer: Callable = self._persistence_map.get(entity.__class__) - return retainer(entity) if retainer else False - - def delete(self, entity: Union[ - Parent, TenantSettings, Application, Complemented]): # noqa - """ - Mandates persistence-based removal of Maestro Common Domain Model - complementary entities such as Parent and TenantSettings. - :parameter entity: Union[Parent, TenantSettings, Complemented] - :return: bool - """ - eraser: Callable = self._erasure_map.get(entity.__class__) - return eraser(entity) if eraser else False - - @cached_property - def available_types(self) -> Set[str]: - return { - TENANT_ENTITY_TYPE, - 'RULESET_LICENSE_PRIORITY', # this one is built dynamically :(, - None # if none, just prefix is returned - } - - def entity_type(self, _type: Optional[str] = None) -> str: - """ - Builds type string for Maestro Parent and key string for - Maestro TenantSetting associated with Custodian - """ - assert _type in self.available_types, \ - 'Not available type. Add it to the set and tell about it Maestro' - if _type: - return f'{CUSTODIAN_TYPE}_{_type}' - return CUSTODIAN_TYPE - - # Public Customer related actions - - def get_complemented_customer( - self, customer: Union[str, Customer], - complement_type: str = None - ) -> Optional[Complemented]: - """ - DON'T use this method - Returns a complemented, based on a given type, Customer - Parent - object, even if customer's parent does not exist, creating said parent. - :parameter customer: Union[str, Customer] - :parameter complement_type: str, defaults to a generic complement - :return: Optional[Complemented] - """ - template = COMPLETE_ENTITY.format(entity=complement_type) - entity = customer if isinstance(customer, Customer) else \ - self._get_customer(customer) - retrieved = self._fetch_complemented_customer_parent( - customer=entity, complement_type=complement_type - ) if entity else None - if isinstance(retrieved, Customer): - _LOG.warning(template + PERSISTENCE_ERROR) - entity = self.create_complemented_customer_parent(retrieved) - if entity: - _LOG.info(template + INSTANTIATED) - elif isinstance(retrieved, Complemented): - template += f'\'{retrieved.entity.name}\':' - _LOG.info(template + RETRIEVED) - entity = retrieved - return entity - - def customer_inherit(self, customer: Union[str, Customer]) -> bool: - """ - Returns the value of the given customer's inherit attribute - retrieved from its parent. If the parent does not exist the default - value is returned. - :parameter customer: Union[str, Customer] - :return: bool - """ - _parent = self._fetch_customer_bound_parent(customer) - return (_parent.meta.as_dict() if _parent else {}).get( - INHERIT_ATTR) or DEFAULT_CUSTOMER_INHERIT - - @staticmethod - def does_customer_inherit(entity: Union[Complemented, Parent]): - _a_complement = False - if isinstance(entity, Complemented): - entity = entity.complement - _a_complement = True - if not isinstance(entity, Parent): - reason = 'does not contain' if _a_complement else 'is not' - _LOG.error(f'Given entity {reason} a Parent.') - entity = None - return (entity.meta.as_dict() if entity else {}).get( - INHERIT_ATTR) or DEFAULT_CUSTOMER_INHERIT - - def get_customer(self, customer: Union[str, Customer]) -> \ - Optional[Union[Customer, Complemented]]: - """ - Returns either a bare Customer, Customer-complemented-Parent or None. - :parameter customer: Union[str, Customer] - :return: Union[Customer, Complemented, Type[None]] - """ - return self._get_customer(customer=customer) - - def i_get_customer(self, iterator: Iterator[Union[str, Customer]]) -> \ - Iterator[Union[Customer, Complemented]]: - """ - Yields either a bare Customer or Customer-complemented-Parent, - adhering to the type of given `customer` parameter. - :parameter iterator: Iterator[Union[str, Customer]] - :return: Iterator[Union[Customer, Complemented]] - """ - return self._i_fetch_entity(iterator=iterator, key=Customer) - - def i_get_customers(self) -> Iterator[Customer]: - """ - Yields each MaestroCommonDomainModel:Customer entity. - :return: Iterator[Customer] - """ - return self._client.customer_service().i_get_customer() - - @staticmethod - def i_get_custodian_customer_name(iterator: Iterator[Application]) -> \ - Iterator[str]: - """ - Yields Custodian-Customer names out of an Application-iterator. - :parameter iterator: Iterator[Application] - :return: Iterator[str] - """ - for application in iterator: - if isinstance(application, Application): - yield application.customer_id - - def get_parent_application(self, parent: Parent) -> Optional[Application]: - if not parent.application_id: - return - application = self.get_application(parent.application_id) - if not application or application.is_deleted: - return - return application - - def get_tenant_application(self, tenant: Tenant, _type: ParentType - ) -> Optional[Application]: - """ - Resolved application from tenant - :param tenant: - :param _type: parent type, not tenant type - :return: - """ - parent = self.modular_client.parent_service().get_linked_parent_by_tenant(tenant, _type) # noqa - if not parent: - return - return self.get_parent_application(parent) - - def get_complemented_tenant( - self, tenant: Union[str, Tenant], - complement_type: str = TENANT_ENTITY_TYPE - ) -> Optional[Complemented]: - """ - Returns a complemented, based on a given type, Tenant - Settings - instance, creating Tenant-Settings entity, given one does not exist. - :parameter tenant: Union[str, Tenant] - :parameter complement_type: str - :return: Optional[Complemented] - """ - template = COMPLETE_ENTITY.format(entity=complement_type) - entity = tenant if isinstance(tenant, Tenant) else self._get_tenant( - tenant=tenant - ) - retrieved = self._fetch_complemented_tenant_settings( - tenant=entity, complement_type=complement_type - ) if entity else None - if isinstance(retrieved, Tenant): - _LOG.warning(template + PERSISTENCE_ERROR) - entity = self.create_complemented_tenant_settings( - tenant=retrieved, complement_type=complement_type - ) - _LOG.info(template + INSTANTIATED) - elif isinstance(retrieved, Complemented): - template += f'\'{retrieved.entity.name}\':' - _LOG.info(template + RETRIEVED) - entity = retrieved - return entity - - def get_tenant_bound_setting(self, tenant: Union[str, Tenant], - complemented_type: str = TENANT_ENTITY_TYPE - ) -> TenantSettings: - """ - Returns tenant-bound setting with given type creating the entity - if it does not exist yet. - """ - template = COMPLETE_ENTITY.format(entity=complemented_type) - - name = tenant.name if isinstance(tenant, Tenant) else tenant - setting = self._fetch_bare_tenant_settings( - tenant=name, complement_type=complemented_type - ) - if not setting: - _LOG.warning(template + PERSISTENCE_ERROR) - setting = self.create_tenant_settings( - tenant_name=name, - key=self.entity_type(complemented_type) - ) - _LOG.info(template + INSTANTIATED) - return setting - - @staticmethod - def get_tenant_regions(tenant: Tenant) -> Set[str]: - """ - Returns active tenant's regions - """ - # Maestro's regions in tenants have attribute "is_active" ("act"). - # But currently (22.06.2023) they ignore it. They deem all the - # regions listed in an active tenant to be active as well. So do we - tenant_json = tenant.get_json() - return { - r.get('native_name') for r in tenant_json.get('regions') or [] - if r.get('is_hidden') is not True - } - - def get_tenant(self, tenant: Union[str, Tenant]) -> \ - Optional[Union[Tenant, Complemented]]: - """ - Returns a bare MaestroCommonDomainModel Tenant. - Given that inquery has been unsuccessful, returns a None. - :parameter tenant: Union[str, Tenant] - :return: Optional[Union[Tenant, Complemented]] - """ - return self._get_tenant(tenant=tenant) - - def i_get_tenant(self, iterator: Iterator[Union[str, Tenant]]) -> \ - Iterator[Union[Tenant, Complemented]]: - """ - Yields a bare MaestroCommonDomainModel Tenant. - :parameter iterator: Iterator[Union[str, Customer]] - :return: Iterator[Union[Customer, Complemented]] - """ - return self._i_fetch_entity(iterator=iterator, key=Tenant) - - def i_get_customer_tenant(self, customer: str, name: str = None, - active: bool = True, limit: Optional[int] = None, - last_evaluated_key: Union[str, dict] = None): - """ - Yields MaestroCommonDomainModel Tenant entities of a particular - customer. Given a name of a tenants - singles out the said tenants - :parameter customer: str - :parameter name: str - :parameter active: bool - :parameter limit: Optional[int] - :parameter last_evaluated_key: Union[str, dict] - :return: Iterator[Tenant] - """ - service = self._client.tenant_service() - return service.i_get_tenant_by_customer( - customer_id=customer, tenant_name=name, active=active, - limit=limit, last_evaluated_key=last_evaluated_key - ) - - def i_get_tenants(self, active: bool = True, limit: Optional[int] = None, - last_evaluated_key: Union[str, dict] = None - ) -> Iterator[Tenant]: - """ - Yields each MaestroCommonDomainModel Tenant entity - :parameter active: bool - :parameter limit: Optional[int] - :parameter last_evaluated_key: Union[str, dict] - :return: Iterator[Tenant] - """ - service = self._client.tenant_service() - return service.i_scan_tenants(active, limit=limit, - last_evaluated_key=last_evaluated_key) - - def i_get_tenants_by_acc(self, acc: str, active: Optional[bool] = None, - limit: int = None, - last_evaluated_key: Union[dict, str] = None, - attrs_to_get: List[str] = None - ) -> Iterator[Tenant]: - """ - - :param acc: cloud identifier - :param active: - :param limit: - :param last_evaluated_key: - :param attrs_to_get: - :return: - """ - return self._client.tenant_service().i_get_by_acc( - acc, active, limit, last_evaluated_key, attrs_to_get) - - def i_get_tenants_by_accN(self, accN: str, active: Optional[bool] = None, - limit: int = None, - last_evaluated_key: Union[dict, str] = None, - attrs_to_get: List[str] = None - ) -> Iterator[Tenant]: - return self._client.tenant_service().i_get_by_accN( - accN, active, limit, last_evaluated_key, attrs_to_get) - - @staticmethod - def is_tenant_valid(tenant: Optional[Tenant] = None, - customer: Optional[str] = None) -> bool: - if not tenant or (customer and tenant.customer_name != customer - or not tenant.is_active): - return False - return True - - def assert_tenant_valid(self, tenant: Optional[Tenant] = None, - customer: Optional[str] = None): - if not self.is_tenant_valid(tenant, customer): - generic = 'No active tenant could be found.' - template = 'Active tenant \'{tdn}\' not found' - issue = template.format( - tdn=tenant.name) if tenant else generic - raise_error_response( - code=HTTPStatus.NOT_FOUND, content=issue - ) - - def i_get_tenant_by_display_name_to_lower( - self, lower_display_name: str, cloud: str = None, - active: Optional[bool] = None, limit: int = None, - last_evaluated_key: Union[dict, str] = None) -> List[Tenant]: - return self._client.tenant_service().i_get_by_dntl( - lower_display_name, cloud=cloud, active=active, limit=limit, - last_evaluated_key=last_evaluated_key - ) - - # Public Parent related actions - - def get_parent(self, parent: str): - """ - Returns a bare MaestroCommonDomainModel Parent. - Given that inquery has been unsuccessful, returns a None. - :parameter parent: str - :return: Union[Parent, Type[None]] - """ - return self._get_parent(parent=parent) - - def get_customer_bound_parent(self, customer: Union[str, Customer]): - """ - Returns either a bare Parent bound to a customer. - :parameter customer: Union[str, Customer] - :return: Union[Parent, Type[None]] - """ - return self._fetch_customer_bound_parent(customer=customer) - - def get_customer_bound_parents(self, customer: Union[str, Customer], - parent_type: Optional[ - Union[str, List[str]]] = None, - is_deleted: Optional[bool] = None, - meta_conditions: Optional[Condition] = None, - limit: Optional[int] = None - ) -> Iterator[Parent]: - name = customer.name if isinstance(customer, Customer) else customer - return self.modular_client.parent_service().i_get_parent_by_customer( - customer_id=name, - parent_type=parent_type, - is_deleted=is_deleted, - meta_conditions=meta_conditions, - limit=limit - ) - - def i_get_parent(self, iterator: Iterator[str]) -> \ - Iterator[Parent]: - """ - Yields a bare Maestro Common Domain Model Parent. - :parameter iterator: Iterator[str] - :return: Iterator[Parent] - """ - return self._i_fetch_entity(iterator=iterator, key=Tenant) - - def i_get_parents(self) -> Iterator[Parent]: - """ - Yields each MaestroCommonDomainModel Parent entity. - :return: Iterator[Parent] - """ - service = self._client.parent_service() - return iter(service.list()) - - def create_parent(self, customer_id: str, parent_type: str, - application_id: str, description: Optional[str] = None, - meta: Optional[dict] = None) -> \ - Union[Parent, Type[None]]: - """ - Mandates Maestro Common Domain Model Parent instantiation. - :parameter customer_id: str - :parameter application_id: str - :parameter parent_type: str - :parameter description: str - :parameter meta: dict - :return: Union[Parent, Type[None]] - """ - service = self._client.parent_service() - try: - return service.create( - application_id=application_id, - customer_id=customer_id, - parent_type=parent_type, - description=description, - meta=meta - ) - except (AttributeError, Exception): - return None - - # Public TenantSettings actions - def create_tenant_settings( - self, tenant_name: str, key: str, value: Optional[dict] = None - ) -> TenantSettings: - """ - Mandates Maestro Common Domain Model TenantSettings instantiation. - :parameter tenant_name: str - :parameter key: str - :parameter value: dict - :return: Union[TenantSettings, Type[None]] - """ - service = self._client.tenant_settings_service() - return service.create( - tenant_name=tenant_name, key=key, value=value - ) - - # Public Complemented actions - def create_complemented_customer_parent( - self, customer: Union[Customer, str] - ) -> Optional[Union[Complemented, Parent]]: - """ - Don't use it, the logicc is wrong - Mandates Maestro Common Domain Model Parent instantiation, - driven by a Customer entity, infused into a Complemented entity. - :parameter customer: Union[Customer, str] - :return: Optional[Union[Complemented, Parent]] - If customer object is given as parameters, complemented object is - returned. If customer's name is given, just parent object is returned - """ - name = customer.name if isinstance(customer, Customer) else customer - template = ENTITY_TEMPLATE.format(type='Customer', id=name) - - application = self._get_customer_application(customer=customer) - if not application: - absence = APPLICATION_ABSENCE - content = FAILED_TO_INSTANTIATE.format( - 'Parent') + ' due to' + absence + ' Using a mocked app id' - _LOG.warning(template + content) - application_id = DEFAULT_APPLICATION_ID - else: - application_id = application.application_id - - parent = self.create_parent( - application_id=application_id, - customer_id=name, - parent_type=self.entity_type(), - description=SAMPLE_PARENT_DESCRIPTION.format('customer'), - meta={ACTIVATION_DATE_ATTR: utc_iso()} - ) - if not parent: - return - return parent if not isinstance(customer, Customer) else Complemented( - entity=customer, complement=parent - ) - - def create_complemented_tenant_settings( - self, tenant: Tenant, complement_type: str - ): - """ - Mandates Maestro Common Domain TenantSettings instantiation, driven by - a Tenant entity, infused into a Complemented entity, based on a given - type. - :parameter tenant: Tenant - :parameter complement_type: str - :return: Optional[Complemented] - """ - settings = self.create_tenant_settings( - tenant_name=tenant.name, - key=self.entity_type(complement_type) - ) - return Complemented(entity=tenant, complement=settings) - - # Public Application related actions - - def get_application(self, application: str) -> Application: - """ - Returns a bare Maestro Common Domain Model Application. - Given that inquery has been unsuccessful, returns a None. - :parameter application: str - :return: Union[Application, Type[None]] - """ - return self._get_application(application=application) - - def get_applications(self, customer: Optional[str] = None, - _type: Optional[str] = CUSTODIAN_TYPE, - deleted: Optional[bool] = False, - limit: Optional[int] = None - ) -> Iterator[Application]: - return self.modular_client.application_service().list( - customer=customer, - _type=_type, - deleted=deleted, - limit=limit - ) - - def get_customer_application(self, customer: Union[str, Customer], - deleted: Optional[bool] = False): - """ - Returns a bare Maestro Common Domain Model Application, - related to a given customer, produced by a `fetcher`. - Given no aforementioned `fetcher` could be established, returns None. - :param deleted: - :parameter customer: Union[str] - :return: Union[Application, Type[None]] - """ - # TODO remove this method - return self._get_customer_application( - customer=customer, - deleted=deleted - ) - - def i_get_application(self, iterator: Iterator[str]) -> \ - Iterator[Application]: - """ - Yields a bare Maestro Common Domain Model Application - :parameter iterator: Iterator[Union[str, Customer]] - :return: Iterator[Application] - """ - return self._i_fetch_entity(iterator=iterator, key=Application) - - def create_application(self, customer: str, _type: Optional[str] = None, - description: Optional[ - str] = 'Custodian management application', - meta: Optional[dict] = None, - secret: Optional[str] = None): - return self.modular_client.application_service().create( - customer_id=customer, - type=_type, - description=description, - meta=meta, - secret=secret - ) - - # Protected entity-common related actions - - def _get_entity_dto(self, entity: Union[BaseModel, Complemented]) -> dict: - """ - Mediates data transfer object retrieval of a given - Maestro Common Domain Model entity. - Object resolution is based on the service-entity priority. - Given no sub-service is bound to an entity, a default `get_json` - retriever is used. - :parameter entity: Union[BaseModel, Complemented] - :return: Dict - """ - service_map = self._entity_service_reference_map - service = service_map.get(entity.__class__) - payload = [] - try: - retriever = service.get_dto - payload.append(entity) - except (AttributeError, Exception): - retriever = entity.get_json - return retriever(*payload) - - def _get_complemented_dto(self, complemented: Complemented) -> dict: - """ - Unlike Complemented.get_json it also takes into consideration - complemented entity service's `get_dto` method. - """ - payload = {} - entity, complement = complemented.entity, complemented.complement - if entity: - payload.update(self.get_dto(entity)) - if complement: - # it works - that is important :) - payload.update(complement.get_json().get( - getattr(complemented, DATA_AGGREGATE_ATTR), dict()) - ) - return payload - - def _get_concealed_attributes(self, - entity: Union[BaseModel, Complemented]): - """ - Mediates attributes to conceal retrieval for bare and complemented - entities. - :parameter entity: Union[BaseModel, Complemented] - :return: Iterable - """ - default = self._obscure_bare_entity - reference_map = self._concealed_attribute_method_map - obscure: Callable = reference_map.get(entity.__class__, default) - return obscure(entity) if obscure else tuple() - - def _obscure_bare_entity(self, entity: BaseModel): - """ - Returns attributes to obscure, based on a provided entity. - Given no said attributes have been found, returns an empty Iterable. - :parameter entity: Union[BaseModel, Complemented] - :return: Iterable - """ - reference: Dict[Type[BaseModel]] = self._entity_conceal_attribute_map() - return reference.get(entity.__class__, tuple()) - - def _obscure_complemented_entity(self, entity: Complemented): - """ - Returns Data Transfer Object as a JSON description of a - given entity model. - :parameter entity: Union[BaseModel, Complemented] - :return: Dict - """ - parent_attributes = self._obscure_bare_entity(entity.complement) - entity_attributes = self._obscure_bare_entity(entity.entity) - return entity_attributes + parent_attributes - - def _i_fetch_entity(self, iterator: Iterator, key: Type[Union[ - Customer, Parent, Tenant, Application]]) -> Iterator[ - Union[Customer, Parent, Tenant, Application, Complemented] - ]: - """ - Yields Maestro Common Domain Model entities out of a given iterator, - delegating derivation of said entities to a respective `fetcher` - methods, described in each `fetcher-map` driven by a provided `key` - and an item out of the aforementioned iterator. - :parameter iterator: Iterator - :parameter key: Type[Union[Customer, Parent, Tenant, Application]] - :return: Iterator - """ - - for item in iterator: - if item: - template = ENTITY_TEMPLATE.format(type=key.__name__, id=item) - - fetcher_map = self._fetcher_map.get(key, lambda: {}) - fetcher: Callable = fetcher_map.get(item.__class__, None) - entity = fetcher(item) if fetcher else None - if entity: - _LOG.debug(template + FETCHED_TEMPLATE.format(entity)) - yield entity - else: - _LOG.warning(template + FAILED_TO_DERIVE.format(ENTITY)) - - # Protected Customer related actions - - def _get_customer(self, customer: Union[str, Customer]): - """ - Returns either a bare Customer or Customer-complemented-Parent, - produced by a `fetcher`, adhering to the type of given `customer` - parameter. - Given no aforementioned `fetcher` could be established, returns None. - :parameter customer: Union[str, Customer] - :return: Union[Customer, Complemented, Type[None]] - """ - fetcher: Callable = self._customer_fetcher_map.get(customer.__class__) - return fetcher(customer=customer) if fetcher else None - - def _fetch_customer(self, customer: str): - service = self._client.customer_service() - return service.get(name=customer) if service else None - - # Protected Tenant related actions - - def _get_tenant(self, tenant: Union[str, Tenant]): - """ - Returns a bare Tenant entity produced by a `fetcher`. - Given no aforementioned `fetcher` could be established, returns None. - :parameter tenant: Union[str, Tenant] - :return: Optional[Union[Tenant, Complemented]] - """ - fetcher: Callable = self._tenant_fetcher_map.get(tenant.__class__) - return fetcher(tenant=tenant) if fetcher else None - - def _fetch_tenant(self, tenant: str): - service = self._client.tenant_service() - return service.get(tenant_name=tenant) if service else None - - # Protected Customer - Parent related actions - - def _fetch_customer_bound_parent(self, customer: Union[str, Customer]) -> \ - Union[Parent, Type[None]]: - """ - Mediates Customer related Parent entity retrieval, adhering to - the respective `fetcher` map. - :parameter customer: Union[str, Customer] - :return: Union[Parent, Type[None]] - """ - reference_map = self._customer_parent_fetcher_map - fetcher: Callable = reference_map.get(customer.__class__) - entity: Optional[Parent, Complemented] = fetcher(customer) if fetcher \ - else None - return getattr(entity, PUBLIC_COMPLEMENT_ATTR, entity) - - def _fetch_bare_customer_parent( - self, customer: str, complement_type: str = None - ) -> Union[Parent, Type[None]]: - """ - Returns a bare Customer related Parent entity. - :parameter customer: str, index reference. - :return: Union[Parent, Type[None]] - """ - id_reference = customer - type_reference = self.entity_type(complement_type) - service = self._client.parent_service() - return None if not service else next(service.i_get_parent_by_customer( - customer_id=id_reference, parent_type=type_reference - ), None) - - def _fetch_complemented_customer_parent( - self, customer: Customer, - complement_type: str = None - ) -> Union[Complemented, Customer]: - """ - Returns a Customer complemented Parent entity, which keeps - a backward reference to the said Customer. - :parameter customer: Customer - :return: Union[Complemented, Type[None]] - """ - parent = self._fetch_bare_customer_parent( - customer=customer.name, complement_type=complement_type - ) - return customer if not parent else Complemented( - entity=customer, complement=parent - ) - - # Protected Tenant - Tenant-Settings related actions - def _fetch_bare_tenant_settings(self, tenant: str, complement_type: str - ) -> Optional[TenantSettings]: - """ - Returns a TenantSettings entity of a given complement type, - related to a respective Tenant. - :parameter tenant: str, index reference. - :parameter complement_type: str - :return: Optional[TenantSettings] - """ - name_reference = tenant - # type reference - key_reference = self.entity_type(complement_type) - service = self._client.tenant_settings_service() - return next(service.i_get_by_tenant( - tenant=name_reference, key=key_reference - ), None) - - def _fetch_complemented_tenant_settings( - self, tenant: Tenant, complement_type: str = TENANT_ENTITY_TYPE - ) -> Union[Complemented, Tenant]: - """ - Returns a Tenant complemented entity - adhering to a given type, - storing backward reference to said Tenant. - :parameter tenant: Tenant - :parameter complement_type: str, defaults to a generic entity-type. - :return: Union[Tenant, Optional[Complemented]] - """ - settings = self._fetch_bare_tenant_settings( - tenant=tenant.name, complement_type=complement_type - ) - return tenant if not settings else Complemented( - entity=tenant, complement=settings - ) - - def _persist_tenant_settings(self, entity: TenantSettings): - """ - Mandates Maestro Common Domain Model TenantSettings entity persistence. - :parameter entity: TenantSettings - :return: bool, denotes whether entity has been retained - """ - service, retained = self._client.tenant_settings_service(), True - try: - service.save(tenant_setting=entity) - except (AttributeError, TypeError, Exception) as e: - _id, data = entity, dict() - if isinstance(entity, TenantSettings): - _id = f'{entity.tenant_name}\':\'{entity.key}' - data.update(entity.get_json()) - - template = ENTITY_TEMPLATE.format(type='Tenant', id=_id) - _LOG.error( - template + FAILED_TO_PERSIST.format(data) + REASON.format(e) - ) - - retained = False - - return retained - - def _remove_tenant_settings(self, entity: TenantSettings): - """ - Mandates Maestro Common Domain Model TenantSettings entity erasure. - :parameter entity: TenantSettings - :return: bool, denotes whether entity has been retained - """ - service, erased = self._client.tenant_settings_service(), True - try: - service.delete(entity=entity) - except (AttributeError, TypeError, Exception) as e: - is_of_type = isinstance(entity, TenantSettings) - _id = entity.tenant_name if is_of_type else entity - data = entity.get_json() if is_of_type else dict() - - template = ENTITY_TEMPLATE.format(type='TenantSettings', id=_id) - _LOG.error( - template + FAILED_TO_CEASE_PERSISTENCE.format(data) - + REASON.format(e) - ) - - erased = False - - return erased - - # Protected Parent related actions - - def _get_parent(self, parent: Union[str]) -> Optional[Parent]: - """ - Returns a bare Parent entity produced by a `fetcher`. - Given no aforementioned `fetcher` could be established, returns None. - :parameter parent: Union[str] - :return: Union[Parent, Type[None]] - """ - fetcher = self._parent_fetcher_map.get(parent.__class__) - return fetcher(parent=parent) if fetcher else None - - def _fetch_parent(self, parent: str): - service = self._client.parent_service() - return service.get_parent_by_id(parent_id=parent) if service else None - - def _persist_parent(self, parent: Parent): - """ - Mandates Maestro Common Domain Model Parent entity persistence. - :parameter parent: Parent - :return: bool, denotes whether entity has been retained - """ - service, retained = self._client.parent_service(), True - try: - service.save(parent=parent) - except (AttributeError, TypeError, Exception) as e: - _id = parent.parent_id if isinstance(parent, Parent) else parent - data = parent.get_json() if isinstance(parent, Parent) else dict() - - template = ENTITY_TEMPLATE.format(type='Parent', id=_id) - _LOG.error( - template + FAILED_TO_PERSIST.format(data) + REASON.format(e) - ) - - retained = False - - return retained - - def _remove_parent(self, parent: Parent) -> bool: - """ - Mandates Maestro Common Domain Model Parent entity erasure. - :parameter parent: Parent - :return: bool, denotes whether entity has been retained - """ - service, erased = self._client.parent_service(), True - try: - service.mark_deleted(parent=parent) - except ModularException as e: - if e.code != 200: # 200 in case already deleted - erased = False - return erased - - # Protected Complemented actions - def _persist_complemented(self, entity: Complemented): - """ - Delegates persistence of a Complemented Maestro Common Domain Entity. - :parameter entity: Complemented - :return: bool, denotes whether entity has been retained - """ - return self.save(entity=getattr(entity, PUBLIC_COMPLEMENT_ATTR, None)) - - def _remove_complemented(self, entity: Complemented): - """ - Delegates persistence of a Complemented Maestro Common Domain Entity. - :parameter entity: Complemented - :return: bool, denotes whether entity has been retained - """ - return self.delete( - entity=getattr(entity, PUBLIC_COMPLEMENT_ATTR, None) - ) - - # Protected Application related actions - - def _get_application(self, application: Union[str] - ) -> Optional[Application]: - """ - Returns a bare Application entity produced by a `fetcher`. - Given no aforementioned `fetcher` could be established, returns None. - :parameter application: Union[str] - :return: Union[Application, Type[None]] - """ - fetcher = self._application_fetcher_map.get(application.__class__) - return fetcher(application=application) if fetcher else None - - def _fetch_application(self, application: str): - service = self._client.application_service() - return service.get_application_by_id(application) if service else None - - def _get_customer_application( - self, customer: Union[str, Customer], deleted: bool = False - ) -> Optional[Application]: - """ - Returns very first Customer related Application entity - produced by a `fetcher`. - Given no aforementioned `fetcher` could be established, returns None. - :parameter customer: Union[str, Customer] - :return: Union[Application, Type[None]] - """ - reference = self._customer_application_fetcher_map - fetcher: Callable = reference.get(customer.__class__) - return fetcher(customer=customer, deleted=deleted) if fetcher else None - - def _fetch_direct_customer_application(self, customer: str, - deleted: bool = False) -> \ - Optional[Application]: - service = self._client.application_service() - return None if not service else next( - service.i_get_application_by_customer( - customer_id=customer, deleted=deleted, - application_type=self.entity_type() - ), None - ) - - def _fetch_entity_customer_application(self, customer: Customer, - deleted: bool = False) -> \ - Optional[Customer]: - return self._fetch_direct_customer_application( - customer=customer.name, deleted=deleted - ) - - def _persist_application(self, application: Application): - return self.modular_client.application_service().save(application) - - def _persist_tenant(self, tenant: Tenant): - return self.modular_client.tenant_service().save(tenant) - - def _remove_application(self, application: Application): - service, erased = self._client.application_service(), True - try: - service.mark_deleted(application=application) - except ModularException as e: - if e.code != 200: # 200 in case already deleted - erased = False - return erased - - # Protected reference map definitions - - @cached_property - def _entity_service_reference_map(self): - - _complemented_service = type('ComplementedService', (), { - 'get_dto': staticmethod(lambda c: self._get_complemented_dto(c)) - }) - - return { - Customer: self._client.customer_service(), - Tenant: self._client.tenant_service(), - Parent: self._client.parent_service(), - Application: self._client.application_service(), - - Complemented: _complemented_service - } - - @cached_property - def _concealed_attribute_method_map(self): - return { - Complemented: self._obscure_complemented_entity - } - - @cached_property - def _fetcher_method_map(self): - return { - Customer: self._get_customer, - Tenant: self._get_tenant, - Parent: self._get_parent, - Application: self._get_application - } - - @cached_property - def _fetcher_map(self): - return { - Customer: self._customer_fetcher_map, - Tenant: self._tenant_fetcher_map, - Parent: self._parent_fetcher_map, - Application: self._application_fetcher_map - } - - @cached_property - def _customer_fetcher_map(self): - return { - str: self._fetch_customer, - Customer: self._fetch_complemented_customer_parent - } - - @cached_property - def _tenant_fetcher_map(self): - return { - str: self._fetch_tenant, - Tenant: self._fetch_complemented_tenant_settings - } - - @cached_property - def _parent_fetcher_map(self): - return {str: self._fetch_parent} - - @cached_property - def _application_fetcher_map(self): - return {str: self._fetch_application} - - @cached_property - def _customer_application_fetcher_map(self): - return { - str: self._fetch_direct_customer_application, - Customer: self._fetch_entity_customer_application - } - - @cached_property - def _customer_parent_fetcher_map(self): - return { - str: self._fetch_bare_customer_parent, - Customer: self._fetch_complemented_customer_parent - } - - @cached_property - def _persistence_map(self): - return { - Parent: self._persist_parent, - TenantSettings: self._persist_tenant_settings, - Complemented: self._persist_complemented, - Application: self._persist_application, - Tenant: self._persist_tenant - } - - @cached_property - def _erasure_map(self): - return { - Parent: self._remove_parent, - TenantSettings: self._remove_tenant_settings, - Application: self._remove_application, - Complemented: self._remove_complemented, - } - - @staticmethod - def _entity_conceal_attribute_map(): - return { - Tenant: (MODULAR_MANAGEMENT_ID_ATTR, MODULAR_CLOUD_ATTR, - MODULAR_DISPLAY_NAME_ATTR, MODULAR_READ_ONLY_ATTR, - MODULAR_DISPLAY_NAME_TO_LOWER, MODULAR_CONTACTS, - MODULAR_PARENT_MAP), - Parent: (MODULAR_DELETION_DATE,), - Application: (MODULAR_SECRET, MODULAR_IS_DELETED, MODULAR_TYPE, - MODULAR_DELETION_DATE) - } diff --git a/src/services/obfuscation.py b/src/services/obfuscation.py new file mode 100644 index 000000000..e42656ada --- /dev/null +++ b/src/services/obfuscation.py @@ -0,0 +1,59 @@ +import uuid +from typing import TYPE_CHECKING + +from helpers import iter_values, flip_dict + +if TYPE_CHECKING: + from services.sharding import ShardsCollection + + +def obfuscate_finding(finding: dict, dictionary_out: dict, + dictionary: dict | None = None) -> dict: + """ + Changes the given finding in-place. Does not change dictionary but + writes into dictionary_out. Returns the same object that was given in + `finding` param + :param finding: + :param dictionary: + :param dictionary_out: + :return: + """ + dictionary = dictionary or {} + gen = iter_values(finding) + try: + real = next(gen) + gen_id = uuid.uuid4 + while True: + alias = dictionary_out.setdefault( + real, dictionary.get(real) or str(gen_id()) + ) + real = gen.send(alias) + except StopIteration: + pass + return finding + + +def obfuscate_collection(collection: 'ShardsCollection', + dictionary_out: dict): + """ + Changes everything in place + :param collection: + :param dictionary_out: + :return: + """ + for part in collection.iter_parts(): + for res in part.resources: + obfuscate_finding(res, dictionary_out) + + +def get_obfuscation_dictionary(collection: 'ShardsCollection') -> dict: + """ + Basically the same as obfuscate_collection but does some additional + boilerplate. I just cannot make up the right name for this function + :param collection: changed in place + :return: + """ + dictionary = {} + obfuscate_collection(collection, dictionary) + flip_dict(dictionary) + return dictionary diff --git a/src/services/openapi_spec_generator.py b/src/services/openapi_spec_generator.py new file mode 100644 index 000000000..95408b20b --- /dev/null +++ b/src/services/openapi_spec_generator.py @@ -0,0 +1,259 @@ +import re +from http import HTTPStatus +from typing import Iterable +from enum import Enum + +from pydantic import BaseModel +from typing_extensions import TypedDict, NotRequired + +from helpers import urljoin, dereference_json +from helpers.constants import HTTPMethod, Permission + + +class OpenApiInfoLicense(TypedDict): + name: str + url: str + + +class OpenApiInfo(TypedDict): + title: str + description: str + version: str + license: OpenApiInfoLicense + + +class OpenApiServer(TypedDict): + url: str + description: str + variables: dict[str, dict] + + +class OpenApiTag(TypedDict): + name: str + description: NotRequired[str] + + +class OpenApiComponents(TypedDict): + schemas: dict[str, dict] + securitySchemes: dict[str, dict] + + +class OpenApiV3(TypedDict): + openapi: str + info: OpenApiInfo + servers: list[OpenApiServer] + paths: dict[str, dict] + tags: list[OpenApiTag] + components: OpenApiComponents + + +class EndpointInfo: + """ + Just container for data + """ + __slots__ = ('path', 'method', 'summary', 'description', 'request_model', + 'responses', 'auth', 'tags', 'permission') + + def __init__(self, path: str | Enum, method: HTTPMethod, + summary: str | None = None, + description: str | None = None, + request_model: type[BaseModel] | None = None, + responses: list[tuple[HTTPStatus, type[BaseModel] | None, str | None]] | None = None, + auth: bool = True, tags: list[str] | None = None, + permission: Permission | None = None): + """ + + :param path: + :param method: + :param summary: + :param description: + :param request_model: + :param responses: list of tuples: (202, JobCreatedModel, 'description') + :param auth: + :param tags: + :param permission: permission that is necessary for this endpoint + """ + self.path: str = path.value if isinstance(path, Enum) else path + self.method: HTTPMethod = method + self.summary: str | None = summary + self.description: str | None = description + self.request_model: type[BaseModel] | None = request_model + self.responses: list = responses or [] + self.auth: bool = auth + self.tags: list = tags or [] + self.permission: Permission | None = permission + + +class OpenApiGenerator: + dynamic_resource_regex = re.compile(r'([^{/]+)(?=})') + + def __init__(self, title: str, description: str, url: list[str] | str, + stages: list[str] | str, version: str, + endpoints: Iterable[EndpointInfo], auto_tags: bool = True): + """ + :param title: + :param description: + :param url: + :param stages: + :param version: + :param endpoints: + :param auto_tags: resolves tags from path prefix. Only for endpoints + that don't have tags provided + """ + self._title = title + self._description = description + if isinstance(url, str): + self._urls = [url] + else: + self._urls = url + + assert stages, 'stages cannot be empty' + self._stages: list[str] | str = stages + self._version = version + self._endpoints = endpoints + self._auto_tags = auto_tags + + def _generate_base(self) -> OpenApiV3: + match self._stages: + case str(): + stages = {'default': self._stages, 'description': 'Main stage'} + case _: # list() + stages = { + 'default': self._stages[0], + 'description': 'Api stage', + 'enum': self._stages + } + return { + 'openapi': '3.0.3', + 'info': { + 'title': self._title, + 'description': self._description, + 'version': self._version, + 'license': { + 'name': 'Apache 2.0', + 'url': 'http://www.apache.org/licenses/LICENSE-2.0.html' + } + }, + 'servers': [{ + 'url': urljoin(url, '{stage}'), + 'description': 'Main url', + 'variables': {'stage': stages} + } for url in self._urls], + 'paths': {}, + 'tags': [], + 'components': { + 'schemas': {}, + 'securitySchemes': { + 'access_token': { + 'type': 'apiKey', + 'description': 'Simple token authentication. The same as AWS Cognito and AWS Api gateway integration has', + 'name': 'Authorization', + 'in': 'header', + } + } + } + } + + @staticmethod + def _model_schema(model: type[BaseModel]) -> dict: + sch = model.model_json_schema() + dereference_json(sch) + sch.pop('$defs', None) + return sch + + def _model_to_parameters(self, model: type[BaseModel]) -> list[dict]: + parameters = [] + sch = self._model_schema(model) + for name, field in model.model_fields.items(): + param = { + 'name': name, + 'in': 'query', + 'required': field.is_required(), + } + if d := field.description: + param['description'] = d + if e := field.examples: + param['example'] = e[0] + if s := sch.get('properties', {}).get(name): + param['schema'] = s + parameters.append(param) + return parameters + + def generate(self) -> OpenApiV3: + base = self._generate_base() + paths = base['paths'] + schemas = base['components']['schemas'] + tags = set() # todo allow to provide tags with description + for endpoint in self._endpoints: + data = paths.setdefault(endpoint.path, {}).setdefault( + endpoint.method.lower(), {}) + # tags + if self._auto_tags and not endpoint.tags: + tag = endpoint.path.strip('/').split('/')[0].title() + data['tags'] = [tag] + tags.add(tag) + else: + data['tags'] = endpoint.tags + tags.update(endpoint.tags) + + # path params + for slot in re.finditer(self.dynamic_resource_regex, + endpoint.path): + data.setdefault('parameters', []).append({ + 'name': slot.group(), + 'in': 'path', + 'required': True, + 'schema': {'type': 'string'} + }) + + # summary + if summary := endpoint.summary: + data['summary'] = summary + + # description + if description := endpoint.description: + data['description'] = description + + # request model / query params + if model := endpoint.request_model: + match endpoint.method: + case HTTPMethod.GET: + data.setdefault('parameters', []).extend( + self._model_to_parameters(model) + ) + case _: + name = model.__name__ + data['requestBody'] = { + 'content': { + 'application/json': { + 'schema': { + '$ref': f'#/components/schemas/{name}'} + } + }, + 'required': True + } + schemas[name] = self._model_schema(model) + + # auth + if endpoint.auth: + data['security'] = [{'access_token': []}] + + # responses + responses = data.setdefault('responses', {}) + for code, model, description in endpoint.responses: + resp = {} + if description: + resp['description'] = description + if model: + name = model.__name__ + resp['content'] = { + 'application/json': {'schema': { + '$ref': f'#/components/schemas/{name}' + }} + } + schemas[name] = self._model_schema(model) + responses[str(code.value)] = resp + + base['tags'].extend({'name': name} for name in sorted(tags)) + + return base diff --git a/src/services/platform_service.py b/src/services/platform_service.py new file mode 100644 index 000000000..8d816f81f --- /dev/null +++ b/src/services/platform_service.py @@ -0,0 +1,240 @@ +import hashlib +import json +import operator +import tempfile +import uuid +from pathlib import Path +from typing import Any, Optional + +from modular_sdk.commons.constants import ParentScope, ParentType +from modular_sdk.models.application import Application +from modular_sdk.models.parent import Parent +from modular_sdk.models.tenant import Tenant + +from helpers import NotHereDescriptor +from helpers.constants import PlatformType, GLOBAL_REGION +from services import SP +from services.base_data_service import BaseDataService + + +class Platform: + __slots__ = ('parent', 'application') + + def __init__(self, parent: Parent, application: Application | None = None): + self.parent = parent + self.application = application + + def __repr__(self) -> str: + return f'Platform<{self.id}>' + + @property + def id(self) -> str: + return self.parent.parent_id + + @property + def type(self) -> PlatformType: + return PlatformType[self.parent.meta['type']] + + @property + def name(self) -> str: + return self.parent.meta['name'] + + @property + def region(self) -> str | None: + try: + return self.parent.meta['region'] + except KeyError: + return + + @property + def tenant_name(self) -> str: + return self.parent.tenant_name + + @property + def description(self) -> str: + return self.parent.description + + @property + def customer(self) -> str: + return self.parent.customer_id + + @property + def application_meta(self) -> dict | None: + if self.application: + return self.application.meta.as_dict() + return + + @property + def platform_id(self) -> str: + """ + Some inner id based on name and region + :return: + """ + name = self.name + region = self.region or GLOBAL_REGION + return f'{name}-{region}' + + +class PlatformService(BaseDataService[Platform]): + batch_delete = NotHereDescriptor() + batch_write = NotHereDescriptor() + + def save(self, item: Platform): + """ + Application secret must be saved beforehand + :param item: + :return: + """ + item.parent.save() + if item.application: + item.application.save() + + def delete(self, item: Platform): + SP.modular_client.parent_service().mark_deleted(item.parent) + app = item.application + if app: + SP.modular_client.application_service().mark_deleted(app) + + def dto(self, item: Platform) -> dict[str, Any]: + return { + 'id': item.id, + 'name': item.name, + 'tenant_name': item.tenant_name, + 'type': item.type, + 'description': item.description, + 'region': item.region, + 'customer': item.customer + } + + @staticmethod + def generate_id(tenant_name: str, region: str, name: str) -> str: + hs = hashlib.md5( + ''.join((tenant_name, region, name)).encode()).digest() + return str(uuid.UUID(bytes=hs, version=3)) + + def create(self, tenant: Tenant, application: Application, + name: str, type_: PlatformType, created_by: str, + region: str | None, + description: str = 'Custodian created native k8s', + ) -> Platform: + parent = SP.modular_client.parent_service().build( + customer_id=tenant.customer_name, + application_id=application.application_id, + parent_type=ParentType.PLATFORM_K8S, + created_by=created_by, + description=description, + meta={'name': name, 'region': region, 'type': type_.value}, + scope=ParentScope.SPECIFIC, + tenant_name=tenant.name + ) + if type_ != PlatformType.SELF_MANAGED: + parent.parent_id = self.generate_id(tenant.name, region, name) + return Platform(parent=parent, application=application) + + def get_nullable(self, *args, **kwargs) -> Optional[Platform]: + parent = Parent.get_nullable(*args, **kwargs) + if not parent or parent.is_deleted: + return + return Platform(parent=parent) + + @staticmethod + def fetch_application(platform: Platform): + if platform.application: + return + platform.application = SP.modular_client.application_service().get_application_by_id( + platform.parent.application_id + ) + + +class Kubeconfig: + __slots__ = ('_raw',) + + def __init__(self, raw: dict | None = None): + raw = raw or {} + raw.setdefault('apiVersion', 'v1') + raw.setdefault('kind', 'Config') + raw.setdefault('clusters', []) + raw.setdefault('contexts', []) + raw.setdefault('users', []) + raw.setdefault('preferences', {}) + self._raw = raw + + @property + def raw(self) -> dict: + return self._raw + + @property + def current_context(self) -> str | None: + return self._raw.get('current-context') + + @current_context.setter + def current_context(self, value: str): + self._raw['current-context'] = value + + def cluster_names(self) -> map: + return map(operator.itemgetter('name'), self._raw['clusters']) + + def context_names(self) -> map: + return map(operator.itemgetter('name'), self._raw['contexts']) + + def user_names(self) -> map: + return map(operator.itemgetter('name'), self._raw['users']) + + def add_cluster(self, name: str, server: str, ca_data: Optional[str]): + if name in self.cluster_names(): + raise ValueError('cluster with such name exists') + data = {'name': name, 'cluster': {'server': server}} + if ca_data: + data['cluster']['certificate-authority-data'] = ca_data + self._raw['clusters'].append(data) + + def add_user(self, name: str, token: str): + if name in self.user_names(): + raise ValueError('user with such name exists') + self._raw['users'].append({ + 'name': name, + 'user': {'token': token} + }) + + def add_context(self, name: str, cluster: str, user: str): + if name in self.context_names(): + raise ValueError('context with such name exists') + self._raw['contexts'].append({ + 'name': name, + 'context': {'cluster': cluster, 'user': user} + }) + + def to_temp_file(self) -> Path: + with tempfile.NamedTemporaryFile(delete=False, mode='w') as fp: + json.dump(self.raw, fp, separators=(',', ':')) + return Path(fp.name) + + +class K8STokenKubeconfig: + __slots__ = ('endpoint', 'ca', 'token') + + def __init__(self, endpoint: str, ca: str | None = None, + token: str | None = None): + self.endpoint = endpoint + self.ca = ca + self.token = token + + def build_config(self, context: str = 'temp') -> dict: + config = Kubeconfig() + cluster = context + '-cluster' + user = context + '-user' + config.add_cluster(cluster, self.endpoint, self.ca) + config.add_user(user, self.token) + config.add_context(context, cluster, user) + config.current_context = context + return config.raw + + def to_temp_file(self, context: str = 'temp') -> Path: + config = Kubeconfig() + cluster = context + '-cluster' + user = context + '-user' + config.add_cluster(cluster, self.endpoint, self.ca) + config.add_user(user, self.token) + config.add_context(context, cluster, user) + config.current_context = context + return config.to_temp_file() diff --git a/src/services/rabbitmq_service.py b/src/services/rabbitmq_service.py index 301578f47..652ff0896 100644 --- a/src/services/rabbitmq_service.py +++ b/src/services/rabbitmq_service.py @@ -1,46 +1,117 @@ +from datetime import datetime from http import HTTPStatus -from typing import Optional +import json +import random +from typing import cast +from unittest.mock import MagicMock +from uuid import uuid4 -from modular_sdk.commons.constants import RABBITMQ_TYPE -from modular_sdk.services.impl.maestro_credentials_service import \ - MaestroCredentialsService, RabbitMQCredentials -from modular_sdk.services.impl.maestro_rabbit_transport_service import \ - MaestroRabbitMQTransport, MaestroRabbitConfig +from modular_sdk.commons import ModularException +from modular_sdk.commons.constants import ApplicationType +from modular_sdk.models.application import Application +from modular_sdk.modular import Modular +from modular_sdk.services.impl.maestro_credentials_service import ( + MaestroCredentialsService, + RabbitMQCredentials, +) +from modular_sdk.services.impl.maestro_rabbit_transport_service import ( + MaestroRabbitConfig, + MaestroRabbitMQTransport, +) -from helpers import get_logger, build_response -from models.modular.application import Application -from services.modular_service import ModularService +from helpers.lambda_response import ReportNotSendException, ResponseFactory +from helpers.log_helper import get_logger +from services import SP +from services import cache +from services.clients.s3 import S3Url +from services.environment_service import EnvironmentService _LOG = get_logger(__name__) +class MockedRabbitMQTransport: + """ + Fake version of MaestroRabbitMQTransport. Can be used for testing. + Writes each call to a specified location in s3 + """ + __slots__ = '_client', '_bucket', '_key', '_rate' + + def __init__(self, bucket: str, key: str, success_rate: float = 1.0): + self._client = SP.s3 + self._bucket = bucket + self._key = key + self._rate = success_rate + + def __getattr__(self, item): + _LOG.info(f'trying to access {item} of {self.__class__.__name__}. ' + f'Returning mock') + return MagicMock() + + def send_sync(self, **kwargs): + is_success = random.random() < self._rate + if is_success: + fmt = '%Y-%m-%d-%H-%M-%S-success.json' + else: + fmt = '%Y-%m-%d-%H-%M-%S-fail.json' + name = self._key.strip('/') + '/' + datetime.now().strftime(fmt) + data = json.dumps(kwargs, default=str, sort_keys=True, + separators=(',', ':')) + _LOG.info(f'Writing rabbitmq report to s3://{self._bucket}/{name}') + self._client.put_object( + bucket=self._bucket, + key=name, + body=data.encode(), + content_type='application/json', + ) + _LOG.info('Data was saved to s3') + if is_success: + _LOG.info('Mocking successful rabbit response') + return 200, 'SUCCESS', 'Successfully sent' + _LOG.info('Mocking failed rabbit response') + return 500, 'FAIL', 'Internal server error' + + class RabbitMQService: + __slots__ = ('modular_client', 'customer_rabbit_cache', + 'environment_service') - def __init__(self, modular_service: ModularService): - self.modular_service = modular_service + def __init__(self, modular_client: Modular, + environment_service: EnvironmentService): + self.modular_client = modular_client + self.environment_service = environment_service + self.customer_rabbit_cache = cache.factory() - def get_rabbitmq_application(self, customer: str) -> Optional[Application]: - # TODO cache - return next(self.modular_service.get_applications( + def get_rabbitmq_application(self, customer: str) -> Application | None: + aps = self.modular_client.application_service() + _LOG.debug(f'Getting rabbit mq application by customer: {customer}') + return next(aps.list( customer=customer, - _type=RABBITMQ_TYPE, + _type=ApplicationType.RABBITMQ.value, + deleted=False, limit=1 ), None) @staticmethod - def no_rabbit_configuration() -> dict: - return build_response( - code=HTTPStatus.SERVICE_UNAVAILABLE, - content='No valid RabbitMq configuration found' - ) + def no_rabbit_configuration(): + raise ResponseFactory(HTTPStatus.SERVICE_UNAVAILABLE).message( + 'No valid RabbitMq configuration found' + ).exc() def build_maestro_mq_transport(self, application: Application - ) -> Optional[MaestroRabbitMQTransport]: - # TODO cache - assert application.type == RABBITMQ_TYPE - modular = self.modular_service.modular_client + ) -> MaestroRabbitMQTransport | None: + if tpl := self.environment_service.mock_rabbitmq_s3_url(): + url, rate = tpl + parsed = S3Url(url) + _LOG.warning('Mocked rabbitmq specified in envs. Returning' + ' mocked client') + return cast(MaestroRabbitMQTransport, MockedRabbitMQTransport( + bucket=parsed.bucket, + key=parsed.key, + success_rate=rate + )) + assert application.type == ApplicationType.RABBITMQ.value mcs = MaestroCredentialsService.build( - ssm_service=modular.ssm_service() # not assume role ssm service + ssm_service=self.modular_client.ssm_service() # not assume role ssm service ) creds: RabbitMQCredentials = mcs.get_by_application(application) if not creds: @@ -53,7 +124,67 @@ def build_maestro_mq_transport(self, application: Application sdk_secret_key=creds.sdk_secret_key, maestro_user=creds.maestro_user ) - return modular.rabbit_transport_service( + return self.modular_client.rabbit_transport_service( connection_url=creds.connection_url, config=maestro_config ) + + @staticmethod + def send_notification_to_m3(command_name: str, + json_model: list | dict, + rabbitmq: MaestroRabbitMQTransport, + is_event_driven: bool = False): + _LOG.debug('Pushing to rabbitmq') + _LOG.debug(json.dumps(json_model)) + factory = ResponseFactory(HTTPStatus.INTERNAL_SERVER_ERROR) + try: + code, status, response = rabbitmq.send_sync( + command_name=command_name, + parameters=json_model, + is_flat_request=False, async_request=False, + secure_parameters=None, compressed=True) + _LOG.debug(f'Response code: {code}, response message: {response}') + if code == HTTPStatus.INTERNAL_SERVER_ERROR and \ + not is_event_driven: + raise factory.message(response).exc(ReportNotSendException) + return code + except ModularException as e: + _LOG.error(f'Modular error: {e}') + raise factory.message(str(e)).exc(ReportNotSendException) + except Exception as e: # can occur in case access data is invalid + _LOG.exception('An error occurred sending a message to rabbit') + raise factory.message(str(e)).exc(ReportNotSendException) + + @staticmethod + def build_m3_json_model(notification_type, data): + return { + 'viewType': 'm3', + 'model': { + "uuid": str(uuid4()), + "notificationType": notification_type, + "notificationAsJson": json.dumps(data, + separators=(",", ":")), + "notificationProcessorTypes": ["MAIL"] + } + } + + def get_customer_rabbitmq(self, customer: str + ) -> MaestroRabbitMQTransport | None: + if rabbitmq := self.customer_rabbit_cache.get(customer): + _LOG.debug('Rabbitmq found in cache') + return rabbitmq + + application = self.get_rabbitmq_application(customer) + if not application: + _LOG.warning( + f'No application with type {ApplicationType.RABBITMQ.value} ' + f'found for customer {customer}') + return + _LOG.debug(f'Application found: {application}') + rabbitmq = self.build_maestro_mq_transport(application) + if not rabbitmq: + _LOG.warning(f'Could not build rabbit client from application ' + f'for customer {customer}') + return + self.customer_rabbit_cache[customer] = rabbitmq + return rabbitmq diff --git a/src/services/rbac/__init__.py b/src/services/rbac/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/services/rbac/access_control_service.py b/src/services/rbac/access_control_service.py deleted file mode 100644 index 78a4782aa..000000000 --- a/src/services/rbac/access_control_service.py +++ /dev/null @@ -1,168 +0,0 @@ -from functools import cached_property -from itertools import chain -from typing import Set - -from helpers.log_helper import get_logger -from services.rbac.endpoint_to_permission_mapping import \ - ENDPOINT_PERMISSION_MAPPING, PERMISSIONS -from services.rbac.iam_cache_service import CachedIamService -from helpers.system_customer import SYSTEM_CUSTOMER - -_LOG = get_logger(__name__) - -PARAM_NAME = 'name' -PARAM_PERMISSIONS = 'permissions' -PARAM_EXPIRATION = 'expiration' -PARAM_REQUEST_PATH = 'request_path' -PARAM_TARGET_USER = 'target_user' - - -class AccessControlService: - - def __init__(self, iam_service: CachedIamService): - self._iam_service = iam_service # CachedIamService - - self._all_domains: Set[str] = set() - self._all_actions: Set[str] = set() - - @property - def allow_all(self) -> str: - return '*' - - @cached_property - def all_permissions(self) -> Set[str]: - return { - method.get(PERMISSIONS) for value in - ENDPOINT_PERMISSION_MAPPING.values() - for method in value.values() if method.get(PERMISSIONS) - } - - @cached_property - def permissions_to_exclude_for_admin(self) -> Set[str]: - return { - 'user:describe_customer', - 'user:assign_customer', - 'user:update_customer', - 'user:unassign_customer', - 'user:describe_role', - 'user:assign_role', - 'user:update_role', - 'user:unassign_role', - - "user:signup", - "customer:update_customer", - "license:create_license_sync", - "ruleset:get_content", - 'settings:describe_mail', - 'settings:create_mail', - 'settings:delete_mail', - - 'ruleset:describe_event_driven', - 'ruleset:create_event_driven', - 'ruleset:delete_event_driven', - 'run:initiate_standard_run' - } - - @cached_property - def permissions_to_exclude_for_user(self) -> Set[str]: - return self.permissions_to_exclude_for_admin | { - # "license:create_license", # endpoint does not exists - "license:remove_license", - - "user:describe_tenants", - "user:assign_tenants", - "user:update_tenants", - "user:unassign_tenants", - - "run:initiate_event_run", - - "report:department", - "report:clevel" - - "application:activate", - "application:describe", - } - - @cached_property - def admin_permissions(self) -> Set[str]: - return self.all_permissions - self.permissions_to_exclude_for_admin - - @cached_property - def user_permissions(self) -> Set[str]: - return self.all_permissions - self.permissions_to_exclude_for_user - - def is_permission_allowed(self, customer: str, permission: str) -> bool: - """ - Tells whether it's allowed for 'customer' to assign or access - permissions. - :param customer: customer who MAKES THE REQUEST, but NOT the actual - customer, whose policy is modified. - :param permission: permission the customer wants to assign - :return bool - The logic is fast-made and actually, some day may be completed to - something decent - """ - if customer != SYSTEM_CUSTOMER: # wildcards not allowed here - _scope = self.admin_permissions - return permission in _scope - # SYSTEM - domain, action = permission.split(':') - _domain_valid = domain == self.allow_all or domain in self.all_domains - _action_valid = action == self.allow_all or action in self.all_actions - return _domain_valid and _action_valid - - def _init_domains_actions(self): - for perm in self.all_permissions: - domain, action = perm.split(':') - self._all_domains.add(domain) - self._all_actions.add(action) - - @property - def all_domains(self) -> Set[str]: - if not self._all_domains: - self._init_domains_actions() - return self._all_domains - - @property - def all_actions(self) -> Set[str]: - if not self._all_actions: - self._init_domains_actions() - return self._all_actions - - def is_allowed_to_access(self, customer: str, role_name: str, - target_permission: str) -> bool: - role = self._iam_service.get_role(customer, role_name) - if not role: - return False - if self._iam_service.is_role_expired(role): - return False - # making efficient user's permission iterator - it = chain.from_iterable( - self._iam_service.i_policy_permissions(policy) - for policy in self._iam_service.i_role_policies(role) - ) - for permission in it: - if self.does_permission_match(target_permission, permission): - return True - return False - - def does_permission_match(self, target_permission: str, - permission: str) -> bool: - """ - Our permissions adhere to such a format "domain:action". - :param target_permission: permission a user want to access. - It's not supposed to contain '*'. Must be a solid perm. - to access one endpoint - :param permission: permission a user has - :return: - """ - if any(':' not in perm for perm in (target_permission, permission)): - return False - tp_domain, tp_action = map( - str.strip, target_permission.split(':', maxsplit=2)) - p_domain, p_action = map( - str.strip, permission.split(':', maxsplit=2)) - - _domain_match = tp_domain == p_domain or p_domain == self.allow_all - _action_match = tp_action == p_action or p_action == self.allow_all - return _domain_match and _action_match diff --git a/src/services/rbac/endpoint_to_permission_mapping.py b/src/services/rbac/endpoint_to_permission_mapping.py deleted file mode 100644 index 1aa9b0ff9..000000000 --- a/src/services/rbac/endpoint_to_permission_mapping.py +++ /dev/null @@ -1,812 +0,0 @@ -from helpers.constants import HTTPMethod -from validators.request_validation import JobGetModel, SoloJobGetModel, \ - JobPostModel, JobDeleteModel, TenantGetModel, CustomerGetModel, \ - PolicyGetModel, PolicyPostModel, PolicyPatchModel, PolicyDeleteModel, \ - PolicyCacheDeleteModel, RoleGetModel, RolePostModel, RolePatchModel, \ - RoleDeleteModel, RoleCacheDeleteModel, RuleUpdateMetaPostModel, \ - RuleSourceGetModel, RuleSourcePostModel, RuleSourcePatchModel, \ - RuleSourceDeleteModel, CredentialsManagerGetModel, \ - CredentialsManagerPostModel, CredentialsManagerPatchModel, \ - CredentialsManagerDeleteModel, SignUpPostModel, SignInPostModel, \ - UserDeleteModel, UserPasswordResetPostModel, UserCustomerGetModel, \ - UserCustomerPostModel, UserCustomerPatchModel, UserCustomerDeleteModel, \ - UserRoleGetModel, UserRolePostModel, UserRolePatchModel, \ - UserRoleDeleteModel, UserTenantsGetModel, UserTenantsPatchModel, \ - UserTenantsDeleteModel, EventPostModel, LicenseGetModel, \ - LicenseDeleteModel, LicenseSyncPostModel, FindingsGetModel, \ - FindingsDeleteModel, ScheduledJobGetModel, SoloScheduledJobGetModel, \ - ScheduledJobPostModel, \ - ScheduledJobDeleteModel, ScheduledJobPatchModel, \ - MailSettingGetModel, MailSettingPostModel, \ - LicenseManagerConfigSettingPostModel, TenantErrorReportGetModel, \ - LicenseManagerClientSettingPostModel, JobRuleReportGetModel, \ - SoleBatchResultsGetModel, BatchResultsGetModel, CLevelGetReportModel, \ - JobReportGetModel, TimeRangedTenantsReportGetModel, \ - TimeRangedTenantReportGetModel, LicenseManagerClientSettingsGetModel, \ - JobErrorReportGetModel, TenantsErrorReportGetModel, \ - TenantsRuleReportGetModel, TenantRuleReportGetModel, \ - JobComplianceReportGetModel, TenantComplianceReportGetModel, \ - TenantPostModel, TenantRegionPostModel, \ - TenantPatchModel, OperationalGetReportModel, DepartmentGetReportModel, \ - ProjectGetReportModel, ApplicationPostModel, ApplicationGetModel, \ - ApplicationPatchModel, ApplicationDeleteModel, \ - ParentGetModel, ParentListModel, ParentDeleteModel, \ - ParentPatchModel, HealthCheckGetModel, SoleHealthCheckGetModel, \ - StandardJobPostModel, LicenseManagerClientSettingDeleteModel, \ - ApplicationListModel, RabbitMQGetModel, RabbitMQPostModel, \ - RabbitMQDeleteModel, AccessApplicationDeleteModel, \ - AccessApplicationGetModel, AccessApplicationListModel, \ - AccessApplicationPatchModel, AccessApplicationPostModel, \ - ReportPushByJobIdModel, ReportPushMultipleModel, \ - DojoApplicationDeleteModel, DojoApplicationGetModel, \ - DojoApplicationListModel, DojoApplicationPostModel, \ - DojoApplicationPatchModel, ResourcesReportGet, ResourceReportJobsGet, \ - ResourceReportJobGet - -PERMISSIONS = 'permissions' -VALIDATION = 'validation' - -# You can specify a validator for an endpoint in two different ways: -# - you can specify it below using VALIDATION key. You must provide a -# pydantic model which contains query params/json body validation and -# optionally expected types for path params -# - you can leave VALIDATION empty and specify a Pydantic model as type hint -# for event inside the corresponding handler. Such validation models must -# be inherited from PreparedEvent model. See -# validators.request_validation.PlatformK8sPost and the place where it's used -ENDPOINT_PERMISSION_MAPPING = { - '/reports/push/dojo/{job_id}/': { - HTTPMethod.POST: { - PERMISSIONS: 'report:push_report_to_dojo', - VALIDATION: ReportPushByJobIdModel - } - }, - '/reports/push/security-hub/{job_id}/': { - HTTPMethod.POST: { - PERMISSIONS: 'report:push_report_to_security_hub', - VALIDATION: ReportPushByJobIdModel - } - }, - '/reports/push/dojo/': { - HTTPMethod.POST: { - PERMISSIONS: 'report:push_report_to_dojo', - VALIDATION: ReportPushMultipleModel - } - }, - '/reports/push/security-hub/': { - HTTPMethod.POST: { - PERMISSIONS: 'report:push_report_to_security_hub', - VALIDATION: ReportPushMultipleModel - } - }, - '/reports/operational/': { - HTTPMethod.GET: { - PERMISSIONS: 'report:operational', - VALIDATION: OperationalGetReportModel - } - }, - '/reports/project/': { - HTTPMethod.GET: { - PERMISSIONS: 'report:project', - VALIDATION: ProjectGetReportModel - } - }, - '/reports/department/': { - HTTPMethod.GET: { - PERMISSIONS: 'report:department', - VALIDATION: DepartmentGetReportModel - } - }, - '/reports/clevel/': { - HTTPMethod.GET: { - PERMISSIONS: 'report:clevel', - VALIDATION: CLevelGetReportModel - } - }, - '/jobs/standard/': { - HTTPMethod.POST: { - PERMISSIONS: 'run:initiate_standard_run', - VALIDATION: StandardJobPostModel - }, - }, - '/jobs/': { - HTTPMethod.GET: { - PERMISSIONS: 'run:describe_job', - VALIDATION: JobGetModel - }, - HTTPMethod.POST: { - PERMISSIONS: 'run:initiate_run', - VALIDATION: JobPostModel - }, - }, - '/jobs/{job_id}/': { - HTTPMethod.GET: { - PERMISSIONS: 'run:describe_job', - VALIDATION: SoloJobGetModel - }, - HTTPMethod.DELETE: { - PERMISSIONS: 'run:terminate_run', - VALIDATION: JobDeleteModel - } - }, - '/customers/': { - HTTPMethod.GET: { - PERMISSIONS: 'customer:describe_customer', - VALIDATION: CustomerGetModel - }, - # HTTPMethod.PATCH: { - # PERMISSIONS: 'customer:update_customer', - # VALIDATION: CustomerPatchModel - # }, - }, - '/tenants/': { - HTTPMethod.GET: { - PERMISSIONS: 'tenant:describe_tenant', - VALIDATION: TenantGetModel - }, - HTTPMethod.POST: { - PERMISSIONS: 'tenant:activate_tenant', - VALIDATION: TenantPostModel - }, - HTTPMethod.PATCH: { - PERMISSIONS: 'tenant:update_tenant', - VALIDATION: TenantPatchModel - } - }, - '/tenants/regions/': { - HTTPMethod.POST: { - PERMISSIONS: 'tenant:activate_region', - VALIDATION: TenantRegionPostModel - } - }, - '/policies/': { - HTTPMethod.GET: { - PERMISSIONS: 'iam:describe_policy', - VALIDATION: PolicyGetModel - }, - HTTPMethod.POST: { - PERMISSIONS: 'iam:create_policy', - VALIDATION: PolicyPostModel - }, - HTTPMethod.PATCH: { - PERMISSIONS: 'iam:update_policy', - VALIDATION: PolicyPatchModel - }, - HTTPMethod.DELETE: { - PERMISSIONS: 'iam:remove_policy', - VALIDATION: PolicyDeleteModel - } - }, - '/policies/cache/': { - HTTPMethod.DELETE: { - PERMISSIONS: 'iam:remove_policy_cache', # todo system: - VALIDATION: PolicyCacheDeleteModel - } - }, - '/roles/': { - HTTPMethod.GET: { - PERMISSIONS: 'iam:describe_role', - VALIDATION: RoleGetModel - }, - HTTPMethod.POST: { - PERMISSIONS: 'iam:create_role', - VALIDATION: RolePostModel - }, - HTTPMethod.PATCH: { - PERMISSIONS: 'iam:update_role', - VALIDATION: RolePatchModel - }, - HTTPMethod.DELETE: { - PERMISSIONS: 'iam:remove_role', - VALIDATION: RoleDeleteModel - } - }, - '/roles/cache/': { - HTTPMethod.DELETE: { - PERMISSIONS: 'iam:remove_role_cache', # todo system: - VALIDATION: RoleCacheDeleteModel - } - }, - '/rules/': { - HTTPMethod.GET: { - PERMISSIONS: 'rule:describe_rule', - }, - HTTPMethod.DELETE: { - PERMISSIONS: 'rule:remove_rule', - } - }, - '/rules/update-meta/': { - HTTPMethod.POST: { - PERMISSIONS: 'system:update_meta', - VALIDATION: RuleUpdateMetaPostModel - } - }, - '/backup/': { - HTTPMethod.POST: { - PERMISSIONS: 'system:create_backup', - VALIDATION: None - } - }, - '/metrics/update': { - HTTPMethod.POST: { - PERMISSIONS: 'system:update_metrics', - VALIDATION: None - } - }, - '/metrics/status': { - HTTPMethod.POST: { - PERMISSIONS: 'system:metrics_status', - VALIDATION: None - } - }, - '/rulesets/': { - HTTPMethod.GET: { - PERMISSIONS: 'ruleset:describe_ruleset', - }, - HTTPMethod.POST: { - PERMISSIONS: 'ruleset:create_ruleset', - }, - HTTPMethod.PATCH: { - PERMISSIONS: 'ruleset:update_ruleset', - }, - HTTPMethod.DELETE: { - PERMISSIONS: 'ruleset:remove_ruleset', - }, - }, - '/rulesets/content/': { - HTTPMethod.GET: { - PERMISSIONS: 'ruleset:get_content', - } - }, - '/rulesets/event-driven/': { - HTTPMethod.GET: { - PERMISSIONS: 'ruleset:describe_event_driven', - }, - HTTPMethod.POST: { - PERMISSIONS: 'ruleset:create_event_driven', - }, - HTTPMethod.DELETE: { - PERMISSIONS: 'ruleset:delete_event_driven', - }, - }, - '/rule-sources/': { - HTTPMethod.GET: { - PERMISSIONS: 'rule_source:describe_rule_source', - VALIDATION: RuleSourceGetModel - }, - HTTPMethod.POST: { - PERMISSIONS: 'rule_source:create_rule_source', - VALIDATION: RuleSourcePostModel - }, - HTTPMethod.PATCH: { - PERMISSIONS: 'rule_source:update_rule_source', - VALIDATION: RuleSourcePatchModel - }, - HTTPMethod.DELETE: { - PERMISSIONS: 'rule_source:remove_rule_source', - VALIDATION: RuleSourceDeleteModel - } - }, - '/accounts/credential_manager/': { - HTTPMethod.GET: { - PERMISSIONS: 'account:describe_credential_manager', - VALIDATION: CredentialsManagerGetModel - }, - HTTPMethod.POST: { - PERMISSIONS: 'account:create_credential_manager', - VALIDATION: CredentialsManagerPostModel - }, - HTTPMethod.PATCH: { - PERMISSIONS: 'account:update_credential_manager', - VALIDATION: CredentialsManagerPatchModel - }, - HTTPMethod.DELETE: { - PERMISSIONS: 'account:remove_credential_manager', - VALIDATION: CredentialsManagerDeleteModel - } - }, - '/signup/': { - HTTPMethod.POST: { - PERMISSIONS: 'user:signup', - VALIDATION: SignUpPostModel - } - }, - '/signin/': { - HTTPMethod.POST: { - PERMISSIONS: None, - VALIDATION: SignInPostModel - } - }, - '/users/': { - HTTPMethod.DELETE: { - PERMISSIONS: 'user:delete_users', - VALIDATION: UserDeleteModel - } - }, - '/users/password-reset/': { - HTTPMethod.POST: { - PERMISSIONS: 'user:reset_password', - VALIDATION: UserPasswordResetPostModel - } - }, - '/users/customer/': { - HTTPMethod.GET: { - PERMISSIONS: 'user:describe_customer', - VALIDATION: UserCustomerGetModel - }, - HTTPMethod.POST: { - PERMISSIONS: 'user:assign_customer', - VALIDATION: UserCustomerPostModel - }, - HTTPMethod.PATCH: { - PERMISSIONS: 'user:update_customer', - VALIDATION: UserCustomerPatchModel - }, - HTTPMethod.DELETE: { - PERMISSIONS: 'user:unassign_customer', - VALIDATION: UserCustomerDeleteModel - } - }, - '/users/role/': { - HTTPMethod.GET: { - PERMISSIONS: 'user:describe_role', - VALIDATION: UserRoleGetModel - }, - HTTPMethod.POST: { - PERMISSIONS: 'user:assign_role', - VALIDATION: UserRolePostModel - }, - HTTPMethod.PATCH: { - PERMISSIONS: 'user:update_role', - VALIDATION: UserRolePatchModel - }, - HTTPMethod.DELETE: { - PERMISSIONS: 'user:unassign_role', - VALIDATION: UserRoleDeleteModel - } - }, - '/users/tenants/': { - HTTPMethod.GET: { - PERMISSIONS: 'user:describe_tenants', - VALIDATION: UserTenantsGetModel - }, - HTTPMethod.PATCH: { - PERMISSIONS: 'user:update_tenants', - VALIDATION: UserTenantsPatchModel - }, - HTTPMethod.DELETE: { - PERMISSIONS: 'user:unassign_tenants', - VALIDATION: UserTenantsDeleteModel - } - }, - '/event/': { - HTTPMethod.POST: { - PERMISSIONS: 'run:initiate_event_run', - VALIDATION: EventPostModel - } - }, - '/license/': { - HTTPMethod.GET: { - PERMISSIONS: 'license:describe_license', - VALIDATION: LicenseGetModel - }, - # HTTPMethod.POST: { - # PERMISSIONS: 'license:create_license', - # VALIDATION: LicensePostModel - # }, - HTTPMethod.DELETE: { - PERMISSIONS: 'license:remove_license', - VALIDATION: LicenseDeleteModel - } - }, - '/license/sync/': { - HTTPMethod.POST: { - PERMISSIONS: 'license:create_license_sync', - VALIDATION: LicenseSyncPostModel - } - }, - '/findings/': { - HTTPMethod.GET: { - PERMISSIONS: 'findings:describe_findings', - VALIDATION: FindingsGetModel - }, - HTTPMethod.DELETE: { - PERMISSIONS: 'findings:remove_findings', - VALIDATION: FindingsDeleteModel - } - }, - '/scheduled-job/': { - HTTPMethod.GET: { - PERMISSIONS: 'scheduled-job:describe', - VALIDATION: ScheduledJobGetModel - }, - HTTPMethod.POST: { - PERMISSIONS: 'scheduled-job:register', - VALIDATION: ScheduledJobPostModel - }, - - }, - '/scheduled-job/{name}/': { - HTTPMethod.DELETE: { - PERMISSIONS: 'scheduled-job:deregister', - VALIDATION: ScheduledJobDeleteModel - }, - HTTPMethod.PATCH: { - PERMISSIONS: 'scheduled-job:update', - VALIDATION: ScheduledJobPatchModel - }, - HTTPMethod.GET: { - PERMISSIONS: 'scheduled-job:describe', - VALIDATION: SoloScheduledJobGetModel - }, - }, - '/settings/mail/': { - HTTPMethod.GET: { - PERMISSIONS: 'settings:describe_mail', - VALIDATION: MailSettingGetModel - }, - HTTPMethod.POST: { - PERMISSIONS: 'settings:create_mail', - VALIDATION: MailSettingPostModel - }, - HTTPMethod.DELETE: { - PERMISSIONS: 'settings:delete_mail' - } - }, - '/settings/license-manager/config/': { - HTTPMethod.GET: { - PERMISSIONS: 'settings:describe_lm_config' - }, - HTTPMethod.POST: { - PERMISSIONS: 'settings:create_lm_config', - VALIDATION: LicenseManagerConfigSettingPostModel - }, - HTTPMethod.DELETE: { - PERMISSIONS: 'settings:delete_lm_config' - } - }, - '/settings/license-manager/client/': { - HTTPMethod.GET: { - PERMISSIONS: 'settings:describe_lm_client', - VALIDATION: LicenseManagerClientSettingsGetModel - }, - HTTPMethod.POST: { - PERMISSIONS: 'settings:create_lm_client', - VALIDATION: LicenseManagerClientSettingPostModel - }, - HTTPMethod.DELETE: { - PERMISSIONS: 'settings:delete_lm_client', - VALIDATION: LicenseManagerClientSettingDeleteModel - } - }, - '/batch_results/': { - HTTPMethod.GET: { - PERMISSIONS: 'batch_results:describe', - VALIDATION: BatchResultsGetModel - } - }, - '/batch_results/{batch_results_id}/': { - HTTPMethod.GET: { - PERMISSIONS: 'batch_results:describe', - VALIDATION: SoleBatchResultsGetModel - } - }, - - '/reports/digests/jobs/{id}/': { - HTTPMethod.GET: { - PERMISSIONS: 'report_digests:describe', - VALIDATION: JobReportGetModel - } - }, - '/reports/digests/tenants/jobs/': { - HTTPMethod.GET: { - PERMISSIONS: 'report_digests:describe', - VALIDATION: TimeRangedTenantsReportGetModel - } - }, - '/reports/digests/tenants/{tenant_name}/jobs/': { - HTTPMethod.GET: { - PERMISSIONS: 'report_digests:describe', - VALIDATION: TimeRangedTenantReportGetModel - } - }, - '/reports/digests/tenants/': { - HTTPMethod.GET: { - PERMISSIONS: 'report_digests:describe', - VALIDATION: TimeRangedTenantsReportGetModel - } - }, - '/reports/digests/tenants/{tenant_name}/': { - HTTPMethod.GET: { - PERMISSIONS: 'report_digests:describe', - VALIDATION: TimeRangedTenantReportGetModel - } - }, - '/reports/details/jobs/{id}/': { - HTTPMethod.GET: { - PERMISSIONS: 'report_details:describe', - VALIDATION: JobReportGetModel - } - }, - '/reports/details/tenants/jobs/': { - HTTPMethod.GET: { - PERMISSIONS: 'report_details:describe', - VALIDATION: TimeRangedTenantsReportGetModel - } - }, - '/reports/details/tenants/{tenant_name}/jobs/': { - HTTPMethod.GET: { - PERMISSIONS: 'report_details:describe', - VALIDATION: TimeRangedTenantReportGetModel - } - }, - '/reports/details/tenants/': { - HTTPMethod.GET: { - PERMISSIONS: 'report_details:describe', - VALIDATION: TimeRangedTenantsReportGetModel - } - }, - '/reports/details/tenants/{tenant_name}/': { - HTTPMethod.GET: { - PERMISSIONS: 'report_details:describe', - VALIDATION: TimeRangedTenantReportGetModel - } - }, - '/reports/compliance/jobs/{id}/': { - HTTPMethod.GET: { - PERMISSIONS: 'report_compliance:describe', - VALIDATION: JobComplianceReportGetModel - } - }, - '/reports/compliance/tenants/{tenant_name}/': { - HTTPMethod.GET: { - PERMISSIONS: 'report_compliance:describe', - VALIDATION: TenantComplianceReportGetModel - } - }, - '/reports/errors/jobs/{id}/': { - HTTPMethod.GET: { - PERMISSIONS: 'report_errors:describe', - VALIDATION: JobErrorReportGetModel - } - }, - '/reports/errors/access/jobs/{id}/': { - HTTPMethod.GET: { - PERMISSIONS: 'report_errors:describe', - VALIDATION: JobErrorReportGetModel - } - }, - '/reports/errors/core/jobs/{id}/': { - HTTPMethod.GET: { - PERMISSIONS: 'report_errors:describe', - VALIDATION: JobErrorReportGetModel - } - }, - - '/reports/errors/tenants/': { - HTTPMethod.GET: { - PERMISSIONS: 'report_errors:describe', - VALIDATION: TenantsErrorReportGetModel - } - }, - '/reports/errors/access/tenants/': { - HTTPMethod.GET: { - PERMISSIONS: 'report_errors:describe', - VALIDATION: TenantsErrorReportGetModel - } - }, - '/reports/errors/core/tenants/': { - HTTPMethod.GET: { - PERMISSIONS: 'report_errors:describe', - VALIDATION: TenantsErrorReportGetModel - } - }, - - '/reports/errors/tenants/{tenant_name}/': { - HTTPMethod.GET: { - PERMISSIONS: 'report_errors:describe', - VALIDATION: TenantErrorReportGetModel - } - }, - '/reports/errors/access/tenants/{tenant_name}/': { - HTTPMethod.GET: { - PERMISSIONS: 'report_errors:describe', - VALIDATION: TenantErrorReportGetModel - } - }, - '/reports/errors/core/tenants/{tenant_name}/': { - HTTPMethod.GET: { - PERMISSIONS: 'report_errors:describe', - VALIDATION: TenantErrorReportGetModel - } - }, - '/reports/rules/jobs/{id}/': { - HTTPMethod.GET: { - PERMISSIONS: 'report_rules:describe', - VALIDATION: JobRuleReportGetModel - } - }, - '/reports/rules/tenants/': { - HTTPMethod.GET: { - PERMISSIONS: 'report_rules:describe', - VALIDATION: TenantsRuleReportGetModel - } - }, - '/reports/rules/tenants/{tenant_name}/': { - HTTPMethod.GET: { - PERMISSIONS: 'report_rules:describe', - VALIDATION: TenantRuleReportGetModel - } - }, - '/applications/': { - HTTPMethod.POST: { - PERMISSIONS: 'application:activate', - VALIDATION: ApplicationPostModel - }, - HTTPMethod.GET: { - PERMISSIONS: 'application:describe', - VALIDATION: ApplicationListModel - }, - }, - '/applications/{application_id}/': { - HTTPMethod.GET: { - PERMISSIONS: 'application:describe', - VALIDATION: ApplicationGetModel - }, - HTTPMethod.PATCH: { - PERMISSIONS: 'application:update', - VALIDATION: ApplicationPatchModel - }, - HTTPMethod.DELETE: { - PERMISSIONS: 'application:delete', - VALIDATION: ApplicationDeleteModel - } - }, - '/applications/access/{application_id}/': { - HTTPMethod.GET: { - PERMISSIONS: 'access_application:describe', - VALIDATION: AccessApplicationGetModel - }, - HTTPMethod.PATCH: { - PERMISSIONS: 'access_application:update', - VALIDATION: AccessApplicationPatchModel - }, - HTTPMethod.DELETE: { - PERMISSIONS: 'access_application:delete', - VALIDATION: AccessApplicationDeleteModel - } - }, - '/applications/access/': { - HTTPMethod.POST: { - PERMISSIONS: 'access_application:activate', - VALIDATION: AccessApplicationPostModel - }, - HTTPMethod.GET: { - PERMISSIONS: 'access_application:describe', - VALIDATION: AccessApplicationListModel - }, - }, - '/applications/dojo/{application_id}/': { - HTTPMethod.GET: { - PERMISSIONS: 'dojo_application:describe', - VALIDATION: DojoApplicationGetModel - }, - HTTPMethod.PATCH: { - PERMISSIONS: 'dojo_application:update', - VALIDATION: DojoApplicationPatchModel - }, - HTTPMethod.DELETE: { - PERMISSIONS: 'dojo_application:delete', - VALIDATION: DojoApplicationDeleteModel - } - }, - '/applications/dojo/': { - HTTPMethod.POST: { - PERMISSIONS: 'dojo_application:activate', - VALIDATION: DojoApplicationPostModel - }, - HTTPMethod.GET: { - PERMISSIONS: 'dojo_application:describe', - VALIDATION: DojoApplicationListModel - }, - }, - '/parents/': { - HTTPMethod.POST: { - PERMISSIONS: 'parent:activate', - }, - HTTPMethod.GET: { - PERMISSIONS: 'parent:describe', - VALIDATION: ParentListModel - } - }, - '/parents/{parent_id}/': { - HTTPMethod.GET: { - PERMISSIONS: 'parent:describe', - VALIDATION: ParentGetModel - }, - HTTPMethod.DELETE: { - PERMISSIONS: 'parent:delete', - VALIDATION: ParentDeleteModel - }, - HTTPMethod.PATCH: { - PERMISSIONS: 'parent:update', - } - }, - '/health/': { - HTTPMethod.GET: { - PERMISSIONS: None, - VALIDATION: HealthCheckGetModel - } - }, - '/health/{id}/': { - HTTPMethod.GET: { - PERMISSIONS: None, - VALIDATION: SoleHealthCheckGetModel - } - }, - '/customers/rabbitmq/': { - HTTPMethod.GET: { - PERMISSIONS: 'rabbitmq:describe', - VALIDATION: RabbitMQGetModel - }, - HTTPMethod.POST: { - PERMISSIONS: 'rabbitmq:create', - VALIDATION: RabbitMQPostModel - }, - HTTPMethod.DELETE: { - PERMISSIONS: 'rabbitmq:delete', - VALIDATION: RabbitMQDeleteModel - } - }, - '/rule-meta/standards/': { - HTTPMethod.POST: { - PERMISSIONS: 'meta:update_standards' - } - }, - '/rule-meta/mappings/': { - HTTPMethod.POST: { - PERMISSIONS: 'meta:update_mappings' - } - }, - '/rule-meta/meta/': { - HTTPMethod.POST: { - PERMISSIONS: 'meta:update_meta' - } - }, - '/reports/resources/tenants/{tenant_name}/state/latest/': { - HTTPMethod.GET: { - PERMISSIONS: 'report_resources:get_latest', - VALIDATION: ResourcesReportGet - } - }, - '/reports/resources/tenants/{tenant_name}/jobs/': { - HTTPMethod.GET: { - PERMISSIONS: 'report_resources:get_jobs', - VALIDATION: ResourceReportJobsGet - } - }, - '/reports/resources/jobs/{id}/': { - HTTPMethod.GET: { - PERMISSIONS: 'report_resources:get_jobs', - VALIDATION: ResourceReportJobGet - } - }, - '/platforms/k8s/': { - HTTPMethod.GET: {PERMISSIONS: 'platform:list_k8s'} - }, - '/platforms/k8s/native/': { - HTTPMethod.POST: {PERMISSIONS: 'platform:create_k8s_native'}, - }, - '/platforms/k8s/eks/': { - HTTPMethod.POST: {PERMISSIONS: 'platform:create_k8s_eks'}, - }, - '/platforms/k8s/native/{id}/': { - HTTPMethod.DELETE: {PERMISSIONS: 'platform:delete_k8s_native'} - }, - '/platforms/k8s/eks/{id}/': { - HTTPMethod.DELETE: {PERMISSIONS: 'platform:delete_k8s_eks'} - }, - '/jobs/k8s/': { - HTTPMethod.POST: {PERMISSIONS: 'run:initiate_k8s_run'} - } -} diff --git a/src/services/rbac/governance/__init__.py b/src/services/rbac/governance/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/services/rbac/governance/abstract_governance_service.py b/src/services/rbac/governance/abstract_governance_service.py deleted file mode 100644 index 71963677a..000000000 --- a/src/services/rbac/governance/abstract_governance_service.py +++ /dev/null @@ -1,58 +0,0 @@ -from typing import Any -from abc import ABC, abstractmethod - -MANAGEMENT_ATTR = 'management' -GOVERNANCE_ATTR = 'governance' -DELEGATION_ATTR = 'delegation' - - -class AbstractGovernanceService(ABC): - - """ - Entity Governance could be expressed to mediate access and interaction, - adhering expressed data-model. - - entity - - resource `management`: Dict - * $(management-id or mid)(s): str - * $management-data: Any - - party `delegation`: Dict - * $(delegation-id or did)(s): str - * $delegation-data: Any - - interaction `governance`: Dict - * $(governance-id or gid): str - * $governance-data: Any - """ - - @property - @abstractmethod - def governance_type(self): - raise NotImplementedError - - @governance_type.setter - @abstractmethod - def governance_type(self, attr: str): - raise NotImplementedError - - @abstractmethod - def get_entity(self, entity: Any): - raise NotImplementedError - - @abstractmethod - def get_management(self, entity: Any, mid: str): - raise NotImplementedError - - @abstractmethod - def i_get_management(self, entity: Any): - raise NotImplementedError - - @abstractmethod - def create_management(self, entity: Any, data: Any): - raise NotImplementedError - - @abstractmethod - def delete_management(self, entity: Any, mid: str): - raise NotImplementedError - - @abstractmethod - def get_management_dto(self, mid: str, data: Any): - raise NotImplementedError diff --git a/src/services/rbac/governance/priority_governance_service.py b/src/services/rbac/governance/priority_governance_service.py deleted file mode 100644 index 32a2e6e1e..000000000 --- a/src/services/rbac/governance/priority_governance_service.py +++ /dev/null @@ -1,377 +0,0 @@ -from services.modular_service import ModularService, Tenant, Customer, \ - Complemented, TenantSettings -from helpers.constants import ATTACHMENT_MODEL_ATTR, \ - LICENSE_KEYS_ATTR, ACCOUNT_ATTR, PERMITTED_ATTR, \ - ALLOWED_ATTACHMENT_ATTRS, PROHIBITED_ATTR - -from services.rbac.governance.abstract_governance_service import \ - AbstractGovernanceService, MANAGEMENT_ATTR, GOVERNANCE_ATTR, \ - DELEGATION_ATTR - -from helpers import generate_id -from helpers.log_helper import get_logger - -from typing import List, Callable, Union, Optional, Dict, Type, Any - -ACCOUNTS_ATTR = ACCOUNT_ATTR + 's' - -_LOG = get_logger(__name__) - -MANAGEMENT_ID_ATTR = f'{MANAGEMENT_ATTR}_id' -DELEGATION_ID_ATTR = f'{DELEGATION_ATTR}_id' - - -class PriorityGovernanceService(AbstractGovernanceService): - """ - Governance Service which mediates priority management, which is expressed - by the following data model. - - entity: Complemented[Tenant, Customer] - - resource `management` of priorities: Dict - * $(management-id or mid)(s): str - * $management-data: Any - - party `delegation` of accounts: Dict - * $(delegation-id or did)(s): str - * $delegation-data: Dict of attachment-model: str, accounts: List[str] - - interaction `governance`: Dict - * $(governance-id or gid): str - * $governance-data: Dict of - * attachment-model: str - """ - - def __init__(self, modular_service: ModularService): - self._modular_service = modular_service - self._entity_type: Optional[Type[Union[Tenant, Customer]]] = None - self._governance_type: Optional[str] = None - - self._managed_attr: str = 'priorities' - self._delegated_attr: str = 'delegation' - - @staticmethod - def _allowed_entity_types(): - return Tenant, Customer - - @property - def managed_attr(self): - return self._managed_attr - - @managed_attr.setter - def managed_attr(self, other: str): - self._managed_attr = other - - @property - def delegated_attr(self): - return self._delegated_attr - - @delegated_attr.setter - def delegated_attr(self, other: str): - self._delegated_attr = other - - @property - def entity_type(self): - return self._entity_type - - @entity_type.setter - def entity_type(self, other: Type[Union[Tenant, Customer]]): - assert other in (*self._allowed_entity_types(), None) - self._entity_type = other - - @property - def entity_fetcher_map(self): - return { - Customer: self._modular_service.get_complemented_customer, - Tenant: self._modular_service.get_complemented_tenant - } - - @property - def governance_type(self): - return self._governance_type - - @governance_type.setter - def governance_type(self, other: str): - self._governance_type = other - - @property - def _attribute_dto_map(self): - return { - MANAGEMENT_ATTR: self.get_management_dto - } - - def get_entity(self, entity: Union[str, Tenant, Customer]): - reference = self.entity_fetcher_map - entity_type = self.entity_type - governance_type: str = self.governance_type - fetcher: Callable[[str, str], Optional[Complemented]] = reference.get( - entity_type - ) - - if not entity_type: - _LOG.error('\'entity_type\' attribute has not been set.') - return None - elif not fetcher: - _LOG.error(f'{entity_type} \'entity_type\' is not allowed.') - return None - elif not governance_type: - _LOG.error('\'governance_type\' attribute has not been set.') - return None - entity = fetcher(entity, governance_type) - return entity - - def get_management_dto(self, mid: str, data: Dict[str, List]): - """ - Produces a single priority management data-transfer-object, - adhering to the attached `managed` data type. - :return: Dict[str, List[str]] - """ - return { - MANAGEMENT_ID_ATTR: mid, self.managed_attr: data - } - - def get_management( - self, entity: Complemented, mid: str - ): - """ - Retrieves id-driven ruleset priority-management data, within a - Ruleset-License-Priority TenantSetting bound to tenant name, - denoted with `entity`. - :parameter entity: str, TenantSetting.tenant_name - :parameter mid: str, priority identifier - :return: Optional[Dict] - """ - return self._get_attr_data( - attr=MANAGEMENT_ATTR, entity=entity, subject=mid - ) - - def i_get_management(self, entity: Complemented): - return self._i_get_attr_data(attr=MANAGEMENT_ATTR, entity=entity) - - def get_delegation( - self, entity: Complemented, did: str - ): - """ - Retrieves id-driven ruleset priority-management data, within a - Ruleset-License-Priority TenantSetting bound to tenant name, - denoted with `entity`. - :parameter entity: str, TenantSetting.tenant_name - :parameter did: str, delegation identifier - :return: Optional[Dict] - """ - return self._get_attr_data( - attr=DELEGATION_ATTR, entity=entity, subject=did - ) - - def i_get_delegation(self, entity: Complemented): - return self._i_get_attr_data(attr=DELEGATION_ATTR, entity=entity) - - def get_governance(self, entity: Complemented, scope: str): - """ - Retrieves scope-driven ruleset-license governance data, within a - Ruleset-License-Priority TenantSetting. - :parameter entity: str, TenantSetting.tenant_name - :parameter scope: str, Account identifier or 'All' - :return: Optional[Dict] - """ - return self._get_attr_data( - attr=GOVERNANCE_ATTR, entity=entity, subject=scope - ) - - def i_get_governance(self, entity: Complemented): - return self._i_get_attr_data(attr=MANAGEMENT_ATTR, entity=entity) - - def _get_attr_data(self, attr: str, entity: Complemented, subject: str): - """ - Mediates data-transfer object derivation of ruleset-license priority - content of either: - * attr: `management`, subject: priority-identifier `$mid` - * attr: `governance`, subject: `$scope` - :parameter attr: str, either `management` or `scope` - :parameter entity: Complemented - :parameter subject: str, either `mid` or `scope` identifier - :return: Dict[str, List[str]] - """ - - output = None - - attr_data = getattr(entity, attr, None) or dict() - - _get_dto: Callable = self._attribute_dto_map.get(attr, None) - - if attr_data and subject in attr_data and _get_dto: - output = _get_dto(subject, attr_data.get(subject)) - - return output - - def _i_get_attr_data(self, attr: str, entity: Complemented): - """ - Mediates data-transfer object derivation of ruleset-license priority - content of either: - * attr: `management` - * attr: `governance` - :parameter attr: str, either `management` or `scope` - :parameter entity: Union[str, Tenant], Tenant of TenantSettings - :return: Dict[str, List[str]] - """ - - attr_data = getattr(entity, attr, None) or dict() - - _get_dto: Callable = self._attribute_dto_map.get(attr, None) - - if _get_dto: - for subject, data in attr_data.items(): - yield _get_dto(mid=subject, data=data) - - def create_management( - self, entity: Complemented, data: Dict[str, List[str]] - ) -> Optional[str]: - management = entity.management or dict() - mid = generate_id() - management[mid] = data - entity.management = management - return mid - - def delete_management(self, entity: Complemented, mid: str): - head = f'{self.governance_type}:\'{entity.name}\'' - - mgmt = entity.management - mgmt = (mgmt if isinstance(mgmt, dict) else None) or dict() - if mid not in mgmt: - _LOG.warning(head + f'\'{mid}\' {MANAGEMENT_ATTR} does not exist.') - return None - else: - mgmt.pop(mid) - entity.management = mgmt - _LOG.info(head + f'\'{mid}\' {MANAGEMENT_ATTR} has been removed.') - - gvc: Dict[str, Dict] = entity.governance - gvc = (gvc if isinstance(gvc, dict) else None) or dict() - - # Currently obsolete. - gvc_items = list(gvc.items()) - updated_gvc = {} - for did, gvc_data in gvc_items: - attachment_data = gvc_data - - if self.is_subject_applicable( - subject=mid, attachment=gvc_data, attached_attr=MANAGEMENT_ATTR - ): - _to_log = f' going to exclude \'{mid}\' {MANAGEMENT_ATTR} ' \ - f'out of \'{did}\' {GOVERNANCE_ATTR}.' - - _LOG.info(head + _to_log) - # Update attachment. - _head = head + f' - \'{mid}\' {GOVERNANCE_ATTR}:' - data = self.derive_attachment_update_entity_data( - entity=gvc_data, head_attr=_head, - attached_attr=MANAGEMENT_ATTR, - attachment_model=PROHIBITED_ATTR, - to_attach=[mid] - ) - if not data: - continue - - attached = data.get(MANAGEMENT_ATTR) - if not attached: - # Detached of all management-id(s) - _LOG.info(head + f' removing \'{did}\' {GOVERNANCE_ATTR}' - f' due to absence of {MANAGEMENT_ATTR}.') - continue - else: - attachment_data = data - - updated_gvc[did] = attachment_data - - if updated_gvc: - entity.governance = updated_gvc - return entity - - @staticmethod - def is_subject_applicable( - subject: str, attached_attr: str, attachment: dict - ): - atm = attachment.get(ATTACHMENT_MODEL_ATTR) - if atm not in ALLOWED_ATTACHMENT_ATTRS: - return False - - attached = attachment.get(attached_attr, None) - if not isinstance(attached, list): - return False - - permit = atm == PERMITTED_ATTR - retained = subject in attached - _all = not attached - - return ( - (permit and (retained or _all)) or - (not permit and not(retained or _all)) - ) - - @staticmethod - def derive_attachment_update_entity_data( - entity: dict, head_attr: str, attached_attr: str, - attachment_model: str, to_attach: List[str] - ): - """ - Designated retrieve a patched attachment model of a given entity data, - based on a requested model and payload to attach, commencing diff or - merge actions. - :parameter entity: Dict, attachment data of an entity - :parameter head_attr: str, entity log-header - :parameter attached_attr: str, attribute referencing attached data - :parameter attachment_model: str, model of payload to attach - :parameter to_attach: List[str], payload to attach - :return: Optional[Dict] - """ - - head = head_attr - atm = attachment_model - - # Entity data. - e_atm: str = entity.get(ATTACHMENT_MODEL_ATTR) - e_attached: set = set(entity.get(attached_attr, [])) - - if e_atm not in ALLOWED_ATTACHMENT_ATTRS: - _LOG.error(head + f' maintains improper {ATTACHMENT_MODEL_ATTR}.') - return None - - if attachment_model not in ALLOWED_ATTACHMENT_ATTRS: - _LOG.warning(head + f' issued {ATTACHMENT_MODEL_ATTR} - invalid.') - return None - - # Explicitly empty tenants - apply to all. - to_attach = set(to_attach) - - _apply_model = f' explicitly applying \'{atm}\' model to' - _update_model = f' updating \'{e_atm}\' to' - _switch_model = f' switching to \'{atm}\' model and' - - attr = f'{attached_attr}(s)' - - if not to_attach: - scope = f' all {attached_attr}(s).' - _LOG.info(head + _apply_model + scope) - e_attached = to_attach - e_atm = atm - - elif e_atm == atm: - scope = f' merge {to_attach} with {e_attached} {attr}.' - _LOG.info(head + _update_model + scope) - e_attached |= to_attach - - else: - scope = f' diff {to_attach} out of {e_attached} {attr}.' - _LOG.info(head + _update_model + scope) - remainder = to_attach - e_attached - e_attached -= to_attach - if not e_attached and remainder: - scope = f' attaching {remainder} remaining {attr}.' - _LOG.info(head + _switch_model + scope) - e_attached = remainder - e_atm = atm - - entity[attached_attr] = list(e_attached) - entity[ATTACHMENT_MODEL_ATTR] = e_atm - state = f' attachment-model:\'{e_atm}\' is bound to ' \ - f'{", ".join(e_attached)} {attr}.' - _LOG.info(head + state) - - return entity - diff --git a/src/services/rbac/iam_cache_service.py b/src/services/rbac/iam_cache_service.py deleted file mode 100644 index 562c4dffb..000000000 --- a/src/services/rbac/iam_cache_service.py +++ /dev/null @@ -1,236 +0,0 @@ -import operator -from datetime import timedelta -from functools import cached_property -from typing import Callable, Union, Optional, Iterable, Generator, Type, \ - Dict, Tuple, TypedDict, List - -import services.cache as cache -from helpers.log_helper import get_logger -from helpers.time_helper import utc_datetime, utc_iso -from models.policy import Policy -from models.role import Role - -Entity = Union[Role, Policy] - -_LOG = get_logger(__name__) - - -class IamService: - class PolicyData(TypedDict): - customer: str - name: str - permissions: List[str] - - class RoleData(TypedDict): - customer: str - name: str - expiration: Optional[str] - policies: List[str] - # resource: List[str] # not used - - def get_policy(self, customer: str, name: str) -> Optional[Policy]: - _LOG.debug(f'Querying policy ({customer}, {name})') - return Policy.get_nullable(hash_key=customer, range_key=name) - - def get_role(self, customer: str, name: str) -> Optional[Role]: - _LOG.debug(f'Querying role ({customer}, {name})') - return Role.get_nullable(hash_key=customer, range_key=name) - - def list_policies(self, customer: Optional[str] = None, - name: Optional[str] = None) -> Iterable[Policy]: - if customer and name: - item = self.get_policy(customer, name) - return iter([item]) if item else iter([]) - if customer: - return Policy.query(hash_key=customer) - # now only scan is possible - condition = None - if name: - condition &= Policy.name == name - return Policy.scan(filter_condition=condition) - - def list_roles(self, customer: Optional[str] = None, - name: Optional[str] = None) -> Iterable[Role]: - """ - TODO add "expired" attr with such behaviour: if None, all the roles - are returned. If True -> only expired, if False, only still valid - :param customer: - :param name: - :return: - """ - if customer and name: - item = self.get_role(customer, name) - return iter([item]) if item else iter([]) - if customer: - return Role.query(hash_key=customer) - # now only scan is possible - condition = None - if name: - condition &= Role.name == name - return Role.scan(filter_condition=condition) - - def i_not_expired(self, roles: Iterable[Role] - ) -> Generator[Role, None, None]: - for role in roles: - if self.is_role_expired(role): - continue - yield role - - @staticmethod - def is_role_expired(role: Role) -> bool: - return utc_datetime() >= utc_datetime(_from=role.expiration) - - @staticmethod - def i_policy_permissions(policy: Policy) -> Generator[str, None, None]: - yield from policy.permissions or [] - - def i_role_policies(self, role: Role) -> Generator[Policy, None, None]: - for policy in role.policies: - item = self.get_policy(role.customer, policy) - if not item: - continue - yield item - - @cached_property - def dto_getter_map(self) -> Dict[Type[Entity], Callable]: - return { - Role: self._get_role_dto, - Policy: self._get_policy_dto - } - - @cached_property - def saver_map(self) -> Dict[Type[Entity], Callable]: - return { - Role: self._save_role, - Policy: self._save_policy - } - - @cached_property - def deleter_map(self) -> Dict[Type[Entity], Callable]: - return { - Role: self._delete_role, - Policy: self._delete_policy - } - - def _delete_role(self, role: Role): - role.delete() - - def _delete_policy(self, policy: Policy): - policy.delete() - - @staticmethod - def _get_role_dto(role: Role) -> dict: - dto = role.get_json() - dto.pop('resource', None) # no use - return dto - - @staticmethod - def _get_policy_dto(policy: Policy) -> dict: - return policy.get_json() - - def _save_role(self, role: Role): - role.save() - - def _save_policy(self, policy: Policy): - policy.save() - - def get_dto(self, entity: Entity) -> dict: - _dto = self.dto_getter_map.get(entity.__class__, lambda x: dict()) - return _dto(entity) - - def save(self, entity: Entity): - _save = self.saver_map.get(entity.__class__, lambda x: None) - _save(entity) - - def delete(self, entity: Entity): - _delete = self.deleter_map.get(entity.__class__, lambda x: None) - _delete(entity) - - @staticmethod - def create_policy(data: PolicyData) -> Policy: - return Policy(**data) - - @staticmethod - def create_role(data: RoleData) -> Role: - data.setdefault('expiration', - utc_iso(utc_datetime() + timedelta(days=90))) - return Role(**data) - - -class CachedIamService(IamService): - """ - I think, we need cache mainly in order not to query Roles and Policies - for each request ('cause we need to check whether the user is - allowed for each request). So, cache is currently not necessary for all - the methods. ... - """ - - def __init__(self): - self._roles_cache = cache.factory() - self._policies_cache = cache.factory() - - @cache.cachedmethod(operator.attrgetter('_policies_cache')) - def get_policy(self, customer: str, name: str) -> Optional[Policy]: - return super().get_policy(customer, name) - - @cache.cachedmethod(operator.attrgetter('_roles_cache')) - def get_role(self, customer: str, name: str) -> Optional[Role]: - return super().get_role(customer, name) - - @staticmethod - def _role_cache_key(role: Role) -> Tuple[str, str]: - """ - Must be the same as "get_role" method's args - :param role: - :return: - """ - return role.customer, role.name - - @staticmethod - def _policy_cache_key(policy: Policy) -> Tuple[str, str]: - """ - Must be the same as "get_policy" method's args - :param policy: - :return: - """ - return policy.customer, policy.name - - def _save_role(self, role: Role): - super()._save_role(role) - self._roles_cache[self._role_cache_key(role)] = role - - def _save_policy(self, policy: Policy): - super()._save_policy(policy) - self._policies_cache[self._policy_cache_key(policy)] = policy - - def _delete_role(self, role: Role): - super()._delete_role(role) - self._roles_cache.pop(self._role_cache_key(role), None) - - def _delete_policy(self, policy: Policy): - super()._delete_policy(policy) - self._policies_cache.pop(self._policy_cache_key(policy), None) - - def clean_role_cache(self, customer: Optional[str] = None, - name: Optional[str] = None): - if customer and name: - self._roles_cache.pop((customer, name), None) - return - # customer or name - keys = iter(self._roles_cache.keys()) - for key in keys: - if key[0] == customer or key[1] == name: - self._roles_cache.pop(key, None) - return - - def clean_policy_cache(self, customer: Optional[str] = None, - name: Optional[str] = None): - if customer and name: - self._policies_cache.pop((customer, name), None) - return - # customer or name - keys = iter(self._policies_cache.keys()) - for key in keys: - if key[0] == customer or key[1] == name: - self._policies_cache.pop(key, None) - return diff --git a/src/services/rbac/restriction_service.py b/src/services/rbac/restriction_service.py deleted file mode 100644 index 90eebb4fc..000000000 --- a/src/services/rbac/restriction_service.py +++ /dev/null @@ -1,408 +0,0 @@ -from functools import cached_property -from http import HTTPStatus -from typing import Optional, List, Callable, Tuple, Iterable, Union, Dict, Set - -from helpers import build_response -from helpers.constants import PARAM_CUSTOMER, \ - TENANT_NAME_ATTR, TENANTS_ATTR, \ - TENANT_ATTR -from helpers.log_helper import get_logger -from helpers.system_customer import SYSTEM_CUSTOMER -from services.modular_service import ModularService - -NOT_ALLOWED_TO_ACCESS_ENTITY = 'You are not allowed to access this entity' -NOT_ENOUGH_DATA = 'Not enough data to proceed the request. ' - -_LOG = get_logger(__name__) - - -class RestrictionService: - """Restricts access by entities: customer and tenants""" - def __init__(self, modular_service: ModularService): - self._modular_service = modular_service - - self._path = None - self._method = None - self._user_customer = None - self._user_tenants = set() - - self._required = [] - - @staticmethod - def not_enough_data( - to_specify: Optional[Union[str, list]] = None) -> str: - if to_specify: - to_specify = to_specify if isinstance(to_specify, - list) else [to_specify, ] - return NOT_ENOUGH_DATA + f'Specify: {", ".join(to_specify)}' - return NOT_ENOUGH_DATA - - @property - def user_customer(self) -> str: - return self._user_customer - - @property - def user_tenants(self) -> set: - return self._user_tenants - - def set_endpoint(self, path: str, method: str): - self._path = path - self._method = method - - def set_user_entities(self, user_customer: str, - user_tenants: Optional[Iterable] = None): - self._user_customer = user_customer - self._user_tenants = set(user_tenants or []) - - def endpoint(self, path: Optional[str] = None, - method: Optional[str] = None) -> Tuple[str, str]: - return path or self._path, method or self._method - - @staticmethod - def tenant_attr(event: dict) -> Optional[str]: - """ - Retrieves the `ultimate` tenant attribute from the event. - You shall always send tenant name exactly in `TENANT_NAME_ATTR` - """ - return event.pop(TENANT_NAME_ATTR, None) or None - - def update_event(self, event: dict): - """ - Applies the set of steps (methods) to the input event if the - mentioned set is determined for the endpoint. Restricts by - customer for any endpoint - """ - self._required.clear() - _LOG.info('Performing a compulsory restriction step: by customer') - _endpoint = self.endpoint() - - _mandatory_step = self._check_customer - if _endpoint in self.customer_required_endpoints: - _mandatory_step = self._check_customer_required - _mandatory_step(event) - chain: Optional[List[Callable]] = self.registry.get(_endpoint) - if not chain: - _LOG.info(f'No other restriction for endpoint ' - f'\'{_endpoint}\' required') - return - _LOG.info(f'Performing other restriction steps for ' - f'endpoint \'{_endpoint}\'') - for step in chain: - step(event) - - @cached_property - def customer_required_endpoints(self) -> Set[Tuple[str, str]]: - return { - ('/parents', 'GET'), - ('/parents', 'POST'), - ('/parents/{parent_id}', 'PATCH'), - - ('/applications/access', 'POST'), - ('/applications/access/{application_id}', 'PATCH'), - ('/applications/access/{application_id}', 'DELETE'), - ('/applications/dojo', 'POST'), - ('/applications/dojo/{application_id}', 'PATCH'), - ('/applications/dojo/{application_id}', 'DELETE'), - - ('/applications', 'POST'), - ('/applications/{application_id}', 'PATCH'), - ('/applications/{application_id}', 'DELETE'), - ('/license', 'DELETE'), - - # ('/jobs', 'POST'), - # ('/jobs/standard', 'POST'), - - ('/customers/rabbitmq', 'POST'), - ('/customers/rabbitmq', 'GET'), - ('/customers/rabbitmq', 'DELETE'), - - ('/reports/operational', 'GET'), - ('/reports/project', 'GET'), - ('/reports/department', 'GET'), - ('/reports/clevel', 'GET'), - - ('/platforms/k8s', 'GET') - } - - @cached_property - def registry(self) -> Dict[Tuple[str, str], Tuple[Callable, ...]]: - """ - Here you specify a sequence of steps. The step is a method that - receives an event as a sole attr and shall change it and check access. - - Take into account that if you use `self._sole_tenant` and it - can't resolve the sole tenant name, None will be returned. You must - handle this manually in a handler or explicitly - specify `self._check_required`. - """ - return { - ('/tenants/regions', 'POST'): (self._sole_tenant, self._check_required), - ('/findings', 'GET'): (self._sole_tenant, self._check_required), - ('/findings', 'DELETE'): (self._sole_tenant, self._check_required), - ('/accounts/credential_manager', 'GET'): (self._multiple_tenants,), - ('/accounts/credential_manager', 'POST'): (self._multiple_tenants,), - ('/accounts/credential_manager', 'PATCH'): (self._multiple_tenants,), - ('/accounts/credential_manager', 'DELETE'): (self._multiple_tenants,), - ('/tenants', 'GET'): (self._multiple_tenants,), - ('/tenants', 'PATCH'): (self._sole_tenant, self._check_required, ), - ('/tenants/license-priorities', 'GET'): (self._sole_tenant,), - ('/tenants/license-priorities', 'POST'): (self._sole_tenant, self._check_required), - ('/tenants/license-priorities', 'PATCH'): (self._sole_tenant,), - ('/tenants/license-priorities', 'DELETE'): (self._sole_tenant,), - ('/jobs', 'GET'): (self._multiple_tenants, ), - ('/jobs/{job_id}', 'GET'): (self._multiple_tenants, ), - ('/jobs', 'POST'): (self._sole_tenant, self._check_required), - ('/jobs/standard', 'POST'): (self._sole_tenant, self._check_required), - ('/jobs/{job_id}', 'DELETE'): (self._multiple_tenants, ), - ('/scheduled-job', 'GET'): (self._multiple_tenants,), - ('/scheduled-job', 'POST'): (self._sole_tenant, self._check_required), - ('/scheduled-job/{name}', 'PATCH'): (self._multiple_tenants, ), - ('/scheduled-job/{name}', 'DELETE'): (self._multiple_tenants, ), - ('/scheduled-job/{name}', 'GET'): (self._multiple_tenants, ), - ('/license', 'GET'): (self._multiple_tenants,), - ('/license', 'POST'): (self._sole_tenant, self._check_required), - ('/rulesets', 'GET'): (self._multiple_tenants, ), - ('/rulesets', 'POST'): (self._multiple_tenants, ), - ('/rule-sources', 'GET'): (self._multiple_tenants,), - ('/rule-sources', 'POST'): (self._multiple_tenants,), - ('/rule-sources', 'PATCH'): (self._multiple_tenants,), - ('/rule-sources', 'DELETE'): (self._multiple_tenants,), - ('/rules', 'GET'): (self._multiple_tenants,), - ('/rules', 'DELETE'): (self._multiple_tenants,), - ('/siem/security-hub', 'GET'): (self._sole_tenant, ), - ('/siem/defect-dojo', 'GET'): (self._sole_tenant, ), - ('/siem/security-hub', 'POST'): (self._sole_tenant, - self._check_required), - ('/siem/defect-dojo', 'POST'): (self._sole_tenant, - self._check_required), - ('/siem/security-hub', 'DELETE'): (self._sole_tenant, - self._check_required), - ('/siem/defect-dojo', 'DELETE'): (self._sole_tenant, - self._check_required), - ('/siem/security-hub', 'PATCH'): (self._sole_tenant, - self._check_required), - ('/siem/defect-dojo', 'PATCH'): (self._sole_tenant, - self._check_required), - - ('/batch_results', 'GET'): (self._multiple_tenants, ), - ('/batch_results/{batch_results_id}', 'GET'): ( - self._multiple_tenants, - ), - - ('/reports/digests/jobs/{id}', 'GET'): ( - self._multiple_tenants, - ), - ('/reports/digests/tenants/jobs', 'GET'): ( - self._multiple_tenants, - ), - ('/reports/digests/tenants/{tenant_name}/jobs', 'GET'): ( - self._sole_tenant, self._check_required - ), - - ('/reports/digests/tenants', 'GET'): ( - self._multiple_tenants, - ), - ('/reports/digests/tenants/{tenant_name}', 'GET'): ( - self._sole_tenant, self._check_required - ), - - ('/reports/details/jobs/{id}', 'GET'): ( - self._multiple_tenants, - ), - ('/reports/details/tenants/jobs', 'GET'): ( - self._multiple_tenants, - ), - ('/reports/details/tenants/{tenant_name}/jobs', 'GET'): ( - self._sole_tenant, self._check_required - ), - - ('/reports/details/tenants', 'GET'): ( - self._multiple_tenants, - ), - ('/reports/details/tenants/{tenant_name}', 'GET'): ( - self._sole_tenant, self._check_required - ), - - ('/reports/compliance/jobs/{id}', 'GET'): ( - self._multiple_tenants, - ), - ('/reports/compliance/tenants/{tenant_name}', 'GET'): ( - self._sole_tenant, self._check_required - ), - - ('/reports/errors/jobs/{id}','GET'): ( - self._multiple_tenants, - ), - ('/reports/errors/access/jobs/{id}', 'GET'): ( - self._multiple_tenants, - ), - ('/reports/errors/core/jobs/{id}', 'GET'): ( - self._multiple_tenants, - ), - - ('/reports/errors/tenants', 'GET'): ( - self._multiple_tenants, - ), - ('/reports/errors/tenants/{tenant_name}', 'GET'): ( - self._sole_tenant, self._check_required - ), - - ('/reports/errors/access/tenants', 'GET'): ( - self._multiple_tenants, - ), - ('/reports/errors/access/tenants/{tenant_name}', 'GET'): ( - self._sole_tenant, self._check_required - ), - - ('/reports/errors/core/tenants', 'GET'): ( - self._multiple_tenants, - ), - ('/reports/errors/core/tenants/{tenant_name}', 'GET'): ( - self._sole_tenant, self._check_required - ), - - ('/reports/rules/jobs/{id}', 'GET'): (self._multiple_tenants, ), - ('/reports/rules/tenants', 'GET'): (self._multiple_tenants, ), - ('/reports/rules/tenants/{tenant_name}', 'GET'): ( - self._sole_tenant, self._check_required - ), - ('/reports/push/dojo', 'POST'): (self._sole_tenant, self._check_required), - ('/reports/push/security-hub', 'POST'): (self._sole_tenant, self._check_required), - ('/parents/tenant-link', 'POST'): (self._sole_tenant, - self._check_required), - ('/parents/tenant-link', 'DELETE'): (self._sole_tenant, - self._check_required), - ('/rules/update-meta', 'POST'): (self._multiple_tenants, ), - - } - - @cached_property - def _check_customer(self) -> Callable[[dict], None]: - return lambda event: self._restrict_customer(event, False) - - @cached_property - def _check_customer_required(self) -> Callable[[dict], None]: - return lambda event: self._restrict_customer(event, True) - - def _restrict_customer(self, event: dict, require_for_system: bool = False): - """ - For common users - restricts access to the customers they do not own - and sets `customer` attr equal to their customer's name. - For the SYSTEM user checks whether the customer he wants to - access exists and sets `customer` attr equal to its name. If the - SYSTEM does not want to access another customer, `customer` - attr will be None - :parameter event: dict - :parameter require_for_system: bool - if True, system customer will - be forced to specify a customer name for the request - """ - if self._user_customer != SYSTEM_CUSTOMER: - _LOG.debug(f'Extending event with user customer: ' - f'{self._user_customer}') - if not event.get(PARAM_CUSTOMER): - event[PARAM_CUSTOMER] = self._user_customer - elif event.get(PARAM_CUSTOMER) != self._user_customer: - _LOG.warning( - f'Customer \'{self._user_customer}\' tried ' - f'to access \'{event.get(PARAM_CUSTOMER)}\'') - return build_response(code=HTTPStatus.FORBIDDEN, - content=NOT_ALLOWED_TO_ACCESS_ENTITY) - else: # user_customer == SYSTEM_CUSTOMER - customer = event.get(PARAM_CUSTOMER) - if customer == SYSTEM_CUSTOMER: - # we are gradually making the system customer a ghost. - # Most SYSTEM customer-bound action currently work without - # its model in DB - return - _LOG.info(f'Checking customer \'{customer}\' existence') - if customer and not self._modular_service.get_customer(customer): - message = f'Customer \'{customer}\' was not found' - _LOG.warning(message) - return build_response( - code=HTTPStatus.NOT_FOUND, - content=message - ) - if not customer and require_for_system: - message = f'System did not specify \'{customer}\'' - _LOG.warning(message) - return build_response( - code=HTTPStatus.BAD_REQUEST, - content='Specify customer to make a request on his behalf' - ) - - def _multiple_tenants(self, event: dict): - """ - Adds `TENANTS_ATTR` attribute that will contain a list of tenants - available for user based on Cognito's `custom:user_tenants` attribute - and input param `tenant_name`. If the list is empty all the - tenants are available - :parameter event: dict - """ - _attr = TENANTS_ATTR - self._required.append(_attr) - - tenant = self.tenant_attr(event) - if not self._user_tenants: - event[_attr] = [tenant, ] if tenant else [] - elif tenant: # and self._user_tenants - if tenant in self._user_tenants: - event[_attr] = [tenant, ] - else: - return build_response(code=HTTPStatus.FORBIDDEN, - content=NOT_ALLOWED_TO_ACCESS_ENTITY) - else: # self._user_tenants and not tenant - event[_attr] = list(self._user_tenants) - - def _sole_tenant(self, event: dict): - """ - Tries to resolve a sole tenant name for the current - request derived from Cognito's `custom:user_tenants` and input - param `tenant_name`. The attr may end up being None. That means that - it cannot be defined yet (due to multiple available tenants and no - input for instance). In such a case it should be handled either by - You or by self._check_required - :parameter event: dict - """ - _attr = TENANT_ATTR - self._required.append(_attr) - - tenant = self.tenant_attr(event) - if not self._user_tenants: - event[_attr] = tenant - elif tenant: # and self._user_tenants - if tenant in self._user_tenants: - event[_attr] = tenant - else: - return build_response(code=HTTPStatus.FORBIDDEN, - content=NOT_ALLOWED_TO_ACCESS_ENTITY) - else: # self._user_tenants and not tenant - if len(self._user_tenants) == 1: - event[_attr] = next(iter(self._user_tenants)) - else: - event[_attr] = None - - def _check_required(self, event: dict): - """ - A possible part of a chain. Asserts that event contains values by - keys from self._required. The attr can be filled in previous - steps of the chain - :parameter event: dict - """ - for _attr in self._required: - if not event.get(_attr): - return build_response(code=HTTPStatus.BAD_REQUEST, - content=self.not_enough_data(_attr)) - self._required.clear() - - def is_allowed_tenant(self, tenant_name: str) -> bool: - """ - Checks whether the given tenant is allowed for the current user - """ - allowed_tenants = self.user_tenants - if allowed_tenants: - return tenant_name in allowed_tenants - # all tenant's within customer allowed. We must check if the given - # tenant's customer is the user's customer - - tenant = self._modular_service.get_tenant(tenant_name) - return tenant and tenant.customer_name == self.user_customer diff --git a/src/services/rbac_service.py b/src/services/rbac_service.py new file mode 100644 index 000000000..78aace7da --- /dev/null +++ b/src/services/rbac_service.py @@ -0,0 +1,304 @@ +from helpers.constants import Permission +from models.policy import PolicyEffect, Policy +from helpers.time_helper import utc_iso +from datetime import datetime +from typing import Generator +from pynamodb.pagination import ResultIterator +from models.role import Role +from services.base_data_service import BaseDataService + + +class PolicyStruct: + __slots__ = ('customer', 'name', 'effect', 'permissions', 'tenants', + 'description') + + def __init__(self, customer: str, name: str, + effect: PolicyEffect = PolicyEffect.ALLOW, + permissions: set[str] | None = None, + tenants: set[str] | None = None, + description: str | None = None): + self.customer: str = customer + self.name: str = name + self.effect: PolicyEffect = effect + self.permissions: set[str] = permissions or set() + self.tenants: set[str] = tenants or set() + self.description: str | None = description + + def __repr__(self) -> str: + return (f'{self.__class__.__name__}' + f'(customer={self.customer}, name={self.name})') + + @classmethod + def from_model(cls, policy: Policy) -> 'PolicyStruct': + ef = PolicyEffect.ALLOW + if policy.effect: + ef = PolicyEffect(policy.effect) + return cls( + customer=policy.customer, + name=policy.name, + effect=ef, + permissions=set(policy.permissions), + tenants=set(policy.tenants), + description=policy.description + ) + + @classmethod + def from_dct(cls, dct: dict) -> 'PolicyStruct': + """ + Assuming that the dict is valid + :param dct: + :return: + """ + ef = PolicyEffect.ALLOW + if effect := dct.get('effect'): + ef = PolicyEffect(effect) + return cls( + customer=dct['customer'], + name=dct['name'], + effect=ef, + permissions=set(dct.get('permissions') or ()), + tenants=set(dct.get('tenants') or ()), + description=dct.get('description') + ) + + @property + def contains_all_tenants(self) -> bool: + return '*' in self.tenants + + def touches(self, permission: Permission) -> bool: + """ + Tells whether the given permission is mentioned inside a policy + :param permission: + :return: + """ + permissions = self.permissions + domain, action = permission.split(':', maxsplit=1) + return (f'{domain}:*' in permissions + or f'*:{action}' in permissions + or '*:*' in permissions + or permission in permissions) + + def forbids(self, permission: Permission) -> bool: + """ + Fast forbid. In case this method returns True this policy absolutely + forbids the given permission and there is no need to check other + policies, we can do 403 immediately. In case this method return False + it does not forbid the permission, and we must check other policies + as well. + In case this method return False it does not mean that the permission + is allowed. It's just not forbidden. In order to understand whether + the permission is allowed we must be aware of all the policies + :param permission: + :return: + """ + if self.effect == PolicyEffect.ALLOW: + # allowing policy definitely cannot forbid a permission + return False + # assert self.effect == PolicyEffect.ALLOW + if not self.touches(permission): + # denying policy cannot forbid a permission if it's not in + # permissions list + return False + # target permission is mentioned inside this policy + if permission.depends_on_tenant and self.contains_all_tenants: + return True + if not permission.depends_on_tenant: + return True + return False + + def allows(self, permission: Permission) -> bool: + """ + Tells whether this policy allows the given permission. Even if this + policy does allow the permission some other policy can forbid the + permission. So, we cannot use this method independently on other + policies + :param permission: + :return: + """ + if self.effect == PolicyEffect.DENY: + # denying policy cannot allow a permission + return False + if not self.touches(permission): + # allowing policy cannot allow a permission that one is not + # mentioned inside + return False + if permission.depends_on_tenant and not self.tenants: + return False + return True + + +class TenantsAccessPayload: + ALL = object() + + __slots__ = '_names', '_allowed_flag' + + def __init__(self, names: tuple[str, ...], allowed: bool): + """ + Two states can be represented: + - permission is allowed for some specific tenant names + - permission is allowed for all tenant names except some specific + If allowed is True then `names` contains only allowed tenants. If + allowed is False then `names` contains tenants that forbidden. All + tenant are allowed except those inside `names` + :param names: + :param allowed: + """ + self._names = names + self._allowed_flag = allowed + + def __repr__(self) -> str: + return (f'{self.__class__.__name__}(names={self._names}, ' + f'allowed={self._allowed_flag})') + + __str__ = __repr__ + + @classmethod + def build_allowing_all(cls) -> 'TenantsAccessPayload': + return cls(names=(), allowed=False) + + @classmethod + def build_denying_all(cls) -> 'TenantsAccessPayload': + # a bit confusing... `allowed` is just a flag that tells what + # tenants inside `names`. Since names are empty there are no allowed + return cls(names=(), allowed=True) + + def is_allowed_for(self, tenant_name: str) -> bool: + _mention = tenant_name in self._names + _allowed = self._allowed_flag + return _mention and _allowed or not _mention and not _allowed + + def is_allowed_for_all_tenants(self) -> bool: + """ + Return True only if the permission is allowed for any tenant + :return: + """ + return not self._names and not self._allowed_flag + + def allowed_denied(self + ) -> tuple[object | tuple[str, ...], tuple[str, ...]]: + """ + >>> all_items = set() # some items here + >>> allowed, denied = self.allowed_denied() + >>> if allowed is TenantsAccessPayload.ALL: + ... items = tuple(filter(lambda x: x not in denied, all_items)) + ... else: + ... items = tuple(all_items & set(allowed)) + """ + if self._allowed_flag: + return self._names, () + # allowed for all + return self.ALL, self._names + + +class TenantAccess: + __slots__ = '_allow_policies', '_deny_policies' + + def __init__(self): + self._allow_policies: list[PolicyStruct] = [] + self._deny_policies: list[PolicyStruct] = [] + + def add(self, policy: PolicyStruct) -> None: + match policy.effect: + case PolicyEffect.ALLOW: + self._allow_policies.append(policy) + case PolicyEffect.DENY: + self._deny_policies.append(policy) + + def resolve_payload(self, permission: Permission) -> TenantsAccessPayload: + """ + Builds TenantsAccessPayload for the given permission considering all + the policies. Then this payload can be used inside handlers + :return: + """ + if not permission.depends_on_tenant: + # you should not need this payload for handlers that do not + # depend on tenants. So I even not try to resolve + return TenantsAccessPayload.build_denying_all() + + names, flag = set(), True # starting state, allowed for no-one + for policy in self._allow_policies: + if not policy.touches(permission): + continue + # does touch + if policy.contains_all_tenants: + flag = False + names.clear() + else: # todo can be optimized slightly + if flag: + names.update(policy.tenants) + else: + names.difference_update(policy.tenants) + for policy in self._deny_policies: + if not policy.touches(permission): + continue + # does touch + if policy.contains_all_tenants: + flag = True + names.clear() + else: # todo can be optimized slightly + if flag: + names.difference_update(policy.tenants) + else: + names.update(policy.tenants) + return TenantsAccessPayload(tuple(names), flag) + + +class PolicyService(BaseDataService[Policy]): + def get_nullable(self, customer: str, name: str) -> Policy | None: + return super().get_nullable(hash_key=customer, range_key=name) + + def iter_role_policies(self, role: Role) -> Generator[Policy, None, None]: + customer = role.customer + for name in set(role.policies): + item = self.get_nullable(customer, name) + if not item: + continue + yield item + + def create(self, customer: str, name: str, description: str | None = None, + permissions: tuple[str, ...] | list[str] = (), + tenants: tuple[str, ...] | list[str] = (), + effect: PolicyEffect = PolicyEffect.DENY + ) -> Policy: + return Policy( + customer=customer, + name=name, + description=description, + permissions=list(permissions), + tenants=list(tenants), + effect=effect.value, + ) + + def query(self, customer: str, limit: int | None = None, + last_evaluated_key: dict | int | None = None + ) -> ResultIterator[Policy]: + return Policy.query( + hash_key=customer, + limit=limit, + last_evaluated_key=last_evaluated_key + ) + + +class RoleService(BaseDataService[Role]): + def get_nullable(self, customer: str, name: str) -> Role | None: + return super().get_nullable(hash_key=customer, range_key=name) + + def create(self, customer: str, name: str, expiration: datetime | None, + policies: tuple[str, ...] | list[str] = (), + description: str | None = None) -> Role: + return Role( + customer=customer, + name=name, + expiration=utc_iso(expiration) if expiration else None, + policies=list(policies), + description=description + ) + + def query(self, customer: str, limit: int | None = None, + last_evaluated_key: dict | int | None = None + ) -> ResultIterator[Role]: + return Role.query( + hash_key=customer, + limit=limit, + last_evaluated_key=last_evaluated_key + ) diff --git a/src/services/report_convertors.py b/src/services/report_convertors.py new file mode 100644 index 000000000..5732c698f --- /dev/null +++ b/src/services/report_convertors.py @@ -0,0 +1,403 @@ +from abc import ABC, abstractmethod +from base64 import b64encode +import csv +from datetime import datetime, timezone +from functools import partial +import io +from typing import Literal, TYPE_CHECKING, TypedDict +from typing_extensions import NotRequired + +from xlsxwriter.workbook import Workbook +import msgspec + +from helpers import filter_dict, hashable +from helpers.constants import REPORT_FIELDS +from helpers.reports import NONE_VERSION, Standard, Severity +from services import SP +from services.xlsx_writer import CellContent, Table, XlsxRowsWriter + +if TYPE_CHECKING: + from services.sharding import ShardsCollection + + +class ShardCollectionConvertor(ABC): + mc = SP.mappings_collector + + @abstractmethod + def convert(self, collection: 'ShardsCollection'): + """ + Must convert the given shards collection to some other report + :param collection: + :return: + """ + + +class ShardCollectionDojoConvertor(ShardCollectionConvertor): + """ + Subclass only for defect dojo convertors + """ + @abstractmethod + def convert(self, collection: 'ShardsCollection'): + ... + + @classmethod + def from_scan_type(cls, scan_type: str, **kwargs + ) -> 'ShardCollectionDojoConvertor': + """ + Returns a generic dojo convertor by default + :param scan_type: + :param kwargs: + :return: + """ + match scan_type: + case 'Generic Findings Import': + return ShardsCollectionGenericDojoConvertor(**kwargs) + case 'Cloud Custodian Scan': + return ShardsCollectionCloudCustodianDojoConvertor(**kwargs) + case _: + return ShardsCollectionGenericDojoConvertor(**kwargs) + + +# for generic dojo parser +class FindingFile(TypedDict): + title: str + data: str + + +class Finding(TypedDict): + title: str + date: str # when discovered, iso + severity: str # Info, Low, Medium, High, Critical. Info, if we don't know + description: str + mitigation: str | None + impact: str | None + references: str # standards vs mitre? + tags: list[str] + vuln_id_from_tool: str # rule id + service: str # service + files: NotRequired[list[FindingFile]] + + +class Findings(TypedDict): + findings: list[Finding] + + +class ShardsCollectionGenericDojoConvertor(ShardCollectionDojoConvertor): + + def __init__(self, attachment: Literal['json', 'xlsx', 'csv'] | None = None, + **kwargs): + """ + In case attachment is provided, findings data will be attached as file + in that format. Otherwise, table will be drawn directly inside + description + :param attachment: + :param kwargs: + """ + self._attachment = attachment + + @staticmethod + def _make_table(resources: list[dict]) -> str: + """ + In case resource have arn, we don't show id and name and namespace + (cause arn can be really long and can break dojo description), + otherwise -> id, name, namespace + :param resources: + :return: + """ + from tabulate import tabulate + if resources[0].get('arn'): # can be sure IndexError won't occur + # all resources within a table are similar + headers = ('arn', ) + else: # id name, namespace + headers = ('id', 'name', 'namespace') + + return tabulate( + tabular_data=[[res.get(h) for h in headers] for res in resources], + headers=map(str.title, headers), # type: ignore + tablefmt='rounded_grid', + stralign='center', + numalign='center', + showindex='always', + missingval='-', + disable_numparse=True, + ) + + @staticmethod + def _make_references(standards: dict) -> str: + data = bytearray(b'#### Standards\n') + for st in Standard.deserialize(standards): + if st.version == NONE_VERSION: + data.extend(f'* {st.name}\n'.encode()) + else: + data.extend(f'* {st.name} **{st.version}**\n'.encode()) + # todo add mitre here + return data.decode('utf-8') + + @staticmethod + def _make_json_file(resources: list[dict]) -> str: + """ + Dumps resources to json and encodes to base64 as dojo expects + :return: + """ + return b64encode(msgspec.json.encode(resources)).decode() + + @staticmethod + def _make_xlsx_file(resources: list[dict]) -> str: + """ + Dumps resources to xlsx file and encodes to base64 as dojo expects + :param resources: + :return: + """ + + buffer = io.BytesIO() + + with Workbook(buffer) as wb: + bold = wb.add_format({'bold': True}) + table = Table() + table.new_row() + headers = ('arn', 'id', 'name', 'namespace') + for h in ('№', ) + headers: + table.add_cells(CellContent(h.title(), bold)) + for i, r in enumerate(resources, 1): + table.new_row() + table.add_cells(CellContent(i)) + for h in headers: + table.add_cells(CellContent(r.get(h))) + + wsh = wb.add_worksheet('resources') + XlsxRowsWriter().write(wsh, table) + return b64encode(buffer.getvalue()).decode() + + @staticmethod + def _make_csv_file(resources: list[dict]) -> str: + """ + Dumps resources to csv file and encodes to base64 as dojo expects + :param resources: + :return: + """ + buffer = io.StringIO() + writer = csv.writer(buffer) + writer.writerow(('№', 'Arn', 'Id', 'Name', 'Namespace')) + + writer.writerows( + (i, res.get('arn'), res.get('id'), res.get('name'), res.get('namespace')) + for i, res in enumerate(resources, 1) + ) + + return b64encode(buffer.getvalue().encode()).decode() + + def convert(self, collection: 'ShardsCollection') -> Findings: + findings = [] + meta = collection.meta + human_data = self.mc.human_data + severity = self.mc.severity + standards = self.mc.standard + service = self.mc.service + ss = self.mc.service_section + for part in collection.iter_parts(): + if not part.resources: + continue + pm = meta.get(part.policy) or {} # part meta + p = part.policy + + # tags + tags = [part.location, pm.get('resource')] + if service_section := ss.get(p): + tags.append(service_section) + + article = human_data.get(p, {}).get('article', '') + + match self._attachment: + case 'xlsx': + extra = { + 'description': article, + 'files': [{ + 'title': f'{p}.xlsx', + 'data': self._make_xlsx_file(part.resources) + }] + } + case 'json': + extra = { + 'description': article, + 'files': [{ + 'title': f'{p}.json', + 'data': self._make_json_file(part.resources) + }] + } + case 'csv': + extra = { + 'description': article, + 'files': [{ + 'title': f'{p}.csv', + 'data': self._make_csv_file(part.resources) + }] + } + case _: # None or some unexpected + table = self._make_table(part.resources) + extra = {'description': f'{article}\n{table}'} + + findings.append({ + 'title': pm['description'] if 'description' in pm else p, + 'date': datetime.fromtimestamp(part.timestamp, + tz=timezone.utc).isoformat(), + 'severity': severity.get(p) or Severity.INFO.value, + 'mitigation': human_data.get(p, {}).get('remediation'), + 'impact': human_data.get(p, {}).get('impact'), + 'references': self._make_references(standards.get(p, {})), + 'tags': tags, + 'vuln_id_from_tool': p, + 'service': service.get(p), + **extra + }) + return {'findings': findings} + + +class ShardsCollectionCloudCustodianDojoConvertor(ShardCollectionDojoConvertor): + """ + Converts existing shards collection to the format that is accepted by + Cloud Custodian dojo parser + """ + + class Model(TypedDict): + """ + Parser expects a list of such items + """ + description: str + resources: list[dict] + remediation: str | None + impact: str | None + standard: dict + severity: str | None + article: str | None + service: str | None + vuln_id_from_tool: str | None + tags: list[str] + + def __init__(self, resource_per_finding: bool = False, **kwargs): + self._rpf = resource_per_finding + + @staticmethod + def _convert_standards(standards: dict | None = None) -> dict: + if not standards: + return {} + res = {} + for item in Standard.deserialize(standards): + res.setdefault(item.name, []).append(item.version) + return res + + @staticmethod + def _prepare_resources(resources: list[dict]) -> list[dict]: + """ + Keeps only report fields and sorts by + :param resources: + :return: + """ + skey = 'id' + ftr = partial(filter_dict, keys=REPORT_FIELDS) + return sorted(map(ftr, resources), + key=lambda r: r.get(skey) or chr(123)) + + def convert(self, collection: 'ShardsCollection') -> list[Model]: + result = [] + meta = collection.meta + human_data = self.mc.human_data or {} + severity = self.mc.severity or {} + standards = self.mc.standard or {} + for part in collection.iter_parts(): + if not part.resources: + continue + rule = part.policy + hd = human_data.get(rule) or {} + base = { + 'description': meta.get(rule, {}).get('description'), + 'remediation': hd.get('remediation'), + 'impact': hd.get('impact'), + 'severity': severity.get(rule), + 'standard': self._convert_standards(standards.get(rule)), + 'article': hd.get('article'), + 'service': hd.get('service') or meta.get(rule, {}).get('resource'), + 'vuln_id_from_tool': rule, + 'tags': [part.location], + } + if self._rpf: + for res in part.resources: + result.append({ + **base, + 'resources': filter_dict(res, REPORT_FIELDS) + }) + else: + base['resources'] = self._prepare_resources(part.resources) + result.append(base) + return result + + +class ShardsCollectionDigestConvertor(ShardCollectionConvertor): + class DigestsReport(TypedDict): + total_checks: int + successful_checks: int + failed_checks: dict + violating_resources: int + + def convert(self, collection: 'ShardsCollection') -> DigestsReport: + total_checks = 0 + successful_checks = 0 + total_resources = set() + failed_checks = { + 'total': 0 + } + failed_by_severity = {} + severity = self.mc.severity + for part in collection.iter_parts(): + total_checks += 1 + if part.resources: + failed_checks['total'] += 1 + failed_by_severity.setdefault(severity.get(part.policy), 0) + failed_by_severity[severity.get(part.policy)] += 1 + else: + successful_checks += 1 + keep_report_fields = partial(filter_dict, keys=REPORT_FIELDS) + total_resources.update( + map(hashable, map(keep_report_fields, part.resources)) + ) + if None in failed_by_severity: + failed_by_severity['Unknown'] = failed_by_severity.pop(None) + failed_checks['severity'] = failed_by_severity + return { + 'total_checks': total_checks, + 'successful_checks': successful_checks, + 'failed_checks': failed_checks, + 'violating_resources': len(total_resources) + } + + +class ShardsCollectionDetailsConvertor(ShardCollectionConvertor): + + def convert(self, collection: 'ShardsCollection') -> dict[str, list[dict]]: + res = {} + for part in collection.iter_parts(): + res.setdefault(part.location, []).append({ + 'policy': { + 'name': part.policy, + **(collection.meta.get(part.policy) or {}) + }, + 'resources': part.resources + }) + return res + + +class ShardsCollectionFindingsConvertor(ShardCollectionConvertor): + def convert(self, collection: 'ShardsCollection') -> dict[str, dict]: + """ + Can't be two parts with the same policy and region + :param collection: + :return: + """ + res = {} + meta = collection.meta + for part in collection.iter_parts(): + inner = res.setdefault(part.policy, { + 'resources': {}, + **(meta.get(part.policy) or {}), + }) + inner['resources'][part.location] = part.resources + return res diff --git a/src/services/report_service.py b/src/services/report_service.py index ceb65a7cd..044c03881 100644 --- a/src/services/report_service.py +++ b/src/services/report_service.py @@ -1,52 +1,99 @@ -import json -import os -import tempfile +import statistics from datetime import datetime -from pathlib import PurePosixPath -from typing import Dict, List, Optional, Union, TypedDict +import urllib.request +import urllib.error +from itertools import chain +from typing import TypedDict, Generator, BinaryIO, cast -from xlsxwriter import Workbook -from xlsxwriter.utility import xl_col_to_name +import msgspec +from modular_sdk.models.tenant import Tenant +from urllib3.util import parse_url, Url +from helpers.constants import Cloud, ReportFormat, PolicyErrorType from helpers.log_helper import get_logger -from helpers.reports import Standard, FindingsCollection -from services.clients.s3 import S3Client +from models.batch_results import BatchResults +from models.job import Job +from services.ambiguous_job_service import AmbiguousJob +from services.clients.s3 import S3Client, Json +from services import cache from services.environment_service import EnvironmentService -from services.rule_meta_service import LazyLoadedMappingsCollector -_LOG = get_logger(__name__) - -DETAILED_REPORT_FILE = 'detailed_report.json' -USER_REPORT_FILE = 'user_detailed_report.json' -DIGEST_REPORT_FILE = 'report.json' - -STATISTICS_FILE = 'statistics.json' -API_CALLS_FILE = 'api_calls.json' +from services.mappings_collector import LazyLoadedMappingsCollector +from services.platform_service import Platform +from services.reports_bucket import (TenantReportsBucketKeysBuilder, \ + PlatformReportsBucketKeysBuilder, + StatisticsBucketKeysBuilder, + ReportsBucketKeysBuilder) +from services.sharding import ShardsCollection, ShardsCollectionFactory, \ + ShardsS3IO -KEYS_TO_EXCLUDE_FOR_USER = {'standard_points', } - -Coverage = Dict[str, Dict[str, float]] - -# Failed rule types. -ACCESS_TYPE = 'access' -CORE_TYPE = 'core' +_LOG = get_logger(__name__) -class PolicyReportItem(TypedDict): - """ - Incoming data - """ - description: str +class StatisticsItem(TypedDict, total=False): + policy: str region: str - multiregional: str # "true" or "false" - resources: List[Dict] - remediation: Optional[str] - impact: Optional[str] - standard: Optional[Dict] - severity: Optional[str] - article: Optional[str] - service: str - vuln_id_from_tool: Optional[str] - tags: List[str] + tenant_name: str + customer_name: str + start_time: float + end_time: float + api_calls: dict + + scanned_resources: int | None + failed_resources: int | None + reason: str | None + traceback: list[str] + error_type: PolicyErrorType | None + + +class AverageStatisticsItem(TypedDict, total=False): + policy: str + invocations: int + succeeded_invocations: int + failed_invocations: int + total_api_calls: dict + min_exec: float + max_exec: float + total_exec: float + average_exec: float + resources_failed: int + resources_scanned: int + average_resources_scanned: int + average_resources_failed: int + + +class ReportResponse: + __slots__ = ('entity', 'content', 'fmt', 'dictionary_url') + + def __init__(self, entity: AmbiguousJob | Tenant | Platform, + content: Json | None = None, + dictionary_url: str | None = None, + fmt: ReportFormat = ReportFormat.JSON): + self.entity = entity + self.content = content or {} + self.dictionary_url = dictionary_url + self.fmt = fmt + + def dict(self) -> dict: + res = {'format': self.fmt, 'obfuscated': bool(self.dictionary_url)} + if self.dictionary_url: + res['dictionary_url'] = self.dictionary_url + if isinstance(self.content, str): + res['url'] = self.content + else: + res['content'] = self.content + if isinstance(self.entity, AmbiguousJob): + res['job_id'] = self.entity.id + res['job_type'] = self.entity.type + res['tenant_name'] = self.entity.tenant_name + res['customer_name'] = self.entity.customer_name + elif isinstance(self.entity, Platform): + res['platform_id'] = self.entity.id + res['tenant_name'] = self.entity.tenant_name + res['customer_name'] = self.entity.customer + else: + res['tenant_name'] = self.entity.name + res['customer_name'] = self.entity.customer_name + return res class ReportService: @@ -57,742 +104,303 @@ def __init__(self, s3_client: S3Client, self.environment_service = environment_service self.mappings_collector = mappings_collector - @property - def job_report_bucket(self): - return self.environment_service.default_reports_bucket_name() - - def pull_job_statistics(self, path: str): - bucket_name = self.environment_service.get_statistics_bucket_name() - path = path.replace(':', '_') - _LOG.info(f'Pulling {path} of job-source, within {bucket_name}.') - try: - data = self.s3_client.get_json_file_content( - bucket_name=bucket_name, full_file_name=path - ) - except (BaseException, Exception) as e: - _LOG.error(f'{path} of job-source could not be pulled,' - f' due to - {e}.') - data = None - return data - - def pull_job_report(self, path: str): - bucket_name = self.environment_service.default_reports_bucket_name() - path = path.replace(':', '_') - _LOG.info(f'Pulling {path} of job-source, within {bucket_name}.') - try: - data = self.s3_client.get_json_file_content( - bucket_name=bucket_name, full_file_name=path - ) - except (BaseException, Exception) as e: - _LOG.error(f'{path} of job-source could not be pulled,' - f' due to - {e}.') - data = None - return data - - def put_job_report(self, data: dict, path: str): - bucket_name = self.environment_service.default_reports_bucket_name() - path = path.replace(':', '_') - _LOG.info(f'Putting {path} job-source data, within {bucket_name}.') - try: - data = self.s3_client.put_object( - bucket_name=bucket_name, object_name=path, - body=json.dumps(data) - ) - except (BaseException, Exception) as e: - _LOG.error(f'Data, could not be put within {path}, due to - {e}.') - data = None - return data - - def href_job_report(self, path: str, check: bool = True): - bucket_name = self.environment_service.default_reports_bucket_name() - path = path.replace(':', '_') - - if check: - _LOG.info(f'Verifying whether {path} of {bucket_name} exists.') - if not self.s3_client.file_exists( - bucket_name=bucket_name, key=path - ): - return None - - _LOG.info(f'Generating presigned-url of {path}, within {bucket_name}.') - try: - url = self.s3_client.generate_presigned_url( - bucket_name=bucket_name, full_file_name=path - ) - except (BaseException, Exception) as e: - _LOG.error(f'Presigned URL of {path} path, could not be generated' - f' due to - {e}.') - url = None - return url - - def pull_concrete_report(self, path: str): - bucket_name = self.environment_service.default_reports_bucket_name() - path = path.replace(':', '_') - _LOG.info(f'Pulling {path} of job-source, within {bucket_name}.') - try: - data = self.s3_client.get_json_file_content( - bucket_name=bucket_name, full_file_name=path - ) - except (BaseException, Exception) as e: - _LOG.error(f'{path} of job-source could not be pulled,' - f' due to - {e}.') - data = None - return data + self._ipv4_cache = cache.TTLCache(maxsize=2, ttl=300) - def put_json_concrete_report(self, data: Union[Dict, List], path: str): - bucket_name = self.environment_service.default_reports_bucket_name() - _LOG.info(f'Putting {path} file within {bucket_name}.') - return self.s3_client.put_object( - bucket_name=bucket_name, object_name=path, body=json.dumps(data) + def job_collection(self, tenant: Tenant, job: Job) -> ShardsCollection: + collection = ShardsCollectionFactory.from_tenant(tenant) + collection.io = ShardsS3IO( + bucket=self.environment_service.default_reports_bucket_name(), + key=TenantReportsBucketKeysBuilder(tenant).job_result(job), + client=self.s3_client ) + return collection + + def ed_job_collection(self, tenant: Tenant, br: BatchResults + ) -> ShardsCollection: + collection = ShardsCollectionFactory.from_tenant(tenant) + collection.io = ShardsS3IO( + bucket=self.environment_service.default_reports_bucket_name(), + key=TenantReportsBucketKeysBuilder(tenant).ed_job_result(br), + client=self.s3_client + ) + return collection + + def ambiguous_job_collection(self, tenant: Tenant, job: AmbiguousJob + ) -> ShardsCollection: + if not job.is_ed_job: + return self.job_collection(tenant, job.job) + return self.ed_job_collection(tenant, job.job) + + def ed_job_difference_collection(self, tenant: Tenant, br: BatchResults + ) -> ShardsCollection: + collection = ShardsCollectionFactory.from_tenant(tenant) + collection.io = ShardsS3IO( + bucket=self.environment_service.default_reports_bucket_name(), + key=TenantReportsBucketKeysBuilder(tenant).ed_job_difference(br), + client=self.s3_client + ) + return collection + + def tenant_latest_collection(self, tenant: Tenant) -> ShardsCollection: + collection = ShardsCollectionFactory.from_tenant(tenant) + collection.io = ShardsS3IO( + bucket=self.environment_service.default_reports_bucket_name(), + key=TenantReportsBucketKeysBuilder(tenant).latest_key(), + client=self.s3_client + ) + return collection + + def platform_latest_collection(self, platform: Platform + ) -> ShardsCollection: + collection = ShardsCollectionFactory.from_cloud(Cloud.KUBERNETES) + collection.io = ShardsS3IO( + bucket=self.environment_service.default_reports_bucket_name(), + key=PlatformReportsBucketKeysBuilder(platform).latest_key(), + client=self.s3_client + ) + return collection + + def platform_job_collection(self, platform: Platform, job: Job + ) -> ShardsCollection: + collection = ShardsCollectionFactory.from_cloud(Cloud.KUBERNETES) + collection.io = ShardsS3IO( + bucket=self.environment_service.default_reports_bucket_name(), + key=PlatformReportsBucketKeysBuilder(platform).job_result(job), + client=self.s3_client + ) + return collection - def put_path_retained_concrete_report(self, stream_path: str, - object_path: str): - bucket_name = self.environment_service.default_reports_bucket_name() - path = object_path - _LOG.info(f'Reading {stream_path}, meant for {path}.') - with open(stream_path, 'rb') as f: - body = f.read() - if body: - _LOG.info(f'Putting {path} file within {bucket_name}.') - return self.s3_client.put_object( - bucket_name=bucket_name, object_name=path, body=body - ) - - def href_concrete_report(self, path: str, check: bool = True): - bucket_name = self.environment_service.default_reports_bucket_name() - if check: - _LOG.info(f'Verifying whether {path} of {bucket_name} exists.') - if not self.s3_client.file_exists( - bucket_name=bucket_name, key=path - ): - return None - - _LOG.info(f'Generating presigned-url of {path}, within {bucket_name}.') - try: - url = self.s3_client.generate_presigned_url( - bucket_name=bucket_name, full_file_name=path - ) - except (BaseException, Exception) as e: - _LOG.error(f'Presigned URL of {path} path, could not be generated' - f' due to - {e}.') - url = None - return url - - def check_concrete_report(self, path: str): - bucket_name = self.environment_service.default_reports_bucket_name() - _LOG.info(f'Checking whether {path} file within {bucket_name} exists.') - return self.s3_client.file_exists(bucket_name=bucket_name, key=path) - - @staticmethod - def derive_name_of_report_object_path(object_path: str): - return PurePosixPath(object_path).name - - @staticmethod - def derive_digests_report_object_path( - entity_attr: str, entity_value: str, start: datetime, end: datetime - ): - time_range = f'{start.timestamp()}_{end.timestamp()}' - time_range = time_range.replace('.', '-') - key = f'{entity_attr}_digests_report_{entity_value}_{time_range}.json' - return str(PurePosixPath('digests', key)) - - @staticmethod - def derive_details_report_object_path( - entity_attr: str, entity_value: str, start: datetime, end: datetime - ): - time_range = f'{start.timestamp()}_{end.timestamp()}' - time_range = time_range.replace('.', '-') - key = f'details_report_{entity_attr}_{entity_value}_{time_range}.json' - return str(PurePosixPath('details_report', key)) - - @staticmethod - def derive_compliance_report_object_path( - entity_attr: Optional[str] = None, - entity_value: Optional[str] = None, - start: Optional[datetime] = None, end: Optional[datetime] = None, - job_id: Optional[str] = None, fext: str = 'xlsx' - ): - named = (entity_attr and entity_value) - ranged = (named and (start and end)) - # Backward compatible with the previous implementation. - assert named or ranged or job_id, \ - 'Report must either be range, job or named specific.' - - key = 'compliance_report' - if job_id: - key += f'_{job_id}' - else: - key += f'_{entity_attr}_{entity_value}' - if ranged: - time_range = f'{start.isoformat()}_{end.isoformat()}' - time_range = time_range.replace('.', '-') - key += f'_{time_range}' - key += f'.{fext}' - return str(PurePosixPath('compliance_report', key)) - - @staticmethod - def derive_error_report_object_path( - subtype: Optional[str] = None, - entity_attr: Optional[str] = None, - entity_value: Optional[str] = None, - start: Optional[datetime] = None, - end: Optional[datetime] = None, - job_id: Optional[str] = None, - fext: str = 'xlsx' - ): - named = (entity_attr and entity_value) - ranged = (named and (start and end)) - - # Backward compatible with the previous implementation. - assert named or ranged or job_id, \ - 'Report must either be range, job or named specific.' - - key = 'error' - if subtype: - key += f'_{subtype}' - key += '_report' - - if job_id: - key += f'_{job_id}' - else: - key += f'_{entity_attr}_{entity_value}' - if ranged: - time_range = f'{start.isoformat()}_{end.isoformat()}' - time_range = time_range.replace('.', '-') - key += f'_{time_range}' - - key += f'.{fext}' - return str(PurePosixPath('error_report', key)) - - @staticmethod - def derive_rule_report_object_path( - entity_attr: Optional[str] = None, - entity_value: Optional[str] = None, - start: Optional[datetime] = None, - end: Optional[datetime] = None, - job_id: Optional[str] = None, - fext: str = 'xlsx' - ): - named = (entity_attr and entity_value) - ranged = (named and (start and end)) - - # Backward compatible with the previous implementation. - assert named or ranged or job_id, \ - 'Report must either be range, job or named specific.' - - key = 'rule_report' - - if job_id: - key += f'_{job_id}' - else: - key += f'_{entity_attr}_{entity_value}' - if ranged: - time_range = f'{start.isoformat()}_{end.isoformat()}' - time_range = time_range.replace('.', '-') - key += f'_{time_range}' - - key += f'.{fext}' - return str(PurePosixPath('rule_report', key)) - - @staticmethod - def derive_job_object_path(job_id: str, typ: str): - _files = ( - DIGEST_REPORT_FILE, DETAILED_REPORT_FILE, USER_REPORT_FILE, - STATISTICS_FILE, API_CALLS_FILE + def tenant_snapshot_collection(self, tenant: Tenant, + date: datetime) -> ShardsCollection | None: + key = TenantReportsBucketKeysBuilder(tenant).nearest_snapshot_key(date) + if not key: + return + collection = ShardsCollectionFactory.from_tenant(tenant) + collection.io = ShardsS3IO( + bucket=self.environment_service.default_reports_bucket_name(), + key=key, + client=self.s3_client ) - assert typ in _files, f'{typ} file is not job recognizable.' - return str(PurePosixPath(job_id, typ)) + return collection - @staticmethod - def derive_findings_from_report(report: dict, user_detailed: bool): - kte = None if user_detailed else list(KEYS_TO_EXCLUDE_FOR_USER) - return FindingsCollection.from_detailed_report( - report=report, only_report_fields=False, - retain_all_keys=True, - keys_to_exclude=kte + def platform_snapshot_collection(self, platform: Platform, date: datetime + ) -> ShardsCollection | None: + key = PlatformReportsBucketKeysBuilder(platform).nearest_snapshot_key( + date + ) + if not key: + return + collection = ShardsCollectionFactory.from_cloud(Cloud.KUBERNETES) + collection.io = ShardsS3IO( + bucket=self.environment_service.default_reports_bucket_name(), + key=key, + client=self.s3_client ) + return collection - @staticmethod - def generate_digest(detailed_report: dict): - total_checks_performed = 0 - failed_checks = 0 - successful_checks = 0 - total_resources_violated_rules = 0 - for region, reports in detailed_report.items(): - region_total = len(reports) - failed_summaries = [summary for summary in reports if - summary.get('resources')] - region_failed = len(failed_summaries) - region_successful = region_total - region_failed - total_checks_performed += region_total - failed_checks += region_failed - successful_checks += region_successful - total_resources_violated_rules += sum( - len(summary.get('resources')) - for summary in failed_summaries) - return { - 'total_checks_performed': total_checks_performed, - 'successful_checks': successful_checks, - 'failed_checks': failed_checks, - 'total_resources_violated_rules': total_resources_violated_rules - } + def fetch_meta(self, tp: Tenant | Platform) -> dict: + if isinstance(tp, Tenant): + collection = self.tenant_latest_collection(tp) + else: + collection = self.platform_latest_collection(tp) + collection.fetch_meta() + return collection.meta or {} + + def job_statistics(self, job: Job | BatchResults) -> list[StatisticsItem]: + data = self.s3_client.gz_get_json( + bucket=self.environment_service.get_statistics_bucket_name(), + key=StatisticsBucketKeysBuilder.job_statistics(job) + ) + if not data: + return [] + return data @staticmethod - def derive_failed_rule_map( - job_id: str, statistics: List[Dict] - ) -> Optional[Dict[str, List[Dict]]]: - ref = {} - for rule in statistics: - rid = rule.get('id') - is_failed = 'reason' in rule or rule.get('status') == 'FAILED' - if not rid or not is_failed: - continue - scope = ref.setdefault(rid, []) - scope.append( - { - job_id: { - 'account_name': rule.get('account_display_name'), - 'tenant': rule.get('tenant_display_name'), - 'region': rule.get('region'), - 'failure_reason': rule.get('reason'), - 'traceback': rule.get('traceback', - 'Non-source error'), - # 'cw_logs_snippet': cw_logs - } - } - ) - return ref + def average_statistics(*iterables: list[StatisticsItem] + ) -> Generator[dict, None, None]: + remapped = {} # (policy region) to items + for i in chain(*iterables): + remapped.setdefault((i['policy'], i['region']), []).append(i) + for key, items in remapped.items(): + total_api_calls = {} + executions = [] + failed_invocations = 0 + scanned, failed = [], [] + for item in items: + for k, v in (item.get('api_calls') or {}).items(): + if k not in total_api_calls: + total_api_calls[k] = v + else: + total_api_calls[k] += v + executions.append(item['end_time'] - item['start_time']) + if item.get('scanned_resources'): + scanned.append(item['scanned_resources']) + if item.get('failed_resources'): + failed.append(item['failed_resources']) + if item.get('error_type'): + failed_invocations += 1 + scanned = scanned or [0] + failed = failed or [0] + yield { + 'policy': key[0], + 'region': key[1], + 'invocations': len(items), + 'succeeded_invocations': len(items) - failed_invocations, + 'failed_invocations': failed_invocations, + 'total_api_calls': total_api_calls, + 'min_exec': min(executions), + 'max_exec': max(executions), + 'total_exec': sum(executions), + 'average_exec': statistics.mean(executions), + 'resources_failed': sum(failed), + 'resources_scanned': sum(scanned), + 'average_resources_scanned': statistics.mean(scanned), + 'average_resources_failed': statistics.mean(failed), + } @staticmethod - def derive_type_based_failed_rule_map( - typ: str, failed_rule_map: Dict[str, List[Dict]] - ) -> Optional[Dict[str, List[Dict]]]: - - value_typ_ref = { - 'Non-source error': ACCESS_TYPE - } - typed: Dict[str, List[Dict]] = {} - for rule_id, rule_errors in failed_rule_map.items(): - for rule_error in rule_errors: - for job_id, error in rule_error.items(): - traceback = error.get('traceback') - # Defaults to `core`. - to_typ = value_typ_ref.get(traceback, CORE_TYPE) - if to_typ == typ: - rule_scope = typed.setdefault(rule_id, []) - rule_scope.append({job_id: error}) - return typed + def sum_average_statistics(iterables: list[AverageStatisticsItem] + ) -> list[AverageStatisticsItem]: + result = {} + for item in iterables: + for k, v in item.items(): + if k == 'policy': + result.setdefault(item['policy'], {}) + elif k == 'region': + continue + elif k == 'total_api_calls': + for k_api, v_api in v.items(): + result[item['policy']].setdefault(k, {}).setdefault( + k_api, 0) + result[item['policy']][k][k_api] += v_api + else: + result[item['policy']].setdefault(k, 0) + result[item['policy']][k] += v - @staticmethod - def derive_clean_filtered_statistics_list( - statistic_list: List[List[Dict]], target_rule: Optional[str] = None - ): - result: Union[List[List[Dict]], list] = [] - if target_rule: - for stat_item in statistic_list: - temp = [] - for rule in stat_item: - if rule.get('id') == target_rule: - temp.append(rule) - result.append(temp) - - # Cleans up statistics, by removing duplicates - result = statistic_list if not result else result - for account in result: - duplicates = [] - for index, stat in enumerate(account, start=0): - if any(word in ' '.join(stat.get('id').split('_')) for word in - ('iam', 's3', 'mfa', 'access key', 'cloudfront')): - if stat.get('id') in duplicates: - del account[index] - else: - duplicates.append(stat.get('id')) - return result + return [{'policy': k, **v} for k, v in result.items()] @staticmethod - def average_out_statistics(statistics_list: List[List[Dict]], - list_format=True): - full_statistics = [] - metrics = {} - for stat_list in statistics_list: - full_statistics.extend(stat_list) - - for rule in full_statistics: - rid = rule.get('id') - rule_scope = metrics.setdefault(rid, []) - rule_scope.append(rule) - - if list_format: - result_data = [] - else: - result_data = {} - for rule_name, rules in metrics.items(): - result_item = { - 'invocations': len(rules) - } - success = 0 - failed = 0 - skipped = 0 - resources_scanned = 0 - violating_resources = 0 - min_execution_time = float('inf') - max_execution_time = 0.0 - total_execution_time = 0.0 - for rule in rules: - elapsed_time = float(rule.get('elapsed_time')) if rule.get( - 'elapsed_time') else 0 - resources_scanned += int(rule.get('resources_scanned')) \ - if rule.get('resources_scanned') else 0 - violating_resources += len( - rule.get('failed_resources')) if rule.get( - 'failed_resources') else 0 - total_execution_time += elapsed_time - min_execution_time = min_execution_time if \ - min_execution_time < elapsed_time or \ - elapsed_time == 0 else elapsed_time - max_execution_time = max_execution_time if \ - max_execution_time > elapsed_time else elapsed_time - success += 1 if rule.get('status') == 'SUCCEEDED' else 0 - failed += 1 if rule.get('status') == 'FAILED' else 0 - skipped += 1 if rule.get('status') == 'SKIPPED' else 0 - - result_item.update({ - 'succeeded': success, - 'failed': failed, - 'skipped': skipped, - 'resources_scanned': resources_scanned, - 'violating_resources': violating_resources, - 'min_exec': round( - min_execution_time, 3) if min_execution_time != float( - 'inf') else 0.0, - 'max_exec': round(max_execution_time, 3), - 'total_exec': round(total_execution_time, 3) - }) - if list_format: - result_item.update({'policy_name': rule_name}) - result_data.append(result_item) - else: - result_data.update({rule_name: result_item}) - return result_data + def format_statistic(item: StatisticsItem) -> dict: + item = cast(dict, item) + item.pop('tenant_name', None) + item.pop('customer_name', None) + item.pop('traceback', None) + item.pop('reason', None) + item['succeeded'] = not bool(item.get('error_type')) + item['execution_time'] = item['end_time'] - item['start_time'] + item.pop('start_time', None) + item.pop('end_time', None) + return item @staticmethod - def accumulate_digest(digest_list: List[Dict[str, int]]): - keys = ( - 'total_checks_performed', 'successful_checks', - 'failed_checks', 'total_resources_violated_rules' - ) - accumulated = {} - for digest in digest_list: - for key in keys: - pending = accumulated.get(key, 0) - value = digest.get(key) - if isinstance(value, int): - accumulated[key] = pending + value - return accumulated - - @classmethod - def accumulate_details( - cls, detailed_report_list: List[Dict], user_detailed: bool - ) -> Optional[FindingsCollection]: - if not detailed_report_list: - return - base = cls.derive_findings_from_report( - report=detailed_report_list[0], user_detailed=user_detailed - ) - for report in detailed_report_list[1:]: - base.update( - other=cls.derive_findings_from_report( - report=report, user_detailed=user_detailed - ) - ) - return base + def format_failed(item: StatisticsItem) -> StatisticsItem: + """ + Changes the given item. Does not create new one + :param item: + :return: + """ + item.pop('tenant_name', None) + item.pop('customer_name', None) + item.pop('start_time', None) + item.pop('end_time', None) + item.pop('api_calls', None) + item.pop('scanned_resources', None) + item.pop('failed_resources', None) + item.pop('traceback', None) + return item @staticmethod - def accumulate_compliance( - coverages: List[Coverage], - regions: Optional[List[str]] = None - ) -> Coverage: + def only_failed(statistic: list[StatisticsItem], + error_type: PolicyErrorType = None) -> filter: """ - Aggregates coverages, by averaging out standard-specific - points within regions. - - :param coverages: List[Dict[str, Dict[str, float]]] - :param regions: Optional[List[str]], denotes regions to derive for, - given None - assumes to calculate for any region. - :param: Dict[str, Dict[str, float]] + Keeps only failed statistics items + :param statistic: + :param error_type: + :return: """ - averaged = {} - if not coverages: - return averaged - - # Maintains accumulation counter. - count_ref: Dict[str, Dict[str, int]] = {} - for pending in coverages: - for region in pending: - - if regions and region not in regions: - continue - region_scope = averaged.get(region, {}) - for standard, value in pending[region].items(): - if standard in region_scope: - # Given standard already seen, start counter at 2 - rg_scope = count_ref.setdefault(region, {}) - rg_scope[standard] = rg_scope.get(standard, 1) + 1 - retained = region_scope.get(standard, 0) - region_scope[standard] = retained + value + def check(item): + et = item.get('error_type') + if not et: + return False + if error_type and et != error_type: + return False + return True - if region_scope: - averaged[region] = region_scope + return filter(check, statistic) - for region in count_ref: - for standard, count in count_ref[region].items(): - averaged[region][standard] /= count - - return averaged - - @staticmethod - def derive_compliance_report_excel_path( - file_name, coverages, standards_coverage - ) -> str: - - path = os.path.join(tempfile.gettempdir(), file_name) - - wb = Workbook(path) - percent_fmt = wb.add_format({'num_format': '0.00%'}) - - report_ws = wb.add_worksheet("Compliance Report") - coverage_ws = wb.add_worksheet("Standards Coverage") - to_write = [] - _standards = set() # to be able to get length and format cells - for region, standards in coverages.items(): - to_write.append({ - 'region': region, - **{k: standards[k] for k in sorted(standards)} - }) - _standards.update(standards.keys()) - - # Write lists of dicts. - data = to_write - breakdown_field = None - headers = [] - for item in data: - headers.extend(item.keys()) - headers = list(dict.fromkeys(headers)) - row = 1 - for item_index, item in enumerate(data): - for index, key in enumerate(headers): - value = item.get(key, '') - report_ws.write(row, index, value) - if breakdown_field and (len(data) - 1 != item_index) \ - and (data[item_index + 1].get(breakdown_field) != data[ - item_index].get(breakdown_field)): - row += 1 - row += 1 - - # Amends headers - headers = [header.replace('_', ' ').capitalize() for header in headers] - first_row = 0 - for index, header in enumerate(headers): - report_ws.write(first_row, index, header) - - # formatting necessary cells to percentage and writing averages - row = len(to_write) + 1 - report_ws.write(row, 0, 'AVERAGE') - report_ws.conditional_format(1, 1, row, len(_standards), { - 'type': 'cell', 'criteria': '>=', 'value': 0, 'format': percent_fmt - }) - for i in range(1, len(_standards) + 1): - col = xl_col_to_name(i) - report_ws.write_formula(row, i, - f'{{=AVERAGE({col}2:{col}{row})}}') - # writing the maximum available percentage to a separate sheet - for index, standard_name in enumerate(sorted(standards_coverage)): - for version, info in standards_coverage[standard_name].items(): - coverage_ws.write(0, index, - Standard(standard_name, version).full_name) - coverage_ws.write(1, index, info.get('%'), percent_fmt) - - wb.close() - return path - - @staticmethod - def derive_errors_report_excel_path( - file_name: str, failed_rules: Dict[str, List[Dict]], - subtype: Optional[str] = None - ): - subtype_to_sheet_ref = { - ACCESS_TYPE: 'Access Errors', - CORE_TYPE: 'Core Errors' - } - - headers = ['rule', 'account_name', 'job_id', 'region', - 'failure_reason', 'traceback'] - errors_report = [] - for rule, errors in failed_rules.items(): - for job_errors in errors: - for job_id, job_error in job_errors.items(): - job_error['rule'] = rule - job_error['job_id'] = job_id - errors_report.append(job_error) - - if os.name == 'nt': - # there are problems with xlsx files on Windows if there is - # a colon in the name - file_name = file_name.replace(':', '_') - path = os.path.join(tempfile.gettempdir(), file_name) - - wb = Workbook(filename=path) - sheet_name = subtype_to_sheet_ref.get(subtype, 'Execution Errors') - ws = wb.add_worksheet(sheet_name) - first_row = 0 - for index, header in enumerate(headers): - ws.write(first_row, index, header) - row = 1 - for item in errors_report: - for index, key in enumerate(headers): - value = item.get(key, '') - ws.write(row, index, value) - row += 1 - - wb.close() - return path + @cache.cachedmethod(lambda self: self._ipv4_cache) + def _resolve_instance_public_ipv4(self) -> str | None: + _LOG.info('Trying to resolve instance ipv4') + url = 'http://169.254.169.254/latest/meta-data/public-ipv4' + try: + with urllib.request.urlopen(url, timeout=1) as resp: + return resp.read().decode() + except urllib.error.URLError: + _LOG.warning('Cannot resolve public-ipv4 from instance metadata') - @staticmethod - def derive_rule_statistics_report_xlsx_path( - file_name: str, averaged_statistics: List[Dict] - ): + def _prepare_url(self, url: str) -> str: """ - Writes to xlsx - :param file_name: - :param averaged_statistics: + In case public ipv4 is available -> replaces host in url to that ipv4. + Otherwise, is host is equal to `minio` -> replaces to 127.0.0.1 + (because 'minio' is docker compose domain) + :param url: :return: """ - headers = [ - 'policy_name', 'invocations', 'succeeded', 'failed', 'skipped', - 'resources_scanned', 'violating_resources', 'min_exec', 'max_exec', - 'total_exec' - ] - if os.name == 'nt': - # there are problems with xlsx files on Windows if there is - # a colon in the name - file_name = file_name.replace(':', '_') - path = os.path.join(tempfile.gettempdir(), file_name) - wb = Workbook(path) - ws = wb.add_worksheet('Rules statistics') - first_row = 0 - for index, header in enumerate(headers): - ws.write(first_row, index, header) - row = 1 - for item in averaged_statistics: - for index, key in enumerate(headers): - value = item.get(key, '') - ws.write(row, index, value) - row += 1 - wb.close() - return path - - def formatted_to_dojo_policy_report(self, - detailed_report: Dict[str, List[Dict]], - cloud: Optional[str] = None - ) -> List[PolicyReportItem]: + parsed: Url = parse_url(url) + if ipv4 := self._resolve_instance_public_ipv4(): + new_host = ipv4 + elif parsed.host == 'minio': + new_host = '127.0.0.1' + else: + new_host = parsed.host + return Url( + scheme=parsed.scheme, + auth=parsed.auth, + host=new_host, + port=parsed.port, + path=parsed.path, + query=parsed.query, + fragment=parsed.fragment + ).url + + def one_time_url(self, buffer: BinaryIO, filename: str) -> str: """ - Returns a dojo policy report out of formatted, region-specific - policy report. - - :param detailed_report: Dict[str, List[Dict]] - :param cloud: Optional[str] = None - :return: List[Dict] + Can be used to generate one time presigned urls. Such files will be + placed to a temp directory in S3 where they will be removed by + lifecycle policies. These files a basically temp files + :param buffer: + :param filename: desired filename for the file that will be downloaded + :return: """ - policy_report: List[Dict] = [] - _human = self.mappings_collector.human_data or {} - _severity = self.mappings_collector.severity or {} - for region, policies in detailed_report.items(): - for policy_scope in policies: - policy = policy_scope.get('policy') or {} - name = policy.get('name') - resources = policy_scope.get('resources') or [] - - # _cloud = cloud - # if not cloud and '.' in resource_type: - # _cloud, _ = resource_type.split(sep='.', maxsplit=1) - - # No `custodian-run-log` to check within. - # run_result = 'Unknown' - - _multi_regional = policy.get("multiregional") - if not _multi_regional: - _multi_regional = 'false' - - resources = self._derive_policy_report_resources( - report_fields=_human.get(name, {}).get('report_fields'), - resource_type=policy.get('resourceType'), - resources=resources, - name=name - ) - policy_report.append({ - "description": policy.get('description'), - # "region": region, - # "multiregional": _multi_regional, - "resources": resources, - "remediation": _human.get(name, {}).get('remediation'), - "impact": _human.get(name, {}).get('impact'), - "standard": {}, - "severity": _severity.get(name), - "article": _human.get(name, {}).get('article'), - "service": policy.get('resourceType'), - "vuln_id_from_tool": name, - "tags": [region], - "report_fields": _human.get(name, {}).get('report_fields') - }) - return policy_report + key = ReportsBucketKeysBuilder.one_time_on_demand() + self.s3_client.gz_put_object( + bucket=self.environment_service.default_reports_bucket_name(), + key=key, + body=buffer + ) + return self._prepare_url(self.s3_client.gz_download_url( + bucket=self.environment_service.default_reports_bucket_name(), + key=key, + filename=filename + )) - @staticmethod - def _derive_policy_report_resources( - name: str, resource_type: str, resources: List[Dict], - report_fields: List[str]) -> List[Dict]: - - # No `custodian-run-log` to check within. - # run_result = 'Unknown' - - skey = report_fields[0] if report_fields else None - if skey: - try: - _resources = sorted(resources, key=lambda r: r[skey]) - except (BaseException, Exception) as e: - msg = f'Sorting of resources, bound to \'{name}\' policy' - msg += f', by {skey} has run into an issue: {e}.' - msg += ' Using the unsorted ones.' - _LOG.warning(msg) - - _resources = [] - for resource in resources: - _resource = {} - for resource_key, resource_value in resource.items(): - - # `report_fields` are toggled during the Dojo upload. - # ergo, keeping key-values pairs of all fields. - - if isinstance(resource_value, (str, int, float)): - _resource[resource_key] = resource_value - - elif resource_type.startswith("gcp."): - if type(resource_value) is dict: - for inner_key in resource_value: - _key = resource_key + "_" + inner_key - _resource[_key] = resource_value[inner_key] - - elif type(resource_value) is list: - _len = len(resource_value) > 0 - _target = resource_value[0] - _predicate = not isinstance(_target, dict) - # v3.3.1 todo check for all non-str values? - if _len and _predicate: - _resource[resource_key] = "\n".join( - resource_value - ) - - _resources.append(_resource or resource) - - return _resources + def one_time_url_json(self, obj: Json, filename: str) -> str: + """ + The same as the one above, but returns one time url for json object + :param obj: + :param filename: + :return: + """ + key = ReportsBucketKeysBuilder.one_time_on_demand() + self.s3_client.gz_put_object( + bucket=self.environment_service.default_reports_bucket_name(), + key=key, + body=msgspec.json.encode(obj), + content_type='application/json' + ) + return self._prepare_url(self.s3_client.gz_download_url( + bucket=self.environment_service.default_reports_bucket_name(), + key=key, + filename=filename + )) diff --git a/src/services/report_statistics_service.py b/src/services/report_statistics_service.py new file mode 100644 index 000000000..b91331f46 --- /dev/null +++ b/src/services/report_statistics_service.py @@ -0,0 +1,147 @@ +import json +import uuid +from enum import Enum + +from pynamodb.pagination import ResultIterator + +from helpers import get_logger +from helpers.constants import ReportDispatchStatus +from helpers.lambda_response import ReportNotSendException, CustodianException +from helpers.time_helper import utc_iso +from models.report_statistics import ReportStatistics +from services.abs_lambda import ProcessedEvent +from services.setting_service import SettingsService + +_LOG = get_logger(__name__) + + +class ReportStatisticsService: + def __init__(self, setting_service: SettingsService): + self._setting_service = setting_service + + @staticmethod + def job_id_from_execution_id(ex_id: str | None) -> str | None: + if not ex_id: + return + return ex_id.split(':')[-1] + + @staticmethod + def _prepare_event(event: ProcessedEvent) -> dict: + processed = {} + for k, v in event.items(): + if k in ('tenant_access_payload', 'additional_kwargs'): + continue + match v: + case Enum(): + processed[k] = v.value + case _: + processed[k] = v + return processed + + def create_from_processed_event(self, event: ProcessedEvent + ) -> ReportStatistics: + ex_id = event['body'].get('execution_job_id') + job_id = self.job_id_from_execution_id(ex_id) + attempt = event['body'].get('attempt', 0) + + body = event['body'] + tenant = ','.join( + body.get('tenant_names') or body.get('tenant_display_names') or () + ) + types = ','.join(body.get('types') or ('ALL', )) + return ReportStatistics( + id=job_id or str(uuid.uuid4()), # it actually must always exist + triggered_at=utc_iso(), + attempt=attempt, + user=event['cognito_user_id'], + level=event['resource'].split('/')[-1], + type=types, + status=ReportDispatchStatus.PENDING.value, + customer_name=event['cognito_customer'], + tenant=tenant, + event=self._prepare_event(event) + ) + + def create_failed(self, event: ProcessedEvent, + exception: ReportNotSendException | CustodianException + ) -> ReportStatistics: + item = self.create_from_processed_event(event) + item.status = ReportDispatchStatus.FAILED.value + content = exception.response.content + if isinstance(content, dict) and 'message' in content: + item.reason = content['message'] + else: + item.reason = json.dumps(content, separators=(',', ':')) + item.save() + if item.attempt == self._setting_service.get_max_attempt_number(): + _LOG.info('Max number of report retries reached. Disabling them') + self._setting_service.disable_send_reports() + return item + + def save(self, item: ReportStatistics): + item.save() + + @staticmethod + def iter_by_id(job_id: str, customer: str | None = None, + limit: int | None = None) -> ResultIterator[ReportStatistics]: + fc = None + if customer: + fc = (ReportStatistics.customer_name == customer) + return ReportStatistics.query( + hash_key=job_id, + limit=limit, + scan_index_forward=False, + filter_condition=fc + ) + + @staticmethod + def iter_by_customer(customer_name: str, triggered_at: str | None, + end_date: str | None + ) -> ResultIterator[ReportStatistics]: + rkc = None + if triggered_at and end_date: + rkc = ReportStatistics.triggered_at.between( + lower=triggered_at, + upper=end_date + ) + elif triggered_at: + rkc = (ReportStatistics.triggered_at >= triggered_at) + elif end_date: + rkc = (ReportStatistics.triggered_at < end_date) + return ReportStatistics.customer_name_triggered_at_index.query( + hash_key=customer_name, + range_key_condition=rkc + ) + + @staticmethod + def iter_pending(page_size: int = 10) -> ResultIterator[ReportStatistics]: + return ReportStatistics.status_index.query( + hash_key=ReportDispatchStatus.PENDING.value, + page_size=page_size + ) + + @staticmethod + def update(item: ReportStatistics, + status: ReportDispatchStatus | None = None, + reason: str | None = None) -> None: + actions = [] + if status: + actions.append(ReportStatistics.status.set(status.value)) + if reason: + actions.append(ReportStatistics.reason.set(reason)) + if actions: + item.update(actions=actions) + + @staticmethod + def dto(item: ReportStatistics) -> dict: + return { + 'id': item.id, + 'triggered_at': item.triggered_at, + 'attempt': item.attempt, + 'types': item.type.split(',') if item.type else [], + 'level': item.level, + 'status': item.status, + 'customer': item.customer_name, + 'tenants': item.tenant.split(',') if item.tenant else [], + 'reason': item.reason + } diff --git a/src/services/reports_bucket.py b/src/services/reports_bucket.py new file mode 100644 index 000000000..d5706513c --- /dev/null +++ b/src/services/reports_bucket.py @@ -0,0 +1,364 @@ +import tempfile +from abc import ABC, abstractmethod +from datetime import datetime, timezone, date +from pathlib import PurePosixPath +from typing import TYPE_CHECKING, Optional + +from helpers import urljoin +from helpers.constants import Cloud +from helpers.time_helper import utc_datetime, week_number +from models.batch_results import BatchResults +from models.job import Job +from services import SP + +if TYPE_CHECKING: + from modular_sdk.models.tenant import Tenant + from services.platform_service import Platform + + +class ReportsBucketKeysBuilder(ABC): + """ + Paths must look like this: + raw/EPAM Systems/AWS/31231231231/latest/0.json.gz + raw/EPAM Systems/AWS/31231231231/latest/1.json.gz + + raw/EPAM Systems/AWS/31231231231/snapshots/2023-12-10-14/ + + raw/EPAM Systems/AWS/31231231231/jobs/standard/2023-12-10-14/b00649c9-2657-4ade-bd6b-f0f5924f6a50/result/ # noqa + + raw/EPAM Systems/AWS/31231231231/jobs/event-driven/2023-12-10-14/b00649c9-2657-4ade-bd6b-f0f5924f6a50/result/ # noqa + raw/EPAM Systems/AWS/31231231231/jobs/event-driven/2023-12-10-14/b00649c9-2657-4ade-bd6b-f0f5924f6a50/difference/ # noqa + """ + date_delimiter = '-' + + prefix = 'raw/' + on_demand = 'on-demand/' # any on-flight generated reports + snapshots = 'snapshots/' + latest = 'latest/' + jobs = 'jobs/' + standard = 'standard/' + ed = 'event-driven/' + result = 'result/' + difference = 'difference/' + + @staticmethod + def urljoin(*args) -> str: + return urljoin(*args) + '/' # delimiter + + @staticmethod + def datetime(_from: Optional[datetime] = None) -> str: + """ + Builds datetime part of a path with 1 hour precision in UTC. + By default, uses the current datetime. + '2023-11-02-09/' + :return: + """ + _from = _from or utc_datetime() + _from = _from.astimezone(timezone.utc) # just in case + return _from.strftime(ReportsBucketKeysBuilder.date_delimiter.join( + ['%Y', '%m', '%d', '%H'] + ) + '/') + + @property + @abstractmethod + def cloud(self) -> Cloud: + """ + :return: + """ + + @abstractmethod + def job_result(self, job: 'Job') -> str: + """ + Builds s3 key for a concrete job + :param job: + :return: + """ + + @abstractmethod + def ed_job_result(self, br: 'BatchResults') -> str: + """ + Builds s3 key for a concrete ed job + :param br: + :return: + """ + + @abstractmethod + def ed_job_difference(self, br: 'BatchResults') -> str: + """ + Builds s3 key for a concrete ed job difference + :param br: + :return: + """ + + @abstractmethod + def latest_key(self) -> str: + """ + Builds s3 key to the latest state + :return: + """ + + @abstractmethod + def snapshots_folder(self) -> str: + """ + Returns a path to a folder with snapshots + """ + + def snapshot_key(self, date: datetime) -> str: + """ + Returns a path to snapshot for the given date. You can definitely + use this key to write files. But if you want to read the data you + should get a key with the nearest older date + :param date: + :return: + """ + return self.urljoin( + self.snapshots_folder(), + self.datetime(date) + ) + + def nearest_snapshot_key(self, date: datetime) -> str | None: + """ + Returns the nearest to given date existing snapshot key + """ + prefixes = SP.s3.common_prefixes( + bucket=SP.environment_service.default_reports_bucket_name(), + delimiter='/', + prefix=self.snapshots_folder() + ) + to_check = self.urljoin( + self.snapshots_folder(), + self.datetime(date) + ) + lower = None + for prefix in prefixes: + if prefix <= to_check: + lower = prefix + elif not lower: + lower = prefix + break + else: + break + return lower + + @staticmethod + def _random_filename() -> str: + """ + Each time returns a random name for a file + :return: + """ + with tempfile.NamedTemporaryFile() as file: + return PurePosixPath(file.name).name + + @classmethod + def one_time_on_demand(cls) -> str: + """ + Generates random one time + :return: + """ + return cls.on_demand + cls._random_filename() + + +class TenantReportsBucketKeysBuilder(ReportsBucketKeysBuilder): + def __init__(self, tenant: 'Tenant'): + self._tenant = tenant + + @property + def cloud(self) -> Cloud: + """ + Only AWS|AZURE|GOOGLE currently + :return: + """ + return Cloud[self._tenant.cloud.upper()] + + def job_result(self, job: 'Job') -> str: + assert job.tenant_name == self._tenant.name, \ + f'Job tenant must be {self._tenant.name}' + return self.urljoin( + self.prefix, + self._tenant.customer_name, + self.cloud.value, + self._tenant.project, + self.jobs, + self.standard, + self.datetime(utc_datetime(job.submitted_at)), + job.id, + self.result, + ) + + def ed_job_result(self, br: 'BatchResults') -> str: + assert br.tenant_name == self._tenant.name, \ + f'Job tenant must be {self._tenant.name}' + return self.urljoin( + self.prefix, + self._tenant.customer_name, + self.cloud.value, + self._tenant.project, + self.jobs, + self.ed, + self.datetime(utc_datetime(br.submitted_at)), + br.id, + self.result, + ) + + def ed_job_difference(self, br: 'BatchResults') -> str: + assert br.tenant_name == self._tenant.name, \ + f'Job tenant must be {self._tenant.name}' + return self.urljoin( + self.prefix, + self._tenant.customer_name, + self.cloud.value, + self._tenant.project, + self.jobs, + self.ed, + self.datetime(utc_datetime(br.submitted_at)), + br.id, + self.difference, + ) + + def latest_key(self) -> str: + return self.urljoin( + self.prefix, + self._tenant.customer_name, + self.cloud.value, + self._tenant.project, + self.latest + ) + + def snapshots_folder(self) -> str: + return self.urljoin( + self.prefix, + self._tenant.customer_name, + self.cloud.value, + self._tenant.project, + self.snapshots + ) + + +class PlatformReportsBucketKeysBuilder(ReportsBucketKeysBuilder): + + def __init__(self, platform: 'Platform'): + self._platform = platform + + @property + def cloud(self) -> Cloud: + """ + Currently, the only platform that we support is KUBERNETES + """ + return Cloud.KUBERNETES + + def job_result(self, job: 'Job') -> str: + assert job.platform_id == self._platform.id, \ + f'Job platform must be {self._platform.id}' + + return self.urljoin( + self.prefix, + self._platform.customer, + self.cloud.value, + self._platform.platform_id, + self.jobs, + self.standard, + self.datetime(utc_datetime(job.submitted_at)), + job.id + ) + + def ed_job_result(self, br: 'BatchResults') -> str: + raise NotImplementedError('Event-driven is not available for platform') + + def ed_job_difference(self, br: 'BatchResults') -> str: + raise NotImplementedError('Event-driven is not available for platform') + + def latest_key(self) -> str: + return self.urljoin( + self.prefix, + self._platform.customer, + self.cloud.value, + self._platform.platform_id, + self.latest + ) + + def snapshots_folder(self) -> str: + return self.urljoin( + self.prefix, + self._platform.customer, + self.cloud.value, + self._platform.platform_id, + self.snapshots + ) + + +class StatisticsBucketKeysBuilder: + _statistics = 'job-statistics/' + _standard = 'standard/' + _ed = 'event-driven/' + _statistics_file = 'statistics.json' + _diagnostic_report_file = 'diagnostic_report.json' + _report_statistics = 'report-statistics/' + _tenant_statistics = 'tenant-statistics/' + _rules = 'rules/' + _diagnostic = 'diagnostic/' + + @classmethod + def job_statistics(cls, job: Job | BatchResults) -> str: + if isinstance(job, Job): + return urljoin( + cls._statistics, + cls._standard, + job.id, + cls._statistics_file + ) + return urljoin( + cls._statistics, + cls._ed, + job.id, + cls._statistics_file + ) + + @classmethod + def report_statistics(cls, now: date, customer: str) -> str: + return urljoin( + cls._report_statistics, + cls._diagnostic, + customer, + now.strftime(ReportsBucketKeysBuilder.date_delimiter.join( + ['%Y', '%m']) + '/'), + cls._diagnostic_report_file + ) + + @classmethod + def tenant_statistics(cls, now: date, tenant: Optional['Tenant'] = None, + customer: Optional[str] = None) -> str: + if customer: + return urljoin( + cls._tenant_statistics, + cls._rules, + customer, + now.strftime(ReportsBucketKeysBuilder.date_delimiter.join( + ['%Y', '%m']) + '/') + ) + elif tenant: + return urljoin( + cls._tenant_statistics, + cls._rules, + tenant.customer_name, + now.strftime(ReportsBucketKeysBuilder.date_delimiter.join( + ['%Y', '%m']) + '/'), + tenant.cloud, + tenant.project, + str(week_number(now)) + '.json' + ) + return urljoin( + cls._tenant_statistics, + cls._rules + ) + + @classmethod + def xray_log(cls, job_id: str) -> str: + now = utc_datetime() + return urljoin( + 'xray', + 'executor', + now.year, + now.month, + now.day, + f'{job_id}.log' + ) diff --git a/src/services/rule_meta_service.py b/src/services/rule_meta_service.py index 45dc6e303..d7310ee62 100644 --- a/src/services/rule_meta_service.py +++ b/src/services/rule_meta_service.py @@ -1,60 +1,53 @@ -import base64 -import gzip -import json from datetime import datetime -from http import HTTPStatus from itertools import chain -from typing import Optional, List, Dict, Union, Iterator, Generator, \ - Iterable, Tuple, Any, Set, Callable, TypedDict +from typing import Optional, Iterator, Generator, Iterable, Any, Literal from modular_sdk.models.pynamodb_extension.pynamodb_to_pymongo_adapter import \ Result -from pydantic import BaseModel, Field, validator, root_validator +from pydantic import BaseModel, Field, model_validator, field_validator, \ + ConfigDict from pynamodb.expressions.condition import Condition +from typing_extensions import Self -from helpers import build_response, adjust_cloud +from helpers import adjust_cloud from helpers.constants import COMPOUND_KEYS_SEPARATOR, ID_ATTR, NAME_ATTR, \ - VERSION_ATTR, FILTERS_ATTR, LOCATION_ATTR, CLOUD_ATTR, \ - COMMENT_ATTR -from helpers.enums import RuleDomain + VERSION_ATTR, FILTERS_ATTR, LOCATION_ATTR, CLOUD_ATTR, COMMENT_ATTR, \ + RuleDomain from helpers.log_helper import get_logger from helpers.time_helper import utc_iso from models.rule import Rule -from models.rule_meta import RuleMeta -from services import SERVICE_PROVIDER from services.base_data_service import BaseDataService -from services.s3_settings_service import S3SettingsService -from services.setting_service import SettingsService _LOG = get_logger(__name__) class RuleMetaModel(BaseModel): - class Config: - use_enum_values = True - extra = 'ignore' - anystr_strip_whitespace = True + model_config = ConfigDict(use_enum_values=True, extra='ignore', + str_strip_whitespace=True, + coerce_numbers_to_str=True) name: str version: str - cloud: Optional[str] # AWS, AZURE, GCP, currently not important for us - platform: Optional[List[str]] = [] # Kubernetes, as well not so important + cloud: str | None = None # AWS, AZURE, GCP, currently not important for us + platform: list[str] = Field( + default_factory=list) # Kubernetes, as well not so important source: str # "EPAM" - service: Optional[str] - category: Optional[str] - article: Optional[str] + service: str | None = None + category: str | None = None + article: str | None = None service_section: str impact: str severity: str # make choice? min_core_version: str - report_fields: List[str] = Field(default_factory=list) + report_fields: list[str] = Field(default_factory=list) multiregional: bool = False # false by default only for AWS events: dict = Field(default_factory=dict) standard: dict = Field(default_factory=dict) mitre: dict = Field(alias='MITRE', default_factory=dict) remediation: str - @validator('events', pre=False) + @field_validator('events', mode='after') + @classmethod def process_events(cls, value: dict) -> dict: processed = {} for source, names in value.items(): @@ -63,13 +56,13 @@ def process_events(cls, value: dict) -> dict: )) return processed - @root_validator(pre=False) - def validate_multiregional(cls, values: dict) -> dict: - if values['cloud'] != RuleDomain.AWS.value: - values['multiregional'] = True - return values + @model_validator(mode='after') + def validate_multiregional(self) -> Self: + if self.cloud != RuleDomain.AWS.value: + self.multiregional = True + return self - def get_domain(self) -> Optional[RuleDomain]: + def get_domain(self) -> RuleDomain | None: """ Returns the value which represents the Custom Core domain (adapter or plugin) which this rule uses. In case it's a cloud, it's @@ -87,15 +80,28 @@ def get_domain(self) -> Optional[RuleDomain]: class RuleModel(BaseModel): - class Config: - extra = 'ignore' - anystr_strip_whitespace = True + model_config = ConfigDict(extra='ignore', str_strip_whitespace=True) name: str resource: str description: str - filters: List[Dict] - comment: Optional[str] # index + filters: list[dict | str] = Field(default_factory=list) + comment: str | None = None # index + + @property + def cloud(self) -> RuleDomain: + cl = self.resource.split('.', maxsplit=1)[0] + match cl: + case 'aws': + return RuleDomain.AWS + case 'azure': + return RuleDomain.AZURE + case 'gcp': + return RuleDomain.GCP + case 'k8s': + return RuleDomain.KUBERNETES + case _: + return RuleDomain.AWS class RuleName: @@ -103,9 +109,8 @@ class RuleName: Represents rule name scheme used by security team. """ known_clouds = {'aws', 'azure', 'gcp', 'k8s'} # inside rule name - Resolved = Tuple[ - Optional[str], Optional[str], Optional[str], Optional[str] - ] + Resolved = tuple[str | None, str | None, str | None, str | None] + __slots__ = ('_raw', '_resolved') def __init__(self, raw: str): """ @@ -159,14 +164,15 @@ def cloud(self) -> Optional[RuleDomain]: :return: """ _raw = self.cloud_raw - if _raw == 'aws': - return RuleDomain.AWS - if _raw == 'azure': - return RuleDomain.AZURE - if _raw == 'gcp': - return RuleDomain.GCP - if _raw == 'k8s': - return RuleDomain.KUBERNETES + match self.cloud_raw: + case 'aws': + return RuleDomain.AWS + case 'azure': + return RuleDomain.AZURE + case 'gcp': + return RuleDomain.GCP + case 'k8s': + return RuleDomain.KUBERNETES @property def number(self) -> Optional[str]: @@ -181,12 +187,12 @@ def raw(self) -> str: return self._raw -class RuleNamesResolver: - Payload = Tuple[str, bool] +class RuleNamesResolver: # TODO test + __slots__ = ('_available_ids', '_allow_multiple', '_allow_ambiguous') + Payload = tuple[str, bool] - def __init__(self, resolve_from: List[str], - allow_multiple: Optional[bool] = False, - allow_ambiguous: Optional[bool] = False): + def __init__(self, resolve_from: list[str], allow_multiple: bool = False, + allow_ambiguous: bool = False): """ :param allow_multiple: whether to allow to resolve multiple rules from one provided name (in case the name is ambiguous) @@ -268,11 +274,7 @@ def resolved_names(self, names: Iterable[str] class RuleService(BaseDataService[Rule]): - FilterValue = Union[str, Set[str]] - - def __init__(self, mappings_collector: 'LazyLoadedMappingsCollector'): - super().__init__() - self._mappings_collector = mappings_collector + FilterValue = str | set[str] @staticmethod def gen_rule_id(customer: str, cloud: Optional[str] = None, @@ -328,13 +330,13 @@ def gen_location(git_project: Optional[str] = None, def create(self, customer: str, name: str, resource: str, description: str, cloud: Optional[str] = None, - filters: Optional[List[Dict]] = None, + filters: Optional[list[dict]] = None, comment: Optional[str] = None, version: Optional[str] = None, path: Optional[str] = None, ref: Optional[str] = None, commit_hash: Optional[str] = None, - updated_date: Optional[Union[datetime, str]] = None, + updated_date: Optional[str | datetime] = None, git_project: Optional[str] = None) -> Rule: if isinstance(updated_date, datetime): updated_date = utc_iso(updated_date) @@ -466,7 +468,7 @@ def without_duplicates(rules: Iterable[Rule], the latest version will be kept. If rules_version is specified, it will be preferred in such described cases """ - name_rule: Dict[str, Rule] = {} + name_rule: dict[str, Rule] = {} for rule in rules: _name = rule.name if _name not in name_rule: @@ -484,7 +486,7 @@ def without_duplicates(rules: Iterable[Rule], name_rule[_name] = rule # override with the largest version yield from name_rule.values() - def dto(self, item: Rule) -> Dict[str, Any]: + def dto(self, item: Rule) -> dict[str, Any]: dct = super().dto(item) dct.pop(ID_ATTR, None) dct.pop(FILTERS_ATTR, None) @@ -534,7 +536,8 @@ def get_by(self, customer: str, project: Optional[str] = None, version: Optional[str] = None, ascending: bool = False, limit: Optional[int] = None, last_evaluated_key: Optional[dict] = None, - index: Optional[str] = 'c-l-index') -> Result: + index: Literal['c-l-index', 'c-id-index'] = 'c-l-index' + ) -> Result: """ A hybrid between get_by_id_index and get_by_location_index. This method can use either index. Which one will perform more @@ -584,36 +587,6 @@ def get_by(self, customer: str, project: Optional[str] = None, filter_condition=condition ) - def resolve_names_from_map(self, names: Union[List[str], Set[str]], - clouds: Set[RuleDomain] = None, - allow_multiple: Optional[bool] = False, - allow_ambiguous: Optional[bool] = False - ) -> Generator[Tuple[str, bool], None, None]: - """ - Resolves rules using mappings from meta - """ - clouds = clouds or set(RuleDomain.iter()) - cloud_rules = self._mappings_collector.cloud_rules or {} - resolver = RuleNamesResolver( - resolve_from=list(chain.from_iterable( - cloud_rules.get(str(cloud)) or [] for cloud in clouds - )), - allow_ambiguous=allow_ambiguous, - allow_multiple=allow_multiple - ) - yield from resolver.resolve_multiple_names(names) - - def resolved_names(self, *args, **kwargs) -> Generator[str, None, None]: - """ - Ignores whether the rule was resolved or not. Just tries to do it - :param args: - :param kwargs: - :return: - """ - yield from ( - name for name, _ in self.resolve_names_from_map(*args, **kwargs) - ) - @staticmethod def filter_by(rules: Iterable[Rule], customer: Optional[FilterValue] = None, @@ -668,438 +641,3 @@ def _check(rule: Rule) -> bool: return True return filter(_check, rules) - - -class HumanData(TypedDict): - """ - Human-targeted info about rule - """ - article: Optional[str] - impact: str - report_fields: List[str] - remediation: str - multiregional: bool - - -class MappingsCollector: - """ - This class helps to retrieve specific projections from rule's meta and - keep them as mappings to allow more cost-effective access - """ - SeverityType = Dict[str, str] # rule to severity - StandardType = Dict[str, Dict] # rule to standards map - MitreType = Dict[str, Dict] # rule to mitre map - ServiceSectionType = Dict[str, str] # rule to service section - ServiceType = Dict[str, str] # rule to service section - CategoryType = Dict[str, str] # rule to category - CloudRulesType = Dict[str, Set[str]] # cloud to rules - Events = Dict[str, Dict[str, List[str]]] - HumanDataType = Dict[str, HumanData] - - def __init__(self, compressor=gzip): - """ - Compressor must implement compress and decompress. Use something from - standard library - :param compressor: - """ - self._severity = {} - self._standard = {} - self._mitre = {} - self._service_section = {} - self._cloud_rules = {} - self._human_data = {} - self._category = {} - self._service = {} - - self._aws_standards_coverage = {} - self._azure_standards_coverage = {} - self._google_standards_coverage = {} - - self._aws_events = {} - self._azure_events = {} - self._google_events = {} - - self._compressor = compressor - - def event_map(self, cloud: str) -> Optional[dict]: - if cloud == RuleDomain.AWS: - return self._aws_events - if cloud == RuleDomain.AZURE: - return self._azure_events - if cloud == RuleDomain.GCP: - return self._google_events - - def add_meta(self, meta: RuleMetaModel): - self._severity[meta.name] = meta.severity - self._mitre[meta.name] = meta.mitre - self._service_section[meta.name] = meta.service_section - self._category[meta.name] = meta.category - self._service[meta.name] = meta.service - self._standard[meta.name] = meta.standard - domain = meta.get_domain() - if domain: - self._cloud_rules.setdefault(domain, []).append(meta.name) - if meta.cloud: - self._cloud_rules.setdefault(meta.cloud, []).append(meta.name) - self._human_data[meta.name] = { - 'article': meta.article, - 'impact': meta.impact, - 'report_fields': meta.report_fields, - 'remediation': meta.remediation, - 'multiregional': meta.multiregional, - 'service': meta.service - } - - _map = self.event_map(domain) - if isinstance(_map, dict): - for source, names in meta.events.items(): - _map.setdefault(source, {}) - for name in names: # here already parsed, without ',' - _map[source].setdefault(name, []).append(meta.name) - - def dumps_json(self, data: dict) -> str: - _LOG.debug(f'Dumping to JSON and compressing some data') - return base64.b64encode( - self._compressor.compress( - json.dumps(data, separators=(',', ':')).encode() - ) - ).decode() - - def loads_json(self, data: Union[str, bytes]) -> dict: - _LOG.debug(f'Un-compressing and loading JSON data') - return json.loads(self._compressor.decompress(base64.b64decode(data))) - - @property - def severity(self) -> SeverityType: - return self._severity - - @severity.setter - def severity(self, value: SeverityType): - self._severity = value - - @property - def standard(self) -> StandardType: - return self._standard - - @standard.setter - def standard(self, value: StandardType): - self._standard = value - - @property - def mitre(self) -> MitreType: - return self._mitre - - @mitre.setter - def mitre(self, value: MitreType): - self._mitre = value - - @property - def service_section(self) -> ServiceSectionType: - return self._service_section - - @service_section.setter - def service_section(self, value: ServiceSectionType): - self._service_section = value - - @property - def cloud_rules(self) -> CloudRulesType: - return self._cloud_rules - - @cloud_rules.setter - def cloud_rules(self, value: CloudRulesType): - self._cloud_rules = value - - @property - def human_data(self) -> HumanDataType: - return self._human_data - - @human_data.setter - def human_data(self, value: HumanDataType): - self._human_data = value - - @property - def service(self) -> ServiceType: - return self._service - - @service.setter - def service(self, value: ServiceType): - self._service = value - - @property - def category(self) -> CategoryType: - return self._category - - @category.setter - def category(self, value: CategoryType): - self._category = value - - @property - def aws_standards_coverage(self) -> dict: - return self._aws_standards_coverage - - @aws_standards_coverage.setter - def aws_standards_coverage(self, value: dict): - self._aws_standards_coverage = value - - @property - def azure_standards_coverage(self) -> dict: - return self._azure_standards_coverage - - @azure_standards_coverage.setter - def azure_standards_coverage(self, value: dict): - self._azure_standards_coverage = value - - @property - def google_standards_coverage(self) -> dict: - return self._google_standards_coverage - - @google_standards_coverage.setter - def google_standards_coverage(self, value: dict): - self._google_standards_coverage = value - - @property - def aws_events(self) -> Events: - return self._aws_events - - @aws_events.setter - def aws_events(self, value: Events): - self._aws_events = value - - @property - def azure_events(self) -> Events: - return self._azure_events - - @azure_events.setter - def azure_events(self, value: Events): - self._azure_events = value - - @property - def google_events(self) -> Events: - return self._google_events - - @google_events.setter - def google_events(self, value: Events): - self._google_events = value - - def compressed(self, value: dict) -> str: - return self.dumps_json(value) - - def decompressed(self, value: str) -> dict: - return self.loads_json(value) - - -class LazyLoadedMappingsCollector: - """ - Read only class which allows to load mappings lazily. Currently, it - loads them from S3 - """ - - def __init__(self, collector: MappingsCollector, - settings_service: SettingsService, - s3_settings_service: S3SettingsService, - abort_if_not_found: bool = True): - self._collector = collector - self._settings_service = settings_service - self._s3_settings_service = s3_settings_service - self._abort_if_not_found = abort_if_not_found - - @classmethod - def build(cls) -> 'LazyLoadedMappingsCollector': - return cls( - collector=MappingsCollector(), - settings_service=SERVICE_PROVIDER.settings_service(), - s3_settings_service=SERVICE_PROVIDER.s3_settings_service(), - abort_if_not_found=True - ) - - @staticmethod - def abort(domain: str = 'mapping'): - return build_response( - code=HTTPStatus.SERVICE_UNAVAILABLE, - content=f'Cannot access {domain} data' - ) - - def _load_setting(self, name: str, get: Callable, - abort: Optional[bool] = None): - """ - :param name: MappingsCollector property name. - :param get: method to get value - :return: - """ - if abort is None: - abort = self._abort_if_not_found - if not getattr(self._collector, name, {}): - _LOG.debug(f'Loading setting: {name}') - _raw = get() - if not _raw and abort: - return self.abort(name) - if _raw: - setattr(self._collector, name, - self._collector.decompressed(_raw)) - return getattr(self._collector, name, {}) or {} - - def _load_s3(self, name: str, get: Callable, - abort: Optional[bool] = None): - """ - From s3 setting services files already decompressed - :param name: - :param get: - :return: - """ - if abort is None: - abort = self._abort_if_not_found - if not getattr(self._collector, name, {}): - _LOG.debug(f'Loading s3: {name}') - _raw = get() - if not _raw and abort: - return self.abort(name) - if _raw: - setattr(self._collector, name, _raw) - return getattr(self._collector, name, {}) or {} - - _load = _load_s3 - - @property - def category(self) -> dict: - return self._load('category', - self._s3_settings_service.rules_to_category) - - @property - def service(self) -> dict: - return self._load('service', - self._s3_settings_service.rules_to_service) - - @property - def severity(self) -> dict: - return self._load('severity', - self._s3_settings_service.rules_to_severity) - - @property - def service_section(self) -> dict: - return self._load('service_section', - self._s3_settings_service.rules_to_service_section) - - @property - def standard(self) -> dict: - return self._load('standard', - self._s3_settings_service.rules_to_standards) - - @property - def mitre(self) -> dict: - return self._load('mitre', - self._s3_settings_service.rules_to_mitre) - - @property - def cloud_rules(self) -> dict: - """ - This mapping is kind of special. It's just auxiliary. It means that - the functions, this mapping is designed for, can be used without the - map (obviously they will work worse, but still, they will work), - whereas all the other mappings are required for the functional they - provided for - :return: - """ - return self._load( - 'cloud_rules', - self._s3_settings_service.cloud_to_rules, - abort=False - ) - - @property - def human_data(self) -> dict: - return self._load( - 'human_data', - self._s3_settings_service.human_data, - abort=False - ) - - @property - def aws_standards_coverage(self) -> dict: - def get(): - return self._s3_settings_service.aws_standards_coverage() - # if val and isinstance(val.value, dict): - # return val.value.get('value') - - return self._load('aws_standards_coverage', get) - - @property - def azure_standards_coverage(self) -> dict: - def get(): - return self._s3_settings_service.azure_standards_coverage() - # if val and isinstance(val.value, dict): - # return val.value.get('value') - - return self._load('azure_standards_coverage', get) - - @property - def google_standards_coverage(self) -> dict: - def get(): - return self._s3_settings_service.google_standards_coverage() - # if val and isinstance(val.value, dict): - # return val.value.get('value') - - return self._load('google_standards_coverage', get) - - @property - def aws_events(self) -> dict: - return self._load('aws_events', - self._s3_settings_service.aws_events) - - @property - def azure_events(self) -> dict: - return self._load('azure_events', - self._s3_settings_service.azure_events) - - @property - def google_events(self) -> dict: - return self._load('google_events', - self._s3_settings_service.google_events) - - -class RuleMetaService(BaseDataService[RuleMeta]): - def get_rule_meta(self, rule: Rule) -> Optional[RuleMeta]: - """ - If the rule does not contain version we must receive the latest - available meta for this rule - :param rule: - :return: - """ - name = rule.name - version = rule.version - _LOG.debug(f'Going to retrieve meta for rule: {name}') - if version: - _LOG.debug('Rule has version. ' - 'Retrieving meta of the same version') - return next(self.get_by(name, version, - ascending=False, limit=1), None) - _LOG.debug('Rule does not have version. Retrieving the latest meta') - return self.get_latest_meta(name) - - def get_by(self, name: str, version: Optional[str] = None, - ascending: bool = False, limit: Optional[int] = None, - last_evaluated_key: Optional[dict] = None, - filter_condition: Optional[Condition] = None, - attributes_to_get: Optional[list] = None) -> Iterator[RuleMeta]: - assert False, 'Currently not allowed. Mappings must be used' - rkc = None - if version: - rkc = self.model_class.version.startswith(version) - return self.model_class.query( - hash_key=name, - range_key_condition=rkc, - scan_index_forward=ascending, - limit=limit, - last_evaluated_key=last_evaluated_key, - filter_condition=filter_condition, - attributes_to_get=attributes_to_get - ) - - def get_latest_meta(self, name: str, - attributes_to_get: Optional[list] = None - ) -> Optional[RuleMeta]: - return next(self.get_by( - name=name, - ascending=False, - limit=1, - attributes_to_get=attributes_to_get - ), None) diff --git a/src/services/rule_report_service.py b/src/services/rule_report_service.py deleted file mode 100644 index 072a07fcd..000000000 --- a/src/services/rule_report_service.py +++ /dev/null @@ -1,400 +0,0 @@ -from datetime import datetime -from http import HTTPStatus -from typing import List, Optional, Dict - -from modular_sdk.models.tenant import Tenant - -from handlers.base_handler import BaseReportHandler, SourceReportDerivation, \ - EntitySourcedReportDerivation, SourcedReport, Report, EntityToReport -from helpers.constants import ( - CUSTOMER_ATTR, TENANT_ATTR, START_ISO_ATTR, END_ISO_ATTR, HREF_ATTR, - CONTENT_ATTR, ID_ATTR, FORMAT_ATTR, JSON_ATTR, RULE_ATTR -) -from helpers.log_helper import get_logger -from services.ambiguous_job_service import Source -from services.report_service import STATISTICS_FILE - -DEFAULT_UNRESOLVABLE_RESPONSE = 'Request has run into an unresolvable issue.' - -TYPE_ATTR = 'type' -JOB_ID_ATTR = 'job_id' -RAW_ATTR = 'raw' - -SUBTYPE_ATTR = 'subtype' -ACCESS_ATTR = 'access' -CORE_ATTR = 'core' - -ENTITY_ATTR_KEY = 'entity_attr' -ENTITY_VALUE_KEY = 'entity_value' - -TENANT_NAME_ATTR = 'tenant_name' -TENANT_DISPLAY_NAME_ATTR = 'tenant_display_name' - -_LOG = get_logger(__name__) - -# Report Errors of Jobs resources -JOB_ENDPOINT = '/reports/rules/jobs/{id}' -# Report Errors of accumulated Jobs, driven by entity-driven scope. -TENANTS_ENDPOINT = '/reports/rules/tenants' -TENANT_ENDPOINT = '/reports/rules/tenants/{tenant_name}' - -NO_RESOURCES_FOR_REPORT = ' maintain(s) no resources to derive a report.' - - -class BaseRulesReportHandler(BaseReportHandler): - """ - Provides base behaviour of rule-reporting, establishing - report-derivation function. - """ - - def define_action_mapping(self): - return {} - - @property - def _source_report_derivation_function(self) -> SourceReportDerivation: - return self._statistics_report_derivation - - def _statistics_report_derivation( - self, source: Source, **kwargs: dict - ) -> Optional[Report]: - - """ - Obtains report of rule-failed statistics of a sourced job, - returning a respective report. - :param source: Source - :param kwargs: Dict - :return: Optional[Report] - """ - jid = self._ambiguous_job_service.get_attribute(source, ID_ATTR) - rs = self._report_service - path = rs.derive_job_object_path(job_id=jid, typ=STATISTICS_FILE) - return rs.pull_job_statistics(path=path) - - @property - def _entity_sourced_report_derivation_function(self) -> \ - EntitySourcedReportDerivation: - return self._entity_statistics_report_derivation - - def _entity_statistics_report_derivation( - self, sourced_reports: List[SourcedReport], **kwargs: dict - ) -> Optional[Report]: - - entity_attr: str = kwargs.get(ENTITY_ATTR_KEY, '') - entity_value: str = kwargs.get(ENTITY_VALUE_KEY, '') - href: bool = kwargs.get(HREF_ATTR, False) - frmt: str = kwargs.get(FORMAT_ATTR, '') - target_rule: str = kwargs.get(RULE_ATTR, '') - list_format: bool = kwargs.get('list_format', False) - assert entity_attr, 'Entity attribute is missing' - return self._attain_statistics_report( - sourced_reports=sourced_reports, - entity_attr=entity_attr, - entity_value=entity_value, - href=href, frmt=frmt, - target_rule=target_rule, - list_format=list_format - ) - - def _attain_statistics_report( - self, sourced_reports: List[SourcedReport], - entity_attr: str, entity_value: Optional[str] = None, - href: Optional[bool] = False, frmt: Optional[str] = None, - target_rule: Optional[str] = None, - list_format: Optional[bool] = False - ): - """ - Derives relation map of failed rule, merged amongst sourced reports. - :param sourced_reports: List[Source, List[Dict]] - :param entity_attr: str, denotes unique entity id-attribute - :param entity_value: Optional[str], denotes ta target entity-id value - :return: Dict[str, List[Dict]] - """ - head = entity_attr - if entity_value: - head += f':\'{entity_value}\'' - - ajs = self._ambiguous_job_service - rs = self._report_service - - statistics_list: List[List[Dict]] = [] - _LOG.info(head + ' extending statistics list, for each sourced list.') - - start, end = None, None - source = None - for sourced in sourced_reports: - source, statistics = sourced - - if not entity_value: - _entity = ajs.get_attribute(item=source, attr=entity_attr) - if not entity_value: - entity_value = _entity - - elif entity_value != _entity: - # Precautionary verification. - typ = ajs.get_type(item=source) - uid = ajs.get_attribute(item=source, attr=ID_ATTR) - _e_scope = f'{head}:\'{entity_value}\'' - _s_scope = f'{_entity} of {typ} \'{uid}\' job' - _LOG.error(f'{_e_scope} mismatches {_s_scope}.') - failed_rule_map = {} - break - - # Establishes start and end dates, for the report. - sk = ajs.sort_value_into_datetime(item=source) - if not start or not end: - start = end = sk - start = sk if sk > start else start - end = sk if sk < end else end - - # Derives failed rule map. - statistics_list.append(statistics) - - job_id = self._ambiguous_job_service.get_attribute(source, ID_ATTR) \ - if len(sourced_reports) == 1 else None - object_path = rs.derive_rule_report_object_path( - entity_value=entity_value, entity_attr=entity_attr, - start=start, end=end, fext=frmt, job_id=job_id - ) - - if href and frmt: - _LOG.info(head + ' going to obtain hypertext reference' - ' of the rule statistic report.') - ref: Optional[str] = self._report_service.href_job_report( - path=object_path, check=True - ) - if ref: - return ref - - # Otherwise produces data, on its own. - - message = ' removing duplicates' - if target_rule: - message += f', filtering by {target_rule} rule' - message += ' within aggregated statistic lists.' - _LOG.info(head + message) - - statistics_list: List[ - List[Dict]] = rs.derive_clean_filtered_statistics_list( - statistic_list=statistics_list, target_rule=target_rule - ) - if not statistics_list: - _LOG.warning(head + ' no statistics could be established.') - - else: - - _LOG.info(head + ' averaging out statistics.') - statistics_list: List[Dict] = rs.average_out_statistics( - statistics_list=statistics_list, list_format=list_format - ) - - if statistics_list and href and frmt: - message = f' providing hypertext reference to {frmt} file of' - message += ' rule statistics.' - _LOG.info(head + message) - if frmt == JSON_ATTR: - message = ' retaining error json hypertext reference.' - _LOG.info(head + message) - if not rs.put_json_concrete_report( - data=statistics_list, path=object_path - ): - object_path = None - - else: - _LOG.info(head + ' deriving xlsx statistics report.') - file_name = rs.derive_name_of_report_object_path( - object_path=object_path - ) - stream_path = rs.derive_rule_statistics_report_xlsx_path( - file_name=file_name, - averaged_statistics=statistics_list - ) - if stream_path: - message = ' retaining xlsx hypertext reference.' - _LOG.warning(head + message) - if rs.put_path_retained_concrete_report( - stream_path=stream_path, - object_path=object_path - ) is None: - message = ' xlsx statistic report hypertext could' - message += ' not be provided.' - _LOG.warning(head + message) - # Explicitly denote reference absence. - object_path = None - - if object_path: - message = ' obtaining hypertext reference.' - _LOG.info(head + message) - statistics_list = rs.href_concrete_report( - path=object_path, check=False - ) - - return statistics_list - - -class RuleReportService(BaseRulesReportHandler): - - def define_action_mapping(self): - return {} - - def get_by_tenant(self, event: dict): - _LOG.info(f'GET Rule Report(s) of an Tenant - {event}.') - - # `tenant` has been injected via the restriction service. - tenant_name = event[TENANT_ATTR] - # Lower bound. - start_iso: datetime = event[START_ISO_ATTR] - # Upper bound. - end_iso: datetime = event[END_ISO_ATTR] - - href = event.get(HREF_ATTR) - customer = event.get(CUSTOMER_ATTR) - - head = f'Tenant: \'{tenant_name}\'' - tenants: List[Tenant] = [ - *self._modular_service.i_get_tenant(iterator=iter([tenant_name])) - ] - if not tenants: - self._code = HTTPStatus.NOT_FOUND - self._content = head + ' does not exist' - return self.response - - referenced_reports = self.attain_referenced_reports( - entity_attr=TENANT_ATTR, entity_value=tenant_name, - start_iso=start_iso, end_iso=end_iso, - customer=customer, tenants=[tenant_name], - cloud_ids=[each.project for each in tenants], - typ=event.get(TYPE_ATTR), href=event.get(HREF_ATTR), - frmt=event.get(FORMAT_ATTR), target_rule=event.get(RULE_ATTR) - ) - - if referenced_reports: - ref_attr = HREF_ATTR if href else CONTENT_ATTR - self._code = HTTPStatus.OK - self._content = [ - self.dto( - entity_attr=TENANT_ATTR, entity_value=entity, - report=report, ref_attr=ref_attr - ) - for entity, report in referenced_reports.items() - ] - - return self.response - - def attain_referenced_reports( - self, entity_attr: str, - start_iso: datetime, end_iso: datetime, - customer: Optional[str] = None, - tenants: Optional[List[str]] = None, - cloud_ids: Optional[List[str]] = None, - typ: Optional[str] = None, - href: bool = False, frmt: Optional[str] = None, - entity_value: Optional[str] = None, - target_rule: Optional[str] = None, - source_list: Optional[List[Source]] = None, - list_format: bool = False - ) -> EntityToReport: - - cloud_ids = cloud_ids or [] - ajs = self._ambiguous_job_service - - head = '' - - # Log-Header. - - if tenants: - multiple = len(tenants) > 1 - bind = ', '.join(map("'{}'".format, tenants or [])) - head += f'{bind} tenant' - if multiple: - head += 's' - - if customer: - head = 'Tenants' if not head else head - head += f' of \'{customer}\' customer' - - typ_scope = f'{typ} type' if typ else 'all types' - time_scope = f'from {start_iso.isoformat()} till {end_iso.isoformat()}' - job_scope = f'job(s) of {typ_scope}, {time_scope}' - - entity_scope = f'\'{entity_attr}\' entity' - if entity_value: - entity_scope += f' of the \'{entity_value}\' target value' - - # todo Responsibility chain - - _LOG.info(f'Obtaining {job_scope}, for {head or "tenants"}.') - head = head or 'Tenants' - typ_params_map = ajs.derive_typ_param_map( - typ=typ, tenants=tenants, - cloud_ids=cloud_ids, - ) - if not source_list: - source_list = ajs.batch_list( - typ_params_map=typ_params_map, customer=customer, - start=start_iso, end=end_iso, sort=False - ) - if not source_list: - message = f' - no source-data of {job_scope} could be derived.' - _LOG.warning(head + message) - self._code = HTTPStatus.NOT_FOUND - self._content = head + NO_RESOURCES_FOR_REPORT - return self.response - - _source_scope = ', '.join( - ajs.get_attribute(item=s, attr=ID_ATTR) for s in source_list - ) - - message = f' retrieving statistics of {_source_scope} source(s).' - _LOG.info(head + message) - source_to_report = self._attain_source_report_map( - source_list=source_list - ) - if not source_to_report: - message = f' - no reports of {job_scope} could be derived.' - _LOG.warning(head + message) - self._code = HTTPStatus.NOT_FOUND - self._content = head + NO_RESOURCES_FOR_REPORT - return self.response - - message = f' mapping sourced reports to {entity_scope}.' - _LOG.info(head + message) - - entity_sourced_reports = self._attain_entity_sourced_reports( - entity_attr=entity_attr, entity_value=entity_value, - source_to_report=source_to_report - ) - if not entity_sourced_reports: - message = f' - no reports of {job_scope} could be mapped ' - message += f' to {entity_scope}.' - _LOG.warning(head + message) - self._code = HTTPStatus.NOT_FOUND - self._content = head + NO_RESOURCES_FOR_REPORT - return self.response - - message = f' deriving failed rule reports of {entity_scope}.' - _LOG.info(head + message) - - entity_to_report = self._attain_entity_report_map_from_sourced_reports( - entity_sourced_reports=entity_sourced_reports, - entity_attr=entity_attr, entity_value=entity_value, - href=href, format=frmt, rule=target_rule, - list_format=list_format - ) - if not entity_to_report: - message = f' - no reports of {job_scope} could be derived' - message += f' based on {entity_scope}.' - _LOG.warning(head + message) - self._code = HTTPStatus.NOT_FOUND - self._content = head + NO_RESOURCES_FOR_REPORT - return self.response - - return entity_to_report - - @staticmethod - def dto( - entity_attr: str, entity_value: str, report: Report, ref_attr: str - ): - return { - entity_attr: entity_value, - ref_attr: report - } diff --git a/src/services/rule_source_service.py b/src/services/rule_source_service.py index 2427c9c3f..ebe2ac0e5 100644 --- a/src/services/rule_source_service.py +++ b/src/services/rule_source_service.py @@ -1,20 +1,30 @@ from hashlib import md5 from http import HTTPStatus -from typing import List, Optional, Generator, Iterable, Tuple - -from helpers import build_response, list_update -from helpers.constants import GIT_ACCESS_SECRET_ATTR, CUSTOMER_ATTR, \ - ID_ATTR, GIT_PROJECT_ID_ATTR, GIT_RULES_PREFIX_ATTR, STATUS_SYNCING, \ - STATUS_SYNCED, GIT_ACCESS_TYPE_ATTR, LATEST_SYNC_ATTR, \ - RESTRICT_FROM_ATTR, COMMIT_HASH_ATTR, COMMIT_TIME_ATTR, STATUS_ATTR, \ - ALL_ATTR, ALLOWED_FOR_ATTR, RULE_SOURCE_REQUIRED_ATTRS, GIT_REF_ATTR, \ - GIT_URL_ATTR, RULE_SOURCE_ID_ATTR, TYPE_ATTR, STATUS_SYNCING_FAILED +from typing import Generator, Iterable, Optional + +from pynamodb.pagination import ResultIterator + +from helpers.constants import ( + COMMIT_HASH_ATTR, + COMMIT_TIME_ATTR, + CUSTOMER_ATTR, + GIT_ACCESS_SECRET_ATTR, + GIT_ACCESS_TYPE_ATTR, + GIT_PROJECT_ID_ATTR, + LATEST_SYNC_ATTR, + RESTRICT_FROM_ATTR, + RULE_SOURCE_ID_ATTR, + STATUS_ATTR, + STATUS_SYNCED, + STATUS_SYNCING, + STATUS_SYNCING_FAILED, + TYPE_ATTR, +) +from helpers.lambda_response import ResponseFactory from helpers.log_helper import get_logger from helpers.time_helper import utc_datetime from models.rule_source import RuleSource -from services.abstract_rule_service import AbstractRuleService -from services.clients.git_service_clients import GitLabClient, GitHubClient -from services.rbac.restriction_service import RestrictionService +from services.clients.git_service_clients import GitHubClient, GitLabClient from services.ssm_service import SSMService SSM_SECRET_NAME_TEMPLATE = 'caas.{rule_source_id}.{timestamp}.repo_secret' @@ -23,77 +33,55 @@ 'Rule source is currently being updated. ' \ 'Rule update event has not been submitted' -RULE_SOURCE_ATTRS_FOR_ID = ( - CUSTOMER_ATTR, GIT_URL_ATTR, GIT_PROJECT_ID_ATTR, - GIT_REF_ATTR, GIT_RULES_PREFIX_ATTR -) _LOG = get_logger(__name__) -RuleSourceGenerator = Generator[RuleSource, None, None] -class RuleSourceService(AbstractRuleService): +class RuleSourceService: - def __init__(self, ssm_service: SSMService, - restriction_service: RestrictionService): - super().__init__(restriction_service) + def __init__(self, ssm_service: SSMService): self.ssm_service = ssm_service - def create_rule_source(self, rule_source_data: dict) -> RuleSource: - self._validate_git_access_data(rule_source_data) - rule_source_id = self.derive_rule_source_id(**rule_source_data) - rule_source_data[ID_ATTR] = rule_source_id - git_access_secret = rule_source_data.get(GIT_ACCESS_SECRET_ATTR) + def create_rule_source(self, git_project_id: str, git_url: str, + git_ref: str, git_rules_prefix: str, + git_access_type: str, customer: str, + description: str | None = None, + git_access_secret: str | None = None) -> RuleSource: + rule_source_id = self.derive_rule_source_id( + customer=customer, + git_url=git_url, + git_project_id=git_project_id, + git_ref=git_ref, + git_rules_prefix=git_rules_prefix + ) + item = RuleSource( + id=rule_source_id, + customer=customer, + git_project_id=git_project_id, + git_url=git_url, + git_access_type=git_access_type, + git_rules_prefix=git_rules_prefix, + git_ref=git_ref, + description=description + ) if git_access_secret: _LOG.debug('Access secret was provided. Saving to ssm') - ssm_secret_name = self._save_ssm_secret( - rule_source_id=rule_source_id, - git_access_secret=git_access_secret - ) - rule_source_data[GIT_ACCESS_SECRET_ATTR] = ssm_secret_name - return RuleSource(**rule_source_data) - - def update_rule_source( - self, rule_source: RuleSource, - rule_source_data: dict - ) -> RuleSource: - - secret_name = rule_source.git_access_secret - if not rule_source_data.get(GIT_ACCESS_SECRET_ATTR): - rule_source.git_access_secret = self.ssm_service.get_secret_value( - secret_name - ) - - for key, value in rule_source_data.items(): - if not value and key != ALLOWED_FOR_ATTR: - continue - setattr(rule_source, key, value) - - _attrs = RULE_SOURCE_REQUIRED_ATTRS.copy() # pre v4.2.0 - _attrs.remove(GIT_PROJECT_ID_ATTR) - # _attrs: tuple = RULE_SOURCE_TO_UPDATE_ATTRS.copy() - if any(bool(rule_source_data.get(key)) for key in _attrs): - _LOG.debug('Some rule source access attrs changed. ' - 'Validating git access data') - self._validate_git_access_data(rule_source.get_json()) - - if rule_source_data.get(GIT_ACCESS_SECRET_ATTR): - self.ssm_service.delete_secret( - secret_name=secret_name - ) - ssm_secret_name = self._save_ssm_secret( - rule_source_id=rule_source.id, - git_access_secret=rule_source.git_access_secret - ) - rule_source.git_access_secret = ssm_secret_name - else: - rule_source.git_access_secret = secret_name - return rule_source + self.set_secret(item, git_access_secret) + return item + + def set_secret(self, rule_source: RuleSource, git_access_secret: str): + if rule_source.git_access_secret: + self.ssm_service.delete_secret(rule_source.git_access_secret) + ssm_secret_name = self._save_ssm_secret( + rule_source_id=rule_source.id, + git_access_secret=git_access_secret + ) + rule_source.git_access_secret = ssm_secret_name @classmethod - def list_rule_sources(cls, customer: str = None, - git_project_id: str = None) -> list: + def list_rule_sources(cls, customer: str | None = None, + git_project_id: str | None = None) -> list: if customer: iterable = cls.i_query_by_customer( customer=customer, git_project_id=git_project_id @@ -110,7 +98,7 @@ def get(rule_source_id: str) -> Optional[RuleSource]: return RuleSource.get_nullable(hash_key=rule_source_id) def iter_by_ids(self, ids: Iterable[str] - ) -> Generator[Tuple[RuleSource, Optional[str]], None, None]: + ) -> Generator[tuple[RuleSource, Optional[str]], None, None]: """ Iterates over pairs: rule-source, secret :param ids: @@ -127,19 +115,12 @@ def iter_by_ids(self, ids: Iterable[str] ) yield item, secret - @classmethod - def get_rule_source_by_customer(cls, customer: str, - git_project_id: str) -> RuleSource: - query = cls.i_query_by_customer(customer=customer, - git_project_id=git_project_id) - return next(query, None) - @staticmethod def i_query_by_customer( customer: str, git_project_id: Optional[str] = None, limit: Optional[int] = None, last_evaluated_key: Optional[str] = None - ) -> RuleSourceGenerator: + ) -> ResultIterator[RuleSource]: gpid_attr = RuleSource.git_project_id rk = gpid_attr == git_project_id if git_project_id else None index = RuleSource.customer_git_project_id_index @@ -153,84 +134,13 @@ def save_rule_source(rule_source: RuleSource): return rule_source.save() @staticmethod - def derive_rule_source_id(**kwargs): - delimiter = ':' - encoding = 'utf-8' - missing_template = "RuleSource identifier missing - '{}'." - _LOG.info(f'Deriving RuleSource identifier based on {kwargs}.') - ordered = [] - for attr in RULE_SOURCE_ATTRS_FOR_ID: - if attr not in kwargs: - raise ValueError(missing_template.format(attr)) - ordered.append(kwargs[attr]) - string_to_hash = delimiter.join(ordered) - bytes_to_hash = string_to_hash.encode(encoding) - return md5(bytes_to_hash).hexdigest() - - @staticmethod - def derive_updated_tenants(rule_source: RuleSource, tenants: List[str], - restrict: bool): - """ - >= v4.2.0 - Returns updated-only tenants, - based on pending `allowed_for` ones. - :param rule_source: RuleSource - :param tenants: List[str] - :param restrict: bool - :return: Optional[List[str]] - """ - log_head = f'Rule-source:{rule_source.id!r}' - tenants = set(tenants) - allowed_for = set(rule_source.allowed_for or []) - op = allowed_for.difference if restrict else allowed_for.union - updated = op(tenants) - if sorted(updated) == sorted(allowed_for): - _LOG.warning( - f'{log_head} - `allowed_for`({allowed_for}) has not been updated with {tenants}.') - return - else: - _LOG.info( - f'{log_head} - `allowed_for` has been updated: {updated}.') - return list(updated) - - @classmethod - def is_subject_applicable(cls, rule_source: RuleSource, - customer: Optional[str], tenants: List[str]): - """ - Verifies whether rule-source is applicable to - subject-scope of a customer and given tenants. - :param rule_source: RuleSource - :param customer: Optional[str] - :param tenants: List[str] - :return: bool - """ - is_applicable = True - log_head = f'Rule-source:{rule_source.id!r}' - if customer and rule_source.customer != customer: - related = rule_source.customer - _LOG.warning( - f'{log_head} - {customer!r} customer must be {related!r}.') - is_applicable = False - elif tenants: - is_applicable = cls.is_tenant_applicable(rule_source=rule_source, - tenants=tenants) - return is_applicable - - @staticmethod - def is_tenant_applicable(rule_source: RuleSource, - tenants: List[str]) -> bool: - is_applicable = True - log_head = f'Rule-source:{rule_source.id!r}' - intersection = set(rule_source.allowed_for) | set( - tenants) if tenants else {} - grounds = f"{', '.join(tenants)} tenants" - if (tenants and intersection) or not tenants: - _LOG.info(f'{log_head} - is accessible based on given {grounds}.') - elif not intersection: - _LOG.warning(f'{log_head} - is not accessible for {grounds}.') - is_applicable = False - - return is_applicable + def derive_rule_source_id(customer: str, git_url: str, + git_project_id: str, git_ref: str, + git_rules_prefix: str) -> str: + string_to_hash = ':'.join(( + customer, git_url, git_project_id, git_ref, git_rules_prefix + )) + return md5(string_to_hash.encode('utf-8')).hexdigest() @staticmethod def is_allowed_to_sync(rule_source: RuleSource) -> bool: @@ -258,9 +168,8 @@ def delete_rule_source(self, rule_source: RuleSource): self.ssm_service.delete_secret(secret_name=secret_name) return rule_source.delete() - def get_rule_source_dto(self, rule_source: RuleSource) -> dict: - tenants = self._restriction_service.user_tenants - + @staticmethod + def get_rule_source_dto(rule_source: RuleSource) -> dict: rule_source_json = rule_source.get_json() rule_source_json.pop(GIT_ACCESS_SECRET_ATTR, None) rule_source_json.pop(GIT_ACCESS_TYPE_ATTR, None) @@ -269,13 +178,6 @@ def get_rule_source_dto(self, rule_source: RuleSource) -> dict: None) (rule_source_json.get(LATEST_SYNC_ATTR) or {}).pop(COMMIT_TIME_ATTR, None) - rule_source_json[ALLOWED_FOR_ATTR] = [ - tenant for tenant in - rule_source_json.get( - ALLOWED_FOR_ATTR) or [] - if - not tenants or tenant in tenants - ] or ALL_ATTR.upper() rule_source_json[TYPE_ATTR] = rule_source.type rule_source_json['has_secret'] = rule_source.has_secret return rule_source_json @@ -314,27 +216,17 @@ def _save_ssm_secret(self, rule_source_id, git_access_secret): return secret_name @staticmethod - def _validate_git_access_data(git_access_data): - project = git_access_data.get(GIT_PROJECT_ID_ATTR) - url = git_access_data.get(GIT_URL_ATTR) - secret = git_access_data.get(GIT_ACCESS_SECRET_ATTR) - is_gitlab = str(project).isdigit() + def validate_git_access_data(git_project_id: str, git_url: str, + git_access_secret: str | None = None): + is_gitlab = str(git_project_id).isdigit() if is_gitlab: - client = GitLabClient(url=url, private_token=secret) + client = GitLabClient(url=git_url, private_token=git_access_secret) else: - client = GitHubClient(url=url) - pr = client.get_project(project) + client = GitHubClient(url=git_url) + pr = client.get_project(git_project_id) if not pr: - _LOG.warning(f'Cannot access project: {project}') - return build_response( - code=HTTPStatus.BAD_REQUEST, - content=f'Cannot access {"GitLab" if is_gitlab else "GitHub"} ' - f'project {project}' - ) - - @staticmethod - def expand_systems(system_entities: List[RuleSource], - customer_entities: List[RuleSource] - ) -> List[RuleSource]: - """Updates by `id` attribute""" - return list_update(system_entities, customer_entities, ('id',)) + _LOG.warning(f'Cannot access project: {git_project_id}') + raise ResponseFactory(HTTPStatus.SERVICE_UNAVAILABLE).message( + f'Cannot access {"GitLab" if is_gitlab else "GitHub"} ' + f'project {git_project_id}' + ).exc() diff --git a/src/services/ruleset_service.py b/src/services/ruleset_service.py index 222c9b718..4bf3a62fe 100644 --- a/src/services/ruleset_service.py +++ b/src/services/ruleset_service.py @@ -1,55 +1,37 @@ -from typing import Iterator, Optional, Generator, Set +from typing import BinaryIO, Generator, Iterator, Optional, Iterable -from helpers import STATUS_READY_TO_SCAN -from helpers.constants import RULES_ATTR, RULES_NUMBER, \ - LICENSED_ATTR, ALL_ATTR, ALLOWED_FOR_ATTR, COMPOUND_KEYS_SEPARATOR, \ - ID_ATTR, NAME_ATTR, \ - VERSION_ATTR +from helpers.constants import ( + COMPOUND_KEYS_SEPARATOR, + Cloud, + ED_AWS_RULESET_NAME, + ED_AZURE_RULESET_NAME, + ED_GOOGLE_RULESET_NAME, + ED_KUBERNETES_RULESET_NAME, + ID_ATTR, + LICENSED_ATTR, + NAME_ATTR, + RULES_ATTR, + RULES_NUMBER, + VERSION_ATTR, +) from helpers.log_helper import get_logger from helpers.system_customer import SYSTEM_CUSTOMER from helpers.time_helper import utc_iso -from models.ruleset import Ruleset, RULESET_LICENSES, RULESET_STANDARD +from models.ruleset import RULESET_LICENSES, RULESET_STANDARD, Ruleset from services.base_data_service import BaseDataService from services.clients.s3 import S3Client from services.license_service import LicenseService -from services.rbac.restriction_service import RestrictionService _LOG = get_logger(__name__) class RulesetService(BaseDataService[Ruleset]): - def __init__(self, restriction_service: RestrictionService, - license_service: LicenseService, s3_client: S3Client): + def __init__(self, license_service: LicenseService, + s3_client: S3Client): super().__init__() - self._restriction_service = restriction_service self._license_service = license_service self._s3_client = s3_client - # TODO maybe remove these two --------------------------- - def filter_by_tenants(self, entities: Iterator[Ruleset], - tenants: Optional[Set[str]] = None, - ) -> Generator[Ruleset, None, None]: - """ - Filters the given iterable of entities by the list of allowed - tenants using `allowed_for` attribute - """ - _tenants = tenants or self._restriction_service.user_tenants - if not _tenants: - yield from entities - return - for entity in entities: - allowed_for = list(entity.allowed_for) - if not allowed_for or allowed_for & _tenants: - yield entity - - def get_ruleset_filtered_by_tenant(self, *args, **kwargs): - ruleset = self.get_standard(*args, **kwargs) - ruleset = next( - self.filter_by_tenants([ruleset, ] if ruleset else []), None) - return ruleset - - # ------------------------------------------------------- - def iter_licensed(self, name: Optional[str] = None, version: Optional[str] = None, cloud: Optional[str] = None, @@ -106,13 +88,30 @@ def iter_standard(self, customer: str, name: Optional[str] = None, filter_condition=filter_condition ) + def by_id(self, id: str, attributes_to_get: list = None) -> Ruleset | None: + return self.get_nullable(hash_key=id, + attributes_to_get=attributes_to_get) + + def iter_by_id(self, ids: Iterable[str]) -> Generator[Ruleset, None, None]: + processed = set() + for _id in ids: + if _id in processed: + continue + ruleset = self.by_id(_id) + if ruleset: + yield ruleset + processed.add(_id) + def by_lm_id(self, lm_id: str, attributes_to_get: Optional[list] = None ) -> Optional[Ruleset]: return next(self.model_class.license_manager_id_index.query( - hash_key=lm_id, attributes_to_get=attributes_to_get + hash_key=lm_id, + limit=1, + attributes_to_get=attributes_to_get ), None) - def iter_by_lm_id(self, lm_ids: Iterator[str]) -> Iterator[Ruleset]: + def iter_by_lm_id(self, lm_ids: Iterable[str] + ) -> Generator[Ruleset, None, None]: processed = set() for _id in lm_ids: if _id in processed: @@ -123,7 +122,7 @@ def iter_by_lm_id(self, lm_ids: Iterator[str]) -> Iterator[Ruleset]: processed.add(_id) def get_standard(self, customer: str, name: str, version: str - ) -> Optional[Ruleset]: + ) -> Ruleset | None: return next(self.iter_standard( customer=customer, name=name, @@ -191,26 +190,20 @@ def delete(self, item: Ruleset): super().delete(item) s3_path = item.s3_path.as_dict() if s3_path: - self._s3_client.delete_file( - bucket_name=s3_path.get('bucket_name'), - file_key=s3_path.get('path') + self._s3_client.gz_delete_object( + bucket=s3_path.get('bucket_name'), + key=s3_path.get('path') ) def dto(self, ruleset: Ruleset, params_to_exclude=None) -> dict: - tenants = self._restriction_service.user_tenants ruleset_json = ruleset.get_json() ruleset_json[RULES_NUMBER] = len(ruleset_json.get(RULES_ATTR) or []) for param in (params_to_exclude or []): ruleset_json.pop(param, None) - ruleset_json[ALLOWED_FOR_ATTR] = [ - tenant for tenant in (ruleset.allowed_for or []) - if (not tenants or tenant in tenants) or ALL_ATTR.upper() - ] ruleset_json[NAME_ATTR] = ruleset.name ruleset_json[VERSION_ATTR] = ruleset.version ruleset_json[LICENSED_ATTR] = ruleset.licensed - ruleset_json['code'] = ruleset.status.as_dict().get('code') ruleset_json['last_update_time'] = ruleset.status.as_dict().get( 'last_update_time') ruleset_json.pop(ID_ATTR, None) @@ -226,12 +219,56 @@ def set_s3_path(ruleset: Ruleset, bucket: str, key: str): @staticmethod def set_ruleset_status(ruleset: Ruleset, - code: Optional[str] = STATUS_READY_TO_SCAN, reason: Optional[str] = None): - status = { - 'code': STATUS_READY_TO_SCAN, - 'last_update_time': utc_iso(), - } + status = {'last_update_time': utc_iso()} if reason: status['reason'] = reason ruleset.status = status + + def get_ed_ruleset(self, cloud: Cloud) -> Ruleset | None: + """ + Event driven rule-sets belong to SYSTEM. + """ + name = None + match cloud: + case Cloud.AWS: + name = ED_AWS_RULESET_NAME + case Cloud.AZURE: + name = ED_AZURE_RULESET_NAME + case Cloud.GOOGLE: + name = ED_GOOGLE_RULESET_NAME + case Cloud.KUBERNETES: + name = ED_KUBERNETES_RULESET_NAME + sk = self.build_id( + customer=SYSTEM_CUSTOMER, + licensed=False, + name=name, + version='' + ) + return next(Ruleset.customer_id_index.query( + hash_key=SYSTEM_CUSTOMER, + range_key_condition=Ruleset.id.startswith(sk), + filter_condition=(Ruleset.event_driven == True), + limit=1, + scan_index_forward=False + ), None) + + def download(self, ruleset: Ruleset, out: BinaryIO = None) -> BinaryIO: + return self._s3_client.gz_get_object( + bucket=ruleset.s3_path['bucket_name'], + key=ruleset.s3_path['path'], + buffer=out + ) + + def download_url(self, ruleset: Ruleset) -> str: + """ + Returns a presigned url to the given file + :param ruleset: + :return: + """ + return self._s3_client.gz_download_url( + bucket=ruleset.s3_path['bucket_name'], + key=ruleset.s3_path['path'], + filename=ruleset.name, # not so important + response_encoding='gzip', + ) diff --git a/src/services/s3_settings_service.py b/src/services/s3_settings_service.py index 817b1c36c..d011460cd 100644 --- a/src/services/s3_settings_service.py +++ b/src/services/s3_settings_service.py @@ -1,27 +1,10 @@ -import gzip import importlib -import json -from pathlib import PurePosixPath, Path -from typing import Union, Optional - -from helpers.constants import KEY_AWS_EVENTS, KEY_GOOGLE_EVENTS, \ - KEY_AZURE_EVENTS, KEY_GOOGLE_STANDARDS_COVERAGE, KEY_RULES_TO_STANDARDS, \ - KEY_RULES_TO_SERVICE_SECTION, KEY_RULES_TO_MITRE, KEY_CLOUD_TO_RULES, \ - KEY_RULES_TO_SEVERITY, KEY_AZURE_STANDARDS_COVERAGE, \ - KEY_AWS_STANDARDS_COVERAGE, KEY_HUMAN_DATA, KEY_RULES_TO_SERVICE, \ - KEY_RULES_TO_CATEGORY +from pathlib import PurePosixPath + +from helpers.constants import S3SettingKey from services.clients.s3 import S3Client from services.environment_service import EnvironmentService -# this setting is not used -S3_KEY_EVENT_BRIDGE_EVENT_SOURCE_TO_RULES_MAPPING = \ - 'EVENT_BRIDGE_EVENT_SOURCE_TO_RULES_MAPPING' - -S3_KEY_MAESTRO_SUBGROUP_ACTION_TO_AZURE_EVENTS_MAPPING = \ - 'MAESTRO_SUBGROUP_ACTION_TO_AZURE_EVENTS_MAPPING' -S3_KEY_MAESTRO_SUBGROUP_ACTION_TO_GOOGLE_EVENTS_MAPPING = \ - 'MAESTRO_SUBGROUP_ACTION_TO_GOOGLE_EVENTS_MAPPING' - class S3SettingsService: SETTINGS_PREFIX = 'caas-settings' @@ -42,95 +25,73 @@ def key_from_name(self, key: str) -> str: return str(PurePosixPath(self.SETTINGS_PREFIX, key)) def name_from_key(self, key: str) -> str: - return str(Path(key).name).rstrip(self.FILE_SUFFIX) - - def get(self, key: str, - bucket_name: Optional[str] = None) -> Optional[Union[dict, str]]: - key = self.key_from_name(key) - obj = self._s3.get_file_stream(bucket_name or self.bucket_name, key) - if not obj: - return - content = gzip.decompress(obj.read()) - try: - return json.loads(content) - except json.JSONDecodeError: - return content.decode() - - def set(self, key: str, data: Union[dict, str], - bucket_name: Optional[str] = None) -> None: - data = json.dumps(data, separators=(',', ':')) if isinstance( - data, dict) else data - self._s3.put_object(bucket_name or self.bucket_name, - self.key_from_name(key), data) - - def ls(self, bucket_name: Optional[str] = None) -> list: + return str(PurePosixPath(key).name).rstrip(self.FILE_SUFFIX) + + def get(self, key: S3SettingKey | str, + bucket_name: str = None) -> dict: + if isinstance(key, S3SettingKey): + key = key.value + return self._s3.gz_get_json( + bucket=bucket_name or self.bucket_name, + key=self.key_from_name(key) + ) + + def set(self, key: S3SettingKey | str, data: dict, + bucket_name: str = None): + if isinstance(key, S3SettingKey): + key = key.value + self._s3.gz_put_json( + bucket=bucket_name or self.bucket_name, + key=self.key_from_name(key), + obj=data + ) + + def ls(self, bucket_name: str = None) -> list: keys = self._s3.list_dir(bucket_name or self.bucket_name, self.SETTINGS_PREFIX) return [self.name_from_key(key) for key in keys] - def rules_to_service_section(self) -> Optional[dict]: - return self.get(KEY_RULES_TO_SERVICE_SECTION) - - def rules_to_severity(self) -> Optional[dict]: - return self.get(KEY_RULES_TO_SEVERITY) - - def rules_to_standards(self) -> Optional[dict]: - return self.get(KEY_RULES_TO_STANDARDS) + def rules_to_service_section(self) -> dict: + return self.get(S3SettingKey.RULES_TO_SERVICE_SECTION) - def rules_to_mitre(self) -> Optional[dict]: - return self.get(KEY_RULES_TO_MITRE) + def rules_to_severity(self) -> dict: + return self.get(S3SettingKey.RULES_TO_SEVERITY) - def cloud_to_rules(self) -> Optional[dict]: - return self.get(KEY_CLOUD_TO_RULES) + def rules_to_standards(self) -> dict: + return self.get(S3SettingKey.RULES_TO_STANDARDS) - def human_data(self) -> Optional[dict]: - return self.get(KEY_HUMAN_DATA) + def rules_to_mitre(self) -> dict: + return self.get(S3SettingKey.RULES_TO_MITRE) - def rules_to_service(self) -> Optional[dict]: - return self.get(KEY_RULES_TO_SERVICE) + def cloud_to_rules(self) -> dict: + return self.get(S3SettingKey.CLOUD_TO_RULES) - def rules_to_category(self) -> Optional[dict]: - return self.get(KEY_RULES_TO_CATEGORY) + def human_data(self) -> dict: + return self.get(S3SettingKey.HUMAN_DATA) - def aws_standards_coverage(self) -> Optional[dict]: - return self.get(KEY_AWS_STANDARDS_COVERAGE) + def rules_to_service(self) -> dict: + return self.get(S3SettingKey.RULES_TO_SERVICE) - def azure_standards_coverage(self) -> Optional[dict]: - return self.get(KEY_AZURE_STANDARDS_COVERAGE) + def rules_to_category(self) -> dict: + return self.get(S3SettingKey.RULES_TO_CATEGORY) - def google_standards_coverage(self) -> Optional[dict]: - return self.get(KEY_GOOGLE_STANDARDS_COVERAGE) + def aws_standards_coverage(self) -> dict: + return self.get(S3SettingKey.AWS_STANDARDS_COVERAGE) - def aws_events(self) -> Optional[dict]: - return self.get(KEY_AWS_EVENTS) + def azure_standards_coverage(self) -> dict: + return self.get(S3SettingKey.AZURE_STANDARDS_COVERAGE) - def azure_events(self) -> Optional[dict]: - return self.get(KEY_AZURE_EVENTS) + def google_standards_coverage(self) -> dict: + return self.get(S3SettingKey.GOOGLE_STANDARDS_COVERAGE) - def google_events(self) -> Optional[dict]: - return self.get(KEY_GOOGLE_EVENTS) + def aws_events(self) -> dict: + return self.get(S3SettingKey.AWS_EVENTS) + def azure_events(self) -> dict: + return self.get(S3SettingKey.AZURE_EVENTS) -# class CachedS3SettingsService(S3SettingsService): -# def __init__(self, *args, **kwargs): -# super().__init__(*args, **kwargs) -# # not adjusted key to value -# self._cache = TTLCache(maxsize=30, ttl=900) -# -# def get(self, key: str, -# bucket_name: Optional[str] = None) -> Optional[Union[dict, str]]: -# if key in self._cache: -# return self._cache[key] -# value = super().get(key, bucket_name) -# if not value: -# return -# self._cache[key] = value -# return value -# -# def set(self, key: str, data: Union[dict, str], -# bucket_name: Optional[str] = None) -> None: -# super().set(key, data, bucket_name) -# self._cache[key] = data + def google_events(self) -> dict: + return self.get(S3SettingKey.GOOGLE_EVENTS) class S3SettingsServiceLocalWrapper: @@ -142,7 +103,7 @@ def __init__(self, s3_setting_service: S3SettingsService): def adjust_key(self, key: str) -> str: return self._s3_setting_service.name_from_key(key).lower() - def _import(self, key: str) -> Optional[Union[dict, str]]: + def _import(self, key: str) -> dict | None: try: module = importlib.import_module( f'helpers.mappings.{self.adjust_key(key)}') @@ -150,8 +111,8 @@ def _import(self, key: str) -> Optional[Union[dict, str]]: return return getattr(module, self.data_attr, None) - def get(self, key: str) -> Optional[Union[dict, str]]: - imported = self._import(key) + def get(self, key: S3SettingKey) -> dict: + imported = self._import(key.value) if imported: return imported return self._s3_setting_service.get(key) diff --git a/src/services/scheduler_service.py b/src/services/scheduler_service.py index 4862abe15..0ec214785 100644 --- a/src/services/scheduler_service.py +++ b/src/services/scheduler_service.py @@ -1,9 +1,9 @@ -from typing import Iterable, Set, Optional +from typing import Set, Optional, Iterator -from helpers.constants import NAME_ATTR, \ - ID_ATTR -from helpers.log_helper import get_logger from modular_sdk.models.tenant import Tenant + +from helpers.constants import NAME_ATTR, ID_ATTR +from helpers.log_helper import get_logger from models.scheduled_job import ScheduledJob from services.clients.scheduler import AbstractJobScheduler @@ -28,7 +28,7 @@ def get(name: str, customer: Optional[str] = None, return item def list(self, name: Optional[str] = None, customer: Optional[str] = None, - tenants: Optional[Set[str]] = None) -> Iterable[ScheduledJob]: + tenants: Optional[Set[str]] = None) -> Iterator[ScheduledJob]: tenants = tenants or set() if name: _LOG.info('Scheduled job name is given querying by it') diff --git a/src/services/service_provider.py b/src/services/service_provider.py index 6f98a75f4..3107c4040 100644 --- a/src/services/service_provider.py +++ b/src/services/service_provider.py @@ -1,528 +1,363 @@ +from functools import cached_property +from typing import Union, TYPE_CHECKING + from helpers import SingletonMeta, get_logger +if TYPE_CHECKING: + from services.clients.mongo_ssm_auth_client import MongoAndSSMAuthClient + from scheduler.ap_job_scheduler import APJobScheduler + from services.ambiguous_job_service import AmbiguousJobService + from services.assemble_service import AssembleService + from services.batch_results_service import BatchResultsService + from services.clients.cognito import CognitoClient + from services.clients.event_bridge import EventBridgeClient + from services.clients.iam import IAMClient + from services.clients.modular import ModularClient + from services.clients.s3 import ModularAssumeRoleS3Service + from services.clients.s3 import S3Client + from services.clients.scheduler import EventBridgeJobScheduler + from services.clients.ssm import AbstractSSMClient + from services.clients.sts import StsClient + from services.coverage_service import CoverageService + from services.environment_service import EnvironmentService + from services.event_processor_service import EventProcessorService + from services.event_service import EventService + from services.job_service import JobService + from services.job_statistics_service import JobStatisticsService + from services.license_manager_service import LicenseManagerService + from services.license_service import LicenseService + from services.metrics_service import CustomerMetricsService + from services.metrics_service import MetricsService + from services.metrics_service import TenantMetricsService + from services.rabbitmq_service import RabbitMQService + from services.report_service import ReportService + from services.mappings_collector import LazyLoadedMappingsCollector + from services.rule_meta_service import RuleService + from services.rule_source_service import RuleSourceService + from services.ruleset_service import RulesetService + from services.s3_settings_service import S3SettingsService + from services.scheduler_service import SchedulerService + from services.setting_service import CachedSettingsService + from services.ssm_service import SSMService + from services.platform_service import PlatformService + from services.clients.batch import BatchClient, SubprocessBatchClient + from services.integration_service import IntegrationService + from services.defect_dojo_service import DefectDojoService + from services.clients.cognito import BaseAuthClient + from services.rbac_service import RoleService, PolicyService + from services.clients.step_function import ScriptClient, StepFunctionClient + + _LOG = get_logger(__name__) class ServiceProvider(metaclass=SingletonMeta): - # clients - __kms_conn = None - __s3_conn = None - __batch_conn = None - __ssm_conn = None - __cognito_conn = None - __ecr_conn = None - __lambda_conn = None - __sts_conn = None - __modular_conn = None - __standalone_key_management = None - __event_bridge_client = None - __iam_client = None - __event_bridge_job_scheduler = None - __ap_job_scheduler = None - __smtp_conn = None - __assume_role_s3 = None - - # services - __job_service = None - __user_service = None - __rule_service = None - __rule_meta_service = None - __environment_service = None - __modular_service = None - __ssm_service = None - __settings_service = None - __mappings_collector = None - __s3_settings_service = None - __s3_settings_service_local_wrapper = None - __access_control_service = None - __iam_cache_service = None - __credential_manager_service = None - __event_processor_service = None - __ruleset_service = None - __rule_source_service = None - __license_service = None - __license_manager_service = None - __findings_service = None - __coverage_service = None - __token_service = None - __scheduler_service = None - __restriction_service = None - __notification_service = None - __key_management_service = None - __event_service = None - __assemble_service = None - __batch_results_service = None - __ambiguous_job_service = None - __report_service = None - __rule_report_service = None - __metrics_service = None - __tenant_metrics_service = None - __customer_metrics_service = None - __rabbitmq_service = None - __job_statistics_service = None - def __str__(self): return id(self) - # clients - def kms(self): - if not self.__kms_conn: - from services.clients.kms import KMSClient - self.__kms_conn = KMSClient( - region=self.environment_service().aws_region()) - return self.__kms_conn - - def s3(self): - # ModularAssumeRoleSSMService is a temporary solution - if not self.__s3_conn: - from services.clients.s3 import S3Client - self.__s3_conn = S3Client( - region=self.environment_service().aws_region()) - return self.__s3_conn - - def batch(self): - if not self.__batch_conn: - from connections.batch_extension.base_job_client import \ - BaseBatchClient - self.__batch_conn = BaseBatchClient( - environment_service=self.environment_service(), - sts_client=self.sts_client()) - return self.__batch_conn - - def ssm(self): - if not self.__ssm_conn: - from services.clients.ssm import SSMClient, VaultSSMClient - _env = self.environment_service() - if _env.is_docker(): - self.__ssm_conn = VaultSSMClient(environment_service=_env) - else: - self.__ssm_conn = SSMClient(environment_service=_env) - return self.__ssm_conn - - def cognito(self): - if not self.__cognito_conn: - if self.environment_service().is_docker(): - from connections.auth_extension.cognito_to_jwt_adapter \ - import MongoAndSSMAuthClient - self.__cognito_conn = MongoAndSSMAuthClient( - ssm_service=self.ssm_service() - ) - else: - from services.clients.cognito import CognitoClient - self.__cognito_conn = CognitoClient( - environment_service=self.environment_service()) - return self.__cognito_conn - - def ecr(self): - if not self.__ecr_conn: - from services.clients.ecr import ECRClient - self.__ecr_conn = ECRClient( - region=self.environment_service().aws_region()) - return self.__ecr_conn - - def lambda_func(self): - if not self.__lambda_conn: - from services.clients.lambda_func import LambdaClient - self.__lambda_conn = LambdaClient( - environment_service=self.environment_service()) - return self.__lambda_conn - - def modular_client(self): - if not self.__modular_conn: - from services.clients.modular import ModularClient - self.__modular_conn = ModularClient() - return self.__modular_conn - - def standalone_key_management(self): - if not self.__standalone_key_management: - from services.clients.standalone_key_management import \ - StandaloneKeyManagementClient - self.__standalone_key_management = \ - StandaloneKeyManagementClient(ssm_client=self.ssm()) - return self.__standalone_key_management - - def events(self): - if not self.__event_bridge_client: - from services.clients.event_bridge import EventBridgeClient - self.__event_bridge_client = EventBridgeClient( - environment_service=self.environment_service(), - sts_client=self.sts_client()) - return self.__event_bridge_client - - def iam(self): - if not self.__iam_client: - from services.clients.iam import IAMClient - self.__iam_client = IAMClient( - sts_client=self.sts_client() - ) - return self.__iam_client - - def event_bridge_job_scheduler(self): - if not self.__event_bridge_job_scheduler: - from services.clients.scheduler import EventBridgeJobScheduler - self.__event_bridge_job_scheduler = EventBridgeJobScheduler( - client=self.events(), - environment_service=self.environment_service(), - iam_client=self.iam(), - batch_client=self.batch() - ) - return self.__event_bridge_job_scheduler - - def ap_job_scheduler(self): - if not self.__ap_job_scheduler: - from scheduler.ap_job_scheduler import APJobScheduler - self.__ap_job_scheduler = APJobScheduler() - return self.__ap_job_scheduler - - def smtp_client(self): - if not self.__smtp_conn: - from services.clients.smtp import SMTPClient - self.__smtp_conn = SMTPClient() - return self.__smtp_conn - - def assume_role_s3(self): - if not self.__assume_role_s3: - from services.clients.s3 import ModularAssumeRoleS3Service - self.__assume_role_s3 = ModularAssumeRoleS3Service( - region=self.environment_service().aws_region()) - return self.__assume_role_s3 - - # services - - def job_service(self): - if not self.__job_service: - from services.job_service import JobService - self.__job_service = JobService( - restriction_service=self.restriction_service() - ) - return self.__job_service - - def user_service(self): - if not self.__user_service: - from services.user_service import CognitoUserService - self.__user_service = CognitoUserService( - client=self.cognito()) - return self.__user_service - - def rule_service(self): - if not self.__rule_service: - from services.rule_meta_service import RuleService - self.__rule_service = RuleService( - mappings_collector=self.mappings_collector() - ) - return self.__rule_service - - def rule_meta_service(self): - if not self.__rule_meta_service: - from services.rule_meta_service import RuleMetaService - self.__rule_meta_service = RuleMetaService() - return self.__rule_meta_service - - def environment_service(self): - if not self.__environment_service: - from services.environment_service import EnvironmentService - self.__environment_service = EnvironmentService() - return self.__environment_service - - # def azure_subscriptions_service(self): - # from services.azure_subscriptions_service import \ - # AzureSubscriptionsService - # if not self.__azure_subscriptions_service: - # self.__azure_subscriptions_service = \ - # AzureSubscriptionsService() - # return self.__azure_subscriptions_service - - def modular_service(self): - if not self.__modular_service: - from services.modular_service import ModularService - self.__modular_service = ModularService( - client=self.modular_client()) - return self.__modular_service - - def credential_manager_service(self): - if not self.__credential_manager_service: - from services.credentials_manager_service import \ - CredentialsManagerService - self.__credential_manager_service = CredentialsManagerService() - return self.__credential_manager_service - - def ssm_service(self): - if not self.__ssm_service: - from services.ssm_service import SSMService - self.__ssm_service = SSMService(client=self.ssm()) - return self.__ssm_service - - def settings_service(self): - if not self.__settings_service: - from services.setting_service import CachedSettingsService - self.__settings_service = CachedSettingsService( - environment_service=self.environment_service() - ) - return self.__settings_service - - def mappings_collector(self): - if not self.__mappings_collector: - from services.rule_meta_service import \ - LazyLoadedMappingsCollector, MappingsCollector - self.__mappings_collector = LazyLoadedMappingsCollector( - collector=MappingsCollector(), - settings_service=self.settings_service(), - s3_settings_service=self.s3_settings_service(), - abort_if_not_found=True - ) - return self.__mappings_collector - - def s3_settings_service(self): - if not self.__s3_settings_service: - from services.s3_settings_service import S3SettingsService - self.__s3_settings_service = S3SettingsService( - s3_client=self.s3(), - environment_service=self.environment_service() - ) - return self.__s3_settings_service - - def s3_setting_service_local_wrapper(self): - if not self.__s3_settings_service_local_wrapper: - from services.s3_settings_service import \ - S3SettingsServiceLocalWrapper - self.__s3_settings_service_local_wrapper = S3SettingsServiceLocalWrapper( - s3_setting_service=self.s3_settings_service() - ) - return self.__s3_settings_service_local_wrapper - - def access_control_service(self): - if not self.__access_control_service: - from services.rbac.access_control_service import \ - AccessControlService - self.__access_control_service = AccessControlService( - iam_service=self.iam_cache_service() - ) - return self.__access_control_service - - def iam_cache_service(self): - if not self.__iam_cache_service: - from services.rbac.iam_cache_service import CachedIamService - self.__iam_cache_service = CachedIamService() - return self.__iam_cache_service - - def event_processor_service(self): - if not self.__event_processor_service: - from services.event_processor_service import \ - EventProcessorService - self.__event_processor_service = EventProcessorService( - s3_settings_service=self.s3_settings_service(), - environment_service=self.environment_service(), - sts_client=self.sts_client(), - mappings_collector=self.mappings_collector() - ) - return self.__event_processor_service - - def sts_client(self): - if not self.__sts_conn: - from services.clients.sts import StsClient - self.__sts_conn = StsClient( - environment_service=self.environment_service()) - return self.__sts_conn - - def ruleset_service(self): - if not self.__ruleset_service: - from services.ruleset_service import RulesetService - self.__ruleset_service = RulesetService( - license_service=self.license_service(), - restriction_service=self.restriction_service(), - s3_client=self.s3() - ) - return self.__ruleset_service - - def rule_source_service(self): - if not self.__rule_source_service: - from services.rule_source_service import RuleSourceService - self.__rule_source_service = RuleSourceService( - ssm_service=self.ssm_service(), - restriction_service=self.restriction_service() - ) - return self.__rule_source_service - - def license_service(self): - if not self.__license_service: - from services.license_service import LicenseService - self.__license_service = LicenseService( - settings_service=self.settings_service() - ) - return self.__license_service - - def license_manager_service(self): - if not self.__license_manager_service: - from services.license_manager_service import \ - LicenseManagerService - self.__license_manager_service = LicenseManagerService( - settings_service=self.settings_service(), - token_service=self.token_service() - ) - return self.__license_manager_service - - def findings_service(self): - if not self.__findings_service: - from services.findings_service import FindingsService - self.__findings_service = FindingsService( - environment_service=self.environment_service(), - s3_client=self.s3() - ) - return self.__findings_service - - def coverage_service(self): - if not self.__coverage_service: - from services.coverage_service import CoverageService - self.__coverage_service = CoverageService( - mappings_collector=self.mappings_collector() - ) - return self.__coverage_service - - def token_service(self): - if not self.__token_service: - from services.token_service import TokenService - self.__token_service = TokenService( - client=self.standalone_key_management() - ) - return self.__token_service - - def scheduler_service(self): - if not self.__scheduler_service: - from services.scheduler_service import SchedulerService - client = self.event_bridge_job_scheduler() \ - if not self.environment_service().is_docker() \ - else self.ap_job_scheduler() - self.__scheduler_service = SchedulerService( - client=client - ) - return self.__scheduler_service - - def restriction_service(self): - if not self.__restriction_service: - from services.rbac.restriction_service import \ - RestrictionService - self.__restriction_service = RestrictionService( - modular_service=self.modular_service() - ) - return self.__restriction_service - - def notification_service(self): - if not self.__notification_service: - from services.notification_service import NotificationService - self.__notification_service = NotificationService( - setting_service=self.settings_service(), - s3_service=self.s3(), - ssm_service=self.ssm_service() - ) - return self.__notification_service - - def key_management_service(self): - if not self.__key_management_service: - from services.key_management_service import \ - KeyManagementService - self.__key_management_service = KeyManagementService( - key_management_client=self.standalone_key_management() - ) - return self.__key_management_service - - def event_service(self): - if not self.__event_service: - from services.event_service import EventService - self.__event_service = EventService( - environment_service=self.environment_service() + @cached_property + def environment_service(self) -> 'EnvironmentService': + from services.environment_service import EnvironmentService + return EnvironmentService() + + @cached_property + def s3(self) -> 'S3Client': + from services.clients.s3 import S3Client + env = self.environment_service + if env.is_docker(): + return S3Client.factory().build_minio() + return S3Client.factory().build_s3(env.aws_region()) + + @cached_property + def ssm(self) -> 'AbstractSSMClient': + from services.clients.ssm import VaultSSMClient, SSMClient + env = self.environment_service + if env.is_docker(): + return VaultSSMClient(environment_service=env) + return SSMClient(environment_service=env) + + @cached_property + def sts(self) -> 'StsClient': + from services.clients.sts import StsClient + if self.environment_service.is_docker(): + return StsClient.build() + return StsClient.factory().build( + region_name=self.environment_service.aws_region() + ) + + @cached_property + def batch(self) -> Union['BatchClient', 'SubprocessBatchClient']: + if self.environment_service.is_docker(): + from services.clients.batch import SubprocessBatchClient + return SubprocessBatchClient.build() + from services.clients.batch import BatchClient + return BatchClient.factory().build( + region_name=self.environment_service.aws_region() + ) + + @cached_property + def onprem_users_client(self) -> 'MongoAndSSMAuthClient': + from services.clients.mongo_ssm_auth_client import MongoAndSSMAuthClient + return MongoAndSSMAuthClient(ssm_client=self.ssm) + + @cached_property + def saas_users_client(self) -> 'CognitoClient': + from services.clients.cognito import CognitoClient + return CognitoClient(environment_service=self.environment_service) + + @cached_property + def users_client(self) -> 'BaseAuthClient': + if self.environment_service.is_docker(): + return self.onprem_users_client + return self.saas_users_client + + @cached_property + def ssm_service(self) -> 'SSMService': + from services.ssm_service import SSMService + return SSMService(client=self.ssm) + + @cached_property + def lambda_client(self): + from services.clients.lambda_func import LambdaClient + return LambdaClient(environment_service=self.environment_service) + + @cached_property + def modular_client(self) -> 'ModularClient': + from services.clients.modular import ModularClient + return ModularClient() + + @cached_property + def events(self) -> 'EventBridgeClient': + from services.clients.event_bridge import EventBridgeClient + return EventBridgeClient.factory().build( + region_name=self.environment_service.aws_region() + ) + + @cached_property + def iam(self) -> 'IAMClient': + from services.clients.iam import IAMClient + return IAMClient(sts_client=self.sts) + + @cached_property + def event_bridge_job_scheduler(self) -> 'EventBridgeJobScheduler': + from services.clients.scheduler import EventBridgeJobScheduler + return EventBridgeJobScheduler( + client=self.events, + environment_service=self.environment_service, + iam_client=self.iam, + batch_client=self.batch + ) + + @cached_property + def ap_job_scheduler(self) -> 'APJobScheduler': + from scheduler.ap_job_scheduler import APJobScheduler + return APJobScheduler( + batch_client=self.batch, + setting_service=self.settings_service, + ) + + @cached_property + def assume_role_s3(self) -> 'ModularAssumeRoleS3Service': + from services.clients.s3 import ModularAssumeRoleS3Service + return ModularAssumeRoleS3Service() + + @cached_property + def job_service(self) -> 'JobService': + from services.job_service import JobService + return JobService() + + @cached_property + def rule_service(self) -> 'RuleService': + from services.rule_meta_service import RuleService + return RuleService() + + @cached_property + def mappings_collector(self) -> 'LazyLoadedMappingsCollector': + from services.mappings_collector import LazyLoadedMappingsCollector + return LazyLoadedMappingsCollector.build() + + @cached_property + def settings_service(self) -> 'CachedSettingsService': + from services.setting_service import CachedSettingsService + return CachedSettingsService( + environment_service=self.environment_service + ) + + @cached_property + def s3_settings_service(self) -> 'S3SettingsService': + from services.s3_settings_service import S3SettingsService + return S3SettingsService( + s3_client=self.s3, + environment_service=self.environment_service + ) + + @cached_property + def role_service(self) -> 'RoleService': + from services.rbac_service import RoleService + return RoleService() + + @cached_property + def policy_service(self) -> 'PolicyService': + from services.rbac_service import PolicyService + return PolicyService() + + @cached_property + def event_processor_service(self) -> 'EventProcessorService': + from services.event_processor_service import EventProcessorService + return EventProcessorService( + s3_settings_service=self.s3_settings_service, + environment_service=self.environment_service, + sts_client=self.sts, + mappings_collector=self.mappings_collector + ) + + @cached_property + def ruleset_service(self) -> 'RulesetService': + from services.ruleset_service import RulesetService + return RulesetService( + license_service=self.license_service, + s3_client=self.s3 + ) + + @cached_property + def rule_source_service(self) -> 'RuleSourceService': + from services.rule_source_service import RuleSourceService + return RuleSourceService( + ssm_service=self.ssm_service, + ) + + @cached_property + def license_service(self) -> 'LicenseService': + from services.license_service import LicenseService + return LicenseService( + application_service=self.modular_client.application_service(), + parent_service=self.modular_client.parent_service(), + customer_service=self.modular_client.customer_service() + ) + + @cached_property + def license_manager_service(self) -> 'LicenseManagerService': + from services.license_manager_service import LicenseManagerService + return LicenseManagerService( + settings_service=self.settings_service, + environment_service=self.environment_service, + ssm_service=self.ssm_service + ) + + @cached_property + def coverage_service(self) -> 'CoverageService': + from services.coverage_service import CoverageService + return CoverageService( + mappings_collector=self.mappings_collector + ) + + @cached_property + def scheduler_service(self) -> 'SchedulerService': + from services.scheduler_service import SchedulerService + client = self.event_bridge_job_scheduler \ + if not self.environment_service.is_docker() \ + else self.ap_job_scheduler + return SchedulerService(client=client) + + @cached_property + def event_service(self) -> 'EventService': + from services.event_service import EventService + return EventService(environment_service=self.environment_service) + + @cached_property + def assemble_service(self) -> 'AssembleService': + from services.assemble_service import AssembleService + return AssembleService(environment_service=self.environment_service) + + @cached_property + def batch_results_service(self) -> 'BatchResultsService': + from services.batch_results_service import BatchResultsService + return BatchResultsService() + + @cached_property + def ambiguous_job_service(self) -> 'AmbiguousJobService': + from services.ambiguous_job_service import AmbiguousJobService + return AmbiguousJobService( + batch_results_service=self.batch_results_service, + job_service=self.job_service + ) + + @cached_property + def report_service(self) -> 'ReportService': + from services.report_service import ReportService + return ReportService( + s3_client=self.s3, + environment_service=self.environment_service, + mappings_collector=self.mappings_collector + ) + + @cached_property + def metrics_service(self) -> 'MetricsService': + from services.metrics_service import MetricsService + return MetricsService(mappings_collector=self.mappings_collector) + + @cached_property + def tenant_metrics_service(self) -> 'TenantMetricsService': + from services.metrics_service import TenantMetricsService + return TenantMetricsService( + mappings_collector=self.mappings_collector + ) + + @cached_property + def customer_metrics_service(self) -> 'CustomerMetricsService': + from services.metrics_service import CustomerMetricsService + return CustomerMetricsService( + mappings_collector=self.mappings_collector + ) + + @cached_property + def rabbitmq_service(self) -> 'RabbitMQService': + from services.rabbitmq_service import RabbitMQService + return RabbitMQService( + modular_client=self.modular_client, + environment_service=self.environment_service + ) + + @cached_property + def job_statistics_service(self) -> 'JobStatisticsService': + from services.job_statistics_service import JobStatisticsService + return JobStatisticsService() + + @cached_property + def report_statistics_service(self): + from services.report_statistics_service import ReportStatisticsService + return ReportStatisticsService( + setting_service=self.settings_service + ) + + @cached_property + def platform_service(self) -> 'PlatformService': + from services.platform_service import PlatformService + return PlatformService() + + @cached_property + def integration_service(self) -> 'IntegrationService': + from services.integration_service import IntegrationService + return IntegrationService( + parent_service=self.modular_client.parent_service(), + defect_dojo_service=self.defect_dojo_service + ) + + @cached_property + def step_function(self) -> Union['ScriptClient', 'StepFunctionClient']: + from services.clients.step_function import ScriptClient, \ + StepFunctionClient + if self.environment_service.is_docker(): + return ScriptClient(environment_service=self.environment_service) + else: + return StepFunctionClient( + environment_service=self.environment_service ) - return self.__event_service - def assemble_service(self): - if not self.__assemble_service: - from services.assemble_service import AssembleService - self.__assemble_service = AssembleService( - environment_service=self.environment_service(), - ) - return self.__assemble_service - - def batch_results_service(self): - if not self.__batch_results_service: - from services.batch_results_service import BatchResultsService - self.__batch_results_service = BatchResultsService() - return self.__batch_results_service - - def ambiguous_job_service(self): - if not self.__ambiguous_job_service: - from services.ambiguous_job_service import AmbiguousJobService - self.__ambiguous_job_service = AmbiguousJobService( - batch_results_service=self.batch_results_service(), - job_service=self.job_service() - ) - return self.__ambiguous_job_service - - def report_service(self): - if not self.__report_service: - from services.report_service import ReportService - self.__report_service = ReportService( - s3_client=self.s3(), - environment_service=self.environment_service(), - mappings_collector=self.mappings_collector() - ) - return self.__report_service - - def rule_report_service(self): - if not self.__rule_report_service: - from services.rule_report_service import RuleReportService - self.__rule_report_service = RuleReportService( - ambiguous_job_service=self.ambiguous_job_service(), - modular_service=self.modular_service(), - report_service=self.report_service() - ) - return self.__rule_report_service - - def metrics_service(self): - if not self.__metrics_service: - from services.metrics_service import MetricsService - self.__metrics_service = MetricsService( - mappings_collector=self.__mappings_collector) - return self.__metrics_service - - def tenant_metrics_service(self): - if not self.__tenant_metrics_service: - from services.metrics_service import TenantMetricsService - self.__tenant_metrics_service = TenantMetricsService( - mappings_collector=self.__mappings_collector - ) - return self.__tenant_metrics_service - - def customer_metrics_service(self): - if not self.__customer_metrics_service: - from services.metrics_service import CustomerMetricsService - self.__customer_metrics_service = CustomerMetricsService( - mappings_collector=self.__mappings_collector - ) - return self.__customer_metrics_service - - def rabbitmq_service(self): - if not self.__rabbitmq_service: - from services.rabbitmq_service import RabbitMQService - self.__rabbitmq_service = RabbitMQService( - modular_service=self.modular_service() - ) - return self.__rabbitmq_service - - def job_statistics_service(self): - if not self.__job_statistics_service: - from services.job_statistics_service import JobStatisticsService - self.__job_statistics_service = JobStatisticsService() - return self.__job_statistics_service - - def reset(self, service: str): - """ - Removes the saved instance of the service. It is useful, - for example, in case of gitlab service - when we want to use - different rule-sources configurations - """ - private_service_name = f'_ServiceProvider__{service}' - if not hasattr(self, private_service_name): - raise AssertionError( - f'In case you are using this method, make sure your ' - f'service {private_service_name} exists amongst the ' - f'private attributes') - setattr(self, private_service_name, None) + @cached_property + def defect_dojo_service(self) -> 'DefectDojoService': + from services.defect_dojo_service import DefectDojoService + return DefectDojoService( + application_service=self.modular_client.application_service(), + ssm_service=self.modular_client.assume_role_ssm_service() + ) diff --git a/src/services/setting_service.py b/src/services/setting_service.py index 522d8be05..cae2c2555 100644 --- a/src/services/setting_service.py +++ b/src/services/setting_service.py @@ -3,47 +3,19 @@ from pynamodb.exceptions import PynamoDBException import services.cache as cache -from helpers.constants import DEFAULT_SYSTEM_CUSTOMER, \ - DEFAULT_METRICS_BUCKET_NAME, \ - DEFAULT_TEMPLATES_BUCKET_NAME, DEFAULT_STATISTICS_BUCKET_NAME, \ - DEFAULT_RULESETS_BUCKET_NAME, DEFAULT_REPORTS_BUCKET_NAME, \ - DEFAULT_SSM_BACKUP_BUCKET_NAME, KEY_AWS_EVENTS, KEY_GOOGLE_EVENTS, \ - KEY_AZURE_EVENTS, KEY_GOOGLE_STANDARDS_COVERAGE, KEY_RULES_TO_STANDARDS, \ - KEY_RULES_TO_SERVICE_SECTION, KEY_RULES_TO_MITRE, KEY_CLOUD_TO_RULES, \ - KEY_RULES_TO_SEVERITY, KEY_AZURE_STANDARDS_COVERAGE, \ - KEY_AWS_STANDARDS_COVERAGE +from helpers.constants import (DEFAULT_SYSTEM_CUSTOMER, SettingKey, + DEFAULT_RULES_METADATA_REPO_ACCESS_SSM_NAME) from helpers.log_helper import get_logger from models.setting import Setting from services.environment_service import EnvironmentService -KEY_SYSTEM_CUSTOMER = 'SYSTEM_CUSTOMER_NAME' -KEY_BACKUP_REPO_CONFIGURATION = 'BACKUP_REPO_ACCESS_INFO' -KEY_STATS_S3_BUCKET_NAME = 'STATS_S3_BUCKET_NAME' -KEY_TEMPLATE_BUCKET = 'TEMPLATES_S3_BUCKET_NAME' -KEY_CURRENT_CUSTODIAN_CUSTOM_CORE_VERSION = 'CURRENT_CCC_VERSION' -KEY_ACCESS_DATA_LM = 'ACCESS_DATA_LM' -KEY_MAIL_CONFIGURATION = 'MAIL_CONFIGURATION' -KEY_CONTACTS = 'CONTACTS' -KEY_LM_CLIENT_KEY = 'LM_CLIENT_KEY' -KEY_EVENT_ASSEMBLER = 'EVENT_ASSEMBLER' -KEY_REPORT_DATE_MARKER = 'REPORT_DATE_MARKER' -KEY_RULES_METADATA_REPO_ACCESS_SSM_NAME = 'RULES_METADATA_REPO_ACCESS_SSM_NAME' -DEFAULT_RULES_METADATA_REPO_ACCESS_SSM_NAME = \ - 'custodian.rules-metadata-repo-access' - -KEY_BUCKET_NAMES = 'BUCKET_NAMES' -RULESETS_BUCKET = 'rulesets' -REPORTS_BUCKET = 'reports' -SSM_BACKUP_BUCKET = 'ssm-backup' -STATISTICS_BUCKET = 'statistics' -TEMPLATES_BUCKET = 'templates' -METRICS_BUCKET = 'metrics' EVENT_CURSOR_TIMESTAMP_ATTR = 'ect' _LOG = get_logger(__name__) +# TODO do not retrieve items from db before updating class SettingsService: def __init__(self, environment_service: EnvironmentService): self._environment = environment_service @@ -52,35 +24,40 @@ def __init__(self, environment_service: EnvironmentService): def get_all_settings(): return Setting.scan() - def get_backup_repo_settings(self): - return self.get(name=KEY_BACKUP_REPO_CONFIGURATION) - @staticmethod - def create(name, value) -> Setting: + def create(name: SettingKey | str, + value: float | str | list | dict) -> Setting: + if isinstance(name, SettingKey): + name = name.value return Setting(name=name, value=value) - def get(self, name, value: bool = True) -> Optional[Union[Setting, dict]]: + def get(self, name: SettingKey | str, value: bool = True, + consistent_read: bool = False) -> Setting | dict | None: + if isinstance(name, SettingKey): + name = name.value _LOG.debug(f'Querying {name} setting') - setting = Setting.get_nullable(hash_key=name) + setting = Setting.get_nullable(hash_key=name, + consistent_read=consistent_read) if setting and value: return setting.value elif setting: return setting - def delete(self, setting: Union[Setting, str]) -> bool: - name = setting if isinstance(setting, str) else setting.name + def delete(self, setting: Union[Setting, str, SettingKey]) -> bool: + if isinstance(setting, Setting): + name = setting.name + elif isinstance(setting, SettingKey): + name = setting.value + else: + name = setting Setting(name=name).delete() return True def save(self, setting: Setting): return setting.save() - def get_current_ccc_version(self): - return self.get( - name=KEY_CURRENT_CUSTODIAN_CUSTOM_CORE_VERSION) - def get_license_manager_access_data(self, value: bool = True): - return self.get(name=KEY_ACCESS_DATA_LM, value=value) + return self.get(name=SettingKey.ACCESS_DATA_LM, value=value) def create_license_manager_access_data_configuration( self, host: str, @@ -93,11 +70,11 @@ def create_license_manager_access_data_configuration( model.update_host(host=host, port=port, protocol=protocol, stage=stage) model.api_version = api_version return self.create( - name=KEY_ACCESS_DATA_LM, value=model.dict() + name=SettingKey.ACCESS_DATA_LM.value, value=model.dict() ) def get_license_manager_client_key_data(self, value: bool = True): - return self.get(name=KEY_LM_CLIENT_KEY, value=value) + return self.get(name=SettingKey.LM_CLIENT_KEY, value=value) def create_license_manager_client_key_data(self, kid: str, alg: str ) -> Setting: @@ -110,7 +87,7 @@ def create_license_manager_client_key_data(self, kid: str, alg: str Ergo, kid is used to derive reference to the persisted data. """ return self.create( - name=KEY_LM_CLIENT_KEY, value=dict( + name=SettingKey.LM_CLIENT_KEY, value=dict( kid=kid, alg=alg ) @@ -122,7 +99,7 @@ def create_mail_configuration( max_emails: Optional[int] = None ): return self.create( - name=KEY_MAIL_CONFIGURATION, value=dict( + name=SettingKey.MAIL_CONFIGURATION, value=dict( username=username, password=password_alias, default_sender=default_sender, @@ -135,7 +112,7 @@ def create_mail_configuration( def get_mail_configuration(self, value: bool = True ) -> Optional[Union[Setting, dict]]: return self.get( - name=KEY_MAIL_CONFIGURATION, value=value + name=SettingKey.MAIL_CONFIGURATION, value=value ) def get_system_customer_name(self) -> str: @@ -149,90 +126,43 @@ def get_system_customer_name(self) -> str: _LOG.info('Querying CaaSSettings in order to get SYSTEM Customer name') name: Optional[str] = None try: - name = self.get(KEY_SYSTEM_CUSTOMER) + name = self.get(SettingKey.SYSTEM_CUSTOMER) except PynamoDBException as e: - _LOG.warning(f'Could not query {KEY_SYSTEM_CUSTOMER} setting: {e}.' + _LOG.warning(f'Could not query {SettingKey.SYSTEM_CUSTOMER} ' + f'setting: {e}.' f' Using the default SYSTEM customer name') except Exception as e: _LOG.warning(f'Unexpected error occurred trying querying ' - f'{KEY_SYSTEM_CUSTOMER} setting: {e}. Using the ' + f'{SettingKey.SYSTEM_CUSTOMER} setting: {e}. Using the ' f'default SYSTEM customer name') return name or DEFAULT_SYSTEM_CUSTOMER def create_event_assembler_configuration(self, cursor: float) -> Setting: return self.create( - name=KEY_EVENT_ASSEMBLER, value={ + name=SettingKey.EVENT_ASSEMBLER, value={ EVENT_CURSOR_TIMESTAMP_ATTR: cursor } ) def get_event_assembler_configuration(self, value: bool = True ) -> Optional[Union[Setting, dict]]: - return self.get(name=KEY_EVENT_ASSEMBLER, value=value) - - def get_template_bucket(self): - bucket = self.get(name=KEY_TEMPLATE_BUCKET) - return bucket if bucket else '' - - def get_custodian_contacts(self): - contacts = self.get(name=KEY_CONTACTS) - return contacts if contacts else '' + return self.get(name=SettingKey.EVENT_ASSEMBLER, value=value) def get_report_date_marker(self) -> dict: - marker = self.get(name=KEY_REPORT_DATE_MARKER) + marker = self.get(name=SettingKey.REPORT_DATE_MARKER) return marker or {} def set_report_date_marker(self, current_week_date: str = None, last_week_date: str = None): - marker = self.get(name=KEY_REPORT_DATE_MARKER) + marker = self.get(name=SettingKey.REPORT_DATE_MARKER) if current_week_date: marker.update({'current_week_date': current_week_date}) if last_week_date: marker.update({'last_week_date': last_week_date}) - new_marker = self.create(name=KEY_REPORT_DATE_MARKER, value=marker) + new_marker = self.create(name=SettingKey.REPORT_DATE_MARKER, + value=marker) new_marker.save() - def get_bucket_names(self) -> dict: - """ - Such a dict is returned. Default values is used in case the - values are not set - { - "rulesets": "", - "reports": "", - "ssm-backup": "", - "statistics": "", - "templates": "", - "metrics": "" - } - :return: - """ - value = self.get(KEY_BUCKET_NAMES) or {} - value.setdefault(RULESETS_BUCKET, DEFAULT_RULESETS_BUCKET_NAME) - value.setdefault(REPORTS_BUCKET, DEFAULT_REPORTS_BUCKET_NAME) - value.setdefault(STATISTICS_BUCKET, DEFAULT_STATISTICS_BUCKET_NAME) - value.setdefault(TEMPLATES_BUCKET, DEFAULT_TEMPLATES_BUCKET_NAME) - value.setdefault(METRICS_BUCKET, DEFAULT_METRICS_BUCKET_NAME) - value.setdefault(SSM_BACKUP_BUCKET, DEFAULT_SSM_BACKUP_BUCKET_NAME) - return value - - def rulesets_bucket(self) -> str: - return self.get_bucket_names().get(RULESETS_BUCKET) - - def reports_bucket(self) -> str: - return self.get_bucket_names().get(REPORTS_BUCKET) - - def statistics_bucket(self) -> str: - return self.get_bucket_names().get(REPORTS_BUCKET) - - def templates_bucket(self) -> str: - return self.get_bucket_names().get(TEMPLATES_BUCKET) - - def metrics_bucket(self) -> str: - return self.get_bucket_names().get(METRICS_BUCKET) - - def ssm_backup_bucket(self) -> str: - return self.get_bucket_names().get(SSM_BACKUP_BUCKET) - # metadata def rules_metadata_repo_access_data(self) -> str: """ @@ -240,41 +170,55 @@ def rules_metadata_repo_access_data(self) -> str: repository with rules metadata :return: """ - return self.get(KEY_RULES_METADATA_REPO_ACCESS_SSM_NAME) or \ + return self.get(SettingKey.RULES_METADATA_REPO_ACCESS_SSM_NAME) or \ DEFAULT_RULES_METADATA_REPO_ACCESS_SSM_NAME - def rules_to_service_section(self) -> Optional[str]: - return self.get(KEY_RULES_TO_SERVICE_SECTION) - - def rules_to_severity(self) -> Optional[str]: - return self.get(KEY_RULES_TO_SEVERITY) - - def rules_to_standards(self) -> Optional[str]: - return self.get(KEY_RULES_TO_STANDARDS) - - def rules_to_mitre(self) -> Optional[str]: - return self.get(KEY_RULES_TO_MITRE) - - def cloud_to_rules(self) -> Optional[str]: - return self.get(KEY_CLOUD_TO_RULES) - def aws_standards_coverage(self) -> Optional[Setting]: - return self.get(KEY_AWS_STANDARDS_COVERAGE, value=False) + return self.get(SettingKey.AWS_STANDARDS_COVERAGE, value=False) def azure_standards_coverage(self) -> Optional[Setting]: - return self.get(KEY_AZURE_STANDARDS_COVERAGE, value=False) + return self.get(SettingKey.AZURE_STANDARDS_COVERAGE, value=False) def google_standards_coverage(self) -> Optional[Setting]: - return self.get(KEY_GOOGLE_STANDARDS_COVERAGE, value=False) - - def aws_events(self) -> Optional[str]: - return self.get(KEY_AWS_EVENTS) - - def azure_events(self) -> Optional[str]: - return self.get(KEY_AZURE_EVENTS) - - def google_events(self) -> Optional[str]: - return self.get(KEY_GOOGLE_EVENTS) + return self.get(SettingKey.GOOGLE_STANDARDS_COVERAGE, value=False) + + def max_cron_number(self) -> Optional[int]: + value = self.get(SettingKey.MAX_CRON_NUMBER) + if isinstance(value, str) and value.isdigit(): + value = int(value) + return value if 2 <= value <= 20 else 10 + else: + return 10 + + def get_retry_interval(self) -> Optional[int]: + value = self.get('retry_interval') + if isinstance(value, str) and value.isdigit(): + value = int(value) if int(value) <= 60 else 60 + return value if value >= 0 else 30 + return 30 + + def disable_send_reports(self): + value = self.get(name=SettingKey.SEND_REPORTS, value=False) + value.value = False + value.save() + + def enable_send_reports(self): + value = self.get(name=SettingKey.SEND_REPORTS, value=False) + value.value = True + value.save() + + def get_send_reports(self) -> bool: + value = self.get(name=SettingKey.SEND_REPORTS, + consistent_read=True) + return value if value else False + + def get_max_attempt_number(self) -> int: + value = self.get(name=SettingKey.MAX_ATTEMPT) + return value if value else 4 + + def get_max_rabbitmq_size(self) -> int: + value = self.get(name=SettingKey.MAX_RABBITMQ_REQUEST_SIZE) + return int(value) if value else 5000000 # 5 MB class CachedSettingsService(SettingsService): @@ -283,13 +227,16 @@ def __init__(self, *args, **kwargs): # name to Setting instance self._cache = cache.factory() - def get(self, name: str, value: bool = True + def get(self, name: str, value: bool = True, consistent_read: bool = False ) -> Optional[Union[Setting, dict]]: - if name in self._cache: + if name in self._cache and not consistent_read: setting = self._cache[name] + _LOG.debug(f'Getting setting {name} from cache') return setting.value if value else setting # not in cache - setting = super().get(name, value=False) + _LOG.debug(f'{name} setting value is missing from cache') + setting = super().get(name, value=False, + consistent_read=consistent_read) if not setting: return self._cache[name] = setting @@ -302,4 +249,5 @@ def delete(self, setting: Union[Setting, str]) -> bool: def save(self, setting: Setting): self._cache[setting.name] = setting + _LOG.debug(f'{setting.name} setting was saved to cache') return super().save(setting) diff --git a/src/services/sharding.py b/src/services/sharding.py new file mode 100644 index 000000000..a8bc665db --- /dev/null +++ b/src/services/sharding.py @@ -0,0 +1,637 @@ +from abc import ABC, abstractmethod +from collections import ChainMap, defaultdict +import io +from pathlib import PurePosixPath +import pickle +import tempfile +import time +from typing import ( + BinaryIO, + Generator, + Iterable, + Iterator, + Literal, + TYPE_CHECKING, + TypedDict, + cast, +) + +import msgspec + +from helpers import hashable, urljoin +from helpers.constants import Cloud, GLOBAL_REGION +from services import SP +from services.clients.s3 import S3Client + +if TYPE_CHECKING: + from modular_sdk.models.tenant import Tenant + +# do not change the order, just append new regions. This collection is only +# for shards distributor +AWS_REGIONS = ( + 'us-east-1', 'us-east-2', 'us-west-1', 'us-west-2', + 'ap-south-1', 'ap-northeast-1', 'ap-northeast-2', 'ap-northeast-3', + 'ap-southeast-1', 'ap-southeast-2', 'ca-central-1', 'eu-central-1', + 'eu-west-1', 'eu-west-2', 'eu-west-3', 'eu-north-1', 'sa-east-1', + 'ap-southeast-3', 'ap-southeast-4', 'af-south-1', 'ap-east-1', + 'ap-south-2', 'eu-south-1', 'eu-south-2', 'eu-central-2', + 'il-central-1', 'me-south-1', 'me-central-1', 'us-gov-east-1', + 'us-gov-west-1' +) + + +class ShardPartDict(TypedDict): + p: str # policy name + l: str # region + r: list[dict] # resources + t: float # timestamp + + +class BaseShardPart: + """ + Keeps a list of resources and some attributes that define the belonging + of these resources. Can be region, namespace, group - something that + differentiate these resources from some others + Each part has policy attribute. We assume that if some policy (policy + in a region in case of AWS) was scanned its output can safely be + considered true and the previous output can be overriden by the new one. + This class is just an interface + """ + policy: str + location: str + resources: list[dict] + timestamp: float + + def drop(self): ... + + def __repr__(self) -> str: + return f'<{self.__class__.__name__} {self.policy}:{self.location}>' + + def serialize(self) -> ShardPartDict: + return { + 'p': self.policy, + 'l': self.location, + 'r': self.resources, + 't': self.timestamp + } + + +class ShardPart(msgspec.Struct, BaseShardPart, frozen=True): + policy: str = msgspec.field(name='p') + location: str = msgspec.field(name='l', default=GLOBAL_REGION) + timestamp: float = msgspec.field(default_factory=time.time, name='t') + resources: list[dict] = msgspec.field(default_factory=list, name='r') + + +class LazyPickleShardPart(BaseShardPart): + __slots__ = 'policy', 'location', '_resources', 'timestamp', 'filepath' + + def __init__(self, filepath: str, policy: str, + location: str = GLOBAL_REGION, + timestamp: float | None = None): + self.policy: str = policy + self.location: str = location + self.timestamp: float = timestamp or time.time() + self._resources: list[dict] | None = None + self.filepath: str = filepath + + @classmethod + def from_resources(cls, resources: list[dict], policy: str, + location: str = GLOBAL_REGION): + """ + Creates shard part with given resources but immediately dumps them + to inner file storage + :param resources: + :param policy: + :param location: + :return: + """ + with tempfile.NamedTemporaryFile(delete=False) as fp: + pickle.dump(resources, fp, protocol=pickle.HIGHEST_PROTOCOL) + return cls( + filepath=fp.name, + policy=policy, + location=location + ) + + @property + def resources(self) -> list[dict]: + if self._resources is None: + with open(self.filepath, 'rb') as fp: + self._resources = pickle.load(fp) + return self._resources + + def drop(self) -> None: + self._resources = None # not sure if it can help + + +class Shard(Iterable[BaseShardPart]): + """ + Shard store shard parts. This shard implementation uses policy and + location as a key to a shard part. This means that we can update + resources within policy & region. Maybe we will need some other + implementations in the future + """ + __slots__ = ('_data',) + + def __init__(self, data: dict | None = None): + self._data: dict[tuple[str, str], BaseShardPart] = data or dict() + + def __iter__(self) -> Iterator[BaseShardPart]: + return self._data.values().__iter__() + + def __len__(self) -> int: + return self._data.__len__() + + @property + def raw(self) -> dict[tuple[str, str], BaseShardPart]: + return self._data + + def put(self, part: BaseShardPart) -> None: + """ + Adds a part to the shard in case such part does not exist yet. + Each part contains its timestamp. In case we try to put a part and + the same one already exists - the one with higher timestamp will + be kept + :param part: + :return: + """ + key = (part.policy, part.location) + existing = self._data.get(key) + if existing and existing.timestamp > part.timestamp: + return + self._data[key] = part + + def update(self, shard: 'Shard') -> None: + """ + Updates shard parts in the current shard with parts from existing one. + :param shard: + :return: + """ + for part in shard: + self.put(part) + + +class ShardDataDistributor(ABC): + """ + Defines logic how we must distribute parts between shards. We always have + N shards and some trait based on which we must assign a piece of data to + a shard. + This class knows how and based on what attributes to distribute + """ + + def __init__(self, n: int = 1): + self._n = n + + @property + def shards_number(self) -> int: + return self._n + + @abstractmethod + def distribute(self, **kwargs) -> int: + """ + Pure idempotent function without side effects. Must return + shard number in range [0; n-1] + Must accept some attributes and values based on which the distribution + happens. Concrete incoming values depend on the implementation + :param kwargs: + :return: + """ + + @abstractmethod + def key(self, part: BaseShardPart) -> dict: + """ + Must retrieve data from the given part based on which the + distribution happens + :param part: + :return: + """ + + def distribute_part(self, part: BaseShardPart) -> int: + """ + Must return shard for a concrete shard part + :param part: + :return: + """ + return self.distribute(**self.key(part)) + + +class SingleShardDistributor(ShardDataDistributor): + """ + Is used to distribute azure and gcp findings. Since regions on + gcp projects and azure subscriptions are scanned all at once -> it + probably won't be efficient to distribute those by regions, because we + will spend more money on S3 requests than could save on traffic + """ + + def key(self, part: BaseShardPart) -> dict: + return {} + + def distribute(self, **kwargs) -> int: + return 0 + + +class AWSRegionDistributor(ShardDataDistributor): + """ + The approach of distributing AWS resources to different shards by + regions can be efficient because mostly users scan only some of their + regions. Since findings is a collection which we keep up-to-date and + consistent must update it each time. So in order not to download the + whole data from S3 each time - we can make this distribution. + """ + regions = {r: i for i, r in enumerate([GLOBAL_REGION, *AWS_REGIONS])} + + def key(self, part: BaseShardPart) -> dict: + return dict(region=part.location) + + def distribute(self, region: str) -> int: + """ + Distributes regions by shards + :param region: + :return: + """ + index = self.regions.get(region) + if index is None: + index = len(self.regions) + return index % self._n + + +class ShardsIO(ABC): + """ + Defines an interface for shards writer + """ + __slots__ = () + + @abstractmethod + def write(self, n: int, shard: Shard): + """ + Must write one shard + :param n: + :param shard: + :return: + """ + + def write_many(self, pairs: Iterable[tuple[int, Shard]]): + for n, shard in pairs: + self.write(n, shard) + + def read_raw_many(self, numbers: Iterable[int] + ) -> Iterator[list[BaseShardPart]]: + return filter(lambda x: x is not None, map(self.read_raw, numbers)) + + @abstractmethod + def read_raw(self, n: int) -> list[BaseShardPart] | None: + """ + Reads a specific shard but returns raw json + :param n: + :return: + """ + + @abstractmethod + def write_meta(self, meta: dict): + ... + + @abstractmethod + def read_meta(self) -> dict: + ... + + +class ShardsS3IO(ShardsIO): + """ + Writer V1 + """ + __slots__ = '_bucket', '_root', '_client' + + def __init__(self, bucket: str, key: str, client: S3Client): + """ + :param bucket: + :param key: root folder where to put shards + """ + self._bucket = bucket + self._root = key + self._client = client + + @staticmethod + def shard_to_filelike(shard: Shard) -> BinaryIO: + encoder = msgspec.json.Encoder() + buf = tempfile.TemporaryFile() + _first = True + for part in shard: + if _first: + s = b'[' + _first = False + else: + s = b',' + buf.write(s) + buf.write(encoder.encode(part.serialize())) + buf.write(b']') + buf.seek(0) + return buf + + @property + def key(self) -> str: + return self._root + + @key.setter + def key(self, value: str): + self._root = value + + def _key(self, n: int) -> str: + return str((PurePosixPath(self._root) / str(n)).with_suffix('.json')) + + def write(self, n: int, shard: Shard): + self._client.gz_put_object( + bucket=self._bucket, + key=self._key(n), + body=self.shard_to_filelike(shard), + ) + + def read_raw(self, n: int) -> list[BaseShardPart] | None: + obj = self._client.gz_get_object( + bucket=self._bucket, + key=self._key(n), + gz_buffer=tempfile.TemporaryFile(), + ) + if not obj: + return + return msgspec.json.decode(cast(io.BytesIO, obj).getvalue(), + type=list[ShardPart]) + + def write_meta(self, meta: dict): + self._client.gz_put_json( + bucket=self._bucket, + key=str((PurePosixPath(self._root) / 'meta.json')), + obj=meta + ) + + def read_meta(self) -> dict: + return self._client.gz_get_json( + bucket=self._bucket, + key=str((PurePosixPath(self._root) / 'meta.json')) + ) or {} + + +class ShardsS3IOV2(ShardsS3IO): + """ + Writer v2. Writes shard parts as json lines + """ + @staticmethod + def shard_to_filelike(shard: Shard) -> BinaryIO: + encoder = msgspec.json.Encoder() + buf = tempfile.TemporaryFile() + first = True + for part in shard: + if not first: + buf.write(b'\n') + else: + first = False + buf.write(encoder.encode(part.serialize())) + buf.seek(0) + return buf + + +class ShardsIterator(Iterator[tuple[int, Shard]]): + def __init__(self, shards: dict, n: int): + self.shards = shards + self.n = n # max number of shards + self._reset() + + def __iter__(self): + self._reset() + return self + + def _reset(self): + self.indexes = iter(range(self.n)) # [0; n) + + def __next__(self) -> tuple[int, Shard]: + item, index = None, None + while not item: + index = next(self.indexes) + item = self.shards.get(index) + return index, item + + +class ShardsCollection(Iterable[tuple[int, Shard]]): + """ + Light abstraction over shards, shards writer and distributor + """ + __slots__ = '_distributor', '_io', 'shards', 'meta' + + def __init__(self, distributor: ShardDataDistributor, + io: ShardsIO | None = None): + self._distributor = distributor + self._io = io + + self.shards: defaultdict[int, Shard] = defaultdict(Shard) + self.meta = {} + + def __iter__(self) -> Iterator[tuple[int, Shard]]: + """ + Iterates over inner shards and their number in order + :return: + """ + return ShardsIterator( + shards=self.shards, + n=self._distributor.shards_number + ) + + def __len__(self) -> int: + return self.shards.__len__() + + def iter_parts(self) -> Generator[BaseShardPart, None, None]: + for _, shard in self: + yield from shard + + def update(self, other: 'ShardsCollection'): + """ + Puts shard parts from the given collection to this one, + redistributing them + """ + for _, shard in other: + self.put_parts(shard) + + def __sub__(self, other: 'ShardsCollection') -> 'ShardsCollection': + """ + Returns a difference between two collections. Uses + SingleShardDistributor for the new collection + """ + # todo rewrite + new = ShardsCollectionFactory.difference() + + current = ChainMap(*[shard.raw for _, shard in self]) + other = ChainMap(*[shard.raw for _, shard in other]) + for key, part in current.items(): + other_part = other.get(key) + if not other_part: # definitely new + new.put_part(part) + continue + current_res = set(hashable(res) for res in part.resources) + other_res = set(hashable(res) for res in other_part.resources) + new.put_part(ShardPart( + policy=part.policy, + location=part.location, + resources=list(current_res - other_res) + )) + return new + + @property + def distributor(self) -> ShardDataDistributor: + return self._distributor + + @property + def io(self) -> ShardsIO: + return self._io + + @io.setter + def io(self, value: ShardsIO): + self._io = value + + def put_part(self, part: BaseShardPart) -> None: + """ + Distribute the given shard part to its shard + :param part: + :return: + """ + n = self._distributor.distribute_part(part) + self.shards[n].put(part) + + def put_parts(self, parts: Iterable[BaseShardPart]) -> None: + """ + Puts multiple parts. It distributes the given parts to their right + shards according to the current distributor. + :param parts: Can be another shard since a shard can be iterated + over its parts + :return: + """ + for part in parts: + self.put_part(part) + + def write_all(self): + """ + Writes all the shards that are currently in memory + :return: + """ + self._io.write_many(iter(self)) + + def fetch_by_indexes(self, it: Iterable[int]): + """ + Fetches shards by specified indexes + """ + for parts in self._io.read_raw_many(set(it)): + self.put_parts(parts) + + def fetch_all(self): + """ + Fetches all the shards + :return: + """ + it = range(self._distributor.shards_number) + self.fetch_by_indexes(it) + + def fetch(self, **kwargs): + """ + Fetch only one shard, distributes its part to the right local + shards according to the distributor + :param kwargs: depends on the self._distributor instance + :return: + """ + n = self._distributor.distribute(**kwargs) + self.fetch_by_indexes([n]) + + def fetch_multiple(self, params: list[dict]): + it = [self._distributor.distribute(**kw) for kw in params] + self.fetch_by_indexes(it) + + def fetch_modified(self): + """ + Fetches only those shards that were modified locally + :return: + """ + self.fetch_by_indexes(self.shards.keys()) + + def update_meta(self, other: dict): + for rule, data in other.items(): + self.meta.setdefault(rule, {}).update(data) + + def fetch_meta(self): + self.update_meta(self._io.read_meta()) + + def write_meta(self): + if self.meta: + self._io.write_meta(self.meta) + + +class ShardingConfig: + filename = '.conf' + + __slots__ = 'version', + + def __init__(self, version: Literal[1, 2]): + """ + :param version: + 1 - one shard is a json - list of dits + 2 - one shard is a bunch of JSONs separated by newline + """ + self.version = version + # todo add distributor, and maybe smt else here + + @classmethod + def from_raw(cls, dct: dict) -> 'ShardingConfig': + return cls(**dct) + + @classmethod + def build_default(cls) -> 'ShardingConfig': + return cls(version=1) + + +class ShardsCollectionFactory: + """ + Builds distributors but without writers + """ + @staticmethod + def from_s3_key(cloud: Cloud, key: str) -> ShardsCollection: + bucket = SP.environment_service.default_reports_bucket_name() + item = SP.s3.gz_get_json(bucket, urljoin(key, ShardingConfig.filename)) + if item: + conf = ShardingConfig(item) + else: + conf = ShardingConfig.build_default() + col = ShardsCollection( + distributor=ShardsCollectionFactory._cloud_distributor(cloud) + ) + match conf.version: + case 1: + col.io = ShardsS3IO(bucket=bucket, key=key, client=SP.s3) + case 2: + col.io = ShardsS3IOV2(bucket=bucket, key=key, client=SP.s3) + case _: + raise RuntimeError('Invalid shards version') + return col + + @staticmethod + def _cloud_distributor(cloud: Cloud) -> ShardDataDistributor: + match cloud: + case Cloud.AWS: + return AWSRegionDistributor(2) + case _: + return SingleShardDistributor() + + @staticmethod + def from_cloud(cloud: Cloud) -> ShardsCollection: + return ShardsCollection( + distributor=ShardsCollectionFactory._cloud_distributor(cloud) + ) + + @staticmethod + def from_tenant(tenant: 'Tenant') -> ShardsCollection: + cloud = Cloud[tenant.cloud.upper()] + return ShardsCollectionFactory.from_cloud(cloud) + + @staticmethod + def difference() -> ShardsCollection: + """ + Event driven reports are differences (only new resources that were + found by a job). Their size is mostly low, so we don't need sharding + there + """ + return ShardsCollection(distributor=SingleShardDistributor()) diff --git a/src/services/ssm_service.py b/src/services/ssm_service.py index 523c5855f..1c4f871b7 100644 --- a/src/services/ssm_service.py +++ b/src/services/ssm_service.py @@ -1,8 +1,8 @@ import re -from typing import Union, Dict from helpers.log_helper import get_logger from helpers.time_helper import utc_datetime +from services import cache from services.clients.ssm import AbstractSSMClient _LOG = get_logger(__name__) @@ -13,28 +13,19 @@ class SSMService: def __init__(self, client: AbstractSSMClient): self.client = client - self.__secrets = {} + self.__secrets = cache.factory() def get_secret_value(self, secret_name): - _LOG.debug(f'Retrieving parameter with name \'{secret_name}\'') - if secret_name in self.__secrets: - return self.__secrets[secret_name] + if val := self.__secrets.get(secret_name): + _LOG.debug(f'Returning cached value: of {secret_name}') + return val + _LOG.debug(f'Requesting secret: {secret_name}') secret = self.client.get_secret_value(secret_name=secret_name) if secret: self.__secrets[secret_name] = secret return secret - def get_secret_values(self, secret_names: list): - result = {} - if len(secret_names) > 10: - while len(secret_names) > 10: - result.update(self.client.get_secret_values( - secret_names=secret_names[:10])) - secret_names = secret_names[10:] - result.update(self.client.get_secret_values(secret_names=secret_names)) - return result - def create_secret_value(self, secret_name, secret_value): self.client.create_secret(secret_name=secret_name, secret_value=secret_value) @@ -48,7 +39,7 @@ def enable_secrets_engine(self, mount_point=None): def is_secrets_engine_enabled(self, mount_point=None): return self.client.is_secrets_engine_enabled(mount_point) - def save_data(self, name: str, value: Union[str, Dict], + def save_data(self, name: str, value: str | dict, prefix: str = 'custodian') -> str: """ Creates safe ssm parameter name and saved data there diff --git a/src/services/token_service.py b/src/services/token_service.py deleted file mode 100644 index 8c4a5f9d1..000000000 --- a/src/services/token_service.py +++ /dev/null @@ -1,54 +0,0 @@ -from services.clients.abstract_key_management import \ - AbstractKeyManagementClient -from helpers.security import AbstractTokenEncoder, TokenEncoder - -from helpers.constants import CLIENT_TOKEN_ATTR - -from helpers.log_helper import get_logger - -from typing import Union, Type - -_LOG = get_logger(__name__) - -EXPIRATION_ATTR = 'exp' -ENCODING = 'utf-8' - - -class TokenService: - def __init__(self, client: AbstractKeyManagementClient): - self._client = client - - def derive_encoder(self, token_type: str, **payload) -> \ - Union[AbstractTokenEncoder, Type[None]]: - """ - Mandates preliminary token-encoder preparation, based on a respective - token type, given there is such one. For any injectable data, - attaches said payload to the token as claims. - :parameter token_type: str - :return: Union[AbstractTokenEncoder, Type[None]] - """ - t_head = f'\'{token_type}\'' - _LOG.debug(f'Going to instantiate a {t_head} encoder.') - - reference_map = self._token_type_builder_map() - _Encoder: Type[AbstractTokenEncoder] = reference_map.get(token_type) - if not _Encoder: - _LOG.warning(f'{t_head.capitalize()} encoder does not exist.') - return None - - encoder, key = _Encoder(), None - encoder.key_management = self._client - encoder.typ = token_type - for key, value in payload.items(): - encoder[key] = value - else: - if key: - _LOG.debug(f'{t_head} encoder has been attached ' - f'with {payload} claims.') - return encoder - - @staticmethod - def _token_type_builder_map(): - return { - CLIENT_TOKEN_ATTR: TokenEncoder - } diff --git a/src/services/user_service.py b/src/services/user_service.py deleted file mode 100644 index d9c3be09c..000000000 --- a/src/services/user_service.py +++ /dev/null @@ -1,101 +0,0 @@ -import re -import secrets -from http import HTTPStatus -from typing import Optional - -from connections.auth_extension.base_auth_client import BaseAuthClient -from helpers import CustodianException -from helpers.log_helper import get_logger - -_LOG = get_logger(__name__) - -SSM_NOT_AVAILABLE = re.compile(r'[^a-zA-Z0-9\/_.-]') - - -class CognitoUserService: - - def __init__(self, client: BaseAuthClient): - self.client = client - - def save(self, username, password, customer, role, tenants=None): - _LOG.debug(f'Validating password for user {username}') - if self.client.is_user_exists(username): - raise CustodianException( - code=HTTPStatus.BAD_REQUEST, - content=f'The user with name {username} already exists.') - - _LOG.debug(f'Creating the user with username {username}') - if isinstance(tenants, list): - tenants = ','.join(tenants) - self.client.sign_up(username=username, - password=password, - customer=customer, - role=role, - tenants=tenants) - _LOG.debug(f'Setting the password for the user {username}') - self.client.set_password(username=username, - password=password) - _LOG.debug(f'Saving to table tenants {tenants}') - - @staticmethod - def safe_name(name: str) -> str: - return str(re.sub(SSM_NOT_AVAILABLE, '-', name)) - - def generate_username(self, name: Optional[str] = 'custodian') -> str: - """ - Generates unique username - """ - return f'{self.safe_name(name)}-{secrets.token_urlsafe(4)}' - - def get_user_role_name(self, user): - return self.client.get_user_role(user) - - def get_user_customer(self, user): - return self.client.get_user_customer(user) - - def get_user_tenants(self, user): - return self.client.get_user_tenants(user) - - def initiate_auth(self, username, password): - return self.client.admin_initiate_auth(username=username, - password=password) - - def respond_to_auth_challenge(self, challenge_name): - return self.client.respond_to_auth_challenge( - challenge_name=challenge_name) - - def update_role(self, username, role): - self.client.update_role(username=username, role=role) - - def update_customer(self, username, customer): - self.client.update_customer(username=username, customer=customer) - - def update_tenants(self, username, tenants): - self.client.update_tenants(username=username, tenants=tenants) - - def is_user_exists(self, username): - return self.client.is_user_exists(username) - - def delete_role(self, username): - self.client.delete_role(username=username) - - def delete_tenants(self, username): - self.client.delete_tenants(username=username) - - def delete_customer(self, username): - self.client.delete_customer(username=username) - - def is_system_user_exists(self): - return self.client.is_system_user_exists() - - def get_system_user(self): - return self.client.get_system_user() - - def get_customers_latest_logins(self, customers: list = None): - return self.client.get_customers_latest_logins(customers) - - def admin_delete_user(self, username: str): - self.client.admin_delete_user(username) - - def set_password(self, username: str, password: str): - self.client.set_password(username, password) diff --git a/src/services/xlsx_writer.py b/src/services/xlsx_writer.py new file mode 100644 index 000000000..99980d7b2 --- /dev/null +++ b/src/services/xlsx_writer.py @@ -0,0 +1,166 @@ +""" +This is a wrapper over xlsxwriter. It handles such difficulties: +- skipping empty columns: in case the number of empty cells in a specific + column exceeds a limit, the whole column will be skipped automatically. + By, default the limit is 1. +- merging cells: in case some columns of a specific row contain more cells + than other columns of the same row, all the others will be merged. +- provides more or less convenient interface: +>>> ft: Format # some workbook format +>>> wsh: Worksheet +>>> table = Table() +>>> table.new_row() +>>> table.add_cells(CellContent('№')) +>>> table.add_cells(CellContent('Resource', ft)) +>>> table.add_cells(CellContent('Rules', ft)) +>>> table.new_row() +>>> table.add_cells(CellContent(1)) +>>> table.add_cells(CellContent({'arn': 'arn:aws:test'})) +>>> table.add_cells(CellContent('ecc-001'), CellContent('ecc-002')) +>>> XlsxRowsWriter().write(wsh, table) + +This will result in such a table: + ++----+-------------------------+---------+ +| № | Resource | Rules | ++----+-------------------------+---------+ +| 1 | {"arn": "arn:aws:test"} | ecc-001 | +| | +---------+ +| | | ecc-002 | ++----+-------------------------+---------+ + +See, how merging of cells automatically appeared. It happend because we +put two arguments into add_cells() method. + +""" + +import json + +from xlsxwriter.format import Format +from xlsxwriter.worksheet import Worksheet + +from helpers import skip_indexes + + +class Cell: + __slots__ = ('row', 'col') + + def __init__(self, row: int = 0, col: int = 0): + self.row = row + self.col = col + + def __repr__(self) -> str: + return f'{self.__class__.__name__}(row={self.row}, col={self.col})' + + +class CellContent: + __slots__ = ('dt', 'ft') + + def __init__(self, dt: int | str | dict | list | None = None, + ft: Format | None = None): + self.dt = dt + self.ft = ft + + @property + def data(self) -> str: + if isinstance(self.dt, str): + return self.dt + return json.dumps(self.dt, default=str) + + def __bool__(self) -> bool: + return self.dt is not None + + def __repr__(self) -> str: + return f'{self.__class__.__name__}(dt={self.dt}, ft={self.ft})' + + +# inner tuple cannot contain Nones. ONLY CellContent. +Row = list[tuple[CellContent, ...]] + + +class Table: + __slots__ = ('buffer', ) + + def __init__(self, buffer: list[Row] | None = None): + self.buffer: list[Row] = buffer or list() + + def new_row(self): + """ + Creates a new raw + :return: + """ + self.buffer.append(list()) + + def add_cells(self, *args: CellContent): + """ + Adds new cells to the current row + :param args: + :return: + """ + self.buffer[-1].append(tuple(args)) # make sure you called new_row + + @property + def rows(self) -> int: + return len(self.buffer) + + @property + def cols(self) -> int: + return len(max(self.buffer, key=len)) + + +class XlsxRowsWriter: + def __init__(self, empty_col_threshold: int = 1): + """ + :param empty_col_threshold: number of not empty cells when we still + consider this column an empty one. Default 1 - for header. + """ + self._threshold = empty_col_threshold + + def empty_cols(self, rows: list[Row]) -> set[int]: + """ + :param rows: + :return: + """ + empty = set() + for i, col in enumerate(zip(*rows)): + if sum(map(any, col)) <= self._threshold: + empty.add(i) + return empty + + @staticmethod + def _write_row(row: Row, wsh: Worksheet, pointer: Cell, + empty: set[int]): + """ + + :param row: + :param wsh: + :param pointer: + :param empty: + :return: + """ + highest = len(max(skip_indexes(row, empty), key=len, default=())) + + for i, col in enumerate(skip_indexes(row, empty)): + for j, cell in enumerate(col): + if cell.ft: + wsh.write(pointer.row + j, pointer.col + i, cell.data, + cell.ft) + else: + wsh.write(pointer.row + j, pointer.col + i, cell.data) + if highest > 1 and highest > len(col) > 0: # need merge + wsh.merge_range( + pointer.row + j, + pointer.col + i, + pointer.row + j + highest - len(col), + pointer.col + i, + '' + ) + pointer.row += highest + + def write(self, wsh: Worksheet, table: Table, start: Cell | None = None): + # todo allow to pass iterator of Rows of CellContent instead of table + pointer = start or Cell() + rows = table.buffer + empty = self.empty_cols(rows) + for row in rows: + self._write_row(row, wsh, pointer, empty) diff --git a/src/validators/deployment_resources.json b/src/validators/deployment_resources.json index 5fa33e146..7f48e1d86 100644 --- a/src/validators/deployment_resources.json +++ b/src/validators/deployment_resources.json @@ -1,5900 +1,6563 @@ { "custodian-as-a-service-api": { - "resource_type": "api_gateway", "dependencies": [], - "resources": {}, "models": { - "ScheduledJobDeleteModel": { + "BaseModel": { + "content_type": "application/json", + "schema": { + "properties": {}, + "title": "BaseModel", + "type": "object" + } + }, + "CLevelGetReportModel": { "content_type": "application/json", "schema": { - "title": "ScheduledJobDeleteModel", - "type": "object", "properties": { - "name": { - "title": "Name", - "type": "string" - }, - "customer": { - "title": "Customer", - "type": "string" + "types": { + "items": { + "enum": [ + "OVERVIEW", + "COMPLIANCE", + "ATTACK_VECTOR" + ], + "type": "string" + }, + "title": "Types", + "type": "array", + "uniqueItems": true } }, - "required": [ - "name" - ], - "additionalProperties": false + "title": "CLevelGetReportModel", + "type": "object" } }, - "RuleSourcePostModel": { + "CredentialsActivationModel": { "content_type": "application/json", "schema": { - "title": "RuleSourcePostModel", - "type": "object", "properties": { - "git_project_id": { - "title": "Git Project Id", - "type": "string" - }, - "git_url": { - "title": "Git Url", - "pattern": "^https?:\\/\\/[^\\/]+$", - "type": "string" - }, - "git_ref": { - "title": "Git Ref", - "default": "main", - "type": "string" - }, - "git_rules_prefix": { - "title": "Git Rules Prefix", - "default": "/", - "type": "string" - }, - "git_access_type": { - "default": "TOKEN", - "allOf": [ - { - "$ref": "#/definitions/GitAccessType" + "data": { + "properties": { + "activated_for": { + "items": { + "type": "string" + }, + "title": "Activated For", + "type": "array" + }, + "activated_for_all": { + "title": "Activated For All", + "type": "boolean" + }, + "excluding": { + "items": { + "type": "string" + }, + "title": "Excluding", + "type": "array" + }, + "within_clouds": { + "items": { + "type": "string" + }, + "title": "Within Clouds", + "type": "array" } - ] - }, - "git_access_secret": { - "title": "Git Access Secret", - "type": "string" - }, - "customer": { - "title": "Customer", - "type": "string" - }, - "tenant_allowance": { - "title": "Tenant Allowance", - "default": [], - "type": "array", - "items": {} - }, - "description": { - "title": "Description", - "type": "string" + }, + "required": [ + "activated_for_all", + "excluding" + ], + "title": "BaseActivation", + "type": "object" } }, "required": [ - "git_project_id", - "description" + "data" ], - "additionalProperties": false, - "definitions": { - "GitAccessType": { - "title": "GitAccessType", - "description": "An enumeration.", - "enum": [ - "TOKEN" - ], - "type": "string" - } - } + "title": "CredentialsActivationModel", + "type": "object" } }, - "LicenseManagerConfigSettingPostModel": { + "CredentialsBindModel": { "content_type": "application/json", "schema": { - "title": "LicenseManagerConfigSettingPostModel", - "type": "object", "properties": { - "host": { - "title": "Host", - "type": "string" - }, - "port": { - "title": "Port", - "type": "integer" - }, - "protocol": { - "title": "Protocol", - "enum": [ - "HTTP", - "HTTPS" - ], - "type": "string" + "all_tenants": { + "default": false, + "title": "All Tenants", + "type": "boolean" }, - "stage": { - "title": "Stage", - "type": "string" + "exclude_tenants": { + "items": { + "type": "string" + }, + "title": "Exclude Tenants", + "type": "array", + "uniqueItems": true }, - "api_version": { - "title": "Api Version", - "type": "string" + "tenant_names": { + "items": { + "type": "string" + }, + "title": "Tenant Names", + "type": "array", + "uniqueItems": true } }, - "required": [ - "host" - ], - "additionalProperties": false + "title": "CredentialsBindModel", + "type": "object" } }, - "RoleCacheDeleteModel": { + "CustomerExcludedRulesPutModel": { "content_type": "application/json", "schema": { - "title": "RoleCacheDeleteModel", - "type": "object", "properties": { - "customer": { - "title": "Customer", - "type": "string" - }, - "name": { - "title": "Name", - "type": "string" + "rules": { + "items": { + "type": "string" + }, + "title": "Rules", + "type": "array", + "uniqueItems": true } }, - "additionalProperties": false + "required": [ + "rules" + ], + "title": "CustomerExcludedRulesPutModel", + "type": "object" } }, - "TenantPostModel": { + "DefectDojoActivationPutModel": { "content_type": "application/json", "schema": { - "title": "TenantPostModel", - "type": "object", "properties": { - "name": { - "title": "Name", - "type": "string" - }, - "display_name": { - "title": "Display Name", - "type": "string" + "all_tenants": { + "default": false, + "title": "All Tenants", + "type": "boolean" }, - "cloud": { - "title": "Cloud", + "attachment": { + "default": null, "enum": [ - "AWS", - "AZURE", - "GOOGLE" + "json", + "xlsx", + "csv" ], + "title": "Attachment", "type": "string" }, - "cloud_identifier": { - "title": "Cloud Identifier", - "type": "string" - }, - "primary_contacts": { - "title": "Primary Contacts", - "default": [], - "type": "array", + "clouds": { "items": { + "enum": [ + "AWS", + "AZURE", + "GOOGLE" + ], "type": "string" - } - }, - "secondary_contacts": { - "title": "Secondary Contacts", - "default": [], + }, + "title": "Clouds", "type": "array", + "uniqueItems": true + }, + "engagement": { + "default": "Rule-Engine Main", + "description": "Defect dojo engagement name", + "title": "Engagement", + "type": "string" + }, + "exclude_tenants": { "items": { "type": "string" - } - }, - "tenant_manager_contacts": { - "title": "Tenant Manager Contacts", - "default": [], + }, + "title": "Exclude Tenants", "type": "array", + "uniqueItems": true + }, + "product": { + "default": "{tenant_name}", + "description": "Defect dojo product name", + "title": "Product", + "type": "string" + }, + "product_type": { + "default": "Rule Engine", + "description": "Defect dojo product type name", + "title": "Product Type", + "type": "string" + }, + "scan_type": { + "default": "Generic Findings Import", + "description": "Defect dojo scan type", + "enum": [ + "Generic Findings Import", + "Cloud Custodian Scan" + ], + "title": "Scan Type", + "type": "string" + }, + "send_after_job": { + "default": false, + "description": "Whether to send the results to dojo after each scan", + "title": "Send After Job", + "type": "boolean" + }, + "tenant_names": { "items": { "type": "string" - } + }, + "title": "Tenant Names", + "type": "array", + "uniqueItems": true }, - "default_owner": { - "title": "Default Owner", + "test": { + "default": "{job_id}", + "description": "Defect dojo test", + "title": "Test", "type": "string" } }, - "required": [ - "name", - "cloud", - "cloud_identifier" - ], - "additionalProperties": false + "title": "DefectDojoActivationPutModel", + "type": "object" } }, - "ApplicationDeleteModel": { + "DefectDojoPostModel": { "content_type": "application/json", "schema": { - "title": "ApplicationDeleteModel", - "type": "object", "properties": { - "application_id": { - "title": "Application Id", + "api_key": { + "title": "Api Key", + "type": "string" + }, + "description": { + "title": "Description", "type": "string" }, - "customer": { - "title": "Customer", + "url": { + "format": "uri", + "minLength": 1, + "title": "Url", "type": "string" } }, "required": [ - "application_id" + "url", + "api_key", + "description" ], - "additionalProperties": false + "title": "DefectDojoPostModel", + "type": "object" } }, - "RabbitMQDTO": { + "DepartmentGetReportModel": { "content_type": "application/json", "schema": { - "title": "RabbitMQDTO", - "type": "object", "properties": { - "trace_id": { - "title": "Trace Id", - "type": "string" - }, - "items": { - "title": "Items", - "type": "array", + "types": { "items": { - "$ref": "#/definitions/RabbitMQ" - } - } - }, - "required": [ - "trace_id", - "items" - ], - "definitions": { - "RabbitMQ": { - "title": "RabbitMQ", - "type": "object", - "properties": { - "maestro_user": { - "title": "Maestro User", - "type": "string" - }, - "rabbit_exchange": { - "title": "Rabbit Exchange", - "type": "string" - }, - "request_queue": { - "title": "Request Queue", - "type": "string" - }, - "response_queue": { - "title": "Response Queue", - "type": "string" - }, - "sdk_access_key": { - "title": "Sdk Access Key", - "type": "string" - }, - "customer": { - "title": "Customer", - "type": "string" - } + "enum": [ + "TOP_RESOURCES_BY_CLOUD", + "TOP_TENANTS_RESOURCES", + "TOP_TENANTS_COMPLIANCE", + "TOP_COMPLIANCE_BY_CLOUD", + "TOP_TENANTS_ATTACKS", + "TOP_ATTACK_BY_CLOUD" + ], + "type": "string" }, - "required": [ - "maestro_user", - "request_queue", - "response_queue", - "sdk_access_key", - "customer" - ] + "title": "Types", + "type": "array", + "uniqueItems": true } - } + }, + "title": "DepartmentGetReportModel", + "type": "object" } }, - "ParentDTO": { + "EntityResourcesReportModel": { "content_type": "application/json", "schema": { - "title": "ParentDTO", - "type": "object", "properties": { - "trace_id": { - "title": "Trace Id", - "type": "string" - }, - "items": { - "title": "Items", - "type": "array", - "items": { - "$ref": "#/definitions/Parent" - } - } - }, - "required": [ - "trace_id", - "items" - ], - "definitions": { - "ParentMeta": { - "title": "ParentMeta", - "type": "object", - "properties": { - "cloud": { - "title": "Cloud", - "enum": [ - "AWS", - "AZURE", - "GOOGLE", - "ALL" + "data": { + "anyOf": [ + { + "properties": { + "content": { + "title": "Content", + "type": "object" + }, + "customer_name": { + "title": "Customer Name", + "type": "string" + }, + "format": { + "enum": [ + "json", + "xlsx" + ], + "title": "ReportFormat", + "type": "string" + }, + "platform_id": { + "title": "Platform Id", + "type": "string" + }, + "tenant_name": { + "title": "Tenant Name", + "type": "string" + }, + "url": { + "title": "Url", + "type": "string" + } + }, + "required": [ + "format", + "customer_name" ], - "type": "string" + "title": "BaseReportEntity", + "type": "object" }, - "scope": { - "title": "Scope", - "enum": [ - "ALL", - "SPECIFIC_TENANT" - ], - "type": "string" + { + "type": "null" } - } + ] }, - "Parent": { - "title": "Parent", - "type": "object", - "properties": { - "application_id": { - "title": "Application Id", - "type": "string" - }, - "customer_id": { - "title": "Customer Id", - "type": "string" - }, - "description": { - "title": "Description", - "type": "string" - }, - "is_deleted": { - "title": "Is Deleted", - "type": "boolean" + "items": { + "anyOf": [ + { + "items": { + "properties": { + "account_id": { + "title": "Account Id", + "type": "string" + }, + "data": { + "title": "Data", + "type": "object" + }, + "job_id": { + "title": "Job Id", + "type": "string" + }, + "last_found": { + "title": "Last Found", + "type": "number" + }, + "matched_by": { + "title": "Matched By", + "type": "object" + }, + "platform_id": { + "title": "Platform Id", + "type": "string" + }, + "region": { + "title": "Region", + "type": "string" + }, + "resource_type": { + "title": "Resource Type", + "type": "string" + }, + "type": { + "enum": [ + "manual", + "reactive" + ], + "title": "JobType", + "type": "string" + }, + "violated_rules": { + "properties": { + "article": { + "title": "Article", + "type": "string" + }, + "description": { + "title": "Description", + "type": "string" + }, + "impact": { + "title": "Impact", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "remediation": { + "title": "Remediation", + "type": "string" + }, + "severity": { + "title": "Severity", + "type": "string" + } + }, + "required": [ + "name", + "description", + "severity" + ], + "title": "ViolatedRule", + "type": "object" + } + }, + "required": [ + "data", + "last_found", + "matched_by", + "region", + "resource_type", + "violated_rules" + ], + "title": "ResourcesReportItem", + "type": "object" + }, + "type": "array" }, - "meta": { - "$ref": "#/definitions/ParentMeta" + { + "type": "null" } - }, - "required": [ - "application_id", - "customer_id", - "description", - "is_deleted", - "meta" - ] + ], + "title": "Items" } - } + }, + "required": [ + "items", + "data" + ], + "title": "EntityResourcesReportModel", + "type": "object" } }, - "TenantDTO": { + "EntityRulesReportModel": { "content_type": "application/json", "schema": { - "title": "TenantDTO", - "type": "object", "properties": { - "trace_id": { - "title": "Trace Id", - "type": "string" - }, "items": { - "title": "Items", - "type": "array", "items": { - "$ref": "#/definitions/Tenant" - } - } - }, - "required": [ - "trace_id", - "items" - ], - "definitions": { - "Tenant": { - "title": "Tenant", - "type": "object", - "properties": { - "name": { - "title": "Name", - "type": "string" - }, - "customer_name": { - "title": "Customer Name", - "type": "string" - }, - "activation_date": { - "title": "Activation Date", - "type": "string" - }, - "is_active": { - "title": "Is Active", - "type": "boolean" - }, - "regions": { - "title": "Regions", - "type": "array", - "items": { + "properties": { + "average_exec": { + "title": "Average Exec", + "type": "number" + }, + "average_resources_failed": { + "title": "Average Resources Failed", + "type": "integer" + }, + "average_resources_scanned": { + "title": "Average Resources Scanned", + "type": "integer" + }, + "failed_invocations": { + "title": "Failed Invocations", + "type": "integer" + }, + "invocations": { + "title": "Invocations", + "type": "integer" + }, + "max_exec": { + "title": "Max Exec", + "type": "number" + }, + "min_exec": { + "title": "Min Exec", + "type": "number" + }, + "policy": { + "title": "Policy", + "type": "string" + }, + "region": { + "title": "Region", "type": "string" + }, + "resources_failed": { + "title": "Resources Failed", + "type": "integer" + }, + "resources_scanned": { + "title": "Resources Scanned", + "type": "integer" + }, + "succeeded_invocations": { + "title": "Succeeded Invocations", + "type": "integer" + }, + "total_api_calls": { + "title": "Total Api Calls", + "type": "object" + }, + "total_exec": { + "title": "Total Exec", + "type": "number" } }, - "project": { - "title": "Project", - "type": "string" - } + "required": [ + "average_exec", + "average_resources_failed", + "average_resources_scanned", + "failed_invocations", + "invocations", + "max_exec", + "min_exec", + "policy", + "region", + "resources_failed", + "resources_scanned", + "succeeded_invocations", + "total_api_calls", + "total_exec" + ], + "title": "AverageRulesReportItem", + "type": "object" }, - "required": [ - "name", - "customer_name", - "regions", - "project" - ] + "title": "Items", + "type": "array" } - } + }, + "required": [ + "items" + ], + "title": "EntityRulesReportModel", + "type": "object" } }, - "FindingsDeleteModel": { + "ErrorsModel": { "content_type": "application/json", "schema": { - "title": "FindingsDeleteModel", - "type": "object", + "description": "400 Validation error", "properties": { - "tenant_name": { - "title": "Tenant Name", - "type": "string" + "errors": { + "items": { + "properties": { + "location": { + "items": { + "type": "string" + }, + "title": "Location", + "type": "array" + }, + "message": { + "title": "Message", + "type": "string" + } + }, + "required": [ + "location", + "message" + ], + "title": "ErrorData", + "type": "object" + }, + "title": "Errors", + "type": "array" } }, - "additionalProperties": false + "required": [ + "errors" + ], + "title": "ErrorsModel", + "type": "object" } }, - "AccessApplicationDTO": { + "ErrorsReportModel": { "content_type": "application/json", "schema": { - "title": "AccessApplicationDTO", - "type": "object", "properties": { - "trace_id": { - "title": "Trace Id", - "type": "string" - }, - "items": { - "title": "Items", - "type": "array", - "items": { - "$ref": "#/definitions/AccessApplication" - } - } - }, - "required": [ - "trace_id", - "items" - ], - "definitions": { - "AccessApplicationMeta": { - "title": "AccessApplicationMeta", - "type": "object", - "properties": { - "username": { - "title": "Username", - "type": "string" - }, - "host": { - "title": "Host", - "type": "string" - }, - "port": { - "title": "Port", - "type": "integer" - }, - "protocol": { - "title": "Protocol", - "enum": [ - "HTTP", - "HTTPS" + "data": { + "anyOf": [ + { + "properties": { + "content": { + "title": "Content", + "type": "object" + }, + "customer_name": { + "title": "Customer Name", + "type": "string" + }, + "format": { + "enum": [ + "json", + "xlsx" + ], + "title": "ReportFormat", + "type": "string" + }, + "job_id": { + "title": "Job Id", + "type": "string" + }, + "job_type": { + "enum": [ + "manual", + "reactive" + ], + "title": "JobType", + "type": "string" + }, + "tenant_name": { + "title": "Tenant Name", + "type": "string" + }, + "url": { + "title": "Url", + "type": "string" + } + }, + "required": [ + "format", + "tenant_name", + "customer_name" ], - "type": "string" + "title": "BaseReportJob", + "type": "object" }, - "stage": { - "title": "Stage", - "type": "string" + { + "type": "null" } - } + ] }, - "AccessApplication": { - "title": "AccessApplication", - "type": "object", - "properties": { - "application_id": { - "title": "Application Id", - "type": "string" - }, - "customer_id": { - "title": "Customer Id", - "type": "string" - }, - "description": { - "title": "Description", - "type": "string" + "items": { + "anyOf": [ + { + "items": { + "properties": { + "error_type": { + "description": "For statistics", + "enum": [ + "SKIPPED", + "ACCESS", + "CREDENTIALS", + "CLIENT", + "INTERNAL" + ], + "title": "PolicyErrorType", + "type": "string" + }, + "policy": { + "title": "Policy", + "type": "string" + }, + "reason": { + "title": "Reason", + "type": "string" + }, + "region": { + "title": "Region", + "type": "string" + } + }, + "required": [ + "error_type", + "policy", + "reason", + "region" + ], + "title": "ErrorReportItem", + "type": "object" + }, + "type": "array" }, - "meta": { - "$ref": "#/definitions/AccessApplicationMeta" + { + "type": "null" } - }, - "required": [ - "application_id", - "customer_id", - "description", - "meta" - ] + ], + "title": "Items" } - } + }, + "required": [ + "items", + "data" + ], + "title": "ErrorsReportModel", + "type": "object" } }, - "UserPasswordResetPostModel": { + "EventDrivenRulesetDeleteModel": { "content_type": "application/json", "schema": { - "title": "UserPasswordResetPostModel", - "type": "object", "properties": { - "password": { - "title": "Password", + "cloud": { + "enum": [ + "AWS", + "AZURE", + "GCP", + "KUBERNETES" + ], + "title": "RuleDomain", "type": "string" }, - "username": { - "title": "Username", - "type": "string" + "version": { + "title": "Version", + "type": "number" } }, "required": [ - "password" + "cloud", + "version" ], - "additionalProperties": false + "title": "EventDrivenRulesetDeleteModel", + "type": "object" } }, - "RolePostModel": { + "EventDrivenRulesetPostModel": { "content_type": "application/json", "schema": { - "title": "RolePostModel", - "type": "object", "properties": { - "name": { - "title": "Name", + "cloud": { + "enum": [ + "AWS", + "AZURE", + "GCP", + "KUBERNETES" + ], + "title": "RuleDomain", "type": "string" }, - "policies": { - "title": "Policies", - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true - }, - "customer": { - "title": "Customer", - "type": "string" + "rules": { + "items": {}, + "title": "Rules", + "type": "array" }, - "expiration": { - "title": "Expiration", - "type": "string", - "format": "date-time" + "version": { + "title": "Version", + "type": "number" } }, "required": [ - "name", - "policies" + "cloud", + "version" ], - "additionalProperties": false + "title": "EventDrivenRulesetPostModel", + "type": "object" } }, - "ApplicationPostModel": { + "EventModel": { "content_type": "application/json", "schema": { - "title": "ApplicationPostModel", - "type": "object", "properties": { - "customer": { - "title": "Customer", - "type": "string" - }, - "description": { - "title": "Description", - "default": "Custodian application", - "type": "string" - }, - "cloud": { - "$ref": "#/definitions/RuleDomain" - }, - "access_application_id": { - "title": "Access Application Id", - "type": "string" - }, - "tenant_license_key": { - "title": "Tenant License Key", - "type": "string" - } - }, - "additionalProperties": false, - "definitions": { - "RuleDomain": { - "title": "RuleDomain", - "description": "An enumeration.", - "enum": [ - "AWS", - "AZURE", - "GCP", - "KUBERNETES" + "data": { + "description": "202 POST /event", + "properties": { + "received": { + "title": "Received", + "type": "integer" + }, + "saved": { + "title": "Saved", + "type": "integer" + } + }, + "required": [ + "received", + "saved" ], - "type": "string" + "title": "Event", + "type": "object" } - } + }, + "required": [ + "data" + ], + "title": "EventModel", + "type": "object" } }, - "UserCustomerDeleteModel": { + "EventPostModel": { "content_type": "application/json", "schema": { - "title": "UserCustomerDeleteModel", - "type": "object", "properties": { - "target_user": { - "title": "Target User", + "events": { + "items": { + "type": "object" + }, + "title": "Events", + "type": "array" + }, + "vendor": { + "enum": [ + "AWS", + "MAESTRO" + ], + "title": "Vendor", + "type": "string" + }, + "version": { + "default": "1.0.0", + "title": "Event type version", "type": "string" } }, - "additionalProperties": false + "required": [ + "vendor", + "events" + ], + "title": "EventPostModel", + "type": "object" } }, - "UserRolePostModel": { + "JobPostModel": { "content_type": "application/json", "schema": { - "title": "UserRolePostModel", - "type": "object", "properties": { - "role": { - "title": "Role", - "type": "string" + "credentials": { + "anyOf": [ + { + "properties": { + "AWS_ACCESS_KEY_ID": { + "title": "Aws Access Key Id", + "type": "string" + }, + "AWS_DEFAULT_REGION": { + "default": "us-east-1", + "title": "Aws Default Region", + "type": "string" + }, + "AWS_SECRET_ACCESS_KEY": { + "title": "Aws Secret Access Key", + "type": "string" + }, + "AWS_SESSION_TOKEN": { + "default": null, + "title": "Aws Session Token", + "type": "string" + } + }, + "required": [ + "AWS_ACCESS_KEY_ID", + "AWS_SECRET_ACCESS_KEY" + ], + "title": "AWSCredentials", + "type": "object" + }, + { + "properties": { + "AZURE_CLIENT_ID": { + "title": "Azure Client Id", + "type": "string" + }, + "AZURE_CLIENT_SECRET": { + "title": "Azure Client Secret", + "type": "string" + }, + "AZURE_SUBSCRIPTION_ID": { + "default": null, + "title": "Azure Subscription Id", + "type": "string" + }, + "AZURE_TENANT_ID": { + "title": "Azure Tenant Id", + "type": "string" + } + }, + "required": [ + "AZURE_TENANT_ID", + "AZURE_CLIENT_ID", + "AZURE_CLIENT_SECRET" + ], + "title": "AZURECredentials", + "type": "object" + }, + { + "properties": { + "auth_provider_x509_cert_url": { + "title": "Auth Provider X509 Cert Url", + "type": "string" + }, + "auth_uri": { + "title": "Auth Uri", + "type": "string" + }, + "client_email": { + "title": "Client Email", + "type": "string" + }, + "client_id": { + "title": "Client Id", + "type": "string" + }, + "client_x509_cert_url": { + "title": "Client X509 Cert Url", + "type": "string" + }, + "private_key": { + "title": "Private Key", + "type": "string" + }, + "private_key_id": { + "title": "Private Key Id", + "type": "string" + }, + "project_id": { + "title": "Project Id", + "type": "string" + }, + "token_uri": { + "title": "Token Uri", + "type": "string" + }, + "type": { + "title": "Type", + "type": "string" + } + }, + "required": [ + "type", + "project_id", + "private_key_id", + "private_key", + "client_email", + "client_id", + "auth_uri", + "token_uri", + "auth_provider_x509_cert_url", + "client_x509_cert_url" + ], + "title": "GOOGLECredentials1", + "type": "object" + }, + { + "properties": { + "access_token": { + "title": "Access Token", + "type": "string" + }, + "client_id": { + "title": "Client Id", + "type": "string" + }, + "client_secret": { + "title": "Client Secret", + "type": "string" + }, + "project_id": { + "title": "Project Id", + "type": "string" + }, + "refresh_token": { + "title": "Refresh Token", + "type": "string" + }, + "type": { + "title": "Type", + "type": "string" + } + }, + "required": [ + "type", + "access_token", + "refresh_token", + "client_id", + "client_secret", + "project_id" + ], + "title": "GOOGLECredentials2", + "type": "object" + }, + { + "properties": { + "access_token": { + "title": "Access Token", + "type": "string" + }, + "project_id": { + "title": "Project Id", + "type": "string" + } + }, + "required": [ + "access_token", + "project_id" + ], + "title": "GOOGLECredentials3", + "type": "object" + } + ], + "default": null, + "title": "Credentials" + }, + "rules_to_scan": { + "items": { + "type": "string" + }, + "title": "Rules To Scan", + "type": "array", + "uniqueItems": true + }, + "target_regions": { + "items": { + "type": "string" + }, + "title": "Target Regions", + "type": "array", + "uniqueItems": true + }, + "target_rulesets": { + "items": { + "type": "string" + }, + "title": "Target Rulesets", + "type": "array", + "uniqueItems": true }, - "target_user": { - "title": "Target User", + "tenant_name": { + "title": "Tenant Name", "type": "string" } }, "required": [ - "role" + "tenant_name" ], - "additionalProperties": false + "title": "JobPostModel", + "type": "object" } }, - "CustomerDTO": { + "JobResourcesReportModel": { "content_type": "application/json", "schema": { - "title": "CustomerDTO", - "type": "object", "properties": { - "trace_id": { - "title": "Trace Id", - "type": "string" + "data": { + "anyOf": [ + { + "properties": { + "content": { + "title": "Content", + "type": "object" + }, + "customer_name": { + "title": "Customer Name", + "type": "string" + }, + "format": { + "enum": [ + "json", + "xlsx" + ], + "title": "ReportFormat", + "type": "string" + }, + "job_id": { + "title": "Job Id", + "type": "string" + }, + "job_type": { + "enum": [ + "manual", + "reactive" + ], + "title": "JobType", + "type": "string" + }, + "tenant_name": { + "title": "Tenant Name", + "type": "string" + }, + "url": { + "title": "Url", + "type": "string" + } + }, + "required": [ + "format", + "tenant_name", + "customer_name" + ], + "title": "BaseReportJob", + "type": "object" + }, + { + "type": "null" + } + ] }, "items": { - "title": "Items", - "type": "array", "items": { - "$ref": "#/definitions/Customer" - } - } - }, - "required": [ - "trace_id", - "items" - ], - "definitions": { - "Customer": { - "title": "Customer", - "type": "object", - "properties": { - "name": { - "title": "Name", - "type": "string" - }, - "display_name": { - "title": "Display Name", - "type": "string" - }, - "admins": { - "title": "Admins", - "type": "array", - "items": { + "properties": { + "account_id": { + "title": "Account Id", + "type": "string" + }, + "data": { + "title": "Data", + "type": "object" + }, + "job_id": { + "title": "Job Id", + "type": "string" + }, + "last_found": { + "title": "Last Found", + "type": "number" + }, + "matched_by": { + "title": "Matched By", + "type": "object" + }, + "platform_id": { + "title": "Platform Id", + "type": "string" + }, + "region": { + "title": "Region", + "type": "string" + }, + "resource_type": { + "title": "Resource Type", + "type": "string" + }, + "type": { + "enum": [ + "manual", + "reactive" + ], + "title": "JobType", "type": "string" + }, + "violated_rules": { + "properties": { + "article": { + "title": "Article", + "type": "string" + }, + "description": { + "title": "Description", + "type": "string" + }, + "impact": { + "title": "Impact", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "remediation": { + "title": "Remediation", + "type": "string" + }, + "severity": { + "title": "Severity", + "type": "string" + } + }, + "required": [ + "name", + "description", + "severity" + ], + "title": "ViolatedRule", + "type": "object" } }, - "latest_login": { - "title": "Latest Login", - "type": "string" - }, - "activation_date": { - "title": "Activation Date", - "type": "string" - } + "required": [ + "data", + "last_found", + "matched_by", + "region", + "resource_type", + "violated_rules" + ], + "title": "ResourcesReportItem", + "type": "object" }, - "required": [ - "name", - "display_name", - "admins", - "latest_login", - "activation_date" - ] + "title": "Items", + "type": "array" } - } + }, + "required": [ + "items", + "data" + ], + "title": "JobResourcesReportModel", + "type": "object" } }, - "ApplicationDTO": { + "K8sJobPostModel": { "content_type": "application/json", "schema": { - "title": "ApplicationDTO", - "type": "object", + "description": "K8s platform job", "properties": { - "trace_id": { - "title": "Trace Id", + "platform_id": { + "title": "Platform Id", "type": "string" }, - "items": { - "title": "Items", - "type": "array", + "target_rulesets": { "items": { - "$ref": "#/definitions/Application" - } + "type": "string" + }, + "title": "Target Rulesets", + "type": "array", + "uniqueItems": true + }, + "token": { + "default": null, + "title": "Token", + "type": "string" } }, "required": [ - "trace_id", - "items" + "platform_id" ], - "definitions": { - "ApplicationMeta": { - "title": "ApplicationMeta", - "type": "object", - "properties": { - "awsAid": { - "title": "Awsaid", - "type": "string" - }, - "azureAid": { - "title": "Azureaid", - "type": "string" - }, - "googleAid": { - "title": "Googleaid", - "type": "string" - }, - "awsLk": { - "title": "Awslk", - "type": "string" - }, - "azureLk": { - "title": "Azurelk", - "type": "string" - }, - "googleLk": { - "title": "Googlelk", - "type": "string" - } - } + "title": "K8sJobPostModel", + "type": "object" + } + }, + "LicenseActivationPatchModel": { + "content_type": "application/json", + "schema": { + "properties": { + "add_tenants": { + "items": { + "type": "string" + }, + "title": "Add Tenants", + "type": "array", + "uniqueItems": true }, - "Application": { - "title": "Application", - "type": "object", - "properties": { - "application_id": { - "title": "Application Id", - "type": "string" - }, - "customer_id": { - "title": "Customer Id", - "type": "string" - }, - "description": { - "title": "Description", - "type": "string" - }, - "meta": { - "$ref": "#/definitions/ApplicationMeta" - } + "remove_tenants": { + "items": { + "type": "string" }, - "required": [ - "application_id", - "customer_id", - "description", - "meta" - ] + "title": "Remove Tenants", + "type": "array", + "uniqueItems": true } - } + }, + "title": "LicenseActivationPatchModel", + "type": "object" } }, - "MailSettingPostModel": { + "LicenseActivationPutModel": { "content_type": "application/json", "schema": { - "title": "MailSettingPostModel", - "type": "object", "properties": { - "username": { - "title": "Username", - "type": "string" + "all_tenants": { + "default": false, + "title": "All Tenants", + "type": "boolean" }, - "password": { - "title": "Password", + "clouds": { + "items": { + "enum": [ + "AWS", + "AZURE", + "GOOGLE", + "KUBERNETES" + ], + "type": "string" + }, + "title": "Clouds", + "type": "array", + "uniqueItems": true + }, + "exclude_tenants": { + "items": { + "type": "string" + }, + "title": "Exclude Tenants", + "type": "array", + "uniqueItems": true + }, + "tenant_names": { + "items": { + "type": "string" + }, + "title": "Tenant Names", + "type": "array", + "uniqueItems": true + } + }, + "title": "LicenseActivationPutModel", + "type": "object" + } + }, + "LicenseManagerClientSettingDeleteModel": { + "content_type": "application/json", + "schema": { + "properties": { + "key_id": { + "title": "Key Id", + "type": "string" + } + }, + "required": [ + "key_id" + ], + "title": "LicenseManagerClientSettingDeleteModel", + "type": "object" + } + }, + "LicenseManagerClientSettingPostModel": { + "content_type": "application/json", + "schema": { + "properties": { + "algorithm": { + "default": "ECC:p521_DSS_SHA:256", + "title": "LM algorithm", "type": "string" }, - "password_alias": { - "title": "Password Alias", + "b64_encoded": { + "title": "B64 Encoded", + "type": "boolean" + }, + "key_id": { + "title": "Key Id", "type": "string" }, - "port": { - "title": "Port", - "type": "integer" + "private_key": { + "title": "Private Key", + "type": "string" + } + }, + "required": [ + "key_id", + "private_key", + "b64_encoded" + ], + "title": "LicenseManagerClientSettingPostModel", + "type": "object" + } + }, + "LicenseManagerConfigSettingPostModel": { + "content_type": "application/json", + "schema": { + "properties": { + "api_version": { + "default": null, + "title": "Api Version", + "type": "string" }, "host": { "title": "Host", "type": "string" }, - "max_emails": { - "title": "Max Emails", - "default": 1, + "port": { + "default": null, + "title": "Port", "type": "integer" }, - "default_sender": { - "title": "Default Sender", + "protocol": { + "default": null, + "enum": [ + "HTTP", + "HTTPS", + "http", + "https" + ], + "title": "Protocol", "type": "string" }, - "use_tls": { - "title": "Use Tls", - "default": false, - "type": "boolean" + "stage": { + "default": null, + "title": "Stage", + "type": "string" } }, "required": [ - "username", - "password", - "password_alias", - "port", - "host", - "default_sender" + "host" ], - "additionalProperties": false + "title": "LicenseManagerConfigSettingPostModel", + "type": "object" } }, - "RulesetDeleteModel": { + "LicensePostModel": { "content_type": "application/json", "schema": { - "title": "RulesetDeleteModel", - "description": "Prepared event historically contains both some request meta and incoming\nbody on one level.\nYou can inherit your models from this one and use them directly as type\nannotations inside handlers (with validate_kwargs) decorator", - "type": "object", "properties": { - "httpMethod": { - "title": "Httpmethod", + "description": { + "default": "Custodian license", + "title": "Description", "type": "string" }, - "path": { - "title": "Path", + "license_key": { + "title": "License Key", "type": "string" - }, - "user_id": { - "title": "User Id", + } + }, + "required": [ + "license_key" + ], + "title": "LicensePostModel", + "type": "object" + } + }, + "MailSettingPostModel": { + "content_type": "application/json", + "schema": { + "properties": { + "default_sender": { + "title": "Default Sender", "type": "string" }, - "user_role": { - "title": "User Role", + "host": { + "title": "Host", "type": "string" }, - "user_customer": { - "title": "User Customer", - "type": "string" + "max_emails": { + "default": 1, + "title": "Max Emails", + "type": "integer" }, - "customer": { - "title": "Customer", + "password": { + "title": "Password", "type": "string" }, - "tenant": { - "title": "Tenant", + "password_alias": { + "title": "Password Alias", "type": "string" }, - "tenants": { - "title": "Tenants", - "type": "array", - "items": { - "type": "string" - } + "port": { + "title": "Port", + "type": "integer" }, - "name": { - "title": "Name", - "type": "string" + "use_tls": { + "default": false, + "title": "Use Tls", + "type": "boolean" }, - "version": { - "title": "Version", + "username": { + "title": "Username", "type": "string" } }, "required": [ - "httpMethod", - "path", - "user_id", - "user_role", - "user_customer", - "name", - "version" + "username", + "password", + "password_alias", + "port", + "host", + "default_sender" ], - "additionalProperties": false + "title": "MailSettingPostModel", + "type": "object" } }, - "LicenseManagerClientDTO": { + "MessageModel": { "content_type": "application/json", "schema": { - "title": "LicenseManagerClientDTO", - "type": "object", "properties": { - "trace_id": { - "title": "Trace Id", + "message": { + "title": "Message", "type": "string" - }, - "items": { - "title": "Items", - "type": "array", - "items": { - "$ref": "#/definitions/LicenseManagerClient" - } } }, "required": [ - "trace_id", - "items" + "message" ], - "definitions": { - "LicenseManagerClient": { - "title": "LicenseManagerClient", - "type": "object", - "properties": { - "kid": { - "title": "Kid", - "type": "string" - }, - "alg": { - "title": "Alg", - "type": "string" - }, - "public_key": { - "title": "Public Key", - "type": "string" - }, - "format": { - "title": "Format", - "type": "string" - } - }, - "required": [ - "kid", - "alg" - ] - } - } + "title": "MessageModel", + "type": "object" } }, - "RuleDTO": { + "MultipleBatchResultsModel": { "content_type": "application/json", "schema": { - "title": "RuleDTO", - "type": "object", "properties": { - "trace_id": { - "title": "Trace Id", - "type": "string" - }, "items": { - "title": "Items", - "type": "array", "items": { - "$ref": "#/definitions/Rule" - } - } - }, - "required": [ - "trace_id", - "items" - ], - "definitions": { - "Cloud": { - "title": "Cloud", - "description": "An enumeration.", - "enum": [ - "aws", - "azure", - "gcp", - "GCP", - "AWS", - "AZURE" - ], - "type": "string" - }, - "Rule": { - "title": "Rule", - "type": "object", - "properties": { - "id": { - "title": "Id", - "type": "string" - }, - "description": { - "title": "Description", - "type": "string" - }, - "service_section": { - "title": "Service Section", - "type": "string" - }, - "cloud": { - "$ref": "#/definitions/Cloud" + "properties": { + "cloud_identifier": { + "title": "Cloud Identifier", + "type": "string" + }, + "customer_name": { + "title": "Customer Name", + "type": "string" + }, + "id": { + "title": "Id", + "type": "string" + }, + "status": { + "description": "https://docs.aws.amazon.com/batch/latest/userguide/job_states.html", + "enum": [ + "SUBMITTED", + "PENDING", + "RUNNABLE", + "STARTING", + "RUNNING", + "FAILED", + "SUCCEEDED" + ], + "title": "JobState", + "type": "string" + }, + "stopped_at": { + "title": "Stopped At", + "type": "string" + }, + "submitted_at": { + "title": "Submitted At", + "type": "string" + }, + "tenant_name": { + "title": "Tenant Name", + "type": "string" + } }, - "customer": { - "title": "Customer", - "type": "string" - } + "required": [ + "cloud_identifier", + "customer_name", + "id", + "status", + "stopped_at", + "submitted_at", + "tenant_name" + ], + "title": "BatchResult", + "type": "object" }, - "required": [ - "id", - "description", - "service_section", - "cloud", - "customer" - ] + "title": "Items", + "type": "array" } - } + }, + "required": [ + "items" + ], + "title": "MultipleBatchResultsModel", + "type": "object" } }, - "ParentDeleteModel": { + "MultipleCredentialsModel": { "content_type": "application/json", "schema": { - "title": "ParentDeleteModel", - "type": "object", "properties": { - "parent_id": { - "title": "Parent Id", - "type": "string" + "items": { + "items": { + "properties": { + "credentials": { + "title": "Credentials", + "type": "object" + }, + "description": { + "title": "Description", + "type": "string" + }, + "has_secret": { + "title": "Has Secret", + "type": "boolean" + }, + "id": { + "title": "Id", + "type": "string" + }, + "type": { + "enum": [ + "AWS_CREDENTIALS", + "AWS_ROLE", + "AZURE_CREDENTIALS", + "AZURE_CERTIFICATE", + "GCP_SERVICE_ACCOUNT", + "GCP_COMPUTE_ACCOUNT" + ], + "title": "Type", + "type": "string" + } + }, + "required": [ + "id", + "type", + "description", + "has_secret", + "credentials" + ], + "title": "Credentials", + "type": "object" + }, + "title": "Items", + "type": "array" + }, + "next_token": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Next Token" } }, "required": [ - "parent_id" + "items", + "next_token" ], - "additionalProperties": false + "title": "MultipleCredentialsModel", + "type": "object" } }, - "LicenseManagerConfigDTO": { + "MultipleCustomersModel": { "content_type": "application/json", "schema": { - "title": "LicenseManagerConfigDTO", - "type": "object", "properties": { - "trace_id": { - "title": "Trace Id", - "type": "string" - }, "items": { - "title": "Items", - "type": "array", "items": { - "$ref": "#/definitions/LicenseManagerConfig" - } + "properties": { + "admins": { + "items": { + "type": "string" + }, + "title": "Admins", + "type": "array" + }, + "display_name": { + "title": "Display Name", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + } + }, + "required": [ + "name", + "display_name", + "admins" + ], + "title": "Customer", + "type": "object" + }, + "title": "Items", + "type": "array" } }, "required": [ - "trace_id", "items" ], - "definitions": { - "LicenseManagerConfig": { - "title": "LicenseManagerConfig", - "type": "object", + "title": "MultipleCustomersModel", + "type": "object" + } + }, + "MultipleDefectDojoModel": { + "content_type": "application/json", + "schema": { + "properties": { + "items": { "properties": { + "description": { + "title": "Description", + "type": "string" + }, "host": { "title": "Host", "type": "string" }, + "id": { + "title": "Id", + "type": "string" + }, "port": { "title": "Port", "type": "integer" }, - "version": { - "title": "Version", - "type": "number" + "protocol": { + "enum": [ + "HTTP", + "HTTPS" + ], + "title": "Protocol", + "type": "string" + }, + "stage": { + "title": "Stage", + "type": "string" } }, "required": [ + "id", + "description", "host", "port", - "version" - ] + "stage", + "protocol" + ], + "title": "DefectDojo", + "type": "object" } - } + }, + "required": [ + "items" + ], + "title": "MultipleDefectDojoModel", + "type": "object" } }, - "AZURECredentials": { + "MultipleDefectDojoPushResult": { "content_type": "application/json", "schema": { - "title": "AZURECredentials", - "type": "object", "properties": { - "AZURE_TENANT_ID": { - "title": "Azure Tenant Id", - "type": "string" - }, - "AZURE_CLIENT_ID": { - "title": "Azure Client Id", - "type": "string" - }, - "AZURE_CLIENT_SECRET": { - "title": "Azure Client Secret", - "type": "string" - }, - "AZURE_SUBSCRIPTION_ID": { - "title": "Azure Subscription Id", - "type": "string" + "items": { + "items": { + "properties": { + "attachment": { + "anyOf": [ + { + "enum": [ + "json", + "xlsx", + "csv" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Attachment" + }, + "dojo_integration_id": { + "title": "Dojo Integration Id", + "type": "string" + }, + "engagement_name": { + "title": "Engagement Name", + "type": "string" + }, + "error": { + "title": "Error", + "type": "string" + }, + "job_id": { + "title": "Job Id", + "type": "string" + }, + "platform_id": { + "title": "Platform Id", + "type": "string" + }, + "product_name": { + "title": "Product Name", + "type": "string" + }, + "product_type_name": { + "title": "Product Type Name", + "type": "string" + }, + "scan_type": { + "enum": [ + "Generic Findings Import", + "Cloud Custodian Scan" + ], + "title": "Scan Type", + "type": "string" + }, + "success": { + "title": "Success", + "type": "boolean" + }, + "tenant_name": { + "title": "Tenant Name", + "type": "string" + }, + "test_title": { + "title": "Test Title", + "type": "string" + } + }, + "required": [ + "job_id", + "scan_type", + "product_type_name", + "product_name", + "engagement_name", + "test_title", + "tenant_name", + "dojo_integration_id", + "success", + "attachment" + ], + "title": "DojoPushResult", + "type": "object" + }, + "title": "Items", + "type": "array" } }, "required": [ - "AZURE_TENANT_ID", - "AZURE_CLIENT_ID", - "AZURE_CLIENT_SECRET" + "items" ], - "additionalProperties": false + "title": "MultipleDefectDojoPushResult", + "type": "object" } }, - "DojoApplicationPatchModel": { + "MultipleHealthChecksModel": { "content_type": "application/json", "schema": { - "title": "DojoApplicationPatchModel", - "type": "object", "properties": { - "application_id": { - "title": "Application Id", - "type": "string" - }, - "customer": { - "title": "Customer", - "type": "string" - }, - "description": { - "title": "Description", - "type": "string" - }, - "url": { - "title": "Url", - "minLength": 1, - "maxLength": 65536, - "format": "uri", - "type": "string" - }, - "api_key": { - "title": "Api Key", - "type": "string" + "items": { + "items": { + "properties": { + "details": { + "title": "Details", + "type": "object" + }, + "id": { + "title": "Id", + "type": "string" + }, + "impact": { + "title": "Impact", + "type": "string" + }, + "remediation": { + "title": "Remediation", + "type": "string" + }, + "status": { + "enum": [ + "OK", + "UNKNOWN", + "NOT_OK" + ], + "title": "HealthCheckStatus", + "type": "string" + } + }, + "required": [ + "id", + "status", + "details" + ], + "title": "HealthCheck", + "type": "object" + }, + "title": "Items", + "type": "array" } }, "required": [ - "application_id" + "items" ], - "additionalProperties": false + "title": "MultipleHealthChecksModel", + "type": "object" } }, - "MailSettingDTO": { + "MultipleJobReportModel": { "content_type": "application/json", "schema": { - "title": "MailSettingDTO", - "type": "object", "properties": { - "trace_id": { - "title": "Trace Id", - "type": "string" - }, "items": { - "title": "Items", - "type": "array", "items": { - "$ref": "#/definitions/MailSetting" - } + "properties": { + "content": { + "title": "Content", + "type": "object" + }, + "customer_name": { + "title": "Customer Name", + "type": "string" + }, + "format": { + "enum": [ + "json", + "xlsx" + ], + "title": "ReportFormat", + "type": "string" + }, + "job_id": { + "title": "Job Id", + "type": "string" + }, + "job_type": { + "enum": [ + "manual", + "reactive" + ], + "title": "JobType", + "type": "string" + }, + "tenant_name": { + "title": "Tenant Name", + "type": "string" + }, + "url": { + "title": "Url", + "type": "string" + } + }, + "required": [ + "format", + "tenant_name", + "customer_name" + ], + "title": "BaseReportJob", + "type": "object" + }, + "title": "Items", + "type": "array" } }, "required": [ - "trace_id", "items" ], - "definitions": { - "MailSetting": { - "title": "MailSetting", - "type": "object", - "properties": { - "username": { - "title": "Username", - "type": "string" - }, - "password": { - "title": "Password", - "type": "string" - }, - "default_sender": { - "title": "Default Sender", - "type": "string" - }, - "host": { - "title": "Host", - "type": "string" - }, - "port": { - "title": "Port", - "type": "string" - }, - "max_emails": { - "title": "Max Emails", - "type": "integer" - }, - "use_tls": { - "title": "Use Tls", - "type": "boolean" - } - }, - "required": [ - "username", - "password", - "default_sender", - "host", - "port", - "max_emails", - "use_tls" - ] - } - } + "title": "MultipleJobReportModel", + "type": "object" } }, - "LicenseManagerClientSettingDeleteModel": { + "MultipleJobsModel": { "content_type": "application/json", "schema": { - "title": "LicenseManagerClientSettingDeleteModel", - "type": "object", + "description": "200 GET /jobs", "properties": { - "key_id": { - "title": "Key Id", - "type": "string" + "items": { + "items": { + "properties": { + "created_at": { + "format": "date-time", + "title": "Created At", + "type": "string" + }, + "customer_name": { + "title": "Customer Name", + "type": "string" + }, + "id": { + "title": "Id", + "type": "string" + }, + "regions": { + "items": { + "type": "string" + }, + "title": "Regions", + "type": "array" + }, + "rulesets": { + "items": { + "type": "string" + }, + "title": "Rulesets", + "type": "array" + }, + "scheduled_rule_name": { + "title": "Scheduled Rule Name", + "type": "string" + }, + "started_at": { + "format": "date-time", + "title": "Started At", + "type": "string" + }, + "status": { + "description": "https://docs.aws.amazon.com/batch/latest/userguide/job_states.html", + "enum": [ + "SUBMITTED", + "PENDING", + "RUNNABLE", + "STARTING", + "RUNNING", + "FAILED", + "SUCCEEDED" + ], + "title": "JobState", + "type": "string" + }, + "stopped_at": { + "format": "date-time", + "title": "Stopped At", + "type": "string" + }, + "submitted_at": { + "title": "Submitted At", + "type": "string" + }, + "tenant_name": { + "title": "Tenant Name", + "type": "string" + } + }, + "required": [ + "customer_name", + "id", + "regions", + "rulesets", + "status", + "submitted_at", + "tenant_name" + ], + "title": "Job", + "type": "object" + }, + "title": "Items", + "type": "array" + }, + "next_token": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Next Token" } }, "required": [ - "key_id" + "items", + "next_token" ], - "additionalProperties": false + "title": "MultipleJobsModel", + "type": "object" } }, - "ScheduledJobPostModel": { + "MultipleK8SPlatformsModel": { "content_type": "application/json", "schema": { - "title": "ScheduledJobPostModel", - "type": "object", "properties": { - "schedule": { - "title": "Schedule", - "type": "string" - }, - "tenant_name": { - "title": "Tenant Name", - "type": "string" - }, - "customer": { - "title": "Customer", - "type": "string" - }, - "name": { - "title": "Name", - "type": "string" - }, - "target_rulesets": { - "title": "Target Rulesets", - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true - }, - "target_regions": { - "title": "Target Regions", - "type": "array", + "items": { "items": { - "type": "string" + "properties": { + "customer": { + "title": "Customer", + "type": "string" + }, + "description": { + "title": "Description", + "type": "string" + }, + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "region": { + "title": "Region", + "type": "string" + }, + "tenant_name": { + "title": "Tenant Name", + "type": "string" + }, + "type": { + "enum": [ + "SELF_MANAGED", + "EKS", + "AKS", + "GKS" + ], + "title": "PlatformType", + "type": "string" + } + }, + "required": [ + "customer", + "description", + "id", + "name", + "tenant_name", + "type" + ], + "title": "K8sPlatform", + "type": "object" }, - "uniqueItems": true + "title": "Items", + "type": "array" } }, "required": [ - "schedule" + "items" ], - "additionalProperties": false + "title": "MultipleK8SPlatformsModel", + "type": "object" } }, - "BackUpResponseDTO": { + "MultipleLicensesModel": { "content_type": "application/json", "schema": { - "title": "BackUpResponseDTO", - "type": "object", "properties": { - "trace_id": { - "title": "Trace Id", - "type": "string" - }, "items": { - "title": "Items", - "type": "array", "items": { - "$ref": "#/definitions/BackUpResponse" - } + "properties": { + "allowance": { + "properties": { + "balance_exhaustion_model": { + "enum": [ + "collective", + "independent" + ], + "title": "Balance Exhaustion Model", + "type": "string" + }, + "job_balance": { + "title": "Job Balance", + "type": "integer" + }, + "time_range": { + "enum": [ + "DAY", + "WEEK", + "MONTH" + ], + "title": "Time Range", + "type": "string" + } + }, + "required": [ + "balance_exhaustion_model", + "job_balance", + "time_range" + ], + "title": "LicenseAllowance", + "type": "object" + }, + "event_driven": { + "properties": { + "active": { + "title": "Active", + "type": "boolean" + } + }, + "required": [ + "active" + ], + "title": "LicenseEventDriven", + "type": "object" + }, + "expiration": { + "format": "date-time", + "title": "Expiration", + "type": "string" + }, + "latest_sync": { + "format": "date-time", + "title": "Latest Sync", + "type": "string" + }, + "license_key": { + "title": "License Key", + "type": "string" + }, + "ruleset_ids": { + "items": { + "type": "string" + }, + "title": "Ruleset Ids", + "type": "array" + } + }, + "required": [ + "allowance", + "event_driven", + "expiration", + "latest_sync", + "license_key", + "ruleset_ids" + ], + "title": "License", + "type": "object" + }, + "title": "Items", + "type": "array" } }, "required": [ - "trace_id", "items" ], - "definitions": { - "BackUpResponse": { - "title": "BackUpResponse", - "type": "object", - "properties": { - "title": { - "title": "Title", - "type": "string" - }, - "commit_url": { - "title": "Commit Url", - "type": "string" - }, - "stats": { - "title": "Stats", - "type": "string" - }, - "git_files_created": { - "title": "Git Files Created", - "type": "string" - }, - "ssm_params_created": { - "title": "Ssm Params Created", - "type": "string" - } - }, - "required": [ - "title", - "commit_url", - "stats", - "git_files_created", - "ssm_params_created" - ] - } - } + "title": "MultipleLicensesModel", + "type": "object" } }, - "GOOGLECredentials2": { + "MultipleMetricsStatusesModel": { "content_type": "application/json", "schema": { - "title": "GOOGLECredentials2", - "type": "object", "properties": { - "type": { - "title": "Type", - "type": "string" - }, - "access_token": { - "title": "Access Token", - "type": "string" - }, - "refresh_token": { - "title": "Refresh Token", - "type": "string" - }, - "client_id": { - "title": "Client Id", - "type": "string" - }, - "client_secret": { - "title": "Client Secret", - "type": "string" - }, - "project_id": { - "title": "Project Id", - "type": "string" + "items": { + "items": { + "properties": { + "started_at": { + "format": "date-time", + "title": "Started At", + "type": "string" + }, + "state": { + "title": "State", + "type": "string" + } + }, + "required": [ + "started_at", + "state" + ], + "title": "MetricsStatus", + "type": "object" + }, + "title": "Items", + "type": "array" } }, "required": [ - "type", - "access_token", - "refresh_token", - "client_id", - "client_secret", - "project_id" + "items" ], - "additionalProperties": false + "title": "MultipleMetricsStatusesModel", + "type": "object" } }, - "PlatformK8sEksPost": { + "MultiplePoliciesModel": { "content_type": "application/json", "schema": { - "title": "PlatformK8sEksPost", - "description": "Prepared event historically contains both some request meta and incoming\nbody on one level.\nYou can inherit your models from this one and use them directly as type\nannotations inside handlers (with validate_kwargs) decorator", - "type": "object", "properties": { - "httpMethod": { - "title": "Httpmethod", - "type": "string" - }, - "path": { - "title": "Path", - "type": "string" - }, - "user_id": { - "title": "User Id", - "type": "string" - }, - "user_role": { - "title": "User Role", - "type": "string" - }, - "user_customer": { - "title": "User Customer", - "type": "string" - }, - "customer": { - "title": "Customer", - "type": "string" - }, - "tenant": { - "title": "Tenant", - "type": "string" - }, - "tenants": { - "title": "Tenants", - "type": "array", + "items": { "items": { - "type": "string" - } - }, - "tenant_name": { - "title": "Tenant Name", - "type": "string" - }, - "name": { - "title": "Name", - "type": "string" - }, - "region": { - "$ref": "#/definitions/AWSRegion" - }, - "application_id": { - "title": "Application Id", - "type": "string" - }, - "description": { - "title": "Description", - "type": "string" + "properties": { + "customer": { + "title": "Customer", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Description" + }, + "effect": { + "enum": [ + "allow", + "deny" + ], + "title": "PolicyEffect", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "permissions": { + "items": { + "type": "string" + }, + "title": "Permissions", + "type": "array" + }, + "tenants": { + "items": { + "type": "string" + }, + "title": "Tenants", + "type": "array" + } + }, + "required": [ + "customer", + "name", + "permissions", + "effect", + "description", + "tenants" + ], + "title": "Policy", + "type": "object" + }, + "title": "Items", + "type": "array" } }, "required": [ - "httpMethod", - "path", - "user_id", - "user_role", - "user_customer", - "tenant_name", - "name", - "region", - "application_id" + "items" ], - "additionalProperties": false, - "definitions": { - "AWSRegion": { - "title": "AWSRegion", - "description": "An enumeration.", - "enum": [ - "eu-west-3", - "ap-southeast-1", - "us-west-2", - "ap-northeast-2", - "ap-northeast-3", - "eu-west-2", - "ca-central-1", - "sa-east-1", - "eu-north-1", - "us-east-1", - "ap-northeast-1", - "us-east-2", - "eu-west-1", - "eu-central-1", - "us-west-1", - "ap-southeast-2", - "ap-southeast-3", - "ap-south-1" - ], - "type": "string" - } - } + "title": "MultiplePoliciesModel", + "type": "object" } }, - "CredentialsManagerDTO": { + "MultipleReportStatusModel": { "content_type": "application/json", "schema": { - "title": "CredentialsManagerDTO", - "type": "object", "properties": { - "trace_id": { - "title": "Trace Id", - "type": "string" - }, "items": { - "title": "Items", - "type": "array", "items": { - "$ref": "#/definitions/CredentialsManager" - } + "properties": { + "attempt": { + "title": "Attempt", + "type": "integer" + }, + "customer_name": { + "title": "Customer Name", + "type": "string" + }, + "id": { + "title": "Id", + "type": "string" + }, + "level": { + "title": "Level", + "type": "string" + }, + "reason": { + "title": "Reason", + "type": "string" + }, + "status": { + "title": "Status", + "type": "string" + }, + "tenant": { + "title": "Tenant", + "type": "string" + }, + "triggered_at": { + "title": "Triggered At", + "type": "string" + }, + "type": { + "title": "Type", + "type": "string" + }, + "user": { + "title": "User", + "type": "string" + } + }, + "required": [ + "id", + "triggered_at", + "attempt", + "customer_name", + "level", + "status", + "type" + ], + "title": "ReportStatus", + "type": "object" + }, + "title": "Items", + "type": "array" } }, "required": [ - "trace_id", "items" ], - "definitions": { - "Cloud": { - "title": "Cloud", - "description": "An enumeration.", - "enum": [ - "aws", - "azure", - "gcp", - "GCP", - "AWS", - "AZURE" - ], - "type": "string" - }, - "CredentialsManager": { - "title": "CredentialsManager", - "type": "object", - "properties": { - "cloud": { - "$ref": "#/definitions/Cloud" - }, - "cloud_identifier": { - "title": "Cloud Identifier", - "type": "string" - }, - "credentials_key": { - "title": "Credentials Key", - "type": "string" - }, - "expiration": { - "title": "Expiration", - "type": "string" - }, - "trusted_role_arn": { - "title": "Trusted Role Arn", - "type": "string" + "title": "MultipleReportStatusModel", + "type": "object" + } + }, + "MultipleRoleModel": { + "content_type": "application/json", + "schema": { + "properties": { + "items": { + "items": { + "properties": { + "customer": { + "title": "Customer", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Description" + }, + "expiration": { + "format": "date-time", + "title": "Expiration", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "policies": { + "items": { + "type": "string" + }, + "title": "Policies", + "type": "array" + } }, - "enabled": { - "title": "Enabled", - "type": "boolean" - } + "required": [ + "customer", + "name", + "expiration", + "policies", + "description" + ], + "title": "Role", + "type": "object" }, - "required": [ - "cloud", - "cloud_identifier" - ] + "title": "Items", + "type": "array" } - } + }, + "required": [ + "items" + ], + "title": "MultipleRoleModel", + "type": "object" } }, - "HealthCheckDTO": { + "MultipleRuleMetaUpdateModel": { "content_type": "application/json", "schema": { - "title": "HealthCheckDTO", - "type": "object", "properties": { - "trace_id": { - "title": "Trace Id", - "type": "string" - }, "items": { - "title": "Items", - "type": "array", "items": { - "$ref": "#/definitions/HealthCheck" - } + "properties": { + "customer": { + "title": "Customer", + "type": "string" + }, + "git_project_id": { + "title": "Git Project Id", + "type": "string" + }, + "rule_source_id": { + "title": "Rule Source Id", + "type": "string" + }, + "status": { + "title": "Status", + "type": "string" + } + }, + "required": [ + "customer", + "git_project_id", + "rule_source_id", + "status" + ], + "title": "RuleMetaUpdate", + "type": "object" + }, + "title": "Items", + "type": "array" } }, "required": [ - "trace_id", "items" ], - "definitions": { - "HealthCheck": { - "title": "HealthCheck", - "type": "object", - "properties": { - "API": { - "title": "Api", - "type": "string" - }, - "MinIO": { - "title": "Minio", - "type": "string" - }, - "Vault": { - "title": "Vault", - "type": "string" + "title": "MultipleRuleMetaUpdateModel", + "type": "object" + } + }, + "MultipleRuleSourceModel": { + "content_type": "application/json", + "schema": { + "properties": { + "items": { + "items": { + "properties": { + "customer": { + "title": "Customer", + "type": "string" + }, + "description": { + "title": "Description", + "type": "string" + }, + "git_project_id": { + "title": "Git Project Id", + "type": "string" + }, + "git_ref": { + "title": "Git Ref", + "type": "string" + }, + "git_rules_prefix": { + "title": "Git Rules Prefix", + "type": "string" + }, + "git_url": { + "title": "Git Url", + "type": "string" + }, + "has_secret": { + "title": "Has Secret", + "type": "boolean" + }, + "id": { + "title": "Id", + "type": "string" + }, + "latest_sync": { + "properties": { + "current_status": { + "enum": [ + "SYNCED", + "SYNCING", + "SYNCING_FAILED" + ], + "title": "Current Status", + "type": "string" + }, + "sync_date": { + "format": "date-time", + "title": "Sync Date", + "type": "string" + } + }, + "required": [ + "current_status", + "sync_date" + ], + "title": "RuleSourceLatestSync", + "type": "object" + }, + "type": { + "enum": [ + "GITHUB", + "GITLAB" + ], + "title": "RuleSourceType", + "type": "string" + } }, - "MongoDB": { - "title": "Mongodb", - "type": "string" - } + "required": [ + "id", + "customer", + "git_project_id", + "git_url", + "git_ref", + "git_rules_prefix", + "description", + "has_secret", + "type" + ], + "title": "RuleSource", + "type": "object" }, - "required": [ - "API", - "MinIO", - "Vault", - "MongoDB" - ] + "title": "Items", + "type": "array" } - } + }, + "required": [ + "items" + ], + "title": "MultipleRuleSourceModel", + "type": "object" } }, - "GOOGLECredentials1": { + "MultipleRulesModel": { "content_type": "application/json", "schema": { - "title": "GOOGLECredentials1", - "type": "object", "properties": { - "type": { - "title": "Type", - "type": "string" - }, - "project_id": { - "title": "Project Id", - "type": "string" - }, - "private_key_id": { - "title": "Private Key Id", - "type": "string" - }, - "private_key": { - "title": "Private Key", - "type": "string" - }, - "client_email": { - "title": "Client Email", - "type": "string" - }, - "client_id": { - "title": "Client Id", - "type": "string" - }, - "auth_uri": { - "title": "Auth Uri", - "type": "string" - }, - "token_uri": { - "title": "Token Uri", - "type": "string" - }, - "auth_provider_x509_cert_url": { - "title": "Auth Provider X509 Cert Url", - "type": "string" + "items": { + "items": { + "properties": { + "branch": { + "title": "Branch", + "type": "string" + }, + "cloud": { + "enum": [ + "AWS", + "AZURE", + "GCP", + "KUBERNETES" + ], + "title": "RuleDomain", + "type": "string" + }, + "customer": { + "title": "Customer", + "type": "string" + }, + "description": { + "title": "Description", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "project": { + "title": "Project", + "type": "string" + }, + "resource": { + "title": "Resource", + "type": "string" + } + }, + "required": [ + "description", + "name", + "cloud", + "branch", + "customer", + "project", + "resource" + ], + "title": "Rule", + "type": "object" + }, + "title": "Items", + "type": "array" }, - "client_x509_cert_url": { - "title": "Client X509 Cert Url", - "type": "string" + "next_token": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Next Token" } }, "required": [ - "type", - "project_id", - "private_key_id", - "private_key", - "client_email", - "client_id", - "auth_uri", - "token_uri", - "auth_provider_x509_cert_url", - "client_x509_cert_url" - ] + "items", + "next_token" + ], + "title": "MultipleRulesModel", + "type": "object" } }, - "DojoApplicationDeleteModel": { + "MultipleRulesetsModel": { "content_type": "application/json", "schema": { - "title": "DojoApplicationDeleteModel", - "type": "object", "properties": { - "application_id": { - "title": "Application Id", - "type": "string" - }, - "customer": { - "title": "Customer", - "type": "string" + "items": { + "items": { + "properties": { + "active": { + "title": "Active", + "type": "boolean" + }, + "cloud": { + "enum": [ + "AWS", + "AZURE", + "GCP", + "KUBERNETES" + ], + "title": "RuleDomain", + "type": "string" + }, + "customer": { + "title": "Customer", + "type": "string" + }, + "last_update_time": { + "format": "date-time", + "title": "Last Update Time", + "type": "string" + }, + "license_keys": { + "items": { + "type": "string" + }, + "title": "License Keys", + "type": "array" + }, + "license_manager_id": { + "title": "License Manager Id", + "type": "string" + }, + "licensed": { + "title": "Licensed", + "type": "boolean" + }, + "name": { + "title": "Name", + "type": "string" + }, + "rules": { + "items": { + "type": "string" + }, + "title": "Rules", + "type": "array" + }, + "rules_number": { + "title": "Rules Number", + "type": "integer" + }, + "version": { + "title": "Version", + "type": "string" + } + }, + "required": [ + "active", + "cloud", + "customer", + "last_update_time", + "license_keys", + "licensed", + "name", + "rules_number", + "version" + ], + "title": "Ruleset", + "type": "object" + }, + "title": "Items", + "type": "array" } }, "required": [ - "application_id" + "items" ], - "additionalProperties": false + "title": "MultipleRulesetsModel", + "type": "object" } }, - "BaseMessageResponseModel": { + "MultipleScheduledJobsModel": { "content_type": "application/json", "schema": { - "title": "BaseMessageResponseModel", - "type": "object", + "description": "200 GET /jobs", "properties": { - "trace_id": { - "title": "Trace Id", - "type": "string" - }, - "message": { - "title": "Message", - "type": "string" + "items": { + "items": { + "properties": { + "creation_date": { + "format": "date-time", + "title": "Creation Date", + "type": "string" + }, + "customer_name": { + "title": "Customer Name", + "type": "string" + }, + "enabled": { + "title": "Enabled", + "type": "boolean" + }, + "last_execution_time": { + "format": "date-time", + "title": "Last Execution Time", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "scan_regions": { + "items": { + "type": "string" + }, + "title": "Scan Regions", + "type": "array" + }, + "scan_rulesets": { + "items": { + "type": "string" + }, + "title": "Scan Rulesets", + "type": "array" + }, + "schedule": { + "title": "Schedule", + "type": "string" + }, + "tenant_name": { + "title": "Tenant Name", + "type": "string" + } + }, + "required": [ + "name", + "customer_name", + "tenant_name", + "creation_date", + "enabled", + "schedule", + "scan_regions", + "scan_rulesets" + ], + "title": "ScheduledJob", + "type": "object" + }, + "title": "Items", + "type": "array" } }, "required": [ - "trace_id", - "message" - ] + "items" + ], + "title": "MultipleScheduledJobsModel", + "type": "object" } }, - "UserCustomerPatchModel": { + "MultipleTenantsModel": { "content_type": "application/json", "schema": { - "title": "UserCustomerPatchModel", - "type": "object", "properties": { - "customer": { - "title": "Customer", - "type": "string" - }, - "target_user": { - "title": "Target User", - "type": "string" + "items": { + "items": { + "properties": { + "account_id": { + "title": "Account Id", + "type": "string" + }, + "activation_date": { + "format": "date-time", + "title": "Activation Date", + "type": "string" + }, + "customer_name": { + "title": "Customer Name", + "type": "string" + }, + "is_active": { + "title": "Is Active", + "type": "boolean" + }, + "name": { + "title": "Name", + "type": "string" + }, + "regions": { + "items": { + "type": "string" + }, + "title": "Regions", + "type": "array" + } + }, + "required": [ + "account_id", + "activation_date", + "customer_name", + "is_active", + "name", + "regions" + ], + "title": "Tenant", + "type": "object" + }, + "title": "Items", + "type": "array" } }, "required": [ - "customer" + "items" ], - "additionalProperties": false + "title": "MultipleTenantsModel", + "type": "object" } }, - "RulesetPostModel": { + "MultipleUsersModel": { "content_type": "application/json", "schema": { - "title": "RulesetPostModel", - "description": "Prepared event historically contains both some request meta and incoming\nbody on one level.\nYou can inherit your models from this one and use them directly as type\nannotations inside handlers (with validate_kwargs) decorator", - "type": "object", "properties": { - "httpMethod": { - "title": "Httpmethod", - "type": "string" + "items": { + "items": { + "properties": { + "created_at": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Created At" + }, + "customer": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Customer" + }, + "latest_login": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Latest Login" + }, + "role": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Role" + }, + "username": { + "title": "Username", + "type": "string" + } + }, + "required": [ + "username", + "customer", + "role", + "latest_login", + "created_at" + ], + "title": "User", + "type": "object" + }, + "title": "Items", + "type": "array" }, - "path": { - "title": "Path", - "type": "string" + "next_token": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Next Token" + } + }, + "required": [ + "items", + "next_token" + ], + "title": "MultipleUsersModel", + "type": "object" + } + }, + "OperationalGetReportModel": { + "content_type": "application/json", + "schema": { + "properties": { + "receivers": { + "items": { + "type": "string" + }, + "title": "Receivers", + "type": "array", + "uniqueItems": true }, - "user_id": { - "title": "User Id", - "type": "string" + "tenant_names": { + "items": { + "type": "string" + }, + "title": "Tenant Names", + "type": "array", + "uniqueItems": true }, - "user_role": { - "title": "User Role", + "types": { + "items": { + "enum": [ + "OVERVIEW", + "RESOURCES", + "COMPLIANCE", + "RULE", + "ATTACK_VECTOR", + "FINOPS", + "KUBERNETES" + ], + "type": "string" + }, + "title": "Types", + "type": "array", + "uniqueItems": true + } + }, + "required": [ + "tenant_names" + ], + "title": "OperationalGetReportModel", + "type": "object" + } + }, + "PlatformK8SPostModel": { + "content_type": "application/json", + "schema": { + "properties": { + "certificate_authority": { + "default": null, + "title": "Certificate Authority", "type": "string" }, - "user_customer": { - "title": "User Customer", + "description": { + "title": "Description", "type": "string" }, - "customer": { - "title": "Customer", + "endpoint": { + "default": null, + "format": "uri", + "maxLength": 2083, + "minLength": 1, + "title": "Endpoint", "type": "string" }, - "tenant": { - "title": "Tenant", + "name": { + "title": "Name", "type": "string" }, - "tenants": { - "title": "Tenants", - "type": "array", - "items": { - "type": "string" - } + "region": { + "allOf": [ + { + "enum": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ap-southeast-3", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + "asia", + "asiapacific", + "australia", + "australiacentral", + "australiacentral2", + "australiaeast", + "australiasoutheast", + "brazil", + "brazilsouth", + "brazilsoutheast", + "canada", + "canadacentral", + "canadaeast", + "centralindia", + "centralus", + "centraluseuap", + "centralusstage", + "eastasia", + "eastasiastage", + "eastus", + "eastus2", + "eastus2euap", + "eastus2stage", + "eastusstage", + "eastusstg", + "europe", + "france", + "francecentral", + "francesouth", + "germany", + "germanynorth", + "germanywestcentral", + "global", + "india", + "japan", + "japaneast", + "japanwest", + "jioindiacentral", + "jioindiawest", + "korea", + "koreacentral", + "koreasouth", + "northcentralus", + "northcentralusstage", + "northeurope", + "norway", + "norwayeast", + "norwaywest", + "qatarcentral", + "singapore", + "southafrica", + "southafricanorth", + "southafricawest", + "southcentralus", + "southcentralusstage", + "southcentralusstg", + "southeastasia", + "southeastasiastage", + "southindia", + "swedencentral", + "switzerland", + "switzerlandnorth", + "switzerlandwest", + "uae", + "uaecentral", + "uaenorth", + "uk", + "uksouth", + "ukwest", + "unitedstates", + "unitedstateseuap", + "westcentralus", + "westeurope", + "westindia", + "westus", + "westus2", + "westus2stage", + "westus3", + "westusstage", + "asia-east1", + "asia-east2", + "asia-northeast1", + "asia-northeast2", + "asia-northeast3", + "asia-south1", + "asia-southeast1", + "asia-southeast2", + "australia-southeast1", + "europe-north1", + "europe-west1", + "europe-west2", + "europe-west3", + "europe-west4", + "europe-west6", + "northamerica-northeast1", + "southamerica-east1", + "us-central1", + "us-east1", + "us-east4", + "us-west1", + "us-west2", + "us-west3", + "us-west4" + ], + "title": "AllRegions", + "type": "string" + } + ], + "default": null }, - "name": { - "title": "Name", + "tenant_name": { + "title": "Tenant Name", "type": "string" }, - "version": { - "title": "Version", + "token": { + "default": null, + "title": "Token", "type": "string" }, - "cloud": { - "$ref": "#/definitions/RuleDomain" + "type": { + "enum": [ + "SELF_MANAGED", + "EKS", + "AKS", + "GKS" + ], + "title": "PlatformType", + "type": "string" + } + }, + "required": [ + "tenant_name", + "name", + "type", + "description" + ], + "title": "PlatformK8SPostModel", + "type": "object" + } + }, + "PolicyPatchModel": { + "content_type": "application/json", + "schema": { + "properties": { + "description": { + "default": null, + "title": "Description", + "type": "string" }, - "active": { - "title": "Active", - "default": true, - "type": "boolean" + "effect": { + "allOf": [ + { + "enum": [ + "allow", + "deny" + ], + "title": "PolicyEffect", + "type": "string" + } + ], + "default": null }, - "tenant_allowance": { - "title": "Tenant Allowance", - "default": [], + "permissions_to_attach": { + "items": { + "enum": [ + "report:push_report_to_dojo", + "report:push_to_dojo_batch", + "report:post_operational", + "report:post_project", + "report:post_department", + "report:post_clevel", + "report:get_diagnostic", + "report:get_status", + "report:get_digest", + "report:get_digest_batch", + "report:get_details", + "report:get_details_batch", + "report:get_findings", + "report:get_findings_batch", + "report:get_job_compliance", + "report:get_tenant_compliance", + "report:get_job_errors", + "report:get_job_rules", + "report:get_tenant_rules", + "report:get_tenant_latest_resources", + "report:get_k8s_platform_latest_resources", + "report:get_job_resources", + "report:get_job_resources_batch", + "report:get_tenant_latest_raw_report", + "job:post_for_tenant_standard", + "job:query", + "job:get", + "job:post_for_tenant", + "job:post_for_k8s_platform", + "job:terminate", + "customer:describe", + "customer:set_excluded_rules", + "customer:get_excluded_rules", + "tenant:query", + "tenant:get", + "tenant:get_active_licenses", + "tenant:set_excluded_rules", + "tenant:get_excluded_rules", + "iam:describe_policy", + "iam:create_policy", + "iam:update_policy", + "iam:remove_policy", + "iam:describe_role", + "iam:create_role", + "iam:update_role", + "iam:remove_role", + "rule:describe", + "rule:delete", + "system:update_meta", + "system:update_metrics", + "system:metrics_status", + "meta:update_standards", + "meta:update_mappings", + "meta:update_meta", + "ruleset:describe", + "ruleset:create", + "ruleset:update", + "ruleset:delete", + "ruleset:get_content", + "ruleset:describe_event_driven", + "ruleset:create_event_driven", + "ruleset:delete_event_driven", + "rule_source:describe", + "rule_source:create", + "rule_source:update", + "rule_source:delete", + "event:post", + "license:add_license", + "license:query", + "license:get", + "license:delete_license", + "license:sync", + "license:activate", + "license:get_activation", + "license:delete_activation", + "license:update_activation", + "scheduled-job:get", + "scheduled-job:query", + "scheduled-job:register", + "scheduled-job:deregister", + "scheduled-job:update", + "settings:describe_mail", + "settings:create_mail", + "settings:delete_mail", + "settings:change_send_reports", + "settings:describe_lm_config", + "settings:create_lm_config", + "settings:delete_lm_config", + "settings:describe_lm_client", + "settings:create_lm_client", + "settings:delete_lm_client", + "rabbitmq:describe", + "rabbitmq:create", + "rabbitmq:delete", + "batch_results:get", + "batch_results:query", + "platform:get_k8s", + "platform:query_k8", + "platform:create_k8s", + "platform:delete_k8s", + "self_integration:create", + "self_integration:update", + "self_integration:describe", + "self_integration:delete", + "dojo_integration:create", + "dojo_integration:describe", + "dojo_integration:delete", + "dojo_integration:activate", + "dojo_integration:get_activation", + "dojo_integration:delete_activation", + "credentials:describe", + "credentials:bind", + "credentials:unbind", + "credentials:get_binding", + "users:describe", + "users:create", + "users:update", + "users:delete", + "users:get_caller", + "users:reset_password" + ], + "title": "Permission", + "type": "string" + }, + "title": "Permissions To Attach", "type": "array", - "items": {} + "uniqueItems": true }, - "rules": { - "title": "Rules", + "permissions_to_detach": { + "items": { + "enum": [ + "report:push_report_to_dojo", + "report:push_to_dojo_batch", + "report:post_operational", + "report:post_project", + "report:post_department", + "report:post_clevel", + "report:get_diagnostic", + "report:get_status", + "report:get_digest", + "report:get_digest_batch", + "report:get_details", + "report:get_details_batch", + "report:get_findings", + "report:get_findings_batch", + "report:get_job_compliance", + "report:get_tenant_compliance", + "report:get_job_errors", + "report:get_job_rules", + "report:get_tenant_rules", + "report:get_tenant_latest_resources", + "report:get_k8s_platform_latest_resources", + "report:get_job_resources", + "report:get_job_resources_batch", + "report:get_tenant_latest_raw_report", + "job:post_for_tenant_standard", + "job:query", + "job:get", + "job:post_for_tenant", + "job:post_for_k8s_platform", + "job:terminate", + "customer:describe", + "customer:set_excluded_rules", + "customer:get_excluded_rules", + "tenant:query", + "tenant:get", + "tenant:get_active_licenses", + "tenant:set_excluded_rules", + "tenant:get_excluded_rules", + "iam:describe_policy", + "iam:create_policy", + "iam:update_policy", + "iam:remove_policy", + "iam:describe_role", + "iam:create_role", + "iam:update_role", + "iam:remove_role", + "rule:describe", + "rule:delete", + "system:update_meta", + "system:update_metrics", + "system:metrics_status", + "meta:update_standards", + "meta:update_mappings", + "meta:update_meta", + "ruleset:describe", + "ruleset:create", + "ruleset:update", + "ruleset:delete", + "ruleset:get_content", + "ruleset:describe_event_driven", + "ruleset:create_event_driven", + "ruleset:delete_event_driven", + "rule_source:describe", + "rule_source:create", + "rule_source:update", + "rule_source:delete", + "event:post", + "license:add_license", + "license:query", + "license:get", + "license:delete_license", + "license:sync", + "license:activate", + "license:get_activation", + "license:delete_activation", + "license:update_activation", + "scheduled-job:get", + "scheduled-job:query", + "scheduled-job:register", + "scheduled-job:deregister", + "scheduled-job:update", + "settings:describe_mail", + "settings:create_mail", + "settings:delete_mail", + "settings:change_send_reports", + "settings:describe_lm_config", + "settings:create_lm_config", + "settings:delete_lm_config", + "settings:describe_lm_client", + "settings:create_lm_client", + "settings:delete_lm_client", + "rabbitmq:describe", + "rabbitmq:create", + "rabbitmq:delete", + "batch_results:get", + "batch_results:query", + "platform:get_k8s", + "platform:query_k8", + "platform:create_k8s", + "platform:delete_k8s", + "self_integration:create", + "self_integration:update", + "self_integration:describe", + "self_integration:delete", + "dojo_integration:create", + "dojo_integration:describe", + "dojo_integration:delete", + "dojo_integration:activate", + "dojo_integration:get_activation", + "dojo_integration:delete_activation", + "credentials:describe", + "credentials:bind", + "credentials:unbind", + "credentials:get_binding", + "users:describe", + "users:create", + "users:update", + "users:delete", + "users:get_caller", + "users:reset_password" + ], + "title": "Permission", + "type": "string" + }, + "title": "Permissions To Detach", "type": "array", - "items": {}, "uniqueItems": true }, - "git_project_id": { - "title": "Git Project Id", - "type": "string" + "tenants_to_add": { + "items": { + "type": "string" + }, + "title": "Tenants To Add", + "type": "array", + "uniqueItems": true }, - "git_ref": { - "title": "Git Ref", + "tenants_to_remove": { + "items": { + "type": "string" + }, + "title": "Tenants To Remove", + "type": "array", + "uniqueItems": true + } + }, + "title": "PolicyPatchModel", + "type": "object" + } + }, + "PolicyPostModel": { + "content_type": "application/json", + "schema": { + "properties": { + "description": { + "title": "Description", "type": "string" }, - "service_section": { - "title": "Service Section", + "effect": { + "enum": [ + "allow", + "deny" + ], + "title": "PolicyEffect", "type": "string" }, - "severity": { - "title": "Severity", + "name": { + "title": "Name", "type": "string" }, - "mitre": { - "title": "Mitre", - "default": [], - "type": "array", + "permissions": { "items": { + "enum": [ + "report:push_report_to_dojo", + "report:push_to_dojo_batch", + "report:post_operational", + "report:post_project", + "report:post_department", + "report:post_clevel", + "report:get_diagnostic", + "report:get_status", + "report:get_digest", + "report:get_digest_batch", + "report:get_details", + "report:get_details_batch", + "report:get_findings", + "report:get_findings_batch", + "report:get_job_compliance", + "report:get_tenant_compliance", + "report:get_job_errors", + "report:get_job_rules", + "report:get_tenant_rules", + "report:get_tenant_latest_resources", + "report:get_k8s_platform_latest_resources", + "report:get_job_resources", + "report:get_job_resources_batch", + "report:get_tenant_latest_raw_report", + "job:post_for_tenant_standard", + "job:query", + "job:get", + "job:post_for_tenant", + "job:post_for_k8s_platform", + "job:terminate", + "customer:describe", + "customer:set_excluded_rules", + "customer:get_excluded_rules", + "tenant:query", + "tenant:get", + "tenant:get_active_licenses", + "tenant:set_excluded_rules", + "tenant:get_excluded_rules", + "iam:describe_policy", + "iam:create_policy", + "iam:update_policy", + "iam:remove_policy", + "iam:describe_role", + "iam:create_role", + "iam:update_role", + "iam:remove_role", + "rule:describe", + "rule:delete", + "system:update_meta", + "system:update_metrics", + "system:metrics_status", + "meta:update_standards", + "meta:update_mappings", + "meta:update_meta", + "ruleset:describe", + "ruleset:create", + "ruleset:update", + "ruleset:delete", + "ruleset:get_content", + "ruleset:describe_event_driven", + "ruleset:create_event_driven", + "ruleset:delete_event_driven", + "rule_source:describe", + "rule_source:create", + "rule_source:update", + "rule_source:delete", + "event:post", + "license:add_license", + "license:query", + "license:get", + "license:delete_license", + "license:sync", + "license:activate", + "license:get_activation", + "license:delete_activation", + "license:update_activation", + "scheduled-job:get", + "scheduled-job:query", + "scheduled-job:register", + "scheduled-job:deregister", + "scheduled-job:update", + "settings:describe_mail", + "settings:create_mail", + "settings:delete_mail", + "settings:change_send_reports", + "settings:describe_lm_config", + "settings:create_lm_config", + "settings:delete_lm_config", + "settings:describe_lm_client", + "settings:create_lm_client", + "settings:delete_lm_client", + "rabbitmq:describe", + "rabbitmq:create", + "rabbitmq:delete", + "batch_results:get", + "batch_results:query", + "platform:get_k8s", + "platform:query_k8", + "platform:create_k8s", + "platform:delete_k8s", + "self_integration:create", + "self_integration:update", + "self_integration:describe", + "self_integration:delete", + "dojo_integration:create", + "dojo_integration:describe", + "dojo_integration:delete", + "dojo_integration:activate", + "dojo_integration:get_activation", + "dojo_integration:delete_activation", + "credentials:describe", + "credentials:bind", + "credentials:unbind", + "credentials:get_binding", + "users:describe", + "users:create", + "users:update", + "users:delete", + "users:get_caller", + "users:reset_password" + ], + "title": "Permission", "type": "string" }, + "title": "Permissions", + "type": "array", "uniqueItems": true }, - "standard": { - "title": "Standard", - "default": [], - "type": "array", + "permissions_admin": { + "default": false, + "title": "Permissions Admin", + "type": "boolean" + }, + "tenants": { "items": { "type": "string" }, + "title": "Tenants", + "type": "array", "uniqueItems": true } }, "required": [ - "httpMethod", - "path", - "user_id", - "user_role", - "user_customer", "name", - "version", - "cloud" + "effect", + "description" ], - "additionalProperties": false, - "definitions": { - "RuleDomain": { - "title": "RuleDomain", - "description": "An enumeration.", - "enum": [ - "AWS", - "AZURE", - "GCP", - "KUBERNETES" - ], - "type": "string" - } - } + "title": "PolicyPostModel", + "type": "object" } }, - "EventPostModel": { + "ProjectGetReportModel": { "content_type": "application/json", "schema": { - "title": "EventPostModel", - "type": "object", "properties": { - "version": { - "title": "Version", - "default": "1.0.0", - "type": "string" - }, - "vendor": { - "$ref": "#/definitions/EventVendor" + "receivers": { + "items": { + "type": "string" + }, + "title": "Receivers", + "type": "array", + "uniqueItems": true }, - "events": { - "title": "Events", + "tenant_display_names": { + "items": { + "type": "string" + }, + "title": "Tenant Display Names", "type": "array", + "uniqueItems": true + }, + "types": { "items": { - "type": "object" - } + "enum": [ + "OVERVIEW", + "RESOURCES", + "COMPLIANCE", + "ATTACK_VECTOR", + "FINOPS" + ], + "type": "string" + }, + "title": "Types", + "type": "array", + "uniqueItems": true } }, "required": [ - "vendor", - "events" + "tenant_display_names" ], - "additionalProperties": false, - "definitions": { - "EventVendor": { - "title": "EventVendor", - "description": "An enumeration.", - "enum": [ - "AWS", - "MAESTRO" - ], - "type": "string" - } - } + "title": "ProjectGetReportModel", + "type": "object" + } + }, + "RabbitMQDeleteModel": { + "content_type": "application/json", + "schema": { + "properties": {}, + "title": "RabbitMQDeleteModel", + "type": "object" } }, - "RuleMetaUpdateDTO": { + "RabbitMQPostModel": { "content_type": "application/json", "schema": { - "title": "RuleMetaUpdateDTO", - "type": "object", "properties": { - "trace_id": { - "title": "Trace Id", + "connection_url": { + "format": "uri", + "minLength": 1, + "title": "Connection Url", "type": "string" }, - "items": { - "title": "Items", - "type": "array", - "items": { - "$ref": "#/definitions/RuleMetaUpdate" - } + "maestro_user": { + "title": "Maestro User", + "type": "string" + }, + "rabbit_exchange": { + "default": null, + "title": "Rabbit Exchange", + "type": "string" + }, + "request_queue": { + "title": "Request Queue", + "type": "string" + }, + "response_queue": { + "title": "Response Queue", + "type": "string" + }, + "sdk_access_key": { + "title": "Sdk Access Key", + "type": "string" + }, + "sdk_secret_key": { + "title": "Sdk Secret Key", + "type": "string" } }, "required": [ - "trace_id", - "items" + "maestro_user", + "request_queue", + "response_queue", + "sdk_access_key", + "connection_url", + "sdk_secret_key" ], - "definitions": { - "RuleMetaUpdate": { - "title": "RuleMetaUpdate", - "type": "object", + "title": "RabbitMQPostModel", + "type": "object" + } + }, + "RawReportModel": { + "content_type": "application/json", + "schema": { + "properties": { + "data": { "properties": { - "customer": { - "title": "Customer", + "customer_name": { + "title": "Customer Name", "type": "string" }, - "id": { - "title": "Id", + "dictionary_url": { + "title": "Dictionary Url", "type": "string" }, - "status": { - "title": "Status", + "meta_url": { + "title": "Meta Url", + "type": "string" + }, + "obfuscated": { + "title": "Obfuscated", + "type": "boolean" + }, + "tenant_name": { + "title": "Tenant Name", + "type": "string" + }, + "url": { + "title": "Url", "type": "string" } }, "required": [ - "customer", - "status" - ] + "customer_name", + "tenant_name", + "obfuscated", + "url" + ], + "title": "RawReportItem", + "type": "object" } - } + }, + "required": [ + "data" + ], + "title": "RawReportModel", + "type": "object" } }, - "AWSCredentials": { + "RefreshPostModel": { "content_type": "application/json", "schema": { - "title": "AWSCredentials", - "type": "object", "properties": { - "AWS_ACCESS_KEY_ID": { - "title": "Aws Access Key Id", - "type": "string" - }, - "AWS_SECRET_ACCESS_KEY": { - "title": "Aws Secret Access Key", - "type": "string" - }, - "AWS_SESSION_TOKEN": { - "title": "Aws Session Token", - "type": "string" - }, - "AWS_DEFAULT_REGION": { - "title": "Aws Default Region", - "default": "us-east-1", + "refresh_token": { + "title": "Refresh Token", "type": "string" } }, "required": [ - "AWS_ACCESS_KEY_ID", - "AWS_SECRET_ACCESS_KEY" + "refresh_token" ], - "additionalProperties": false + "title": "RefreshPostModel", + "type": "object" } }, - "ApplicationListModel": { + "ReportPushByJobIdModel": { "content_type": "application/json", "schema": { - "title": "ApplicationListModel", - "type": "object", + "description": "/reports/push/dojo/{job_id}/\n/reports/push/security-hub/{job_id}/", "properties": { - "customer": { - "title": "Customer", - "type": "string" + "type": { + "allOf": [ + { + "enum": [ + "manual", + "reactive" + ], + "title": "JobType", + "type": "string" + } + ], + "default": "manual" } }, - "additionalProperties": false + "title": "ReportPushByJobIdModel", + "type": "object" } }, - "ScheduledJobDTO": { + "ReportPushMultipleModel": { "content_type": "application/json", "schema": { - "title": "ScheduledJobDTO", - "type": "object", + "description": "/reports/push/dojo\n/reports/push/security-hub", "properties": { - "trace_id": { - "title": "Trace Id", - "type": "string" - }, - "items": { - "title": "Items", - "type": "array", - "items": { - "$ref": "#/definitions/ScheduledJob" - } - } - }, - "required": [ - "trace_id", - "items" - ], - "definitions": { - "ScheduledJob": { - "title": "ScheduledJob", - "type": "object", - "properties": { - "name": { - "title": "Name", + "from": { + "anyOf": [ + { + "format": "date-time", "type": "string" }, - "customer_name": { - "title": "Customer Name", + { + "format": "date", "type": "string" - }, - "creation_date": { - "title": "Creation Date", + } + ], + "default": null, + "title": "From" + }, + "tenant_name": { + "title": "Tenant Name", + "type": "string" + }, + "to": { + "anyOf": [ + { + "format": "date-time", "type": "string" }, - "enabled": { - "title": "Enabled", - "type": "boolean" - }, - "last_execution_time": { - "title": "Last Execution Time", + { + "format": "date", "type": "string" - }, - "schedule": { - "title": "Schedule", + } + ], + "default": null, + "title": "To" + }, + "type": { + "allOf": [ + { + "enum": [ + "manual", + "reactive" + ], + "title": "JobType", "type": "string" - }, - "scan_regions": { - "title": "Scan Regions", - "type": "array", - "items": {} - }, - "scan_rulesets": { - "title": "Scan Rulesets", - "type": "array", - "items": {} } - }, - "required": [ - "name", - "customer_name", - "creation_date", - "enabled", - "schedule" - ] + ], + "default": null } - } + }, + "required": [ + "tenant_name" + ], + "title": "ReportPushMultipleModel", + "type": "object" } }, - "RabbitMQDeleteModel": { + "ReportsSendingSettingPostModel": { "content_type": "application/json", "schema": { - "title": "RabbitMQDeleteModel", - "type": "object", "properties": { - "customer": { - "title": "Customer", - "type": "string" + "enable": { + "default": true, + "title": "Enable", + "type": "boolean" } }, - "additionalProperties": false + "title": "ReportsSendingSettingPostModel", + "type": "object" } }, - "TenantRegionPostModel": { + "RolePatchModel": { "content_type": "application/json", "schema": { - "title": "TenantRegionPostModel", - "type": "object", "properties": { - "tenant_name": { - "title": "Tenant Name", + "description": { + "default": null, + "title": "Description", "type": "string" }, - "region": { - "title": "Region", + "expiration": { + "default": null, + "format": "date-time", + "title": "Expiration", "type": "string" + }, + "policies_to_attach": { + "items": { + "type": "string" + }, + "title": "Policies To Attach", + "type": "array", + "uniqueItems": true + }, + "policies_to_detach": { + "items": { + "type": "string" + }, + "title": "Policies To Detach", + "type": "array", + "uniqueItems": true } }, - "required": [ - "region" - ], - "additionalProperties": false + "title": "RolePatchModel", + "type": "object" } }, - "PolicyPatchModel": { + "RolePostModel": { "content_type": "application/json", "schema": { - "title": "PolicyPatchModel", - "type": "object", "properties": { + "description": { + "title": "Description", + "type": "string" + }, + "expiration": { + "format": "date-time", + "title": "Expiration", + "type": "string" + }, "name": { "title": "Name", "type": "string" }, - "permissions_to_attach": { - "title": "Permissions To Attach", - "type": "array", + "policies": { "items": { "type": "string" }, - "uniqueItems": true - }, - "permissions_to_detach": { - "title": "Permissions To Detach", + "title": "Policies", "type": "array", - "items": { - "type": "string" - }, "uniqueItems": true - }, - "customer": { - "title": "Customer", - "type": "string" } }, "required": [ - "name" + "name", + "policies", + "description" ], - "additionalProperties": false + "title": "RolePostModel", + "type": "object" } }, - "DojoApplicationPostModel": { + "RuleDeleteModel": { "content_type": "application/json", "schema": { - "title": "DojoApplicationPostModel", - "type": "object", "properties": { - "customer": { - "title": "Customer", - "type": "string" + "cloud": { + "allOf": [ + { + "enum": [ + "AWS", + "AZURE", + "GCP", + "KUBERNETES" + ], + "title": "RuleDomain", + "type": "string" + } + ], + "default": null }, - "description": { - "title": "Description", - "default": "Custodian Defect Dojo", + "git_project_id": { + "default": null, + "title": "Git Project Id", "type": "string" }, - "api_key": { - "title": "Api Key", + "git_ref": { + "default": null, + "title": "Git Ref", "type": "string" }, - "url": { - "title": "Url", - "minLength": 1, - "maxLength": 65536, - "format": "uri", + "rule": { + "default": null, + "title": "Rule", "type": "string" } }, - "required": [ - "api_key", - "url" - ], - "additionalProperties": false + "title": "RuleDeleteModel", + "type": "object" } }, - "GenericReportDTO": { + "RuleSourceDeleteModel": { "content_type": "application/json", "schema": { - "title": "GenericReportDTO", - "type": "object", "properties": { - "trace_id": { - "title": "Trace Id", + "id": { + "title": "Id", "type": "string" - }, - "items": { - "title": "Items", - "type": "array", - "items": { - "anyOf": [ - { - "$ref": "#/definitions/JobReport" - }, - { - "$ref": "#/definitions/TenantReport" - } - ] - } } }, "required": [ - "trace_id", - "items" + "id" ], - "definitions": { - "JobReport": { - "title": "JobReport", - "type": "object", - "properties": { - "id": { - "title": "Id", - "type": "string" - }, - "type": { - "title": "Type", - "type": "string" - }, - "content": { - "title": "Content", - "type": "object" - }, - "href": { - "title": "Href", - "type": "string" - } - }, - "required": [ - "id", - "type" - ] - }, - "TenantReport": { - "title": "TenantReport", - "type": "object", - "properties": { - "tenant": { - "title": "Tenant", - "type": "string" - }, - "content": { - "title": "Content", - "type": "object" - }, - "href": { - "title": "Href", - "type": "string" - } - }, - "required": [ - "tenant" - ] - } - } + "title": "RuleSourceDeleteModel", + "type": "object" } }, - "CredentialsManagerPatchModel": { + "RuleSourcePatchModel": { "content_type": "application/json", "schema": { - "title": "CredentialsManagerPatchModel", - "type": "object", "properties": { - "cloud_identifier": { - "title": "Cloud Identifier", - "type": "string" - }, - "cloud": { - "title": "Cloud", - "enum": [ - "AWS", - "AZURE", - "GCP" - ], + "description": { + "default": null, + "title": "Description", "type": "string" }, - "trusted_role_arn": { - "title": "Trusted Role Arn", + "git_access_secret": { + "default": null, + "title": "Git Access Secret", "type": "string" }, - "enabled": { - "title": "Enabled", - "type": "boolean" - } - }, - "required": [ - "cloud_identifier", - "cloud" - ], - "additionalProperties": false - } - }, - "UserCustomerPostModel": { - "content_type": "application/json", - "schema": { - "title": "UserCustomerPostModel", - "type": "object", - "properties": { - "customer": { - "title": "Customer", + "git_access_type": { + "default": "TOKEN", + "title": "Git access type", "type": "string" }, - "target_user": { - "title": "Target User", - "type": "string" - } - }, - "required": [ - "customer" - ], - "additionalProperties": false - } - }, - "BatchResultDTO": { - "content_type": "application/json", - "schema": { - "title": "BatchResultDTO", - "type": "object", - "properties": { - "trace_id": { - "title": "Trace Id", + "id": { + "title": "Id", "type": "string" - }, - "items": { - "title": "Items", - "type": "array", - "items": { - "$ref": "#/definitions/BatchResult" - } } }, "required": [ - "trace_id", - "items" + "id" ], - "definitions": { - "BatchResult": { - "title": "BatchResult", - "type": "object", - "properties": { - "batch_results_id": { - "title": "Batch Results Id", - "type": "string" - }, - "job_id": { - "title": "Job Id", - "type": "string" - }, - "customer_name": { - "title": "Customer Name", - "type": "string" - }, - "tenant_name": { - "title": "Tenant Name", - "type": "string" - }, - "cloud_identifier": { - "title": "Cloud Identifier", - "type": "string" - }, - "status": { - "title": "Status", - "type": "string" - }, - "registration_start": { - "title": "Registration Start", - "type": "string" - }, - "registration_end": { - "title": "Registration End", - "type": "string" - }, - "submitted_at": { - "title": "Submitted At", - "type": "string" - } - }, - "required": [ - "batch_results_id", - "job_id", - "customer_name", - "tenant_name", - "cloud_identifier", - "status", - "registration_start", - "registration_end", - "submitted_at" - ] - } - } + "title": "RuleSourcePatchModel", + "type": "object" } }, - "PlatformK8sDelete": { + "RuleSourcePostModel": { "content_type": "application/json", "schema": { - "title": "PlatformK8sDelete", - "description": "Prepared event historically contains both some request meta and incoming\nbody on one level.\nYou can inherit your models from this one and use them directly as type\nannotations inside handlers (with validate_kwargs) decorator", - "type": "object", "properties": { - "httpMethod": { - "title": "Httpmethod", - "type": "string" - }, - "path": { - "title": "Path", + "description": { + "title": "Description", "type": "string" }, - "user_id": { - "title": "User Id", + "git_access_secret": { + "default": null, + "title": "Git Access Secret", "type": "string" }, - "user_role": { - "title": "User Role", + "git_access_type": { + "default": "TOKEN", + "title": "Git access type", "type": "string" }, - "user_customer": { - "title": "User Customer", + "git_project_id": { + "title": "Git Project Id", "type": "string" }, - "customer": { - "title": "Customer", + "git_ref": { + "default": "main", + "title": "Git Ref", "type": "string" }, - "tenant": { - "title": "Tenant", + "git_rules_prefix": { + "default": "/", + "title": "Git Rules Prefix", "type": "string" }, - "tenants": { - "title": "Tenants", - "type": "array", - "items": { - "type": "string" - } - }, - "id": { - "title": "Id", + "git_url": { + "default": null, + "pattern": "^https?:\\/\\/[^\\/]+$", + "title": "Git Url", "type": "string" } }, "required": [ - "httpMethod", - "path", - "user_id", - "user_role", - "user_customer", - "id" + "git_project_id", + "description" ], - "additionalProperties": false + "title": "RuleSourcePostModel", + "type": "object" } }, - "SignInPostModel": { + "RuleUpdateMetaPostModel": { "content_type": "application/json", "schema": { - "title": "SignInPostModel", - "type": "object", "properties": { - "username": { - "title": "Username", - "type": "string" - }, - "password": { - "title": "Password", + "rule_source_id": { + "default": null, + "title": "Rule Source Id", "type": "string" } }, - "required": [ - "username", - "password" - ], - "additionalProperties": false + "title": "RuleUpdateMetaPostModel", + "type": "object" } }, - "RuleSourceDeleteModel": { + "RulesReportModel": { "content_type": "application/json", "schema": { - "title": "RuleSourceDeleteModel", - "type": "object", "properties": { - "id": { - "title": "Id", - "type": "string" + "data": { + "anyOf": [ + { + "properties": { + "content": { + "title": "Content", + "type": "object" + }, + "customer_name": { + "title": "Customer Name", + "type": "string" + }, + "format": { + "enum": [ + "json", + "xlsx" + ], + "title": "ReportFormat", + "type": "string" + }, + "job_id": { + "title": "Job Id", + "type": "string" + }, + "job_type": { + "enum": [ + "manual", + "reactive" + ], + "title": "JobType", + "type": "string" + }, + "tenant_name": { + "title": "Tenant Name", + "type": "string" + }, + "url": { + "title": "Url", + "type": "string" + } + }, + "required": [ + "format", + "tenant_name", + "customer_name" + ], + "title": "BaseReportJob", + "type": "object" + }, + { + "type": "null" + } + ] }, - "customer": { - "title": "Customer", - "type": "string" + "items": { + "anyOf": [ + { + "items": { + "properties": { + "api_calls": { + "title": "Api Calls", + "type": "object" + }, + "execution_time": { + "title": "Execution Time", + "type": "number" + }, + "failed_resources": { + "title": "Failed Resources", + "type": "integer" + }, + "policy": { + "title": "Policy", + "type": "string" + }, + "region": { + "title": "Region", + "type": "string" + }, + "scanned_resources": { + "title": "Scanned Resources", + "type": "integer" + }, + "succeeded": { + "title": "Succeeded", + "type": "boolean" + } + }, + "required": [ + "api_calls", + "execution_time", + "failed_resources", + "policy", + "region", + "scanned_resources", + "succeeded" + ], + "title": "RulesReportItem", + "type": "object" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Items" } }, "required": [ - "id" + "items", + "data" ], - "additionalProperties": false + "title": "RulesReportModel", + "type": "object" } }, - "PolicyDeleteModel": { + "RulesetDeleteModel": { "content_type": "application/json", "schema": { - "title": "PolicyDeleteModel", - "type": "object", "properties": { "name": { "title": "Name", "type": "string" }, - "customer": { - "title": "Customer", + "version": { + "title": "Version", "type": "string" } }, "required": [ - "name" + "name", + "version" ], - "additionalProperties": false - } - }, - "TenantDeleteModel": { - "content_type": "application/json", - "schema": { - "title": "TenantDeleteModel", - "type": "object", - "properties": {}, - "additionalProperties": false + "title": "RulesetDeleteModel", + "type": "object" } }, - "EventDrivenRulesetPostModel": { + "RulesetPatchModel": { "content_type": "application/json", "schema": { - "title": "EventDrivenRulesetPostModel", - "description": "Prepared event historically contains both some request meta and incoming\nbody on one level.\nYou can inherit your models from this one and use them directly as type\nannotations inside handlers (with validate_kwargs) decorator", - "type": "object", "properties": { - "httpMethod": { - "title": "Httpmethod", - "type": "string" - }, - "path": { - "title": "Path", - "type": "string" - }, - "user_id": { - "title": "User Id", - "type": "string" - }, - "user_role": { - "title": "User Role", - "type": "string" - }, - "user_customer": { - "title": "User Customer", - "type": "string" - }, - "customer": { - "title": "Customer", - "type": "string" + "active": { + "default": null, + "title": "Active", + "type": "boolean" }, - "tenant": { - "title": "Tenant", + "name": { + "title": "Name", "type": "string" }, - "tenants": { - "title": "Tenants", + "rules_to_attach": { + "items": {}, + "title": "Rules To Attach", "type": "array", - "items": { - "type": "string" - } + "uniqueItems": true }, - "cloud": { - "$ref": "#/definitions/RuleDomain" + "rules_to_detach": { + "items": {}, + "title": "Rules To Detach", + "type": "array", + "uniqueItems": true }, "version": { "title": "Version", - "type": "number" - }, - "rules": { - "title": "Rules", - "default": [], - "type": "array", - "items": {} + "type": "string" } }, "required": [ - "httpMethod", - "path", - "user_id", - "user_role", - "user_customer", - "cloud", + "name", "version" ], - "additionalProperties": false, - "definitions": { - "RuleDomain": { - "title": "RuleDomain", - "description": "An enumeration.", + "title": "RulesetPatchModel", + "type": "object" + } + }, + "RulesetPostModel": { + "content_type": "application/json", + "schema": { + "properties": { + "active": { + "default": true, + "title": "Active", + "type": "boolean" + }, + "cloud": { "enum": [ "AWS", "AZURE", "GCP", "KUBERNETES" ], + "title": "RuleDomain", "type": "string" - } - } - } - }, - "SiemDojoDTO": { - "content_type": "application/json", - "schema": { - "title": "SiemDojoDTO", - "type": "object", - "properties": { - "trace_id": { - "title": "Trace Id", + }, + "git_project_id": { + "default": null, + "title": "Git Project Id", "type": "string" }, - "items": { - "title": "Items", + "git_ref": { + "default": null, + "title": "Git Ref", + "type": "string" + }, + "mitre": { + "items": { + "type": "string" + }, + "title": "Mitre", + "type": "array", + "uniqueItems": true + }, + "name": { + "title": "Name", + "type": "string" + }, + "rules": { + "items": {}, + "title": "Rules", "type": "array", + "uniqueItems": true + }, + "service_section": { + "default": null, + "title": "Service Section", + "type": "string" + }, + "severity": { + "default": null, + "title": "Severity", + "type": "string" + }, + "standard": { "items": { - "$ref": "#/definitions/SecurityHubSiem" - } + "type": "string" + }, + "title": "Standard", + "type": "array", + "uniqueItems": true + }, + "version": { + "title": "Version", + "type": "string" } }, "required": [ - "trace_id", - "items" + "name", + "version", + "cloud" ], - "definitions": { - "SecurityHubConfiguration": { - "title": "SecurityHubConfiguration", - "type": "object", - "properties": { - "region": { - "title": "Region", - "type": "string" - }, - "product_arn": { - "title": "Product Arn", - "type": "string" - } - } + "title": "RulesetPostModel", + "type": "object" + } + }, + "ScheduledJobPatchModel": { + "content_type": "application/json", + "schema": { + "properties": { + "enabled": { + "default": null, + "title": "Enabled", + "type": "boolean" }, - "SecurityHubSiem": { - "title": "SecurityHubSiem", - "type": "object", - "properties": { - "type": { - "title": "Type", - "type": "string" - }, - "customer": { - "title": "Customer", - "type": "string" - }, - "configuration": { - "$ref": "#/definitions/SecurityHubConfiguration" - } - }, - "required": [ - "type", - "customer" - ] + "schedule": { + "default": null, + "title": "Schedule", + "type": "string" } - } + }, + "title": "ScheduledJobPatchModel", + "type": "object" } }, - "ResourceReportJobsDTO": { + "ScheduledJobPostModel": { "content_type": "application/json", "schema": { - "title": "ResourceReportJobsDTO", - "type": "object", "properties": { - "trace_id": { - "title": "Trace Id", + "name": { + "default": null, + "title": "Name", "type": "string" }, - "items": { - "title": "Items", + "schedule": { + "title": "Schedule", + "type": "string" + }, + "target_regions": { + "items": { + "type": "string" + }, + "title": "Target Regions", "type": "array", + "uniqueItems": true + }, + "target_rulesets": { "items": { - "$ref": "#/definitions/ResourceReportJobs" - } + "type": "string" + }, + "title": "Target Rulesets", + "type": "array", + "uniqueItems": true + }, + "tenant_name": { + "default": null, + "title": "Tenant Name", + "type": "string" } }, "required": [ - "trace_id", - "items" + "schedule" ], - "definitions": { - "ViolatedRule": { - "title": "ViolatedRule", - "type": "object", - "properties": { - "name": { - "title": "Name", - "type": "string" - }, - "description": { - "title": "Description", - "type": "string" - }, - "severity": { - "title": "Severity", - "type": "string" - } + "title": "ScheduledJobPostModel", + "type": "object" + } + }, + "SelfIntegrationPatchModel": { + "content_type": "application/json", + "schema": { + "properties": { + "add_tenants": { + "items": { + "type": "string" }, - "required": [ - "name", - "severity" - ] + "title": "Add Tenants", + "type": "array", + "uniqueItems": true }, - "ResourceReportJobs": { - "title": "ResourceReportJobs", - "type": "object", - "properties": { - "account_id": { - "title": "Account Id", - "type": "string" - }, - "input_identifier": { - "title": "Input Identifier", - "type": "string" - }, - "identifier": { - "title": "Identifier", - "type": "string" - }, - "last_scan_date": { - "title": "Last Scan Date", - "type": "string" - }, - "violated_rules": { - "title": "Violated Rules", - "type": "array", - "items": { - "$ref": "#/definitions/ViolatedRule" - } - }, - "data": { - "title": "Data", - "type": "object" - }, - "region": { - "title": "Region", - "type": "string" - }, - "resource_type": { - "title": "Resource Type", - "type": "string" - }, - "job_id": { - "title": "Job Id", - "type": "string" - }, - "submitted_at": { - "title": "Submitted At", - "type": "string" - }, - "type": { - "title": "Type", - "type": "string" - } + "remove_tenants": { + "items": { + "type": "string" }, - "required": [ - "account_id", - "input_identifier", - "identifier", - "violated_rules", - "data", - "region", - "resource_type", - "job_id", - "submitted_at", - "type" - ] + "title": "Remove Tenants", + "type": "array", + "uniqueItems": true } - } + }, + "title": "SelfIntegrationPatchModel", + "type": "object" } }, - "SiemSecurityHubDTO": { + "SelfIntegrationPutModel": { "content_type": "application/json", "schema": { - "title": "SiemSecurityHubDTO", - "type": "object", "properties": { - "trace_id": { - "title": "Trace Id", + "all_tenants": { + "default": false, + "title": "All Tenants", + "type": "boolean" + }, + "auto_resolve_access": { + "default": false, + "title": "Auto Resolve Access", + "type": "boolean" + }, + "clouds": { + "items": { + "enum": [ + "AWS", + "AZURE", + "GOOGLE" + ], + "type": "string" + }, + "title": "Clouds", + "type": "array", + "uniqueItems": true + }, + "description": { + "default": "Custodian access application", + "title": "Description", "type": "string" }, - "items": { - "title": "Items", + "exclude_tenants": { + "items": { + "type": "string" + }, + "title": "Exclude Tenants", "type": "array", + "uniqueItems": true + }, + "password": { + "title": "Password", + "type": "string" + }, + "results_storage": { + "default": null, + "title": "Results Storage", + "type": "string" + }, + "tenant_names": { "items": { - "$ref": "#/definitions/DojoSiem" - } - } - }, - "required": [ - "trace_id", - "items" - ], - "definitions": { - "DojoConfiguration": { - "title": "DojoConfiguration", - "type": "object", - "properties": { - "host": { - "title": "Host", - "type": "string" - }, - "key": { - "title": "Key", - "type": "string" - }, - "user": { - "title": "User", - "type": "string" - }, - "upload_files": { - "title": "Upload Files", - "type": "boolean" - }, - "display_all_fields": { - "title": "Display All Fields", - "type": "boolean" - }, - "resource_per_finding": { - "title": "Resource Per Finding", - "type": "boolean" - } - } + "type": "string" + }, + "title": "Tenant Names", + "type": "array", + "uniqueItems": true }, - "EntitiesMapping": { - "title": "EntitiesMapping", - "type": "object", - "properties": { - "product_type_name": { - "title": "Product Type Name", - "type": "string" - }, - "test_title": { - "title": "Test Title", - "type": "string" - }, - "product_name": { - "title": "Product Name", - "type": "string" - }, - "engagement_name": { - "title": "Engagement Name", - "type": "string" - } - } + "url": { + "default": null, + "format": "uri", + "minLength": 1, + "title": "Url", + "type": "string" }, - "DojoSiem": { - "title": "DojoSiem", - "type": "object", - "properties": { - "type": { - "title": "Type", - "type": "string" - }, - "customer": { - "title": "Customer", - "type": "string" - }, - "configuration": { - "$ref": "#/definitions/DojoConfiguration" - }, - "entities_mapping": { - "$ref": "#/definitions/EntitiesMapping" - } - }, - "required": [ - "type", - "customer" - ] + "username": { + "title": "Username", + "type": "string" + } + }, + "required": [ + "username", + "password" + ], + "title": "SelfIntegrationPutModel", + "type": "object" + } + }, + "SignInModel": { + "content_type": "application/json", + "schema": { + "properties": { + "access_token": { + "title": "Access Token", + "type": "string" + }, + "expires_in": { + "title": "Expires In", + "type": "integer" + }, + "refresh_token": { + "title": "Refresh Token", + "type": "string" } - } + }, + "required": [ + "access_token", + "refresh_token", + "expires_in" + ], + "title": "SignInModel", + "type": "object" } }, - "ResourceReportDTO": { + "SignInPostModel": { "content_type": "application/json", "schema": { - "title": "ResourceReportDTO", - "type": "object", "properties": { - "trace_id": { - "title": "Trace Id", + "password": { + "title": "Password", "type": "string" }, - "items": { - "title": "Items", - "type": "array", - "items": { - "$ref": "#/definitions/ResourceReport" - } + "username": { + "title": "Username", + "type": "string" } }, "required": [ - "trace_id", - "items" + "username", + "password" ], - "definitions": { - "ViolatedRule": { - "title": "ViolatedRule", - "type": "object", - "properties": { - "name": { - "title": "Name", - "type": "string" - }, - "description": { - "title": "Description", - "type": "string" - }, - "severity": { - "title": "Severity", - "type": "string" - } + "title": "SignInPostModel", + "type": "object" + } + }, + "SignUpModel": { + "content_type": "application/json", + "schema": { + "properties": { + "customer_admins": { + "items": { + "type": "string" }, - "required": [ - "name", - "severity" - ] + "title": "Customer Admins", + "type": "array", + "uniqueItems": true + }, + "customer_display_name": { + "title": "Customer Display Name", + "type": "string" + }, + "customer_name": { + "title": "Customer Name", + "type": "string" + }, + "password": { + "title": "Password", + "type": "string" }, - "ResourceReport": { - "title": "ResourceReport", - "type": "object", + "username": { + "title": "Username", + "type": "string" + } + }, + "required": [ + "username", + "password", + "customer_name", + "customer_display_name" + ], + "title": "SignUpModel", + "type": "object" + } + }, + "SingleBatchResultModel": { + "content_type": "application/json", + "schema": { + "properties": { + "data": { "properties": { - "account_id": { - "title": "Account Id", + "cloud_identifier": { + "title": "Cloud Identifier", "type": "string" }, - "input_identifier": { - "title": "Input Identifier", + "customer_name": { + "title": "Customer Name", "type": "string" }, - "identifier": { - "title": "Identifier", + "id": { + "title": "Id", "type": "string" }, - "last_scan_date": { - "title": "Last Scan Date", + "status": { + "description": "https://docs.aws.amazon.com/batch/latest/userguide/job_states.html", + "enum": [ + "SUBMITTED", + "PENDING", + "RUNNABLE", + "STARTING", + "RUNNING", + "FAILED", + "SUCCEEDED" + ], + "title": "JobState", "type": "string" }, - "violated_rules": { - "title": "Violated Rules", - "type": "array", - "items": { - "$ref": "#/definitions/ViolatedRule" - } - }, - "data": { - "title": "Data", - "type": "object" + "stopped_at": { + "title": "Stopped At", + "type": "string" }, - "region": { - "title": "Region", + "submitted_at": { + "title": "Submitted At", "type": "string" }, - "resource_type": { - "title": "Resource Type", + "tenant_name": { + "title": "Tenant Name", "type": "string" } }, "required": [ - "account_id", - "input_identifier", - "identifier", - "violated_rules", - "data", - "region", - "resource_type" - ] + "cloud_identifier", + "customer_name", + "id", + "status", + "stopped_at", + "submitted_at", + "tenant_name" + ], + "title": "BatchResult", + "type": "object" } - } + }, + "required": [ + "data" + ], + "title": "SingleBatchResultModel", + "type": "object" } }, - "TenantLicensePriorityDTO": { + "SingleCredentialsModel": { "content_type": "application/json", "schema": { - "title": "TenantLicensePriorityDTO", - "type": "object", "properties": { - "trace_id": { - "title": "Trace Id", - "type": "string" - }, - "items": { - "title": "Items", - "type": "array", - "items": { - "$ref": "#/definitions/TenantLicensePriority" - } - } - }, - "required": [ - "trace_id", - "items" - ], - "definitions": { - "TenantLicensePriority": { - "title": "TenantLicensePriority", - "type": "object", + "data": { "properties": { - "tenant": { - "title": "Tenant", - "type": "string" + "credentials": { + "title": "Credentials", + "type": "object" }, - "priority_id": { - "title": "Priority Id", + "description": { + "title": "Description", "type": "string" }, - "ruleset": { - "title": "Ruleset", + "has_secret": { + "title": "Has Secret", + "type": "boolean" + }, + "id": { + "title": "Id", "type": "string" }, - "license_keys": { - "title": "License Keys", - "type": "array", - "items": { - "type": "string" - } + "type": { + "enum": [ + "AWS_CREDENTIALS", + "AWS_ROLE", + "AZURE_CREDENTIALS", + "AZURE_CERTIFICATE", + "GCP_SERVICE_ACCOUNT", + "GCP_COMPUTE_ACCOUNT" + ], + "title": "Type", + "type": "string" } }, "required": [ - "tenant", - "priority_id", - "ruleset", - "license_keys" - ] + "id", + "type", + "description", + "has_secret", + "credentials" + ], + "title": "Credentials", + "type": "object" } - } + }, + "required": [ + "data" + ], + "title": "SingleCredentialsModel", + "type": "object" } }, - "UserCustomerDTO": { + "SingleCustomerExcludedRules": { "content_type": "application/json", "schema": { - "title": "UserCustomerDTO", - "type": "object", "properties": { - "trace_id": { - "title": "Trace Id", - "type": "string" - }, - "items": { - "title": "Items", - "type": "array", - "items": { - "$ref": "#/definitions/UserCustomer" - } - } - }, - "required": [ - "trace_id", - "items" - ], - "definitions": { - "UserCustomer": { - "title": "UserCustomer", - "type": "object", + "data": { "properties": { - "customer": { - "title": "Customer", + "customer_name": { + "title": "Customer Name", "type": "string" + }, + "rules": { + "items": { + "type": "string" + }, + "title": "Rules", + "type": "array" } }, "required": [ - "customer" - ] - } - } - } - }, - "DojoApplicationListModel": { - "content_type": "application/json", - "schema": { - "title": "DojoApplicationListModel", - "type": "object", - "properties": { - "customer": { - "title": "Customer", - "type": "string" + "customer_name", + "rules" + ], + "title": "CustomerExcludedRules", + "type": "object" } }, - "additionalProperties": false + "required": [ + "data" + ], + "title": "SingleCustomerExcludedRules", + "type": "object" } }, - "JobReportDTO": { + "SingleDefeDojoModel": { "content_type": "application/json", "schema": { - "title": "JobReportDTO", - "type": "object", "properties": { - "trace_id": { - "title": "Trace Id", - "type": "string" - }, - "items": { - "title": "Items", - "type": "array", - "items": { - "$ref": "#/definitions/JobReport" - } - } - }, - "required": [ - "trace_id", - "items" - ], - "definitions": { - "JobReport": { - "title": "JobReport", - "type": "object", + "data": { "properties": { + "description": { + "title": "Description", + "type": "string" + }, + "host": { + "title": "Host", + "type": "string" + }, "id": { "title": "Id", "type": "string" }, - "type": { - "title": "Type", - "type": "string" + "port": { + "title": "Port", + "type": "integer" }, - "content": { - "title": "Content", - "type": "object" + "protocol": { + "enum": [ + "HTTP", + "HTTPS" + ], + "title": "Protocol", + "type": "string" }, - "href": { - "title": "Href", + "stage": { + "title": "Stage", "type": "string" } }, "required": [ "id", - "type" - ] + "description", + "host", + "port", + "stage", + "protocol" + ], + "title": "DefectDojo", + "type": "object" } - } + }, + "required": [ + "data" + ], + "title": "SingleDefeDojoModel", + "type": "object" } }, - "RuleReportDTO": { + "SingleDefectDojoActivation": { "content_type": "application/json", "schema": { - "title": "RuleReportDTO", - "type": "object", "properties": { - "trace_id": { - "title": "Trace Id", - "type": "string" - }, - "items": { - "title": "Items", - "type": "array", - "items": { - "anyOf": [ - { - "$ref": "#/definitions/JobRuleReport" - }, - { - "$ref": "#/definitions/TenantRuleReport" - } - ] - } - } - }, - "required": [ - "trace_id", - "items" - ], - "definitions": { - "JobRuleReport": { - "title": "JobRuleReport", - "type": "object", + "data": { "properties": { - "id": { - "title": "Id", - "type": "string" + "activated_for": { + "items": { + "type": "string" + }, + "title": "Activated For", + "type": "array" }, - "type": { - "title": "Type", + "activated_for_all": { + "title": "Activated For All", + "type": "boolean" + }, + "attachment": { + "anyOf": [ + { + "enum": [ + "json", + "xlsx", + "csv" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Attachment" + }, + "engagement": { + "title": "Engagement", "type": "string" }, - "content": { - "title": "Content", - "type": "array", - "items": {} + "excluding": { + "items": { + "type": "string" + }, + "title": "Excluding", + "type": "array" }, - "href": { - "title": "Href", + "product": { + "title": "Product", "type": "string" - } - }, - "required": [ - "id", - "type" - ] - }, - "TenantRuleReport": { - "title": "TenantRuleReport", - "type": "object", - "properties": { - "tenant": { - "title": "Tenant", + }, + "product_type": { + "title": "Product Type", "type": "string" }, - "content": { - "title": "Content", - "type": "array", - "items": {} + "scan_type": { + "enum": [ + "Generic Findings Import", + "Cloud Custodian Scan" + ], + "title": "Scan Type", + "type": "string" + }, + "send_after_job": { + "title": "Send After Job", + "type": "boolean" }, - "href": { - "title": "Href", + "test": { + "title": "Test", "type": "string" + }, + "within_clouds": { + "items": { + "type": "string" + }, + "title": "Within Clouds", + "type": "array" } }, "required": [ - "tenant" - ] + "activated_for_all", + "excluding", + "scan_type", + "product_type", + "product", + "engagement", + "test", + "send_after_job", + "attachment" + ], + "title": "DefectDojoActivation", + "type": "object" } - } + }, + "required": [ + "data" + ], + "title": "SingleDefectDojoActivation", + "type": "object" } }, - "UserRoleDTO": { + "SingleDefectDojoPushResult": { "content_type": "application/json", "schema": { - "title": "UserRoleDTO", - "type": "object", "properties": { - "trace_id": { - "title": "Trace Id", - "type": "string" - }, - "items": { - "title": "Items", - "type": "array", - "items": { - "$ref": "#/definitions/UserRole" - } - } - }, - "required": [ - "trace_id", - "items" - ], - "definitions": { - "UserRole": { - "title": "UserRole", - "type": "object", + "data": { "properties": { - "role": { - "title": "Role", + "attachment": { + "anyOf": [ + { + "enum": [ + "json", + "xlsx", + "csv" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Attachment" + }, + "dojo_integration_id": { + "title": "Dojo Integration Id", + "type": "string" + }, + "engagement_name": { + "title": "Engagement Name", + "type": "string" + }, + "error": { + "title": "Error", + "type": "string" + }, + "job_id": { + "title": "Job Id", + "type": "string" + }, + "platform_id": { + "title": "Platform Id", + "type": "string" + }, + "product_name": { + "title": "Product Name", + "type": "string" + }, + "product_type_name": { + "title": "Product Type Name", + "type": "string" + }, + "scan_type": { + "enum": [ + "Generic Findings Import", + "Cloud Custodian Scan" + ], + "title": "Scan Type", + "type": "string" + }, + "success": { + "title": "Success", + "type": "boolean" + }, + "tenant_name": { + "title": "Tenant Name", + "type": "string" + }, + "test_title": { + "title": "Test Title", "type": "string" } }, "required": [ - "role" - ] + "job_id", + "scan_type", + "product_type_name", + "product_name", + "engagement_name", + "test_title", + "tenant_name", + "dojo_integration_id", + "success", + "attachment" + ], + "title": "DojoPushResult", + "type": "object" } - } + }, + "required": [ + "data" + ], + "title": "SingleDefectDojoPushResult", + "type": "object" } }, - "PlatformK8sQuery": { + "SingleEntityReportModel": { "content_type": "application/json", "schema": { - "title": "PlatformK8sQuery", - "description": "Prepared event historically contains both some request meta and incoming\nbody on one level.\nYou can inherit your models from this one and use them directly as type\nannotations inside handlers (with validate_kwargs) decorator", - "type": "object", "properties": { - "httpMethod": { - "title": "Httpmethod", - "type": "string" - }, - "path": { - "title": "Path", - "type": "string" - }, - "user_id": { - "title": "User Id", - "type": "string" - }, - "user_role": { - "title": "User Role", - "type": "string" - }, - "user_customer": { - "title": "User Customer", - "type": "string" - }, - "customer": { - "title": "Customer", - "type": "string" - }, - "tenant": { - "title": "Tenant", - "type": "string" - }, - "tenants": { - "title": "Tenants", - "type": "array", - "items": { - "type": "string" - } - }, - "tenant_name": { - "title": "Tenant Name", - "type": "string" + "data": { + "properties": { + "content": { + "title": "Content", + "type": "object" + }, + "customer_name": { + "title": "Customer Name", + "type": "string" + }, + "format": { + "enum": [ + "json", + "xlsx" + ], + "title": "ReportFormat", + "type": "string" + }, + "platform_id": { + "title": "Platform Id", + "type": "string" + }, + "tenant_name": { + "title": "Tenant Name", + "type": "string" + }, + "url": { + "title": "Url", + "type": "string" + } + }, + "required": [ + "format", + "customer_name" + ], + "title": "BaseReportEntity", + "type": "object" } }, "required": [ - "httpMethod", - "path", - "user_id", - "user_role", - "user_customer" + "data" ], - "additionalProperties": false + "title": "SingleEntityReportModel", + "type": "object" } }, - "SiemPushDTO": { + "SingleHealthCheckModel": { "content_type": "application/json", "schema": { - "title": "SiemPushDTO", - "type": "object", "properties": { - "trace_id": { - "title": "Trace Id", - "type": "string" - }, - "items": { - "title": "Items", - "type": "array", - "items": { - "$ref": "#/definitions/SiemPush" - } - } - }, - "required": [ - "trace_id", - "items" - ], - "definitions": { - "SiemConfigurationType": { - "title": "SiemConfigurationType", - "description": "An enumeration.", - "enum": [ - "dojo", - "security_hub" - ], - "type": "string" - }, - "SiemPush": { - "title": "SiemPush", - "type": "object", + "data": { "properties": { - "job_id": { - "title": "Job Id", - "type": "string" + "details": { + "title": "Details", + "type": "object" }, - "type": { - "$ref": "#/definitions/SiemConfigurationType" + "id": { + "title": "Id", + "type": "string" }, - "status": { - "title": "Status", - "type": "integer" + "impact": { + "title": "Impact", + "type": "string" }, - "message": { - "title": "Message", + "remediation": { + "title": "Remediation", "type": "string" }, - "error": { - "title": "Error", + "status": { + "enum": [ + "OK", + "UNKNOWN", + "NOT_OK" + ], + "title": "HealthCheckStatus", "type": "string" } }, "required": [ - "job_id", - "type", - "status" - ] + "id", + "status", + "details" + ], + "title": "HealthCheck", + "type": "object" } - } + }, + "required": [ + "data" + ], + "title": "SingleHealthCheckModel", + "type": "object" } }, - "AccessApplicationPatchModel": { + "SingleJobModel": { "content_type": "application/json", "schema": { - "title": "AccessApplicationPatchModel", - "type": "object", + "description": "201 POST /jobs\n201 POST /jobs/k8s\n201 POST /jobs/standard\n200 GET /jobs/{job_id}", "properties": { - "application_id": { - "title": "Application Id", - "type": "string" - }, - "customer": { - "title": "Customer", - "type": "string" - }, - "description": { - "title": "Description", - "type": "string" - }, - "username": { - "title": "Username", - "type": "string" - }, - "password": { - "title": "Password", - "type": "string" - }, - "auto_resolve_access": { - "title": "Auto Resolve Access", - "default": false, - "type": "boolean" - }, - "url": { - "title": "Url", - "minLength": 1, - "maxLength": 65536, - "format": "uri", - "type": "string" - }, - "results_storage": { - "title": "Results Storage", - "type": "string" - } - }, - "required": [ - "application_id" - ], - "additionalProperties": false - } - }, - "LicenseManagerClientSettingPostModel": { - "content_type": "application/json", - "schema": { - "title": "LicenseManagerClientSettingPostModel", - "type": "object", - "properties": { - "key_id": { - "title": "Key Id", - "type": "string" - }, - "algorithm": { - "title": "Algorithm", - "type": "string" - }, - "private_key": { - "title": "Private Key", - "type": "string" - }, - "format": { - "default": "PEM", - "allOf": [ - { - "$ref": "#/definitions/KeyFormat" + "data": { + "properties": { + "created_at": { + "format": "date-time", + "title": "Created At", + "type": "string" + }, + "customer_name": { + "title": "Customer Name", + "type": "string" + }, + "id": { + "title": "Id", + "type": "string" + }, + "regions": { + "items": { + "type": "string" + }, + "title": "Regions", + "type": "array" + }, + "rulesets": { + "items": { + "type": "string" + }, + "title": "Rulesets", + "type": "array" + }, + "scheduled_rule_name": { + "title": "Scheduled Rule Name", + "type": "string" + }, + "started_at": { + "format": "date-time", + "title": "Started At", + "type": "string" + }, + "status": { + "description": "https://docs.aws.amazon.com/batch/latest/userguide/job_states.html", + "enum": [ + "SUBMITTED", + "PENDING", + "RUNNABLE", + "STARTING", + "RUNNING", + "FAILED", + "SUCCEEDED" + ], + "title": "JobState", + "type": "string" + }, + "stopped_at": { + "format": "date-time", + "title": "Stopped At", + "type": "string" + }, + "submitted_at": { + "title": "Submitted At", + "type": "string" + }, + "tenant_name": { + "title": "Tenant Name", + "type": "string" } - ] - }, - "b64_encoded": { - "title": "B64 Encoded", - "type": "boolean" + }, + "required": [ + "customer_name", + "id", + "regions", + "rulesets", + "status", + "submitted_at", + "tenant_name" + ], + "title": "Job", + "type": "object" } }, "required": [ - "key_id", - "algorithm", - "private_key", - "b64_encoded" + "data" ], - "additionalProperties": false, - "definitions": { - "KeyFormat": { - "title": "KeyFormat", - "description": "An enumeration.", - "enum": [ - "PEM" - ], - "type": "string" - } - } + "title": "SingleJobModel", + "type": "object" } }, - "UserTenantsDTO": { + "SingleJobReportModel": { "content_type": "application/json", "schema": { - "title": "UserTenantsDTO", - "type": "object", "properties": { - "trace_id": { - "title": "Trace Id", - "type": "string" - }, - "items": { - "title": "Items", - "type": "array", - "items": { - "$ref": "#/definitions/UserTenant" - } - } - }, - "required": [ - "trace_id", - "items" - ], - "definitions": { - "UserTenant": { - "title": "UserTenant", - "type": "object", + "data": { "properties": { - "tenants": { - "title": "Tenants", + "content": { + "title": "Content", + "type": "object" + }, + "customer_name": { + "title": "Customer Name", + "type": "string" + }, + "format": { + "enum": [ + "json", + "xlsx" + ], + "title": "ReportFormat", + "type": "string" + }, + "job_id": { + "title": "Job Id", + "type": "string" + }, + "job_type": { + "enum": [ + "manual", + "reactive" + ], + "title": "JobType", + "type": "string" + }, + "tenant_name": { + "title": "Tenant Name", + "type": "string" + }, + "url": { + "title": "Url", "type": "string" } }, "required": [ - "tenants" - ] + "format", + "tenant_name", + "customer_name" + ], + "title": "BaseReportJob", + "type": "object" } - } + }, + "required": [ + "data" + ], + "title": "SingleJobReportModel", + "type": "object" } }, - "ReportDTO": { + "SingleK8SPlatformModel": { "content_type": "application/json", "schema": { - "title": "ReportDTO", - "type": "object", "properties": { - "trace_id": { - "title": "Trace Id", - "type": "string" - }, - "items": { - "title": "Items", - "type": "array", - "items": { - "$ref": "#/definitions/Report" - } - } - }, - "required": [ - "trace_id", - "items" - ], - "definitions": { - "Report": { - "title": "Report", - "type": "object", + "data": { "properties": { - "bucket_name": { - "title": "Bucket Name", + "customer": { + "title": "Customer", + "type": "string" + }, + "description": { + "title": "Description", + "type": "string" + }, + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "region": { + "title": "Region", "type": "string" }, - "file_key": { - "title": "File Key", + "tenant_name": { + "title": "Tenant Name", "type": "string" }, - "presigned_url": { - "title": "Presigned Url", + "type": { + "enum": [ + "SELF_MANAGED", + "EKS", + "AKS", + "GKS" + ], + "title": "PlatformType", "type": "string" } }, "required": [ - "bucket_name", - "file_key", - "presigned_url" - ] + "customer", + "description", + "id", + "name", + "tenant_name", + "type" + ], + "title": "K8sPlatform", + "type": "object" } - } + }, + "required": [ + "data" + ], + "title": "SingleK8SPlatformModel", + "type": "object" } }, - "UserDTO": { + "SingleLMClientModel": { "content_type": "application/json", "schema": { - "title": "UserDTO", - "type": "object", "properties": { - "trace_id": { - "title": "Trace Id", - "type": "string" - }, - "items": { - "title": "Items", - "type": "array", - "items": { - "$ref": "#/definitions/User" - } - } - }, - "required": [ - "trace_id", - "items" - ], - "definitions": { - "User": { - "title": "User", - "type": "object", + "data": { "properties": { - "username": { - "title": "Username", + "algorithm": { + "title": "Algorithm", "type": "string" }, - "customer": { - "title": "Customer", + "b64_encoded": { + "title": "B64 Encoded", + "type": "boolean" + }, + "format": { + "title": "Format", "type": "string" }, - "role": { - "title": "Role", + "key_id": { + "title": "Key Id", "type": "string" }, - "tenants": { - "title": "Tenants", + "public_key": { + "title": "Public Key", "type": "string" } }, "required": [ - "username", - "customer", - "role" - ] - } - } - } - }, - "CredentialsManagerDeleteModel": { - "content_type": "application/json", - "schema": { - "title": "CredentialsManagerDeleteModel", - "type": "object", - "properties": { - "cloud_identifier": { - "title": "Cloud Identifier", - "type": "string" - }, - "cloud": { - "title": "Cloud", - "enum": [ - "AWS", - "AZURE", - "GCP" + "algorithm", + "b64_encoded", + "format", + "key_id", + "public_key" ], - "type": "string" + "title": "LicenseManagerClient", + "type": "object" } }, "required": [ - "cloud_identifier", - "cloud" + "data" ], - "additionalProperties": false + "title": "SingleLMClientModel", + "type": "object" } }, - "PlatformK8sNativePost": { + "SingleLMConfigModel": { "content_type": "application/json", "schema": { - "title": "PlatformK8sNativePost", - "description": "Prepared event historically contains both some request meta and incoming\nbody on one level.\nYou can inherit your models from this one and use them directly as type\nannotations inside handlers (with validate_kwargs) decorator", - "type": "object", "properties": { - "httpMethod": { - "title": "Httpmethod", - "type": "string" - }, - "path": { - "title": "Path", - "type": "string" - }, - "user_id": { - "title": "User Id", - "type": "string" - }, - "user_role": { - "title": "User Role", - "type": "string" - }, - "user_customer": { - "title": "User Customer", - "type": "string" - }, - "customer": { - "title": "Customer", - "type": "string" - }, - "tenant": { - "title": "Tenant", - "type": "string" - }, - "tenants": { - "title": "Tenants", - "type": "array", - "items": { - "type": "string" - } - }, - "tenant_name": { - "title": "Tenant Name", - "type": "string" - }, - "name": { - "title": "Name", - "type": "string" - }, - "endpoint": { - "title": "Endpoint", - "minLength": 1, - "maxLength": 2083, - "format": "uri", - "type": "string" - }, - "certificate_authority": { - "title": "Certificate Authority", - "type": "string" - }, - "token": { - "title": "Token", - "type": "string" - }, - "description": { - "title": "Description", - "type": "string" + "data": { + "properties": { + "host": { + "title": "Host", + "type": "string" + }, + "port": { + "title": "Port", + "type": "integer" + }, + "protocol": { + "enum": [ + "HTTP", + "HTTPS" + ], + "title": "Protocol", + "type": "string" + }, + "stage": { + "title": "Stage", + "type": "string" + } + }, + "required": [ + "host", + "port", + "protocol", + "stage" + ], + "title": "LicenseManagerConfig", + "type": "object" } }, "required": [ - "httpMethod", - "path", - "user_id", - "user_role", - "user_customer", - "tenant_name", - "name", - "endpoint", - "certificate_authority" + "data" ], - "additionalProperties": false + "title": "SingleLMConfigModel", + "type": "object" } }, - "UserTenantsPatchModel": { + "SingleLicenseActivationModel": { "content_type": "application/json", "schema": { - "title": "UserTenantsPatchModel", - "type": "object", "properties": { - "tenants": { - "title": "Tenants", - "type": "array", - "items": { - "type": "string" - } - }, - "target_user": { - "title": "Target User", - "type": "string" + "data": { + "properties": { + "activated_for": { + "items": { + "type": "string" + }, + "title": "Activated For", + "type": "array" + }, + "activated_for_all": { + "title": "Activated For All", + "type": "boolean" + }, + "excluding": { + "items": { + "type": "string" + }, + "title": "Excluding", + "type": "array" + }, + "within_clouds": { + "items": { + "type": "string" + }, + "title": "Within Clouds", + "type": "array" + } + }, + "required": [ + "activated_for_all", + "excluding" + ], + "title": "BaseActivation", + "type": "object" } }, "required": [ - "tenants" + "data" ], - "additionalProperties": false + "title": "SingleLicenseActivationModel", + "type": "object" } }, - "FindingsDTO": { + "SingleLicenseModel": { "content_type": "application/json", "schema": { - "title": "FindingsDTO", - "type": "object", "properties": { - "trace_id": { - "title": "Trace Id", - "type": "string" - }, - "items": { - "title": "Items", - "type": "array", - "items": { - "$ref": "#/definitions/Findings" - } - } - }, - "required": [ - "trace_id", - "items" - ], - "definitions": { - "Findings": { - "title": "Findings", - "type": "object", + "data": { "properties": { - "presigned_url": { - "title": "Presigned Url", + "allowance": { + "properties": { + "balance_exhaustion_model": { + "enum": [ + "collective", + "independent" + ], + "title": "Balance Exhaustion Model", + "type": "string" + }, + "job_balance": { + "title": "Job Balance", + "type": "integer" + }, + "time_range": { + "enum": [ + "DAY", + "WEEK", + "MONTH" + ], + "title": "Time Range", + "type": "string" + } + }, + "required": [ + "balance_exhaustion_model", + "job_balance", + "time_range" + ], + "title": "LicenseAllowance", + "type": "object" + }, + "event_driven": { + "properties": { + "active": { + "title": "Active", + "type": "boolean" + } + }, + "required": [ + "active" + ], + "title": "LicenseEventDriven", + "type": "object" + }, + "expiration": { + "format": "date-time", + "title": "Expiration", + "type": "string" + }, + "latest_sync": { + "format": "date-time", + "title": "Latest Sync", "type": "string" + }, + "license_key": { + "title": "License Key", + "type": "string" + }, + "ruleset_ids": { + "items": { + "type": "string" + }, + "title": "Ruleset Ids", + "type": "array" } }, "required": [ - "presigned_url" - ] + "allowance", + "event_driven", + "expiration", + "latest_sync", + "license_key", + "ruleset_ids" + ], + "title": "License", + "type": "object" } - } + }, + "required": [ + "data" + ], + "title": "SingleLicenseModel", + "type": "object" } }, - "PlatformK8SDTO": { + "SingleMailSettingModel": { "content_type": "application/json", "schema": { - "title": "PlatformK8SDTO", - "type": "object", "properties": { - "trace_id": { - "title": "Trace Id", - "type": "string" - }, - "items": { - "title": "Items", - "type": "array", - "items": { - "$ref": "#/definitions/K8sPlatform" - } - } - }, - "required": [ - "trace_id", - "items" - ], - "definitions": { - "PlatformType": { - "title": "PlatformType", - "description": "An enumeration.", - "enum": [ - "EKS", - "NATIVE" - ], - "type": "string" - }, - "K8sPlatform": { - "title": "K8sPlatform", - "type": "object", + "data": { "properties": { - "description": { - "title": "Description", + "default_sender": { + "title": "Default Sender", "type": "string" }, - "endpoint": { - "title": "Endpoint", + "host": { + "title": "Host", "type": "string" }, - "has_token": { - "title": "Has Token", - "type": "boolean" + "max_emails": { + "title": "Max Emails", + "type": "integer" }, - "id": { - "title": "Id", + "password": { + "title": "Password", "type": "string" }, - "name": { - "title": "Name", + "port": { + "title": "Port", "type": "string" }, - "tenant_name": { - "title": "Tenant Name", - "type": "string" + "use_tls": { + "title": "Use Tls", + "type": "boolean" }, - "type": { - "$ref": "#/definitions/PlatformType" + "username": { + "title": "Username", + "type": "string" } }, "required": [ - "description", - "has_token", - "id", - "name", - "tenant_name", - "type" - ] - } - } - } - }, - "ParentTenantLinkPostModel": { - "content_type": "application/json", - "schema": { - "title": "ParentTenantLinkPostModel", - "type": "object", - "properties": { - "tenant_name": { - "title": "Tenant Name", - "type": "string" - }, - "parent_id": { - "title": "Parent Id", - "type": "string" + "username", + "password", + "default_sender", + "host", + "port", + "max_emails", + "use_tls" + ], + "title": "MailSetting", + "type": "object" } }, "required": [ - "parent_id" + "data" ], - "additionalProperties": false + "title": "SingleMailSettingModel", + "type": "object" } }, - "RulesetDTO": { + "SinglePolicyModel": { "content_type": "application/json", "schema": { - "title": "RulesetDTO", - "type": "object", "properties": { - "trace_id": { - "title": "Trace Id", - "type": "string" - }, - "items": { - "title": "Items", - "type": "array", - "items": { - "$ref": "#/definitions/Ruleset" - } - } - }, - "required": [ - "trace_id", - "items" - ], - "definitions": { - "Cloud": { - "title": "Cloud", - "description": "An enumeration.", - "enum": [ - "aws", - "azure", - "gcp", - "GCP", - "AWS", - "AZURE" - ], - "type": "string" - }, - "Ruleset": { - "title": "Ruleset", - "type": "object", + "data": { "properties": { "customer": { "title": "Customer", "type": "string" }, - "name": { - "title": "Name", - "type": "string" + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Description" }, - "version": { - "title": "Version", + "effect": { + "enum": [ + "allow", + "deny" + ], + "title": "PolicyEffect", "type": "string" }, - "cloud": { - "$ref": "#/definitions/Cloud" - }, - "rules_number": { - "title": "Rules Number", - "type": "integer" - }, - "status_code": { - "title": "Status Code", + "name": { + "title": "Name", "type": "string" }, - "status_reason": { - "title": "Status Reason", - "type": "string" + "permissions": { + "items": { + "type": "string" + }, + "title": "Permissions", + "type": "array" }, - "event_driven": { - "title": "Event Driven", - "type": "boolean" - }, - "active": { - "title": "Active", - "type": "boolean" - }, - "license_keys": { - "title": "License Keys", - "type": "array", + "tenants": { "items": { "type": "string" - } - }, - "licensed": { - "title": "Licensed", - "type": "boolean" - }, - "status_last_update_time": { - "title": "Status Last Update Time", - "type": "string" - }, - "allowed_for": { - "title": "Allowed For", - "anyOf": [ - { - "type": "object" - }, - { - "type": "string" - } - ] + }, + "title": "Tenants", + "type": "array" } }, "required": [ "customer", "name", - "version", - "cloud", - "status_last_update_time" - ] - } - } - } - }, - "AccessApplicationDeleteModel": { - "content_type": "application/json", - "schema": { - "title": "AccessApplicationDeleteModel", - "type": "object", - "properties": { - "application_id": { - "title": "Application Id", - "type": "string" - }, - "customer": { - "title": "Customer", - "type": "string" + "permissions", + "effect", + "description", + "tenants" + ], + "title": "Policy", + "type": "object" } }, "required": [ - "application_id" + "data" ], - "additionalProperties": false + "title": "SinglePolicyModel", + "type": "object" } }, - "TenantPatchModel": { + "SingleRabbitMQModel": { "content_type": "application/json", "schema": { - "title": "TenantPatchModel", - "type": "object", "properties": { - "tenant_name": { - "title": "Tenant Name", - "type": "string" - }, - "rules_to_exclude": { - "title": "Rules To Exclude", - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true - }, - "rules_to_include": { - "title": "Rules To Include", - "type": "array", - "items": { - "type": "string" + "data": { + "properties": { + "customer": { + "title": "Customer", + "type": "string" + }, + "maestro_user": { + "title": "Maestro User", + "type": "string" + }, + "rabbit_exchange": { + "title": "Rabbit Exchange", + "type": "string" + }, + "request_queue": { + "title": "Request Queue", + "type": "string" + }, + "response_queue": { + "title": "Response Queue", + "type": "string" + }, + "sdk_access_key": { + "title": "Sdk Access Key", + "type": "string" + } }, - "uniqueItems": true + "required": [ + "maestro_user", + "request_queue", + "response_queue", + "sdk_access_key", + "customer" + ], + "title": "RabbitMQ", + "type": "object" } }, "required": [ - "tenant_name" + "data" ], - "additionalProperties": false - } - }, - "TimeRangedReportModel": { - "content_type": "application/json", - "schema": { - "title": "TimeRangedReportModel", - "description": "Base model which provides time-range constraint", - "type": "object", - "properties": { - "start_iso": { - "title": "Start Iso", - "type": "string", - "format": "date-time" - }, - "end_iso": { - "title": "End Iso", - "type": "string", - "format": "date-time" - } - }, - "additionalProperties": false - } - }, - "AccessApplicationPostModel": { - "content_type": "application/json", - "schema": { - "title": "AccessApplicationPostModel", - "type": "object", - "properties": { - "customer": { - "title": "Customer", - "type": "string" - }, - "description": { - "title": "Description", - "default": "Custodian access application", - "type": "string" - }, - "username": { - "title": "Username", - "type": "string" - }, - "password": { - "title": "Password", - "type": "string" - }, - "auto_resolve_access": { - "title": "Auto Resolve Access", - "default": false, - "type": "boolean" - }, - "url": { - "title": "Url", - "minLength": 1, - "maxLength": 65536, - "format": "uri", - "type": "string" - }, - "results_storage": { - "title": "Results Storage", - "type": "string" - } - }, - "additionalProperties": false + "title": "SingleRabbitMQModel", + "type": "object" } }, - "PolicyPostModel": { + "SingleRoleModel": { "content_type": "application/json", "schema": { - "title": "PolicyPostModel", - "type": "object", "properties": { - "name": { - "title": "Name", - "type": "string" - }, - "permissions": { - "title": "Permissions", - "type": "array", - "items": { - "type": "string", - "pattern": "^([\\w-]+|\\*):([\\w-]+|\\*)$" + "data": { + "properties": { + "customer": { + "title": "Customer", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Description" + }, + "expiration": { + "format": "date-time", + "title": "Expiration", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "policies": { + "items": { + "type": "string" + }, + "title": "Policies", + "type": "array" + } }, - "uniqueItems": true - }, - "customer": { - "title": "Customer", - "type": "string" + "required": [ + "customer", + "name", + "expiration", + "policies", + "description" + ], + "title": "Role", + "type": "object" } }, "required": [ - "name", - "permissions" + "data" ], - "additionalProperties": false + "title": "SingleRoleModel", + "type": "object" } }, - "RolePatchModel": { + "SingleRuleSourceModel": { "content_type": "application/json", "schema": { - "title": "RolePatchModel", - "type": "object", "properties": { - "name": { - "title": "Name", - "type": "string" - }, - "policies_to_attach": { - "title": "Policies To Attach", - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true - }, - "policies_to_detach": { - "title": "Policies To Detach", - "type": "array", - "items": { - "type": "string" + "data": { + "properties": { + "customer": { + "title": "Customer", + "type": "string" + }, + "description": { + "title": "Description", + "type": "string" + }, + "git_project_id": { + "title": "Git Project Id", + "type": "string" + }, + "git_ref": { + "title": "Git Ref", + "type": "string" + }, + "git_rules_prefix": { + "title": "Git Rules Prefix", + "type": "string" + }, + "git_url": { + "title": "Git Url", + "type": "string" + }, + "has_secret": { + "title": "Has Secret", + "type": "boolean" + }, + "id": { + "title": "Id", + "type": "string" + }, + "latest_sync": { + "properties": { + "current_status": { + "enum": [ + "SYNCED", + "SYNCING", + "SYNCING_FAILED" + ], + "title": "Current Status", + "type": "string" + }, + "sync_date": { + "format": "date-time", + "title": "Sync Date", + "type": "string" + } + }, + "required": [ + "current_status", + "sync_date" + ], + "title": "RuleSourceLatestSync", + "type": "object" + }, + "type": { + "enum": [ + "GITHUB", + "GITLAB" + ], + "title": "RuleSourceType", + "type": "string" + } }, - "uniqueItems": true - }, - "expiration": { - "title": "Expiration", - "type": "string", - "format": "date-time" - }, - "customer": { - "title": "Customer", - "type": "string" + "required": [ + "id", + "customer", + "git_project_id", + "git_url", + "git_ref", + "git_rules_prefix", + "description", + "has_secret", + "type" + ], + "title": "RuleSource", + "type": "object" } }, "required": [ - "name" + "data" ], - "additionalProperties": false + "title": "SingleRuleSourceModel", + "type": "object" } }, - "ParentPostModel": { + "SingleRulesetModel": { "content_type": "application/json", "schema": { - "title": "ParentPostModel", - "type": "object", "properties": { - "customer": { - "title": "Customer", - "type": "string" - }, - "application_id": { - "title": "Application Id", - "type": "string" - }, - "description": { - "title": "Description", - "default": "Custodian parent", - "type": "string" - }, - "clouds": { - "title": "Clouds", - "default": {}, - "type": "array", - "items": { - "enum": [ - "AWS", - "AZURE", - "GOOGLE" - ], - "type": "string" + "data": { + "properties": { + "active": { + "title": "Active", + "type": "boolean" + }, + "cloud": { + "enum": [ + "AWS", + "AZURE", + "GCP", + "KUBERNETES" + ], + "title": "RuleDomain", + "type": "string" + }, + "customer": { + "title": "Customer", + "type": "string" + }, + "last_update_time": { + "format": "date-time", + "title": "Last Update Time", + "type": "string" + }, + "license_keys": { + "items": { + "type": "string" + }, + "title": "License Keys", + "type": "array" + }, + "license_manager_id": { + "title": "License Manager Id", + "type": "string" + }, + "licensed": { + "title": "Licensed", + "type": "boolean" + }, + "name": { + "title": "Name", + "type": "string" + }, + "rules": { + "items": { + "type": "string" + }, + "title": "Rules", + "type": "array" + }, + "rules_number": { + "title": "Rules Number", + "type": "integer" + }, + "version": { + "title": "Version", + "type": "string" + } }, - "uniqueItems": true - }, - "scope": { - "title": "Scope", - "enum": [ - "SPECIFIC_TENANT", - "ALL" + "required": [ + "active", + "cloud", + "customer", + "last_update_time", + "license_keys", + "licensed", + "name", + "rules_number", + "version" ], - "type": "string" - }, - "rules_to_exclude": { - "title": "Rules To Exclude", - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true - }, - "type": { - "$ref": "#/definitions/ParentType" + "title": "Ruleset", + "type": "object" } }, "required": [ - "application_id", - "type" + "data" ], - "additionalProperties": false, - "definitions": { - "ParentType": { - "title": "ParentType", - "description": "An enumeration.", - "enum": [ - "CUSTODIAN", - "CUSTODIAN_LICENSES", - "SIEM_DEFECT_DOJO", - "CUSTODIAN_ACCESS" - ], - "type": "string" - } - } + "title": "SingleRulesetModel", + "type": "object" } }, - "RuleSourceDTO": { + "SingleScheduledJobModel": { "content_type": "application/json", "schema": { - "title": "RuleSourceDTO", - "type": "object", + "description": "201 POST /jobs\n201 POST /jobs/k8s\n201 POST /jobs/standard\n200 GET /jobs/{job_id}", "properties": { - "trace_id": { - "title": "Trace Id", - "type": "string" - }, - "items": { - "title": "Items", - "type": "array", - "items": { - "$ref": "#/definitions/RuleSource" - } - } - }, - "required": [ - "trace_id", - "items" - ], - "definitions": { - "RuleSourceSyncStatus": { - "title": "RuleSourceSyncStatus", - "description": "An enumeration.", - "enum": [ - "SYNCING", - "SYNCED" - ] - }, - "RuleSource": { - "title": "RuleSource", - "type": "object", + "data": { "properties": { - "id": { - "title": "Id", + "creation_date": { + "format": "date-time", + "title": "Creation Date", "type": "string" }, - "customer": { - "title": "Customer", + "customer_name": { + "title": "Customer Name", "type": "string" }, - "latest_sync_current_status": { - "$ref": "#/definitions/RuleSourceSyncStatus" + "enabled": { + "title": "Enabled", + "type": "boolean" }, - "git_url": { - "title": "Git Url", + "last_execution_time": { + "format": "date-time", + "title": "Last Execution Time", "type": "string" }, - "git_ref": { - "title": "Git Ref", + "name": { + "title": "Name", "type": "string" }, - "git_rules_prefix": { - "title": "Git Rules Prefix", - "type": "string" + "scan_regions": { + "items": { + "type": "string" + }, + "title": "Scan Regions", + "type": "array" }, - "latest_sync_sync_date": { - "title": "Latest Sync Sync Date", + "scan_rulesets": { + "items": { + "type": "string" + }, + "title": "Scan Rulesets", + "type": "array" + }, + "schedule": { + "title": "Schedule", "type": "string" }, - "restrict_from": { - "title": "Restrict From", - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "type": "string" - } - } + "tenant_name": { + "title": "Tenant Name", + "type": "string" } }, "required": [ - "id", - "customer", - "latest_sync_current_status", - "git_url", - "git_ref", - "git_rules_prefix" - ] + "name", + "customer_name", + "tenant_name", + "creation_date", + "enabled", + "schedule", + "scan_regions", + "scan_rulesets" + ], + "title": "ScheduledJob", + "type": "object" } - } + }, + "required": [ + "data" + ], + "title": "SingleScheduledJobModel", + "type": "object" } }, - "JobDeleteModel": { + "SingleSelfIntegration": { "content_type": "application/json", "schema": { - "title": "JobDeleteModel", - "type": "object", "properties": { - "job_id": { - "title": "Job Id", - "type": "string" - }, - "customer": { - "title": "Customer", - "type": "string" + "data": { + "properties": { + "activated_for": { + "items": { + "type": "string" + }, + "title": "Activated For", + "type": "array" + }, + "activated_for_all": { + "title": "Activated For All", + "type": "boolean" + }, + "customer_name": { + "title": "Customer Name", + "type": "string" + }, + "description": { + "title": "Description", + "type": "string" + }, + "excluding": { + "items": { + "type": "string" + }, + "title": "Excluding", + "type": "array" + }, + "host": { + "title": "Host", + "type": "string" + }, + "port": { + "title": "Port", + "type": "integer" + }, + "protocol": { + "enum": [ + "HTTP", + "HTTPS" + ], + "title": "Protocol", + "type": "string" + }, + "results_storage": { + "title": "Results Storage", + "type": "string" + }, + "stage": { + "title": "Stage", + "type": "string" + }, + "username": { + "title": "Username", + "type": "string" + }, + "within_clouds": { + "items": { + "type": "string" + }, + "title": "Within Clouds", + "type": "array" + } + }, + "required": [ + "activated_for_all", + "excluding", + "customer_name", + "description", + "username", + "host", + "stage", + "port", + "protocol" + ], + "title": "SelfIntegration", + "type": "object" } }, "required": [ - "job_id" + "data" ], - "additionalProperties": false + "title": "SingleSelfIntegration", + "type": "object" } }, - "LicenseSyncPostModel": { + "SingleTenantExcludedRules": { "content_type": "application/json", "schema": { - "title": "LicenseSyncPostModel", - "type": "object", "properties": { - "license_key": { - "title": "License Key", - "type": "string" + "data": { + "properties": { + "rules": { + "items": { + "type": "string" + }, + "title": "Rules", + "type": "array" + }, + "tenant_name": { + "title": "Tenant Name", + "type": "string" + } + }, + "required": [ + "tenant_name", + "rules" + ], + "title": "TenantExcludedRules", + "type": "object" } }, "required": [ - "license_key" + "data" ], - "additionalProperties": false + "title": "SingleTenantExcludedRules", + "type": "object" } }, - "ParentPatchModel": { + "SingleTenantsModel": { "content_type": "application/json", "schema": { - "title": "ParentPatchModel", - "type": "object", "properties": { - "parent_id": { - "title": "Parent Id", - "type": "string" - }, - "application_id": { - "title": "Application Id", - "type": "string" - }, - "description": { - "title": "Description", - "type": "string" - }, - "clouds": { - "title": "Clouds", - "type": "array", - "items": { - "enum": [ - "AWS", - "AZURE", - "GOOGLE" - ], - "type": "string" - }, - "uniqueItems": true - }, - "scope": { - "title": "Scope", - "enum": [ - "SPECIFIC_TENANT", - "ALL" - ], - "type": "string" - }, - "rules_to_exclude": { - "title": "Rules To Exclude", - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true - }, - "rules_to_include": { - "title": "Rules To Include", - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true - }, - "customer": { - "title": "Customer", - "type": "string" - } - }, - "required": [ - "parent_id" - ], - "additionalProperties": false - } - }, - "RabbitMQPostModel": { - "content_type": "application/json", - "schema": { - "title": "RabbitMQPostModel", - "type": "object", - "properties": { - "customer": { - "title": "Customer", - "type": "string" - }, - "maestro_user": { - "title": "Maestro User", - "type": "string" - }, - "rabbit_exchange": { - "title": "Rabbit Exchange", - "type": "string" - }, - "request_queue": { - "title": "Request Queue", - "type": "string" - }, - "response_queue": { - "title": "Response Queue", - "type": "string" - }, - "sdk_access_key": { - "title": "Sdk Access Key", - "type": "string" - }, - "connection_url": { - "title": "Connection Url", - "minLength": 1, - "maxLength": 65536, - "format": "uri", - "type": "string" - }, - "sdk_secret_key": { - "title": "Sdk Secret Key", - "type": "string" - } - }, - "required": [ - "maestro_user", - "request_queue", - "response_queue", - "sdk_access_key", - "connection_url", - "sdk_secret_key" - ], - "additionalProperties": false - } - }, - "K8sJobPostModel": { - "content_type": "application/json", - "schema": { - "title": "K8sJobPostModel", - "description": "K8s platform job", - "type": "object", - "properties": { - "httpMethod": { - "title": "Httpmethod", - "type": "string" - }, - "path": { - "title": "Path", - "type": "string" - }, - "user_id": { - "title": "User Id", - "type": "string" - }, - "user_role": { - "title": "User Role", - "type": "string" - }, - "user_customer": { - "title": "User Customer", - "type": "string" - }, - "customer": { - "title": "Customer", - "type": "string" - }, - "tenant": { - "title": "Tenant", - "type": "string" - }, - "tenants": { - "title": "Tenants", - "type": "array", - "items": { - "type": "string" - } - }, - "platform_id": { - "title": "Platform Id", - "type": "string" - }, - "target_rulesets": { - "title": "Target Rulesets", - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true - }, - "token": { - "title": "Token", - "type": "string" - } - }, - "required": [ - "httpMethod", - "path", - "user_id", - "user_role", - "user_customer", - "platform_id" - ], - "additionalProperties": false - } - }, - "LicenseDeleteModel": { - "content_type": "application/json", - "schema": { - "title": "LicenseDeleteModel", - "type": "object", - "properties": { - "license_key": { - "title": "License Key", - "type": "string" - }, - "customer": { - "title": "Customer", - "type": "string" - } - }, - "required": [ - "license_key" - ], - "additionalProperties": false - } - }, - "EventDrivenRulesetDeleteModel": { - "content_type": "application/json", - "schema": { - "title": "EventDrivenRulesetDeleteModel", - "description": "Prepared event historically contains both some request meta and incoming\nbody on one level.\nYou can inherit your models from this one and use them directly as type\nannotations inside handlers (with validate_kwargs) decorator", - "type": "object", - "properties": { - "httpMethod": { - "title": "Httpmethod", - "type": "string" - }, - "path": { - "title": "Path", - "type": "string" - }, - "user_id": { - "title": "User Id", - "type": "string" - }, - "user_role": { - "title": "User Role", - "type": "string" - }, - "user_customer": { - "title": "User Customer", - "type": "string" - }, - "customer": { - "title": "Customer", - "type": "string" - }, - "tenant": { - "title": "Tenant", - "type": "string" - }, - "tenants": { - "title": "Tenants", - "type": "array", - "items": { - "type": "string" - } - }, - "cloud": { - "$ref": "#/definitions/RuleDomain" - }, - "version": { - "title": "Version", - "type": "number" - } - }, - "required": [ - "httpMethod", - "path", - "user_id", - "user_role", - "user_customer", - "cloud", - "version" - ], - "additionalProperties": false, - "definitions": { - "RuleDomain": { - "title": "RuleDomain", - "description": "An enumeration.", - "enum": [ - "AWS", - "AZURE", - "GCP", - "KUBERNETES" - ], - "type": "string" - } - } - } - }, - "RoleDTO": { - "content_type": "application/json", - "schema": { - "title": "RoleDTO", - "type": "object", - "properties": { - "trace_id": { - "title": "Trace Id", - "type": "string" - }, - "items": { - "title": "Items", - "type": "array", - "items": { - "$ref": "#/definitions/Role" - } - } - }, - "required": [ - "trace_id", - "items" - ], - "definitions": { - "Role": { - "title": "Role", - "type": "object", - "properties": { - "customer": { - "title": "Customer", - "type": "string" - }, - "name": { - "title": "Name", - "type": "string" - }, - "expiration": { - "title": "Expiration", - "type": "string" - }, - "policies": { - "title": "Policies", - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": [ - "customer", - "name", - "expiration", - "policies" - ] - } - } - } - }, - "RuleDeleteModel": { - "content_type": "application/json", - "schema": { - "title": "RuleDeleteModel", - "description": "Prepared event historically contains both some request meta and incoming\nbody on one level.\nYou can inherit your models from this one and use them directly as type\nannotations inside handlers (with validate_kwargs) decorator", - "type": "object", - "properties": { - "httpMethod": { - "title": "Httpmethod", - "type": "string" - }, - "path": { - "title": "Path", - "type": "string" - }, - "user_id": { - "title": "User Id", - "type": "string" - }, - "user_role": { - "title": "User Role", - "type": "string" - }, - "user_customer": { - "title": "User Customer", - "type": "string" - }, - "customer": { - "title": "Customer", - "type": "string" - }, - "tenant": { - "title": "Tenant", - "type": "string" - }, - "tenants": { - "title": "Tenants", - "type": "array", - "items": { - "type": "string" - } - }, - "rule": { - "title": "Rule", - "type": "string" - }, - "cloud": { - "$ref": "#/definitions/RuleDomain" - }, - "git_project_id": { - "title": "Git Project Id", - "type": "string" - }, - "git_ref": { - "title": "Git Ref", - "type": "string" - } - }, - "required": [ - "httpMethod", - "path", - "user_id", - "user_role", - "user_customer" - ], - "additionalProperties": false, - "definitions": { - "RuleDomain": { - "title": "RuleDomain", - "description": "An enumeration.", - "enum": [ - "AWS", - "AZURE", - "GCP", - "KUBERNETES" - ], - "type": "string" - } - } - } - }, - "CredentialsManagerPostModel": { - "content_type": "application/json", - "schema": { - "title": "CredentialsManagerPostModel", - "type": "object", - "properties": { - "cloud_identifier": { - "title": "Cloud Identifier", - "type": "string" - }, - "trusted_role_arn": { - "title": "Trusted Role Arn", - "type": "string" - }, - "cloud": { - "title": "Cloud", - "enum": [ - "AWS", - "AZURE", - "GCP" - ], - "type": "string" - }, - "enabled": { - "title": "Enabled", - "default": true, - "type": "boolean" - } - }, - "required": [ - "cloud_identifier", - "trusted_role_arn", - "cloud" - ], - "additionalProperties": false - } - }, - "RuleSourcePatchModel": { - "content_type": "application/json", - "schema": { - "title": "RuleSourcePatchModel", - "type": "object", - "properties": { - "id": { - "title": "Id", - "type": "string" - }, - "customer": { - "title": "Customer", - "type": "string" - }, - "git_access_type": { - "$ref": "#/definitions/GitAccessType" - }, - "git_access_secret": { - "title": "Git Access Secret", - "type": "string" - }, - "tenant_allowance": { - "title": "Tenant Allowance", - "type": "array", - "items": {} - }, - "tenant_restriction": { - "title": "Tenant Restriction", - "type": "array", - "items": {} - }, - "description": { - "title": "Description", - "type": "string" - } - }, - "required": [ - "id" - ], - "additionalProperties": false, - "definitions": { - "GitAccessType": { - "title": "GitAccessType", - "description": "An enumeration.", - "enum": [ - "TOKEN" - ], - "type": "string" - } - } - } - }, - "UserRoleDeleteModel": { - "content_type": "application/json", - "schema": { - "title": "UserRoleDeleteModel", - "type": "object", - "properties": { - "target_user": { - "title": "Target User", - "type": "string" - } - }, - "additionalProperties": false - } - }, - "RoleDeleteModel": { - "content_type": "application/json", - "schema": { - "title": "RoleDeleteModel", - "type": "object", - "properties": { - "name": { - "title": "Name", - "type": "string" - }, - "customer": { - "title": "Customer", - "type": "string" - } - }, - "required": [ - "name" - ], - "additionalProperties": false - } - }, - "ParentTenantLinkDeleteModel": { - "content_type": "application/json", - "schema": { - "title": "ParentTenantLinkDeleteModel", - "type": "object", - "properties": { - "tenant_name": { - "title": "Tenant Name", - "type": "string" - }, - "type": { - "$ref": "#/definitions/ParentType" - } - }, - "required": [ - "type" - ], - "additionalProperties": false, - "definitions": { - "ParentType": { - "title": "ParentType", - "description": "An enumeration.", - "enum": [ - "CUSTODIAN", - "CUSTODIAN_LICENSES", - "SIEM_DEFECT_DOJO", - "CUSTODIAN_ACCESS" - ], - "type": "string" - } - } - } - }, - "PreparedEvent": { - "content_type": "application/json", - "schema": { - "title": "PreparedEvent", - "description": "Prepared event historically contains both some request meta and incoming\nbody on one level.\nYou can inherit your models from this one and use them directly as type\nannotations inside handlers (with validate_kwargs) decorator", - "type": "object", - "properties": { - "httpMethod": { - "title": "Httpmethod", - "type": "string" - }, - "path": { - "title": "Path", - "type": "string" - }, - "user_id": { - "title": "User Id", - "type": "string" - }, - "user_role": { - "title": "User Role", - "type": "string" - }, - "user_customer": { - "title": "User Customer", - "type": "string" - }, - "customer": { - "title": "Customer", - "type": "string" - }, - "tenant": { - "title": "Tenant", - "type": "string" - }, - "tenants": { - "title": "Tenants", - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": [ - "httpMethod", - "path", - "user_id", - "user_role", - "user_customer" - ], - "additionalProperties": false - } - }, - "RulesetPatchModel": { - "content_type": "application/json", - "schema": { - "title": "RulesetPatchModel", - "description": "Prepared event historically contains both some request meta and incoming\nbody on one level.\nYou can inherit your models from this one and use them directly as type\nannotations inside handlers (with validate_kwargs) decorator", - "type": "object", - "properties": { - "httpMethod": { - "title": "Httpmethod", - "type": "string" - }, - "path": { - "title": "Path", - "type": "string" - }, - "user_id": { - "title": "User Id", - "type": "string" - }, - "user_role": { - "title": "User Role", - "type": "string" - }, - "user_customer": { - "title": "User Customer", - "type": "string" - }, - "customer": { - "title": "Customer", - "type": "string" - }, - "tenant": { - "title": "Tenant", - "type": "string" - }, - "tenants": { - "title": "Tenants", - "type": "array", - "items": { - "type": "string" - } - }, - "name": { - "title": "Name", - "type": "string" - }, - "version": { - "title": "Version", - "type": "string" - }, - "rules_to_attach": { - "title": "Rules To Attach", - "default": [], - "type": "array", - "items": {}, - "uniqueItems": true - }, - "rules_to_detach": { - "title": "Rules To Detach", - "default": [], - "type": "array", - "items": {}, - "uniqueItems": true - }, - "active": { - "title": "Active", - "type": "boolean" - }, - "tenant_allowance": { - "title": "Tenant Allowance", - "default": [], - "type": "array", - "items": {} - }, - "tenant_restriction": { - "title": "Tenant Restriction", - "default": [], - "type": "array", - "items": {} - } - }, - "required": [ - "httpMethod", - "path", - "user_id", - "user_role", - "user_customer", - "name", - "version" - ], - "additionalProperties": false - } - }, - "PolicyCacheDeleteModel": { - "content_type": "application/json", - "schema": { - "title": "PolicyCacheDeleteModel", - "type": "object", - "properties": { - "name": { - "title": "Name", - "type": "string" - }, - "customer": { - "title": "Customer", - "type": "string" - } - }, - "additionalProperties": false - } - }, - "UnauthorizedResponseModel": { - "content_type": "application/json", - "schema": { - "title": "UnauthorizedResponseModel", - "type": "object", - "properties": { - "message": { - "title": "Message", - "type": "string" - } - }, - "required": [ - "message" - ] - } - }, - "StandardJobPostModel": { - "content_type": "application/json", - "schema": { - "title": "StandardJobPostModel", - "description": "standard jobs means not licensed job -> without licensed rule-sets", - "type": "object", - "properties": { - "credentials": { - "title": "Credentials", - "anyOf": [ - { - "$ref": "#/definitions/AWSCredentials" - }, - { - "$ref": "#/definitions/AZURECredentials" - }, - { - "$ref": "#/definitions/GOOGLECredentials1" - }, - { - "$ref": "#/definitions/GOOGLECredentials2" - }, - { - "$ref": "#/definitions/GOOGLECredentials3" - } - ] - }, - "tenant_name": { - "title": "Tenant Name", - "type": "string" - }, - "target_rulesets": { - "title": "Target Rulesets", - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true - }, - "target_regions": { - "title": "Target Regions", - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true - } - }, - "additionalProperties": false, - "definitions": { - "AWSCredentials": { - "title": "AWSCredentials", - "type": "object", - "properties": { - "AWS_ACCESS_KEY_ID": { - "title": "Aws Access Key Id", - "type": "string" - }, - "AWS_SECRET_ACCESS_KEY": { - "title": "Aws Secret Access Key", - "type": "string" - }, - "AWS_SESSION_TOKEN": { - "title": "Aws Session Token", - "type": "string" - }, - "AWS_DEFAULT_REGION": { - "title": "Aws Default Region", - "default": "us-east-1", - "type": "string" - } - }, - "required": [ - "AWS_ACCESS_KEY_ID", - "AWS_SECRET_ACCESS_KEY" - ], - "additionalProperties": false - }, - "AZURECredentials": { - "title": "AZURECredentials", - "type": "object", - "properties": { - "AZURE_TENANT_ID": { - "title": "Azure Tenant Id", - "type": "string" - }, - "AZURE_CLIENT_ID": { - "title": "Azure Client Id", - "type": "string" - }, - "AZURE_CLIENT_SECRET": { - "title": "Azure Client Secret", - "type": "string" - }, - "AZURE_SUBSCRIPTION_ID": { - "title": "Azure Subscription Id", - "type": "string" - } - }, - "required": [ - "AZURE_TENANT_ID", - "AZURE_CLIENT_ID", - "AZURE_CLIENT_SECRET" - ], - "additionalProperties": false - }, - "GOOGLECredentials1": { - "title": "GOOGLECredentials1", - "type": "object", - "properties": { - "type": { - "title": "Type", - "type": "string" - }, - "project_id": { - "title": "Project Id", - "type": "string" - }, - "private_key_id": { - "title": "Private Key Id", - "type": "string" - }, - "private_key": { - "title": "Private Key", - "type": "string" - }, - "client_email": { - "title": "Client Email", - "type": "string" - }, - "client_id": { - "title": "Client Id", - "type": "string" - }, - "auth_uri": { - "title": "Auth Uri", - "type": "string" - }, - "token_uri": { - "title": "Token Uri", - "type": "string" - }, - "auth_provider_x509_cert_url": { - "title": "Auth Provider X509 Cert Url", - "type": "string" - }, - "client_x509_cert_url": { - "title": "Client X509 Cert Url", - "type": "string" - } - }, - "required": [ - "type", - "project_id", - "private_key_id", - "private_key", - "client_email", - "client_id", - "auth_uri", - "token_uri", - "auth_provider_x509_cert_url", - "client_x509_cert_url" - ] - }, - "GOOGLECredentials2": { - "title": "GOOGLECredentials2", - "type": "object", + "data": { "properties": { - "type": { - "title": "Type", - "type": "string" - }, - "access_token": { - "title": "Access Token", + "account_id": { + "title": "Account Id", "type": "string" }, - "refresh_token": { - "title": "Refresh Token", + "activation_date": { + "format": "date-time", + "title": "Activation Date", "type": "string" }, - "client_id": { - "title": "Client Id", + "customer_name": { + "title": "Customer Name", "type": "string" }, - "client_secret": { - "title": "Client Secret", - "type": "string" + "is_active": { + "title": "Is Active", + "type": "boolean" }, - "project_id": { - "title": "Project Id", - "type": "string" - } - }, - "required": [ - "type", - "access_token", - "refresh_token", - "client_id", - "client_secret", - "project_id" - ], - "additionalProperties": false - }, - "GOOGLECredentials3": { - "title": "GOOGLECredentials3", - "type": "object", - "properties": { - "access_token": { - "title": "Access Token", + "name": { + "title": "Name", "type": "string" }, - "project_id": { - "title": "Project Id", - "type": "string" + "regions": { + "items": { + "type": "string" + }, + "title": "Regions", + "type": "array" } }, "required": [ - "access_token", - "project_id" + "account_id", + "activation_date", + "customer_name", + "is_active", + "name", + "regions" ], - "additionalProperties": false + "title": "Tenant", + "type": "object" } - } + }, + "required": [ + "data" + ], + "title": "SingleTenantsModel", + "type": "object" } }, - "SignUpPostModel": { + "SingleUserModel": { "content_type": "application/json", "schema": { - "title": "SignUpPostModel", - "type": "object", "properties": { - "username": { - "title": "Username", - "type": "string" - }, - "password": { - "title": "Password", - "type": "string" - }, - "customer": { - "title": "Customer", - "type": "string" - }, - "role": { - "title": "Role", - "type": "string" - }, - "tenants": { - "title": "Tenants", - "type": "array", - "items": { - "type": "string" - } + "data": { + "properties": { + "created_at": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Created At" + }, + "customer": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Customer" + }, + "latest_login": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Latest Login" + }, + "role": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Role" + }, + "username": { + "title": "Username", + "type": "string" + } + }, + "required": [ + "username", + "customer", + "role", + "latest_login", + "created_at" + ], + "title": "User", + "type": "object" } }, "required": [ - "username", - "password", - "customer", - "role" + "data" ], - "additionalProperties": false + "title": "SingleUserModel", + "type": "object" } }, - "JobPostModel": { + "StandardJobPostModel": { "content_type": "application/json", "schema": { - "title": "JobPostModel", - "type": "object", + "description": "standard jobs means not licensed job -> without licensed rule-sets", "properties": { "credentials": { - "title": "Credentials", "anyOf": [ { - "$ref": "#/definitions/AWSCredentials" + "properties": { + "AWS_ACCESS_KEY_ID": { + "title": "Aws Access Key Id", + "type": "string" + }, + "AWS_DEFAULT_REGION": { + "default": "us-east-1", + "title": "Aws Default Region", + "type": "string" + }, + "AWS_SECRET_ACCESS_KEY": { + "title": "Aws Secret Access Key", + "type": "string" + }, + "AWS_SESSION_TOKEN": { + "default": null, + "title": "Aws Session Token", + "type": "string" + } + }, + "required": [ + "AWS_ACCESS_KEY_ID", + "AWS_SECRET_ACCESS_KEY" + ], + "title": "AWSCredentials", + "type": "object" }, { - "$ref": "#/definitions/AZURECredentials" + "properties": { + "AZURE_CLIENT_ID": { + "title": "Azure Client Id", + "type": "string" + }, + "AZURE_CLIENT_SECRET": { + "title": "Azure Client Secret", + "type": "string" + }, + "AZURE_SUBSCRIPTION_ID": { + "default": null, + "title": "Azure Subscription Id", + "type": "string" + }, + "AZURE_TENANT_ID": { + "title": "Azure Tenant Id", + "type": "string" + } + }, + "required": [ + "AZURE_TENANT_ID", + "AZURE_CLIENT_ID", + "AZURE_CLIENT_SECRET" + ], + "title": "AZURECredentials", + "type": "object" }, { - "$ref": "#/definitions/GOOGLECredentials1" + "properties": { + "auth_provider_x509_cert_url": { + "title": "Auth Provider X509 Cert Url", + "type": "string" + }, + "auth_uri": { + "title": "Auth Uri", + "type": "string" + }, + "client_email": { + "title": "Client Email", + "type": "string" + }, + "client_id": { + "title": "Client Id", + "type": "string" + }, + "client_x509_cert_url": { + "title": "Client X509 Cert Url", + "type": "string" + }, + "private_key": { + "title": "Private Key", + "type": "string" + }, + "private_key_id": { + "title": "Private Key Id", + "type": "string" + }, + "project_id": { + "title": "Project Id", + "type": "string" + }, + "token_uri": { + "title": "Token Uri", + "type": "string" + }, + "type": { + "title": "Type", + "type": "string" + } + }, + "required": [ + "type", + "project_id", + "private_key_id", + "private_key", + "client_email", + "client_id", + "auth_uri", + "token_uri", + "auth_provider_x509_cert_url", + "client_x509_cert_url" + ], + "title": "GOOGLECredentials1", + "type": "object" }, { - "$ref": "#/definitions/GOOGLECredentials2" + "properties": { + "access_token": { + "title": "Access Token", + "type": "string" + }, + "client_id": { + "title": "Client Id", + "type": "string" + }, + "client_secret": { + "title": "Client Secret", + "type": "string" + }, + "project_id": { + "title": "Project Id", + "type": "string" + }, + "refresh_token": { + "title": "Refresh Token", + "type": "string" + }, + "type": { + "title": "Type", + "type": "string" + } + }, + "required": [ + "type", + "access_token", + "refresh_token", + "client_id", + "client_secret", + "project_id" + ], + "title": "GOOGLECredentials2", + "type": "object" }, { - "$ref": "#/definitions/GOOGLECredentials3" - } - ] - }, - "tenant_name": { - "title": "Tenant Name", - "type": "string" - }, - "target_rulesets": { - "title": "Target Rulesets", - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true - }, - "target_regions": { - "title": "Target Regions", - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true - }, - "rules_to_scan": { - "title": "Rules To Scan", - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true - }, - "customer": { - "title": "Customer", - "type": "string" - } - }, - "additionalProperties": false, - "definitions": { - "AWSCredentials": { - "title": "AWSCredentials", - "type": "object", - "properties": { - "AWS_ACCESS_KEY_ID": { - "title": "Aws Access Key Id", - "type": "string" - }, - "AWS_SECRET_ACCESS_KEY": { - "title": "Aws Secret Access Key", - "type": "string" - }, - "AWS_SESSION_TOKEN": { - "title": "Aws Session Token", - "type": "string" - }, - "AWS_DEFAULT_REGION": { - "title": "Aws Default Region", - "default": "us-east-1", - "type": "string" - } - }, - "required": [ - "AWS_ACCESS_KEY_ID", - "AWS_SECRET_ACCESS_KEY" - ], - "additionalProperties": false - }, - "AZURECredentials": { - "title": "AZURECredentials", - "type": "object", - "properties": { - "AZURE_TENANT_ID": { - "title": "Azure Tenant Id", - "type": "string" - }, - "AZURE_CLIENT_ID": { - "title": "Azure Client Id", - "type": "string" - }, - "AZURE_CLIENT_SECRET": { - "title": "Azure Client Secret", - "type": "string" - }, - "AZURE_SUBSCRIPTION_ID": { - "title": "Azure Subscription Id", - "type": "string" - } - }, - "required": [ - "AZURE_TENANT_ID", - "AZURE_CLIENT_ID", - "AZURE_CLIENT_SECRET" - ], - "additionalProperties": false - }, - "GOOGLECredentials1": { - "title": "GOOGLECredentials1", - "type": "object", - "properties": { - "type": { - "title": "Type", - "type": "string" - }, - "project_id": { - "title": "Project Id", - "type": "string" - }, - "private_key_id": { - "title": "Private Key Id", - "type": "string" - }, - "private_key": { - "title": "Private Key", - "type": "string" - }, - "client_email": { - "title": "Client Email", - "type": "string" - }, - "client_id": { - "title": "Client Id", - "type": "string" - }, - "auth_uri": { - "title": "Auth Uri", - "type": "string" - }, - "token_uri": { - "title": "Token Uri", - "type": "string" - }, - "auth_provider_x509_cert_url": { - "title": "Auth Provider X509 Cert Url", - "type": "string" - }, - "client_x509_cert_url": { - "title": "Client X509 Cert Url", - "type": "string" - } - }, - "required": [ - "type", - "project_id", - "private_key_id", - "private_key", - "client_email", - "client_id", - "auth_uri", - "token_uri", - "auth_provider_x509_cert_url", - "client_x509_cert_url" - ] - }, - "GOOGLECredentials2": { - "title": "GOOGLECredentials2", - "type": "object", - "properties": { - "type": { - "title": "Type", - "type": "string" - }, - "access_token": { - "title": "Access Token", - "type": "string" - }, - "refresh_token": { - "title": "Refresh Token", - "type": "string" - }, - "client_id": { - "title": "Client Id", - "type": "string" - }, - "client_secret": { - "title": "Client Secret", - "type": "string" - }, - "project_id": { - "title": "Project Id", - "type": "string" + "properties": { + "access_token": { + "title": "Access Token", + "type": "string" + }, + "project_id": { + "title": "Project Id", + "type": "string" + } + }, + "required": [ + "access_token", + "project_id" + ], + "title": "GOOGLECredentials3", + "type": "object" } - }, - "required": [ - "type", - "access_token", - "refresh_token", - "client_id", - "client_secret", - "project_id" ], - "additionalProperties": false + "default": null, + "title": "Credentials" }, - "GOOGLECredentials3": { - "title": "GOOGLECredentials3", - "type": "object", - "properties": { - "access_token": { - "title": "Access Token", - "type": "string" - }, - "project_id": { - "title": "Project Id", - "type": "string" - } + "target_regions": { + "items": { + "type": "string" }, - "required": [ - "access_token", - "project_id" - ], - "additionalProperties": false - } - } - } - }, - "ReportPushMultipleModel": { - "content_type": "application/json", - "schema": { - "title": "ReportPushMultipleModel", - "description": "/reports/push/dojo\n/reports/push/security-hub", - "type": "object", - "properties": { - "start_iso": { - "title": "Start Iso", - "type": "string", - "format": "date-time" + "title": "Target Regions", + "type": "array", + "uniqueItems": true }, - "end_iso": { - "title": "End Iso", - "type": "string", - "format": "date-time" + "target_rulesets": { + "items": { + "type": "string" + }, + "title": "Target Rulesets", + "type": "array", + "uniqueItems": true }, "tenant_name": { "title": "Tenant Name", "type": "string" - }, - "customer": { - "title": "Customer", - "type": "string" - }, - "type": { - "$ref": "#/definitions/JobType" } }, "required": [ "tenant_name" ], - "additionalProperties": false, - "definitions": { - "JobType": { - "title": "JobType", - "description": "An enumeration.", - "enum": [ - "manual", - "reactive" - ], - "type": "string" - } - } + "title": "StandardJobPostModel", + "type": "object" } }, - "PolicyDTO": { + "TenantExcludedRulesPutModel": { "content_type": "application/json", "schema": { - "title": "PolicyDTO", - "type": "object", "properties": { - "trace_id": { - "title": "Trace Id", - "type": "string" - }, - "items": { - "title": "Items", - "type": "array", + "rules": { "items": { - "$ref": "#/definitions/Policy" - } - } - }, - "required": [ - "trace_id", - "items" - ], - "definitions": { - "Policy": { - "title": "Policy", - "type": "object", - "properties": { - "customer": { - "title": "Customer", - "type": "string" - }, - "name": { - "title": "Name", - "type": "string" - }, - "permissions": { - "title": "Permissions", - "type": "array", - "items": { - "type": "string" - } - } + "type": "string" }, - "required": [ - "customer", - "name", - "permissions" - ] - } - } - } - }, - "GOOGLECredentials3": { - "content_type": "application/json", - "schema": { - "title": "GOOGLECredentials3", - "type": "object", - "properties": { - "access_token": { - "title": "Access Token", - "type": "string" - }, - "project_id": { - "title": "Project Id", - "type": "string" + "title": "Rules", + "type": "array", + "uniqueItems": true } }, "required": [ - "access_token", - "project_id" + "rules" ], - "additionalProperties": false + "title": "TenantExcludedRulesPutModel", + "type": "object" } }, - "RuleUpdateMetaPostModel": { + "UserPatchModel": { "content_type": "application/json", "schema": { - "title": "RuleUpdateMetaPostModel", - "type": "object", + "description": "System admin endpoint", "properties": { - "customer": { - "title": "Customer", - "type": "string" - }, - "rule_source_id": { - "title": "Rule Source Id", + "password": { + "default": null, + "title": "Password", "type": "string" - } - }, - "additionalProperties": false - } - }, - "UserTenantsDeleteModel": { - "content_type": "application/json", - "schema": { - "title": "UserTenantsDeleteModel", - "type": "object", - "properties": { - "all": { - "title": "All", - "default": false, - "type": "boolean" - }, - "tenants": { - "title": "Tenants", - "type": "array", - "items": {} }, - "target_user": { - "title": "Target User", + "role_name": { + "default": null, + "title": "Role Name", "type": "string" } }, - "additionalProperties": false + "title": "UserPatchModel", + "type": "object" } }, - "ScheduledJobPatchModel": { + "UserPostModel": { "content_type": "application/json", "schema": { - "title": "ScheduledJobPatchModel", - "type": "object", "properties": { - "name": { - "title": "Name", - "type": "string" - }, - "schedule": { - "title": "Schedule", + "password": { + "title": "Password", "type": "string" }, - "enabled": { - "title": "Enabled", - "type": "boolean" - }, - "customer": { - "title": "Customer", - "type": "string" - } - }, - "required": [ - "name" - ], - "additionalProperties": false - } - }, - "UserDeleteModel": { - "content_type": "application/json", - "schema": { - "title": "UserDeleteModel", - "type": "object", - "properties": { - "customer": { - "title": "Customer", + "role_name": { + "default": null, + "title": "Role Name", "type": "string" }, "username": { @@ -5902,220 +6565,32 @@ "type": "string" } }, - "additionalProperties": false - } - }, - "UserRolePatchModel": { - "content_type": "application/json", - "schema": { - "title": "UserRolePatchModel", - "type": "object", - "properties": { - "role": { - "title": "Role", - "type": "string" - }, - "target_user": { - "title": "Target User", - "type": "string" - } - }, - "required": [ - "role" - ], - "additionalProperties": false - } - }, - "AccessApplicationListModel": { - "content_type": "application/json", - "schema": { - "title": "AccessApplicationListModel", - "type": "object", - "properties": { - "customer": { - "title": "Customer", - "type": "string" - } - }, - "additionalProperties": false - } - }, - "ParentListModel": { - "content_type": "application/json", - "schema": { - "title": "ParentListModel", - "type": "object", - "properties": { - "customer": { - "title": "Customer", - "type": "string" - } - }, - "additionalProperties": false - } - }, - "ApplicationPatchModel": { - "content_type": "application/json", - "schema": { - "title": "ApplicationPatchModel", - "type": "object", - "properties": { - "application_id": { - "title": "Application Id", - "type": "string" - }, - "customer": { - "title": "Customer", - "type": "string" - }, - "description": { - "title": "Description", - "type": "string" - }, - "cloud": { - "$ref": "#/definitions/RuleDomain" - }, - "access_application_id": { - "title": "Access Application Id", - "type": "string" - }, - "tenant_license_key": { - "title": "Tenant License Key", - "type": "string" - } - }, - "required": [ - "application_id" - ], - "additionalProperties": false, - "definitions": { - "RuleDomain": { - "title": "RuleDomain", - "description": "An enumeration.", - "enum": [ - "AWS", - "AZURE", - "GCP", - "KUBERNETES" - ], - "type": "string" - } - } - } - }, - "ReportPushByJobIdModel": { - "content_type": "application/json", - "schema": { - "title": "ReportPushByJobIdModel", - "description": "/reports/push/dojo/{job_id}/\n/reports/push/security-hub/{job_id}/", - "type": "object", - "properties": { - "job_id": { - "title": "Job Id", - "type": "string" - }, - "customer": { - "title": "Customer", - "type": "string" - } - }, "required": [ - "job_id" + "username", + "password" ], - "additionalProperties": false + "title": "UserPostModel", + "type": "object" } }, - "JobDTO": { + "UserResetPasswordModel": { "content_type": "application/json", "schema": { - "title": "JobDTO", - "type": "object", "properties": { - "trace_id": { - "title": "Trace Id", + "new_password": { + "title": "New Password", "type": "string" - }, - "items": { - "title": "Items", - "type": "array", - "items": { - "$ref": "#/definitions/Job" - } } }, "required": [ - "trace_id", - "items" + "new_password" ], - "definitions": { - "JobStatus": { - "title": "JobStatus", - "description": "An enumeration.", - "enum": [ - "FAILED", - "SUCCEEDED", - "SUBMITTED", - "PENDING", - "RUNNABLE", - "STARTING", - "RUNNING" - ], - "type": "string" - }, - "Job": { - "title": "Job", - "type": "object", - "properties": { - "job_id": { - "title": "Job Id", - "type": "string" - }, - "job_owner": { - "title": "Job Owner", - "type": "string" - }, - "tenant_display_name": { - "title": "Tenant Display Name", - "type": "string" - }, - "status": { - "$ref": "#/definitions/JobStatus" - }, - "scan_regions": { - "title": "Scan Regions", - "type": "array", - "items": { - "type": "string" - } - }, - "scan_rulesets": { - "title": "Scan Rulesets", - "type": "array", - "items": { - "type": "string" - } - }, - "started_at": { - "title": "Started At", - "type": "string" - }, - "stopped_at": { - "title": "Stopped At", - "type": "string" - } - }, - "required": [ - "job_id", - "job_owner", - "tenant_display_name", - "status", - "scan_regions", - "scan_rulesets" - ] - } - } + "title": "UserResetPasswordModel", + "type": "object" } } - } + }, + "resource_type": "api_gateway", + "resources": {} } } \ No newline at end of file diff --git a/src/validators/registry.py b/src/validators/registry.py new file mode 100644 index 000000000..567868491 --- /dev/null +++ b/src/validators/registry.py @@ -0,0 +1,1197 @@ +from http import HTTPStatus +from typing import Generator + +from helpers.constants import CustodianEndpoint, HTTPMethod, Permission +from services.openapi_spec_generator import EndpointInfo +from validators.swagger_request_models import ( + BaseModel, + BasePaginationModel, + BatchResultsQueryModel, + CLevelGetReportModel, + CredentialsBindModel, + CredentialsQueryModel, + CustomerExcludedRulesPutModel, + CustomerGetModel, + DefectDojoActivationPutModel, + DefectDojoPostModel, + DefectDojoQueryModel, + DepartmentGetReportModel, + EventDrivenRulesetDeleteModel, + EventDrivenRulesetGetModel, + EventDrivenRulesetPostModel, + EventPostModel, + HealthCheckQueryModel, + JobComplianceReportGetModel, + JobDetailsReportGetModel, + JobDigestReportGetModel, + JobErrorReportGetModel, + JobFindingsReportGetModel, + JobGetModel, + JobPostModel, + JobRuleReportGetModel, + K8sJobPostModel, + LicenseActivationPatchModel, + LicenseActivationPutModel, + LicenseManagerClientSettingDeleteModel, + LicenseManagerClientSettingPostModel, + LicenseManagerConfigSettingPostModel, + LicensePostModel, + MailSettingGetModel, + MailSettingPostModel, + MetricsStatusGetModel, + MultipleTenantsGetModel, + OperationalGetReportModel, + PlatformK8SPostModel, + PlatformK8sQueryModel, + PlatformK8sResourcesReportGetModel, + PolicyPatchModel, + PolicyPostModel, + ProjectGetReportModel, + RabbitMQDeleteModel, + RabbitMQGetModel, + RabbitMQPostModel, + RefreshPostModel, + ReportPushByJobIdModel, + ReportPushMultipleModel, + ReportStatusGetModel, + ReportsSendingSettingPostModel, + ResourceReportJobGetModel, + ResourceReportJobsGetModel, + ResourcesReportGetModel, + RolePatchModel, + RolePostModel, + RuleDeleteModel, + RuleGetModel, + RuleSourceDeleteModel, + RuleSourceGetModel, + RuleSourcePatchModel, + RuleSourcePostModel, + RuleUpdateMetaPostModel, + RulesetContentGetModel, + RulesetDeleteModel, + RulesetGetModel, + RulesetPatchModel, + RulesetPostModel, + ScheduledJobGetModel, + ScheduledJobPatchModel, + ScheduledJobPostModel, + SelfIntegrationPatchModel, + SelfIntegrationPutModel, + SignInPostModel, + SignUpModel, + StandardJobPostModel, + TenantComplianceReportGetModel, + TenantExcludedRulesPutModel, + TenantGetActiveLicensesModel, + TenantJobsDetailsReportGetModel, + TenantJobsDigestsReportGetModel, + TenantJobsFindingsReportGetModel, + TenantRuleReportGetModel, + UserPatchModel, + UserPostModel, + UserResetPasswordModel, + RawReportGetModel +) +from validators.swagger_response_models import ( + CredentialsActivationModel, + EntityResourcesReportModel, + EntityRulesReportModel, + ErrorsModel, + ErrorsReportModel, + EventModel, + JobResourcesReportModel, + MessageModel, + MultipleBatchResultsModel, + MultipleCredentialsModel, + MultipleCustomersModel, + MultipleDefectDojoModel, + MultipleDefectDojoPushResult, + MultipleHealthChecksModel, + MultipleJobReportModel, + MultipleJobsModel, + MultipleK8SPlatformsModel, + MultipleLicensesModel, + MultipleMetricsStatusesModel, + MultiplePoliciesModel, + MultipleReportStatusModel, + MultipleRoleModel, + MultipleRuleMetaUpdateModel, + MultipleRuleSourceModel, + MultipleRulesModel, + MultipleRulesetsModel, + MultipleScheduledJobsModel, + MultipleTenantsModel, + MultipleUsersModel, + RulesReportModel, + SignInModel, + SingleBatchResultModel, + SingleCredentialsModel, + SingleCustomerExcludedRules, + SingleDefeDojoModel, + SingleDefectDojoActivation, + SingleDefectDojoPushResult, + SingleEntityReportModel, + SingleHealthCheckModel, + SingleJobModel, + SingleJobReportModel, + SingleK8SPlatformModel, + SingleLMClientModel, + SingleLMConfigModel, + SingleLicenseActivationModel, + SingleLicenseModel, + SingleMailSettingModel, + SinglePolicyModel, + SingleRabbitMQModel, + SingleRoleModel, + SingleRuleSourceModel, + SingleRulesetModel, + SingleScheduledJobModel, + SingleSelfIntegration, + SingleTenantExcludedRules, + SingleTenantsModel, + SingleUserModel, + RawReportModel +) + + +data: tuple[EndpointInfo, ...] = ( + # auth + EndpointInfo( + path=CustodianEndpoint.SIGNUP, + method=HTTPMethod.POST, + request_model=SignUpModel, + responses=[(HTTPStatus.CREATED, MessageModel, None), + (HTTPStatus.CONFLICT, MessageModel, None)], + auth=False + ), + EndpointInfo( + path=CustodianEndpoint.SIGNIN, + method=HTTPMethod.POST, + request_model=SignInPostModel, + responses=[(HTTPStatus.OK, SignInModel, None)], + auth=False + ), + EndpointInfo( + path=CustodianEndpoint.REFRESH, + method=HTTPMethod.POST, + request_model=RefreshPostModel, + responses=[(HTTPStatus.OK, SignInModel, None)], + auth=False + ), + + # event + EndpointInfo( + path=CustodianEndpoint.EVENT, + method=HTTPMethod.POST, + request_model=EventPostModel, + responses=[(HTTPStatus.ACCEPTED, EventModel, None)], + permission=Permission.EVENT_POST + ), + + # health + EndpointInfo( + path=CustodianEndpoint.HEALTH, + method=HTTPMethod.GET, + request_model=HealthCheckQueryModel, + responses=[(HTTPStatus.OK, MultipleHealthChecksModel, None)] + ), + EndpointInfo( + path=CustodianEndpoint.HEALTH_ID, + method=HTTPMethod.GET, + request_model=BaseModel, + responses=[(HTTPStatus.OK, SingleHealthCheckModel, None)] + ), + + # jobs + EndpointInfo( + path=CustodianEndpoint.JOBS_STANDARD, + method=HTTPMethod.POST, + request_model=StandardJobPostModel, + responses=[(HTTPStatus.ACCEPTED, SingleJobModel, None)], + permission=Permission.JOB_POST_STANDARD + ), + EndpointInfo( + path=CustodianEndpoint.JOBS_K8S, + method=HTTPMethod.POST, + request_model=K8sJobPostModel, + responses=[(HTTPStatus.ACCEPTED, SingleJobModel, None)], + permission=Permission.JOB_POST_K8S + ), + EndpointInfo( + path=CustodianEndpoint.JOBS, + method=HTTPMethod.GET, + request_model=JobGetModel, + responses=[(HTTPStatus.OK, MultipleJobsModel, None)], + permission=Permission.JOB_QUERY + ), + EndpointInfo( + path=CustodianEndpoint.JOBS, + method=HTTPMethod.POST, + request_model=JobPostModel, + responses=[(HTTPStatus.ACCEPTED, SingleJobModel, None)], + permission=Permission.JOB_POST_LICENSED + ), + EndpointInfo( + path=CustodianEndpoint.JOBS_JOB, + method=HTTPMethod.GET, + request_model=BaseModel, + responses=[(HTTPStatus.OK, SingleJobModel, None)], + permission=Permission.JOB_GET + ), + EndpointInfo( + path=CustodianEndpoint.JOBS_JOB, + method=HTTPMethod.DELETE, + request_model=BaseModel, + responses=[(HTTPStatus.ACCEPTED, MessageModel, None)], + permission=Permission.JOB_TERMINATE + ), + + # scheduled jobs + EndpointInfo( + path=CustodianEndpoint.SCHEDULED_JOB, + method=HTTPMethod.GET, + request_model=ScheduledJobGetModel, + responses=[(HTTPStatus.OK, MultipleScheduledJobsModel, None)], + permission=Permission.SCHEDULED_JOB_QUERY + ), + EndpointInfo( + path=CustodianEndpoint.SCHEDULED_JOB, + method=HTTPMethod.POST, + request_model=ScheduledJobPostModel, + responses=[(HTTPStatus.CREATED, SingleScheduledJobModel, None)], + permission=Permission.SCHEDULED_JOB_CREATE + ), + EndpointInfo( + path=CustodianEndpoint.SCHEDULED_JOB_NAME, + method=HTTPMethod.GET, + request_model=BaseModel, + responses=[(HTTPStatus.OK, SingleScheduledJobModel, None)], + permission=Permission.SCHEDULED_JOB_GET + ), + EndpointInfo( + path=CustodianEndpoint.SCHEDULED_JOB_NAME, + method=HTTPMethod.PATCH, + request_model=ScheduledJobPatchModel, + responses=[(HTTPStatus.OK, SingleScheduledJobModel, None)], + permission=Permission.SCHEDULED_JOB_UPDATE + ), + EndpointInfo( + path=CustodianEndpoint.SCHEDULED_JOB_NAME, + method=HTTPMethod.DELETE, + request_model=BaseModel, + responses=[(HTTPStatus.NO_CONTENT, None, None)], + permission=Permission.SCHEDULED_JOB_DELETE + ), + + # customers + EndpointInfo( + path=CustodianEndpoint.CUSTOMERS, + method=HTTPMethod.GET, + request_model=CustomerGetModel, + responses=[(HTTPStatus.OK, MultipleCustomersModel, None)], + permission=Permission.CUSTOMER_DESCRIBE + ), + EndpointInfo( + path=CustodianEndpoint.CUSTOMERS_RABBITMQ, + method=HTTPMethod.GET, + request_model=RabbitMQGetModel, + responses=[(HTTPStatus.OK, SingleRabbitMQModel, None)], + permission=Permission.RABBITMQ_DESCRIBE + ), + EndpointInfo( + path=CustodianEndpoint.CUSTOMERS_RABBITMQ, + method=HTTPMethod.POST, + request_model=RabbitMQPostModel, + responses=[(HTTPStatus.OK, SingleRabbitMQModel, None)], + permission=Permission.RABBITMQ_CREATE + ), + EndpointInfo( + path=CustodianEndpoint.CUSTOMERS_RABBITMQ, + method=HTTPMethod.DELETE, + request_model=RabbitMQDeleteModel, + responses=[(HTTPStatus.NO_CONTENT, None, None)], + permission=Permission.RABBITMQ_DELETE + ), + EndpointInfo( + path=CustodianEndpoint.CUSTOMERS_EXCLUDED_RULES, + method=HTTPMethod.PUT, + request_model=CustomerExcludedRulesPutModel, + responses=[(HTTPStatus.OK, SingleCustomerExcludedRules, None)], + permission=Permission.CUSTOMER_SET_EXCLUDED_RULES + ), + EndpointInfo( + path=CustodianEndpoint.CUSTOMERS_EXCLUDED_RULES, + method=HTTPMethod.GET, + request_model=BaseModel, + responses=[(HTTPStatus.OK, SingleCustomerExcludedRules, None)], + permission=Permission.CUSTOMER_GET_EXCLUDED_RULES + ), + + # tenants + EndpointInfo( + path=CustodianEndpoint.TENANTS, + method=HTTPMethod.GET, + request_model=MultipleTenantsGetModel, + responses=[(HTTPStatus.OK, MultipleTenantsModel, None)], + permission=Permission.TENANT_QUERY + ), + EndpointInfo( + path=CustodianEndpoint.TENANTS_TENANT_NAME, + method=HTTPMethod.GET, + request_model=BaseModel, + responses=[(HTTPStatus.OK, SingleTenantsModel, None)], + permission=Permission.TENANT_GET + ), + EndpointInfo( + path=CustodianEndpoint.TENANTS_TENANT_NAME_ACTIVE_LICENSES, + method=HTTPMethod.GET, + request_model=TenantGetActiveLicensesModel, + responses=[(HTTPStatus.OK, MultipleLicensesModel, None)], + permission=Permission.TENANT_GET_ACTIVE_LICENSES + ), + EndpointInfo( + path=CustodianEndpoint.TENANTS_TENANT_NAME_EXCLUDED_RULES, + method=HTTPMethod.PUT, + request_model=TenantExcludedRulesPutModel, + responses=[(HTTPStatus.OK, SingleTenantExcludedRules, None)], + permission=Permission.TENANT_SET_EXCLUDED_RULES + ), + EndpointInfo( + path=CustodianEndpoint.TENANTS_TENANT_NAME_EXCLUDED_RULES, + method=HTTPMethod.GET, + request_model=BaseModel, + responses=[(HTTPStatus.OK, SingleTenantExcludedRules, None)], + permission=Permission.TENANT_GET_EXCLUDED_RULES + ), + + # credentials + EndpointInfo( + path=CustodianEndpoint.CREDENTIALS, + method=HTTPMethod.GET, + request_model=CredentialsQueryModel, + responses=[(HTTPStatus.OK, MultipleCredentialsModel, None)], + permission=Permission.CREDENTIALS_DESCRIBE, + ), + EndpointInfo( + path=CustodianEndpoint.CREDENTIALS_ID, + method=HTTPMethod.GET, + request_model=BaseModel, + responses=[(HTTPStatus.OK, SingleCredentialsModel, None)], + permission=Permission.CREDENTIALS_DESCRIBE, + ), + EndpointInfo( + path=CustodianEndpoint.CREDENTIALS_ID_BINDING, + method=HTTPMethod.GET, + request_model=BaseModel, + responses=[(HTTPStatus.OK, CredentialsActivationModel, None)], + permission=Permission.CREDENTIALS_GET_BINDING + ), + EndpointInfo( + path=CustodianEndpoint.CREDENTIALS_ID_BINDING, + method=HTTPMethod.PUT, + request_model=CredentialsBindModel, + responses=[(HTTPStatus.OK, CredentialsActivationModel, None)], + permission=Permission.CREDENTIALS_BIND + ), + EndpointInfo( + path=CustodianEndpoint.CREDENTIALS_ID_BINDING, + method=HTTPMethod.DELETE, + request_model=BaseModel, + responses=[(HTTPStatus.NO_CONTENT, None, None)], + permission=Permission.CREDENTIALS_UNBIND + ), + + # rules + EndpointInfo( + path=CustodianEndpoint.RULES, + method=HTTPMethod.GET, + request_model=RuleGetModel, + responses=[(HTTPStatus.OK, MultipleRulesModel, None)], + permission=Permission.RULE_DESCRIBE + ), + EndpointInfo( + path=CustodianEndpoint.RULES, + method=HTTPMethod.DELETE, + request_model=RuleDeleteModel, + responses=[(HTTPStatus.NO_CONTENT, None, None)], + permission=Permission.RULE_DELETE + ), + EndpointInfo( + path=CustodianEndpoint.RULE_META_UPDATER, + method=HTTPMethod.POST, + request_model=RuleUpdateMetaPostModel, + responses=[(HTTPStatus.ACCEPTED, MultipleRuleMetaUpdateModel, None)], + permission=Permission.RULE_UPDATE_META + ), + + # metrics + EndpointInfo( + path=CustodianEndpoint.METRICS_UPDATE, + method=HTTPMethod.POST, + request_model=BaseModel, + responses=[(HTTPStatus.ACCEPTED, MessageModel, None)], + permission=Permission.METRICS_UPDATE + ), + EndpointInfo( + path=CustodianEndpoint.METRICS_STATUS, + method=HTTPMethod.GET, + request_model=MetricsStatusGetModel, + responses=[(HTTPStatus.OK, MultipleMetricsStatusesModel, None)], + permission=Permission.METRICS_STATUS + ), + + # rulesets + EndpointInfo( + path=CustodianEndpoint.RULESETS, + method=HTTPMethod.GET, + request_model=RulesetGetModel, + responses=[(HTTPStatus.OK, MultipleRulesetsModel, None)], + permission=Permission.RULESET_DESCRIBE + ), + EndpointInfo( + path=CustodianEndpoint.RULESETS, + method=HTTPMethod.POST, + request_model=RulesetPostModel, + responses=[(HTTPStatus.CREATED, SingleRulesetModel, None)], + permission=Permission.RULESET_CREATE + ), + EndpointInfo( + path=CustodianEndpoint.RULESETS, + method=HTTPMethod.PATCH, + request_model=RulesetPatchModel, + responses=[(HTTPStatus.OK, SingleRulesetModel, None)], + permission=Permission.RULESET_UPDATE + ), + EndpointInfo( + path=CustodianEndpoint.RULESETS, + method=HTTPMethod.DELETE, + request_model=RulesetDeleteModel, + responses=[(HTTPStatus.NO_CONTENT, None, None)], + permission=Permission.RULESET_DELETE + ), + EndpointInfo( + path=CustodianEndpoint.ED_RULESETS, + method=HTTPMethod.GET, + request_model=EventDrivenRulesetGetModel, + responses=[(HTTPStatus.OK, MultipleRulesetsModel, None)], + permission=Permission.RULESET_DESCRIBE_ED + ), + EndpointInfo( + path=CustodianEndpoint.ED_RULESETS, + method=HTTPMethod.POST, + request_model=EventDrivenRulesetPostModel, + responses=[(HTTPStatus.CREATED, SingleRulesetModel, None)], + permission=Permission.RULESET_CREATE_ED + ), + EndpointInfo( + path=CustodianEndpoint.ED_RULESETS, + method=HTTPMethod.DELETE, + request_model=EventDrivenRulesetDeleteModel, + responses=[(HTTPStatus.NO_CONTENT, None, None)], + permission=Permission.RULESET_DELETE_ED + ), + EndpointInfo( + path=CustodianEndpoint.RULESETS_CONTENT, + method=HTTPMethod.GET, + request_model=RulesetContentGetModel, + responses=[(HTTPStatus.OK, MessageModel, None)], + permission=Permission.RULESET_GET_CONTENT + ), + + # rulesources + EndpointInfo( + path=CustodianEndpoint.RULE_SOURCES, + method=HTTPMethod.GET, + request_model=RuleSourceGetModel, + responses=[(HTTPStatus.OK, MultipleRuleSourceModel, None)], + permission=Permission.RULE_SOURCE_DESCRIBE + ), + EndpointInfo( + path=CustodianEndpoint.RULE_SOURCES, + method=HTTPMethod.POST, + request_model=RuleSourcePostModel, + responses=[(HTTPStatus.CREATED, SingleRuleSourceModel, None)], + permission=Permission.RULE_SOURCE_CREATE + ), + EndpointInfo( + path=CustodianEndpoint.RULE_SOURCES, + method=HTTPMethod.PATCH, + request_model=RuleSourcePatchModel, + responses=[(HTTPStatus.OK, SingleRuleSourceModel, None)], + permission=Permission.RULE_SOURCE_UPDATE + ), + EndpointInfo( + path=CustodianEndpoint.RULE_SOURCES, + method=HTTPMethod.DELETE, + request_model=RuleSourceDeleteModel, + responses=[(HTTPStatus.NO_CONTENT, None, None)], + permission=Permission.RULE_SOURCE_DELETE + ), + + # policies + EndpointInfo( + path=CustodianEndpoint.POLICIES, + method=HTTPMethod.GET, + request_model=BasePaginationModel, + responses=[(HTTPStatus.OK, MultiplePoliciesModel, None)], + permission=Permission.POLICY_DESCRIBE + ), + EndpointInfo( + path=CustodianEndpoint.POLICIES_NAME, + method=HTTPMethod.GET, + request_model=BaseModel, + responses=[(HTTPStatus.OK, SinglePolicyModel, None)], + permission=Permission.POLICY_DESCRIBE + ), + EndpointInfo( + path=CustodianEndpoint.POLICIES, + method=HTTPMethod.POST, + request_model=PolicyPostModel, + responses=[(HTTPStatus.CREATED, SinglePolicyModel, None)], + permission=Permission.POLICY_CREATE + ), + EndpointInfo( + path=CustodianEndpoint.POLICIES_NAME, + method=HTTPMethod.PATCH, + request_model=PolicyPatchModel, + responses=[(HTTPStatus.OK, SinglePolicyModel, None)], + permission=Permission.POLICY_UPDATE + ), + EndpointInfo( + path=CustodianEndpoint.POLICIES_NAME, + method=HTTPMethod.DELETE, + request_model=BaseModel, + responses=[(HTTPStatus.NO_CONTENT, None, None)], + permission=Permission.POLICY_DELETE + ), + # EndpointInfo( + # path=CustodianEndpoint.POLICIES_CACHE, + # method=HTTPMethod.DELETE, + # request_model=PolicyCacheDeleteModel, + # responses=[(HTTPStatus.NO_CONTENT, None, None)], + # permission=Permission.POLICY_RESET_CACHE + # ), + + # roles + EndpointInfo( + path=CustodianEndpoint.ROLES, + method=HTTPMethod.GET, + request_model=BasePaginationModel, + responses=[(HTTPStatus.OK, MultipleRoleModel, None)], + permission=Permission.ROLE_DESCRIBE + ), + EndpointInfo( + path=CustodianEndpoint.ROLES_NAME, + method=HTTPMethod.GET, + request_model=BaseModel, + responses=[(HTTPStatus.OK, SingleRoleModel, None)], + permission=Permission.ROLE_DESCRIBE + ), + EndpointInfo( + path=CustodianEndpoint.ROLES, + method=HTTPMethod.POST, + request_model=RolePostModel, + responses=[(HTTPStatus.CREATED, SingleRoleModel, None)], + permission=Permission.ROLE_CREATE + ), + EndpointInfo( + path=CustodianEndpoint.ROLES_NAME, + method=HTTPMethod.PATCH, + request_model=RolePatchModel, + responses=[(HTTPStatus.OK, SingleRoleModel, None)], + permission=Permission.ROLE_UPDATE + ), + EndpointInfo( + path=CustodianEndpoint.ROLES_NAME, + method=HTTPMethod.DELETE, + request_model=BaseModel, + responses=[(HTTPStatus.NO_CONTENT, None, None)], + permission=Permission.ROLE_DELETE + ), + # EndpointInfo( + # path=CustodianEndpoint.ROLES_CACHE, + # method=HTTPMethod.DELETE, + # request_model=RoleCacheDeleteModel, + # responses=[(HTTPStatus.NO_CONTENT, None, None)], + # permission=Permission.ROLE_RESET_CACHE + # ), + + # licenses + EndpointInfo( + path=CustodianEndpoint.LICENSES, + method=HTTPMethod.POST, + request_model=LicensePostModel, + responses=[(HTTPStatus.ACCEPTED, None, None)], + permission=Permission.LICENSE_ADD + ), + EndpointInfo( + path=CustodianEndpoint.LICENSES, + method=HTTPMethod.GET, + request_model=BaseModel, + responses=[(HTTPStatus.OK, MultipleLicensesModel, None)], + permission=Permission.LICENSE_QUERY + ), + EndpointInfo( + path=CustodianEndpoint.LICENSES_LICENSE_KEY, + method=HTTPMethod.GET, + request_model=BaseModel, + responses=[(HTTPStatus.OK, SingleLicenseModel, None)], + permission=Permission.LICENSE_GET + ), + EndpointInfo( + path=CustodianEndpoint.LICENSES_LICENSE_KEY, + method=HTTPMethod.DELETE, + request_model=BaseModel, + responses=[(HTTPStatus.NO_CONTENT, None, None)], + permission=Permission.LICENSE_DELETE + ), + EndpointInfo( + path=CustodianEndpoint.LICENSES_LICENSE_KEY_SYNC, + method=HTTPMethod.POST, + request_model=BaseModel, + responses=[(HTTPStatus.ACCEPTED, MessageModel, None)], + permission=Permission.LICENSE_SYNC + ), + EndpointInfo( + path=CustodianEndpoint.LICENSE_LICENSE_KEY_ACTIVATION, + method=HTTPMethod.DELETE, + request_model=BaseModel, + responses=[(HTTPStatus.NO_CONTENT, None, None)], + permission=Permission.LICENSE_DELETE_ACTIVATION + ), + EndpointInfo( + path=CustodianEndpoint.LICENSE_LICENSE_KEY_ACTIVATION, + method=HTTPMethod.GET, + request_model=BaseModel, + responses=[(HTTPStatus.OK, SingleLicenseActivationModel, None)], + permission=Permission.LICENSE_GET_ACTIVATION + ), + EndpointInfo( + path=CustodianEndpoint.LICENSE_LICENSE_KEY_ACTIVATION, + method=HTTPMethod.PUT, + request_model=LicenseActivationPutModel, + responses=[(HTTPStatus.OK, SingleLicenseActivationModel, None)], + permission=Permission.LICENSE_ACTIVATE + ), + EndpointInfo( + path=CustodianEndpoint.LICENSE_LICENSE_KEY_ACTIVATION, + method=HTTPMethod.PATCH, + request_model=LicenseActivationPatchModel, + responses=[(HTTPStatus.OK, SingleLicenseActivationModel, None)], + permission=Permission.LICENSE_UPDATE_ACTIVATION + ), + + # settings + EndpointInfo( + path=CustodianEndpoint.SETTINGS_MAIL, + method=HTTPMethod.GET, + request_model=MailSettingGetModel, + responses=[(HTTPStatus.OK, SingleMailSettingModel, None)], + permission=Permission.SETTINGS_DESCRIBE_MAIL + ), + EndpointInfo( + path=CustodianEndpoint.SETTINGS_MAIL, + method=HTTPMethod.POST, + request_model=MailSettingPostModel, + responses=[(HTTPStatus.CREATED, SingleMailSettingModel, None)], + permission=Permission.SETTINGS_CREATE_MAIL + ), + EndpointInfo( + path=CustodianEndpoint.SETTINGS_MAIL, + method=HTTPMethod.DELETE, + request_model=BaseModel, + responses=[(HTTPStatus.NO_CONTENT, None, None)], + permission=Permission.SETTINGS_DELETE_MAIL + ), + EndpointInfo( + path=CustodianEndpoint.SETTINGS_SEND_REPORTS, + method=HTTPMethod.POST, + request_model=ReportsSendingSettingPostModel, + responses=[(HTTPStatus.OK, MessageModel, None)], + permission=Permission.SETTINGS_CHANGE_SET_REPORTS + ), + EndpointInfo( + path=CustodianEndpoint.SETTINGS_LICENSE_MANAGER_CONFIG, + method=HTTPMethod.GET, + request_model=BaseModel, + responses=[(HTTPStatus.OK, SingleLMConfigModel, None)], + permission=Permission.SETTINGS_DESCRIBE_LM_CONFIG + ), + EndpointInfo( + path=CustodianEndpoint.SETTINGS_LICENSE_MANAGER_CONFIG, + method=HTTPMethod.POST, + request_model=LicenseManagerConfigSettingPostModel, + responses=[(HTTPStatus.CREATED, SingleLMConfigModel, None)], + permission=Permission.SETTINGS_CREATE_LM_CONFIG + ), + EndpointInfo( + path=CustodianEndpoint.SETTINGS_LICENSE_MANAGER_CONFIG, + method=HTTPMethod.DELETE, + request_model=BaseModel, + responses=[(HTTPStatus.NO_CONTENT, None, None)], + permission=Permission.SETTINGS_DELETE_LM_CONFIG + ), + EndpointInfo( + path=CustodianEndpoint.SETTINGS_LICENSE_MANAGER_CLIENT, + method=HTTPMethod.GET, + request_model=BaseModel, + responses=[(HTTPStatus.OK, SingleLMClientModel, None)], + permission=Permission.SETTINGS_DESCRIBE_LM_CLIENT + ), + EndpointInfo( + path=CustodianEndpoint.SETTINGS_LICENSE_MANAGER_CLIENT, + method=HTTPMethod.POST, + request_model=LicenseManagerClientSettingPostModel, + responses=[(HTTPStatus.CREATED, SingleLMClientModel, None)], + permission=Permission.SETTINGS_CREATE_LM_CLIENT + ), + EndpointInfo( + path=CustodianEndpoint.SETTINGS_LICENSE_MANAGER_CLIENT, + method=HTTPMethod.DELETE, + request_model=LicenseManagerClientSettingDeleteModel, + responses=[(HTTPStatus.NO_CONTENT, None, None)], + permission=Permission.SETTINGS_DELETE_LM_CLIENT + ), + + # batch results + EndpointInfo( + path=CustodianEndpoint.BATCH_RESULTS, + method=HTTPMethod.GET, + request_model=BatchResultsQueryModel, + responses=[(HTTPStatus.OK, MultipleBatchResultsModel, None)], + permission=Permission.BATCH_RESULTS_QUERY + ), + EndpointInfo( + path=CustodianEndpoint.BATCH_RESULTS_JOB_ID, + method=HTTPMethod.GET, + request_model=BaseModel, + responses=[(HTTPStatus.OK, SingleBatchResultModel, None)], + permission=Permission.BATCH_RESULTS_GET + ), + + # digest reports + EndpointInfo( + path=CustodianEndpoint.REPORTS_DIGESTS_JOBS_JOB_ID, + method=HTTPMethod.GET, + request_model=JobDigestReportGetModel, + responses=[(HTTPStatus.OK, SingleJobReportModel, None)], + permission=Permission.REPORT_DIGEST_DESCRIBE + ), + EndpointInfo( + path=CustodianEndpoint.REPORTS_DIGESTS_TENANTS_TENANT_NAME_JOBS, + method=HTTPMethod.GET, + request_model=TenantJobsDigestsReportGetModel, + responses=[(HTTPStatus.OK, MultipleJobReportModel, None)], + permission=Permission.REPORT_DIGEST_DESCRIBE + ), + + # details reports + EndpointInfo( + path=CustodianEndpoint.REPORTS_DETAILS_JOBS_JOB_ID, + method=HTTPMethod.GET, + request_model=JobDetailsReportGetModel, + responses=[(HTTPStatus.OK, SingleJobReportModel, None)], + permission=Permission.REPORT_DETAILS_DESCRIBE + ), + EndpointInfo( + path=CustodianEndpoint.REPORTS_DETAILS_TENANTS_TENANT_NAME_JOBS, + method=HTTPMethod.GET, + request_model=TenantJobsDetailsReportGetModel, + responses=[(HTTPStatus.OK, MultipleJobReportModel, None)], + permission=Permission.REPORT_DETAILS_DESCRIBE + ), + + # findings reports + EndpointInfo( + path=CustodianEndpoint.REPORTS_FINDINGS_JOBS_JOB_ID, + method=HTTPMethod.GET, + request_model=JobFindingsReportGetModel, + responses=[(HTTPStatus.OK, SingleJobReportModel, None)], + permission=Permission.REPORT_FINDINGS_DESCRIBE + ), + EndpointInfo( + path=CustodianEndpoint.REPORTS_FINDINGS_TENANTS_TENANT_NAME_JOBS, + method=HTTPMethod.GET, + request_model=TenantJobsFindingsReportGetModel, + responses=[(HTTPStatus.OK, MultipleJobReportModel, None)], + permission=Permission.REPORT_FINDINGS_DESCRIBE + ), + + # compliance reports + EndpointInfo( + path=CustodianEndpoint.REPORTS_COMPLIANCE_JOBS_JOB_ID, + method=HTTPMethod.GET, + request_model=JobComplianceReportGetModel, + responses=[(HTTPStatus.OK, SingleJobReportModel, None)], + permission=Permission.REPORT_COMPLIANCE_DESCRIBE_JOB + ), + EndpointInfo( + path=CustodianEndpoint.REPORTS_COMPLIANCE_TENANTS_TENANT_NAME, + method=HTTPMethod.GET, + request_model=TenantComplianceReportGetModel, + responses=[(HTTPStatus.OK, SingleEntityReportModel, None)], + permission=Permission.REPORT_COMPLIANCE_DESCRIBE_TENANT + ), + + # errors report + EndpointInfo( + path=CustodianEndpoint.REPORTS_ERRORS_JOBS_JOB_ID, + method=HTTPMethod.GET, + request_model=JobErrorReportGetModel, + responses=[(HTTPStatus.OK, ErrorsReportModel, None)], + permission=Permission.REPORT_ERRORS_DESCRIBE + ), + + # rules report + EndpointInfo( + path=CustodianEndpoint.REPORTS_RULES_JOBS_JOB_ID, + method=HTTPMethod.GET, + request_model=JobRuleReportGetModel, + responses=[(HTTPStatus.OK, RulesReportModel, None)], + permission=Permission.REPORT_RULES_DESCRIBE_JOB + ), + EndpointInfo( + path=CustodianEndpoint.REPORTS_RULES_TENANTS_TENANT_NAME, + method=HTTPMethod.GET, + request_model=TenantRuleReportGetModel, + responses=[(HTTPStatus.OK, EntityRulesReportModel, None)], + permission=Permission.REPORT_RULES_DESCRIBE_TENANT + ), + + # push to dojo report + EndpointInfo( + path=CustodianEndpoint.REPORTS_PUSH_DOJO_JOB_ID, + method=HTTPMethod.POST, + request_model=ReportPushByJobIdModel, + responses=[(HTTPStatus.OK, SingleDefectDojoPushResult, None)], + permission=Permission.REPORT_PUSH_TO_DOJO + ), + EndpointInfo( + path=CustodianEndpoint.REPORTS_PUSH_DOJO, + method=HTTPMethod.POST, + request_model=ReportPushMultipleModel, + responses=[(HTTPStatus.OK, MultipleDefectDojoPushResult, None)], + permission=Permission.REPORT_PUSH_TO_DOJO_BATCH + ), + + # high level reports + EndpointInfo( + path=CustodianEndpoint.REPORTS_OPERATIONAL, + method=HTTPMethod.POST, + request_model=OperationalGetReportModel, + responses=[(HTTPStatus.OK, MessageModel, None)], + permission=Permission.REPORT_OPERATIONAL + ), + EndpointInfo( + path=CustodianEndpoint.REPORTS_PROJECT, + method=HTTPMethod.POST, + request_model=ProjectGetReportModel, + responses=[(HTTPStatus.OK, MessageModel, None)], + permission=Permission.REPORT_PROJECT + ), + EndpointInfo( + path=CustodianEndpoint.REPORTS_DEPARTMENT, + method=HTTPMethod.POST, + request_model=DepartmentGetReportModel, + responses=[(HTTPStatus.OK, MessageModel, None)], + permission=Permission.REPORT_DEPARTMENT + ), + EndpointInfo( + path=CustodianEndpoint.REPORTS_CLEVEL, + method=HTTPMethod.POST, + request_model=CLevelGetReportModel, + responses=[(HTTPStatus.OK, MessageModel, None)], + permission=Permission.REPORT_CLEVEL + ), + EndpointInfo( + path=CustodianEndpoint.REPORTS_DIAGNOSTIC, + method=HTTPMethod.GET, + request_model=BaseModel, + responses=[(HTTPStatus.OK, MessageModel, None)], + permission=Permission.REPORT_DIAGNOSTIC + ), + EndpointInfo( + path=CustodianEndpoint.REPORTS_STATUS, + method=HTTPMethod.GET, + request_model=ReportStatusGetModel, + responses=[(HTTPStatus.OK, MultipleReportStatusModel, None)], + permission=Permission.REPORT_STATUS + ), + + # meta mappings + EndpointInfo( + path=CustodianEndpoint.META_STANDARDS, + method=HTTPMethod.POST, + request_model=BaseModel, + responses=[(HTTPStatus.ACCEPTED, MessageModel, None)], + permission=Permission.META_UPDATE_STANDARDS + ), + EndpointInfo( + path=CustodianEndpoint.META_MAPPINGS, + method=HTTPMethod.POST, + request_model=BaseModel, + responses=[(HTTPStatus.ACCEPTED, MessageModel, None)], + permission=Permission.META_UPDATE_MAPPINGS + ), + EndpointInfo( + path=CustodianEndpoint.META_META, + method=HTTPMethod.POST, + request_model=BaseModel, + responses=[(HTTPStatus.ACCEPTED, MessageModel, None)], + permission=Permission.META_UPDATE_META + ), + + # resources reports + EndpointInfo( + path=CustodianEndpoint.REPORTS_RESOURCES_PLATFORMS_K8S_PLATFORM_ID_LATEST, + method=HTTPMethod.GET, + request_model=PlatformK8sResourcesReportGetModel, + responses=[(HTTPStatus.OK, EntityResourcesReportModel, None)], + permission=Permission.REPORT_RESOURCES_GET_K8S_PLATFORM_LATEST + ), + EndpointInfo( + path=CustodianEndpoint.REPORTS_RESOURCES_TENANTS_TENANT_NAME_LATEST, + method=HTTPMethod.GET, + request_model=ResourcesReportGetModel, + responses=[(HTTPStatus.OK, EntityResourcesReportModel, None)], + permission=Permission.REPORT_RESOURCES_GET_TENANT_LATEST + ), + EndpointInfo( + path=CustodianEndpoint.REPORTS_RESOURCES_TENANTS_TENANT_NAME_JOBS, + method=HTTPMethod.GET, + request_model=ResourceReportJobsGetModel, + responses=[(HTTPStatus.OK, JobResourcesReportModel, None)], + permission=Permission.REPORT_RESOURCES_GET_JOBS_BATCH + ), + EndpointInfo( + path=CustodianEndpoint.REPORTS_RESOURCES_JOBS_JOB_ID, + method=HTTPMethod.GET, + request_model=ResourceReportJobGetModel, + responses=[(HTTPStatus.OK, JobResourcesReportModel, None)], + permission=Permission.REPORT_RESOURCES_GET_JOBS + ), + EndpointInfo( + path=CustodianEndpoint.REPORTS_RAW_TENANTS_TENANT_NAME_STATE_LATEST, + method=HTTPMethod.GET, + request_model=RawReportGetModel, + responses=[(HTTPStatus.OK, RawReportModel, None)], + permission=Permission.REPORT_RAW_GET_TENANT_LATEST + ), + + # platforms + EndpointInfo( + path=CustodianEndpoint.PLATFORMS_K8S, + method=HTTPMethod.GET, + request_model=PlatformK8sQueryModel, + responses=[(HTTPStatus.OK, MultipleK8SPlatformsModel, None)], + permission=Permission.PLATFORM_QUERY_K8S + ), + EndpointInfo( + path=CustodianEndpoint.PLATFORMS_K8S, + method=HTTPMethod.POST, + request_model=PlatformK8SPostModel, + responses=[(HTTPStatus.OK, SingleK8SPlatformModel, None)], + permission=Permission.PLATFORM_CREATE_K8S + ), + EndpointInfo( + path=CustodianEndpoint.PLATFORMS_K8S_ID, + method=HTTPMethod.GET, + request_model=BaseModel, + responses=[(HTTPStatus.OK, SingleK8SPlatformModel, None)], + permission=Permission.PLATFORM_GET_K8S + ), + EndpointInfo( + path=CustodianEndpoint.PLATFORMS_K8S_ID, + method=HTTPMethod.DELETE, + request_model=BaseModel, + responses=[(HTTPStatus.NO_CONTENT, None, None)], + permission=Permission.PLATFORM_DELETE_K8S + ), + + # dojo integrations + EndpointInfo( + path=CustodianEndpoint.INTEGRATIONS_DEFECT_DOJO, + method=HTTPMethod.POST, + request_model=DefectDojoPostModel, + responses=[(HTTPStatus.CREATED, SingleDefeDojoModel, None)], + permission=Permission.DOJO_INTEGRATION_CREATE + ), + EndpointInfo( + path=CustodianEndpoint.INTEGRATIONS_DEFECT_DOJO, + method=HTTPMethod.GET, + request_model=DefectDojoQueryModel, + responses=[(HTTPStatus.OK, MultipleDefectDojoModel, None)], + permission=Permission.DOJO_INTEGRATION_DESCRIBE + ), + EndpointInfo( + path=CustodianEndpoint.INTEGRATIONS_DEFECT_DOJO_ID, + method=HTTPMethod.DELETE, + request_model=BaseModel, + responses=[(HTTPStatus.NO_CONTENT, None, None)], + permission=Permission.DOJO_INTEGRATION_DELETE + ), + EndpointInfo( + path=CustodianEndpoint.INTEGRATIONS_DEFECT_DOJO_ID, + method=HTTPMethod.GET, + request_model=BaseModel, + responses=[(HTTPStatus.OK, SingleDefeDojoModel, None)], + permission=Permission.DOJO_INTEGRATION_DESCRIBE + ), + EndpointInfo( + path=CustodianEndpoint.INTEGRATIONS_DEFECT_DOJO_ID_ACTIVATION, + method=HTTPMethod.PUT, + request_model=DefectDojoActivationPutModel, + responses=[(HTTPStatus.CREATED, SingleDefectDojoActivation, None)], + permission=Permission.DOJO_INTEGRATION_ACTIVATE + ), + EndpointInfo( + path=CustodianEndpoint.INTEGRATIONS_DEFECT_DOJO_ID_ACTIVATION, + method=HTTPMethod.GET, + request_model=BaseModel, + responses=[(HTTPStatus.OK, SingleDefectDojoActivation, None)], + permission=Permission.DOJO_INTEGRATION_ACTIVATE + ), + EndpointInfo( + path=CustodianEndpoint.INTEGRATIONS_DEFECT_DOJO_ID_ACTIVATION, + method=HTTPMethod.DELETE, + request_model=BaseModel, + responses=[(HTTPStatus.NO_CONTENT, None, None)], + permission=Permission.DOJO_INTEGRATION_DELETE_ACTIVATION + ), + + # self integration + EndpointInfo( + path=CustodianEndpoint.INTEGRATIONS_SELF, + method=HTTPMethod.PUT, + request_model=SelfIntegrationPutModel, + responses=[(HTTPStatus.CREATED, SingleSelfIntegration, None)], + permission=Permission.SRE_INTEGRATION_CREATE + ), + EndpointInfo( + path=CustodianEndpoint.INTEGRATIONS_SELF, + method=HTTPMethod.PATCH, + request_model=SelfIntegrationPatchModel, + responses=[(HTTPStatus.OK, SingleSelfIntegration, None)], + permission=Permission.SRE_INTEGRATION_UPDATE + ), + EndpointInfo( + path=CustodianEndpoint.INTEGRATIONS_SELF, + method=HTTPMethod.GET, + request_model=BaseModel, + responses=[(HTTPStatus.OK, SingleSelfIntegration, None)], + permission=Permission.SRE_INTEGRATION_DESCRIBE + ), + EndpointInfo( + path=CustodianEndpoint.INTEGRATIONS_SELF, + method=HTTPMethod.DELETE, + request_model=BaseModel, + responses=[(HTTPStatus.NO_CONTENT, None, None)], + permission=Permission.SRE_INTEGRATION_DELETE + ), + + # Users + EndpointInfo( + path=CustodianEndpoint.USERS_USERNAME, + method=HTTPMethod.GET, + request_model=BaseModel, + responses=[(HTTPStatus.OK, SingleUserModel, None)], + permission=Permission.USERS_DESCRIBE + ), + EndpointInfo( + path=CustodianEndpoint.USERS, + method=HTTPMethod.POST, + request_model=UserPostModel, + responses=[(HTTPStatus.OK, SingleUserModel, None), + (HTTPStatus.CONFLICT, MessageModel, None)], + permission=Permission.USERS_CREATE + ), + EndpointInfo( + path=CustodianEndpoint.USERS_USERNAME, + method=HTTPMethod.PATCH, + request_model=UserPatchModel, + responses=[(HTTPStatus.OK, SingleUserModel, None)], + permission=Permission.USERS_UPDATE, + ), + EndpointInfo( + path=CustodianEndpoint.USERS_USERNAME, + method=HTTPMethod.DELETE, + request_model=BaseModel, + responses=[(HTTPStatus.NO_CONTENT, None, None)], + permission=Permission.USERS_DELETE, + ), + EndpointInfo( + path=CustodianEndpoint.USERS, + method=HTTPMethod.GET, + request_model=BasePaginationModel, + responses=[(HTTPStatus.OK, MultipleUsersModel, None)], + permission=Permission.USERS_DESCRIBE + ), + EndpointInfo( + path=CustodianEndpoint.USERS_WHOAMI, + method=HTTPMethod.GET, + request_model=BaseModel, + responses=[(HTTPStatus.OK, SingleUserModel, None)], + permission=Permission.USERS_GET_CALLER + ), + EndpointInfo( + path=CustodianEndpoint.USERS_RESET_PASSWORD, + method=HTTPMethod.POST, + request_model=UserResetPasswordModel, + responses=[(HTTPStatus.NO_CONTENT, None, None)], + permission=Permission.USERS_RESET_PASSWORD + ) +) + +common_responses = ( + (HTTPStatus.BAD_REQUEST, ErrorsModel, 'Validation error'), + (HTTPStatus.UNAUTHORIZED, MessageModel, 'Invalid credentials'), + (HTTPStatus.FORBIDDEN, MessageModel, 'Cannot access the resource'), + (HTTPStatus.INTERNAL_SERVER_ERROR, MessageModel, 'Server error'), + (HTTPStatus.SERVICE_UNAVAILABLE, MessageModel, + 'Service is temporarily unavailable'), + (HTTPStatus.GATEWAY_TIMEOUT, MessageModel, + 'Gateway 30s timeout is reached') +) + + +def iter_all() -> Generator[EndpointInfo, None, None]: + """ + Extends data + :return: + """ + for endpoint in data: + existing = {r[0] for r in endpoint.responses} + for tpl in common_responses: + if tpl[0] in existing: + continue + endpoint.responses.append(tpl) + if '{' in endpoint.path and HTTPStatus.NOT_FOUND not in existing: + endpoint.responses.append((HTTPStatus.NOT_FOUND, MessageModel, + 'Entity is not found')) + endpoint.responses.sort(key=lambda x: x[0]) + yield endpoint + + +def iter_models(without_get: bool = True + ) -> Generator[type[BaseModel], None, None]: + """ + :param without_get: omits request models for GET method + :return: + """ + models = set() + for endpoint in iter_all(): + request_model = endpoint.request_model + if without_get and endpoint.method == HTTPMethod.GET: + request_model = None + + if request_model: + models.add(request_model) + + models.update(resp[1] for resp in endpoint.responses if resp[1]) + yield from models + + +permissions_mapping = { + (CustodianEndpoint(e.path), e.method): e.permission + for e in iter_all() +} diff --git a/src/validators/request_validation.py b/src/validators/request_validation.py deleted file mode 100644 index 8be1c39af..000000000 --- a/src/validators/request_validation.py +++ /dev/null @@ -1,1492 +0,0 @@ -import sys -from base64 import standard_b64decode -from datetime import datetime, timedelta -from enum import Enum -from inspect import getmembers -from itertools import chain -from typing import Dict, List, Optional, Literal, Set, Union - -from pydantic import BaseModel as BaseModelPydantic, validator, constr, \ - root_validator, AmqpDsn, AnyUrl, HttpUrl -from pydantic.fields import Field -from typing_extensions import TypedDict -from modular_sdk.commons.constants import ParentScope -from helpers.constants import HealthCheckStatus -from helpers.enums import ParentType, RuleDomain -from helpers.regions import AllRegions, AllRegionsWithMultiregional, AWSRegion -from helpers.reports import Standard -from helpers.time_helper import utc_datetime -from services import SERVICE_PROVIDER -from services.rule_meta_service import RuleName - -GitURLType = constr(regex=r'^https?:\/\/[^\/]+$') - - -def password_must_be_secure(password: str) -> str: - errors = [] - upper = any(char.isupper() for char in password) - numeric = any(char.isdigit() for char in password) - symbol = any(not char.isalnum() for char in password) - if not upper: - errors.append('must have uppercase characters') - if not numeric: - errors.append('must have numeric characters') - if not symbol: - errors.append('must have symbol characters') - if len(password) < 8: - errors.append('valid min length for password: 8') - - if errors: - raise ValueError(', '.join(errors)) - - return password - - -def get_day_captured_utc( - _from: Optional[datetime] = None, _back: bool = True, - reset: dict = None, shift: dict = None, now: datetime = None -): - """ - Returns an utc-datetime object, shifted by a given parameters, - which captures start of the day, as well. - :return: datetime - """ - _reset = reset or dict() - _shift = shift or dict() - now = now or datetime.utcnow() - _from = _from or now - op = _from.__sub__ if _back else _from.__add__ - return op(timedelta(**shift)).replace(**reset) - - -class BaseModel(BaseModelPydantic): - class Config: - use_enum_values = True - extra = 'forbid' - - -class PreparedEvent(BaseModel): - """ - Prepared event historically contains both some request meta and incoming - body on one level. - You can inherit your models from this one and use them directly as type - annotations inside handlers (with validate_kwargs) decorator - """ - class Config: - use_enum_values = True - extra = 'allow' - - httpMethod: str # GET, POST, ... - path: str # without prefix (stage) - queryStringParameters: Optional[dict] - user_id: str # cognito user id - user_role: str # user role - user_customer: str # customer of the user making the request - # customer of the user on whose behalf the request must be done. - # For standard users it's the same as user_customer - customer: Optional[str] - tenant: Optional[str] # for some endpoints defined in restriction_service - tenants: Optional[List[str]] # the same as one above - - # other validators can inherit this model and contain body attributes - - -# FYI: if you are to add a new model, name it according to the pattern: -# "{YOU_NAME}Model". But all the middleware configurations must be -# named without "Model" in the end. In case the class name contains -# "Get", it will be considered as a model for GET request - - -class RegionState(str, Enum): - INACTIVE = 'INACTIVE' - ACTIVE = 'ACTIVE' - - -class KeyFormat(str, Enum): - PEM = 'PEM' - - -class Credentials(TypedDict): - AWS_ACCESS_KEY_ID: str - AWS_SECRET_ACCESS_KEY: str - AWS_DEFAULT_REGION: str - AWS_SESSION_TOKEN: Optional[str] - - -class CustomerGetModel(BaseModel): - name: Optional[str] - complete: Optional[bool] = False - - -# License -class LicenseGetModel(BaseModel): - license_key: Optional[str] - customer: Optional[str] - - -class LicenseDeleteModel(BaseModel): - license_key: str - customer: Optional[str] - - -class LicenseSyncPostModel(BaseModel): - license_key: str - - -class RulesetPostModel(PreparedEvent): - name: str - version: str - cloud: RuleDomain - active: bool = True - tenant_allowance: Optional[List] = [] - customer: Optional[str] - - # if empty, all the rules for cloud is chosen - rules: Optional[Set] = Field(default_factory=set) - git_project_id: Optional[str] - git_ref: Optional[str] - - service_section: Optional[str] - severity: Optional[str] - mitre: Set[str] = set() - standard: Set[str] = set() - - @root_validator(pre=False, skip_on_failure=True, allow_reuse=True) - def validate_filters(cls, values: dict) -> dict: - if values.get('git_ref') and not values.get('git_project_id'): - raise ValueError('git_project_id must be specified with git_ref') - cloud = values['cloud'] - col = SERVICE_PROVIDER.mappings_collector() - if values.get('service_section'): - available = set( - value for key, value in col.service_section.items() - if RuleName(key).cloud == cloud - ) - if values.get('service_section') not in available: - raise ValueError('Not available service section. ' - f'Choose from: {", ".join(available)}') - if values.get('severity'): - available = set( - value for key, value in col.severity.items() - if RuleName(key).cloud == cloud - ) - if values.get('severity') not in available: - raise ValueError('Not available severity. ' - f'Choose from: {", ".join(available)}') - if values.get('mitre'): - available = set(chain.from_iterable( - value.keys() for key, value in col.mitre.items() - if RuleName(key).cloud == cloud - )) - not_available = values.get('mitre') - available - if not_available: - raise ValueError( - f'Not available mitre: {", ".join(not_available)}. ' - f'Choose from: {", ".join(available)}') - if values.get('standard'): - available = set() - it = ( - (v or {}) - for k, v in col.standard.items() - if RuleName(k).cloud == cloud - ) - for st in it: - available.update(Standard.deserialize(st, return_strings=True)) - available.update(st.keys()) - not_available = values.get('standard') - available - if not_available: - raise ValueError( - f'Not available standard: {", ".join(not_available)}. ' - f'Choose from: {", ".join(available)}') - return values - - -class RulesetPatchModel(PreparedEvent): - name: str - version: str - customer: Optional[str] - rules_to_attach: Optional[set] = set() - rules_to_detach: Optional[set] = set() - active: Optional[bool] - tenant_allowance: Optional[list] = [] - tenant_restriction: Optional[list] = [] - - @root_validator(pre=False, skip_on_failure=True) - def at_least_one_given(cls, values: dict): - is_provided = lambda x: x is not None - keys = [k for k in values.keys() if - k not in ['name', 'version', 'customer']] - if not any(is_provided(v) for v in (values.get(key) for key in keys)): - raise ValueError( - f'At least one of {", ".join(keys)} must be provided' - ) - return values - - -class RulesetDeleteModel(PreparedEvent): - name: str - version: str - customer: Optional[str] - - -class RulesetGetModel(PreparedEvent): - name: Optional[str] - version: Optional[str] - cloud: Optional[RuleDomain] - customer: Optional[str] - get_rules: Optional[bool] = False - active: Optional[bool] - licensed: Optional[bool] - - @root_validator(skip_on_failure=True) - def validate_codependent_params(cls, values: dict) -> dict: - name, version = values.get('name'), values.get('version') - if version and not name: - raise ValueError('\'name\' is required if \'version\' is given') - if name and version and (values.get('cloud') or values.get('active')): - raise ValueError( - 'you don\'t have to specify \'cloud\' or \'active\' ' - 'if \'name\' and \'version\' are given') - return values - - -class RulesetContentGetModel(PreparedEvent): - name: str - version: str - customer: Optional[str] - - -# Rules -class RuleDeleteModel(PreparedEvent): - rule: Optional[str] - customer: Optional[str] - cloud: Optional[RuleDomain] - git_project_id: Optional[str] - git_ref: Optional[str] - - @root_validator(pre=False) - def validate_root(cls, values: dict) -> dict: - if values.get('git_ref') and not values.get('git_project_id'): - raise ValueError('git_project_id must be specified with git_ref') - return values - - -class RuleGetModel(PreparedEvent): - rule: Optional[str] - cloud: Optional[RuleDomain] - git_project_id: Optional[str] - git_ref: Optional[str] - customer: Optional[str] - limit: Optional[int] = 50 - next_token: Optional[str] - - @root_validator(pre=False) - def validate_root(cls, values: dict) -> dict: - if values.get('git_ref') and not values.get('git_project_id'): - raise ValueError('git_project_id must be specified with git_ref') - return values - - -class RuleUpdateMetaPostModel(BaseModel): - customer: Optional[str] - rule_source_id: Optional[str] - - -# Rule Sources -class GitAccessType(str, Enum): - TOKEN = 'TOKEN' - - -class RuleSourcePostModel(BaseModel): - git_project_id: str - git_url: Optional[GitURLType] - git_ref: str = 'main' - git_rules_prefix: str = '/' - git_access_type: GitAccessType = 'TOKEN' - git_access_secret: Optional[str] - customer: Optional[str] - tenant_allowance: Optional[list] = [] - description: str - - @root_validator(pre=False, skip_on_failure=True) - def root(cls, values: dict) -> dict: - values['git_project_id'] = values['git_project_id'].strip('/') - is_github = values['git_project_id'].count('/') == 1 - is_gitlab = values['git_project_id'].isdigit() - if not values.get('git_url'): - if is_github: - values['git_url'] = 'https://api.github.com' - elif is_gitlab: - values['git_url'] = 'https://git.epam.com' - else: - raise ValueError( - 'unknown git_project_id. ' - 'Specify Gitlab project id or Github owner/repo' - ) - if is_gitlab and not values.get('git_access_secret'): - raise ValueError('git_access_secret is required for GitLab') - return values - - -class RuleSourcePatchModel(BaseModel): - id: str - customer: Optional[str] - git_access_type: Optional[GitAccessType] - git_access_secret: Optional[str] - tenant_allowance: Optional[list] - tenant_restriction: Optional[list] - description: Optional[str] - - @root_validator(pre=False) - def validate_any_to_update(cls, values): - attrs = [ - 'git_access_type', - 'git_access_secret', - 'tenant_allowance', - 'tenant_restriction', - 'description' - ] - missing = [attr for attr in attrs - if attr not in values or values[attr] is None] - if len(missing) == len(attrs): - msg = 'Request requires at least one of the following parameters: ' - msg += ', '.join(map("'{}'".format, attrs)) - raise ValueError(msg) - return values - - -class RuleSourceDeleteModel(BaseModel): - id: str - customer: Optional[str] - - -class RuleSourceGetModel(BaseModel): - id: Optional[str] - git_project_id: Optional[str] - customer: Optional[str] - - -# Tenants -class TenantGetModel(BaseModel): - tenant_name: Optional[str] - customer: Optional[str] - cloud_identifier: Optional[str] - complete: Optional[bool] = False - limit: Optional[int] = 10 - next_token: Optional[str] - - -class TenantPostModel(BaseModel): - name: str - display_name: Optional[str] - cloud: Literal['AWS', 'AZURE', 'GOOGLE'] - cloud_identifier: str - primary_contacts: List[str] = [] - secondary_contacts: List[str] = [] - tenant_manager_contacts: List[str] = [] - default_owner: Optional[str] - - @root_validator(skip_on_failure=True) - def set_display_name(cls, values): - if not values.get('display_name'): - values['display_name'] = values['name'] - return values - - -class TenantPatchModel(BaseModel): - tenant_name: str - rules_to_exclude: Optional[Set[str]] - rules_to_include: Optional[Set[str]] - - # send_scan_result: Optional[bool] - - @root_validator(skip_on_failure=True) - def at_least_one_given(cls, values: dict): - is_provided = lambda x: x is not None - keys = [k for k in values.keys() if k != 'tenant_name'] - if not any(is_provided(v) for v in (values.get(key) for key in keys)): - raise ValueError( - f'At least one of {", ".join(keys)} must be provided' - ) - return values - - -class TenantDeleteModel(BaseModel): - pass - - -class TenantRegionPostModel(BaseModel): - tenant_name: Optional[str] - region: str # means native region name - - @validator('region') - def validate_region(cls, value): - """ - Of course, we can use typing "region: AllRegions", but the output is - huge is validation fails - """ - if not AllRegions.has(value): - raise ValueError(f'Not known region: {value}') - return value - - -# Findings -class FindingsDataType(str, Enum): - map_type = 'map_type' - list_type = 'list_type' - - -class FindingsExpandOn(str, Enum): - resources = 'resources' - - -class FindingsGetModel(BaseModel): - tenant_name: Optional[str] - # Output type. - get_url: Optional[bool] = False - raw: Optional[bool] = False - # Format scheming. - expand_on: FindingsExpandOn - data_type: FindingsDataType - # Filter scheming. - # Explicit strings. - map_key: Optional[str] - dependent_inclusion: Optional[bool] = False - rules_to_include: Optional[str] - resource_types_to_include: Optional[str] - regions_to_include: Optional[str] - severities_to_include: Optional[str] - - -class FindingsDeleteModel(BaseModel): - tenant_name: Optional[str] - - -# Credential manager -class CredentialsManagerPostModel(BaseModel): - cloud_identifier: str - trusted_role_arn: str - cloud: Literal['AWS', 'AZURE', 'GCP'] - enabled: bool = True - - -class CredentialsManagerDeleteModel(BaseModel): - cloud_identifier: str - cloud: Literal['AWS', 'AZURE', 'GCP'] - - -class CredentialsManagerPatchModel(BaseModel): - cloud_identifier: str - cloud: Literal['AWS', 'AZURE', 'GCP'] - trusted_role_arn: Optional[str] - enabled: Optional[bool] - - -class CredentialsManagerGetModel(BaseModel): - cloud_identifier: Optional[str] - cloud: Optional[Literal['AWS', 'AZURE', 'GCP']] - - -# Role -class RolePostModel(BaseModel): - name: str - policies: Set[str] - customer: Optional[str] - expiration: Optional[datetime] = Field( - default_factory=lambda: utc_datetime() + timedelta(days=60) - ) - - -class RoleDeleteModel(BaseModel): - name: str - customer: Optional[str] - - -class RolePatchModel(BaseModel): - name: str - policies_to_attach: Optional[Set[str]] = Field(default_factory=set) - policies_to_detach: Optional[Set[str]] = Field(default_factory=set) - expiration: Optional[datetime] - customer: Optional[str] - - @root_validator(skip_on_failure=True) - def to_attach_or_to_detach(cls, values: dict) -> dict: - required = ('policies_to_attach', 'policies_to_detach', 'expiration') - if not any(values.get(key) for key in required): - raise ValueError(f'At least one of: {", ".join(required)} ' - f'must be specified') - return values - - -class RoleGetModel(BaseModel): - name: Optional[str] - customer: Optional[str] - - -class RoleCacheDeleteModel(BaseModel): - customer: Optional[str] - name: Optional[str] - - -PermissionType = constr(regex=r'^([\w-]+|\*):([\w-]+|\*)$') - - -# Policy -class PolicyPostModel(BaseModel): - # todo add check to pass either permissions or permissions_admin - name: str - permissions: Set[PermissionType] - customer: Optional[str] - - -class PolicyDeleteModel(BaseModel): - name: str - customer: Optional[str] - - -class PolicyPatchModel(BaseModel): - name: str - permissions_to_attach: Set[str] = Field(default_factory=set) - permissions_to_detach: Set[str] = Field(default_factory=set) - customer: Optional[str] - - @root_validator(skip_on_failure=True) - def to_attach_or_to_detach(cls, values: dict) -> dict: - required = ('permissions_to_attach', 'permissions_to_detach') - if not any(values.get(key) for key in required): - raise ValueError(f'At least one of: {", ".join(required)} ' - f'must be specified') - return values - - -class PolicyGetModel(BaseModel): - name: Optional[str] - customer: Optional[str] - - -class PolicyCacheDeleteModel(BaseModel): - name: Optional[str] - customer: Optional[str] - - -# Jobs -class JobGetModel(BaseModel): - customer: Optional[str] - tenant_name: Optional[str] - limit: Optional[int] = 10 - next_token: Optional[str] - - -class SoloJobGetModel(BaseModel): - job_id: str - customer: Optional[str] - - -class AWSCredentials(BaseModel): - AWS_ACCESS_KEY_ID: str - AWS_SECRET_ACCESS_KEY: str - AWS_SESSION_TOKEN: Optional[str] - AWS_DEFAULT_REGION: Optional[str] = 'us-east-1' - - -# TODO, add certificates & username-password creds -# https://learn.microsoft.com/en-us/dotnet/api/azure.identity.environmentcredential?view=azure-dotnet -class AZURECredentials(BaseModel): - AZURE_TENANT_ID: str - AZURE_CLIENT_ID: str - AZURE_CLIENT_SECRET: str - AZURE_SUBSCRIPTION_ID: Optional[str] - - -class GOOGLECredentials1(BaseModel): - class Config: - use_enum_values = True - extra = 'allow' - - type: str - project_id: str - private_key_id: str - private_key: str - client_email: str - client_id: str - auth_uri: str - token_uri: str - auth_provider_x509_cert_url: str - client_x509_cert_url: str - - -class GOOGLECredentials2(BaseModel): - type: str - access_token: str - refresh_token: str - client_id: str - client_secret: str - project_id: str - - -class GOOGLECredentials3(BaseModel): - access_token: str - project_id: str - - -class JobPostModel(BaseModel): - credentials: Optional[Union[ - AWSCredentials, AZURECredentials, GOOGLECredentials1, - GOOGLECredentials2, GOOGLECredentials3]] - tenant_name: Optional[str] - target_rulesets: Optional[Set[str]] = Field(default_factory=set) - target_regions: Optional[Set[str]] = Field(default_factory=set) - rules_to_scan: Optional[Set[str]] = Field(default_factory=set) - # ruleset_license_priority: Optional[Dict[str, List[str]]] - # check_permission: Optional[bool] = True - customer: Optional[str] - - -class StandardJobPostModel(BaseModel): - """ - standard jobs means not licensed job -> without licensed rule-sets - """ - credentials: Optional[Union[ - AWSCredentials, AZURECredentials, GOOGLECredentials1, - GOOGLECredentials2, GOOGLECredentials3]] - tenant_name: Optional[str] - target_rulesets: Optional[Set[str]] = Field(default_factory=set) - target_regions: Optional[Set[str]] = Field(default_factory=set) - - -class JobDeleteModel(BaseModel): - job_id: str - # we don't have this parameter, but it does not spoil anything - customer: Optional[str] - - -# ScheduledJobs -class ScheduledJobGetModel(BaseModel): - customer: Optional[str] - tenant_name: Optional[str] - - -class SoloScheduledJobGetModel(BaseModel): - name: str - - -class ScheduledJobPostModel(BaseModel): - schedule: str - tenant_name: Optional[str] - customer: Optional[str] - name: Optional[str] - target_rulesets: Optional[Set[str]] = Field(default_factory=set) - target_regions: Optional[Set[str]] = Field(default_factory=set) - - -class ScheduledJobDeleteModel(BaseModel): - name: str - customer: Optional[str] - - -class ScheduledJobPatchModel(BaseModel): - name: str - schedule: Optional[str] - enabled: Optional[bool] - customer: Optional[str] - - @root_validator(skip_on_failure=True) - def at_least_one_given(cls, values: dict): - is_provided = lambda x: x is not None - keys = ('enabled', 'schedule') - if not any(is_provided(v) for v in (values.get(key) for key in keys)): - raise ValueError( - f'At least one of {", ".join(keys)} must be provided' - ) - return values - - -# Event-driven -class EventVendor(str, Enum): - AWS = 'AWS' - MAESTRO = 'MAESTRO' - - -class EventPostModel(BaseModel): - version: constr(strip_whitespace=True) = '1.0.0' - vendor: EventVendor - events: List[Dict] - - @validator('version', pre=False) - def allowed_version(cls, value: str) -> str: - if value not in ('1.0.0',): - raise ValueError(f'Not allowed event version: {value}') - return value - - -class UserPasswordResetPostModel(BaseModel): - password: str - username: Optional[str] - - # validators - _validate_password = validator('password', allow_reuse=True)( - password_must_be_secure) - - -# User Tenants -class UserTenantsDeleteModel(BaseModel): - all: Optional[bool] = False - tenants: Optional[list] - target_user: Optional[str] - - -class UserTenantsPatchModel(BaseModel): - tenants: List[str] - target_user: Optional[str] - - -class UserTenantsGetModel(BaseModel): - target_user: Optional[str] - - -# User Role -class UserRoleGetModel(BaseModel): - target_user: Optional[str] - - -class UserRolePostModel(BaseModel): - role: str - target_user: Optional[str] - - -class UserRolePatchModel(BaseModel): - role: str - target_user: Optional[str] - - -class UserRoleDeleteModel(BaseModel): - target_user: Optional[str] - - -# User Customer -class UserCustomerGetModel(BaseModel): - target_user: Optional[str] - - -class UserCustomerPostModel(BaseModel): - customer: str - target_user: Optional[str] - - -class UserCustomerPatchModel(BaseModel): - customer: str - target_user: Optional[str] - - -class UserCustomerDeleteModel(BaseModel): - target_user: Optional[str] - - -class UserDeleteModel(BaseModel): - customer: Optional[str] - username: Optional[str] - - -# Signup - -class SignUpPostModel(BaseModel): - username: str - password: str - customer: str - role: str - tenants: Optional[List[str]] - - # validators - _validate_password = validator('password', allow_reuse=True)( - password_must_be_secure) - - -class SignInPostModel(BaseModel): - username: str - password: str - - -# Report - - -# Siem -# class DojoConfiguration(BaseModel): -# host: str -# api_key: str -# user: str -# upload_files: Optional[bool] = False -# display_all_fields: Optional[bool] = False -# resource_per_finding: Optional[bool] = False -# -# -# class DojoConfigurationPatch(DojoConfiguration): -# host: Optional[str] -# api_key: Optional[str] -# user: Optional[str] -# -# -# class SecurityHubConfiguration(BaseModel): -# aws_region: str -# product_arn: str -# trusted_role_arn: Optional[str] -# -# -# class SecurityHubConfigurationPatch(DojoConfiguration): -# aws_region: Optional[str] -# product_arn: Optional[str] -# -# -# class EntitiesMapping(BaseModel): -# product_type_name: Optional[str] -# test_title: Optional[str] -# product_name: Optional[str] -# engagement_name: Optional[str] - - -class MailSettingGetModel(BaseModel): - disclose: bool = False - - -class MailSettingPostModel(BaseModel): - username: str - password: str - password_alias: str - port: int - host: str - max_emails: int = 1 - default_sender: str - use_tls: bool = False - - -class LicenseManagerConfigSettingPostModel(BaseModel): - host: str - port: Optional[int] - protocol: Optional[Literal['HTTP', 'HTTPS']] - stage: Optional[str] - api_version: Optional[str] - - @root_validator(pre=True) - def _(cls, values: dict) -> dict: - prot = values.get('protocol') - if isinstance(prot, str): - values['protocol'] = prot.upper() - return values - - -class LicenseManagerClientSettingsGetModel(BaseModel): - format: KeyFormat - - -class LicenseManagerClientSettingPostModel(BaseModel): - key_id: str - algorithm: str - private_key: str - format: KeyFormat = 'PEM' - b64_encoded: bool - - @root_validator(pre=True) - def check_properly_encoded_key(cls, values): - is_encoded = values.get('b64_encoded') - key: str = values.get('private_key') - if is_encoded: - try: - key = standard_b64decode(key).decode() - except (TypeError, BaseException): - raise ValueError( - '\'private_key\' must be a safe to decode' - ' base64-string.' - ) - values['private_key'] = key - return values - - -class LicenseManagerClientSettingDeleteModel(BaseModel): - key_id: str - - -# BatchResults -class SoleBatchResultsGetModel(BaseModel): - batch_results_id: str - customer: Optional[str] - - -class BatchResultsGetModel(BaseModel): - tenant_name: Optional[str] - customer: Optional[str] - - start: Optional[datetime] - end: Optional[datetime] - - limit: Optional[int] = 10 - next_token: Optional[str] - - -# Reports - -class ReportGetModel(BaseModel): - job_id: Optional[str] - tenant_name: Optional[str] - detailed: Optional[bool] = False - get_url: Optional[bool] = False - - -class JobType(str, Enum): - manual = 'manual' - reactive = 'reactive' - - -class ErrorReportFormat(str, Enum): - json = 'json' - xlsx = 'xlsx' - - -class RuleReportFormat(str, Enum): - json = 'json' - xlsx = 'xlsx' - - -RANGE_DAYS = 7 - - -class TimeRangedReportModel(BaseModel): - """ - Base model which provides time-range constraint - """ - start_iso: Optional[datetime] - end_iso: Optional[datetime] - - @root_validator(pre=False) - def check_start_end_iso_range(cls, values): - """ - :key values[end]: str, upper bound - :key values[start]: str, lower bound - Enforces a constraint of 7 day range. - """ - now = datetime.utcnow() - end: Optional[datetime] = values.get('end_iso') - start: Optional[datetime] = values.get('start_iso') - - if not start: - # Shifts by RANGE_DAYS days, by default. - start = get_day_captured_utc( - _from=end, shift=dict(days=RANGE_DAYS), - reset=dict(hour=0, minute=0, second=0, microsecond=0), - now=now - ) - else: - assert now >= start, '\'start_iso\' cannot ahead the current time.' - - if not end: - rem = (now - start).days - if rem > RANGE_DAYS: - rem = RANGE_DAYS - # rem would not be < 0, due to assertion. - end = get_day_captured_utc( - _from=start, _back=False, shift=dict(days=rem + 1), - reset=dict(minute=0, second=0, microsecond=0), - now=now - ) - else: - assert now > end, '\'end_iso\' cannot ahead the current time.' - - if end < start: - raise ValueError( - 'Value of \'end_iso\' must be >= \'start_iso\' date.' - ) - elif values.get('end_iso') and (end - start).days > RANGE_DAYS: - raise ValueError( - 'Range of days between \'start_iso\' and \'end_iso\' must ' - f'not overflow {RANGE_DAYS}.' - ) - - values['end_iso'] = end - values['start_iso'] = start - return values - - -class JobReportGetModel(BaseModel): - """ - /reports/digests/jobs/{id} - """ - id: str - type: JobType = 'manual' - href: bool = True - - -class TenantsReportGetModel(BaseModel): - """ - $child = [digests, details] - /reports/$child/tenants - /reports/$child/tenants/jobs - """ - customer: Optional[str] - type: Optional[JobType] - href: bool = True - - -class TenantReportGetModel(TenantsReportGetModel): - """ - $child = [digests, details] - /reports/$child/tenants/{tenant_name}/jobs - """ - tenant_name: str - - -class TimeRangedTenantsReportGetModel(TimeRangedReportModel, - TenantsReportGetModel): - ... - - -class TimeRangedTenantReportGetModel(TimeRangedReportModel, - TenantReportGetModel): - ... - - -# Compliance Reporting -# todo models for more flexible queries, such as compliance by regions. -class JobComplianceReportGetModel(JobReportGetModel): - ... - - -class TenantComplianceReportGetModel(TenantReportGetModel): - ... - - -# Error Reporting -class JobErrorReportGetModel(JobReportGetModel): - href: bool = False - format: Optional[ErrorReportFormat] = 'json' - - -class TenantsErrorReportGetModel(TimeRangedTenantsReportGetModel): - href: bool = False - format: Optional[ErrorReportFormat] = 'json' - - -class TenantErrorReportGetModel(TimeRangedTenantReportGetModel): - href: bool = False - format: Optional[ErrorReportFormat] = 'json' - - -# Rule Reporting -class JobRuleReportGetModel(JobReportGetModel): - href: bool = False - format: Optional[RuleReportFormat] = 'json' - rule: Optional[str] - - -class TenantsRuleReportGetModel(TimeRangedTenantsReportGetModel): - href: bool = False - format: Optional[RuleReportFormat] = 'json' - rule: Optional[str] - - -class TenantRuleReportGetModel(TimeRangedTenantReportGetModel): - href: bool = False - format: Optional[RuleReportFormat] = 'json' - rule: Optional[str] - - -class ReportPushByJobIdModel(BaseModel): - """ - /reports/push/dojo/{job_id}/ - /reports/push/security-hub/{job_id}/ - """ - job_id: str - customer: Optional[str] - - -class ReportPushMultipleModel(TimeRangedReportModel): - """ - /reports/push/dojo - /reports/push/security-hub - """ - tenant_name: str - customer: Optional[str] - type: Optional[JobType] - - -class EventDrivenRulesetGetModel(PreparedEvent): - cloud: Optional[RuleDomain] - get_rules: Optional[bool] = False - - -class EventDrivenRulesetPostModel(PreparedEvent): - # name: str - cloud: RuleDomain - version: float - rules: Optional[list] = [] - # rule_version: Optional[float] - - -class EventDrivenRulesetDeleteModel(PreparedEvent): - # name: str - cloud: RuleDomain - version: float - - -class AccessApplicationPostModel(BaseModel): - customer: Optional[str] - description: Optional[str] = 'Custodian access application' - username: Optional[str] - password: Optional[str] - auto_resolve_access: bool = False - url: Optional[AnyUrl] # full link: https://host.com:port/hello - results_storage: Optional[str] - - @root_validator(skip_on_failure=True) - def _(cls, values: dict) -> dict: - if bool(values['username']) ^ bool(values['password']): - raise ValueError('Both username and password must be given') - if not values.get('auto_resolve_access') and not values.get('url'): - raise ValueError('`url` must be given in case ' - '`auto_resolve_access` is not True') - - return values - - -class AccessApplicationPatchModel(BaseModel): - application_id: str - customer: Optional[str] - description: Optional[str] - username: Optional[str] - password: Optional[str] - auto_resolve_access: bool = False - url: Optional[AnyUrl] # full link: https://host.com:port/hello - results_storage: Optional[str] - - @root_validator(skip_on_failure=True) - def _(cls, values: dict) -> dict: - if bool(values['username']) ^ bool(values['password']): - raise ValueError('Both username and password must be given') - return values - - -class AccessApplicationGetModel(BaseModel): - customer: Optional[str] - application_id: str - - -class AccessApplicationListModel(BaseModel): - customer: Optional[str] - - -class AccessApplicationDeleteModel(BaseModel): - application_id: str - customer: Optional[str] - - -class DojoApplicationPostModel(BaseModel): - customer: Optional[str] - description: Optional[str] = 'Custodian Defect Dojo' - api_key: str - url: AnyUrl # full link: https://host.com:port/hello - - -class DojoApplicationPatchModel(BaseModel): - application_id: str - customer: Optional[str] - description: Optional[str] - url: Optional[AnyUrl] - api_key: Optional[str] - - @root_validator(pre=False) - def at_least_one_given(cls, values: dict): - is_provided = lambda x: bool(x) - keys = [k for k in values.keys() if - k not in ['application_id', 'customer']] - if not any(is_provided(v) for v in (values.get(key) for key in keys)): - raise ValueError( - f'At least one of {", ".join(keys)} must be provided' - ) - return values - - -class DojoApplicationGetModel(BaseModel): - customer: Optional[str] - application_id: str - - -class DojoApplicationListModel(BaseModel): - customer: Optional[str] - - -class DojoApplicationDeleteModel(BaseModel): - application_id: str - customer: Optional[str] - - -class ApplicationPostModel(BaseModel): - customer: Optional[str] - description: Optional[str] = 'Custodian application' - cloud: Optional[RuleDomain] - access_application_id: Optional[str] - tenant_license_key: Optional[str] - - @root_validator(skip_on_failure=True) - def _(cls, values: dict) -> dict: - _cloud_modifier = (bool(values['access_application_id']) or - bool(values['tenant_license_key'])) - _cloud = bool(values['cloud']) - - if _cloud ^ _cloud_modifier: - raise ValueError( - 'Both cloud and access_application_id or ' - 'tenant_license_key must be specified or omitted' - ) - return values - - -class ApplicationPatchModel(BaseModel): - application_id: str - customer: Optional[str] - description: Optional[str] - cloud: Optional[RuleDomain] - access_application_id: Optional[str] - tenant_license_key: Optional[str] - - @root_validator(skip_on_failure=True) - def _(cls, values: dict) -> dict: - _cloud_modifier = (bool(values['access_application_id']) or - bool(values['tenant_license_key'])) - _cloud = bool(values['cloud']) - - if _cloud ^ _cloud_modifier: - raise ValueError( - 'Both cloud and access_application_id or ' - 'tenant_license_key must be specified or omitted' - ) - return values - - -class ApplicationGetModel(BaseModel): - customer: Optional[str] - application_id: str - - -class ApplicationListModel(BaseModel): - customer: Optional[str] - - -class ApplicationDeleteModel(BaseModel): - application_id: str - customer: Optional[str] - - -class ParentPostModel(PreparedEvent): - customer: Optional[str] - application_id: str - description: Optional[str] = 'Custodian parent' - cloud: Optional[Literal['AWS', 'AZURE', 'GOOGLE']] - scope: ParentScope - rules_to_exclude: Set[str] = Field(default_factory=set) - type: ParentType - tenant_name: Optional[str] - - @root_validator(pre=False) - def _(cls, values: dict) -> dict: - if (values['scope'] != ParentScope.ALL and - not values.get('tenant_name')): - raise ValueError(f'tenant_name is required if scope ' - f'is {values["scope"]}') - return values - - -class ParentPatchModel(PreparedEvent): - parent_id: str - application_id: Optional[str] - description: Optional[str] - rules_to_exclude: Optional[Set[str]] = Field(default_factory=set) - rules_to_include: Optional[Set[str]] = Field(default_factory=set) - customer: Optional[str] - - @root_validator(skip_on_failure=True) - def at_least_one_given(cls, values: dict): - is_provided = lambda x: bool(x) - keys = [k for k in values.keys() if k not in ['parent_id', 'customer']] - if not any(is_provided(v) for v in (values.get(key) for key in keys)): - raise ValueError( - f'At least one of {", ".join(keys)} must be provided' - ) - return values - - -class ParentGetModel(BaseModel): - parent_id: str - - -class ParentDeleteModel(BaseModel): - parent_id: str - - -class ParentListModel(BaseModel): - customer: Optional[str] - - -class ProjectGetReportModel(BaseModel): - tenant_display_names: str - types: Optional[str] - customer: Optional[str] - receivers: Optional[str] - - -class OperationalGetReportModel(BaseModel): - tenant_names: str - types: Optional[str] - receivers: Optional[str] - customer: Optional[str] - - -class DepartmentGetReportModel(BaseModel): - types: Optional[str] - customer: Optional[str] - - -class CLevelGetReportModel(BaseModel): - types: Optional[str] - customer: Optional[str] - - -class HealthCheckGetModel(BaseModel): - status: Optional[HealthCheckStatus] - - -class SoleHealthCheckGetModel(BaseModel): - id: str - - -class RabbitMQPostModel(BaseModel): - customer: Optional[str] - maestro_user: str - rabbit_exchange: Optional[str] - request_queue: str - response_queue: str - sdk_access_key: str - connection_url: AmqpDsn - sdk_secret_key: str - - -class RabbitMQGetModel(BaseModel): - customer: Optional[str] - - -class RabbitMQDeleteModel(BaseModel): - customer: Optional[str] - - -class ResourcesReportGet(BaseModel): - tenant_name: str - identifier: str - exact_match: bool = True - search_by: Optional[List[str]] = [] - search_by_all: bool = False - resource_type: Optional[constr(to_lower=True, - strip_whitespace=True)] # choice - region: Optional[AllRegionsWithMultiregional] - - @validator('tenant_name') - def upper(cls, value: str) -> str: - return value.upper() - - @validator('search_by', pre=True) - def to_list(cls, value: str) -> List[str]: - if not isinstance(value, str): - raise ValueError('search_by must be a string') - return value.lower().split(',') - - @validator('region', pre=False) - def region_to_lover(cls, value: Optional[str]) -> str: - if value: - value = value.strip().lower() - return value - - @root_validator(pre=False, skip_on_failure=True) - def root(cls, values: dict) -> dict: - search_by = values.get('search_by') - search_by_all = values.get('search_by_all') - if search_by_all and search_by: - raise ValueError('search_by must not be specified if ' - 'search_by_all') - return values - - -class ResourceReportJobsGet(TimeRangedReportModel, TenantReportGetModel): - identifier: str - - exact_match: bool = True - search_by: Optional[List[str]] = [] - search_by_all: bool = False - resource_type: Optional[str] # choice - region: Optional[AllRegionsWithMultiregional] - - @validator('tenant_name') - def upper(cls, value: str) -> str: - return value.upper() - - @validator('search_by', pre=True) - def to_list(cls, value: str) -> List[str]: - if not isinstance(value, str): - raise ValueError('search_by must be a string') - return value.lower().split(',') - - -class ResourceReportJobGet(JobReportGetModel): - identifier: str - - exact_match: bool = True - search_by: Optional[List[str]] = [] - search_by_all: bool = False - resource_type: Optional[str] # choice - region: Optional[AllRegionsWithMultiregional] - - @validator('search_by', pre=True) - def to_list(cls, value: str) -> List[str]: - if not isinstance(value, str): - raise ValueError('search_by must be a string') - return value.lower().split(',') - - -class PlatformK8sNativePost(PreparedEvent): - tenant_name: str - name: str - endpoint: HttpUrl - certificate_authority: str # base64 encoded - token: Optional[str] - description: Optional[str] - - -class PlatformK8sEksPost(PreparedEvent): - tenant_name: str - name: str - region: AWSRegion - application_id: str - description: Optional[str] - - -class PlatformK8sDelete(PreparedEvent): - id: str - - -class PlatformK8sQuery(PreparedEvent): - tenant_name: Optional[str] - - -class K8sJobPostModel(PreparedEvent): - """ - K8s platform job - """ - platform_id: str - target_rulesets: Optional[Set[str]] = Field(default_factory=set) - token: Optional[str] # temp jwt token - - -ALL_MODELS = set( - obj for name, obj in getmembers(sys.modules[__name__]) - if (not name.startswith('_') and isinstance(obj, type) and - issubclass(obj, BaseModel)) -) -ALL_MODELS.remove(BaseModel) - -ALL_MODELS_WITHOUT_GET = { - model for model in ALL_MODELS if 'Get' not in model.__name__ -} diff --git a/src/validators/response_validation.py b/src/validators/response_validation.py deleted file mode 100644 index a5eb8a9e1..000000000 --- a/src/validators/response_validation.py +++ /dev/null @@ -1,562 +0,0 @@ -import sys -from enum import Enum -from inspect import getmembers -from typing import Dict, List, Optional, Union, Literal - -from pydantic import BaseModel - -from helpers.constants import PlatformType - - -class BaseResponseModel(BaseModel): - trace_id: str - - -class BaseMessageResponseModel(BaseResponseModel): - message: str - - -class UnauthorizedResponseModel(BaseModel): - message: str - - -class DojoConfiguration(BaseModel): - host: Optional[str] - key: Optional[str] - user: Optional[str] - upload_files: Optional[bool] - display_all_fields: Optional[bool] - resource_per_finding: Optional[bool] - - -class SecurityHubConfiguration(BaseModel): - region: Optional[str] - product_arn: Optional[str] - - -class EntitiesMapping(BaseModel): - product_type_name: Optional[str] - test_title: Optional[str] - product_name: Optional[str] - engagement_name: Optional[str] - - -class SiemConfigurationType(str, Enum): - dojo = 'dojo' - security_hub = 'security_hub' - - -class Cloud(str, Enum): - aws = 'aws' - azure = 'azure' - gcp = 'gcp' - GCP = 'GCP' - AWS = 'AWS' - AZURE = 'AZURE' - - -class JobStatus(str, Enum): - FAILED = 'FAILED' - SUCCEEDED = 'SUCCEEDED' - SUBMITTED = 'SUBMITTED' - PENDING = 'PENDING' - RUNNABLE = 'RUNNABLE' - STARTING = 'STARTING' - RUNNING = 'RUNNING' - - -class RuleSourceSyncStatus(Enum): - SYNCING = 'SYNCING' - SYNCED = 'SYNCED' - - -class ActivationState(Enum): - ACTIVE = 'ACTIVE' - INACTIVE = 'INACTIVE' - - -# TODO remove Optional everywhere - -class Customer(BaseModel): - name: str - display_name: str - admins: List[str] - latest_login: str - activation_date: str - - -class Tenant(BaseModel): - name: str - customer_name: str - activation_date: Optional[str] - is_active: Optional[bool] - regions: List[str] - project: str - - -class Rule(BaseModel): - id: str - description: str - service_section: str - cloud: Cloud - customer: str - - -class Job(BaseModel): - job_id: str - job_owner: str - tenant_display_name: str - status: JobStatus - scan_regions: List[str] - scan_rulesets: List[str] - started_at: Optional[str] - stopped_at: Optional[str] - - -class Policy(BaseModel): - customer: str - name: str - permissions: List[str] - - -class Role(BaseModel): - customer: str - name: str - expiration: str - policies: List[str] - - -class Ruleset(BaseModel): - customer: str - name: str - version: str - cloud: Cloud - rules_number: Optional[int] - status_code: Optional[str] - status_reason: Optional[str] - event_driven: Optional[bool] - active: Optional[bool] - license_keys: Optional[List[str]] - licensed: Optional[bool] - status_last_update_time: str - allowed_for: Optional[Union[dict, str]] - - -class RuleSource(BaseModel): - id: str - customer: str - latest_sync_current_status: RuleSourceSyncStatus - git_url: str - git_ref: str - git_rules_prefix: str - latest_sync_sync_date: Optional[str] - restrict_from: Optional[Dict[str, List[str]]] - - -class CredentialsManager(BaseModel): - cloud: Cloud - cloud_identifier: str - credentials_key: Optional[str] - expiration: Optional[str] - trusted_role_arn: Optional[str] - enabled: Optional[bool] - - -class RuleMetaUpdate(BaseModel): - customer: str - id: Optional[str] - status: str - - -class UserTenant(BaseModel): - tenants: str - - -class UserRole(BaseModel): - role: str - - -class UserCustomer(BaseModel): - customer: str - - -class SecurityHubSiem(BaseModel): - type: str - customer: str - configuration: Optional[SecurityHubConfiguration] - - -class DojoSiem(BaseModel): - type: str - customer: str - configuration: Optional[DojoConfiguration] - entities_mapping: Optional[EntitiesMapping] - - -class Findings(BaseModel): - presigned_url: str - - -class User(BaseModel): - username: str - customer: str - role: str - tenants: Optional[str] - - -class Report(BaseModel): - bucket_name: str - file_key: str - presigned_url: str - - -class SiemPush(BaseModel): - job_id: str - type: SiemConfigurationType - status: int - message: Optional[str] - error: Optional[str] - - -class TenantLicensePriority(BaseModel): - tenant: str - priority_id: str - ruleset: str - license_keys: List[str] - - -class ScheduledJob(BaseModel): - name: str - customer_name: str - creation_date: str - enabled: bool - last_execution_time: Optional[str] - schedule: str - scan_regions: Optional[list] - scan_rulesets: Optional[list] - - -class BackUpResponse(BaseModel): - title: str - commit_url: str - stats: str - git_files_created: str - ssm_params_created: str - - -class HealthCheck(BaseModel): - API: str - MinIO: str - Vault: str - MongoDB: str - - -class MailSetting(BaseModel): - username: str - password: str - default_sender: str - host: str - port: str - max_emails: int - use_tls: bool - - -class LicenseManagerConfig(BaseModel): - host: str - port: int - version: float - - -class LicenseManagerClient(BaseModel): - kid: str - alg: str - public_key: Optional[str] - format: Optional[str] - - -class JobReport(BaseModel): - id: str - type: str - # Either of the following. - content: Optional[Dict] - href: Optional[str] - - -class TenantReport(BaseModel): - tenant: str - # Either of the following. - content: Optional[Dict] - href: Optional[str] - - -class JobRuleReport(BaseModel): - id: str - type: str - # Either of the following. - content: Optional[List] - href: Optional[str] - - -class TenantRuleReport(BaseModel): - tenant: str - # Either of the following. - content: Optional[List] - href: Optional[str] - - -class ApplicationMeta(BaseModel): - awsAid: Optional[str] - azureAid: Optional[str] - googleAid: Optional[str] - awsLk: Optional[str] - azureLk: Optional[str] - googleLk: Optional[str] - - -class AccessApplicationMeta(BaseModel): - username: Optional[str] - host: Optional[str] - port: Optional[int] - protocol: Optional[Literal['HTTP', 'HTTPS']] - stage: Optional[str] - - -class Application(BaseModel): - application_id: str - customer_id: str - description: str - meta: ApplicationMeta - - -class AccessApplication(BaseModel): - application_id: str - customer_id: str - description: str - meta: AccessApplicationMeta - - -class ParentMeta(BaseModel): - cloud: Optional[Literal['AWS', 'AZURE', 'GOOGLE', 'ALL']] - scope: Optional[Literal['ALL', 'SPECIFIC_TENANT']] - - -class Parent(BaseModel): - application_id: str - customer_id: str - description: str - is_deleted: bool - meta: ParentMeta - - -class RabbitMQ(BaseModel): - maestro_user: str - rabbit_exchange: Optional[str] - request_queue: str - response_queue: str - sdk_access_key: str - customer: str - - -class RuleSourceDTO(BaseResponseModel): - items: List[RuleSource] - - -class RulesetDTO(BaseResponseModel): - items: List[Ruleset] - - -class RoleDTO(BaseResponseModel): - items: List[Role] - - -class PolicyDTO(BaseResponseModel): - items: List[Policy] - - -class JobDTO(BaseResponseModel): - items: List[Job] - - -class RuleDTO(BaseResponseModel): - items: List[Rule] - - -class CustomerDTO(BaseResponseModel): - items: List[Customer] - - -class TenantDTO(BaseResponseModel): - items: List[Tenant] - - -class CredentialsManagerDTO(BaseResponseModel): - items: List[CredentialsManager] - - -class RuleMetaUpdateDTO(BaseResponseModel): - items: List[RuleMetaUpdate] - - -class UserTenantsDTO(BaseResponseModel): - items: List[UserTenant] - - -class UserRoleDTO(BaseResponseModel): - items: List[UserRole] - - -class UserCustomerDTO(BaseResponseModel): - items: List[UserCustomer] - - -class SiemSecurityHubDTO(BaseResponseModel): - items: List[DojoSiem] - - -class SiemDojoDTO(BaseResponseModel): - items: List[SecurityHubSiem] - - -class FindingsDTO(BaseResponseModel): - items: List[Findings] - - -class UserDTO(BaseResponseModel): - items: List[User] - - -class ReportDTO(BaseResponseModel): - items: List[Report] - - -class SiemPushDTO(BaseResponseModel): - items: List[SiemPush] - - -class JobReportDTO(BaseResponseModel): - items: List[JobReport] - - -class TenantLicensePriorityDTO(BaseResponseModel): - items: List[TenantLicensePriority] - - -class ScheduledJobDTO(BaseResponseModel): - items: List[ScheduledJob] - - -class HealthCheckDTO(BaseResponseModel): - items: List[HealthCheck] - - -class BackUpResponseDTO(BaseResponseModel): - items: List[BackUpResponse] - - -class MailSettingDTO(BaseResponseModel): - items: List[MailSetting] - - -class LicenseManagerConfigDTO(BaseResponseModel): - items: List[LicenseManagerConfig] - - -class LicenseManagerClientDTO(BaseResponseModel): - items: List[LicenseManagerClient] - - -class ApplicationDTO(BaseResponseModel): - items: List[Application] - - -class AccessApplicationDTO(BaseResponseModel): - items: List[AccessApplication] - - -class ParentDTO(BaseResponseModel): - items: List[Parent] - - -class RabbitMQDTO(BaseResponseModel): - items: List[RabbitMQ] - - -class BatchResult(BaseModel): - batch_results_id: str - job_id: str - customer_name: str - tenant_name: str - cloud_identifier: str - status: str - registration_start: str - registration_end: str - submitted_at: str - - -class ViolatedRule(BaseModel): - name: str - description: Optional[str] - severity: str - - -class ResourceReport(BaseModel): - account_id: str - input_identifier: str # one user provided - identifier: str # one that was matched by user's - last_scan_date: Optional[str] - violated_rules: List[ViolatedRule] - data: Dict - region: str - resource_type: str - - -class K8sPlatform(BaseModel): - description: str - endpoint: Optional[str] - has_token: bool - id: str - name: str - tenant_name: str - type: PlatformType - - -class ResourceReportJobs(ResourceReport): - job_id: str - submitted_at: str - type: str - - -class BatchResultDTO(BaseResponseModel): - items: List[BatchResult] - - -class GenericReportDTO(BaseResponseModel): - items: List[ - Union[JobReport, TenantReport] - ] - - -class RuleReportDTO(BaseResponseModel): - items: List[ - Union[JobRuleReport, TenantRuleReport] - ] - - -class ResourceReportDTO(BaseResponseModel): - items: List[ResourceReport] - - -class ResourceReportJobsDTO(BaseResponseModel): - items: List[ResourceReportJobs] - - -class PlatformK8SDTO(BaseResponseModel): - items: List[K8sPlatform] - - -ALL_MODELS = set( - obj for name, obj in getmembers(sys.modules[__name__]) - if (name.endswith('DTO') and isinstance(obj, type) and - issubclass(obj, BaseModel)) -) -ALL_MODELS.update((BaseMessageResponseModel, UnauthorizedResponseModel)) diff --git a/src/validators/swagger_request_models.py b/src/validators/swagger_request_models.py new file mode 100644 index 000000000..fa2519b08 --- /dev/null +++ b/src/validators/swagger_request_models.py @@ -0,0 +1,1383 @@ +# classes for swagger models are not instantiated directly in code. +# PreparedEvent models are used instead. + +from base64 import standard_b64decode +from datetime import date, datetime, timedelta, timezone +from itertools import chain +from typing import Literal +from typing_extensions import Annotated, Self + +from modular_sdk.commons.constants import Cloud as ModularCloud +from pydantic import ( + AmqpDsn, + AnyUrl, + BaseModel as PydanticBaseModel, + ConfigDict, + Field, + HttpUrl, + StringConstraints, + field_validator, + model_validator, +) +from pydantic.json_schema import SkipJsonSchema, WithJsonSchema + +from helpers.constants import ( + HealthCheckStatus, + JobState, + JobType, + Permission, + PlatformType, + PolicyErrorType, + ReportFormat, + RuleDomain, +) +from helpers.regions import AllRegions, AllRegionsWithGlobal +from helpers.reports import Standard +from helpers.time_helper import utc_datetime +from services import SERVICE_PROVIDER +from services.rule_meta_service import RuleName +from models.policy import PolicyEffect + + +class BaseModel(PydanticBaseModel): + model_config = ConfigDict( + coerce_numbers_to_str=True, + populate_by_name=True, + ) + customer_id: SkipJsonSchema[str] = Field( + None, + alias='customer', + description='Special parameter. Allows to perform actions on behalf ' + 'on the specified customer. This can be allowed only ' + 'for system users. Parameter will be ignored for ' + 'standard users', + ) + + @property + def customer(self) -> str | None: + """ + Just backward compatibility + :return: + """ + return self.customer_id + + +class BasePaginationModel(BaseModel): + limit: int = Field( + 50, + ge=1, + le=50, + description='Max number of items to return' + ) + next_token: str = Field( + None, + description='Provide next_token received from the previous request' + ) + + +class TimeRangedMixin: + """ + Base model which provides time-range constraint + """ + + # start_iso: datetime | date = Field(None, alias='from') + # end_iso: datetime | date = Field(None, alias='to') + + @classmethod + def skip_validation_if_no_input(cls): + return False + + @classmethod + def max_range(cls) -> timedelta: + return timedelta(days=7) + + @classmethod + def to_datetime(cls, d: datetime | date | None) -> datetime | None: + if not d: + return + if isinstance(d, datetime): + return d + return datetime.combine(d, datetime.min.time()) + + @model_validator(mode='after') + def validate_dates(self) -> Self: + """ + What it does: + - converts start_iso and end_iso to utc tz-aware datetime object + - validates that start_iso < end_iso, end_iso <= now + - sets default values in case they are not provided + :param values: + :return: + """ + now = utc_datetime() + max_range = self.max_range() + start = self.to_datetime(self.start_iso) + end = self.to_datetime(self.end_iso) + if start: + start = start.astimezone(timezone.utc) + if start > now: + raise ValueError('value of \'from\' must be less ' + 'than current date') + if end: + end = end.astimezone(timezone.utc) + if end > now: + raise ValueError('value of \'to\' must be less ' + 'than current date') + if start and end: + pass + elif start: + end = min(start + max_range, now) + elif end: + start = end - max_range + else: # both not provided + if self.skip_validation_if_no_input: + self.start_iso = None + self.end_iso = None + return self + end = now + start = end - max_range + if start >= end: + raise ValueError('value of \'to\' must ' + 'be bigger than \'from\' date') + + if (end - start) > max_range: + raise ValueError( + f'Time range between \'from\' and \'to\' must ' + f'not overflow {max_range}') + self.start_iso = start + self.end_iso = end + return self + + +class CustomerGetModel(BaseModel): + """ + GET + """ + name: str = Field(None) + + @model_validator(mode='after') + def _(self) -> Self: + if name := self.customer: # not system + self.name = name + return self + + +class MultipleTenantsGetModel(BasePaginationModel): + """ + GET + """ + # cloud_identifier: Optional[str] # TODO API separate endpoint for this + active: bool = Field(None) + cloud: ModularCloud = Field(None) + + +class TenantPostModel(BaseModel): + name: str + account_id: str + cloud: Literal['AWS', 'AZURE', 'GOOGLE'] + display_name: str = Field(None) + primary_contacts: list[str] = Field(default_factory=list) + secondary_contacts: list[str] = Field(default_factory=list) + tenant_manager_contacts: list[str] = Field(default_factory=list) + default_owner: str = Field(None) + + @model_validator(mode='after') + def set_display_name(self) -> Self: + if not self.display_name: + self.display_name = self.name + return self + + +class TenantGetActiveLicensesModel(BaseModel): + limit: int = 1 + + +class TenantRegionPostModel(BaseModel): + region: str # means native region name + + @field_validator('region') + @classmethod + def validate_region(cls, value: str) -> str: + """ + Of course, we can use typing "region: AllRegions", but the output is + huge is validation fails + """ + if not AllRegions.has(value): + raise ValueError(f'Not known region: {value}') + return value + + +class RulesetPostModel(BaseModel): + name: str + version: str + cloud: RuleDomain + active: bool = True + + # if empty, all the rules for cloud is chosen + rules: set = Field(default_factory=set) + git_project_id: str = Field(None) + git_ref: str = Field(None) + + service_section: str = Field(None) + severity: str = Field(None) + mitre: set[str] = Field(default_factory=set) + standard: set[str] = Field(default_factory=set) + + @model_validator(mode='after') + def validate_filters(self) -> Self: + if self.git_ref and not self.git_project_id: + raise ValueError('git_project_id must be specified with git_ref') + cloud = self.cloud + col = SERVICE_PROVIDER.mappings_collector + if self.service_section: + ss = col.service_section + if not ss: + raise ValueError('cannot load service section data') + available = set( + value for key, value in ss.items() + if RuleName(key).cloud == cloud + ) + if self.service_section not in available: + raise ValueError('Not available service section. ' + f'Choose from: {", ".join(available)}') + if self.severity: + sv = col.severity + if not sv: + raise ValueError('cannot load severity data') + available = set( + value for key, value in sv.items() + if RuleName(key).cloud == cloud + ) + if self.severity not in available: + raise ValueError('Not available severity. ' + f'Choose from: {", ".join(available)}') + if self.mitre: + mt = col.mitre + if not mt: + raise ValueError('cannot load mitre data') + available = set(chain.from_iterable( + value.keys() for key, value in mt.items() + if RuleName(key).cloud == cloud + )) + not_available = self.mitre - available + if not_available: + raise ValueError( + f'Not available mitre: {", ".join(not_available)}. ' + f'Choose from: {", ".join(available)}') + if self.standard: + st = col.standard + if not st: + raise ValueError('cannot load standards data') + available = set() + it = ( + (v or {}) for k, v in st.items() + if RuleName(k).cloud == cloud + ) + for st in it: + available.update(Standard.deserialize_to_strs(st)) + available.update(st.keys()) + not_available = self.standard - available + if not_available: + raise ValueError( + f'Not available standard: {", ".join(not_available)}. ' + f'Choose from: {", ".join(available)}') + return self + + +class RulesetPatchModel(BaseModel): + name: str + version: str + + rules_to_attach: set = Field(default_factory=set) + rules_to_detach: set = Field(default_factory=set) + active: bool = Field(None) + + @model_validator(mode='after') + def at_least_one_given(self) -> Self: + if not self.rules_to_attach and not self.rules_to_detach and self.active is None: + raise ValueError( + 'At least one attribute to update must be provided') + return self + + +class RulesetDeleteModel(BaseModel): + name: str + version: str + + +class RulesetGetModel(BaseModel): + """ + GET + """ + name: str = Field(None) + version: str = Field(None) + cloud: RuleDomain = Field(None) + get_rules: bool = False + active: bool = Field(None) + licensed: bool = Field(None) + + @model_validator(mode='after') + def validate_codependent_params(self) -> Self: + if self.version and not self.name: + raise ValueError('\'name\' is required if \'version\' is given') + if self.name and self.version and ( + self.cloud or self.active is not None): + raise ValueError( + 'you don\'t have to specify \'cloud\' or \'active\' ' + 'if \'name\' and \'version\' are given') + return self + + +class RulesetContentGetModel(BaseModel): + """ + GET + """ + name: str + version: str + + +class RuleDeleteModel(BaseModel): + rule: str = Field(None) + cloud: RuleDomain = Field(None) + git_project_id: str = Field(None) + git_ref: str = Field(None) + + @model_validator(mode='after') + def validate_root(self) -> Self: + if self.git_ref and not self.git_project_id: + raise ValueError('git_project_id must be specified with git_ref') + return self + + +class RuleGetModel(BasePaginationModel): + """ + GET + """ + rule: str = Field(None) + cloud: RuleDomain = Field(None) + git_project_id: str = Field(None) + git_ref: str = Field(None) + + @model_validator(mode='after') + def validate_root(self) -> Self: + if self.git_ref and not self.git_project_id: + raise ValueError('git_project_id must be specified with git_ref') + return self + + +class RuleUpdateMetaPostModel(BaseModel): + rule_source_id: str = Field(None) + + +class RuleSourcePostModel(BaseModel): + git_project_id: str + git_url: Annotated[ + str, + StringConstraints(pattern=r'^https?:\/\/[^\/]+$') + ] = Field(None) + git_ref: str = 'main' + git_rules_prefix: str = '/' + git_access_type: Annotated[ + Literal['TOKEN'], + WithJsonSchema({'type': 'string', 'title': 'Git access type', + 'default': 'TOKEN'}) + ] = 'TOKEN' + # custom json schema because we don't want "const" key to appear in + # schema because API gw does not support that, + # but we do want validation that only TOKEN is supported now + git_access_secret: str = Field(None) + description: str + + @model_validator(mode='after') + def root(self) -> Self: + self.git_project_id = self.git_project_id.strip('/') + is_github = self.git_project_id.count('/') == 1 + is_gitlab = self.git_project_id.isdigit() + if not self.git_url: + if is_github: + self.git_url = 'https://api.github.com' + elif is_gitlab: + self.git_url = 'https://git.epam.com' + else: + raise ValueError( + 'unknown git_project_id. ' + 'Specify Gitlab project id or Github owner/repo' + ) + if is_gitlab and not self.git_access_secret: + raise ValueError('git_access_secret is required for GitLab') + return self + + +class RuleSourcePatchModel(BaseModel): + id: str + git_access_type: Annotated[ + Literal['TOKEN'], + WithJsonSchema({'type': 'string', 'title': 'Git access type', + 'default': 'TOKEN'}) + ] = 'TOKEN' + git_access_secret: str = Field(None) + description: str = Field(None) + + @model_validator(mode='after') + def validate_any_to_update(self) -> Self: + if not self.git_access_secret and not self.description: + raise ValueError('Provide data to update') + return self + + +class RuleSourceDeleteModel(BaseModel): + id: str + + +class RuleSourceGetModel(BaseModel): + """ + GET + """ + id: str = Field(None) + git_project_id: str = Field(None) + + +class RolePostModel(BaseModel): + name: str + policies: set[str] + expiration: datetime = Field( + default_factory=lambda: utc_datetime() + timedelta(days=365) + ) + description: str + + +class RolePatchModel(BaseModel): + policies_to_attach: set[str] = Field(default_factory=set) + policies_to_detach: set[str] = Field(default_factory=set) + expiration: datetime = Field(None) + description: str = Field(None) + + @model_validator(mode='after') + def to_attach_or_to_detach(self) -> Self: + if not self.policies_to_detach and not self.policies_to_attach and not self.expiration: + raise ValueError('Provide some parameter to update') + return self + + +class PolicyPostModel(BaseModel): + name: str + permissions: set[Permission] = Field(default_factory=set) + permissions_admin: bool = False + effect: PolicyEffect + tenants: set[str] = Field(default_factory=set) + description: str + # todo add effect and tenants + + @field_validator('permissions', mode='after') + @classmethod + def validate_hidden(cls, permission: set[Permission]) -> set[Permission]: + if not_allowed := permission & set(Permission.iter_disabled()): + raise ValueError(f'Permissions: {", ".join(not_allowed)} are ' + f'currently not allowed') + return permission + + @model_validator(mode='after') + def _(self) -> Self: + if not self.permissions_admin and not self.permissions: + raise ValueError('Provide either permissions or permissions_admin') + if self.permissions_admin: + self.permissions = set(Permission.iter_enabled()) + return self + + +class PolicyPatchModel(BaseModel): + permissions_to_attach: set[Permission] = Field(default_factory=set) + permissions_to_detach: set[Permission] = Field(default_factory=set) + effect: PolicyEffect = Field(None) + tenants_to_add: set[str] = Field(default_factory=set) + tenants_to_remove: set[str] = Field(default_factory=set) + description: str = Field(None) + + @field_validator('permissions_to_attach', mode='after') + @classmethod + def validate_hidden(cls, permission: set[Permission]) -> set[Permission]: + if not_allowed := permission & set(Permission.iter_disabled()): + raise ValueError(f'Permissions: {", ".join(not_allowed)} are ' + f'currently not allowed') + return permission + + @model_validator(mode='after') + def _(self) -> Self: + if not any((self.permissions_to_attach, self.permissions_to_detach, + self.effect, self.tenants_to_add, self.tenants_to_remove, + self.description)): + raise ValueError('Provide some attribute to update') + return self + + +class JobGetModel(TimeRangedMixin, BasePaginationModel): + start_iso: datetime | date = Field(None, alias='from') + end_iso: datetime | date = Field(None, alias='to') + tenant_name: str = Field(None) + status: JobState = Field(None) + + @classmethod + def max_range(cls) -> timedelta: + return timedelta(days=365) + + +class AWSCredentials(PydanticBaseModel): + AWS_ACCESS_KEY_ID: str + AWS_SECRET_ACCESS_KEY: str + AWS_SESSION_TOKEN: str = Field(None) + AWS_DEFAULT_REGION: str = 'us-east-1' + + +# TODO, add certificates & username-password creds +# https://learn.microsoft.com/en-us/dotnet/api/azure.identity.environmentcredential?view=azure-dotnet +class AZURECredentials(PydanticBaseModel): + AZURE_TENANT_ID: str + AZURE_CLIENT_ID: str + AZURE_CLIENT_SECRET: str + AZURE_SUBSCRIPTION_ID: str = Field(None) + + +class GOOGLECredentials1(PydanticBaseModel): + type: str + project_id: str + private_key_id: str + private_key: str + client_email: str + client_id: str + auth_uri: str + token_uri: str + auth_provider_x509_cert_url: str + client_x509_cert_url: str + + +class GOOGLECredentials2(PydanticBaseModel): + type: str + access_token: str + refresh_token: str + client_id: str + client_secret: str + project_id: str + + +class GOOGLECredentials3(PydanticBaseModel): + access_token: str + project_id: str + + +class JobPostModel(BaseModel): + credentials: AWSCredentials | AZURECredentials | GOOGLECredentials1 | GOOGLECredentials2 | GOOGLECredentials3 = Field( + None) + tenant_name: str + target_rulesets: set[str] = Field(default_factory=set) + target_regions: set[str] = Field(default_factory=set) + rules_to_scan: set[str] = Field(default_factory=set) + # todo allow to provide desired license key + + +class StandardJobPostModel(BaseModel): + """ + standard jobs means not licensed job -> without licensed rule-sets + """ + credentials: AWSCredentials | AZURECredentials | GOOGLECredentials1 | GOOGLECredentials2 | GOOGLECredentials3 = Field( + None) + tenant_name: str + target_rulesets: set[str] = Field(default_factory=set) + target_regions: set[str] = Field(default_factory=set) + + +class ScheduledJobPostModel(BaseModel): + schedule: str + tenant_name: str = Field(None) + name: str = Field(None) + target_rulesets: set[str] = Field(default_factory=set) + target_regions: set[str] = Field(default_factory=set) + + +class ScheduledJobGetModel(BaseModel): + """ + GET + """ + tenant_name: str = Field(None) + + +class ScheduledJobPatchModel(BaseModel): + schedule: str = Field(None) + enabled: bool = Field(None) + + @model_validator(mode='after') + def at_least_one_given(self) -> Self: + if not self.schedule and self.enabled is None: + raise ValueError('Provide attributes to update') + return self + + +class EventPostModel(BaseModel): + version: Annotated[ + Literal['1.0.0'], + WithJsonSchema({'type': 'string', 'title': 'Event type version', + 'default': '1.0.0'}) + ] = '1.0.0' + vendor: Literal['AWS', 'MAESTRO'] + events: list[dict] + + +class UserPasswordResetPostModel(BaseModel): + password: str + username: str = Field(None) + + # validators + @field_validator('password') + @classmethod + def _(cls, password: str) -> str: + errors = [] + upper = any(char.isupper() for char in password) + numeric = any(char.isdigit() for char in password) + symbol = any(not char.isalnum() for char in password) + if not upper: + errors.append('must have uppercase characters') + if not numeric: + errors.append('must have numeric characters') + if not symbol: + errors.append('must have symbol characters') + if len(password) < 8: + errors.append('valid min length for password: 8') + + if errors: + raise ValueError(', '.join(errors)) + + return password + + +class SignInPostModel(PydanticBaseModel): + username: str + password: str + + +class RefreshPostModel(PydanticBaseModel): + refresh_token: str + + +class MailSettingGetModel(BaseModel): + """ + GET + """ + disclose: bool = False + + +class MailSettingPostModel(BaseModel): + username: str + password: str + password_alias: str + port: int + host: str + max_emails: int = 1 + default_sender: str + use_tls: bool = False + + +class ReportsSendingSettingPostModel(BaseModel): + enable: bool = True + + +class LicenseManagerConfigSettingPostModel(BaseModel): + host: str + port: int = Field(None) + protocol: Annotated[ + Literal['HTTP', 'HTTPS', 'http', 'https'], + StringConstraints(to_upper=True) + ] = Field(None) + stage: str = Field(None) + api_version: str = Field(None) + + + +class LicenseManagerClientSettingPostModel(BaseModel): + key_id: str + algorithm: Annotated[ + Literal['ECC:p521_DSS_SHA:256'], + WithJsonSchema({'type': 'string', 'title': 'LM algorithm', + 'default': 'ECC:p521_DSS_SHA:256'}) + ] = 'ECC:p521_DSS_SHA:256' + private_key: str + b64_encoded: bool + + @model_validator(mode='after') + def check_properly_encoded_key(self) -> Self: + if not self.b64_encoded: + return self + try: + self.private_key = standard_b64decode(self.private_key).decode() + except (TypeError, BaseException): + raise ValueError( + '\'private_key\' must be a safe to decode' + ' base64-string.' + ) + return self + + +class LicenseManagerClientSettingDeleteModel(BaseModel): + key_id: str + + +class BatchResultsQueryModel(BasePaginationModel): + tenant_name: str = Field(None) + + start: datetime = Field(None) + end: datetime = Field(None) + + + +# reports +class JobFindingsReportGetModel(BaseModel): + job_type: JobType = JobType.MANUAL + href: bool = False + obfuscated: bool = False + + +class TenantJobsFindingsReportGetModel(TimeRangedMixin, BaseModel): + start_iso: datetime | date = Field(None, alias='from') + end_iso: datetime | date = Field(None, alias='to') + job_type: JobType = Field(None) + href: bool = False + obfuscated: bool = False + + +class JobDetailsReportGetModel(BaseModel): + job_type: JobType = JobType.MANUAL + href: bool = False + obfuscated: bool = False + + +class TenantJobsDetailsReportGetModel(TimeRangedMixin, BaseModel): + start_iso: datetime | date = Field(None, alias='from') + end_iso: datetime | date = Field(None, alias='to') + job_type: JobType = Field(None) + href: bool = False + obfuscated: bool = False + + +class JobDigestReportGetModel(BaseModel): + job_type: JobType = JobType.MANUAL + + +class TenantJobsDigestsReportGetModel(TimeRangedMixin, BaseModel): + start_iso: datetime | date = Field(None, alias='from') + end_iso: datetime | date = Field(None, alias='to') + job_type: JobType = Field(None) + + +class JobComplianceReportGetModel(BaseModel): + job_type: JobType = JobType.MANUAL + format: ReportFormat = ReportFormat.JSON + href: bool = False + + +class TenantComplianceReportGetModel(BaseModel): + format: ReportFormat = ReportFormat.JSON + href: bool = False + + +class JobErrorReportGetModel(BaseModel): + job_type: JobType = JobType.MANUAL + href: bool = False + format: ReportFormat = ReportFormat.JSON + error_type: PolicyErrorType = Field(None) + + +class JobRuleReportGetModel(BaseModel): + job_type: JobType = JobType.MANUAL + href: bool = False + format: ReportFormat = ReportFormat.JSON + + +class TenantRuleReportGetModel(TimeRangedMixin, BaseModel): + start_iso: datetime | date = Field(None, alias='from') + end_iso: datetime | date = Field(None, alias='to') + job_type: JobType = Field(None) + + +class ReportPushByJobIdModel(BaseModel): + """ + /reports/push/dojo/{job_id}/ + /reports/push/security-hub/{job_id}/ + """ + type: JobType = JobType.MANUAL + + +class ReportPushMultipleModel(TimeRangedMixin, BaseModel): + """ + /reports/push/dojo + /reports/push/security-hub + """ + tenant_name: str + start_iso: datetime | date = Field(None, alias='from') + end_iso: datetime | date = Field(None, alias='to') + type: JobType = Field(None) + + +class EventDrivenRulesetGetModel(BaseModel): + cloud: RuleDomain = Field(None) + get_rules: bool = False + + +class EventDrivenRulesetPostModel(BaseModel): + # name: str + cloud: RuleDomain + version: float + rules: list = Field(default_factory=list) + # rule_version: Optional[float] + + +class EventDrivenRulesetDeleteModel(BaseModel): + # name: str + cloud: RuleDomain + version: float + + +class ProjectGetReportModel(BaseModel): + tenant_display_names: set[str] + types: set[Literal['OVERVIEW', 'RESOURCES', 'COMPLIANCE', 'ATTACK_VECTOR', 'FINOPS']] = Field(default_factory=set) + receivers: set[str] = Field(default_factory=set) + attempt: SkipJsonSchema[int] = 0 + execution_job_id: SkipJsonSchema[str] = Field(None) + + +class OperationalGetReportModel(BaseModel): + tenant_names: set[str] + types: set[Literal['OVERVIEW', 'RESOURCES', 'COMPLIANCE', 'RULE', 'ATTACK_VECTOR', 'FINOPS', 'KUBERNETES']] = Field(default_factory=set) + receivers: set[str] = Field(default_factory=set) + attempt: SkipJsonSchema[int] = 0 + execution_job_id: SkipJsonSchema[str] = Field(None) + + +class DepartmentGetReportModel(BaseModel): + types: set[Literal['TOP_RESOURCES_BY_CLOUD', 'TOP_TENANTS_RESOURCES', 'TOP_TENANTS_COMPLIANCE', 'TOP_COMPLIANCE_BY_CLOUD', 'TOP_TENANTS_ATTACKS', 'TOP_ATTACK_BY_CLOUD']] = Field(default_factory=set) + attempt: SkipJsonSchema[int] = 0 + execution_job_id: SkipJsonSchema[str] = Field(None) + + +class CLevelGetReportModel(BaseModel): + types: set[Literal['OVERVIEW', 'COMPLIANCE', 'ATTACK_VECTOR']] = Field(default_factory=set) + attempt: SkipJsonSchema[int] = 0 + execution_job_id: SkipJsonSchema[str] = Field(None) + + +class HealthCheckQueryModel(BaseModel): + status: HealthCheckStatus = Field(None) + + +class RabbitMQPostModel(BaseModel): + maestro_user: str + rabbit_exchange: str = Field(None) + request_queue: str + response_queue: str + sdk_access_key: str + connection_url: AmqpDsn + sdk_secret_key: str + + +class RabbitMQGetModel(BaseModel): + pass + + +class RabbitMQDeleteModel(BaseModel): + pass + + +class RawReportGetModel(BaseModel): + obfuscated: bool = False + meta: bool = False + + +class ResourcesReportGetModel(BaseModel): + model_config = ConfigDict(extra='allow') + + resource_type: Annotated[ + str, StringConstraints(to_lower=True, strip_whitespace=True)] = Field( + None) + region: AllRegionsWithGlobal = Field(None) + + full: bool = False + obfuscated: bool = False + exact_match: bool = True + search_by_all: bool = False + format: ReportFormat = ReportFormat.JSON + href: bool = False + + @field_validator('region', mode='before') + def _(cls, v: str) -> str: + if v and isinstance(v, str): + v = v.lower() + return v + + @property + def extras(self) -> dict: + """ + These attributes will be used to look for resources + """ + return self.__pydantic_extra__ + + @model_validator(mode='after') + def root(self) -> Self: + if self.search_by_all and not self.__pydantic_extra__: + raise ValueError('If search_by_all, an least one query to search ' + 'by must be provided') + if self.obfuscated and self.format == ReportFormat.JSON and not self.href: + raise ValueError('Obfuscation is currently not supported for ' + 'raw json report') + return self + + # all the other attributes to search by can be provided as well. + # They are not declared + + +class PlatformK8sResourcesReportGetModel(BaseModel): + model_config = ConfigDict(extra='allow') + + resource_type: Annotated[ + str, StringConstraints(to_lower=True, strip_whitespace=True)] = Field( + None) + full: bool = False + obfuscated: bool = False + exact_match: bool = True + search_by_all: bool = False + format: ReportFormat = ReportFormat.JSON + href: bool = False + + @property + def extras(self) -> dict: + """ + These attributes will be used to look for resources + """ + return self.__pydantic_extra__ + + @model_validator(mode='after') + def root(self) -> Self: + if self.search_by_all and not self.__pydantic_extra__: + raise ValueError('If search_by_all, an least one query to search ' + 'by must be provided') + if self.obfuscated and self.format == ReportFormat.JSON and not self.href: + raise ValueError('Obfuscation is currently not supported for ' + 'raw json report') + return self + + # all the other attributes to search by can be provided as well. + # They are not declared + + +class ResourceReportJobsGetModel(TimeRangedMixin, BaseModel): + model_config = ConfigDict(extra='allow') + + start_iso: datetime | date = Field(None, alias='from') + end_iso: datetime | date = Field(None, alias='to') + job_type: JobType = JobType.MANUAL + + resource_type: Annotated[ + str, StringConstraints(to_lower=True, strip_whitespace=True)] = None + region: AllRegionsWithGlobal = Field(None) + full: bool = False + exact_match: bool = True + search_by_all: bool = False + + @field_validator('region', mode='before') + def _(cls, v: str) -> str: + if v and isinstance(v, str): + v = v.lower() + return v + + @property + def extras(self) -> dict: + """ + These attributes will be used to look for resources + """ + return self.__pydantic_extra__ + + @model_validator(mode='after') + def root(self) -> Self: + if self.search_by_all and not self.__pydantic_extra__: + raise ValueError('If search_by_all, an least one query to search ' + 'by must be provided') + return self + + +class ResourceReportJobGetModel(BaseModel): + model_config = ConfigDict(extra='allow') + + job_type: JobType = JobType.MANUAL + + resource_type: Annotated[ + str, StringConstraints(to_lower=True, strip_whitespace=True)] = Field( + None) + region: AllRegionsWithGlobal = Field(None) + full: bool = False + obfuscated: bool = False + exact_match: bool = True + search_by_all: bool = False + href: bool = False + + @field_validator('region', mode='before') + def _(cls, v: str) -> str: + if v and isinstance(v, str): + v = v.lower() + return v + + @property + def extras(self) -> dict: + """ + These attributes will be used to look for resources + """ + return self.__pydantic_extra__ + + @model_validator(mode='after') + def root(self) -> Self: + if self.search_by_all and not self.__pydantic_extra__: + raise ValueError('If search_by_all, an least one query to search ' + 'by must be provided') + if self.obfuscated and not self.href: + raise ValueError('Currently obfuscation is supported only if href ' + 'is true') + return self + + +class PlatformK8SPostModel(BaseModel): + tenant_name: Annotated[ + str, StringConstraints(to_upper=True, strip_whitespace=True)] + name: str + region: AllRegions = Field(None) + type: PlatformType + description: str + + endpoint: HttpUrl = Field(None) + certificate_authority: str = Field(None) # base64 encoded + token: str = Field(None) + + @model_validator(mode='after') + def root(self) -> Self: + if (self.type != PlatformType.SELF_MANAGED + and not self.region): + raise ValueError('region is required if platform is cloud managed') + if self.type != PlatformType.EKS and not self.endpoint: + raise ValueError('endpoint must be ' + 'specified if type is not EKS') + return self + + +class PlatformK8sQueryModel(BaseModel): + tenant_name: str = Field(None) + + +class K8sJobPostModel(BaseModel): + """ + K8s platform job + """ + platform_id: str + target_rulesets: set[str] = Field(default_factory=set) + token: str = Field(None) # temp jwt token + + +class ReportStatusGetModel(BaseModel): + """ + GET + """ + job_id: str + complete: bool = False + + +class MetricsStatusGetModel(TimeRangedMixin, BaseModel): + start_iso: datetime | date = Field(None, alias='from') + end_iso: datetime | date = Field(None, alias='to') + + @classmethod + def max_range(cls) -> timedelta: + return timedelta(days=365) + + @classmethod + def skip_validation_if_no_input(cls): + return True + + +class LicensePostModel(BaseModel): + tenant_license_key: str = Field(alias='license_key') + description: str = 'Custodian license' + + +class LicenseActivationPutModel(BaseModel): + tenant_names: set[str] = Field(default_factory=set) + + all_tenants: bool = False + clouds: set[Literal['AWS', 'AZURE', 'GOOGLE', 'KUBERNETES']] = Field(default_factory=set) + exclude_tenants: set[str] = Field(default_factory=set) + + @model_validator(mode='after') + def _(self) -> Self: + if self.tenant_names and any((self.all_tenants, self.clouds, + self.exclude_tenants)): + raise ValueError('do not provide all_tenants, clouds or ' + 'exclude_tenants if specific ' + 'tenant names are provided') + if not self.all_tenants and not self.tenant_names: + raise ValueError('either all_tenants or specific tenant names ' + 'must be given') + if (self.clouds or self.exclude_tenants) and not self.all_tenants: + raise ValueError('set all tenants to true if you provide clouds ' + 'or excluded') + return self + + +class LicenseActivationPatchModel(BaseModel): + add_tenants: set[str] = Field(default_factory=set) + remove_tenants: set[str] = Field(default_factory=set) + + @model_validator(mode='after') + def _(self) -> Self: + if not self.add_tenants and not self.remove_tenants: + raise ValueError('provide either add_tenants or remove_tenants') + return self + + +class DefectDojoPostModel(BaseModel): + url: AnyUrl # = Field(examples=['http://127.0.0.1:8080/api/v2']) # api gw models does not support examples + api_key: str + + description: str + + +class DefectDojoQueryModel(BaseModel): + pass + + +class DefectDojoActivationPutModel(BaseModel): + tenant_names: set[str] = Field(default_factory=set) + + all_tenants: bool = False + clouds: set[Literal['AWS', 'AZURE', 'GOOGLE']] = Field(default_factory=set) + exclude_tenants: set[str] = Field(default_factory=set) + + scan_type: Literal['Generic Findings Import', 'Cloud Custodian Scan'] = Field('Generic Findings Import', description='Defect dojo scan type') + product_type: str = Field('Rule Engine', + description='Defect dojo product type name') + product: str = Field('{tenant_name}', + description='Defect dojo product name') + engagement: str = Field('Rule-Engine Main', + description='Defect dojo engagement name') + test: str = Field('{job_id}', description='Defect dojo test') + send_after_job: bool = Field( + False, + description='Whether to send the results to dojo after each scan' + ) + attachment: Literal['json', 'xlsx', 'csv'] = Field(None) + + @model_validator(mode='after') + def _(self) -> Self: + if self.tenant_names and any((self.all_tenants, self.clouds, + self.exclude_tenants)): + raise ValueError('do not provide all_tenants, clouds or ' + 'exclude_tenants if specific ' + 'tenant names are provided') + if not self.all_tenants and not self.tenant_names: + raise ValueError('either all_tenants or specific tenant names ' + 'must be given') + if (self.clouds or self.exclude_tenants) and not self.all_tenants: + raise ValueError('set all tenants to true if you provide clouds ' + 'or excluded') + return self + + +class SelfIntegrationPutModel(BaseModel): + description: str = 'Custodian access application' + username: str + password: str + auto_resolve_access: bool = False + url: AnyUrl = Field(None) # full link: https://host.com:port/hello + results_storage: str = Field(None) + + tenant_names: set[str] = Field(default_factory=set) + + all_tenants: bool = False + clouds: set[Literal['AWS', 'AZURE', 'GOOGLE']] = Field(default_factory=set) + exclude_tenants: set[str] = Field(default_factory=set) + + @model_validator(mode='after') + def _(self) -> Self: + if self.tenant_names and any((self.all_tenants, self.clouds, + self.exclude_tenants)): + raise ValueError('do not provide all_tenants, clouds or ' + 'exclude_tenants if specific ' + 'tenant names are provided') + if not self.all_tenants and not self.tenant_names: + raise ValueError('either all_tenants or specific tenant names ' + 'must be given') + if (self.clouds or self.exclude_tenants) and not self.all_tenants: + raise ValueError('set all tenants to true if you provide clouds ' + 'or excluded') + if not self.auto_resolve_access and not self.url: + raise ValueError('url must be given in case ' + 'auto_resolve_access is not True') + return self + + +class SelfIntegrationPatchModel(BaseModel): + add_tenants: set[str] = Field(default_factory=set) + remove_tenants: set[str] = Field(default_factory=set) + + @model_validator(mode='after') + def _(self) -> Self: + if not self.add_tenants and not self.remove_tenants: + raise ValueError('provide either add_tenants or remove_tenants') + return self + + +class TenantExcludedRulesPutModel(BaseModel): + rules: set[str] + + +class CustomerExcludedRulesPutModel(BaseModel): + rules: set[str] + + +class CredentialsQueryModel(BasePaginationModel): + cloud: Literal['AWS', 'AZURE', 'GOOGLE'] + + +class CredentialsBindModel(BaseModel): + tenant_names: set[str] = Field(default_factory=set) + + all_tenants: bool = False + exclude_tenants: set[str] = Field(default_factory=set) + + @model_validator(mode='after') + def _(self) -> Self: + if self.tenant_names and any((self.all_tenants, self.exclude_tenants)): + raise ValueError('do not provide all_tenants or ' + 'exclude_tenants if specific ' + 'tenant names are provided') + if not self.all_tenants and not self.tenant_names: + raise ValueError('either all_tenants or specific tenant names ' + 'must be given') + if self.exclude_tenants and not self.all_tenants: + raise ValueError('set all tenants to true if you provide ' + 'excluded') + return self + + +class UserPatchModel(BaseModel): + """ + System admin endpoint + """ + role_name: str = Field(None) + password: str = Field(None) + + @model_validator(mode='after') + def at_least_one(self) -> Self: + if not any((self.role_name, self.password)): + raise ValueError('provide at least one attribute to update') + return self + + @field_validator('password') + @classmethod + def _(cls, password: str) -> str: + errors = [] + upper = any(char.isupper() for char in password) + lower = any(char.islower() for char in password) + numeric = any(char.isdigit() for char in password) + if not upper: + errors.append('must have uppercase characters') + if not numeric: + errors.append('must have numeric characters') + if not lower: + errors.append('must have lowercase characters') + if len(password) < 8: + errors.append('valid min length for password: 8') + + if errors: + raise ValueError(', '.join(errors)) + + return password + + +class UserPostModel(BaseModel): + username: str + role_name: str = Field(None) + password: str + + @field_validator('username', mode='after') + @classmethod + def check_reserved(cls, username: str) -> str: + if username in ('whoami', 'reset-password'): + raise ValueError('Such username cannot be used.') + return username + + @field_validator('password') + @classmethod + def _(cls, password: str) -> str: + errors = [] + upper = any(char.isupper() for char in password) + lower = any(char.islower() for char in password) + numeric = any(char.isdigit() for char in password) + if not upper: + errors.append('must have uppercase characters') + if not numeric: + errors.append('must have numeric characters') + if not lower: + errors.append('must have lowercase characters') + if len(password) < 8: + errors.append('valid min length for password: 8') + + if errors: + raise ValueError(', '.join(errors)) + + return password + + +class UserResetPasswordModel(BaseModel): + new_password: str + + @field_validator('new_password') + @classmethod + def _(cls, password: str) -> str: + errors = [] + upper = any(char.isupper() for char in password) + lower = any(char.islower() for char in password) + numeric = any(char.isdigit() for char in password) + if not upper: + errors.append('must have uppercase characters') + if not numeric: + errors.append('must have numeric characters') + if not lower: + errors.append('must have lowercase characters') + if len(password) < 8: + errors.append('valid min length for password: 8') + + if errors: + raise ValueError(', '.join(errors)) + + return password + + +class SignUpModel(PydanticBaseModel): + username: str + password: str + customer_name: str + customer_display_name: str + customer_admins: set[str] = Field(default_factory=set) + + @field_validator('password') + @classmethod + def _(cls, password: str) -> str: + errors = [] + upper = any(char.isupper() for char in password) + lower = any(char.islower() for char in password) + numeric = any(char.isdigit() for char in password) + if not upper: + errors.append('must have uppercase characters') + if not numeric: + errors.append('must have numeric characters') + if not lower: + errors.append('must have lowercase characters') + if len(password) < 8: + errors.append('valid min length for password: 8') + + if errors: + raise ValueError(', '.join(errors)) + + return password diff --git a/src/validators/swagger_response_models.py b/src/validators/swagger_response_models.py new file mode 100644 index 000000000..a6893fffb --- /dev/null +++ b/src/validators/swagger_response_models.py @@ -0,0 +1,681 @@ +from datetime import datetime +from typing import Literal + +from pydantic import BaseModel +from typing_extensions import NotRequired, TypedDict +from models.policy import PolicyEffect + +from helpers.constants import ( + HealthCheckStatus, + JobState, + JobType, + PlatformType, + PolicyErrorType, + ReportFormat, + RuleSourceType, + RuleDomain +) + + +class BaseActivation(TypedDict): + activated_for_all: bool + within_clouds: NotRequired[list[str]] + excluding: list[str] + activated_for: NotRequired[list[str]] + + +class Customer(TypedDict): + name: str + display_name: str + admins: list[str] + + +class Tenant(TypedDict): + account_id: str + activation_date: datetime + customer_name: str + is_active: bool + name: str + regions: list[str] + + +class Rule(TypedDict): + description: str + name: str + cloud: RuleDomain + branch: str + customer: str + project: str + resource: str + + +class Job(TypedDict): + created_at: NotRequired[datetime] + customer_name: str + id: str + regions: list[str] + rulesets: list[str] + started_at: NotRequired[datetime] + status: JobState + stopped_at: NotRequired[datetime] + submitted_at: str + tenant_name: str + scheduled_rule_name: NotRequired[str] + + +class Policy(TypedDict): + customer: str + name: str + permissions: list[str] + effect: PolicyEffect + description: str | None + tenants: list[str] + + +class Role(TypedDict): + customer: str + name: str + expiration: datetime + policies: list[str] + description: str | None + + +class Ruleset(TypedDict): + active: bool + cloud: RuleDomain + customer: str + last_update_time: datetime + license_keys: list[str] + license_manager_id: NotRequired[str] + licensed: bool + name: str + rules_number: int + rules: NotRequired[list[str]] + version: str + + +class RuleSourceLatestSync(TypedDict): + current_status: Literal['SYNCED', 'SYNCING', 'SYNCING_FAILED'] + sync_date: datetime + + +class RuleSource(TypedDict): + id: str + customer: str + git_project_id: str + git_url: str + git_ref: str + git_rules_prefix: str + description: str + has_secret: bool + latest_sync: NotRequired[RuleSourceLatestSync] + type: RuleSourceType + + +class RuleMetaUpdate(TypedDict): + customer: str + git_project_id: str + rule_source_id: str + status: str + + +class ScheduledJob(TypedDict): + name: str + customer_name: str + tenant_name: str + creation_date: datetime + enabled: bool + last_execution_time: NotRequired[datetime] + schedule: str + scan_regions: list[str] + scan_rulesets: list[str] + + +class HealthCheck(TypedDict): + id: str + status: HealthCheckStatus + details: dict + remediation: NotRequired[str] + impact: NotRequired[str] + + +class LicenseManagerConfig(TypedDict): + host: str + port: int + protocol: Literal['HTTP', 'HTTPS'] + stage: str + + +class LicenseManagerClient(TypedDict): + algorithm: str + b64_encoded: bool + format: str + key_id: str + public_key: str + + +class RabbitMQ(TypedDict): + maestro_user: str + rabbit_exchange: NotRequired[str] + request_queue: str + response_queue: str + sdk_access_key: str + customer: str + + +class ReportStatus(TypedDict): + id: str + triggered_at: str + attempt: int + customer_name: str + level: str + status: str + reason: NotRequired[str] + tenant: NotRequired[str] + type: str + user: NotRequired[str] + + +class K8sPlatform(TypedDict): + customer: str + description: str + id: str + name: str + region: NotRequired[str] + tenant_name: str + type: PlatformType + + +class BatchResult(TypedDict): + cloud_identifier: str + customer_name: str + id: str + status: JobState + stopped_at: str + submitted_at: str + tenant_name: str + + +class Event(TypedDict): + """ + 202 POST /event + """ + received: int + saved: int + + +class MetricsStatus(TypedDict): + started_at: datetime + state: str + + +class LicenseAllowance(TypedDict): + balance_exhaustion_model: Literal['collective', 'independent'] + job_balance: int + time_range: Literal['DAY', 'WEEK', 'MONTH'] + + +class LicenseEventDriven(TypedDict): + active: bool + + +class License(TypedDict): + allowance: LicenseAllowance + event_driven: LicenseEventDriven + expiration: datetime + latest_sync: datetime + license_key: str + ruleset_ids: list[str] + + +class MailSetting(TypedDict): + username: str + password: str + default_sender: str + host: str + port: str + max_emails: int + use_tls: bool + + +class Credentials(TypedDict): + id: str + type: Literal[ + 'AWS_CREDENTIALS', + 'AWS_ROLE', + 'AZURE_CREDENTIALS', + 'AZURE_CERTIFICATE', + 'GCP_SERVICE_ACCOUNT', + 'GCP_COMPUTE_ACCOUNT' + ] + description: str + has_secret: bool + credentials: dict + + +# reports +class BaseReportJob(TypedDict): + format: ReportFormat + url: NotRequired[str] + dictionary_url: NotRequired[str] + obfuscated: bool + content: NotRequired[dict] + + job_id: NotRequired[str] + job_type: NotRequired[JobType] + tenant_name: str + customer_name: str + + +class BaseReportEntity(TypedDict): + format: ReportFormat + url: NotRequired[str] + dictionary_url: NotRequired[str] + obfuscated: bool + content: NotRequired[dict] + + platform_id: NotRequired[str] + tenant_name: NotRequired[str] + customer_name: str + + +class ErrorReportItem(TypedDict): + error_type: PolicyErrorType + policy: str + reason: str + region: str + + +class RulesReportItem(TypedDict): + api_calls: dict + execution_time: float + failed_resources: int + policy: str + region: str + scanned_resources: int + succeeded: bool + + +class AverageRulesReportItem(TypedDict): + average_exec: float + average_resources_failed: int + average_resources_scanned: int + failed_invocations: int + invocations: int + max_exec: float + min_exec: float + policy: str + region: str + resources_failed: int + resources_scanned: int + succeeded_invocations: int + total_api_calls: dict + total_exec: float + + +class ViolatedRule(TypedDict): + name: str + description: str + severity: str + + remediation: NotRequired[str] + article: NotRequired[str] + impact: NotRequired[str] + + +class ResourcesReportItem(TypedDict): + account_id: NotRequired[str] + platform_id: NotRequired[str] + + job_id: NotRequired[str] + type: NotRequired[JobType] + + data: dict + last_found: float + matched_by: dict + region: str + resource_type: str + violated_rules: ViolatedRule + + +class DefectDojo(TypedDict): + id: str + description: str + host: str + port: int + stage: str + protocol: Literal['HTTP', 'HTTPS'] + + +class DefectDojoActivation(BaseActivation): + scan_type: Literal['Generic Findings Import', 'Cloud Custodian Scan'] + product_type: str + product: str + engagement: str + test: str + send_after_job: bool + attachment: Literal['json', 'xlsx', 'csv'] | None + + +class DojoPushResult(TypedDict): + job_id: str + scan_type: Literal['Generic Findings Import', 'Cloud Custodian Scan'] + product_type_name: str + product_name: str + engagement_name: str + test_title: str + tenant_name: str + dojo_integration_id: str + success: bool + attachment: Literal['json', 'xlsx', 'csv'] | None + platform_id: NotRequired[str] + error: NotRequired[str] + + +class SelfIntegration(BaseActivation): + customer_name: str + description: str + username: str + host: str + stage: str + port: int + protocol: Literal['HTTP', 'HTTPS'] + results_storage: NotRequired[str] + + +class TenantExcludedRules(TypedDict): + tenant_name: str + rules: list[str] + + +class CustomerExcludedRules(TypedDict): + customer_name: str + rules: list[str] + + +class User(TypedDict): + username: str + customer: str | None + role: str | None + latest_login: datetime | None + created_at: datetime | None + + +class RawReportItem(TypedDict): + customer_name: str + tenant_name: str + obfuscated: bool + url: str + dictionary_url: NotRequired[str] + meta_url: NotRequired[str] + + +# Here real response models + + +class RawReportModel(BaseModel): + data: RawReportItem + + +class MessageModel(BaseModel): + message: str + + +class ErrorData(TypedDict): + location: list[str] + message: str + + +class ErrorsModel(BaseModel): + """ + 400 Validation error + """ + errors: list[ErrorData] + + +class MultipleJobsModel(BaseModel): + """ + 200 GET /jobs + """ + items: list[Job] + next_token: str | None + + +class SingleJobModel(BaseModel): + """ + 201 POST /jobs + 201 POST /jobs/k8s + 201 POST /jobs/standard + 200 GET /jobs/{job_id} + """ + data: Job + + +class MultipleScheduledJobsModel(BaseModel): + """ + 200 GET /jobs + """ + items: list[ScheduledJob] + + +class SingleScheduledJobModel(BaseModel): + """ + 201 POST /jobs + 201 POST /jobs/k8s + 201 POST /jobs/standard + 200 GET /jobs/{job_id} + """ + data: ScheduledJob + + +class MultipleK8SPlatformsModel(BaseModel): + items: list[K8sPlatform] + + +class SingleK8SPlatformModel(BaseModel): + data: K8sPlatform + + +class MultipleBatchResultsModel(BaseModel): + items: list[BatchResult] + + +class SingleBatchResultModel(BaseModel): + data: BatchResult + + +class SignInModel(BaseModel): + access_token: str # actually it's Congito's id_token + refresh_token: str + expires_in: int + + +class MultipleCustomersModel(BaseModel): + items: list[Customer] + + +class MultipleTenantsModel(BaseModel): + items: list[Tenant] + + +class SingleTenantsModel(BaseModel): + data: Tenant + + +class EventModel(BaseModel): + data: Event + + +class MultipleHealthChecksModel(BaseModel): + items: list[HealthCheck] + + +class SingleHealthCheckModel(BaseModel): + data: HealthCheck + + +class SingleRabbitMQModel(BaseModel): + data: RabbitMQ + + +class MultipleRulesModel(BaseModel): + items: list[Rule] + next_token: str | None + + +class MultipleRuleMetaUpdateModel(BaseModel): + items: list[RuleMetaUpdate] + + +class MultipleMetricsStatusesModel(BaseModel): + items: list[MetricsStatus] + + +class SingleRulesetModel(BaseModel): + data: Ruleset + + +class MultipleRulesetsModel(BaseModel): + items: list[Ruleset] + + +class SingleRuleSourceModel(BaseModel): + data: RuleSource + + +class MultipleRuleSourceModel(BaseModel): + items: list[RuleSource] + + +class SinglePolicyModel(BaseModel): + data: Policy + + +class MultiplePoliciesModel(BaseModel): + items: list[Policy] + + +class SingleRoleModel(BaseModel): + data: Role + + +class MultipleRoleModel(BaseModel): + items: list[Role] + + +class SingleLicenseModel(BaseModel): + data: License + + +class MultipleLicensesModel(BaseModel): + items: list[License] + + +class SingleMailSettingModel(BaseModel): + data: MailSetting + + +class SingleLMClientModel(BaseModel): + data: LicenseManagerClient + + +class SingleLMConfigModel(BaseModel): + data: LicenseManagerConfig + + +class SingleJobReportModel(BaseModel): + data: BaseReportJob + + +class MultipleJobReportModel(BaseModel): + items: list[BaseReportJob] + + +class SingleEntityReportModel(BaseModel): + data: BaseReportEntity + + +class ErrorsReportModel(BaseModel): + items: list[ErrorReportItem] | None + data: BaseReportJob | None # if href=true + + +class RulesReportModel(BaseModel): + items: list[RulesReportItem] | None + data: BaseReportJob | None # if href=true + + +class EntityRulesReportModel(BaseModel): + items: list[AverageRulesReportItem] + + +class MultipleReportStatusModel(BaseModel): + items: list[ReportStatus] + + +class EntityResourcesReportModel(BaseModel): + items: list[ResourcesReportItem] | None + data: BaseReportEntity | None + + +class JobResourcesReportModel(BaseModel): + items: list[ResourcesReportItem] + data: BaseReportJob | None + + +class SingleLicenseActivationModel(BaseModel): + data: BaseActivation + + +class SingleDefeDojoModel(BaseModel): + data: DefectDojo + + +class MultipleDefectDojoModel(BaseModel): + items: DefectDojo + + +class SingleDefectDojoActivation(BaseModel): + data: DefectDojoActivation + + +class SingleDefectDojoPushResult(BaseModel): + data: DojoPushResult + + +class MultipleDefectDojoPushResult(BaseModel): + items: list[DojoPushResult] + + +class SingleSelfIntegration(BaseModel): + data: SelfIntegration + + +class SingleTenantExcludedRules(BaseModel): + data: TenantExcludedRules + + +class SingleCustomerExcludedRules(BaseModel): + data: CustomerExcludedRules + + +class MultipleCredentialsModel(BaseModel): + items: list[Credentials] + next_token: str | None + + +class SingleCredentialsModel(BaseModel): + data: Credentials + + +class CredentialsActivationModel(BaseModel): + data: BaseActivation + + +class SingleUserModel(BaseModel): + data: User + + +class MultipleUsersModel(BaseModel): + items: list[User] + next_token: str | None diff --git a/src/validators/utils.py b/src/validators/utils.py index 816668119..3e6d26071 100644 --- a/src/validators/utils.py +++ b/src/validators/utils.py @@ -1,10 +1,11 @@ from functools import wraps from http import HTTPStatus -from typing import Callable, Dict, Any, TypeVar +from typing import Callable, Any, TypeVar, TypedDict as TypedDict1 +from typing_extensions import TypedDict as TypedDict2 from pydantic import BaseModel, ValidationError -from helpers import build_response +from helpers.lambda_response import ResponseFactory T = TypeVar('T') @@ -13,39 +14,32 @@ def validate_pydantic(model: type, value: dict) -> BaseModel: try: return model(**value) except ValidationError as e: - # return build_response( - # code=HTTPStatus.BAD_REQUEST, - # content='Validation error', - # meta={ - # PARAM_ERRORS: e.errors() - # } - # ) - errors = {} + errors = [] for error in e.errors(): - loc = error.get("loc")[-1] - msg = error.get("msg") - errors.update({loc: msg}) - errors = '; '.join(f"{k} - {v}" for k, v in errors.items()) - return build_response( - code=HTTPStatus.BAD_REQUEST, - content=f'The following parameters do not match ' - f'the schema requirements: {errors}' - ) - - -def validate_type(_type: type(T), value: Any) -> T: + errors.append({ + 'location': error['loc'], + 'description': error['msg'] + }) + raise ResponseFactory(HTTPStatus.BAD_REQUEST).errors(errors).exc() + + +def validate_type(_type: type[T], value: Any) -> T: + if next(iter(getattr(_type, '__orig_bases__', ())), None) in (TypedDict1, + TypedDict2): + _type = dict # because TypedDict does not support isinstance + if isinstance(value, _type): + return value try: - return _type(value) if not isinstance(value, _type) else value + return _type(value) except (ValueError, TypeError) as e: - return build_response( - code=HTTPStatus.BAD_REQUEST, - content=f'Validation error: \'{value}\' cannot be casted to ' - f'{_type.__name__}' - ) + raise ResponseFactory(HTTPStatus.BAD_REQUEST).errors([{ + 'location': ['path'], + 'message': f'\'{value}\' should have type {_type.__name__}' + }]).exc() -def _validate(kwargs: Dict[str, Any], types: Dict[str, type], - cast: bool = True) -> Dict[str, Any]: +def _validate(kwargs: dict[str, Any], types: dict[str, type], + cast: bool = True) -> dict[str, Any]: """ Received keys and values in `kwargs`, keys and expected values' types in `types`. Returns a dict with keys and validated values @@ -95,28 +89,3 @@ def wrapper(*args, **kwargs): return result return wrapper - - -def validate_kwargs_by(**types) -> Callable: - """ - The same as a decorator above but instead of func.__annotations__ it - uses given by the developer keyword arguments `types`. Also, it - does not cast attributes to the required values `cause it's not - annotations. It would look odd. If you still want this method to cast - attributes just set _cast=True - :param types: - :return: - """ - _cast = False - if types.get('_cast') is True: - _cast = types.get('_cast') - - def decorator(func: Callable) -> Callable: - @wraps(func) - def wrapper(*args, **kwargs): - validated = _validate(kwargs, types, cast=_cast) - return func(*args, **validated) - - return wrapper - - return decorator diff --git a/tox.ini b/tox.ini index b0a344323..5e117fc22 100644 --- a/tox.ini +++ b/tox.ini @@ -1,31 +1,33 @@ [tox] minversion = 2.4 -envlist = lint,pylint,py38 +envlist = lint,pylint,py310 skipsdist = True skip_install = True isolated_build = False [pytest] -addopts = --cov=src/ --cov-report term-missing --cov-report xml:coverage.xml --junitxml=report.xml -testpaths = tests +; addopts = +; testpaths = tests python_files = test*.py -norecursedirs = docs pics scripts +norecursedirs = docs pics patch_scripts [testenv] deps = - tox==3.24.5 + tox==4.4.6 pytest==6.2.5 pytest-cov==3.0.0 - -rtests/requirements-test.txt - -e./mcdm_sdk + -rsrc/onprem/requirements.txt +;setenv = +; PYTHONPATH={env:PYTHONPATH}:${env.PROJECT_HOME_PATH}/ +; PYTHONPATH={env:PYTHONPATH}:${env.PROJECT_HOME_PATH}/src +; PYTHONPATH={env:PYTHONPATH}:${env.PROJECT_HOME_PATH}/tests - -[testenv:py38] -basepython = python3.8 +[testenv:py310] +basepython = python3.10 commands = - pytest -v {posargs} + pytest -v {posargs} tests/ --cov=src/ --cov-report term-missing --cov-report xml:coverage.xml --junitxml=report.xml [testenv:lint] @@ -38,7 +40,7 @@ deps = flake8 commands = flake8 - mypy src/exported_module docker/ src/handlers tests/ + mypy src/onprem docker/ src/handlers tests/ [flake8] exclude = setup.py,.git,.venv*,venv,python2.7,.tox,scripts,pics,docs,build,report_commands_to_tables_script From ef4ac9ef31f4f2dd930bd0f87b351cfc1b5b6b2c Mon Sep 17 00:00:00 2001 From: Dmytro Afanasiev Date: Thu, 9 May 2024 23:36:18 +0300 Subject: [PATCH 2/4] Add tests --- tests/__init__.py | 0 tests/commons.py | 53 + tests/conftest.py | 19 + tests/smoke/README.md | 57 + tests/smoke/commons.py | 586 + tests/smoke/main_flow.py | 231 + tests/smoke/rules_management.py | 272 + tests/test_on_prem_api.py | 19 + tests/test_rule_meta_updater.py | 35 + tests/tests_helpers/__init__.py | 0 tests/tests_helpers/test_constants.py | 16 + tests/tests_helpers/test_lambda_response.py | 118 + tests/tests_helpers/test_reports.py | 104 + tests/tests_helpers/test_utils.py | 299 + tests/tests_metrics/README.md | 9 + tests/tests_metrics/__init__.py | 0 .../aws_tenant_group_finops.json | 401 + .../aws_tenant_resources.json | 1 + .../azure_tenant_group_finops.json | 1 + .../azure_tenant_resources.json | 1 + .../expected_metrics/customer_metrics.json | 361 + .../expected_metrics/department_metrics.json | 2464 ++++ .../google_tenant_group_finops.json | 1 + .../google_tenant_resources.json | 1 + .../tests_metrics/mock_files/old_metrics.json | 1795 +++ .../AWS/AWS-1234567890123/latest/0.json | 10565 ++++++++++++++++ .../AWS/AWS-1234567890123/latest/1.json | 22 + .../AWS/AWS-1234567890123/latest/meta.json | 1 + .../AZURE/AZURE-1234567890123/latest/0.json | 1 + .../AZURE-1234567890123/latest/meta.json | 1 + .../GOOGLE/GOOGLE-1234567890123/latest/0.json | 1 + .../GOOGLE-1234567890123/latest/meta.json | 1 + .../AWS_STANDARDS_COVERAGE.json.gz | 1 + .../AZURE_STANDARDS_COVERAGE.json.gz | 1 + .../GOOGLE_STANDARDS_COVERAGE.json.gz | 1 + .../rulesets/caas-settings/HUMAN_DATA.json.gz | 1 + .../caas-settings/RULES_TO_CATEGORY.json.gz | 1 + .../caas-settings/RULES_TO_MITRE.json.gz | 1 + .../caas-settings/RULES_TO_SERVICE.json.gz | 1 + .../RULES_TO_SERVICE_SECTION.json.gz | 1 + .../caas-settings/RULES_TO_SEVERITY.json.gz | 1 + .../caas-settings/RULES_TO_STANDARDS.json.gz | 1 + .../mock_files/statistics/statistics.json | 1 + .../tests_metrics/test_metrics_aggregation.py | 615 + tests/tests_metrics/test_metrics_updater.py | 307 + tests/tests_models/__init__.py | 0 tests/tests_models/test_rule_comment.py | 41 + tests/tests_services/__init__.py | 0 tests/tests_services/policies.json | 14 + .../test_bucket_keys_builders.py | 168 + .../test_jwt_management_client.py | 85 + .../test_license_manager_token.py | 84 + tests/tests_services/test_license_service.py | 62 + tests/tests_services/test_modular_helpers.py | 218 + .../test_openapi_spec_generator.py | 291 + tests/tests_services/test_rbac.py | 203 + .../tests_services/test_rule_meta_service.py | 34 + tests/tests_services/test_sharding.py | 259 + tests/tests_services/test_xlsx_writer.py | 63 + 59 files changed, 19891 insertions(+) create mode 100644 tests/__init__.py create mode 100644 tests/commons.py create mode 100644 tests/conftest.py create mode 100644 tests/smoke/README.md create mode 100644 tests/smoke/commons.py create mode 100644 tests/smoke/main_flow.py create mode 100644 tests/smoke/rules_management.py create mode 100644 tests/test_on_prem_api.py create mode 100644 tests/test_rule_meta_updater.py create mode 100644 tests/tests_helpers/__init__.py create mode 100644 tests/tests_helpers/test_constants.py create mode 100644 tests/tests_helpers/test_lambda_response.py create mode 100644 tests/tests_helpers/test_reports.py create mode 100644 tests/tests_helpers/test_utils.py create mode 100644 tests/tests_metrics/README.md create mode 100644 tests/tests_metrics/__init__.py create mode 100644 tests/tests_metrics/expected_metrics/aws_tenant_group_finops.json create mode 100644 tests/tests_metrics/expected_metrics/aws_tenant_resources.json create mode 100644 tests/tests_metrics/expected_metrics/azure_tenant_group_finops.json create mode 100644 tests/tests_metrics/expected_metrics/azure_tenant_resources.json create mode 100644 tests/tests_metrics/expected_metrics/customer_metrics.json create mode 100644 tests/tests_metrics/expected_metrics/department_metrics.json create mode 100644 tests/tests_metrics/expected_metrics/google_tenant_group_finops.json create mode 100644 tests/tests_metrics/expected_metrics/google_tenant_resources.json create mode 100644 tests/tests_metrics/mock_files/old_metrics.json create mode 100644 tests/tests_metrics/mock_files/reports/raw/EPAM Systems/AWS/AWS-1234567890123/latest/0.json create mode 100644 tests/tests_metrics/mock_files/reports/raw/EPAM Systems/AWS/AWS-1234567890123/latest/1.json create mode 100644 tests/tests_metrics/mock_files/reports/raw/EPAM Systems/AWS/AWS-1234567890123/latest/meta.json create mode 100644 tests/tests_metrics/mock_files/reports/raw/EPAM Systems/AZURE/AZURE-1234567890123/latest/0.json create mode 100644 tests/tests_metrics/mock_files/reports/raw/EPAM Systems/AZURE/AZURE-1234567890123/latest/meta.json create mode 100644 tests/tests_metrics/mock_files/reports/raw/EPAM Systems/GOOGLE/GOOGLE-1234567890123/latest/0.json create mode 100644 tests/tests_metrics/mock_files/reports/raw/EPAM Systems/GOOGLE/GOOGLE-1234567890123/latest/meta.json create mode 100644 tests/tests_metrics/mock_files/rulesets/caas-settings/AWS_STANDARDS_COVERAGE.json.gz create mode 100644 tests/tests_metrics/mock_files/rulesets/caas-settings/AZURE_STANDARDS_COVERAGE.json.gz create mode 100644 tests/tests_metrics/mock_files/rulesets/caas-settings/GOOGLE_STANDARDS_COVERAGE.json.gz create mode 100644 tests/tests_metrics/mock_files/rulesets/caas-settings/HUMAN_DATA.json.gz create mode 100644 tests/tests_metrics/mock_files/rulesets/caas-settings/RULES_TO_CATEGORY.json.gz create mode 100644 tests/tests_metrics/mock_files/rulesets/caas-settings/RULES_TO_MITRE.json.gz create mode 100644 tests/tests_metrics/mock_files/rulesets/caas-settings/RULES_TO_SERVICE.json.gz create mode 100644 tests/tests_metrics/mock_files/rulesets/caas-settings/RULES_TO_SERVICE_SECTION.json.gz create mode 100644 tests/tests_metrics/mock_files/rulesets/caas-settings/RULES_TO_SEVERITY.json.gz create mode 100644 tests/tests_metrics/mock_files/rulesets/caas-settings/RULES_TO_STANDARDS.json.gz create mode 100644 tests/tests_metrics/mock_files/statistics/statistics.json create mode 100644 tests/tests_metrics/test_metrics_aggregation.py create mode 100644 tests/tests_metrics/test_metrics_updater.py create mode 100644 tests/tests_models/__init__.py create mode 100644 tests/tests_models/test_rule_comment.py create mode 100644 tests/tests_services/__init__.py create mode 100644 tests/tests_services/policies.json create mode 100644 tests/tests_services/test_bucket_keys_builders.py create mode 100644 tests/tests_services/test_jwt_management_client.py create mode 100644 tests/tests_services/test_license_manager_token.py create mode 100644 tests/tests_services/test_license_service.py create mode 100644 tests/tests_services/test_modular_helpers.py create mode 100644 tests/tests_services/test_openapi_spec_generator.py create mode 100644 tests/tests_services/test_rbac.py create mode 100644 tests/tests_services/test_rule_meta_service.py create mode 100644 tests/tests_services/test_sharding.py create mode 100644 tests/tests_services/test_xlsx_writer.py diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/commons.py b/tests/commons.py new file mode 100644 index 000000000..2a84efa19 --- /dev/null +++ b/tests/commons.py @@ -0,0 +1,53 @@ +import os +import sys +from pathlib import Path + +SOURCE = Path(__file__).parent.parent / 'src' + + +class ImportFromContext: + """ + Context object to import lambdas and packages. It's necessary because + root path is not the path to the syndicate project but the path where + lambdas are accumulated - SRC_FOLDER + """ + + def __init__(self, source: Path, envs: dict): + self.envs = envs or {} + self._old_envs = {} + self.source = source.resolve() + + def add_source_to_path(self): + source_path = str(self.source) + if source_path not in sys.path: + sys.path.append(source_path) + + def remove_source_from_path(self): + source_path = str(self.source) + if source_path in sys.path: + sys.path.remove(source_path) + + def add_envs(self): + for k, v in self.envs.items(): + if k in os.environ: + self._old_envs[k] = os.environ[k] + os.environ[k] = v + + def remove_envs(self): + for k in self.envs: + os.environ.pop(k, None) + os.environ.update(self._old_envs) + + def __enter__(self): + self.add_source_to_path() + self.add_envs() + + def __exit__(self, exc_type, exc_val, exc_tb): + self.remove_source_from_path() + self.remove_envs() + + +import_from_source = ImportFromContext(source=SOURCE, envs={ + 'AWS_REGION': 'eu-central-1', + 'CAAS_TESTING': 'true', +}) diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 000000000..a83e562fe --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,19 @@ +from .commons import import_from_source + + +def pytest_sessionstart(): + """ + Adds src to PATH + :return: + """ + import_from_source.add_source_to_path() + import_from_source.add_envs() + + +def pytest_sessionfinish(): + """ + Removes src from PATH + :return: + """ + import_from_source.remove_source_from_path() + import_from_source.remove_envs() diff --git a/tests/smoke/README.md b/tests/smoke/README.md new file mode 100644 index 000000000..bd74bbb31 --- /dev/null +++ b/tests/smoke/README.md @@ -0,0 +1,57 @@ +# Custodian as a service smoke tests + +These smoke tests should be executed against a configured env. They check the +general condition of the environment. + +**Note:** all the smoke tests use `c7ncli` to execute commands. So, the CLI +must be installed in the local execution environment. + + +## Main flow + +This smoke tests case does not create any new entities, except jobs. It checks +most describe actions and starts jobs for specified tenants: + +```bash +python tests/smoke/main_flow.py --username $CAAS_USERNAME --password $CAAS_PASSWORD --api_link $CAAS_API_LINK --tenants TEST_TENANT:eu-west-1,eu-west-2 TEST_TENANT2 +``` + +Such a command will check all the describe actions and will execute two jobs: +one to scan `TEST_TENANT` in `eu-west1` & `eu-west-2` regions, another to +scan `TEST_TENANT2` in all the regions + + +## Rules management flow + +This smoke tests case checks rules & rulesets -bound actions. It requires +at least one rulesource's data. Necessary envs: + +```bash +export SMOKE_CAAS_USERNAME= +export SMOKE_CAAS_PASSWORD= +export SMOKE_CAAS_CUSTOMER= +export SMOKE_CAAS_API_LINK= + +# SMOKE_TEST_[AWS|AZURE|GCP]_... +export SMOKE_CAAS_AWS_RULE_SOURCE_SECRET= +export SMOKE_CAAS_AWS_RULE_SOURCE_PID= +export SMOKE_CAAS_AWS_RULE_SOURCE_REF= +export SMOKE_CAAS_AWS_RULE_SOURCE_URL= +export SMOKE_CAAS_AWS_RULE_SOURCE_PREFIX= +``` + +Example: + +```bash +export SMOKE_CAAS_USERNAME=test +export SMOKE_CAAS_PASSWORD=password +export SMOKE_CAAS_CUSTOMER=TEST +export SMOKE_CAAS_API_LINK=http://127.0.0.1:8000 + +export SMOKE_CAAS_AWS_RULE_SOURCE_PID=epam/ecc-aws-rulepack +export SMOKE_CAAS_AWS_RULE_SOURCE_REF=main +export SMOKE_CAAS_AWS_RULE_SOURCE_PREFIX=policies/ + +python tests/smoke/rules_management.py +``` + diff --git a/tests/smoke/commons.py b/tests/smoke/commons.py new file mode 100644 index 000000000..27692c8d1 --- /dev/null +++ b/tests/smoke/commons.py @@ -0,0 +1,586 @@ +import datetime +import json +import logging +import os +import re +import subprocess +import time +import urllib.error +import urllib.request +from abc import ABC, abstractmethod +from typing import Any, Optional, Union, Sequence, Callable + +_handler = logging.StreamHandler() +_handler.setLevel(logging.INFO) +_LOG = logging.getLogger() +_LOG.setLevel(logging.INFO) +_LOG.addHandler(_handler) + +JSON_PATH_LIST_INDEXES = re.compile(r'\w*\[(-?\d+)\]') +JSON_PATH_PATTERN = re.compile(r'(\$\.[\w\d\[\]\.]+)') +OPTIONS_TO_HIDE = { + '-sk', '--aws_secret_access_key', '-st', '--aws_session_token', + '-secret', '--git_access_secret', '--password', '-gsecret', '-p' +} +SECRET_REPLACEMENT = '****' + +COLOR_TEXT_MARKDOWN = '**{text}**' +PASSED_MARKDOWN = COLOR_TEXT_MARKDOWN.format(color='green', text='passed') +FAILED_MARKDOWN = COLOR_TEXT_MARKDOWN.format(color='red', text='failed') +FAILED_EXPLANATION_TEMPLATE = """#### Output: +```json +{output} +``` +#### Expected: +```json +{expected} +``` +""" +CASE_TEMPLATE = """### {name} +{steps} +""" + +SMOKE_STEP_DELAY_ENV: str = 'SMOKE_TEST_DELAY' + +LOG_NOT_ALLOWED_TO_EXECUTE_STEP = \ + 'The steps if not allowed to be executed. ' \ + 'Some previous steps have not succeeded' + +SRC_FOLDER = 'src' +TESTING_MODE_ENV = 'CUSTODIAN_TESTING' +TESTING_MODE_ENV_TRUE = 'true' + + +class TermColor: + HEADER = '\033[95m' + OKBLUE = '\033[94m' + OKCYAN = '\033[96m' + OKGREEN = '\033[92m' + WARNING = '\033[93m' + FAIL = '\033[91m' + ENDC = '\033[0m' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' + + @classmethod + def blue(cls, st: str) -> str: + return f'{cls.OKBLUE}{st}{cls.ENDC}' + + @classmethod + def cyan(cls, st: str) -> str: + return f'{cls.OKCYAN}{st}{cls.ENDC}' + + @classmethod + def green(cls, st: str) -> str: + return f'{cls.OKGREEN}{st}{cls.ENDC}' + + @classmethod + def yellow(cls, st: str) -> str: + return f'{cls.WARNING}{st}{cls.ENDC}' + + @classmethod + def red(cls, st: str) -> str: + return f'{cls.FAIL}{st}{cls.ENDC}' + + +def magic_get(d: Union[dict, list], path: str) -> Any: + """ + Simple json paths with only basic operations supported. This should be + enough for the current smoke tests. If it becomes not enough you are + welcome to use some external lib. + If the path not found, None is returned. + >>> magic_get({'a': 'b', 'c': [1,2,3, [{'b': 'c'}]]}, 'c[-1][0].b') + 'c' + >>> magic_get([-1, {'one': 'two'}], 'c[-1][0].b') is None + True + >>> magic_get([-1, {'one': 'two'}], '[-1].one') + 'two' + """ + if path.startswith('$'): + path = path[1:] + if path.startswith('.'): + path = path[1:] + parts = path.split('.') + + item = d + for part in parts: + try: + _key = part.split('[')[0] + _indexes = re.findall(JSON_PATH_LIST_INDEXES, part) + if _key: + item = item.get(_key) + for i in _indexes: + item = item[int(i)] + except (IndexError, TypeError, AttributeError): + item = None + break + return item + + +def resolve_dynamic_params(string: str, + data: Union[dict, Sequence]) -> str: + """ + >>> string = 'c7n job describe -id $.[0].items[0].job_id' + >>> data = [{'items': [{'job_id': 'uuid'}]}] + >>> resolve_dynamic_params(string, data) + 'c7n job describe -id uuid' + """ + result = string + paths = re.findall(JSON_PATH_PATTERN, string) + for path in paths: + result = result.replace(path, magic_get(data, path) or '') + return result + + +class Condition(ABC): + def __int__(self): + pass + + @staticmethod + def assert_condition(maybe_condition: Any): + if not isinstance(maybe_condition, Condition): + raise TypeError(f'{maybe_condition} must be a condition') + + def __and__(self, other: 'Condition') -> 'And': + self.assert_condition(other) + return And(self, other) + + def __or__(self, other: 'Condition') -> 'Or': + self.assert_condition(other) + return Or(self, other) + + @abstractmethod + def check(self, value: Any) -> bool: + ... + + @abstractmethod + def __repr__(self) -> str: + ... + + +class Not(Condition): + + def __init__(self, condition: Condition): + self._condition = condition + + def check(self, value: Any) -> bool: + return not self._condition.check(value=value) + + def __repr__(self): + return f'NOT {self._condition.__repr__()}' + + +class And(Condition): + def __init__(self, left: Condition, right: Condition): + self._left = left + self._right = right + + def check(self, value: Any) -> bool: + return self._left.check(value) and self._right.check(value) + + def __repr__(self): + return f'({self._left.__repr__()} AND {self._right.__repr__()})' + + +class Or(Condition): + def __init__(self, left: Condition, right: Condition): + self._left = left + self._right = right + + def check(self, value: Any) -> bool: + return self._left.check(value) or self._right.check(value) + + def __repr__(self): + return f'({self._left.__repr__()} OR {self._right.__repr__()})' + + +class In(Condition): + def __init__(self, *values): + self._values = set(values) + + def check(self, value: Any) -> bool: + return value in self._values + + def __repr__(self) -> str: + return f'IN [{", ".join(map(str, self._values))}]' + + +class Contains(Condition): + def __init__(self, value: str): + self._value = value + + def check(self, value: str) -> bool: + return isinstance(value, str) and self._value in value + + def __repr__(self) -> str: + return f'*{self._value}*' + + +class ListContains(Condition): + def __init__(self, value: Any): + self._value = value + + def check(self, value: list) -> bool: + return self._value in (value or set()) + + def __repr__(self) -> str: + return f'HAS {self._value}' + + +class Equal(Condition): + def __init__(self, value: Any): + self._value = value + + def check(self, value: Any) -> bool: + return value == self._value + + def __repr__(self) -> str: + return str(self._value) + + +class False_(Condition): + def check(self, value: bool) -> bool: + return isinstance(value, bool) and not value + + def __repr__(self) -> str: + return str(False).lower() + + +class True_(Condition): + def check(self, value: Any) -> bool: + return not False_().check(value) + + def __repr__(self) -> str: + return str(True).lower() + + +class NotEmpty(Condition): + def check(self, value: Any) -> bool: + """ + int:0 and bool:False are not considered empty here + """ + return bool(value) if not isinstance(value, (int, bool)) else True + + def __repr__(self) -> str: + return 'EXISTS' + + +class Empty(Condition): + def check(self, value: Any) -> bool: + return not NotEmpty().check(value) + + def __repr__(self) -> str: + return 'NOT EXIST' + + +class Len(Condition): + def __init__(self, operator: Callable, _len: int): + self._operator = operator + self._len = _len + + def check(self, value: Sequence) -> bool: + return isinstance(value, Sequence) and \ + self._operator(len(value), self._len) + + def __repr__(self) -> str: + return f'LEN {self._operator.__name__} {self._len}' + + +class IsInstance(Condition): + def __init__(self, *types: type): + self._types = types + + def check(self, value: Any) -> bool: + return isinstance(value, tuple(self._types)) + + def __repr__(self) -> str: + return f'IS INSTANCE ANY OF: ' \ + f'{", ".join(map(lambda x: x.__name__, self._types))}' + + +Expectations = dict[str, Condition | tuple[Condition, ...]] + + +class AbstractStep(ABC): + """ + Abstract step class which handles expectations logic. Basically it + executes some action which returns JSON and validates this JSON based + on certain conditions. So-called `expectations` are these conditions. + Expectations is a dict where a key is json-paths string and a value + is instance of class __main__.Condition. + Valid json-paths: + - '$.key1.key2[0][2][-1].key3', + - '.key1.key2[0][2][-1].key3', + - 'key1.key2[0][2][-1].key3' + + expectations = { + str: Condition + } + Each expectations' key will be retrieved from the output JSON and + validated through a condition + """ + + def __init__(self, expectations: Optional[Expectations] = None, + depends_on: Sequence['AbstractStep'] | None = None, + delay: Optional[int] = None): + self._expectations = expectations or {} + self._output: dict = {} + self._is_finished: bool = False + self._is_succeeded: Optional[bool] = None + self._depends_on: tuple['AbstractStep', ...] = tuple(depends_on or []) + self._delay = delay or int(os.getenv(SMOKE_STEP_DELAY_ENV) or 0) + + def is_allowed(self) -> bool: + for dependent_step in self._depends_on: + if not dependent_step.succeeded: + return False + return True + + def _resolve_dynamic(self, target_string: str) -> str: + return resolve_dynamic_params( + target_string, tuple(step.output for step in self._depends_on)) + + @staticmethod + def _check(data: dict, expectations: Expectations) -> bool: + expectations = expectations or {} + for path, condition in expectations.items(): + _to_check = magic_get(data, path) + if not condition.check(_to_check): + return False + return True + + def _check_expectations(self, data: dict) -> bool: + return self._check(data, self._expectations) + + def reset(self): + self._output.clear() + self._is_finished = False + self._is_succeeded = None + + @property + def succeeded(self) -> bool: + if not self._is_finished: + return False + if not isinstance(self._is_succeeded, bool): + self._is_succeeded = self._check_expectations(self._output) + return self._is_succeeded + + @property + def finished(self) -> bool: + return self._is_finished + + @property + def output(self) -> dict: + return self._output + + def dump_expectations(self) -> str: + almost_dumped = {} + for path, condition in self._expectations.items(): + almost_dumped[path] = str(condition) + return json.dumps(almost_dumped, indent=4) + + @abstractmethod + def _execute(self, *args, **kwargs) -> str: + """Returns json string. Makes one simple request based on class + attributes and purpose""" + + def execute(self): + if not self.is_allowed(): + _LOG.warning(LOG_NOT_ALLOWED_TO_EXECUTE_STEP) + return + self._output.clear() + self._output.update(json.loads(self._execute())) + self._is_finished = True + time.sleep(self._delay) + + @abstractmethod + def report(self) -> str: + ... + + +class Step(AbstractStep): + """Actually CLI step -> should be renamed""" + + def __init__(self, command: str, + expectations: Optional[Expectations] = None, + depends_on: Optional[Sequence['AbstractStep']] = None, + json_flag: bool = True): + super().__init__(expectations, depends_on) + self._json_flag = json_flag + self._command = self._adjust_command(command) + + def _adjust_command(self, command: str) -> str: + if not self._json_flag: + return command + json_flag = '--json' + if json_flag not in command: + command += f' {json_flag}' + return command + + def _execute(self) -> str: + process = subprocess.run(self._command.split(), capture_output=True) + return process.stdout.decode() + + def execute(self): + self._command = self._resolve_dynamic(self._command) + _LOG.info(f'Executing in CLI: {self.command}') + super().execute() + + @property + def command(self) -> str: + parts = self._command.split() + result = [] + i = 0 + while i < len(parts): + part = parts[i] + result.append(part) + if part in OPTIONS_TO_HIDE: + result.append(SECRET_REPLACEMENT) + i += 2 + else: + i += 1 + return ' '.join(result) + + def report(self) -> str: + _succeeded = self.succeeded + s = f'`{self.command}` - ' \ + f'{PASSED_MARKDOWN if _succeeded else FAILED_MARKDOWN};' + if not _succeeded: + s += '\n' + FAILED_EXPLANATION_TEMPLATE.format( + output=json.dumps(self._output, indent=4), + expected=self.dump_expectations() + ) + s += '\n\n' + return s + + +class WaitUntil(Step): + def __init__(self, command: str, + expectations: Optional[Expectations] = None, + depends_on: Sequence['AbstractStep'] = None, + timeout: int = 900, sleep: int = 15, + break_if: Optional[Expectations] = None): + super().__init__(command, expectations, depends_on) + self._timeout = timeout + self._sleep = sleep + self._timed_out = False + self._break_if = break_if or {} + + def execute(self): + if not self.is_allowed(): + # TODO print in report that a step was skipped + _LOG.warning(LOG_NOT_ALLOWED_TO_EXECUTE_STEP) + return + self._command = self._resolve_dynamic(self._command) + + _request = lambda: json.loads(self._execute()) + _LOG.info(f'Executing in CLI: {self.command}') + + _start = time.time() + _result = _request() + while not self._check_expectations(_result): + if self._break_if and self._check(_result, self._break_if): + _LOG.warning(f'Command {self.command} has passes \'break if\' ' + f'condition. Going to the next step') + break + if time.time() - _start > self._timeout: + _LOG.warning(f'Timeout exceeded. Command {self.command} was ' + f'not in time. Going to the next step') + self._timed_out = True + break + time.sleep(self._sleep) + _LOG.info(f'Executing in CLI: {self.command}') + _result = _request() + self._is_finished = True + self._output.clear() + self._output.update(_result) + time.sleep(self._delay) + + def reset(self): + super().reset() + self._timed_out = False + + def report(self) -> str: + report = super().report() + if self._timed_out: + report = COLOR_TEXT_MARKDOWN.format( + color='yellow', text='Timeout') + ' ' + report + return report + + +class ApiStep(AbstractStep): + def __init__(self, url: str, method: str = 'GET', + data: Optional[dict] = None, + headers: Optional[dict] = None, + expectations: Optional[dict] = None, + ): + super().__init__(expectations) + self._url, self._method = url, method + self._data, self._headers = data or {}, headers or {} + + def _execute(self) -> str: + _data = json.dumps(self._data).encode('utf8') + _headers = {**self._headers, 'Content-Type': 'application/json'} + request = urllib.request.Request( + self._url, data=_data, headers=_headers, method=self._method) + result = '{}' + try: + with urllib.request.urlopen(request) as response: + result = response.read().decode() + except urllib.error.HTTPError as error: + result = error.fp.read().decode() + except urllib.error.URLError as error: + _LOG.error(f'Could not make the request: {error}. ' + f'Returning an empty dict') + except TimeoutError: + _LOG.warning('Timeout occurred making the request. ' + 'Returning an empty dict') + return result + + def execute(self): + _LOG.info(f'Making \'{self._method}\' call to \'{self._url}\'') + super().execute() + + def report(self) -> str: + return 'in progress' + + +Steps = tuple[AbstractStep, ...] + + +class Case: + def __init__(self, steps: Steps, name: str): + self._steps = steps + self._name = name + self._done: list[AbstractStep] = [] + + @property + def finished(self) -> bool: + return self._done and all(step.finished for step in self._done) + + def reset(self): + self._done.clear() + [step.reset() for step in self._steps] + + def execute(self): + for step in self._steps: + try: + step.execute() + self._done.append(step) + except Exception as e: + _LOG.warning(f'Unexpected exception: \'{e}\' occurred while ' + f'executing step: {vars(step)}') + + def report(self) -> str: + return CASE_TEMPLATE.format( + name=self._name.title(), + steps=''.join(s.report() for s in self._done) + ) + + +def write_cases(cases: list[Case], name: Optional[str] = None): + date = datetime.date.today().isoformat() + name = name or f'smoke-report-{date}.md' + with open(name, 'w') as file: + file.write(f'## Custodian smoke report {date}\n\n') + [file.write(case.report()) for case in cases] diff --git a/tests/smoke/main_flow.py b/tests/smoke/main_flow.py new file mode 100644 index 000000000..423f0e92a --- /dev/null +++ b/tests/smoke/main_flow.py @@ -0,0 +1,231 @@ +import argparse +import operator +from collections import namedtuple +from typing import List, Optional +from pathlib import Path +from commons import Case, Step, Equal, Empty, write_cases, NotEmpty, True_, \ + IsInstance, Len, In, WaitUntil, Contains + +TenantPayload = namedtuple('TenantPayload', ['name', 'regions']) + + +class TenantRegionsType: + def __init__(self): + pass + + def __call__(self, item: str) -> TenantPayload: + res = item.split(':', maxsplit=1) + if len(res) == 1: + return TenantPayload(name=res[0], regions=[]) + # len(res) == 2 + name, regions = res + return TenantPayload(name=name, regions=regions.split(',')) + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + description='Entrypoint for smokes', + ) + parser.add_argument('--username', required=True, type=str) + parser.add_argument('--password', required=True, type=str) + parser.add_argument('--api_link', required=True, type=str) + parser.add_argument('--tenants', nargs='+', required=True, + type=TenantRegionsType(), + help='Tenant to list of regions: ' + '--tenants EOOS:eu-central-1,eu-west-1 ' + 'CIT2:eu-west-1') + parser.add_argument('--customer', required=False, type=str, + help='Tenants to submit jobs for') + + def markdown(value: str) -> Path: + if not value.endswith('.md'): + value = value + '.md' + return Path(value) + parser.add_argument('--filename', required=False, type=markdown, + help='Output file') + return parser + + +def main(username: str, password: str, api_link: str, + tenants: List[TenantPayload], customer: Optional[str], + filename: Optional[Path]): + _customer_check = Equal(customer) if customer else NotEmpty() + authentication_case = Case(steps=( + Step(f'c7n configure --api_link {api_link} --json', { + '$.message': Equal( + 'Great! The c7n tool api_link has been configured.') + }), + Step('c7n show_config', { + '$.api_link': Equal(api_link), + }), + Step(f'c7n login -u {username} -p {password} --json', + { + '$.message': Equal( + f'Great! The c7n tool access token has been saved.') + }), + Step('c7n health_check --status NOT_OK', { + '$.items': Empty() + }), + ), name='Authentication') + entities_describe_case = Case(steps=( + Step(f'c7n customer describe', { + '$.items[0].name': _customer_check, + }), + Step(f'c7n customer rabbitmq describe', { + '$.data.customer': _customer_check, + '$.data.maestro_user': NotEmpty(), + '$.data.request_queue': NotEmpty(), + '$.data.response_queue': NotEmpty(), + '$.data.sdk_access_key': NotEmpty(), + }), + *[Step(f'c7n tenant describe -tn {tenant.name}', { + '$.data.name': Equal(tenant.name), + '$.data.activation_date': NotEmpty(), + '$.data.customer_name': _customer_check, + '$.data.is_active': True_(), + '$.data.account_id': NotEmpty(), + '$.data.regions': Len(operator.ge, 1) + }) for tenant in tenants], + Step(f'c7n policy describe', { + '$.items[0].customer': _customer_check, + '$.items[0].name': NotEmpty(), + '$.items[0].permissions': IsInstance(list) + }), # at least one + Step('c7n role describe', { + '$.items[0].name': NotEmpty(), + '$.items[0].customer': _customer_check, + '$.items[0].policies': IsInstance(list) + }), + Step('c7n setting lm client describe', { + '$.data.algorithm': Equal('ECC:p521_DSS_SHA:256'), + '$.data.b64_encoded': IsInstance(bool), + '$.data.format': NotEmpty() & IsInstance(str), + '$.data.key_id': NotEmpty() & IsInstance(str), + '$.data.public_key': NotEmpty() & IsInstance(str) + }), + Step('c7n setting lm config describe', { + '$.data.host': NotEmpty() & IsInstance(str), + '$.data.port': NotEmpty() & IsInstance(int), + '$.data.protocol': In('HTTP', 'HTTPS'), + '$.data.stage': IsInstance(str), + }), + Step('c7n setting mail describe', { + '$.data.default_sender': NotEmpty() & Contains( + '.com') & Contains('@'), + '$.data.host': NotEmpty(), + '$.data.max_emails': IsInstance(int), + '$.data.password': NotEmpty(), + '$.data.port': IsInstance(int), + '$.data.username': NotEmpty(), + }), + Step('c7n results describe', { + '$.items': IsInstance(list) + }), + Step('c7n ruleset describe -ls False', { + '$.items': IsInstance(list) + }), + Step('c7n ruleset describe -ls True', { + '$.items': IsInstance(list) & NotEmpty(), + '$.items[0].customer': Equal('CUSTODIAN_SYSTEM'), + '$.items[0].name': NotEmpty(), + '$.items[0].version': NotEmpty() & IsInstance(str), + '$.items[0].cloud': In('AWS', 'AZURE', 'GCP', 'KUBERNETES'), + '$.items[0].rules_number': IsInstance(int), + '$.items[0].active': True_(), + '$.items[0].license_keys': IsInstance(list), + '$.items[0].license_manager_id': NotEmpty(), + '$.items[0].licensed': True_() + }), + Step('c7n metrics status', { + '$.message': Equal('Cannot find latest metrics update job') | ( + Contains('Last metrics update was started at') & Contains('with status SUCCESS')) + }) + ), name='Entities describe') + + executing_scans_cases = [] + for tenant in tenants: + _regions = ' '.join(f'--region {r}' for r in tenant.regions) + job_submit_step = Step( + f'c7n job submit --tenant_name {tenant.name} {_regions}', { + '$.data.id': NotEmpty(), + '$.data.status': Equal('SUBMITTED'), + '$.data.customer_name': _customer_check, + '$.data.tenant_name': Equal(tenant.name) + }) + executing_scans_cases.append(Case(steps=( + job_submit_step, + WaitUntil(f'c7n job describe -id $.[0].data.id', { + '$.data.status': Equal('SUCCEEDED') + }, break_if={ + '$.data.status': Equal('FAILED') + }, depends_on=(job_submit_step,), sleep=15, timeout=1800), + Step(f'c7n job describe -id $.[0].data.id', { + '$.data.rulesets': Len(operator.eq, 1), + '$.data.stopped_at': NotEmpty() + }, depends_on=(job_submit_step,)) + ), name=f'Executing scans for tenant: {tenant.name}')) + + executing_scans_cases.append(Case(steps=( + Step('c7n report compliance jobs -id $.[0].data.id', { + '$.data.job_type': Equal('manual'), + **{ + f'$.data.content.{region}.HIPAA': NotEmpty() + for region in tenant.regions + }, + **{ + f'$.data.content.{region}.NERC-CIP': NotEmpty() + for region in tenant.regions + }, + }, depends_on=(job_submit_step,)), + Step(f'c7n report compliance accumulated -tn {tenant.name}', { + **{ + f'$.data.content.{region}.HIPAA': NotEmpty() + for region in tenant.regions + }, + **{ + f'$.data.content.{region}.NERC-CIP': NotEmpty() + for region in tenant.regions + }, + }, depends_on=(job_submit_step,)), + Step('c7n report digests jobs -id $.[0].data.id', { + '$.data.job_type': Equal('manual'), + '$.data.content.total_checks': NotEmpty(), + '$.data.content.successful_checks': NotEmpty(), + '$.data.content.failed_checks': NotEmpty(), + '$.data.content.violating_resources': NotEmpty(), + }, depends_on=(job_submit_step,)), + Step('c7n report errors jobs -id $.[0].data.id', { + '$.items[0].type': Equal('manual') | Empty(), + '$.items[0].content': IsInstance(dict) | Empty(), + }, depends_on=(job_submit_step,)), + Step('c7n report rules jobs -id $.[0].data.id', { + '$.items': NotEmpty() & IsInstance(list), + '$.items[0].policy': NotEmpty(), + '$.items[0].region': NotEmpty(), + }, depends_on=(job_submit_step,)), + Step('c7n report details jobs -id $.[0].data.id', { + '$.data.job_type': Equal('manual'), + '$.data.job_id': NotEmpty(), + '$.data.content': IsInstance(dict), + **{ + f'$.data.content.{region}': IsInstance(list) + for region in tenant.regions + }, + }, depends_on=(job_submit_step,)) + ), name=f'Generating reports for tenant: {tenant.name}')) + + cases = ( + authentication_case, + entities_describe_case, + *executing_scans_cases + ) + for case in cases: + case.execute() + if filename: + filename.parent.mkdir(parents=True, exist_ok=True) + filename = str(filename) + write_cases(list(cases), filename) + + +if __name__ == '__main__': + main(**vars(build_parser().parse_args())) diff --git a/tests/smoke/rules_management.py b/tests/smoke/rules_management.py new file mode 100644 index 000000000..cdffaf11c --- /dev/null +++ b/tests/smoke/rules_management.py @@ -0,0 +1,272 @@ +import dataclasses +import operator +import os +from typing import Optional + +from commons import Step, Equal, Case, write_cases, Empty, NotEmpty, WaitUntil, \ + IsInstance, Contains, Len, True_, False_ + +SMOKE_CAAS_USERNAME = os.getenv('SMOKE_CAAS_USERNAME') +SMOKE_CAAS_PASSWORD = os.getenv('SMOKE_CAAS_PASSWORD') +SMOKE_CAAS_CUSTOMER = os.getenv('SMOKE_CAAS_CUSTOMER') +SMOKE_CAAS_API_LINK = os.getenv( + 'SMOKE_CAAS_API_LINK') or 'http://0.0.0.0:8000/caas' + +assert (SMOKE_CAAS_USERNAME and SMOKE_CAAS_PASSWORD and + SMOKE_CAAS_CUSTOMER and SMOKE_CAAS_API_LINK), \ + 'username, password, customer and api link must be provided' + + +@dataclasses.dataclass(repr=False) +class Source: + pid: str + ref: str + url: str + prefix: str + cloud: str + secret: Optional[str] + + +def get_source(cloud: str) -> Optional[Source]: + cloud = cloud.upper() + assert cloud in ['AWS', 'AZURE', 'GCP'] + secret = os.getenv(f'SMOKE_CAAS_{cloud}_RULE_SOURCE_SECRET') + pid = os.getenv(f'SMOKE_CAAS_{cloud}_RULE_SOURCE_PID') + ref = os.getenv(f'SMOKE_CAAS_{cloud}_RULE_SOURCE_REF') or 'main' + url = os.getenv( + f'SMOKE_CAAS_{cloud}_RULE_SOURCE_URL') or 'https://api.github.com' + prefix = os.getenv(f'SMOKE_CAAS_{cloud}_RULE_SOURCE_PREFIX') or 'policies/' + if not pid: + return + return Source(pid=pid, ref=ref, url=url, prefix=prefix, cloud=cloud, + secret=secret) + + +authentication_case = Case(steps=( + Step(f'c7n configure --api_link {SMOKE_CAAS_API_LINK} --json', { + '$.message': Equal('Great! The c7n tool api_link has been configured.') + }), + Step(f'c7n login -u {SMOKE_CAAS_USERNAME} -p {SMOKE_CAAS_PASSWORD} --json', + { + '$.message': Equal( + f'Great! The c7n tool access token has been saved.') + }), +), name='Authentication') + + +def case_for_source(source: Source) -> Case: + s = source + rs_add_step = Step( + f'c7n rulesource add --git_project_id {s.pid} --git_url {s.url} --git_ref {s.ref} --git_rules_prefix {s.prefix} --description {s.cloud}' + ( + '' if not s.secret else f'--git_access_secret {s.secret}'), { + '$.items[0].customer': Equal(SMOKE_CAAS_CUSTOMER), + '$.items[0].git_project_id': Equal(s.pid), + '$.items[0].git_url': Equal(s.url), + '$.items[0].git_ref': Equal(s.ref), + '$.items[0].description': Equal(s.cloud), + '$.items[0].type': Equal('GITHUB') | Equal('GITLAB') + }) + rs_delete_step = Step( + f'c7n rulesource delete --rule_source_id $.[0].items[0].id', { + '$.message': Equal('Request is successful. No content returned') + }, depends_on=(rs_add_step,)) + + rule_update_step = Step(f'c7n rule update -rsid $.[0].items[0].id', { + '$.items[0].status': Equal('Rule update event has been submitted'), + '$.items[0].customer': Equal(SMOKE_CAAS_CUSTOMER), + '$.items[0].git_project_id': Equal(s.pid), + }, depends_on=(rs_add_step,)) + wait_rule_update_step = WaitUntil( + 'c7n rulesource describe -rsid $.[0].items[0].id', { + '$.items[0].id': NotEmpty(), + '$.items[0].customer': Equal(SMOKE_CAAS_CUSTOMER), + '$.items[0].git_project_id': Equal(s.pid), + '$.items[0].latest_sync.current_status': Equal('SYNCED') + }, break_if={'$.message': NotEmpty()}, depends_on=(rs_add_step,), + sleep=5 + ) + rule_describe_step = Step(f'c7n rule describe -l 1 -c {s.cloud}', { + '$.next_token': NotEmpty(), + '$.items': Len(operator.eq, 1), + '$.items[0].name': NotEmpty(), + '$.items[0].cloud': Equal(s.cloud), + '$.items[0].description': NotEmpty(), + '$.items[0].branch': Equal(s.ref), + '$.items[0].project': Equal(s.pid), + '$.items[0].customer': Equal(SMOKE_CAAS_CUSTOMER) + }) + rule_describe_concrete_step = Step( + 'c7n rule describe -r $.[0].items[0].name', { + '$.items[0].name': NotEmpty(), + '$.items[0].cloud': Equal(s.cloud), + '$.items[0].description': NotEmpty(), + '$.items[0].branch': Equal(s.ref), + '$.items[0].project': Equal(s.pid), + '$.items[0].customer': Equal(SMOKE_CAAS_CUSTOMER) + }, depends_on=(rule_describe_step,)) + rule_delete_concrete_step = Step('c7n rule delete -r $.[0].items[0].name', + { + '$.message': Equal( + 'Request is successful. No content returned') + }, depends_on=(rule_describe_step,)) + rule_describe_concrete_not_found_step = Step( + 'c7n rule describe -r $.[0].items[0].name', { + '$.items': Empty() + }, depends_on=(rule_describe_step,)) + + rule_describe_2_step = Step(f'c7n rule describe -l 2 -c {s.cloud}', { + '$.next_token': NotEmpty(), + '$.items': Len(operator.eq, 2), + '$.items[1].name': NotEmpty(), + '$.items[1].cloud': Equal(s.cloud), + '$.items[1].description': NotEmpty(), + '$.items[1].branch': Equal(s.ref), + '$.items[1].project': Equal(s.pid), + '$.items[1].customer': Equal(SMOKE_CAAS_CUSTOMER) + }) + + ruleset_add_step_1 = Step( + f'c7n ruleset add -n SMOKE -v 1 -c {s.cloud} -pid {s.pid} -gr {s.ref} -act', + { + '$.items[0].customer': Equal(SMOKE_CAAS_CUSTOMER), + '$.items[0].name': Equal('SMOKE'), + '$.items[0].version': Equal('1.0'), + '$.items[0].cloud': Equal(s.cloud), + "$.items[0].rules_number": IsInstance(int), + '$.items[0].event_driven': False_(), + '$.items[0].active': True_(), + '$.items[0].code': Equal('READY_TO_SCAN'), + '$.items[0].license_keys': Empty(), + '$.items[0].license_manager_id': Empty(), + '$.items[0].licensed': False_() + }) + ruleset_add_step_2 = Step( + f'c7n ruleset add -n SMOKE -v 2 -c {s.cloud} -act', { + '$.items[0].customer': Equal(SMOKE_CAAS_CUSTOMER), + '$.items[0].name': Equal('SMOKE'), + '$.items[0].version': Equal('2.0'), + '$.items[0].cloud': Equal(s.cloud), + "$.items[0].rules_number": IsInstance(int), + '$.items[0].event_driven': False_(), + '$.items[0].active': True_(), + '$.items[0].code': Equal('READY_TO_SCAN'), + '$.items[0].license_keys': Empty(), + '$.items[0].license_manager_id': Empty(), + '$.items[0].licensed': False_() + }) + ruleset_add_step_3 = Step( + f'c7n ruleset add -n SMOKE -v 3 -c {s.cloud} -act --standard HIPAA', { + '$.items[0].customer': Equal(SMOKE_CAAS_CUSTOMER), + '$.items[0].name': Equal('SMOKE'), + '$.items[0].version': Equal('3.0'), + '$.items[0].cloud': Equal(s.cloud), + "$.items[0].rules_number": IsInstance(int), + '$.items[0].event_driven': False_(), + '$.items[0].active': True_(), + '$.items[0].code': Equal('READY_TO_SCAN'), + '$.items[0].license_keys': Empty(), + '$.items[0].license_manager_id': Empty(), + '$.items[0].licensed': False_() + }) + ruleset_add_step_4 = Step( + f'c7n ruleset add -n SMOKE -v 4 -c {s.cloud} -act -ss Compute', { + '$.items[0].customer': Equal(SMOKE_CAAS_CUSTOMER), + '$.items[0].name': Equal('SMOKE'), + '$.items[0].version': Equal('4.0'), + '$.items[0].cloud': Equal(s.cloud), + "$.items[0].rules_number": IsInstance(int), + '$.items[0].event_driven': False_(), + '$.items[0].active': True_(), + '$.items[0].code': Equal('READY_TO_SCAN'), + '$.items[0].license_keys': Empty(), + '$.items[0].license_manager_id': Empty(), + '$.items[0].licensed': False_() + }) + ruleset_add_step_5 = Step( + f'c7n ruleset add -n SMOKE -v 5 -c {s.cloud} -act -s High', { + '$.items[0].customer': Equal(SMOKE_CAAS_CUSTOMER), + '$.items[0].name': Equal('SMOKE'), + '$.items[0].version': Equal('5.0'), + '$.items[0].cloud': Equal(s.cloud), + "$.items[0].rules_number": IsInstance(int), + '$.items[0].event_driven': False_(), + '$.items[0].active': True_(), + '$.items[0].code': Equal('READY_TO_SCAN'), + '$.items[0].license_keys': Empty(), + '$.items[0].license_manager_id': Empty(), + '$.items[0].licensed': False_() + }) + ruleset_add_the_same_step = Step( + f'c7n ruleset add -n SMOKE -v 5 -c {s.cloud} -act', { + '$.message': Equal( + f'The ruleset \'SMOKE\' version \'5.0\' for in the customer \'{SMOKE_CAAS_CUSTOMER}\' already exists') + }) + ruleset_add_invalid_step = Step( + f'c7n ruleset add -n SMOKE -v 6 -c {s.cloud} -act -ss invalid', { + '$.message': Contains( + f'Not available service section. Choose from:') + }) + ruleset_delete_step_1 = Step('c7n ruleset delete -n SMOKE -v 1', { + '$.message': Equal('Request is successful. No content returned') + }) + ruleset_delete_step_2 = Step('c7n ruleset delete -n SMOKE -v 2', { + '$.message': Equal('Request is successful. No content returned') + }) + ruleset_delete_step_3 = Step('c7n ruleset delete -n SMOKE -v 3', { + '$.message': Equal('Request is successful. No content returned') + }) + ruleset_delete_step_4 = Step('c7n ruleset delete -n SMOKE -v 4', { + '$.message': Equal('Request is successful. No content returned') + }) + ruleset_delete_step_5 = Step('c7n ruleset delete -n SMOKE -v 5', { + '$.message': Equal('Request is successful. No content returned') + }) + rule_describe_empty = Step(f'c7n rule describe -c {s.cloud}', { + '$.items': Empty() + }) + + return Case(steps=( + rs_add_step, + rule_update_step, + wait_rule_update_step, + rule_describe_step, + rule_describe_concrete_step, + rule_delete_concrete_step, + rule_describe_concrete_not_found_step, + rule_describe_2_step, + + ruleset_add_step_1, + ruleset_add_step_2, + ruleset_add_step_3, + ruleset_add_step_4, + ruleset_add_step_5, + ruleset_add_invalid_step, + ruleset_add_the_same_step, + ruleset_delete_step_1, + ruleset_delete_step_2, + ruleset_delete_step_3, + ruleset_delete_step_4, + ruleset_delete_step_5, + + rs_delete_step, + rule_describe_empty, + ), name=f'Rules management for {source.cloud}') + + +if __name__ == '__main__': + aws_source = get_source('AWS') + azure_source = get_source('AZURE') + google_source = get_source('GCP') + cases = [ + authentication_case, + ] + if aws_source: + cases.append(case_for_source(aws_source)) + if azure_source: + cases.append(case_for_source(azure_source)) + if google_source: + cases.append(case_for_source(google_source)) + + for case in cases: + case.execute() + write_cases(cases, name='smoke-rules-management.md') diff --git a/tests/test_on_prem_api.py b/tests/test_on_prem_api.py new file mode 100644 index 000000000..401a6d884 --- /dev/null +++ b/tests/test_on_prem_api.py @@ -0,0 +1,19 @@ +from onprem.api.app import OnPremApiBuilder, AuthPlugin + + +def test_to_bottle_route(): + assert OnPremApiBuilder.to_bottle_route('/path/{id}') == '/path/' + assert OnPremApiBuilder.to_bottle_route('/{test}') == '/' + assert OnPremApiBuilder.to_bottle_route( + '/path/{one}/{two}/') == '/path///' + assert OnPremApiBuilder.to_bottle_route('/path/') == '/path/' + assert OnPremApiBuilder.to_bottle_route('/path') == '/path' + assert OnPremApiBuilder.to_bottle_route('/path/') == '/path/' + + +def test_get_token_from_header(): + assert AuthPlugin.get_token_from_header('qwerty') == 'qwerty' + assert AuthPlugin.get_token_from_header('') is None + assert AuthPlugin.get_token_from_header('Bearer qwerty') == 'qwerty' + assert AuthPlugin.get_token_from_header('bearer qwerty') == 'qwerty' + assert AuthPlugin.get_token_from_header('bearer qwerty') == 'qwerty' diff --git a/tests/test_rule_meta_updater.py b/tests/test_rule_meta_updater.py new file mode 100644 index 000000000..ff6e1ff25 --- /dev/null +++ b/tests/test_rule_meta_updater.py @@ -0,0 +1,35 @@ +import os +import pytest + +from lambdas.custodian_rule_meta_updater.handler import ( + RuleMetaUpdaterLambdaHandler) + + +@pytest.mark.skipif(os.name == 'nt', reason='The test is for Posix OS') +def test_to_rule_name_posix(): + pattern_to_test = [ + '/root/path/name_metadata.yml', + 'path/name_metadata.yml', + 'name_metadata.yml', + 'name_metadata.yaml', + 'name.yml', + 'name.yaml', + 'name' + ] + for pattern in pattern_to_test: + assert RuleMetaUpdaterLambdaHandler.to_rule_name(pattern) == 'name' + + +@pytest.mark.skipif(os.name == 'posix', reason='The test is for NT OS') +def test_to_rule_name_nt(): + pattern_to_test = [ + 'path\\name_metadata.yml', + 'C:\\Documents\\path\\name_metadata.yml', + 'name_metadata.yml', + 'name_metadata.yaml', + 'name.yml', + 'name.yaml', + 'name' + ] + for pattern in pattern_to_test: + assert RuleMetaUpdaterLambdaHandler.to_rule_name(pattern) == 'name' diff --git a/tests/tests_helpers/__init__.py b/tests/tests_helpers/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/tests_helpers/test_constants.py b/tests/tests_helpers/test_constants.py new file mode 100644 index 000000000..675a47d9b --- /dev/null +++ b/tests/tests_helpers/test_constants.py @@ -0,0 +1,16 @@ +""" +Oh, yeah +""" +from helpers.constants import CustodianEndpoint + + +def test_custodian_endpoint_match(): + assert CustodianEndpoint.match('jobs') == CustodianEndpoint.JOBS + assert CustodianEndpoint.match('jobs/') == CustodianEndpoint.JOBS + assert CustodianEndpoint.match('/jobs/') == CustodianEndpoint.JOBS + assert CustodianEndpoint.match( + '/jobs/{job_id}') == CustodianEndpoint.JOBS_JOB + assert CustodianEndpoint.match( + '/jobs/{job_id}/') == CustodianEndpoint.JOBS_JOB + assert CustodianEndpoint.match( + 'jobs/{job_id}/') == CustodianEndpoint.JOBS_JOB diff --git a/tests/tests_helpers/test_lambda_response.py b/tests/tests_helpers/test_lambda_response.py new file mode 100644 index 000000000..f3ee58fc7 --- /dev/null +++ b/tests/tests_helpers/test_lambda_response.py @@ -0,0 +1,118 @@ +from http import HTTPStatus + +import pytest +from helpers.__version__ import __version__ + +from helpers.lambda_response import LambdaResponse, CustodianException, \ + JsonLambdaResponse, ResponseFactory, build_response + + +def test_ok_lambda_response(): + resp = LambdaResponse( + code=HTTPStatus.OK, + content='

Hello, world!

', + headers={'Content-Type': 'text/html'} + ) + assert resp.code == HTTPStatus.OK + assert resp.ok + assert resp.build() == { + 'statusCode': 200, + 'headers': { + 'Access-Control-Allow-Headers': 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': '*', + 'Accept-Version': __version__, + 'Content-Type': 'text/html' + }, + 'isBase64Encoded': False, + 'body': '

Hello, world!

' + } + + +def test_not_ok_lambda_response(): + resp = LambdaResponse(code=HTTPStatus.NOT_FOUND) + assert not resp.ok + with pytest.raises(CustodianException): + raise resp.exc() + + +def test_too_large_lambda_response(): + resp = JsonLambdaResponse( + code=HTTPStatus.OK, + content={'data': (b'a' * (6291456 - 11)).decode()} + ) # 11 for {"data":""} + with pytest.raises(CustodianException): + resp.build() + resp = JsonLambdaResponse( + code=HTTPStatus.OK, + content={'data': (b'a' * (6291456 - 12)).decode()} + ) + resp.build() + + +def test_json_lambda_response(): + resp = JsonLambdaResponse( + code=HTTPStatus.OK, + content={'str': 'value', 'list': [1, 2, 3]}, + ) + assert resp.build() == { + 'statusCode': 200, + 'headers': { + 'Accept-Version': __version__, + 'Access-Control-Allow-Headers': 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token', + 'Access-Control-Allow-Methods': '*', + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'application/json' + }, + 'isBase64Encoded': False, + 'body': '{"list":[1,2,3],"str":"value"}', + } + + +class TestResponseFactory: + def test_build_items(self): + data = ResponseFactory(HTTPStatus.OK).items([1, 2, 3]).build() + assert data['body'] == '{"items":[1,2,3]}' + data = ResponseFactory(HTTPStatus.OK).items(range(5)).build() + assert data['body'] == '{"items":[0,1,2,3,4]}' + + def test_build_data(self): + data = ResponseFactory(HTTPStatus.OK).data({'key': 'value'}).build() + assert data['body'] == '{"data":{"key":"value"}}' + + def test_build_message(self): + data = ResponseFactory(HTTPStatus.OK).message('hello world').build() + assert data['body'] == '{"message":"hello world"}' + + def test_build_errors(self): + data = ResponseFactory(HTTPStatus.OK).errors( + [{'key': 'value'}]).build() + assert data['body'] == '{"errors":[{"key":"value"}]}' + + def test_build_raw(self): + data = ResponseFactory(HTTPStatus.OK).raw({'token': '123'}).build() + assert data['body'] == '{"token":"123"}' + + def test_build_default(self): + data = ResponseFactory( + HTTPStatus.INSUFFICIENT_STORAGE).default().build() + assert data['body'] == '{"message":"Insufficient Storage"}' + + +def test_build_response(): + assert build_response('hello')['body'] == '{"message":"hello"}' + assert (build_response({'key': 'value'})['body'] == + '{"data":{"key":"value"}}') + assert (build_response([{'key': 'value'}])['body'] == + '{"items":[{"key":"value"}]}') + + def gen(): + yield {'k1': 'v1'} + yield {'k2': 'v2'} + yield {'k3': 'v3'} + + assert (build_response(gen())['body'] == + '{"items":[{"k1":"v1"},{"k2":"v2"},{"k3":"v3"}]}') + + with pytest.raises(CustodianException): + build_response(code=HTTPStatus.NOT_FOUND) diff --git a/tests/tests_helpers/test_reports.py b/tests/tests_helpers/test_reports.py new file mode 100644 index 000000000..d277b012a --- /dev/null +++ b/tests/tests_helpers/test_reports.py @@ -0,0 +1,104 @@ +import operator +from functools import cmp_to_key + +import pytest + +from helpers.reports import severity_cmp, keep_highest, Standard + + +@pytest.fixture +def standards_dict() -> dict: + """ + Generates standards dict + :return: + """ + return { + 'HIPAA': [ + 'v1 (point1,sub-point1,point2)', + 'v2' + ], + 'Cis Controls': [ + '(sub-point1,sub-point2)' + ], + } + + +class TestStandard: + def test_hash_eq(self): + st1 = Standard(name='HIPAA', version='1.0', points={'1.1', '1.2'}) + st2 = Standard(name='HIPAA', version='1.0') + + assert st1 == st2 + assert st1 == ('HIPAA', '1.0') + assert st1 != ('HIPAA', '1.0', {'1.2'}) + assert hash(st1) == hash(st2) + assert hash(st1) == hash(('HIPAA', '1.0')) + + def test_full_name(self): + assert Standard(name='HIPAA', version='1.0').full_name == 'HIPAA 1.0' + assert Standard(name='HIPAA').full_name == 'HIPAA' + + def test_deserialize(self, standards_dict: dict): + standards = Standard.deserialize(standards_dict) + assert isinstance(standards, set) + assert len(standards) == 3 + cis, h1, h2 = sorted(standards, key=operator.attrgetter('full_name')) + assert cis.name == 'Cis Controls' + assert cis.version == 'null' + assert cis.points == {'sub-point1', 'sub-point2'} + assert h1.name == 'HIPAA' + assert h1.version == 'v1' + assert h1.points == {'point1', 'sub-point1', 'point2'} + assert h2.name == 'HIPAA' + assert h2.version == 'v2' + assert h2.points == set() + + def test_deserialize_to_str(self, standards_dict: dict): + standards = Standard.deserialize_to_strs(standards_dict) + assert isinstance(standards, set) + assert len(standards) == 3 + cis, h1, h2 = sorted(standards) + assert cis == 'Cis Controls' + assert h1 == 'HIPAA v1' + assert h2 == 'HIPAA v2' + + +def test_keep_highest(): + a = {1, 2, 3} + b = {5} + keep_highest(a, b) + assert a == {1, 2, 3} + assert b == {5} + + a = {1, 2, 3} + b = {3, 5} + keep_highest(a, b) + assert a == {1, 2} + assert b == {3, 5} + + a = {1, 2, 5} + b = {2} + c = {1, 5, 6, 7} + keep_highest(a, b, c) + assert a == set() + assert b == {2} + assert c == {1, 5, 6, 7} + + +def test_severity_cmp(): + assert severity_cmp('Info', 'Low') < 0 + assert severity_cmp('Medium', 'High') < 0 + + assert severity_cmp('Low', 'Info') > 0 + assert severity_cmp('Medium', 'Low') > 0 + assert severity_cmp('High', 'Medium') > 0 + + assert severity_cmp('Info', 'Info') == 0 + assert severity_cmp('Medium', 'Medium') == 0 + assert severity_cmp('High', 'High') == 0 + + assert severity_cmp('High', 'Not existing') < 0 + assert severity_cmp('Not existing', 'High') > 0 + + assert (sorted(['High', 'Medium', 'Info'], key=cmp_to_key(severity_cmp)) == + ['Info', 'Medium', 'High']) diff --git a/tests/tests_helpers/test_utils.py b/tests/tests_helpers/test_utils.py new file mode 100644 index 000000000..975de7a8f --- /dev/null +++ b/tests/tests_helpers/test_utils.py @@ -0,0 +1,299 @@ +import io +import json +from itertools import islice + +import pytest + +from helpers import (deep_get, deep_set, title_keys, setdefault, filter_dict, + hashable, urljoin, skip_indexes, peek, without_duplicates, + MultipleCursorsWithOneLimitIterator, catchdefault, + batches, dereference_json, NextToken, iter_values, + flip_dict) + + +@pytest.fixture +def dictionary() -> dict: + """ + Generates some more or less complex dictionary to test related method on + :return: + """ + return { + 'one': 'two', + 3: { + 'four': 'five' + }, + 'six': { + 'seven': {'eight': 'nine'} + }, + 'ten': [{ + 'eleven': 'twelve' + }] + } + + +def test_deep_get(dictionary): + assert deep_get(dictionary, ('one',)) == 'two' + assert deep_get(dictionary, (3, 'four')) == 'five' + assert deep_get(dictionary, ('six', 'seven', 'eight')) == 'nine' + assert deep_get(dictionary, ('six', 'seven', 'ten')) is None + + +def test_deep_set(): + a = {} + deep_set(a, ('one', 'two', 'three'), 'candy') + deep_set(a, (1, 2, 3), 'candy') + assert a['one']['two']['three'] == 'candy' + assert a[1][2][3] == 'candy' + + +def test_batches(): + a = [1, 2, 3, 4, 5, ] + it = batches(a, 2) + assert next(it) == [1, 2] + assert next(it) == [3, 4] + assert next(it) == [5] + with pytest.raises(StopIteration): + next(it) + + +def test_title_keys(dictionary): + assert title_keys(dictionary) == { + 'One': 'two', + 3: { + 'Four': 'five' + }, + 'Six': { + 'Seven': {'Eight': 'nine'} + }, + 'Ten': [{ + 'Eleven': 'twelve' + }] + } + + +def test_setdefault(): + class Test: + pass + + instance = Test() + assert not hasattr(instance, 'attr') + setdefault(instance, 'attr', 1) + assert instance.attr == 1 + setdefault(instance, 'attr', 2) + assert instance.attr == 1 + + +def test_filter_dict(dictionary): + assert filter_dict(dictionary, ()) == dictionary + assert filter_dict(dictionary, ('one', 3)) == { + 'one': 'two', + 3: { + 'four': 'five' + } + } + + +def test_hashable(dictionary): + assert hashable(dictionary) + d = {'q': [1, 3, 5, {'h': 34, 'c': ['1', '2']}], 'v': {1: [1, 2, 3]}} + d1 = {'v': {1: [1, 2, 3]}, 'q': [1, 3, 5, {'h': 34, 'c': ['1', '2']}]} + assert hash(hashable(d)) == hash(hashable(d1)) + d = {'q': [1, 2, 3]} + d1 = {'Q': [1, 2, 3]} + assert hash(hashable(d)) != hash(hashable(d1)) + + +def test_urljoin(): + assert urljoin('one', 'two', 'three') == 'one/two/three' + assert urljoin('/one/', '/two/', '/three/') == 'one/two/three' + assert urljoin('/one', 'two', 'three/') == 'one/two/three' + assert urljoin('/one/') == 'one' + + +def test_skip_indexes(): + a = [0, 1, 2, 3, 4, 5, 6, 7] + assert list(skip_indexes(a, {0, 2, 4})) == [1, 3, 5, 6, 7] + assert list(skip_indexes(a, {-1, 0, 7, 8})) == [1, 2, 3, 4, 5, 6] + + +def test_peek(): + def make_it(data, chunk_size): + while True: + block = data.read(chunk_size) + if not block: + break + yield block + + it = make_it(io.BytesIO(b'some data'), 4) + + start, it = peek(it) + assert start == b'some' + assert list(it) == [b'some', b' dat', b'a'] + + +def test_without_duplicates(): + assert [1, 2, 3, 4] == list(without_duplicates([1, 1, 2, 1, 1, 3, 4, 4])) + assert [1] == list(without_duplicates([1, 1, 1, 1, 1, 1, 1])) + assert [] == list(without_duplicates([])) + + +def test_catchdefault(): + def method(): + raise Exception + + def method1(): + return 'value' + + assert catchdefault(method) is None + assert catchdefault(method, 'default') == 'default' + assert catchdefault(method1) == 'value' + assert catchdefault(method1, 'default') == 'value' + + +def test_multiple_cursors_with_one_limit_it(): + def build_f(it): + """Builds a factory that builds a cursor""" + + def f(lim): + return islice(it, lim) + + return f + + it1 = iter(range(3)) + it2 = iter(range(2)) + it3 = iter(range(3)) + f1 = build_f(it1) + f2 = build_f(it2) + f3 = build_f(it3) + it = MultipleCursorsWithOneLimitIterator(2, f1, f2, f3) + assert list(it) == [0, 1] + assert list(it1) == [2, ] + assert list(it2) == [0, 1] + assert list(it3) == [0, 1, 2] + + it1 = iter(range(3)) + it2 = iter(range(2)) + it3 = iter(range(3)) + f1 = build_f(it1) + f2 = build_f(it2) + f3 = build_f(it3) + it = MultipleCursorsWithOneLimitIterator(0, f1, f2, f3) + assert list(it) == [] + assert list(it1) == [0, 1, 2] + assert list(it2) == [0, 1] + assert list(it3) == [0, 1, 2] + + it1 = iter(range(3)) + it2 = iter(range(2)) + it3 = iter(range(3)) + f1 = build_f(it1) + f2 = build_f(it2) + f3 = build_f(it3) + it = MultipleCursorsWithOneLimitIterator(None, f1, f2, f3) + assert list(it) == [0, 1, 2, 0, 1, 0, 1, 2] + assert list(it1) == [] + assert list(it2) == [] + assert list(it3) == [] + + it1 = iter(range(3)) + it2 = iter(range(2)) + it3 = iter(range(3)) + f1 = build_f(it1) + f2 = build_f(it2) + f3 = build_f(it3) + it = MultipleCursorsWithOneLimitIterator(5, f1, f2, f3) + assert list(it) == [0, 1, 2, 0, 1] + assert list(it1) == [] + assert list(it2) == [] + assert list(it3) == [0, 1, 2] + + +def test_dereference_json(): + obj = { + 'key1': { + 'a': [1, 2, 3], + 'b': {'$ref': '#/key3'} + }, + 'key2': { + '$ref': '#/key1', + }, + 'key3': 10 + } + dereference_json(obj) + # obj = jsonref.replace_refs(obj, lazy_load=False) + assert obj == { + 'key1': {'a': [1, 2, 3], 'b': 10}, + 'key2': {'a': [1, 2, 3], 'b': 10}, + 'key3': 10 + } + + obj = { + 'key0': {'$ref': '#/key1/value'}, + 'key1': {'value': 1, 'test': {'$ref': '#/key2'}}, + 'key2': [1, 2, 3], + 'key4': [1, 2, {'test': {'$ref': '#/key0'}}] + } + dereference_json(obj) + # obj = jsonref.replace_refs(obj, lazy_load=False) + assert obj == { + 'key0': 1, + 'key1': {'value': 1, 'test': [1, 2, 3]}, + 'key2': [1, 2, 3], + 'key4': [1, 2, {'test': 1}] + } + + +class TestNextToken: + def test_serialize_deserialize(self): + lak = {'key': 'value'} + assert NextToken.deserialize(NextToken(lak).serialize()).value == lak + lak = 5 + assert NextToken.deserialize(NextToken(lak).serialize()).value == lak + + def test_bool(self): + assert NextToken({'key': 'value'}) + assert NextToken(5) + assert not NextToken({}) + assert not NextToken(None) + assert not NextToken(0) + + def test_json_dumps(self): + def default(obj): + if hasattr(obj, '__json__'): + return obj.__json__() + raise TypeError + + nt = NextToken({'key': 'value'}) + assert json.dumps({'next_token': nt}, default=default) + + +def test_iter_values(): + item = { + 'key1': [1, 2, 3], + 'key2': { + 'key3': 4 + } + } + gen = iter_values(item) + try: + real = next(gen) + while True: + real = gen.send(real**2) + except StopIteration: + pass + assert item == { + 'key1': [1, 4, 9], + 'key2': { + 'key3': 16 + } + } + + +def test_flip_dict(): + def gen_mirrored_dicts() -> tuple[dict, dict]: + return ({f'{i}': i for i in range(1000)}, + {i: f'{i}' for i in range(1000)}) + d1, d2 = gen_mirrored_dicts() + assert d1 != d2 + flip_dict(d1) + assert d1 == d2 diff --git a/tests/tests_metrics/README.md b/tests/tests_metrics/README.md new file mode 100644 index 000000000..493b9766e --- /dev/null +++ b/tests/tests_metrics/README.md @@ -0,0 +1,9 @@ +### Folder structure: + +* expected_metrics - folder with jsons to compare results with +* mock_files - folder with jsons that mock some s3 files like metadata, statistics, old metrics to compare with + +### How to run via terminal (on windows it works, hope it works for unix too): + +1. Change folder to the `custodian-as-a-service` +2. Run `pytest tests/tests_metrics` diff --git a/tests/tests_metrics/__init__.py b/tests/tests_metrics/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/tests_metrics/expected_metrics/aws_tenant_group_finops.json b/tests/tests_metrics/expected_metrics/aws_tenant_group_finops.json new file mode 100644 index 000000000..259cac5ff --- /dev/null +++ b/tests/tests_metrics/expected_metrics/aws_tenant_group_finops.json @@ -0,0 +1,401 @@ +[ + { + "service_section": "2x0cxc7m96usyk1nyh0qijah90j", + "rules_data": [ + { + "rule": "Elasticache is not using last generation nodes\n", + "service": "Amazon ElastiCache", + "category": "ussts4fje7ot4snjfqvofxv41py", + "severity": "High", + "resource_type": "Amazon ElastiCache", + "regions_data": { + "eu-west-1": { + "total_violated_resources": { + "value": 1 + } + } + } + } + ] + }, + { + "service_section": "5tnyrkg0f6mrmdylfra90ycpp59", + "rules_data": [ + { + "rule": "A WAF global web ACL does not have at least one rule or rule group\n", + "service": "AWS Web Application Firewall", + "category": "38o2xprmm395zaws6ywy3iskgjv", + "severity": "Low", + "resource_type": "AWS Web Application Firewall", + "regions_data": { + "multiregion": { + "total_violated_resources": { + "value": 1 + } + } + } + } + ] + }, + { + "service_section": "6vppuhuabccjoidbybm5bi7jydm", + "rules_data": [ + { + "rule": "Cache is not enabled for API gateway\n", + "service": "Amazon API Gateway", + "category": "3fv8z82txe37u0wkinnaf5y9bqd", + "severity": "Info", + "resource_type": "Amazon API Gateway", + "regions_data": { + "eu-west-1": { + "total_violated_resources": { + "value": 1 + } + } + } + } + ] + }, + { + "service_section": "76wbaoteragi5x6crz5tsavzzl9", + "rules_data": [ + { + "rule": "Unused Workspaces instances are not removed\n", + "service": "Amazon WorkSpaces Family", + "category": "humxkzg85wj6rbsep3b6cjp666d", + "severity": "Info", + "resource_type": "Amazon WorkSpaces Family", + "regions_data": { + "eu-west-1": { + "total_violated_resources": { + "value": 1 + } + } + } + } + ] + }, + { + "service_section": "8nho78g3z4ufc36fri7g92jgbnx", + "rules_data": [ + { + "rule": "Unused EBS volumes exist\n", + "service": "Amazon Elastic Block Store", + "category": "6mxbruz13bve7xhpye1rumalgh0", + "severity": "High", + "resource_type": "Amazon Elastic Block Store", + "regions_data": { + "eu-west-1": { + "total_violated_resources": { + "value": 1 + } + } + } + } + ] + }, + { + "service_section": "8wiiuo42ymg8je2yv9gs871lq49", + "rules_data": [ + { + "rule": "CloudWatch Log Group does not have retention period set correctly\n", + "service": "Amazon CloudWatch", + "category": "fvh7mvwv3ocnh3tooxhx88p9eyb", + "severity": "Info", + "resource_type": "Amazon CloudWatch", + "regions_data": { + "eu-west-1": { + "total_violated_resources": { + "value": 1 + } + } + } + } + ] + }, + { + "service_section": "ben419975ytq69y59nugba0iqur", + "rules_data": [ + { + "rule": "Stopped EC2 instances are not removed after a specified time period\n", + "service": "Amazon EC2", + "category": "ws9rlv94kkfbznkhk91xfz5gupg", + "severity": "High", + "resource_type": "Amazon EC2", + "regions_data": { + "eu-west-1": { + "total_violated_resources": { + "value": 1 + } + } + } + } + ] + }, + { + "service_section": "c6ycxa6eujsdo2z5yd7pxa23msc", + "rules_data": [ + { + "rule": "Unused Secrets Manager secrets are not removed\n", + "service": "AWS Secrets Manager", + "category": "w82m04p01furm03has0vtsjvhq5", + "severity": "Low", + "resource_type": "AWS Secrets Manager", + "regions_data": { + "eu-west-1": { + "total_violated_resources": { + "value": 1 + } + } + } + } + ] + }, + { + "service_section": "glmhyqyilr5rezqfw47ssrt6ts5", + "rules_data": [ + { + "rule": "S3 Bucket life cycle is not configured\n", + "service": "Amazon S3", + "category": "e9o3h34up0j4gm4z6f3q18j1ob5", + "severity": "Medium", + "resource_type": "Amazon S3", + "regions_data": { + "multiregion": { + "total_violated_resources": { + "value": 1 + } + } + } + } + ] + }, + { + "service_section": "hi5cvzdpiby25rpypmr0m425tk5", + "rules_data": [ + { + "rule": "ECR repository does not have any lifecycle policies configured\n", + "service": "Amazon Elastic Container Registry", + "category": "dy2avk3wx7qob8ihe48krt8mbzy", + "severity": "High", + "resource_type": "Amazon Elastic Container Registry", + "regions_data": { + "eu-west-1": { + "total_violated_resources": { + "value": 1 + } + } + } + } + ] + }, + { + "service_section": "mlwc84t36ge5orrsnpi5fxlv5i1", + "rules_data": [ + { + "rule": "A WAF global rule does not have at least one condition\n", + "service": "AWS Web Application Firewall", + "category": "do3zf09onq25snm5fhgbkmjxqvo", + "severity": "Low", + "resource_type": "AWS Web Application Firewall", + "regions_data": { + "multiregion": { + "total_violated_resources": { + "value": 1 + } + } + } + } + ] + }, + { + "service_section": "nzzaavuevjdg15p85vo1cdrn4rh", + "rules_data": [ + { + "rule": "EBS volumes attached to an EC2 instance is not marked for deletion upon instance termination\n", + "service": "Amazon EC2", + "category": "pb8dug1vbgms1bsogvkoet6dfmj", + "severity": "Info", + "resource_type": "Amazon EC2", + "regions_data": { + "eu-west-1": { + "total_violated_resources": { + "value": 1 + } + } + } + } + ] + }, + { + "service_section": "ssu2my4sijhl8r2drtrnc45vlso", + "rules_data": [ + { + "rule": "A WAF Classic Regional web ACL does not have at least one rule or rule group\n", + "service": "AWS Web Application Firewall", + "category": "f3j3hm6uy2mwlicmgy4nzfino81", + "severity": "Low", + "resource_type": "AWS Web Application Firewall", + "regions_data": { + "eu-west-1": { + "total_violated_resources": { + "value": 1 + } + } + } + } + ] + }, + { + "service_section": "tsybzlpmqqiyp6uoglr24q8m7qw", + "rules_data": [ + { + "rule": "Amazon Glue Job with disabled autoscaling\n", + "service": "AWS Glue", + "category": "v9yrmdbj0okc3g3llspatx83fs6", + "severity": "Info", + "resource_type": "AWS Glue", + "regions_data": { + "eu-west-1": { + "total_violated_resources": { + "value": 1 + } + } + } + } + ] + }, + { + "service_section": "u5lkplud63wbycn8dai4yp6eule", + "rules_data": [ + { + "rule": "EBS volumes are type of gp2 insted of gp3\n", + "service": "Amazon Elastic Block Store", + "category": "kbz6sny25zf479oe32t46h65v6j", + "severity": "Info", + "resource_type": "Amazon Elastic Block Store", + "regions_data": { + "eu-west-1": { + "total_violated_resources": { + "value": 1 + } + } + } + } + ] + }, + { + "service_section": "v1d620by2z5gtqr6hyl9mry8434", + "rules_data": [ + { + "rule": "Amazon Backup plan has a non-compliant lifecycle configuration\n", + "service": "AWS Backup", + "category": "aazwt7bgogn80j89fcbb7wzz3g8", + "severity": "High", + "resource_type": "AWS Backup", + "regions_data": { + "eu-west-1": { + "total_violated_resources": { + "value": 1 + } + } + } + } + ] + }, + { + "service_section": "vgxztga41r7wf5fprb3g605guoe", + "rules_data": [ + { + "rule": "Unused Amazon EFS file systems\n", + "service": "Amazon Elastic File System", + "category": "tu5ci8uafga0kkoza5zdfswkdjj", + "severity": "Medium", + "resource_type": "Amazon Elastic File System", + "regions_data": { + "eu-west-1": { + "total_violated_resources": { + "value": 1 + } + } + } + } + ] + }, + { + "service_section": "vr50yclwkwqn4oudk25t3dpiapm", + "rules_data": [ + { + "rule": "Unused EC2 EIPs exist\n", + "service": "Amazon EC2", + "category": "aap8cs308j47q24kkuq4ka6xa4a", + "severity": "Medium", + "resource_type": "Amazon EC2", + "regions_data": { + "eu-west-1": { + "total_violated_resources": { + "value": 1 + } + } + } + } + ] + }, + { + "service_section": "w9oom4qfwd24jvt97u5tpowjher", + "rules_data": [ + { + "rule": "DynamoDB table Auto Scaling or On-Demand is not enabled on DynamoDB tables \n", + "service": "Amazon DynamoDB", + "category": "oq6d7j8b9jiab1uxaf148wa5i7t", + "severity": "Low", + "resource_type": "Amazon DynamoDB", + "regions_data": { + "eu-west-1": { + "total_violated_resources": { + "value": 1 + } + } + } + } + ] + }, + { + "service_section": "wnz1h0q54tsieuoc6qtqtndc13s", + "rules_data": [ + { + "rule": "S3 buckets with versioning enabled do not have lifecycle policies configured\n", + "service": "Amazon S3", + "category": "ww1g038jqh0ing5b1afcadxfh3o", + "severity": "Medium", + "resource_type": "Amazon S3", + "regions_data": { + "multiregion": { + "total_violated_resources": { + "value": 1 + } + } + } + } + ] + }, + { + "service_section": "xzv6g0qaps6q304tj2ka1i56fz6", + "rules_data": [ + { + "rule": "EBS Snapshots older than 30 days\n", + "service": "Amazon Elastic Block Store", + "category": "ic9dy1ya7o2fkr558ndpulloqgm", + "severity": "Medium", + "resource_type": "Amazon Elastic Block Store", + "regions_data": { + "eu-west-1": { + "total_violated_resources": { + "value": 1 + } + } + } + } + ] + } +] \ No newline at end of file diff --git a/tests/tests_metrics/expected_metrics/aws_tenant_resources.json b/tests/tests_metrics/expected_metrics/aws_tenant_resources.json new file mode 100644 index 000000000..86465d180 --- /dev/null +++ b/tests/tests_metrics/expected_metrics/aws_tenant_resources.json @@ -0,0 +1 @@ +[{"policy": "ecc-aws-001", "resource_type": "AWS Identity and Access Management", "description": "Multi-factor authentication (MFA) is not enabled for all IAM users that have console password\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"Arn": "ad321729-0743-431f-8502-6799379ac29e"}]}}}, {"policy": "ecc-aws-002", "resource_type": "AWS Identity and Access Management", "description": "Access keys are not rotated every 90 days or less\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"Arn": "6aaea08c-3cb0-482e-aaad-c2115df98660"}]}}}, {"policy": "ecc-aws-003", "resource_type": "Amazon Virtual Private Cloud", "description": "VPC flow logging is not enabled in all VPCs\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"VpcId": "cd09e5a7-4e9d-43a3-bf0c-501ec2c86350", "OwnerId": "ca97d147-2529-4471-bbac-71dc7aa0237f"}]}}}, {"policy": "ecc-aws-004", "resource_type": "Amazon S3", "description": "S3 Bucket Policy allows HTTP requests\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"Name": "1793de02-f3ce-4f8a-afed-587b5461027c"}]}}}, {"policy": "ecc-aws-005", "resource_type": "Amazon Relational Database Service", "description": "RDS is open to a large scope\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "6018766b-090e-4dd6-a1af-ead984e34921"}]}}}, {"policy": "ecc-aws-006", "resource_type": "Amazon Relational Database Service", "description": "RDS retention policy is less than 7 days\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "a67221b3-48ca-4789-9ebb-a407b836f629"}]}}}, {"policy": "ecc-aws-007", "resource_type": "Amazon Relational Database Service", "description": "RDS instances do not have multi-availability zone enabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "898ea4b1-f070-4dab-b8a8-768cdfcee359"}]}}}, {"policy": "ecc-aws-008", "resource_type": "AWS Identity and Access Management", "description": "SSL/TLS certificates expire in less than a month\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"Arn": "f12a9539-f6f2-4403-b967-cf278accbe19"}]}}}, {"policy": "ecc-aws-009", "resource_type": "AWS Identity and Access Management", "description": "SSL/TLS certificates expire in less than a week\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"Arn": "58fd1762-0231-4fa6-882b-f41f62f0c387"}]}}}, {"policy": "ecc-aws-010", "resource_type": "Amazon Elastic Load Balancing", "description": "Application or Network Load balancer SSL certificate expire in less than a week\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"LoadBalancerArn": "f919f844-ec93-4f47-b20c-b81b391ea16f"}]}}}, {"policy": "ecc-aws-011", "resource_type": "Amazon Elastic Load Balancing", "description": "Application or Network Load balancer SSL certificate expire in less than a month\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"LoadBalancerArn": "d90f56db-0b6f-42ad-b0d1-edc2943ae761"}]}}}, {"policy": "ecc-aws-012", "resource_type": "Amazon CloudFront", "description": "Cloudfront Distribution uses weak ciphers\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"ARN": "f9318c01-1fc5-4a27-acd3-bfb1e3398268"}]}}}, {"policy": "ecc-aws-013", "resource_type": "Amazon Elastic Load Balancing", "description": "Classic Load Balancer uses weak ciphers\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{}]}}}, {"policy": "ecc-aws-014", "resource_type": "Amazon Elastic Load Balancing", "description": "Classic Load Balancer listeners are not blocking connection requests over http\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{}]}}}, {"policy": "ecc-aws-015", "resource_type": "AWS Account", "description": "Virtual MFA is not enabled for the \"root\" account\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"account_id": "21fc357d-4ebb-4078-bd90-95f958aa5355", "account_name": "633d311f-b202-4afc-acba-b1f1ff91753b", "c7n-service:region": "multiregion"}]}}}, {"policy": "ecc-aws-016", "resource_type": "AWS Account", "description": "Hardware MFA is not enabled for the 'root' account\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"c7n-service:region": "multiregion"}]}}}, {"policy": "ecc-aws-017", "resource_type": "AWS Identity and Access Management", "description": "Credentials unused for 45 days or more are not disabled\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"Arn": "59514406-4995-421a-94d6-ab0ecf7c436e"}]}}}, {"policy": "ecc-aws-018", "resource_type": "AWS Identity and Access Management", "description": "IAM Users receive permissions not only through groups\n", "severity": "Medium", "regions_data": {"multiregion": {"resources": [{"Arn": "af948c20-2f3a-4f0c-87dc-5df5e1b3dc09"}]}}}, {"policy": "ecc-aws-019", "resource_type": "AWS Account", "description": "IAM password policy does not prevent password reuse\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"c7n-service:region": "multiregion"}]}}}, {"policy": "ecc-aws-020", "resource_type": "Amazon EC2", "description": "Instances without any tags\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"InstanceId": "e568ad44-6c39-42f2-b234-b99c8751a054", "OwnerId": "eff9d7a5-6e06-4fc6-85b7-43e9bbb7c08f"}]}}}, {"policy": "ecc-aws-021", "resource_type": "Amazon Elastic Block Store", "description": "EBS Volumes without recent snapshots\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"VolumeId": "fecebf61-2ca8-4cd6-8538-2fc400ab7d4b"}]}}}, {"policy": "ecc-aws-022", "resource_type": "Amazon Elastic Block Store", "description": "EBS Snapshots older than 30 days\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"SnapshotId": "e10ba805-c214-4641-a293-e817ebc7083a"}]}}}, {"policy": "ecc-aws-023", "resource_type": "Amazon Elastic Load Balancing", "description": "Classic Load Balancer Access Logging is disabled \n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{}]}}}, {"policy": "ecc-aws-024", "resource_type": "Amazon Simple Queue Service", "description": "SQS encryption is disabled\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"QueueArn": "658c5ef1-ae43-4b88-942c-e10e7301ca8d"}]}}}, {"policy": "ecc-aws-025", "resource_type": "Amazon EC2", "description": "Instances without termination protection\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"InstanceId": "6776b493-db92-45db-b5ae-159694afaefc", "OwnerId": "f8c741d1-5e64-41c7-a5d2-975934b2fcd1"}]}}}, {"policy": "ecc-aws-026", "resource_type": "Amazon Relational Database Service", "description": "RDS instances without automated backups\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "aaa0039b-c18f-46d7-bedb-f5214df05880"}]}}}, {"policy": "ecc-aws-027", "resource_type": "Amazon EC2", "description": "Security groups do not prevent all incoming traffic from 0-65535\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"GroupId": "3aebf18f-1c51-465e-9d67-23aa4973c939", "VpcId": "1155b1dc-e322-46bc-a608-602e5d1eab91", "OwnerId": "3361fe15-acea-43b4-b04e-53928bcf8fb5"}]}}}, {"policy": "ecc-aws-028", "resource_type": "Amazon EC2", "description": "Security group rule allows internet traffic to DNS port (53)\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"GroupId": "a1b29424-15f5-4aca-aecb-0a07ce54a345", "VpcId": "eed1400e-e66b-49cf-a7aa-b23477221267", "OwnerId": "bffddd9a-7dd4-412c-97e0-0dc695bcd852"}]}}}, {"policy": "ecc-aws-029", "resource_type": "Amazon EC2", "description": "Security group rule allows internet traffic to FTP port (21)\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"GroupId": "ca53efbc-6c17-4f9f-b957-1873938cd700", "VpcId": "6ce306de-9d62-4df8-8fe6-cb982c6a62e4", "OwnerId": "edbf55a6-1839-4839-be36-62855a2b6860"}]}}}, {"policy": "ecc-aws-030", "resource_type": "Amazon EC2", "description": "Security group rule allows internet traffic to HTTP port (80)\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"GroupId": "97e5e150-da6a-4eef-99d6-c8d80ae27fdd", "VpcId": "424a1933-c1cd-4690-8be7-03cb0552b78d", "OwnerId": "056b7394-70f0-46b7-8217-d52d523c58aa"}]}}}, {"policy": "ecc-aws-031", "resource_type": "Amazon EC2", "description": "Security group rule allows internet traffic to Microsoft-DS port (445)\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"GroupId": "df102f56-6ff5-4790-b8e2-cce42c310bc8", "VpcId": "11083019-654a-4f23-975f-834cbad2ee7e", "OwnerId": "87f8e252-a750-4fef-a990-a6a9a051eb1d"}]}}}, {"policy": "ecc-aws-032", "resource_type": "Amazon EC2", "description": "Security group rule allows internet traffic to MongoDB port (27017)\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"GroupId": "337d136c-ea8a-439a-b66b-75556a38d837", "VpcId": "c74144fb-ed7e-419c-a4cb-39c30a20d74a", "OwnerId": "6d92b0e4-1918-46b5-a0c3-e5d63b1e6455"}]}}}, {"policy": "ecc-aws-033", "resource_type": "Amazon EC2", "description": "Security group rule allows internet traffic to MySQL DB port (3306)\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"GroupId": "3b4e878e-af6e-4624-93c0-26234dff53c1", "VpcId": "e63ecc79-83c2-4f11-b240-8d66439ecf87", "OwnerId": "9b0d6528-c8e0-4126-ae07-59a54521e0a7"}]}}}, {"policy": "ecc-aws-034", "resource_type": "Amazon EC2", "description": "Security group rule allows internet traffic to NetBIOS-SSN port (139)\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"GroupId": "389d2ba7-d415-4471-a590-b715670ce34f", "VpcId": "2f671bca-537d-4fb0-8013-c76ef0ba55d6", "OwnerId": "47435021-14d4-4cd2-842c-12024f52708a"}]}}}, {"policy": "ecc-aws-035", "resource_type": "Amazon EC2", "description": "Security group rule allows internet traffic to Oracle DB port (1521)\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"GroupId": "d340e1b3-7f7b-4e78-a86f-8b7221d84d22", "VpcId": "a5ff2b5f-45f3-40eb-8919-0f199efe31a9", "OwnerId": "7f494915-72f4-4dfb-b47f-6e64af694847"}]}}}, {"policy": "ecc-aws-036", "resource_type": "Amazon EC2", "description": "Security group rule allows internet traffic to POP3 port (110)\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"GroupId": "5a5fa5aa-e3d2-480e-8631-eeb5358cd2b9", "VpcId": "f6f31ab2-8d84-40f9-be23-29d53bf53c6f", "OwnerId": "cc7fb74c-be26-47d0-8820-e7b3a969dcf0"}]}}}, {"policy": "ecc-aws-037", "resource_type": "Amazon EC2", "description": "Security group rule allows internet traffic to PostgreSQL port (5432)\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"GroupId": "31e6ab3d-b13a-4c8c-8419-01a64c3c432b", "VpcId": "925ecca9-a3b1-4986-b61b-a3a458582f46", "OwnerId": "f26b8b18-9b2d-4f21-a292-e34758e1a103"}]}}}, {"policy": "ecc-aws-038", "resource_type": "Amazon EC2", "description": "Security group rule allows internet traffic to SMTP port (25)\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"GroupId": "431eefe6-50c5-42cd-9856-fc89d086ae7c", "VpcId": "5fb915b1-c617-4528-aefb-57bd983b10a5", "OwnerId": "7fa49186-ec5c-40d1-84ab-a5533c966b3a"}]}}}, {"policy": "ecc-aws-039", "resource_type": "Amazon EC2", "description": "Security group rule allows internet traffic to Telnet port (23)\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"GroupId": "ed206221-2a8f-4572-97e3-e5ab1a63c881", "VpcId": "2916083b-5fe9-44bd-a71e-4bac900383fc", "OwnerId": "4db4913b-a420-477a-8650-799819062ebf"}]}}}, {"policy": "ecc-aws-040", "resource_type": "Amazon Elastic Kubernetes Service", "description": "EKS cluster is not using the latest version\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"arn": "73029953-0950-4f8d-a3d2-7c15c1a96c6a"}]}}}, {"policy": "ecc-aws-041", "resource_type": "Amazon Relational Database Service", "description": "RDS Instances without tags\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "38fcf76d-5b8c-4f74-a434-49c396438726"}]}}}, {"policy": "ecc-aws-042", "resource_type": "Amazon S3", "description": "S3 is not using a KMS key for encryption\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"Name": "7e2f953c-e2bb-4b9a-bac6-19e4c54faf22"}]}}}, {"policy": "ecc-aws-043", "resource_type": "Amazon S3", "description": "S3 Bucket life cycle is not configured\n", "severity": "Medium", "regions_data": {"multiregion": {"resources": [{"Name": "6becded8-5542-4e9c-a9e1-c26f5fe03cf7"}]}}}, {"policy": "ecc-aws-044", "resource_type": "Amazon S3", "description": "S3 Buckets without tags \n", "severity": "Medium", "regions_data": {"multiregion": {"resources": [{"Name": "ed2863a6-a0b2-4f36-9efb-7927452d9c04"}]}}}, {"policy": "ecc-aws-045", "resource_type": "AWS Account", "description": "Password policy does not require at least one uppercase letter\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"c7n-service:region": "multiregion"}]}}}, {"policy": "ecc-aws-046", "resource_type": "AWS Account", "description": "Root user account access key exists\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"c7n-service:region": "multiregion"}]}}}, {"policy": "ecc-aws-047", "resource_type": "AWS Account", "description": "Password policy does not require at least one lowercase letter\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"c7n-service:region": "multiregion"}]}}}, {"policy": "ecc-aws-048", "resource_type": "AWS Account", "description": "Password policy does not require at least one symbol\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"c7n-service:region": "multiregion"}]}}}, {"policy": "ecc-aws-049", "resource_type": "AWS Account", "description": "Password policy does not require at least one number\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"c7n-service:region": "multiregion"}]}}}, {"policy": "ecc-aws-050", "resource_type": "AWS Account", "description": "Password policy does not require minimum length of 14 characters or greater\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"c7n-service:region": "multiregion"}]}}}, {"policy": "ecc-aws-051", "resource_type": "AWS Account", "description": "IAM password policy is not configured to expire passwords after 90 days or less\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"c7n-service:region": "multiregion"}]}}}, {"policy": "ecc-aws-052", "resource_type": "AWS CloudTrail", "description": "CloudTrail is not enabled in all regions\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"c7n-service:region": "multiregion"}]}}}, {"policy": "ecc-aws-053", "resource_type": "AWS CloudTrail", "description": "CloudTrail log file validation is disabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"TrailARN": "695dc42e-9eb4-4419-bc12-d0782df93fc8"}]}}}, {"policy": "ecc-aws-054", "resource_type": "AWS Identity and Access Management", "description": "IAM policies that allow full \"*:*\" administrative privileges are in use\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{}]}}}, {"policy": "ecc-aws-055", "resource_type": "AWS CloudTrail", "description": "CloudTrail trails are not integrated with CloudWatch Logs\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"TrailARN": "c3da1c55-75c0-463d-bc36-b6a0df063a53"}]}}}, {"policy": "ecc-aws-056", "resource_type": "AWS Identity and Access Management", "description": "Access key was created during initial IAM user setup\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"Arn": "8ea377b1-c317-4109-974d-a6822e748182"}]}}}, {"policy": "ecc-aws-057", "resource_type": "Amazon EC2", "description": "IAM instance roles are not used for AWS resource access from instances\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"InstanceId": "4a1235e7-00ab-4e0a-ba48-db9304f3d870", "OwnerId": "e7c94ec2-67cd-4da9-a522-2766c836692b"}]}}}, {"policy": "ecc-aws-058", "resource_type": "AWS Account", "description": "Support role has not been created to manage incidents with AWS Support\n", "severity": "Medium", "regions_data": {"multiregion": {"resources": [{"c7n-service:region": "multiregion"}]}}}, {"policy": "ecc-aws-059", "resource_type": "AWS Config", "description": "AWS Config is not enabled in all regions\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"c7n-service:region": "eu-west-1"}]}}}, {"policy": "ecc-aws-060", "resource_type": "AWS CloudTrail", "description": "CloudTrail logs are not encrypted at rest using KMS CMK\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"TrailARN": "76b424bf-06e9-4567-bf31-eb7ecff8e005"}]}}}, {"policy": "ecc-aws-061", "resource_type": "AWS Key Management Service", "description": "Rotation for symmetric customer-created CMKs is not enabled\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"KeyArn": "c52e0ca8-4517-43cd-80f7-762da170ed3b", "AWSAccountId": "4320a709-cae8-4320-b8ea-a63c36c1c472"}]}}}, {"policy": "ecc-aws-062", "resource_type": "Amazon EC2", "description": "Security groups allow ingress from 0.0.0.0/0 or ::/0 to remote server administration port (22)\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"GroupId": "bc615100-3b03-428e-93c8-a6af44130e5f", "VpcId": "e5457ad0-2b12-438b-a0e2-10f34d443d3e", "OwnerId": "fb22e019-afe3-4701-be91-828aef3ba5c8"}]}}}, {"policy": "ecc-aws-063", "resource_type": "Amazon EC2", "description": "Security groups allow ingress from 0.0.0.0/0 or ::/0 to remote server administration port (3389)\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"GroupId": "7cc3098d-13a1-452d-898f-83fd92e81a6d", "VpcId": "761df9af-74c0-47ad-a398-78277a685ffa", "OwnerId": "1df61d00-b720-41d7-99cb-5ec4dd17a81c"}]}}}, {"policy": "ecc-aws-064", "resource_type": "Amazon EC2", "description": "VPC default security group does not restrict all traffic\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"GroupId": "5bbc48b1-4f9b-4b49-9ff2-b0651d025c79", "VpcId": "a742807f-13e3-4c4c-aba5-99a5f3f839f8", "OwnerId": "175720ce-a89b-4326-90f9-bc17f62796eb"}]}}}, {"policy": "ecc-aws-065", "resource_type": "Amazon CloudFront", "description": "Traffic between a CloudFront distribution and the origin is not enforced to allow HTTPS-only\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"ARN": "64cf5f63-e8e9-4505-aa23-e35075f6b4da"}]}}}, {"policy": "ecc-aws-066", "resource_type": "Amazon Elastic Kubernetes Service", "description": "EKS cluster endpoint does not have protected access\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"arn": "89d714e6-2a7a-45bf-8d67-3efe7f324686"}]}}}, {"policy": "ecc-aws-067", "resource_type": "AWS Account", "description": "Log metric filter and alarm do not exist for unauthorized API calls\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"c7n-service:region": "eu-west-1"}]}}}, {"policy": "ecc-aws-068", "resource_type": "AWS CloudTrail", "description": "S3 bucket used to store CloudTrail logs is publicly accessible\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"TrailARN": "b1420115-845a-44b5-8696-83a2665a64ba"}]}}}, {"policy": "ecc-aws-069", "resource_type": "Amazon S3", "description": "S3 bucket allows all actions from all principals\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"Name": "7c0ac491-da8f-4c52-88d7-fe4993b532c6"}]}}}, {"policy": "ecc-aws-070", "resource_type": "Amazon EC2", "description": "Unused security groups exist\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"GroupId": "137ae1fe-c9b6-4f30-b74d-602b6d68d3f3", "VpcId": "b1847fba-3753-4fca-9f29-a812284d9a8e", "OwnerId": "22ec2b9a-f6a8-4824-9a9c-71f931091661"}]}}}, {"policy": "ecc-aws-071", "resource_type": "AWS CodeBuild", "description": "CodeBuild GitHub or Bitbucket source repository URLs do not use OAuth\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"arn": "d8b4bcc2-03a9-4a45-b8de-5db717fa6b10"}]}}}, {"policy": "ecc-aws-072", "resource_type": "Amazon EC2 Auto Scaling", "description": "Auto scaling groups associated with a load balancer do not use health checks\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"AutoScalingGroupARN": "173dcb0a-84d6-4dd5-9a31-3d6eea701855"}]}}}, {"policy": "ecc-aws-073", "resource_type": "Amazon EC2", "description": "Unused EC2 EIPs exist\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"AllocationId": "e5aff0d8-324c-4caa-88b3-a925b7e7f6bd"}]}}}, {"policy": "ecc-aws-074", "resource_type": "Amazon OpenSearch Service", "description": "Elasticsearch Service domains are not in a VPC\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"ARN": "125eef8c-a902-4244-8b3a-b11cf0b2900e"}]}}}, {"policy": "ecc-aws-075", "resource_type": "Amazon OpenSearch Service", "description": "Elasticsearch Service domains do not have encryption at rest\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"ARN": "8211d2a0-ff26-4af9-a6b9-d5cf084b01ef"}]}}}, {"policy": "ecc-aws-076", "resource_type": "Amazon Elastic Block Store", "description": "EBS snapshots are publicly restorable\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"SnapshotId": "6eab556b-f8e4-4332-9986-758c12cae095", "OwnerId": "2394a21c-0ef4-47f3-9604-0f9ed9c7c4b6"}]}}}, {"policy": "ecc-aws-077", "resource_type": "AWS Account", "description": "Log metric filter and alarm do not exist for Management Console sign-in without MFA\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"c7n-service:region": "eu-west-1"}]}}}, {"policy": "ecc-aws-078", "resource_type": "AWS Account", "description": "Log metric filter and alarm do not exist for usage of \"root\" account\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"c7n-service:region": "eu-west-1"}]}}}, {"policy": "ecc-aws-079", "resource_type": "AWS Account", "description": "Log metric filter and alarm do not exist for IAM policy changes\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"c7n-service:region": "eu-west-1"}]}}}, {"policy": "ecc-aws-080", "resource_type": "AWS Account", "description": "Log metric filter and alarm do not exist for CloudTrail configuration changes\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"c7n-service:region": "eu-west-1"}]}}}, {"policy": "ecc-aws-081", "resource_type": "AWS Account", "description": "Log metric filter and alarm do not exist for AWS Management Console authentication failures\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"c7n-service:region": "eu-west-1"}]}}}, {"policy": "ecc-aws-082", "resource_type": "AWS Account", "description": "Log metric filter and alarm do not exist for disabling or scheduled deletion of customer created CMKs\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"c7n-service:region": "eu-west-1"}]}}}, {"policy": "ecc-aws-083", "resource_type": "Amazon CloudFront", "description": "Cloud Front is not integrated with WAF\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"ARN": "e12bfe42-e97e-4249-9ef7-8ce92e421ad3"}]}}}, {"policy": "ecc-aws-084", "resource_type": "AWS CloudTrail", "description": "S3 bucket access logging is disabled on the CloudTrail S3 bucket\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"TrailARN": "de4ec41a-a5ac-466b-8978-f954604dff8a"}]}}}, {"policy": "ecc-aws-085", "resource_type": "AWS Lambda", "description": "Lambda functions are not in a VPC\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"FunctionArn": "0d80c469-2e44-4895-8ca5-3c848e2eb48e"}]}}}, {"policy": "ecc-aws-086", "resource_type": "AWS Lambda", "description": "Lambda roles have admin privileges\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"FunctionArn": "ff148746-a8bd-474c-be71-314c4f1d633a"}]}}}, {"policy": "ecc-aws-087", "resource_type": "Amazon Redshift", "description": "Redshift clusters do not prohibit public access\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"ClusterIdentifier": "657c7437-ab75-4b16-bcef-d6f853e9e003"}]}}}, {"policy": "ecc-aws-088", "resource_type": "Amazon S3", "description": "S3 bucket cross-region replication is disabled\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"Name": "96a2b694-14ae-4172-834c-64d6819934fc"}]}}}, {"policy": "ecc-aws-089", "resource_type": "AWS CodeBuild", "description": "CodeBuild project environment variables contain clear text credentials\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"arn": "8151bf1c-2623-4fbb-8b86-bb53b3ec9b24"}]}}}, {"policy": "ecc-aws-090", "resource_type": "Amazon Relational Database Service", "description": "RDS snapshots do not prohibit public access\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"DBSnapshotArn": "1017dedf-4956-472b-a2b5-08727d593190", "DBInstanceIdentifier": "7f122a39-ecbe-4f62-a62b-9343c24c5610"}]}}}, {"policy": "ecc-aws-091", "resource_type": "Amazon EC2", "description": "Amazon EC2 instances managed by Systems Manager have a patch compliance status of NON-COMPLIANT after a patch installation\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"InstanceId": "879147c9-0340-4eff-bf96-b53710245642", "OwnerId": "8d9300cd-69ad-46bf-a9d5-040ec097e242"}]}}}, {"policy": "ecc-aws-092", "resource_type": "Amazon EC2", "description": "AMIs are exposed to public access\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"ImageId": "8eed3bfa-eee0-483d-8179-0f1f7d801e0e", "OwnerId": "06f99e69-4dc7-462f-bf50-c3c1256f34f2"}]}}}, {"policy": "ecc-aws-093", "resource_type": "Amazon SageMaker", "description": "SageMaker is not placed in VPC\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"NotebookInstanceArn": "853dcd69-0cdd-47cc-a4ed-218d76e4d6e1"}]}}}, {"policy": "ecc-aws-094", "resource_type": "AWS Account", "description": "Log metric filter and alarm do not exist for S3 bucket policy changes\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"c7n-service:region": "eu-west-1"}]}}}, {"policy": "ecc-aws-095", "resource_type": "AWS Account", "description": "Log metric filter and alarm do not exist for AWS Config configuration changes\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"c7n-service:region": "eu-west-1"}]}}}, {"policy": "ecc-aws-096", "resource_type": "AWS Account", "description": "Log metric filter and alarm do not exist for security group changes\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"c7n-service:region": "eu-west-1"}]}}}, {"policy": "ecc-aws-097", "resource_type": "AWS Account", "description": "Log metric filter and alarm do not exist for changes to Network Access Control Lists (NACL)\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"c7n-service:region": "eu-west-1"}]}}}, {"policy": "ecc-aws-098", "resource_type": "AWS Account", "description": "Log metric filter and alarm do not exist for changes to network gateways\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"c7n-service:region": "eu-west-1"}]}}}, {"policy": "ecc-aws-099", "resource_type": "AWS Account", "description": "Log metric filter and alarm do not exist for route table changes\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"c7n-service:region": "eu-west-1"}]}}}, {"policy": "ecc-aws-100", "resource_type": "AWS Account", "description": "Log metric filter and alarm do not exist for VPC changes\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"c7n-service:region": "eu-west-1"}]}}}, {"policy": "ecc-aws-101", "resource_type": "Amazon Virtual Private Cloud", "description": "VPC subnets automatic public ip assignment is enabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"SubnetId": "fe19141f-26bc-4afd-a636-41b073ac07a2", "VpcId": "fdd3f732-aa8b-41a4-be4a-33fb3443e2ea", "OwnerId": "a485470d-5dde-45f1-af51-16f2c80c99e7"}]}}}, {"policy": "ecc-aws-102", "resource_type": "Amazon SageMaker", "description": "SageMaker Notebook has direct internet access\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"NotebookInstanceArn": "62fe59bb-a105-4c5b-87fd-8d8df97d2c39"}]}}}, {"policy": "ecc-aws-103", "resource_type": "Amazon CloudFront", "description": "Cloudfront web distributions do not use custom SSL certificates\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"ARN": "52c630ce-0a73-4c65-867e-872718d2e2d4"}]}}}, {"policy": "ecc-aws-104", "resource_type": "Amazon CloudFront", "description": "Cloudfront web distribution with geo restriction is not enabled\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"ARN": "c5a5857e-c845-4ac0-9ddb-c5c05c6afb72"}]}}}, {"policy": "ecc-aws-105", "resource_type": "Amazon Kinesis", "description": "Kinesis Streams Keys are not rotated\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"StreamARN": "2ed5972b-a589-4e80-93d2-d96e257be234"}]}}}, {"policy": "ecc-aws-106", "resource_type": "AWS Certificate Manager", "description": "ACM has certificates with wildcard domain names\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"CertificateArn": "7bc8f2a4-38c6-47e5-9ec5-7794a375bd85", "DomainName": "5595e8a6-dc9b-4057-942e-0f0bc8db3606"}]}}}, {"policy": "ecc-aws-107", "resource_type": "AWS Certificate Manager", "description": "AWS Certificate Manager (ACM) has unused certificates\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"CertificateArn": "5d58157a-f86d-46ab-81cd-1ba2738e7e6e", "DomainName": "04d1edb8-85af-4213-918b-318d5d23a66f"}]}}}, {"policy": "ecc-aws-108", "resource_type": "Amazon CloudFront", "description": "AWS CloudFront distribution with access logging is disabled\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"ARN": "6df84dc2-6098-4565-98d3-2ac8617b9804"}]}}}, {"policy": "ecc-aws-109", "resource_type": "AWS Certificate Manager", "description": "Invalid or failed certificates are not removed from ACM\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"CertificateArn": "e8258d9f-65b8-4dc5-b9d7-9be121af3c02", "DomainName": "bb6b835f-63bf-46e6-ae02-6d64349f5f7f"}]}}}, {"policy": "ecc-aws-110", "resource_type": "Amazon Elastic Container Service", "description": "ECS Cluster At-Rest Encryption is disabled\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"clusterArn": "f8fa2e0a-4490-4d9c-8b36-54ec670bc837"}]}}}, {"policy": "ecc-aws-111", "resource_type": "Amazon Elastic Load Balancing", "description": "ALB is not protected by WAF regional\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"LoadBalancerArn": "85bbb7d7-4578-4004-a0a7-637dc818e34e"}]}}}, {"policy": "ecc-aws-112", "resource_type": "Amazon S3", "description": "S3 bucket versioning MFA delete is disabled\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"Name": "44a99c58-5c95-4056-9410-5d2d5174a7ec"}]}}}, {"policy": "ecc-aws-113", "resource_type": "AWS Identity and Access Management", "description": "Inline IAM policies are in use\n", "severity": "Medium", "regions_data": {"multiregion": {"resources": [{"Arn": "3c2bee50-49d4-40f4-91d5-05be2a83ff0f"}]}}}, {"policy": "ecc-aws-114", "resource_type": "Amazon Elastic Kubernetes Service", "description": "Kubernetes Engine Clusters network firewall inbound rule is overly permissive to all traffic\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"arn": "65908d46-5018-4c55-a4c6-c738bbe88841"}]}}}, {"policy": "ecc-aws-115", "resource_type": "AWS Certificate Manager", "description": "Expired certificates are not removed from the AWS Certificate Manager (ACM)\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"CertificateArn": "a497e11d-caf7-49e6-8660-583a3bc73a8a", "DomainName": "813d6c7b-0f94-4d2a-89ad-f146c3155e57"}]}}}, {"policy": "ecc-aws-116", "resource_type": "Amazon API Gateway", "description": "API endpoint type in the API gateway is not private and exposed to the public internet\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"id": "e346e268-cefc-4de9-8f3a-dc009d28d9f6", "name": "8acb14a9-b421-42b7-959d-3308ecff002f"}]}}}, {"policy": "ecc-aws-117", "resource_type": "Amazon API Gateway", "description": "API Key is not required on Method Request\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"id": "cfecafa7-0d29-46c7-849e-60da110a8194", "path": "e1eb30cb-c017-41ff-af54-a8ffbec7e40c"}]}}}, {"policy": "ecc-aws-118", "resource_type": "Amazon Elastic Container Service", "description": "Container is using IAM roles for an instance\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"serviceArn": "f0e53fc3-354b-46f6-b35b-c62e97d4dd4d"}]}}}, {"policy": "ecc-aws-119", "resource_type": "Amazon Kinesis", "description": "Kinesis streams are not encrypted with KMS CMK\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"StreamARN": "0e541a4b-3176-4d35-a1b6-a35f860a8a0b"}]}}}, {"policy": "ecc-aws-120", "resource_type": "Amazon Kinesis", "description": "Kinesis Server data at rest has no server-side encryption\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"StreamARN": "752f67ec-3e40-413d-ae20-1beac41049ba"}]}}}, {"policy": "ecc-aws-121", "resource_type": "Amazon EC2", "description": "Outbound traffic is allowed to all ports\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"GroupId": "b72ffc71-e95c-4387-8a82-33d6c6207773", "VpcId": "9e581021-347e-4498-94aa-2340365ce4e9", "OwnerId": "1271270b-1726-43bf-b831-c7a90fc1a757"}]}}}, {"policy": "ecc-aws-122", "resource_type": "Amazon DynamoDB", "description": "DynamoDB is not encrypted using KMS CMK\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"TableArn": "c871aa79-5987-463a-b37a-bceff870664e"}]}}}, {"policy": "ecc-aws-123", "resource_type": "Amazon Elastic File System", "description": "Amazon EFS file systems are not encrypted\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"FileSystemArn": "9216634b-6049-4bcd-bd73-5835e48acd81", "OwnerId": "ee6b174c-3b83-41ca-9d72-4cafe7201308"}]}}}, {"policy": "ecc-aws-124", "resource_type": "Amazon Elastic File System", "description": "EFS file systems are not encrypted using KMS CMK\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"FileSystemArn": "3ab579e1-06c2-4243-922c-9cf97de33990", "OwnerId": "efdf225e-58f1-48d0-bae0-5e0c4e944c65"}]}}}, {"policy": "ecc-aws-125", "resource_type": "Amazon ElastiCache", "description": "ElastiCache Redis cluster at-rest encryption is disabled\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"ARN": "00fb12a2-ca2d-4abe-8e92-d939cb28802b"}]}}}, {"policy": "ecc-aws-126", "resource_type": "Amazon Redshift", "description": "Redshift instances are not encrypted\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"ClusterIdentifier": "e4f1a452-54a2-4d95-860c-02552ef726dd"}]}}}, {"policy": "ecc-aws-127", "resource_type": "Amazon Relational Database Service", "description": "Unencrypted RDS cluster storage is in use\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"DBClusterArn": "6db36ded-b489-43d5-b8f5-a267309d2306"}]}}}, {"policy": "ecc-aws-128", "resource_type": "Amazon Route 53", "description": "Expired Route53 domain name\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"DomainName": "18fed3e3-166b-4eae-9962-8cc33961d28b"}]}}}, {"policy": "ecc-aws-129", "resource_type": "Amazon Elastic Load Balancing", "description": "Application or Network Load Balancer access logs is disabled\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"LoadBalancerArn": "94a176b9-33aa-45f6-8d57-274f5cd2747d"}]}}}, {"policy": "ecc-aws-130", "resource_type": "Amazon Elastic Load Balancing", "description": "Security Policy of the Network Load Balancer is not updated\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"LoadBalancerArn": "6e681ece-0210-4494-bd97-320716b352dc"}]}}}, {"policy": "ecc-aws-131", "resource_type": "Amazon EC2", "description": "Instance with unencrypted service is exposed to the public internet\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"InstanceId": "4948f389-9dd0-4ec3-a828-e7ccff25a27b", "OwnerId": "60f8a284-0bfc-4e4a-823c-3f39994f210e"}]}}}, {"policy": "ecc-aws-132", "resource_type": "Amazon EC2", "description": "Public Instance with a sensitive service is exposed to the entire internet\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"InstanceId": "9b95953e-0cd4-4644-b733-ecea902fa432", "OwnerId": "b5fe3702-739d-4ef7-a941-75909d4c214e"}]}}}, {"policy": "ecc-aws-133", "resource_type": "AWS Account", "description": "Amazon GuardDuty service is not enabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"c7n-service:region": "eu-west-1"}]}}}, {"policy": "ecc-aws-134", "resource_type": "Amazon Elastic Load Balancing", "description": "Classic Load Balancer with sensitive services is exposed to the entire internet\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{}]}}}, {"policy": "ecc-aws-135", "resource_type": "Amazon Elastic Load Balancing", "description": "Classic Load Balancer with an unencrypted sensitive service is exposed to the public internet\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{}]}}}, {"policy": "ecc-aws-136", "resource_type": "Amazon Elastic Load Balancing", "description": "Application Load Balancer with sensitive services is exposed to the entire internet\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"LoadBalancerArn": "d3f86561-05ae-4ef5-9de9-a1451f1398bc"}]}}}, {"policy": "ecc-aws-137", "resource_type": "Amazon Elastic Load Balancing", "description": "Application Load Balancer with an unencrypted sensitive service is exposed to the public internet\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"LoadBalancerArn": "87e9ee30-7c4e-4c8e-bb5d-3128e766e93b"}]}}}, {"policy": "ecc-aws-138", "resource_type": "AWS Account", "description": "Root user is used for administrative and daily tasks\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"c7n-service:region": "multiregion"}]}}}, {"policy": "ecc-aws-139", "resource_type": "AWS Account", "description": "IAM Access analyzer is not enabled for all regions\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"c7n-service:region": "eu-west-1"}]}}}, {"policy": "ecc-aws-140", "resource_type": "AWS Identity and Access Management", "description": "More than one active access key is available for a single IAM user\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"Arn": "51aa2680-6074-4d31-8e70-3b9039b9fd7a"}]}}}, {"policy": "ecc-aws-141", "resource_type": "AWS Identity and Access Management", "description": "Expired SSL/TLS certificates stored in IAM are not removed\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"Arn": "53684763-c1e0-4cbb-8c19-c15c2d4d0308"}]}}}, {"policy": "ecc-aws-142", "resource_type": "Amazon S3", "description": "S3 Buckets are not configured with 'Block public access' bucket settings\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"Name": "d4b35558-ffa9-4ec8-ba67-fb6c04432ac4"}]}}}, {"policy": "ecc-aws-143", "resource_type": "AWS CloudTrail", "description": "Object-level logging for write events is disabled for S3 bucket\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"c7n-service:region": "eu-west-1"}]}}}, {"policy": "ecc-aws-144", "resource_type": "AWS CloudTrail", "description": "Object-level logging for read events is disabled for S3 bucket\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"c7n-service:region": "eu-west-1"}]}}}, {"policy": "ecc-aws-145", "resource_type": "AWS Account", "description": "Log metric filter and alarm do not exist for AWS Organizations changes\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"account_id": "e6e7c7af-4348-4b63-8b8e-b81492c33ad7", "c7n-service:region": "eu-west-1"}]}}}, {"policy": "ecc-aws-146", "resource_type": "Amazon Virtual Private Cloud", "description": "Network ACLs allow ingress from 0.0.0.0/0 to remote server administration ports\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"NetworkAclId": "8ecb83da-caa7-4b03-8078-8db682713a5e", "OwnerId": "5f221c6b-3825-4160-bcb2-ae35e4d0efaf"}]}}}, {"policy": "ecc-aws-147", "resource_type": "Amazon Elastic Block Store", "description": "EBS volume encryption is disabled\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"VolumeId": "58b305ab-422d-4486-b8f2-8237f01fd1ea"}]}}}, {"policy": "ecc-aws-148", "resource_type": "Amazon S3", "description": "Logging for S3 bucket is disabled\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"Name": "440d75f8-008d-4e46-8876-da51572011b1"}]}}}, {"policy": "ecc-aws-149", "resource_type": "Amazon Relational Database Service", "description": "RDS instance is publicly accessible \n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "436e80d7-95be-4ccc-9625-36cb3858ca1a"}]}}}, {"policy": "ecc-aws-150", "resource_type": "Amazon API Gateway", "description": "API Gateway REST API cache data is not encrypted at rest\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"stageName": "90fd6810-715e-4ee8-af04-58538c10d18b", "restApiId": "34264bdc-3926-4ac5-91d4-088dc4964570"}]}}}, {"policy": "ecc-aws-151", "resource_type": "Amazon EC2", "description": "Security groups allow unrestricted access to FTP port 20 \n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"GroupId": "36dc074c-fc00-4dad-91bd-63cb23b7b5c0", "VpcId": "c54e38fa-7241-41ce-b4a2-a20dd4395e0b", "OwnerId": "e42064e4-d99c-4ff2-80b5-417fe9d47e75"}]}}}, {"policy": "ecc-aws-152", "resource_type": "Amazon Elastic Load Balancing", "description": "Classic Load Balancers connection draining is not enabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{}]}}}, {"policy": "ecc-aws-153", "resource_type": "Amazon OpenSearch Service", "description": "Elasticsearch domains audit logging is not enabled \n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"ARN": "51b5430a-e08a-4004-81ee-9da89639a308"}]}}}, {"policy": "ecc-aws-154", "resource_type": "Amazon OpenSearch Service", "description": "Elasticsearch domains have less than three data nodes\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"ARN": "8c3e0eed-0c39-4979-9b58-6be6e3c2df57"}]}}}, {"policy": "ecc-aws-155", "resource_type": "Amazon OpenSearch Service", "description": "Elasticsearch domains are not configured with at least three dedicated master nodes\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"ARN": "4d65ed37-5c71-4ee1-9fc7-ca81828a76de"}]}}}, {"policy": "ecc-aws-156", "resource_type": "Amazon OpenSearch Service", "description": "Connections to Elasticsearch domains are not encrypted using TLS 1.2\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"ARN": "fb499ba7-4fad-4ad5-a229-115a56d59b61"}]}}}, {"policy": "ecc-aws-157", "resource_type": "Amazon Relational Database Service", "description": "RDS DB clusters are not configured to copy tags to snapshots\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"DBClusterArn": "03a7fcab-128b-40a8-a144-3d845e725610"}]}}}, {"policy": "ecc-aws-158", "resource_type": "Amazon Relational Database Service", "description": "RDS DB instances are not configured to copy tags to snapshots\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "e78bf0cc-4c03-42cc-9e8d-7fa89a8b1110"}]}}}, {"policy": "ecc-aws-159", "resource_type": "Amazon Relational Database Service", "description": "RDS event notifications subscription is not configured for critical cluster events\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"c7n-service:region": "eu-west-1"}]}}}, {"policy": "ecc-aws-160", "resource_type": "Amazon Relational Database Service", "description": "RDS event notifications subscription is not configured for critical database instance events\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"c7n-service:region": "eu-west-1"}]}}}, {"policy": "ecc-aws-161", "resource_type": "Amazon Relational Database Service", "description": "RDS event notifications subscription is not configured for database parameter group events\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"c7n-service:region": "eu-west-1"}]}}}, {"policy": "ecc-aws-162", "resource_type": "Amazon Relational Database Service", "description": "RDS event notifications subscription is not configured for critical database security group events\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"c7n-service:region": "eu-west-1"}]}}}, {"policy": "ecc-aws-163", "resource_type": "Amazon Relational Database Service", "description": "RDS database instances are using database engine default ports\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "935a1d6a-3920-4b45-b0bf-46b4db33f1c4"}]}}}, {"policy": "ecc-aws-164", "resource_type": "Amazon Redshift", "description": "Redshift clusters audit logging is disabled\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"ClusterIdentifier": "c6665bac-93a4-4690-9726-129bf2d0b3c1"}]}}}, {"policy": "ecc-aws-165", "resource_type": "Amazon Elastic Container Service", "description": "Amazon ECS services public IP addresses are assigned to them automatically \n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"serviceArn": "1ea0e702-0cec-4ef5-9c48-b741514ff3ed"}]}}}, {"policy": "ecc-aws-166", "resource_type": "Amazon EC2", "description": "Security groups allow unrestricted access to RPC port 135\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"GroupId": "b8eb9e4f-a8a0-4880-bb03-5e1575179185", "VpcId": "e4c2d9b0-4be4-46a7-92ef-851fb1bd0ca8", "OwnerId": "29d1ac64-1eab-4f29-81f9-7dca951b5d9e"}]}}}, {"policy": "ecc-aws-167", "resource_type": "Amazon EC2", "description": "Security groups allow unrestricted access to IMAP port 143\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"GroupId": "b63534a7-0866-473d-a5fc-ef0edf2c7b14", "VpcId": "bc9e283b-9a61-490a-ba58-e28ea213e501", "OwnerId": "2283b697-0320-42d0-88c5-548ccb291212"}]}}}, {"policy": "ecc-aws-168", "resource_type": "Amazon EC2", "description": "Security groups allow unrestricted access to MSSQL ports 1433, 1434\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"GroupId": "db115bcf-a855-4e0a-9894-233600ed5027", "VpcId": "fbf7f9eb-4cad-47c4-a44f-9f4773d3adb8", "OwnerId": "25628fa4-a22d-4e33-864c-c5070ea64105"}]}}}, {"policy": "ecc-aws-169", "resource_type": "Amazon EC2", "description": "Security groups allow unrestricted access to ahsp port 4333\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"GroupId": "b283c7e2-9052-458c-958a-5d23e144d422", "VpcId": "2dfe1520-e338-43ea-a730-320af7eaf905", "OwnerId": "93d7cf6d-e109-4fee-a43a-e19314f74217"}]}}}, {"policy": "ecc-aws-170", "resource_type": "Amazon EC2", "description": "Security groups allow unrestricted access to fcp-addr-srvr1 port 5500\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"GroupId": "c8a3508d-a41e-4405-852e-db700c87d310", "VpcId": "a7691999-8bf6-4a0d-9d22-0f0b48dfef75", "OwnerId": "5d8320df-2b11-4521-ac02-1d7bd27843bf"}]}}}, {"policy": "ecc-aws-171", "resource_type": "Amazon EC2", "description": "Security groups allow unrestricted access to Kibana port 5601\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"GroupId": "8558d575-370e-4e0a-98a2-585feb6a2284", "VpcId": "ffd03cb5-0f64-4316-a129-7232d3cf14ff", "OwnerId": "10b28b67-be9d-4fa2-ba2b-644ed25035d3"}]}}}, {"policy": "ecc-aws-172", "resource_type": "Amazon EC2", "description": "Security groups allow unrestricted access to proxy port 8080\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"GroupId": "d045f109-4bab-4d0d-b41b-b2b9e582cbbe", "VpcId": "a4605161-28c9-4171-b907-8fdce371f734", "OwnerId": "66b87f71-f950-4732-8b46-6fe843b9c2bb"}]}}}, {"policy": "ecc-aws-173", "resource_type": "Amazon EC2", "description": "Security groups allow unrestricted access to Elasticsearch service ports 9200, 9300\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"GroupId": "ab2f2b15-ec54-4c7f-b0b6-4708046489ea", "VpcId": "b9818858-bdc6-4230-9766-d1894f67edf2", "OwnerId": "9ad1d875-aaf4-40d1-a326-ed484bc2cce5"}]}}}, {"policy": "ecc-aws-174", "resource_type": "Amazon Relational Database Service", "description": "RDS database clusters are using a database engine default ports\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"DBClusterArn": "31a75d7f-b2e7-4d2e-88ec-10bbb844fa69"}]}}}, {"policy": "ecc-aws-175", "resource_type": "Amazon Relational Database Service", "description": "RDS instances storage not encrypted\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "6c7c692c-1057-4323-b129-f8c553f74647"}]}}}, {"policy": "ecc-aws-176", "resource_type": "Amazon Relational Database Service", "description": "RDS snapshot storage not encrypted\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"DBSnapshotArn": "4df4d71b-231b-4533-ae92-3ff89b85f078", "DBInstanceIdentifier": "2ed2be7b-f2d1-420a-9ad7-eb9b66249535"}]}}}, {"policy": "ecc-aws-177", "resource_type": "Amazon API Gateway", "description": "API Gateway REST API stages are not configured to use SSL certificates for backend authentication\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"stageName": "9792edbc-de5a-47a6-9d73-5166f9b8c278", "restApiId": "4587de23-d66e-422b-9038-46d7be6eda9c"}]}}}, {"policy": "ecc-aws-178", "resource_type": "Amazon API Gateway", "description": "API Gateway REST API stages do not have AWS X-Ray tracing enabled\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"stageName": "675c1ad5-aa1f-4cd5-a6c2-b7bed6fa9940"}]}}}, {"policy": "ecc-aws-179", "resource_type": "Amazon CloudFront", "description": "CloudFront distributions do not have a default root object configured\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"ARN": "84bf2c40-dc17-4f88-878b-04a091fced5a"}]}}}, {"policy": "ecc-aws-180", "resource_type": "Amazon CloudFront", "description": "CloudFront distributions origin failover is not configured\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"ARN": "267ad91f-4434-46ce-8911-e9fce8b00cfe"}]}}}, {"policy": "ecc-aws-181", "resource_type": "AWS Database Migration Service", "description": "AWS Database Migration Service replication instances are public\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"ReplicationInstanceIdentifier": "5a9a80fc-b1ab-4b90-bc1b-7314fb218120", "ReplicationInstanceArn": "13970328-97ec-49d5-9d0c-9880cb9976fc"}]}}}, {"policy": "ecc-aws-182", "resource_type": "Amazon DynamoDB", "description": "DynamoDB table Auto Scaling or On-Demand is not enabled on DynamoDB tables \n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"TableArn": "79f60372-41f2-4be7-8d35-38b91e8945a6"}]}}}, {"policy": "ecc-aws-183", "resource_type": "Amazon DynamoDB", "description": "DynamoDB tables do not have point-in-time recovery enabled\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"TableArn": "d4d2657f-e4fb-4798-9e78-6804e980b0fa"}]}}}, {"policy": "ecc-aws-184", "resource_type": "Amazon DynamoDB Accelerator", "description": "DynamoDB Accelerator clusters are not encrypted at rest\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"ClusterArn": "ef8c27ec-9520-478d-bab7-e3569bba44f1"}]}}}, {"policy": "ecc-aws-185", "resource_type": "Amazon EC2", "description": "Stopped EC2 instances are not removed after a specified time period\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"InstanceId": "c0fa8fcd-afaa-45a1-8793-0e67eebc9f1d", "OwnerId": "e7e48922-eab8-49cb-af92-b6bae901f8fe"}]}}}, {"policy": "ecc-aws-186", "resource_type": "Amazon EC2", "description": "EC2 instances have public IP address\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"InstanceId": "07413f32-c35b-43cd-a3a9-f2deec89b1cf", "OwnerId": "282aa3f9-dd49-4253-b1ed-feab928d9962"}]}}}, {"policy": "ecc-aws-187", "resource_type": "Amazon Virtual Private Cloud", "description": "EC2 is not configured to use VPC endpoints that are created for the EC2 service\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"VpcId": "4d6c8240-ef08-437e-98b7-1d8bdb5e92e9", "OwnerId": "e4f75af3-7725-424b-b55a-43f9ad5224ff"}]}}}, {"policy": "ecc-aws-188", "resource_type": "Amazon Virtual Private Cloud", "description": "Unused network access control lists are not removed\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"NetworkAclId": "de3d01d9-bc68-4eaa-a4d7-38ae6e69f3a8", "OwnerId": "f1a93dc4-83ef-407a-a974-9aaf6f051576"}]}}}, {"policy": "ecc-aws-189", "resource_type": "Amazon EC2", "description": "EC2 instances are using multiple ENIs\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"InstanceId": "6f4b72d5-55bf-4448-b568-e462f6edc6a4", "OwnerId": "bf5f66c3-7ddb-4f9c-9772-e69e18b3b0dc"}]}}}, {"policy": "ecc-aws-190", "resource_type": "Amazon Elastic Container Service", "description": "Amazon ECS task definitions do not have secure networking modes and user definitions\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"taskDefinitionArn": "f0b11a8e-ce5a-460f-ae06-96940187ffe2"}]}}}, {"policy": "ecc-aws-191", "resource_type": "Amazon Elastic File System", "description": "Amazon EFS volumes are not in backup plans\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"FileSystemArn": "8ff4d86d-44c7-4e57-83ab-56ed1d5efbd7"}]}}}, {"policy": "ecc-aws-192", "resource_type": "AWS Elastic Beanstalk", "description": "Elastic Beanstalk environments do not have enhanced health reporting enabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"EnvironmentArn": "d353dfa7-8c9c-45f2-9c50-a55325604757"}]}}}, {"policy": "ecc-aws-193", "resource_type": "Amazon Elastic Load Balancing", "description": "Application load balancers are not configured to drop invalid HTTP headers\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"LoadBalancerArn": "12751a25-d0ea-45f7-bb65-cce006c18e34"}]}}}, {"policy": "ecc-aws-194", "resource_type": "Amazon Elastic Load Balancing", "description": "Application, Network or Gateway Load Balancer deletion protection is not enabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"LoadBalancerArn": "c528b29f-be7e-4bb8-aa18-3ca2510d6d2b"}]}}}, {"policy": "ecc-aws-195", "resource_type": "Amazon Elastic Load Balancing", "description": "Application Load Balancer is not configured to redirect all HTTP requests to HTTPS\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"LoadBalancerArn": "b976d15e-fc7b-4fd8-82bd-6ad95475a53f"}]}}}, {"policy": "ecc-aws-196", "resource_type": "Amazon EMR", "description": "Amazon EMR cluster master nodes have public IP addresses\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"ClusterArn": "477531a0-2cb9-4037-83f5-b697c3b0ef89"}]}}}, {"policy": "ecc-aws-197", "resource_type": "Amazon OpenSearch Service", "description": "Elasticsearch domains data sent between nodes is not encrypted\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"ARN": "b9a9505f-a773-4967-916f-e5e07b1a475c"}]}}}, {"policy": "ecc-aws-198", "resource_type": "Amazon OpenSearch Service", "description": "Elasticsearch domain error logging to CloudWatch Logs is not enabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"ARN": "42cc90b4-c804-4206-b8d3-e6bdcb0acd91"}]}}}, {"policy": "ecc-aws-199", "resource_type": "Amazon Relational Database Service", "description": "Enhanced monitoring is not configured for RDS DB instances\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "0847dc92-2847-40a8-8531-b940bea62592"}]}}}, {"policy": "ecc-aws-200", "resource_type": "Amazon Relational Database Service", "description": "RDS clusters deletion protection is not enabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DBClusterArn": "ca1f7b06-104b-4acc-b542-e716487f204b"}]}}}, {"policy": "ecc-aws-201", "resource_type": "Amazon Relational Database Service", "description": "RDS DB instances deletion protection is not enabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "019fde3f-5ee9-4115-bf3f-dc7347ef75c1"}]}}}, {"policy": "ecc-aws-202", "resource_type": "Amazon Relational Database Service", "description": "Oracle database logging is disabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "9163fe47-f74a-4ab0-9d63-da1fc023545e"}]}}}, {"policy": "ecc-aws-203", "resource_type": "Amazon Relational Database Service", "description": "PostgreSQL database logging is disabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "35c8a049-8810-4e3e-80ab-af85333ce65e"}]}}}, {"policy": "ecc-aws-204", "resource_type": "Amazon Relational Database Service", "description": "MySQL database logging is disabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "3044fb4b-1e5a-4240-a49a-a5bf182746fb"}]}}}, {"policy": "ecc-aws-205", "resource_type": "Amazon Relational Database Service", "description": "MariaDB database logging is disabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "d26c9cfa-da8e-4f49-8681-ba0a73553224"}]}}}, {"policy": "ecc-aws-206", "resource_type": "Amazon Relational Database Service", "description": "SQL Server database logging is disabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "f45cb395-13d3-4e51-819c-a038d469f2d3"}]}}}, {"policy": "ecc-aws-207", "resource_type": "Amazon Relational Database Service", "description": "Aurora database logging is disabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "5c60d036-c4b6-4784-b1f3-d94ecb13c6c3"}]}}}, {"policy": "ecc-aws-208", "resource_type": "Amazon Relational Database Service", "description": "Aurora-MySQL database logging is disabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "c0f4f105-c4a6-4cff-8766-ae2543cd829a"}]}}}, {"policy": "ecc-aws-209", "resource_type": "Amazon Relational Database Service", "description": "Aurora-PostgreSQL database logging is disabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "ff2792f0-ad87-49c3-b346-f5d278338551"}]}}}, {"policy": "ecc-aws-210", "resource_type": "Amazon Relational Database Service", "description": "IAM authentication is not configured for RDS instances\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "2d0e1395-fc1f-428e-9f68-e22b7840c825"}]}}}, {"policy": "ecc-aws-211", "resource_type": "Amazon Relational Database Service", "description": "IAM authentication is not configured for RDS clusters\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DBClusterArn": "e364d44c-8774-4734-a7ae-728d9676153c"}]}}}, {"policy": "ecc-aws-212", "resource_type": "Amazon Relational Database Service", "description": "Amazon Aurora clusters backtracking is disabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DBClusterArn": "51c4c740-cbbe-4542-83c2-ad39b6992c4b"}]}}}, {"policy": "ecc-aws-213", "resource_type": "Amazon Relational Database Service", "description": "DS DB clusters are not configured for multiple Availability Zones\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DBClusterArn": "e7dc1d2c-d74d-40ad-9ac5-a0ff81530a36"}]}}}, {"policy": "ecc-aws-214", "resource_type": "Amazon Redshift", "description": "Connections to Redshift clusters are not encrypted in transit\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"ClusterIdentifier": "24d880a0-014a-4224-977a-c7cd47a04663"}]}}}, {"policy": "ecc-aws-215", "resource_type": "Amazon Redshift", "description": "Amazon Redshift clusters automatic snapshots are disabled\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"ClusterIdentifier": "7e64daa6-eb42-4cdf-aa6f-5dc0c5e5fc3c"}]}}}, {"policy": "ecc-aws-216", "resource_type": "Amazon Redshift", "description": "Amazon Redshift automatic upgrades to major versions are disabled \n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"ClusterIdentifier": "21de4951-3b21-4a84-a323-b6d1f9bdf367"}]}}}, {"policy": "ecc-aws-217", "resource_type": "Amazon Redshift", "description": "Amazon Redshift clusters are not using enhanced VPC routing\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"ClusterIdentifier": "b6465f2c-1098-4f45-963f-ab5122b5ff3a"}]}}}, {"policy": "ecc-aws-218", "resource_type": "AWS Secrets Manager", "description": "Secrets Manager secrets automatic rotation disabled\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"Name": "caeabb1e-a8fd-4e53-9259-302a59cda4a9", "ARN": "e5546d2a-29ca-47f2-8073-4982e8c7f3ee"}]}}}, {"policy": "ecc-aws-219", "resource_type": "AWS Secrets Manager", "description": "Secrets Manager secrets configured with automatic rotation are not rotating successfully\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"Name": "8e87633c-2b70-456e-8169-65545b770dd6", "ARN": "c340c849-ae42-4dd0-9ec1-af09a54bb5e0"}]}}}, {"policy": "ecc-aws-220", "resource_type": "AWS Secrets Manager", "description": "Unused Secrets Manager secrets are not removed\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"Name": "a94fc5ec-b0f1-4e68-80e7-7d2103910840", "ARN": "7710cec5-8c5e-4faf-96d2-07ea292dc5e9"}]}}}, {"policy": "ecc-aws-221", "resource_type": "Amazon Simple Notification Service", "description": "SNS topics are not encrypted at rest using AWS KMS\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"TopicArn": "0b3700e3-25d6-4702-80d3-37bc531b1701"}]}}}, {"policy": "ecc-aws-222", "resource_type": "Amazon EC2", "description": "EC2 instances are not managed by AWS Systems Manager\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"InstanceId": "7af448a0-10da-4455-ab3d-35e2bd922bc3", "OwnerId": "864bf74e-1336-41b7-8e5a-540c40e25ccd"}]}}}, {"policy": "ecc-aws-223", "resource_type": "Amazon EC2", "description": "Instances managed by Systems Manager do not have association compliance status of COMPLIANT\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"InstanceId": "87c72d4a-ae3e-4f2f-a19f-0a68486bc68f", "OwnerId": "1c8f4085-5664-4796-a44b-e0579f6edbe6"}]}}}, {"policy": "ecc-aws-224", "resource_type": "Amazon EC2", "description": "EC2 instances do not use IMDSv2\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"InstanceId": "d207f872-e033-4d56-bac4-7c28fa03f755", "OwnerId": "bc2c921e-d523-4ad7-a4ed-7170a0b59ca9"}]}}}, {"policy": "ecc-aws-225", "resource_type": "Amazon Elastic Kubernetes Service", "description": "EKS control plane logging is disabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"arn": "027d795a-dc91-4905-a354-b21a89a13f9f"}]}}}, {"policy": "ecc-aws-226", "resource_type": "Amazon Elastic Kubernetes Service", "description": "Amazon EKS clusters security group traffic is not restricted\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"arn": "58139cb0-6542-4471-ac92-b93d92adfdb0"}]}}}, {"policy": "ecc-aws-227", "resource_type": "Amazon Elastic Kubernetes Service", "description": "Kubernetes Secrets are not encrypted using KMS CMK\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"arn": "f0797ed9-5d85-4fde-864f-b1167d1067d0"}]}}}, {"policy": "ecc-aws-228", "resource_type": "Amazon Elastic Container Registry", "description": "Amazon ECR is not configured with immutable tags\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"repositoryArn": "411d69ec-1838-45df-ab0c-ca5509922254"}]}}}, {"policy": "ecc-aws-229", "resource_type": "Amazon Elastic Container Registry", "description": "Amazon ECR repository does not have encryption with KMS enabled\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"repositoryArn": "a84a1ab7-fe43-473b-9f22-00642d008785"}]}}}, {"policy": "ecc-aws-230", "resource_type": "Amazon Elastic Container Registry", "description": "Amazon ECR image scanning on push is disabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"repositoryArn": "18320840-9400-404c-93a9-2682dd26978b"}]}}}, {"policy": "ecc-aws-231", "resource_type": "Amazon Relational Database Service", "description": "Maximum log file lifetime is not set correctly for PostgreSQL\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "457a95be-8232-4d93-96d6-b6f94714565f"}]}}}, {"policy": "ecc-aws-232", "resource_type": "Amazon Relational Database Service", "description": "Maximum log file size is not set correctly for PostgreSQL\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "f0482119-9b69-43cd-9202-8a0e9ac42599"}]}}}, {"policy": "ecc-aws-233", "resource_type": "Amazon Relational Database Service", "description": "The 'debug_print_parse' flag is enabled for PostgreSQL\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "fd838b55-a13d-45c7-bf85-a0d04f8721fc"}]}}}, {"policy": "ecc-aws-234", "resource_type": "Amazon Relational Database Service", "description": "The 'debug_print_rewritten' flag is enabled for PostgreSQL\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "366f8cad-897a-4a52-8f86-d432e337b00a"}]}}}, {"policy": "ecc-aws-235", "resource_type": "Amazon Relational Database Service", "description": "The 'debug_print_plan' flag is enabled for PostgreSQL\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "97d3173e-3c16-49e3-8a53-259f6dc6c82b"}]}}}, {"policy": "ecc-aws-236", "resource_type": "Amazon Relational Database Service", "description": "The 'debug_pretty_print' flag is disabled for PostgreSQL\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "9e0a74af-2193-47d3-8f91-02746d54b132"}]}}}, {"policy": "ecc-aws-237", "resource_type": "Amazon Relational Database Service", "description": "The 'log_connections' flag is disabled for PostgreSQL\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "2e8edc21-25ac-4533-8285-459c5add8fd7"}]}}}, {"policy": "ecc-aws-238", "resource_type": "Amazon Relational Database Service", "description": "The 'log_disconnections' flag is disabled for PostgreSQL\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "043c8049-10e6-42a9-863c-40e1b8217b57"}]}}}, {"policy": "ecc-aws-239", "resource_type": "Amazon Relational Database Service", "description": "The 'log_error_verbosity' flag is not set correctly for PostgreSQL\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "13e5b180-5d3d-419b-9b17-c7056e4ccce4"}]}}}, {"policy": "ecc-aws-240", "resource_type": "Amazon Relational Database Service", "description": "The 'log_hostname' flag is not disabled for PostgreSQL\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "339dc6e7-412e-41f1-964c-86ad1ebab9b6"}]}}}, {"policy": "ecc-aws-241", "resource_type": "Amazon Relational Database Service", "description": "The 'log_statement' flag is not set correctly for PostgreSQL\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "a16cf123-b62e-4ebe-a750-a928641385d8"}]}}}, {"policy": "ecc-aws-242", "resource_type": "Amazon Relational Database Service", "description": "The 'log_destination' flag is not set to csvlog for PostgreSQL\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "e8cc5754-3f41-47c5-b24c-fae46519619f"}]}}}, {"policy": "ecc-aws-243", "resource_type": "Amazon Relational Database Service", "description": "The 'log_checkpoints' flag is not enabled for PostgreSQL\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "10d0373e-fad8-448a-a721-146f9874f861"}]}}}, {"policy": "ecc-aws-244", "resource_type": "Amazon Relational Database Service", "description": "The 'log_lock_waits' flag is not enabled for PostgreSQL\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "f5bff5aa-4a7d-4ec1-87e5-6d280d0a3c48"}]}}}, {"policy": "ecc-aws-245", "resource_type": "Amazon Relational Database Service", "description": "The 'log_duration' flag is not enabled for PostgreSQL\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "d8b457de-eccf-4d17-8b60-dbeb609e5001"}]}}}, {"policy": "ecc-aws-246", "resource_type": "AWS Transit Gateway", "description": "Transit gateway default route table association is enabled\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"TransitGatewayArn": "6a47fef0-3dca-4ba7-b735-60281464189f"}]}}}, {"policy": "ecc-aws-247", "resource_type": "AWS Transit Gateway", "description": "Transit gateway default route table propagation is enabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"TransitGatewayArn": "56131c4c-78c9-4601-921e-67eb67105e7a"}]}}}, {"policy": "ecc-aws-248", "resource_type": "Amazon API Gateway", "description": "Api gateway is not protected by WAF\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"stageName": "0e32663a-525a-442e-a5a3-ea52af193c08", "restApiId": "e4616212-b5a0-4341-a4a7-3f0fb2df4db4"}]}}}, {"policy": "ecc-aws-249", "resource_type": "Amazon API Gateway", "description": "Content encoding is not enabled for API Gateway\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"id": "5dfdd140-f48f-4d88-9a19-becf0c1e5c64", "name": "c95063aa-cc52-4c9b-af6d-8f5c1229c2ba"}]}}}, {"policy": "ecc-aws-250", "resource_type": "Amazon API Gateway", "description": "Cache is not enabled for API gateway\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"stageName": "3be79fd4-353f-4380-9e9e-799bf2ae4a46", "restApiId": "89e5db50-fa4e-4e5c-a2ce-f9ca74ac8c0f"}]}}}, {"policy": "ecc-aws-251", "resource_type": "Amazon AppFlow", "description": "Appflow is not encrypted with KMS CMK \n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"flowArn": "55e56e78-fb2a-46d8-9479-9df642037f28"}]}}}, {"policy": "ecc-aws-252", "resource_type": "AWS Glue", "description": "Data catalog encryption is not enabled for AWS Glue\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"CatalogId": "fdfe9a99-3059-412b-a5a9-41fb08b5e17e", "c7n-service:region": "eu-west-1"}]}}}, {"policy": "ecc-aws-253", "resource_type": "AWS Glue", "description": "Data catalog for AWS Glue is not encrypted with KMS CMK\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"CatalogId": "b244dd8d-8ff6-494c-a5ce-8dcf49c9b32d", "c7n-service:region": "eu-west-1"}]}}}, {"policy": "ecc-aws-254", "resource_type": "AWS Glue", "description": "Job bookmarks encryption is not enabled for AWS Glue\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"Name": "0bfedca8-be16-4e05-a1aa-c1eb68c2dbf2"}]}}}, {"policy": "ecc-aws-255", "resource_type": "AWS Glue", "description": "CloudWatch logs are not encrypted for AWS Glue\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"Name": "4285a3d7-3734-4a1a-b2f3-47836938fd44"}]}}}, {"policy": "ecc-aws-256", "resource_type": "AWS Glue", "description": "S3 is not encrypted for AWS Glue\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"Name": "8d9882d7-cd0c-4b99-8cf5-5e2da4c07c95"}]}}}, {"policy": "ecc-aws-257", "resource_type": "Amazon EMR", "description": "Kerberos authentication is not enabled for EMR clusters\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"ClusterArn": "f21fd550-8c02-4130-832c-f2d1b1d35a4d"}]}}}, {"policy": "ecc-aws-258", "resource_type": "Amazon EMR", "description": "At-rest and in-transit encryption is not enabled for EMR clusters\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"ClusterArn": "71a11b89-8fc5-4c03-8712-ec472601ac0f"}]}}}, {"policy": "ecc-aws-259", "resource_type": "Amazon EMR", "description": "EMR clusters are not in VPC\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"ClusterArn": "3ecf10f1-425b-49a5-8e33-97711e641403"}]}}}, {"policy": "ecc-aws-260", "resource_type": "Amazon EMR", "description": "Logging is not enabled for EMR clusters\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"ClusterArn": "3318c9f3-29a8-4330-ab73-658cc61ead11"}]}}}, {"policy": "ecc-aws-261", "resource_type": "Amazon Virtual Private Cloud", "description": "Unused Internet Gateways are not removed\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"InternetGatewayId": "1703718e-1276-49de-96b1-ea5e7e7707a3", "OwnerId": "83b834d2-b0d4-40e6-8a5e-d62ed82547f5"}]}}}, {"policy": "ecc-aws-262", "resource_type": "Amazon Virtual Private Cloud", "description": "Manual acceptance is not enabled for VPC endpoints \n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"ServiceId": "aee6d151-6f43-4174-9abe-ad6c602ecf64"}]}}}, {"policy": "ecc-aws-263", "resource_type": "Amazon Virtual Private Cloud", "description": "Unused Virtual Private Gateways is not removed\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"VpnGatewayId": "3f3e86f4-e0af-4868-91b9-6cbf5702e8cd"}]}}}, {"policy": "ecc-aws-264", "resource_type": "Amazon ElastiCache", "description": "Elasticache is using default ports\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"ARN": "ff793976-d728-4119-abe1-d98c10c52359"}]}}}, {"policy": "ecc-aws-265", "resource_type": "Amazon ElastiCache", "description": "Elasticache is not using last generation nodes\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"ARN": "dcb6a438-c8c0-4830-9dd6-232fca1ff66b"}]}}}, {"policy": "ecc-aws-266", "resource_type": "Amazon ElastiCache", "description": "ElastiCache Redis cluster automatic backups are not enabled or a retention period is not set to at least 7 days\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"ARN": "7d45a63f-0ed1-41df-a5fc-5b7608271339"}]}}}, {"policy": "ecc-aws-267", "resource_type": "Amazon ElastiCache", "description": "ElastiCache is not encrypted in transit\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"ARN": "3b1e325b-0a9c-4dd7-ac5d-e57923d74375"}]}}}, {"policy": "ecc-aws-268", "resource_type": "Amazon ElastiCache", "description": "Elasticache Redis replication group is not encrypted at-rest with KMS CMK\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"ARN": "52fe72f2-1c24-48fd-b5fb-ce924d8a5236"}]}}}, {"policy": "ecc-aws-269", "resource_type": "Amazon ElastiCache", "description": "Elasticache is using default VPC\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"ARN": "8f35e28f-e68b-484f-89cd-20d87b7bd591"}]}}}, {"policy": "ecc-aws-270", "resource_type": "Amazon ElastiCache", "description": "Elasticache Redis Multi-AZ is not enabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"ARN": "8078c035-e0da-4fdc-b156-750898cd3519"}]}}}, {"policy": "ecc-aws-271", "resource_type": "Amazon ElastiCache", "description": "Elasticache redis Auth is not enabled\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"ARN": "0e7131c9-a4dd-43cf-9e9c-2e6a59771534"}]}}}, {"policy": "ecc-aws-272", "resource_type": "Amazon ElastiCache", "description": "Elasticache is not using the latest version\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"ARN": "bc099dd8-aa93-4f6b-a068-be00f1c5f3b2"}]}}}, {"policy": "ecc-aws-273", "resource_type": "Amazon DocumentDB", "description": "DocumentDB logging is not enabled\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"DBClusterArn": "38f6f9a2-7090-483e-b971-798fa550b682"}]}}}, {"policy": "ecc-aws-274", "resource_type": "Amazon Relational Database Service", "description": "Aurora cluster logging is disabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DBClusterArn": "513c0425-61ef-48de-a0af-5ba5b633caee"}]}}}, {"policy": "ecc-aws-275", "resource_type": "Amazon Relational Database Service", "description": "Aurora-MySQL cluster logging is disabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DBClusterArn": "d9e8dcbe-9512-4930-a27c-d9fadfd446fa"}]}}}, {"policy": "ecc-aws-276", "resource_type": "Amazon Relational Database Service", "description": "Aurora-PostgreSQL cluster logging is disabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DBClusterArn": "b3922f3b-1554-4db4-8e08-012b140ad61f"}]}}}, {"policy": "ecc-aws-277", "resource_type": "Amazon OpenSearch Service", "description": "Elasticsearch slow logs is disabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"ARN": "3734c29a-f53b-4508-95fc-8b681b79be5d"}]}}}, {"policy": "ecc-aws-278", "resource_type": "AWS Identity and Access Management Access Analyzer", "description": "IAM Access Analyzer findings are not reviewed and resolved\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"id": "641e13e8-68d2-4dfd-9413-e9512f408725", "c7n-service:region": "eu-west-1"}]}}}, {"policy": "ecc-aws-279", "resource_type": "Amazon ElastiCache", "description": "Elasticache AUTH token is not rotated every 90 days\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"ARN": "baf3d866-ae46-4ce1-ba99-b418facadb65"}]}}}, {"policy": "ecc-aws-280", "resource_type": "Amazon OpenSearch Service", "description": "ElasticSearch is not encrypted with KMS CMK\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"ARN": "8128855f-2bc3-4667-ad58-96b414029531"}]}}}, {"policy": "ecc-aws-281", "resource_type": "Amazon EC2 Auto Scaling", "description": "Auto Scaling Groups are not utilizing cooldown period\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"AutoScalingGroupARN": "e77792e4-b049-4442-b871-89f708ba6cf6"}]}}}, {"policy": "ecc-aws-282", "resource_type": "Amazon OpenSearch Service", "description": "Elasticsearch does not enforce HTTPS\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"ARN": "b9f0acbf-a682-49d2-adc9-8cea8513bad4"}]}}}, {"policy": "ecc-aws-283", "resource_type": "Amazon OpenSearch Service", "description": "ElasticSearch is not using the latest OpenSearch version\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"ARN": "2a978ca1-b794-4de6-80aa-9e470ad4337e"}]}}}, {"policy": "ecc-aws-284", "resource_type": "Amazon EC2 Auto Scaling", "description": "Auto Scaling Groups does not have an associated Elastic Load Balancers or Target Groups\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"AutoScalingGroupARN": "0d3a674c-6ec7-4b07-bf79-ec356ab5812a"}]}}}, {"policy": "ecc-aws-285", "resource_type": "AWS X-Ray", "description": "AWS X-Ray is not encrypted using KMS CMK \n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"c7n-service:region": "eu-west-1"}]}}}, {"policy": "ecc-aws-286", "resource_type": "Amazon WorkSpaces Family", "description": "Unused Workspaces instances are not removed\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"WorkspaceId": "b71b131c-561a-4954-b83c-d930a0019c61"}]}}}, {"policy": "ecc-aws-287", "resource_type": "Amazon EC2 Auto Scaling", "description": "Auto Scaling Groups do not utilize multiple Availability Zones\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"AutoScalingGroupARN": "eeafa3e0-2f57-4aa5-b728-9295285174a4"}]}}}, {"policy": "ecc-aws-288", "resource_type": "Amazon WorkSpaces Family", "description": "Workspaces instances are unhealthy\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"WorkspaceId": "bde2d323-05aa-4e01-9801-bfd5ce28ad27"}]}}}, {"policy": "ecc-aws-289", "resource_type": "Amazon EC2 Auto Scaling", "description": "Auto Scaling Group has invalid configuration\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"AutoScalingGroupARN": "025e05d0-1a24-4aef-bede-3c7e4e2a7d7c"}]}}}, {"policy": "ecc-aws-290", "resource_type": "Amazon WorkSpaces Family", "description": "Workspaces storage is not encrypted\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"WorkspaceId": "e6b8c268-0490-4743-b14b-314e61c347eb"}]}}}, {"policy": "ecc-aws-291", "resource_type": "AWS Backup", "description": "Amazon Backup plan has a non-compliant lifecycle configuration\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"BackupPlanName": "cfc3d344-d7a9-464b-9533-2aee37063b28", "BackupPlanId": "456928be-0dcf-4371-873c-ac163d1389ed"}]}}}, {"policy": "ecc-aws-292", "resource_type": "AWS Elastic Beanstalk", "description": "Elastic Beanstalk environments with application load balancer do not have access logs enabled\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"EnvironmentArn": "8001d0b5-4fe1-43e8-88b6-d9489de14fd9"}]}}}, {"policy": "ecc-aws-293", "resource_type": "AWS Backup", "description": "Backup vaults are not encrypted at rest using KMS CMK\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"BackupVaultArn": "cf353736-db47-4987-8d88-8f0ee96219a8"}]}}}, {"policy": "ecc-aws-294", "resource_type": "AWS Elastic Beanstalk", "description": "Elastic Beanstalk environments notifications disabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"EnvironmentArn": "6577a7ec-8fec-4ae6-8ddf-ab00bd0801e5"}]}}}, {"policy": "ecc-aws-295", "resource_type": "Amazon CloudFront", "description": "Cloudfront origin uses not latest SSL certificate\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"ARN": "dff64cc5-4bea-4cf7-806b-7828bdf787ec"}]}}}, {"policy": "ecc-aws-296", "resource_type": "Amazon Relational Database Service", "description": "RDS MySQL instances are not using latest major version\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "a0488d61-b430-4a2f-b956-177757bcd12c"}]}}}, {"policy": "ecc-aws-297", "resource_type": "AWS Elastic Beanstalk", "description": "Elastic Beanstalk managed platform updates is disabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"EnvironmentArn": "7d79c817-3fb7-4dbc-843f-643fce6e78f0"}]}}}, {"policy": "ecc-aws-298", "resource_type": "Amazon Simple Queue Service", "description": "Ensure SQS is not encrypted with KMS CMK\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"QueueArn": "ee423549-3266-445e-8f02-f1e0b5eb6fff"}]}}}, {"policy": "ecc-aws-299", "resource_type": "Amazon CloudFront", "description": "CloudFront distributions do not enforce field-level encryption\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"ARN": "8fdfcda2-fd6f-42bf-819d-ee58504ade3b"}]}}}, {"policy": "ecc-aws-300", "resource_type": "Amazon Simple Queue Service", "description": "SQS queue is open to everyone\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"QueueArn": "bf72b4fd-aff8-483f-8b5d-29fd6e761f65"}]}}}, {"policy": "ecc-aws-301", "resource_type": "Amazon Simple Queue Service", "description": "SQS Queue dead letter queue is disabled\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"QueueArn": "c2d224ac-1931-4a89-9444-ae66e0aaee3f"}]}}}, {"policy": "ecc-aws-302", "resource_type": "Amazon Relational Database Service", "description": "The 'log_parser_stats' flag is enabled for PostgreSQL\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "c3ceb8df-ad63-46c5-8638-c98e267d6afa"}]}}}, {"policy": "ecc-aws-303", "resource_type": "AWS CloudTrail", "description": "Management events are not included into CloudTrail trails configuration\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"TrailARN": "ea503101-74b9-42eb-930d-9a1e01385d4b"}]}}}, {"policy": "ecc-aws-304", "resource_type": "Amazon EventBridge", "description": "AWS CloudWatch event bus is exposed to everyone\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"Arn": "ce9708cd-bd61-47d9-a9ce-03668cec114c"}]}}}, {"policy": "ecc-aws-305", "resource_type": "Amazon Relational Database Service", "description": "The 'log_planner_stats' flag is enabled for PostgreSQL\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "ec313d5f-3879-49d7-a67d-f633be5b4ee6"}]}}}, {"policy": "ecc-aws-306", "resource_type": "Amazon Relational Database Service", "description": "The 'log_executor_stats' flag is enabled for PostgreSQL\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "2ca231c3-f155-4114-b9c4-40846977539a"}]}}}, {"policy": "ecc-aws-307", "resource_type": "Amazon Relational Database Service", "description": "The 'log_min_error_statement' flag is not set correctly for PostgreSQL\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "3e82410c-e912-4d61-9a84-901090b7f742"}]}}}, {"policy": "ecc-aws-308", "resource_type": "Amazon S3 Glacier", "description": "Glacier Vault policy allows actions from all principals\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"VaultARN": "eb7c5637-7173-40b5-9630-2c0c0bd968a6"}]}}}, {"policy": "ecc-aws-309", "resource_type": "AWS Config", "description": "Amazon Config recorder is failing\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"name": "346f9e5e-8f55-4655-8101-d0fb4603199f", "roleARN": "3a2b8907-a989-44c6-aae4-3ac4ab1bdd06"}]}}}, {"policy": "ecc-aws-310", "resource_type": "AWS Database Migration Service", "description": "DMS replication instances are not using latest version\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"ReplicationInstanceArn": "72f87ee0-f64c-4430-b9f3-9396b50faba1"}]}}}, {"policy": "ecc-aws-311", "resource_type": "Amazon SageMaker", "description": "Ensure Sagemaker instances are not encrypted with KMS CMK\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"NotebookInstanceArn": "926f3b50-5322-4359-b90b-4313b99025e4"}]}}}, {"policy": "ecc-aws-312", "resource_type": "AWS Database Migration Service", "description": "Amazon DMS replication instances Auto Minor Version Upgrade feature disabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"ReplicationInstanceArn": "c00c209f-0921-4ebf-b24e-23a94010a266"}]}}}, {"policy": "ecc-aws-313", "resource_type": "AWS Database Migration Service", "description": "Amazon DMS replication instances not encrypted with KMS CMK\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"ReplicationInstanceArn": "d22d6d62-429b-45e5-b2ea-15b2e8c6272b"}]}}}, {"policy": "ecc-aws-314", "resource_type": "Amazon Relational Database Service", "description": "The 'audit_sys_operations' flag for Oracle is disabled\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "953ab4a8-c53b-4b39-a30a-8cf899d7727b"}]}}}, {"policy": "ecc-aws-315", "resource_type": "Amazon Relational Database Service", "description": "The 'audit_trail' flag is not set correctly for Oracle\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "a6328717-9067-4119-81e7-2f1efd65af35"}]}}}, {"policy": "ecc-aws-316", "resource_type": "Amazon Relational Database Service", "description": "The 'global_names' flag for Oracle is disabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "36d8c3d0-4669-4149-aa6e-d3c6faba696d"}]}}}, {"policy": "ecc-aws-317", "resource_type": "Amazon Relational Database Service", "description": "The 'remote_listener' flag for Oracle is not empty\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "2a84bf52-e3b2-45ec-a4aa-0a0fba4a60da"}]}}}, {"policy": "ecc-aws-318", "resource_type": "Amazon Relational Database Service", "description": "The 'sec_max_failed_login_attempts' flag for Oracle is not set to 3 or less\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "b4716014-b97b-469b-8436-bba2c07373e8"}]}}}, {"policy": "ecc-aws-319", "resource_type": "Amazon Relational Database Service", "description": "The 'sec_protocol_error_further_action' flag for Oracle is not set to '(DROP,3)'\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "0b66fff2-c188-4c92-8b19-fc1158193828"}]}}}, {"policy": "ecc-aws-320", "resource_type": "Amazon Relational Database Service", "description": "The 'sec_protocol_error_trace_action' flag for Oracle is not set to 'LOG'\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "7dc2680a-ab98-4e76-bb80-c6c56a8452b3"}]}}}, {"policy": "ecc-aws-321", "resource_type": "Amazon Relational Database Service", "description": "The 'sec_return_server_release_banner' flag for Oracle is enabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "ca6744c4-a94a-4795-8743-dac6f8fa5e00"}]}}}, {"policy": "ecc-aws-322", "resource_type": "Amazon Relational Database Service", "description": "The 'sql92_security' flag for Oracle is disabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "60b0c056-9c42-4938-935f-17ae6cc5dd03"}]}}}, {"policy": "ecc-aws-323", "resource_type": "Amazon Relational Database Service", "description": "The '_trace_files_public' flag for Oracle is enabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "aa272f25-c970-40fd-97f7-156d5411c754"}]}}}, {"policy": "ecc-aws-324", "resource_type": "Amazon Relational Database Service", "description": "The 'resource_limit' flag for Oracle is disabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "afea2df0-6ffe-47a1-8e4a-b25cb021e473"}]}}}, {"policy": "ecc-aws-325", "resource_type": "AWS Database Migration Service", "description": "Amazon DMS replication instances do not have the Multi-AZ feature enabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"ReplicationInstanceArn": "fc4e9594-c184-4338-8bcc-547c1e08a785"}]}}}, {"policy": "ecc-aws-326", "resource_type": "Amazon Elastic Block Store", "description": "EBS volume not encrypted with KMS CMK\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"VolumeId": "0f5bf479-0f24-44d8-a41e-489fb9e2407b"}]}}}, {"policy": "ecc-aws-327", "resource_type": "Amazon Elastic Block Store", "description": "EBS snapshot encryption is disabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{}]}}}, {"policy": "ecc-aws-328", "resource_type": "Amazon Elastic Block Store", "description": "Unused EBS volumes exist\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"VolumeId": "a865bd41-317c-4d9f-adf4-b4a9eced1b17"}]}}}, {"policy": "ecc-aws-329", "resource_type": "Amazon EC2", "description": "Unused key pairs exist\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"KeyPairId": "67af1062-0be5-4061-80d0-bffb5edc0900"}]}}}, {"policy": "ecc-aws-330", "resource_type": "Amazon Relational Database Service", "description": "The 'sql_mode' flag for MySQL not contains 'strict_all_tables'\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "b668743a-d1a8-43c7-9165-0356292760c4"}]}}}, {"policy": "ecc-aws-331", "resource_type": "Amazon WorkSpaces Family", "description": "Workspaces images are older than 90 days \n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"ImageId": "08a4d72b-b452-4cb5-bbc9-ab5671407766"}]}}}, {"policy": "ecc-aws-332", "resource_type": "Amazon WorkSpaces Family", "description": "Workspaces web access is enabled \n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DirectoryId": "cdef83c8-a948-4e4c-aa4d-7c6b96fb3b26"}]}}}, {"policy": "ecc-aws-333", "resource_type": "Amazon FSx", "description": "AWS FSx file system is not encrypted with KMS CMK\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"ResourceARN": "ad1a7468-58ff-4976-ac65-49ea1d03207d"}]}}}, {"policy": "ecc-aws-334", "resource_type": "Amazon Kinesis", "description": "Kinesis Data Firehose delivery streams are not encrypted using Server-side encryption\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"DeliveryStreamARN": "e04218af-a99e-4c0d-bf58-ee0a358efd5a"}]}}}, {"policy": "ecc-aws-335", "resource_type": "AWS Lambda", "description": "Lambda has active tracing disabled\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{}]}}}, {"policy": "ecc-aws-336", "resource_type": "Amazon SageMaker", "description": "Sagemaker endpoint configurations are not encrypted with KMS CMK\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"EndpointConfigArn": "8358c649-6648-4ffa-bd39-8c75df267193"}]}}}, {"policy": "ecc-aws-337", "resource_type": "AWS Lambda", "description": "Lambda environment variables not encrypted with KMS CMK\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"FunctionArn": "271d4c07-6971-429b-b8d8-12eababae711"}]}}}, {"policy": "ecc-aws-338", "resource_type": "Amazon SageMaker", "description": "Sagemaker instances root access enabled\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"NotebookInstanceArn": "cf4d1a73-e03d-4812-b4ae-151fae9a7a83"}]}}}, {"policy": "ecc-aws-339", "resource_type": "Amazon MQ", "description": "MQ auto minor version upgrade not enabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"BrokerArn": "7e21021f-ab73-4353-9824-685591d434e5"}]}}}, {"policy": "ecc-aws-340", "resource_type": "Amazon MQ", "description": "MQ broker logging not enabled\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"BrokerArn": "ff0e1dda-d4fc-4844-b23c-1a069774d256"}]}}}, {"policy": "ecc-aws-341", "resource_type": "Amazon SageMaker", "description": "Sagemaker model network isolation disabled \n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"ModelArn": "37b688a2-db58-48f5-804b-d5c513d991be"}]}}}, {"policy": "ecc-aws-342", "resource_type": "Amazon Route 53", "description": "Route53 has automatic domain renewal disabled\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"DomainName": "55d6fe80-e87a-47ea-9b5f-456f14840424"}]}}}, {"policy": "ecc-aws-343", "resource_type": "Amazon MQ", "description": "MQ is publicly accessible\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"BrokerArn": "f0c7718c-737a-4ac5-8c5a-bf768bb08a74"}]}}}, {"policy": "ecc-aws-344", "resource_type": "Amazon Route 53", "description": "Route53 domain name expire in less 30 days\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"DomainName": "3ce2be8f-1eb8-4aa4-9fb6-19bc7a4bf377"}]}}}, {"policy": "ecc-aws-345", "resource_type": "Amazon MQ", "description": "Mq broker not restricted only to default ports\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"BrokerArn": "26a33c50-1b7a-490b-a715-01a1bb173665"}]}}}, {"policy": "ecc-aws-346", "resource_type": "Amazon Route 53", "description": "Route53 hosted zone records is not configured with health check\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"c7n:parent-id": "2b0aef5b-6179-4469-89b7-d5f7e4833d9b", "Name": "24ac2b91-3e9f-4749-adf9-f52deded0ae5", "Type": "3a51370e-badf-4927-95e6-8a7d50d0f88a", "SetIdentifier": "93474a4b-5b8f-4643-89d8-fc14ca536918"}]}}}, {"policy": "ecc-aws-347", "resource_type": "Amazon Managed Streaming for Apache Kafka", "description": "MSK not encrypted with KMS CMK\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"ClusterArn": "c84b952c-0514-4b73-b563-46af24ae80f5"}]}}}, {"policy": "ecc-aws-348", "resource_type": "Amazon Managed Streaming for Apache Kafka", "description": "MSK encryption in transit not set only to 'TLS'.\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"ClusterArn": "0c52e58e-9596-479a-a949-3f17ed5a996b"}]}}}, {"policy": "ecc-aws-349", "resource_type": "Amazon Route 53", "description": "Route53 query logging not enabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"Id": "10eed707-780a-484a-b190-588f22cc5f4a", "Name": "1e6ff34a-8794-4ee2-8a79-1d66cce95b4a"}]}}}, {"policy": "ecc-aws-350", "resource_type": "Amazon Managed Streaming for Apache Kafka", "description": "MSK Logging not enabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"ClusterArn": "2b4e8dbf-2d5b-4961-9930-20a0982877ad"}]}}}, {"policy": "ecc-aws-351", "resource_type": "Amazon Relational Database Service", "description": "RDS instances are not encrypted with KMS CMK\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "0f3fe4d2-0fa5-4122-b577-ab9c7f98c2cd"}]}}}, {"policy": "ecc-aws-352", "resource_type": "Amazon Simple Notification Service", "description": "SNS topics are not encrypted at rest using KMS CMK\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"TopicArn": "18463dfd-a672-4a3d-a63a-17d2654f9db3"}]}}}, {"policy": "ecc-aws-353", "resource_type": "Amazon Redshift", "description": "AWS Redshift user activity logging is disabled\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"ClusterIdentifier": "aefd515f-734d-4eb5-9c50-874b163588fa"}]}}}, {"policy": "ecc-aws-354", "resource_type": "Amazon Redshift", "description": "Amazon Redshift uses default port 5439\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"ClusterIdentifier": "092e2223-ddd2-4b31-aa9f-83421f103b15"}]}}}, {"policy": "ecc-aws-355", "resource_type": "Amazon Redshift", "description": "AWS Redshift instances are not encrypted with KMS CMK\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"ClusterIdentifier": "96970797-a87a-4cea-9ef3-72fb27bc7d07"}]}}}, {"policy": "ecc-aws-356", "resource_type": "Amazon Redshift", "description": "AWS Redshift parameter group not require SSL\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"ClusterIdentifier": "d950989f-4ec0-495b-b583-008d1aea93bf"}]}}}, {"policy": "ecc-aws-357", "resource_type": "Amazon Route 53", "description": "Route 53 domain Transfer Lock is disabled\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"DomainName": "7a7b2f84-34e2-48d7-bc7d-c039500fa486"}]}}}, {"policy": "ecc-aws-358", "resource_type": "AWS CloudTrail", "description": "CloudTrail Global Services disabled\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"c7n-service:region": "eu-west-1"}]}}}, {"policy": "ecc-aws-359", "resource_type": "Amazon API Gateway", "description": "API Gateway REST API have access logging disabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"stageName": "4b1f9bff-f42b-426b-82cd-ffa1277fcb0e"}]}}}, {"policy": "ecc-aws-360", "resource_type": "Amazon Elastic Container Service", "description": "ECS Cluster execute command logging encryption is disabled\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"clusterArn": "2d2b271d-2391-4af2-8fb1-751f9ce12cc6"}]}}}, {"policy": "ecc-aws-361", "resource_type": "Amazon API Gateway", "description": "API Gateway REST API does not have logging correctly configured\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"stageName": "12820a45-d8ca-4091-9a70-3cdf6933514a"}]}}}, {"policy": "ecc-aws-362", "resource_type": "Amazon Managed Workflows for Apache Airflow", "description": "Managed Workflows for Apache Airflow data is not encrypted with KMS CMK\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"Arn": "87388c9f-54ab-460b-afbc-e3a1d85b21c6"}]}}}, {"policy": "ecc-aws-363", "resource_type": "Amazon Kinesis", "description": "AWS Kinesis Video Streams are not encrypted with KMS customer master keys\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"StreamARN": "44b59f02-547f-43a7-a209-e14eb36d53bc"}]}}}, {"policy": "ecc-aws-364", "resource_type": "Amazon EC2 Auto Scaling", "description": "Auto Scaling launch configuration public ip association is enabled\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"LaunchConfigurationName": "8d2c36c6-48b0-4a59-a372-3f1f7dda9575"}]}}}, {"policy": "ecc-aws-365", "resource_type": "AWS Glue", "description": "Glue connection password is not encrypted\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"CatalogId": "bcdef47d-6f31-4ed3-be38-76ef19f0ba47", "c7n-service:region": "eu-west-1"}]}}}, {"policy": "ecc-aws-366", "resource_type": "Amazon FSx", "description": "FSx Lustre file logging is disabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{}]}}}, {"policy": "ecc-aws-367", "resource_type": "AWS Directory", "description": "DS directory is open to a large scope\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"DirectoryId": "98a1fac4-6ac6-417f-a950-0660756269c1"}]}}}, {"policy": "ecc-aws-368", "resource_type": "Amazon FSx", "description": "FSx Lustre file system does not have retention period set at least to 7 days\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"ResourceARN": "988bfaae-808d-48ec-8ed0-ac9ef5712ea6"}]}}}, {"policy": "ecc-aws-369", "resource_type": "Amazon WorkSpaces Family", "description": "CloudWatch Events is not set up for successful logins to WorkSpaces\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"account_id": "54a744d3-ec94-4aa1-9432-20058507ef81", "c7n-service:region": "eu-west-1"}]}}}, {"policy": "ecc-aws-370", "resource_type": "Amazon WorkSpaces Family", "description": "Workspaces maintenance mode disabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DirectoryId": "760e175c-a46f-4813-bb36-39aa3cfce0d1"}]}}}, {"policy": "ecc-aws-371", "resource_type": "Amazon WorkSpaces Family", "description": "Primary interface ports for Workspaces are open to all inbound traffic\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"WorkspaceId": "d56ff33d-845e-47da-b950-fcd0e6b53d72"}]}}}, {"policy": "ecc-aws-372", "resource_type": "Amazon WorkSpaces Family", "description": "WorkSpaces API requests do not flow through a VPC Endpoint\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DirectoryId": "6bde7ace-ee8e-428e-977f-dc7d0fc0b2ab"}]}}}, {"policy": "ecc-aws-373", "resource_type": "Amazon WorkSpaces Family", "description": "Radius server is not using the recommended strongest security protocol \n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"DirectoryId": "64c3cf0f-552e-4467-808e-96c4ed8d5e29"}]}}}, {"policy": "ecc-aws-374", "resource_type": "AWS CloudTrail", "description": "Data events are not included into Amazon CloudTrail trails configuration\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"TrailARN": "4661238f-9b6b-447a-bc79-ffd2a3f0d9c2"}]}}}, {"policy": "ecc-aws-375", "resource_type": "Amazon WorkSpaces Family", "description": "Workspaces storage is not encrypted with KMS CMK\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"WorkspaceId": "e7d15a2d-934e-4050-b67b-f2e9b13eba0a"}]}}}, {"policy": "ecc-aws-376", "resource_type": "Amazon API Gateway", "description": "API Gateway HTTP and WEBSOCKET API does not have logging enabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"restApiId": "74a1886d-e640-4b37-b2cf-e8b28e8aa7db", "StageName": "45c81fd1-ac11-4b90-b7a6-e6080f5360ef"}]}}}, {"policy": "ecc-aws-377", "resource_type": "Amazon EC2", "description": "AMI without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"ImageId": "e3f876ee-121c-4409-b772-026860e95bae"}]}}}, {"policy": "ecc-aws-378", "resource_type": "Amazon Elastic Block Store", "description": "EBS volumes without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"VolumeId": "943c4198-2834-4684-8b2f-b1dd1a3fdded"}]}}}, {"policy": "ecc-aws-379", "resource_type": "Amazon Elastic Block Store", "description": "EBS snapshot without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"SnapshotId": "59680382-0fc6-4001-a856-5137039e3f54"}]}}}, {"policy": "ecc-aws-380", "resource_type": "Amazon EC2", "description": "EIP without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"AllocationId": "e1be238b-d6b2-4f53-ae82-ce59adf12034"}]}}}, {"policy": "ecc-aws-381", "resource_type": "Amazon EC2", "description": "ENI without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"NetworkInterfaceId": "413d86ca-06db-4bc7-aa20-3cc1bf317c15"}]}}}, {"policy": "ecc-aws-382", "resource_type": "Amazon Virtual Private Cloud", "description": "Amazon Internet Gateway without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"InternetGatewayId": "9a346f72-3070-4ec5-9ba0-f6f9b676e9a0"}]}}}, {"policy": "ecc-aws-383", "resource_type": "Amazon Virtual Private Cloud", "description": "Amazon Nat Gateway without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"NatGatewayId": "bde7d900-4bd9-4783-92bb-e9da947cbb52"}]}}}, {"policy": "ecc-aws-384", "resource_type": "Amazon Virtual Private Cloud", "description": "Amazon Network ACLs without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"NetworkAclId": "8810d16a-64ae-4e59-88f0-f0f56d24bd3e", "OwnerId": "6229c739-8fef-4809-99c4-e14e959241b3"}]}}}, {"policy": "ecc-aws-385", "resource_type": "Amazon Virtual Private Cloud", "description": "Amazon Route table without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"RouteTableId": "25de385e-2130-4ca7-9e0b-3a5c61c3c6b7"}]}}}, {"policy": "ecc-aws-386", "resource_type": "Amazon EC2", "description": "Security group without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"GroupId": "161ee990-3209-4508-ac25-dc0e3959ed3c"}]}}}, {"policy": "ecc-aws-387", "resource_type": "Amazon Virtual Private Cloud", "description": "Amazon Subnet without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"SubnetId": "8897c26f-ec1f-4c36-9699-354b5b24f406"}]}}}, {"policy": "ecc-aws-388", "resource_type": "AWS Transit Gateway", "description": "Amazon Transit gateway without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{}]}}}, {"policy": "ecc-aws-389", "resource_type": "AWS Transit Gateway", "description": "Amazon Transit gateway attachment without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"TransitGatewayAttachmentId": "9c1a6893-a599-4126-ac2c-cd2a3d042c29"}]}}}, {"policy": "ecc-aws-390", "resource_type": "Amazon Virtual Private Cloud", "description": "Amazon peering connection without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"VpcPeeringConnectionId": "67fcceff-cf00-41ca-a874-466481338e66"}]}}}, {"policy": "ecc-aws-391", "resource_type": "Amazon Virtual Private Cloud", "description": "VPC without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"VpcId": "c4125ab4-9169-4b9b-b348-8c614f210e8c"}]}}}, {"policy": "ecc-aws-392", "resource_type": "Amazon Virtual Private Cloud", "description": "VPC endpoint without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"VpcEndpointId": "3d973ebe-d5d5-4d81-82c8-ec7adc113285"}]}}}, {"policy": "ecc-aws-393", "resource_type": "AWS Certificate Manager", "description": "Amazon ACM without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"CertificateArn": "846826bf-bf9a-496d-a206-05b95bcba22e", "DomainName": "b7a90f64-a007-44d9-9eff-9b7ab6e056bd"}]}}}, {"policy": "ecc-aws-394", "resource_type": "Amazon AppFlow", "description": "Amazon AppFlow without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"flowArn": "4ee29d82-eda6-44fd-990e-a3a128178b33"}]}}}, {"policy": "ecc-aws-395", "resource_type": "Amazon EC2 Auto Scaling", "description": "Auto Scaling Group without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"AutoScalingGroupARN": "700cd465-cc1a-408c-a953-3939adc20c2a"}]}}}, {"policy": "ecc-aws-396", "resource_type": "AWS CloudFormation", "description": "Amazon cloudformation stacks without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{}]}}}, {"policy": "ecc-aws-397", "resource_type": "Amazon CloudFront", "description": "Cloudfront distributions without tag information\n", "severity": "Medium", "regions_data": {"multiregion": {"resources": [{"ARN": "ad08d7dd-0cf4-455d-8ac0-59978a080766"}]}}}, {"policy": "ecc-aws-398", "resource_type": "AWS CloudTrail", "description": "Cloudtrail without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"TrailARN": "4b0beb55-57eb-4a64-ab80-997c6774adc4"}]}}}, {"policy": "ecc-aws-399", "resource_type": "AWS CodeBuild", "description": "Amazon Codebuikd without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"arn": "6bb6263e-07ee-49e7-aae5-fe2297c094af"}]}}}, {"policy": "ecc-aws-400", "resource_type": "Amazon DynamoDB Accelerator", "description": "DynamoDB Accelerator clusters without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"ClusterArn": "df49afd2-1212-4ddb-b099-bba681d7525d"}]}}}, {"policy": "ecc-aws-401", "resource_type": "Amazon Data Lifecycle Manager", "description": "AWS DLM lifecycle policy without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"PolicyId": "6ec921f2-6db6-45e8-97a4-e61fdc6f8455"}]}}}, {"policy": "ecc-aws-402", "resource_type": "AWS Database Migration Service", "description": "Amazon DMS instance without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"ReplicationInstanceIdentifier": "f9266498-df72-478d-89e0-8e271b280963", "ReplicationInstanceArn": "77717486-2a92-4728-877d-0eca18915707"}]}}}, {"policy": "ecc-aws-403", "resource_type": "Amazon Elastic Container Service", "description": "Amazon ECS cluster without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"clusterArn": "1dafbb09-4c73-4f4a-bee3-95c69460fcbe"}]}}}, {"policy": "ecc-aws-404", "resource_type": "Amazon Elastic Kubernetes Service", "description": "Amazon EKS without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"arn": "1d2aa220-66be-48a2-bed5-c5bbddbfbdec"}]}}}, {"policy": "ecc-aws-405", "resource_type": "Amazon Elastic File System", "description": "Amazon EFS without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"FileSystemArn": "45a498d4-24bf-459b-825e-4f553a911096", "OwnerId": "b0034154-a08a-474b-831c-ecddecca7a29"}]}}}, {"policy": "ecc-aws-406", "resource_type": "Amazon ElastiCache", "description": "Elasticache without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"ARN": "8eddfd6c-7a33-4dc1-a190-a4bca90ea6be"}]}}}, {"policy": "ecc-aws-407", "resource_type": "AWS Elastic Beanstalk", "description": "Amazon Beanstalk topic without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{}]}}}, {"policy": "ecc-aws-408", "resource_type": "Amazon Elastic Load Balancing", "description": "Amazon ELB without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{}]}}}, {"policy": "ecc-aws-409", "resource_type": "Amazon EMR", "description": "Amazon EMR clusters without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"ClusterArn": "24441755-fa17-41bb-bf0f-fe7ca841b6bb"}]}}}, {"policy": "ecc-aws-410", "resource_type": "Amazon OpenSearch Service", "description": "Amazon ElasticSearch clusters without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"ARN": "5dd5789b-0972-4040-8d96-aba87ff5660c"}]}}}, {"policy": "ecc-aws-411", "resource_type": "Amazon FSx", "description": "Amazon FSX without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"ResourceARN": "57d08f8b-9c13-40ed-8d1f-66dfa24f52b9"}]}}}, {"policy": "ecc-aws-412", "resource_type": "Amazon FSx", "description": "Amazon FSX Lustre backup without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"FileSystemId": "e15087f9-8536-4075-9acd-552faaa568ab"}]}}}, {"policy": "ecc-aws-413", "resource_type": "Amazon S3 Glacier", "description": "Amazon Glacier without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"VaultARN": "47ff8d33-a311-48dd-8a71-c88af5025086"}]}}}, {"policy": "ecc-aws-414", "resource_type": "AWS Glue", "description": "Amazon Glue Job without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"Name": "f4212040-427f-4067-82a7-bb6eb5ae1dcb"}]}}}, {"policy": "ecc-aws-415", "resource_type": "AWS Identity and Access Management", "description": "IAM User without tag information\n", "severity": "Medium", "regions_data": {"multiregion": {"resources": [{"Arn": "02e6c5e8-b6ea-4e47-a210-9e1c7b974584"}]}}}, {"policy": "ecc-aws-416", "resource_type": "AWS Identity and Access Management", "description": "IAM Role without tag information\n", "severity": "Medium", "regions_data": {"multiregion": {"resources": [{"Arn": "9abed709-1a76-4019-ba2e-65c8f00b3c0a"}]}}}, {"policy": "ecc-aws-417", "resource_type": "Amazon Managed Streaming for Apache Kafka", "description": "Amazon MSK clusters without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"ClusterArn": "d044a2e7-3d97-4ebc-836a-511ccb830d8f"}]}}}, {"policy": "ecc-aws-418", "resource_type": "Amazon Kinesis", "description": "Amazon Kinesis data stream without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{}]}}}, {"policy": "ecc-aws-419", "resource_type": "Amazon Kinesis", "description": "Amazon Kinesis video stream without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"StreamARN": "97e42c3e-4200-4574-b20f-f1f8bf16ad09"}]}}}, {"policy": "ecc-aws-420", "resource_type": "AWS Key Management Service", "description": "Customer manages key without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"KeyArn": "fedb23c2-6201-4997-a157-66b334173b4b", "AWSAccountId": "adaddb00-0e89-4876-bedd-71da8fe30cfb"}]}}}, {"policy": "ecc-aws-421", "resource_type": "AWS Lambda", "description": "Lambda functions without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"FunctionArn": "79135784-c303-492a-a0cf-466586b73b9d"}]}}}, {"policy": "ecc-aws-422", "resource_type": "Amazon Lightsail", "description": "Amazon Lightsail instance without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"arn": "dfc45b36-a33a-4376-9ad9-502a8ffcb88d"}]}}}, {"policy": "ecc-aws-423", "resource_type": "Amazon CloudWatch", "description": "Amazon Log group without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"arn": "803b7990-cab8-43bd-8641-0bda919021d0"}]}}}, {"policy": "ecc-aws-424", "resource_type": "Amazon MQ", "description": "MQ broker without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{}]}}}, {"policy": "ecc-aws-425", "resource_type": "Amazon Managed Workflows for Apache Airflow", "description": "Amazon MWAA without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"Arn": "b6ac6c4c-9cb7-4368-9d6f-364c3c2533de"}]}}}, {"policy": "ecc-aws-426", "resource_type": "Amazon QLDB", "description": "Amazon QLDB ledger without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"EnvironmentArn": "228385d6-92f3-45df-a7ef-8048343101e9"}]}}}, {"policy": "ecc-aws-427", "resource_type": "Amazon Relational Database Service", "description": "RDS cluster without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{}]}}}, {"policy": "ecc-aws-428", "resource_type": "Amazon Relational Database Service", "description": "Amazon RDS snapshot without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{}]}}}, {"policy": "ecc-aws-429", "resource_type": "Amazon Redshift", "description": "Amazon Redshift clusters without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"ClusterIdentifier": "20a1814d-3770-4396-85b1-fdc7737e09c4"}]}}}, {"policy": "ecc-aws-430", "resource_type": "Amazon SageMaker", "description": "Amazon Sagemaker instances without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"NotebookInstanceArn": "2fc591ac-bc61-49f9-8c66-aa1b2f10448b"}]}}}, {"policy": "ecc-aws-431", "resource_type": "Amazon Simple Notification Service", "description": "Amazon SNS topic without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"TopicArn": "b311fd01-7303-4497-8f26-66a5ced561a8"}]}}}, {"policy": "ecc-aws-432", "resource_type": "Amazon Simple Queue Service", "description": "Amazon SQS without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{}]}}}, {"policy": "ecc-aws-433", "resource_type": "Amazon MQ", "description": "MQ broker active deployment not enabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"BrokerArn": "558d3d83-7ac4-4d2e-87ad-c62167e31bb0"}]}}}, {"policy": "ecc-aws-434", "resource_type": "Amazon MQ", "description": "MQ broker not using latest major version\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"BrokerArn": "cf16ef6b-f26f-4ac9-b712-d159bbb78715"}]}}}, {"policy": "ecc-aws-435", "resource_type": "Amazon MQ", "description": "MQ broker not encrypted with KMS CMK\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{}]}}}, {"policy": "ecc-aws-436", "resource_type": "Amazon Kinesis", "description": "Kinesis streams shard level monitoring disabled\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"StreamARN": "b53f3f44-453e-49a7-bf40-0fcd2ffff6da"}]}}}, {"policy": "ecc-aws-437", "resource_type": "Amazon S3", "description": "S3 Bucket object lock disabled\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"Name": "90958420-7917-4490-b0cc-7d6fd58567d6"}]}}}, {"policy": "ecc-aws-438", "resource_type": "Amazon QLDB", "description": "QLDB permission mode is set to 'ALLOW_ALL'\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"EnvironmentArn": "e663eb43-e8b6-4bc1-a414-fc7566091e7d"}]}}}, {"policy": "ecc-aws-439", "resource_type": "Amazon QLDB", "description": "QLDB termination protection not enabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"EnvironmentArn": "a11e9a79-3439-474c-b23a-fc1ed97ecec8"}]}}}, {"policy": "ecc-aws-440", "resource_type": "AWS AppSync", "description": "Appsync logging disabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"arn": "47495d7e-a953-4e00-acae-9dda14688218", "name": "6a3611b6-fe20-46e9-b942-2422acd6be78"}]}}}, {"policy": "ecc-aws-441", "resource_type": "AWS AppSync", "description": "Appsync cache is not encrypted at rest\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"name": "5bd8cb2d-f73c-41fc-b790-4c5d395eb21f", "arn": "66edfe86-4266-4c5f-8f6a-b4455edf34e0"}]}}}, {"policy": "ecc-aws-442", "resource_type": "AWS AppSync", "description": "Appsync cache is not encrypted in transit\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"name": "3bc185ba-86be-42f5-99ff-76c590d25602", "arn": "8b8d6e47-4f2c-486d-8dcc-3f3c18e36d0b"}]}}}, {"policy": "ecc-aws-443", "resource_type": "AWS AppSync", "description": "Appsync is not protected by WAF\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"arn": "bed45d45-0b66-4efb-8317-d53de616cdf5", "name": "8e73df16-d56a-4feb-b75c-b0af11b5d3ed"}]}}}, {"policy": "ecc-aws-444", "resource_type": "Amazon Managed Workflows for Apache Airflow", "description": "Managed Workflows for Apache Airflow dag logs not enabled or set correctly\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"Arn": "a3da669d-425b-4d78-bc0c-61a37b83b2a3"}]}}}, {"policy": "ecc-aws-445", "resource_type": "Amazon Managed Workflows for Apache Airflow", "description": "Managed Workflows for Apache Airflow scheduler logs not enabled or set correctly\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"Arn": "b1fdca3a-c050-401b-8115-6126def4d446"}]}}}, {"policy": "ecc-aws-446", "resource_type": "Amazon Managed Workflows for Apache Airflow", "description": "Managed Workflows for Apache Airflow Task logs not enabled or set correctly\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"Arn": "e953a388-4e5f-42a7-b7bc-07a2453bcb99"}]}}}, {"policy": "ecc-aws-447", "resource_type": "Amazon Managed Workflows for Apache Airflow", "description": "Managed Workflows for Apache Airflow Webserver logs not enabled or set correctly\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"Arn": "e4e34763-d51d-4bb5-9921-088087b7413a"}]}}}, {"policy": "ecc-aws-448", "resource_type": "Amazon Managed Workflows for Apache Airflow", "description": "Managed Workflows for Apache Airflow Worker logs not enabled or set correctly\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"Arn": "7e01d42d-08fe-49e3-a0e5-0f9bfe640262"}]}}}, {"policy": "ecc-aws-449", "resource_type": "Amazon Redshift", "description": "Amazon Redshift clusters availability zone relocation not enabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"ClusterIdentifier": "214782d9-1a2e-4468-b2b0-a20978969644"}]}}}, {"policy": "ecc-aws-450", "resource_type": "AWS Elastic Beanstalk", "description": "Elastic Beanstalk IMDSv1 is enabled\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"EnvironmentArn": "02469467-7dcd-4bf4-a6d3-5f7b434d108a"}]}}}, {"policy": "ecc-aws-451", "resource_type": "AWS Elastic Beanstalk", "description": "Elastic Beanstalk X-Ray is disabled\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"EnvironmentArn": "5840f653-f0f5-430a-adec-2e04ed6448df"}]}}}, {"policy": "ecc-aws-452", "resource_type": "AWS Elastic Beanstalk", "description": "Elastic Beanstalk connection draining is disabled\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"EnvironmentArn": "bd05132b-370f-4f15-afca-fdcdccff667d"}]}}}, {"policy": "ecc-aws-453", "resource_type": "Amazon ElastiCache", "description": "Elasticache Redis logs disabled\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"ARN": "f44ef5ae-35ca-40bb-a42a-7a3d7844659b"}]}}}, {"policy": "ecc-aws-454", "resource_type": "Amazon ElastiCache", "description": "Elasticache notification disabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"ARN": "6186f26b-059c-4598-8802-62b3dcffe5e5"}]}}}, {"policy": "ecc-aws-455", "resource_type": "Amazon EMR", "description": "EMR termination protection not enabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"ClusterArn": "07d1dea8-b9c4-40be-b679-f760e0127ce7"}]}}}, {"policy": "ecc-aws-456", "resource_type": "Amazon EMR", "description": "EMR clusters imdsv1 enabled\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"ClusterArn": "18bf4e69-2962-43c2-8a5c-9e8e4da29c5f"}]}}}, {"policy": "ecc-aws-457", "resource_type": "AWS Glue", "description": "Glue job spark ui disabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"Name": "0e1b9596-dcea-4839-aa91-21daf69b35e1"}]}}}, {"policy": "ecc-aws-458", "resource_type": "AWS Lambda", "description": "Enhanced Monitoring for Lambda Functions disabled\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"FunctionArn": "0edc2b32-f92c-4205-8b6d-6a1ff6dc69c8"}]}}}, {"policy": "ecc-aws-459", "resource_type": "AWS Lambda", "description": "Lambda code signing not enabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"FunctionArn": "6379b0a2-cab4-4668-8062-f35dc2c6eb7c"}]}}}, {"policy": "ecc-aws-460", "resource_type": "AWS Lambda", "description": "Lambda environment variables are not encrypted in transit\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"FunctionArn": "6d01ca2b-9df7-4fc8-98a9-5734bf29a6e6"}]}}}, {"policy": "ecc-aws-461", "resource_type": "AWS Lambda", "description": "Lambda functions not are not using latest runtime environment versions\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"FunctionArn": "c9feb4ea-6781-4d98-94a5-0ba63e034575"}]}}}, {"policy": "ecc-aws-462", "resource_type": "AWS Lambda", "description": "Lambda reserved concurrency disabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{}]}}}, {"policy": "ecc-aws-463", "resource_type": "Amazon S3", "description": "S3 bucket is not DNS compliant\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"Name": "76e565a5-2cc5-48e6-bbe3-d2da6707a9eb"}]}}}, {"policy": "ecc-aws-464", "resource_type": "Amazon Elastic Container Service", "description": "ECS Cluster execute command logging is disabled\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"clusterArn": "3ab39df5-b46a-4729-bd97-fdcd9b0cad91"}]}}}, {"policy": "ecc-aws-465", "resource_type": "Amazon FSx", "description": "FSx file systems do not have retention period set\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"ResourceARN": "5bde604e-f4a8-4c8c-8f1b-459c59f27817"}]}}}, {"policy": "ecc-aws-466", "resource_type": "Amazon FSx", "description": "FSx for NetApp ONTAP file systems do not have Multi-AZ enabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"ResourceARN": "7885c0bd-c83a-40cc-8e43-b21bfab41503"}]}}}, {"policy": "ecc-aws-467", "resource_type": "Amazon FSx", "description": "FSx for for Windows File Server file systems do not have Multi-AZ enabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"ResourceARN": "635e2ea6-0c46-4c5f-85b5-ba5a1d145374"}]}}}, {"policy": "ecc-aws-468", "resource_type": "Amazon FSx", "description": "FSx OpenZFS file system does not copy tags to snapshots\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"ResourceARN": "315b271b-9047-42bc-923e-dffaea024752"}]}}}, {"policy": "ecc-aws-469", "resource_type": "Amazon Elastic Load Balancing", "description": "Application Load Balancers are not configured with defensive or strictest desync mitigation mode\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"LoadBalancerArn": "85d76fb8-3427-43e0-8472-e53bf19ba2f6"}]}}}, {"policy": "ecc-aws-470", "resource_type": "Amazon API Gateway", "description": "API Gateway endpoint type not set correctly\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"id": "3388826b-3758-4763-bb6e-50c94ffd7dca", "name": "ee385b78-f50e-4510-9e35-352f682b4295"}]}}}, {"policy": "ecc-aws-471", "resource_type": "Amazon EC2 Auto Scaling", "description": "Auto Scaling Groups do not use rebalacing capacity\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"AutoScalingGroupARN": "33daf002-5529-4341-a810-86869513f91c"}]}}}, {"policy": "ecc-aws-472", "resource_type": "Amazon EC2 Auto Scaling", "description": "Auto Scaling launch configuration IMDSv1 enabled\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"LaunchConfigurationName": "307b2943-17e7-4a05-89b4-b4cabf7592f2"}]}}}, {"policy": "ecc-aws-473", "resource_type": "Amazon Elastic Load Balancing", "description": "Classic Load Balancers are not configured with defensive or strictest desync mitigation mode\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"LoadBalancerArn": "25eda498-1f2d-4f55-96c6-257ff2d89ca3"}]}}}, {"policy": "ecc-aws-474", "resource_type": "Amazon Elastic Load Balancing", "description": "Classic Load Balancers are not configured with multiple Availability Zones\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"LoadBalancerArn": "05e32a2a-6cd8-45f1-953a-c01640f9e1a8"}]}}}, {"policy": "ecc-aws-475", "resource_type": "Amazon Elastic Load Balancing", "description": "Classic Load Balancers are not configured with cross-zone load balancing.\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"LoadBalancerArn": "7785dd79-bec5-4aca-98c9-9408c3863768"}]}}}, {"policy": "ecc-aws-476", "resource_type": "AWS CloudFormation", "description": "CloudFormation Stack has been drifted\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"StackId": "d4815ded-b950-4696-b162-40d50eeae1f0"}]}}}, {"policy": "ecc-aws-477", "resource_type": "AWS CloudFormation", "description": "CloudFormation Stack notifications do not enabled\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"StackId": "b25c45c3-9926-4a33-9ab3-45b4f767e749"}]}}}, {"policy": "ecc-aws-478", "resource_type": "Amazon CloudFront", "description": "Cloudfront Distribution not uses SNI\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"ARN": "de9bd4a7-e667-4485-87f8-5e8d06134a44"}]}}}, {"policy": "ecc-aws-479", "resource_type": "Amazon CloudWatch", "description": "AWS CloudWatch log groups are not encrypted with KMS CMK\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"arn": "c2110005-0a5b-4045-914f-a8733ed46115"}]}}}, {"policy": "ecc-aws-480", "resource_type": "AWS CodeBuild", "description": "CodeBuild project artifact encryption disabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"arn": "19b1fd21-77a3-4d26-a032-d0808890df32"}]}}}, {"policy": "ecc-aws-481", "resource_type": "AWS CodeBuild", "description": "CodeBuild project environment privileged mode is set to true\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"arn": "8082c409-ae36-440e-9ab6-4cfbf8d17fe2"}]}}}, {"policy": "ecc-aws-482", "resource_type": "AWS CodeBuild", "description": "CodeBuild project logging in disabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"arn": "04d02ba1-abd9-43d4-8e84-7540f7d63684"}]}}}, {"policy": "ecc-aws-483", "resource_type": "AWS CodeBuild", "description": "CodeBuild S3 logs are not encrypted \n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"arn": "be248487-4014-44bf-b771-6a0ab9c4fb37"}]}}}, {"policy": "ecc-aws-484", "resource_type": "AWS CodeDeploy", "description": "CodeDeploy AutoRollbackConfiguration or AlarmConfiguration has not been configured or is not enabled. \n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"applicationName": "14860630-c3b7-4d7c-9236-a4156213aee4", "deploymentGroupName": "10afc39d-9d35-4c61-b60f-5aa571251ae8", "deploymentGroupId": "76b782c1-ef01-4c86-b458-c6d584940497"}]}}}, {"policy": "ecc-aws-485", "resource_type": "AWS CodeDeploy", "description": "CodeDeploy deployment config of application does not meet the requirements\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"applicationName": "bc3df349-14d4-492e-be04-234fa5a1bb66", "deploymentGroupName": "e9c943db-2a12-45f4-b2d3-0d10932adf56"}]}}}, {"policy": "ecc-aws-486", "resource_type": "AWS CodeDeploy", "description": "CodeDeploy Lambda AllAtOnce traffic shift disabled\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"applicationName": "c0169ab9-f4fa-4fda-9789-da853255e9de", "deploymentGroupId": "8da1611c-d8be-4f49-9c83-e5de2b3d1330", "deploymentGroupName": "64495619-0443-45a0-a3ed-11b0cd90616f"}]}}}, {"policy": "ecc-aws-487", "resource_type": "AWS CodePipeline", "description": "CodePipeline s3 artifact bucket is not encrypted with KMS CMK\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"roleArn": "a87ca74c-c5dc-47b1-9ef9-d94933122896"}]}}}, {"policy": "ecc-aws-488", "resource_type": "Amazon CloudWatch", "description": "CloudWatch Log Group does not have retention period set correctly\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"arn": "8cb539d9-eeeb-46a5-bd14-aee30ffe9625"}]}}}, {"policy": "ecc-aws-489", "resource_type": "Amazon EC2", "description": "EC2 instances detailed monitoring disabled\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"InstanceId": "69bcc638-7bc8-49be-a4f8-5007e11ebe05", "OwnerId": "1744e3df-b422-44a6-be2b-2aa7452aa9fd"}]}}}, {"policy": "ecc-aws-490", "resource_type": "Amazon EC2", "description": "EC2 instances token hop limit set correctly\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"InstanceId": "be7042d2-f360-4e15-90eb-9f47acc8bfe4", "OwnerId": "1fb41d9d-327c-4279-a83e-443c02421e97"}]}}}, {"policy": "ecc-aws-491", "resource_type": "AWS Transit Gateway", "description": "Transit gateway automatically accept VPC attachment requests\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"TransitGatewayArn": "e2acc7b8-302a-4314-a745-55030ab00a67"}]}}}, {"policy": "ecc-aws-492", "resource_type": "Amazon Elastic Container Registry", "description": "ECR repository does not have any lifecycle policies configured\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"repositoryArn": "4a912062-8de2-4239-b799-8098680dea7b"}]}}}, {"policy": "ecc-aws-493", "resource_type": "Amazon Elastic Container Service", "description": "ECS container insight is disabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"clusterArn": "7202eaed-0cd4-4be4-a33a-4e773e134a5d"}]}}}, {"policy": "ecc-aws-494", "resource_type": "Amazon Elastic Container Service", "description": "ECS Fargate not latest platform version\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{}]}}}, {"policy": "ecc-aws-495", "resource_type": "Amazon Elastic Container Service", "description": "Amazon ECS task definitions memory hard limit is not set\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"taskDefinitionArn": "97b6486b-18e8-4425-b13b-9047c5053a1e"}]}}}, {"policy": "ecc-aws-496", "resource_type": "Amazon Elastic Container Service", "description": "Amazon ECS task definitions pid mode set to 'host'\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"taskDefinitionArn": "69b6ef2c-04c6-4faf-b5a1-e2eb2aebcec4"}]}}}, {"policy": "ecc-aws-497", "resource_type": "Amazon Elastic Kubernetes Service", "description": "EKS cluster is using unsupported version\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"arn": "a5f28ca8-9cd2-49a0-a1ca-c132e21282cc"}]}}}, {"policy": "ecc-aws-498", "resource_type": "Amazon Elastic Load Balancing", "description": "Application, Gateway and Network Load Balancers are not configured with multiple Availability Zones\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"LoadBalancerArn": "f17b8af2-f370-4b2c-be33-f613f7864190"}]}}}, {"policy": "ecc-aws-499", "resource_type": "AWS Identity and Access Management", "description": "IAM group doesn't have users\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"Arn": "4e7d4167-8352-4334-9f13-6ca65e3651af"}]}}}, {"policy": "ecc-aws-500", "resource_type": "AWS Lambda", "description": "Lambda functions are not operate in more than one Availability Zone\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"FunctionArn": "c40dfc00-44b2-427a-b725-3833a3de2b42"}]}}}, {"policy": "ecc-aws-501", "resource_type": "Amazon OpenSearch Service", "description": "OpenSearch fine grained access control disabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"ARN": "32518e37-23ac-41b4-9502-2d4fe4be113e"}]}}}, {"policy": "ecc-aws-502", "resource_type": "Amazon Relational Database Service", "description": "Automatic minor version upgrade is not configured for RDS DB instances\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "47aa11b5-4a74-4bad-9e58-ee2c86d95f1f"}]}}}, {"policy": "ecc-aws-503", "resource_type": "Amazon Relational Database Service", "description": "Amazon RDS cluster uses default Admin username\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DBClusterArn": "d39a34fb-60e4-4dda-b608-d4fc32700ea6"}]}}}, {"policy": "ecc-aws-504", "resource_type": "Amazon Relational Database Service", "description": "Amazon RDS instance uses default Admin username\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"DBInstanceArn": "9880c034-f9de-4790-b712-ffaea6b90b8e"}]}}}, {"policy": "ecc-aws-505", "resource_type": "Amazon Redshift", "description": "Amazon Redshift uses default Admin username\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"ClusterIdentifier": "b876523c-9b6f-4cb5-a855-3486dfcbabc8"}]}}}, {"policy": "ecc-aws-506", "resource_type": "Amazon Redshift", "description": "Redshift clusters uses the default database name\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"ClusterIdentifier": "9be735c3-6fa8-40dc-a82c-30b33ac436be"}]}}}, {"policy": "ecc-aws-507", "resource_type": "Amazon Simple Notification Service", "description": "Amazon SNS topic message delivery notification is disabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"TopicArn": "97ac1d0e-ee05-478e-934c-a4488e68bf57"}]}}}, {"policy": "ecc-aws-508", "resource_type": "Amazon Managed Workflows for Apache Airflow", "description": "Managed Workflows for Apache Airflow not using latest version\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"arn": "2ae60c43-c853-481a-b712-2c439cd860be"}]}}}, {"policy": "ecc-aws-509", "resource_type": "Amazon DynamoDB Accelerator", "description": "DynamoDB Accelerator clusters encryption in transit of data is disabled\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"ClusterArn": "300676cf-1a43-444f-8db9-dff6213d45c3"}]}}}, {"policy": "ecc-aws-510", "resource_type": "Amazon Elastic File System", "description": "Unused Amazon EFS file systems\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"FileSystemArn": "1788a445-046b-4c52-9ef0-011ca10f7203", "OwnerId": "56d709fb-6ade-4da6-b5fa-a3bf4f15fbbf"}]}}}, {"policy": "ecc-aws-511", "resource_type": "Amazon Elastic Load Balancing", "description": "Amazon CLB is internet facing\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{}]}}}, {"policy": "ecc-aws-512", "resource_type": "Amazon Elastic Load Balancing", "description": "Amazon ELB is internet facing\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{}]}}}, {"policy": "ecc-aws-513", "resource_type": "AWS Certificate Manager", "description": "ACM has certificates minimum rsa key is not 2048 bit\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"CertificateArn": "3815eabc-79c1-4ae4-87dc-9d1a0db5077b", "DomainName": "db4b363b-7d78-4976-b095-c28f6d9de2e4"}]}}}, {"policy": "ecc-aws-514", "resource_type": "AWS Identity and Access Management", "description": "Inactive access keys are not deleted\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"Arn": "0e0d4fb9-e92f-4e78-a3f3-e953b9a941a8"}]}}}, {"policy": "ecc-aws-515", "resource_type": "AWS Security Hub", "description": "Security Hub is not enabled\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"c7n-service:region": "eu-west-1"}]}}}, {"policy": "ecc-aws-516", "resource_type": "Amazon S3", "description": "S3 buckets should have event notifications enabled\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"Name": "96d516c3-b086-4dc8-9373-62cb8bd8747c"}]}}}, {"policy": "ecc-aws-517", "resource_type": "Amazon S3", "description": "S3 access control lists (ACLs) are used to manage user access to buckets\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"Name": "8144b6ae-1b31-4e3c-b77f-8d835e0063cc"}]}}}, {"policy": "ecc-aws-518", "resource_type": "Amazon S3", "description": "S3 buckets with versioning enabled do not have lifecycle policies configured\n", "severity": "Medium", "regions_data": {"multiregion": {"resources": [{"Name": "11a902f6-9bb9-4eaf-8dd8-6756185cc57e"}]}}}, {"policy": "ecc-aws-519", "resource_type": "Amazon Virtual Private Cloud", "description": "One or both VPN tunnels for an AWS Site-to-Site VPN connection are in DOWN status\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"VpnConnectionId": "d0003a62-ff8f-400a-bc38-29dd59826562"}]}}}, {"policy": "ecc-aws-520", "resource_type": "Amazon EC2 Auto Scaling", "description": "Auto Scaling launch configuration hop limit is greater than 1\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"LaunchConfigurationName": "83923c26-1183-4b6a-8f9d-8dc185fcc208"}]}}}, {"policy": "ecc-aws-521", "resource_type": "Amazon Elastic Container Service", "description": "ECS container is not limited to read-only access to root file systems\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"taskDefinitionArn": "e7941485-de3f-4247-887d-77f002110889"}]}}}, {"policy": "ecc-aws-522", "resource_type": "Amazon Elastic Container Service", "description": "Amazon ECS secrets passed as container environment variables\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"taskDefinitionArn": "ee961c53-8c96-48ec-9e12-8a3994620465"}]}}}, {"policy": "ecc-aws-523", "resource_type": "AWS Key Management Service", "description": "KMS keys should not be unintentionally deleted\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"KeyArn": "7eac07a4-5ffa-4ff7-a492-50a5478af189", "AWSAccountId": "759577c0-337f-4833-93f7-bd3b7b355bed"}]}}}, {"policy": "ecc-aws-524", "resource_type": "AWS Web Application Firewall", "description": "A WAF Classic Regional web ACL does not have at least one rule or rule group\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"Name": "2647542f-58f1-4761-b140-413fb3bb9711", "WebACLId": "c003847f-c06b-46b8-a4ba-ce4fdebc0bf8"}]}}}, {"policy": "ecc-aws-525", "resource_type": "AWS Web Application Firewall", "description": "A WAF global rule does not have at least one condition\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"Name": "6d453d24-847e-45f7-801e-9e3da04d29c9", "RuleId": "7f44ee26-26ef-4700-8384-d4c875cd2f0a"}]}}}, {"policy": "ecc-aws-526", "resource_type": "AWS Web Application Firewall", "description": "A WAF global rule group does not have at least one rule\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"Name": "f69f5d0b-0505-43bc-b260-923374be788a", "RuleGroupId": "8e25e722-09b5-4460-8637-b8253dfb7aaa"}]}}}, {"policy": "ecc-aws-527", "resource_type": "AWS Web Application Firewall", "description": "A WAF global web ACL does not have at least one rule or rule group\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"WebACLId": "790d68e0-1e92-4987-8565-92135633c5e7", "Name": "aee9b5eb-af7b-415a-9d60-9f166fadf3b7"}]}}}, {"policy": "ecc-aws-528", "resource_type": "AWS Certificate Manager", "description": "ACM transparency logging disabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"CertificateArn": "10892c1b-1e20-4d3d-ade5-0c4e0ac66b13", "DomainName": "3ef41bc3-152e-4ec1-9f33-63f9dbea35ac"}]}}}, {"policy": "ecc-aws-529", "resource_type": "Amazon EC2", "description": "EBS volumes attached to an EC2 instance is not marked for deletion upon instance termination\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"InstanceId": "94d4049e-bbd3-4f7b-9773-a1c100165d5e"}]}}}, {"policy": "ecc-aws-530", "resource_type": "Amazon CloudFront", "description": "CloudFront distribution not encrypted in transit\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"ARN": "d6ae5de0-aa6a-4e91-af57-80bc38445a42"}]}}}, {"policy": "ecc-aws-531", "resource_type": "Amazon Elastic Block Store", "description": "EBS volume default encryption disabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"account_id": "94aeace7-e212-40f8-8cde-e76efb589ec3", "c7n-service:region": "eu-west-1"}]}}}, {"policy": "ecc-aws-532", "resource_type": "AWS Certificate Manager", "description": "Imported and ACM-issued certificates expire in less than a month\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"CertificateArn": "f16a5765-04ce-4c37-94fa-12364649fc94"}]}}}, {"policy": "ecc-aws-533", "resource_type": "Amazon EC2", "description": "Amazon Key pair without tag information\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"KeyPairId": "1d3b034e-d184-4399-bcf9-3c672974cd9d"}]}}}, {"policy": "ecc-aws-534", "resource_type": "Amazon EC2 Auto Scaling", "description": "EC2 Auto Scaling groups is not using EC2 launch templates\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"AutoScalingGroupARN": "f4d5d2b4-f0e9-4788-ad3f-783b03a540e7"}]}}}, {"policy": "ecc-aws-535", "resource_type": "Amazon Elastic Load Balancing", "description": "Classic Load Balancers with HTTPS/SSL listeners do not use certificate provided by AWS Certificate Manager\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{}]}}}, {"policy": "ecc-aws-536", "resource_type": "AWS Lambda", "description": "Lambda functions should not use no longer supported runtimes\n", "severity": "Medium", "regions_data": {"eu-west-1": {"resources": [{"FunctionArn": "6e86558e-74af-493f-b603-c3c0570b0517"}]}}}, {"policy": "ecc-aws-537", "resource_type": "Amazon Elastic Container Service", "description": "ECS containers should not run in privileged parameter\n", "severity": "Low", "regions_data": {"eu-west-1": {"resources": [{"taskDefinitionArn": "4088dfef-8ebf-4200-8db5-cc01777709ef"}]}}}, {"policy": "ecc-aws-538", "resource_type": "Amazon CloudFront", "description": "CloudFront distributions are pointing to non-existent S3 origins\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"ARN": "d82c4fc0-2fb1-436a-a19c-1b9faf4c6dc0"}]}}}, {"policy": "ecc-aws-539", "resource_type": "Amazon CloudFront", "description": "CloudFront distributions do not have origin access control enabled\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"ARN": "575bbc52-6e1d-45d2-ba07-3b976f1a7293"}]}}}, {"policy": "ecc-aws-540", "resource_type": "AWS Glue", "description": "Amazon Glue Job not latest version\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"Name": "1901cc9b-9d37-486a-bdf4-ef3aed159d1f"}]}}}, {"policy": "ecc-aws-541", "resource_type": "AWS Glue", "description": "Glue job logging disabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"Name": "52409759-502a-4637-bde6-187615f14354"}]}}}, {"policy": "ecc-aws-542", "resource_type": "AWS Glue", "description": "Amazon Glue Job with disabled autoscaling\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"Name": "74a0bfe9-90a5-429a-ab2c-4756fb7d87c5"}]}}}, {"policy": "ecc-aws-543", "resource_type": "Amazon CloudFront", "description": "CloudFront Realtime logging disabled\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"ARN": "5d24edd2-12b0-476e-9292-a43835cb51be"}]}}}, {"policy": "ecc-aws-544", "resource_type": "AWS CloudTrail", "description": "CloudTrail logs delivery failing\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"TrailARN": "6e424d92-4ca7-4902-9ab0-56e6dc0fae86"}]}}}, {"policy": "ecc-aws-545", "resource_type": "AWS Step Functions", "description": "AWS Step Function State Machine logging is disabled\n", "severity": "High", "regions_data": {"eu-west-1": {"resources": [{"stateMachineArn": "732884b1-ec84-48f7-a5d4-c63497968a6c"}]}}}, {"policy": "ecc-aws-548", "resource_type": "Amazon Elastic Block Store", "description": "EBS volumes are type of gp2 insted of gp3\n", "severity": "Info", "regions_data": {"eu-west-1": {"resources": [{"VolumeId": "ce529487-9dbc-4663-882c-e8a1246bc0f2"}]}}}] \ No newline at end of file diff --git a/tests/tests_metrics/expected_metrics/azure_tenant_group_finops.json b/tests/tests_metrics/expected_metrics/azure_tenant_group_finops.json new file mode 100644 index 000000000..bea6e76b3 --- /dev/null +++ b/tests/tests_metrics/expected_metrics/azure_tenant_group_finops.json @@ -0,0 +1 @@ +[{"service_section": "ipqjoieiwpssdkm9k79jgv9gvrk", "rules_data": [{"rule": "Azure Machine Learning Compute cluster have minNodeCount property not equal to 0\n", "service": "Azure Machine Learning", "category": "79qojm2uhkmp1wgqdplfpqx65xs", "severity": "Medium", "resource_type": "Azure Machine Learning", "regions_data": {"westeurope": {"total_violated_resources": {"value": 1}}, "multiregion": {"total_violated_resources": {"value": 1}}}}]}] \ No newline at end of file diff --git a/tests/tests_metrics/expected_metrics/azure_tenant_resources.json b/tests/tests_metrics/expected_metrics/azure_tenant_resources.json new file mode 100644 index 000000000..a9bd4b61f --- /dev/null +++ b/tests/tests_metrics/expected_metrics/azure_tenant_resources.json @@ -0,0 +1 @@ +[{"policy":"ecc-azure-002","resource_type":"Azure RBAC","description":"Custom role with Owner privileges on a subscription scope is created\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"83556a93-74a6-4251-91f8-8c37a03512bd"}]},"multiregion":{"resources":[{"id":"83556a93-74a6-4251-91f8-8c37a03512bd"}]}}},{"policy":"ecc-azure-004","resource_type":"Microsoft Defender for Cloud","description":"Automatic provisioning is set to \"Off\" in Security Center (Microsoft Defender for Cloud)\n","severity":"Medium","regions_data":{"westeurope":{"resources":[{"id":"13e43c91-46ff-4301-ba3b-1a6a25d9f3b0"}]},"multiregion":{"resources":[{"id":"13e43c91-46ff-4301-ba3b-1a6a25d9f3b0"}]}}},{"policy":"ecc-azure-005","resource_type":"Microsoft Defender for Cloud","description":"'Additional email addresses' is not configured in Microsoft Defender for Cloud\n","severity":"Info","regions_data":{"westeurope":{"resources":[{"id":"6a99be11-90a4-4b9d-b8c9-06003202b336"}]},"multiregion":{"resources":[{"id":"6a99be11-90a4-4b9d-b8c9-06003202b336"}]}}},{"policy":"ecc-azure-006","resource_type":"Microsoft Defender for Cloud","description":"Notification alerts are disabled in Security Center (Microsoft Defender for Cloud)\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"f5038039-380b-4bef-aac0-92df0a0dfa6b"}]},"multiregion":{"resources":[{"id":"f5038039-380b-4bef-aac0-92df0a0dfa6b"}]}}},{"policy":"ecc-azure-007","resource_type":"Microsoft Defender for Cloud","description":"Notification alerts to admins or subscription owners are disabled in Microsoft Defender for Cloud\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"f20e50b5-b641-4d60-83d0-fed516912027"}]},"multiregion":{"resources":[{"id":"f20e50b5-b641-4d60-83d0-fed516912027"}]}}},{"policy":"ecc-azure-008","resource_type":"Azure Storage Accounts","description":"Storage account that allows http traffic\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"80cdf030-6761-4b4e-8cc3-b785a2381225"}]},"multiregion":{"resources":[{"id":"80cdf030-6761-4b4e-8cc3-b785a2381225"}]}}},{"policy":"ecc-azure-009","resource_type":"Azure Storage Accounts","description":"Storage Account with publicly accessed blobs\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"645e906b-41aa-46ff-addd-0d6704a3d0bf"}]},"multiregion":{"resources":[{"id":"645e906b-41aa-46ff-addd-0d6704a3d0bf"}]}}},{"policy":"ecc-azure-010","resource_type":"Azure Storage Accounts","description":"Storage Account accepted connections from public network\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"9eb2a816-68cb-4d23-ae5d-9302e6917d26"}]},"multiregion":{"resources":[{"id":"9eb2a816-68cb-4d23-ae5d-9302e6917d26"}]}}},{"policy":"ecc-azure-011","resource_type":"Azure Storage Accounts","description":"Soft delete for Azure Storage Blobs is disabled\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"33692a31-3deb-4ccd-8628-6e09d438c49b"}]},"multiregion":{"resources":[{"id":"33692a31-3deb-4ccd-8628-6e09d438c49b"}]}}},{"policy":"ecc-azure-012","resource_type":"Azure Storage Accounts","description":"Azure Storage account data is encrypted with Microsoft Managed Key\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"d481ed43-f5a0-4639-9ad7-1d1fa49f88b9"}]},"multiregion":{"resources":[{"id":"d481ed43-f5a0-4639-9ad7-1d1fa49f88b9"}]}}},{"policy":"ecc-azure-013","resource_type":"Azure SQL Database","description":"Azure SQL Database Auditing is set to \"Off\"\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"18199393-670a-4daf-9fe0-2cecbfd32382"}]},"multiregion":{"resources":[{"id":"18199393-670a-4daf-9fe0-2cecbfd32382"}]}}},{"policy":"ecc-azure-014","resource_type":"Azure SQL Database","description":"Transparent Data Encryption is disabled on SQL Database\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"e1ea5ad7-e7fd-46f9-8c0a-fe85f629cfec"}]},"multiregion":{"resources":[{"id":"e1ea5ad7-e7fd-46f9-8c0a-fe85f629cfec"}]}}},{"policy":"ecc-azure-015","resource_type":"Azure SQL Database","description":"Azure SQL Database Auditing retention policy set to less than 90 days\n","severity":"Info","regions_data":{"westeurope":{"resources":[{"id":"a95e8ed3-5859-4e67-83fa-413f6bd63b2a"}]},"multiregion":{"resources":[{"id":"a95e8ed3-5859-4e67-83fa-413f6bd63b2a"}]}}},{"policy":"ecc-azure-016","resource_type":"Azure SQL Database","description":"Advanced Threat Protection is disabled on SQL server\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"6f888c13-2f8d-4fc6-8ee0-e82458c555d8"}]},"multiregion":{"resources":[{"id":"6f888c13-2f8d-4fc6-8ee0-e82458c555d8"}]}}},{"policy":"ecc-azure-020","resource_type":"Azure SQL Database","description":"Azure SQL Vulnerability Assessment is disabled or storage account property is not configured\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"f5e95c59-3c16-44fa-ad3c-66cd8341bb10"}]},"multiregion":{"resources":[{"id":"f5e95c59-3c16-44fa-ad3c-66cd8341bb10"}]}}},{"policy":"ecc-azure-021","resource_type":"Azure SQL Database","description":"Azure SQL Vulnerability Assessment Reccuring Scans is disabled","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"a0f0ab30-89d2-4cb1-8738-126fcb15920a"}]},"multiregion":{"resources":[{"id":"a0f0ab30-89d2-4cb1-8738-126fcb15920a"}]}}},{"policy":"ecc-azure-022","resource_type":"Azure SQL Database","description":"Azure SQL Vulnerability Assessment setting 'Send scan reports to' is not configured","severity":"Medium","regions_data":{"westeurope":{"resources":[{"id":"44437dd1-99a0-411b-9d27-391a1c825fd0"}]},"multiregion":{"resources":[{"id":"44437dd1-99a0-411b-9d27-391a1c825fd0"}]}}},{"policy":"ecc-azure-023","resource_type":"Azure SQL Database","description":"Azure SQL Vulnerability Assessment emailing to admins and subscription owners is not configured","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"e6ace001-86b0-4232-8aeb-4ed482f268d9"}]},"multiregion":{"resources":[{"id":"e6ace001-86b0-4232-8aeb-4ed482f268d9"}]}}},{"policy":"ecc-azure-024","resource_type":"Azure Database for PostgreSQL","description":"SSL connection is disabled on PostgreSQL servers\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"5c6bae77-7b60-4566-92f1-9a1d36be1bf9"}]},"multiregion":{"resources":[{"id":"5c6bae77-7b60-4566-92f1-9a1d36be1bf9"}]}}},{"policy":"ecc-azure-025","resource_type":"Azure Database for MySQL","description":"SSL connection is disabled on MySQL servers\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"41f847ce-99bc-43c2-90c0-f68184a1fba2"}]},"multiregion":{"resources":[{"id":"41f847ce-99bc-43c2-90c0-f68184a1fba2"}]}}},{"policy":"ecc-azure-026","resource_type":"Azure Database for PostgreSQL","description":"PostgreSQL instance with server parameter 'log_checkpoints' disabled\n","severity":"Info","regions_data":{"westeurope":{"resources":[{"id":"69117d4e-ad3b-4672-bb81-fb1e013248ff"}]},"multiregion":{"resources":[{"id":"69117d4e-ad3b-4672-bb81-fb1e013248ff"}]}}},{"policy":"ecc-azure-027","resource_type":"Azure Database for PostgreSQL","description":"PostgreSQL instance with server parameter 'log_connections' disabled\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"84b608da-2042-4540-a689-bc47d0ec4d9a"}]},"multiregion":{"resources":[{"id":"84b608da-2042-4540-a689-bc47d0ec4d9a"}]}}},{"policy":"ecc-azure-028","resource_type":"Azure Database for PostgreSQL","description":"PostgreSQL instance with server parameter 'log_disconnections' disabled\n","severity":"Info","regions_data":{"westeurope":{"resources":[{"id":"172e9e1a-f76a-40a9-8b89-daea15fc00f4"}]},"multiregion":{"resources":[{"id":"172e9e1a-f76a-40a9-8b89-daea15fc00f4"}]}}},{"policy":"ecc-azure-030","resource_type":"Azure Database for PostgreSQL","description":"PostgreSQL instance with server parameter 'connection_throttling' disabled\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"3ee093e3-dac8-4075-9f0f-ccb15812a62f"}]},"multiregion":{"resources":[{"id":"3ee093e3-dac8-4075-9f0f-ccb15812a62f"}]}}},{"policy":"ecc-azure-031","resource_type":"Azure Database for PostgreSQL","description":"PostgreSQL instance with server parameter 'log_retention_days' is set to less than 4 days\n","severity":"Info","regions_data":{"westeurope":{"resources":[{"id":"6b8ea0ba-9bbf-4cb1-a4f0-817a741fa414"}]},"multiregion":{"resources":[{"id":"6b8ea0ba-9bbf-4cb1-a4f0-817a741fa414"}]}}},{"policy":"ecc-azure-032","resource_type":"Azure SQL Database","description":"Azure Active Directory admin is not configured for Azure SQL\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"96a5efcc-9904-4c23-bfb3-3583f3565563"}]},"multiregion":{"resources":[{"id":"96a5efcc-9904-4c23-bfb3-3583f3565563"}]}}},{"policy":"ecc-azure-033","resource_type":"Azure SQL Database","description":"Transparent Data Encryption protector is not encrypted with Customer Managed key\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"ee54c003-266c-4c0b-8ea8-8cf7f07c29e3"}]},"multiregion":{"resources":[{"id":"ee54c003-266c-4c0b-8ea8-8cf7f07c29e3"}]}}},{"policy":"ecc-azure-036","resource_type":"Azure Storage Accounts","description":"Storage container that stores activity logs where public access level set to \"Blob\" or \"Container\"\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"5691e9a9-93e8-4bd8-93f0-178cf2f20b46"}]},"multiregion":{"resources":[{"id":"5691e9a9-93e8-4bd8-93f0-178cf2f20b46"}]}}},{"policy":"ecc-azure-037","resource_type":"Azure Storage Accounts","description":"Storage account that contains a container with activity logs not encrypted with Customer Managed Key\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"787dcc8e-cb61-454e-ac36-75ec92d84fdd"}]},"multiregion":{"resources":[{"id":"787dcc8e-cb61-454e-ac36-75ec92d84fdd"}]}}},{"policy":"ecc-azure-038","resource_type":"Key Vault","description":"Key Vault with logging and retention policy disabled\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"aa76a88f-1ae1-499e-9092-2cf254fde6ed"}]},"multiregion":{"resources":[{"id":"aa76a88f-1ae1-499e-9092-2cf254fde6ed"}]}}},{"policy":"ecc-azure-039","resource_type":"Azure Subscription","description":"Subscription where Activity Log Alert does not exist for Create Policy Assignment\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"9d78212a-1c62-4eac-9a8d-60c4ed424ef9"}]},"multiregion":{"resources":[{"id":"9d78212a-1c62-4eac-9a8d-60c4ed424ef9"}]}}},{"policy":"ecc-azure-042","resource_type":"Azure Subscription","description":"Subscription does not contain Activity Log Alert with appropriate scope for Create or Update Network Security Group Rule\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"7c7219ac-a461-4d8a-9865-6c8e32da4525"}]},"multiregion":{"resources":[{"id":"7c7219ac-a461-4d8a-9865-6c8e32da4525"}]}}},{"policy":"ecc-azure-043","resource_type":"Azure Subscription","description":"Subscription does not contain Activity Log Alert with appropriate scope for Delete Network Security Group Rule\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"0ee65b25-f0c5-44bc-af87-e35d057dc211"}]},"multiregion":{"resources":[{"id":"0ee65b25-f0c5-44bc-af87-e35d057dc211"}]}}},{"policy":"ecc-azure-044","resource_type":"Azure Subscription","description":"Subscription does not contain Activity Log Alert with appropriate scope for Create or Update Security Solution\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"79b9fa4b-b759-48d9-b09f-eb4009b8dd0f"}]},"multiregion":{"resources":[{"id":"79b9fa4b-b759-48d9-b09f-eb4009b8dd0f"}]}}},{"policy":"ecc-azure-045","resource_type":"Azure Subscription","description":"Subscription does not contain Activity Log Alert with appropriate scope for Delete Security Solution\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"a438d166-b36f-4d15-a803-ee584d01f236"}]},"multiregion":{"resources":[{"id":"a438d166-b36f-4d15-a803-ee584d01f236"}]}}},{"policy":"ecc-azure-046","resource_type":"Azure Subscription","description":"Subscription does not contain Activity Log Alert with appropriate scope for Create or Update or Delete SQL Server Firewall Rule\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"b23fa74a-1cdd-4ec0-8d26-26f393c969da"}]},"multiregion":{"resources":[{"id":"b23fa74a-1cdd-4ec0-8d26-26f393c969da"}]}}},{"policy":"ecc-azure-048","resource_type":"Network security groups","description":"Network Security Group with inbound rule that allows RDP traffic from the Internet\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"669a34bb-d1dd-4677-8ef2-222b6bd35834"}]},"multiregion":{"resources":[{"id":"669a34bb-d1dd-4677-8ef2-222b6bd35834"}]}}},{"policy":"ecc-azure-049","resource_type":"Network security groups","description":"Network Security Group with inbound rule that allows SSH traffic from the Internet\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"e9f00b12-0431-4c64-8fd7-205388c74a5e"}]},"multiregion":{"resources":[{"id":"e9f00b12-0431-4c64-8fd7-205388c74a5e"}]}}},{"policy":"ecc-azure-050","resource_type":"Azure SQL Database","description":"SQL instances accessible from the Internet or Azure services\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"7dbab209-e4a0-4547-a104-3f6f8edb57b5"}]},"multiregion":{"resources":[{"id":"7dbab209-e4a0-4547-a104-3f6f8edb57b5"}]}}},{"policy":"ecc-azure-052","resource_type":"Network security groups","description":"Network Security Group with inbound rule that allows UDP traffic from the Internet\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"3e0dcc39-68fb-41b6-a6e2-e05c5783e2bc"}]},"multiregion":{"resources":[{"id":"3e0dcc39-68fb-41b6-a6e2-e05c5783e2bc"}]}}},{"policy":"ecc-azure-053","resource_type":"Azure Disk Storage","description":"Managed disk attached to a VM that is not encrypted with Customer Managed Key\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"a91126cf-e575-4965-b7d6-02a4cd75a239"}]},"multiregion":{"resources":[{"id":"a91126cf-e575-4965-b7d6-02a4cd75a239"}]}}},{"policy":"ecc-azure-054","resource_type":"Azure Disk Storage","description":"Unattached managed disks not encrypted with Customer Managed Key\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"ac6eab85-12ec-4fa6-ba69-057e8d5f0f61"}]},"multiregion":{"resources":[{"id":"ac6eab85-12ec-4fa6-ba69-057e8d5f0f61"}]}}},{"policy":"ecc-azure-055","resource_type":"Key Vault","description":"Key without expiration date set\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"d729a292-2a89-4410-8ffb-a4a64f1d0470"}]},"multiregion":{"resources":[{"id":"d729a292-2a89-4410-8ffb-a4a64f1d0470"}]}}},{"policy":"ecc-azure-056","resource_type":"Key Vault","description":"Secret without expiration date set\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"c20fe273-7a01-4a5f-8cdf-a9b40c18ccfa"}]},"multiregion":{"resources":[{"id":"c20fe273-7a01-4a5f-8cdf-a9b40c18ccfa"}]}}},{"policy":"ecc-azure-057","resource_type":"Key Vault","description":"Key vault without Soft Delete or Purge Protection enabled\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"de2b7984-116e-4305-a891-a4c61e332bd3"}]},"multiregion":{"resources":[{"id":"de2b7984-116e-4305-a891-a4c61e332bd3"}]}}},{"policy":"ecc-azure-058","resource_type":"Azure Kubernetes Service","description":"Kubernetes cluster without RBAC enabled\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"9094e29f-7742-4a96-8afd-d126526c80d3"}]},"multiregion":{"resources":[{"id":"9094e29f-7742-4a96-8afd-d126526c80d3"}]}}},{"policy":"ecc-azure-059","resource_type":"App Service","description":"App Service without App Service Authentication enabled\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"bff25e71-01e9-4ce0-8216-1ead80f5f60e"}]},"multiregion":{"resources":[{"id":"bff25e71-01e9-4ce0-8216-1ead80f5f60e"}]}}},{"policy":"ecc-azure-060","resource_type":"App Service","description":"App Service that allows http traffic\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"eb4bdbe5-a63f-4928-a4ca-f573934a84d0"}]},"multiregion":{"resources":[{"id":"eb4bdbe5-a63f-4928-a4ca-f573934a84d0"}]}}},{"policy":"ecc-azure-061","resource_type":"App Service","description":"App Service that uses TLS version before 1.2\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"505f8316-77c5-43b6-ba76-d29a321929a8"}]},"multiregion":{"resources":[{"id":"505f8316-77c5-43b6-ba76-d29a321929a8"}]}}},{"policy":"ecc-azure-064","resource_type":"App Service","description":"App Service that allows FTP deployments\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"6539e661-a285-4eb9-a579-5a7da81d15a5"}]},"multiregion":{"resources":[{"id":"6539e661-a285-4eb9-a579-5a7da81d15a5"}]}}},{"policy":"ecc-azure-065","resource_type":"App Service","description":"App Service without HTTP 2.0 is enabled\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"7a538177-9691-4639-bd13-4e40ace31025"}]},"multiregion":{"resources":[{"id":"7a538177-9691-4639-bd13-4e40ace31025"}]}}},{"policy":"ecc-azure-066","resource_type":"Azure Subscription","description":"Subscription does not contain Activity Log Alert with appropriate scope for Delete Policy Assignment\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"82860b24-cffa-4def-a6c9-7a6fc90ac3b3"}]},"multiregion":{"resources":[{"id":"82860b24-cffa-4def-a6c9-7a6fc90ac3b3"}]}}},{"policy":"ecc-azure-067","resource_type":"Azure Subscription","description":"Subscription does not contain Activity Log Alert with appropriate scope for Create or Update Network Security Group Rule (securityRules)\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"9edfa5c6-729a-4759-8631-1e649ae7e9c1"}]},"multiregion":{"resources":[{"id":"9edfa5c6-729a-4759-8631-1e649ae7e9c1"}]}}},{"policy":"ecc-azure-068","resource_type":"Azure Subscription","description":"Subscription does not contain Activity Log Alert with appropriate scope for the Delete Network Security Group Rule\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"5bdf9836-7396-4647-8c93-2732abbb7963"}]},"multiregion":{"resources":[{"id":"5bdf9836-7396-4647-8c93-2732abbb7963"}]}}},{"policy":"ecc-azure-069","resource_type":"App Service","description":"App Service with outdated Java version\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"76d06ea6-9eb9-4912-a0bd-f0f93259048b"}]},"multiregion":{"resources":[{"id":"76d06ea6-9eb9-4912-a0bd-f0f93259048b"}]}}},{"policy":"ecc-azure-070","resource_type":"App Service","description":"App Service with outdated Python version\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"3b5a60ac-133a-4542-8cd3-31484a674326"}]},"multiregion":{"resources":[{"id":"3b5a60ac-133a-4542-8cd3-31484a674326"}]}}},{"policy":"ecc-azure-071","resource_type":"App Service","description":"App Service with outdated PHP version\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"b423498e-70df-4e51-826d-03c03a55bea3"}]},"multiregion":{"resources":[{"id":"b423498e-70df-4e51-826d-03c03a55bea3"}]}}},{"policy":"ecc-azure-072","resource_type":"App Service","description":"Azure Web App without Key Vault reference configured","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"c82f4e6e-af5e-49c0-9466-febd8aee46ab"}]},"multiregion":{"resources":[{"id":"c82f4e6e-af5e-49c0-9466-febd8aee46ab"}]}}},{"policy":"ecc-azure-094","resource_type":"Microsoft Defender for Cloud","description":"Azure Defender for Servers is set to \"Off\"\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"5ac814ba-ab58-4af9-9786-8ba8898d0374"}]},"multiregion":{"resources":[{"id":"5ac814ba-ab58-4af9-9786-8ba8898d0374"}]}}},{"policy":"ecc-azure-095","resource_type":"Microsoft Defender for Cloud","description":"Azure Defender for App Service is set to \"Off\"\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"867b733a-621c-4396-9258-1cd0dc8766b8"}]},"multiregion":{"resources":[{"id":"867b733a-621c-4396-9258-1cd0dc8766b8"}]}}},{"policy":"ecc-azure-096","resource_type":"Microsoft Defender for Cloud","description":"Azure Defender for SQL database servers is set to \"Off\"\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"1266ab08-2d2a-4b7d-9983-ee7d833748f9"}]},"multiregion":{"resources":[{"id":"1266ab08-2d2a-4b7d-9983-ee7d833748f9"}]}}},{"policy":"ecc-azure-097","resource_type":"Microsoft Defender for Cloud","description":"Azure Defender for SQL servers on machines is set to \"Off\"\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"bf2b4ecf-9ecc-4df2-af28-169b7cb2b241"}]},"multiregion":{"resources":[{"id":"bf2b4ecf-9ecc-4df2-af28-169b7cb2b241"}]}}},{"policy":"ecc-azure-098","resource_type":"Microsoft Defender for Cloud","description":"Azure Defender for Storage is set to \"Off\"\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"ca9625c4-735e-4953-9578-fec17f755986"}]},"multiregion":{"resources":[{"id":"ca9625c4-735e-4953-9578-fec17f755986"}]}}},{"policy":"ecc-azure-099","resource_type":"Microsoft Defender for Cloud","description":"Azure Defender for Kubernetes is set to \"Off\"\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"903e2fe1-f706-47e5-938e-cb07656cd369"}]},"multiregion":{"resources":[{"id":"903e2fe1-f706-47e5-938e-cb07656cd369"}]}}},{"policy":"ecc-azure-100","resource_type":"Microsoft Defender for Cloud","description":"Azure Defender for Container Registries is set to \"Off\"\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"d14140cb-e86a-4217-8e19-ae66adef5e97"}]},"multiregion":{"resources":[{"id":"d14140cb-e86a-4217-8e19-ae66adef5e97"}]}}},{"policy":"ecc-azure-101","resource_type":"Microsoft Defender for Cloud","description":"Azure Defender for Key Vault is set to \"Off\"\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"18d768de-ef35-401f-817c-6c05bfb43d71"}]},"multiregion":{"resources":[{"id":"18d768de-ef35-401f-817c-6c05bfb43d71"}]}}},{"policy":"ecc-azure-102","resource_type":"Microsoft Defender for Cloud","description":"WDATP integration is disabled in Microsoft Defender for Cloud\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"75e39448-b06b-4b59-aeaa-6b2cc00ed33b"}]},"multiregion":{"resources":[{"id":"75e39448-b06b-4b59-aeaa-6b2cc00ed33b"}]}}},{"policy":"ecc-azure-103","resource_type":"Microsoft Defender for Cloud","description":"MCAS integration is disabled in Security Center (Microsoft Defender for Cloud)\n","severity":"Info","regions_data":{"westeurope":{"resources":[{"id":"c8ed0673-61f5-411e-ac91-9f5caa02c17b"}]},"multiregion":{"resources":[{"id":"c8ed0673-61f5-411e-ac91-9f5caa02c17b"}]}}},{"policy":"ecc-azure-105","resource_type":"Azure Storage Accounts","description":"Storage account without recently regenerated access keys\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"b8a24961-1ac0-41d3-a635-849d62d62505"}]},"multiregion":{"resources":[{"id":"b8a24961-1ac0-41d3-a635-849d62d62505"}]}}},{"policy":"ecc-azure-106","resource_type":"Azure Storage Accounts","description":"Storage account without logging enabled for Queues\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"e685b29e-41da-48ee-8369-b8e62e0f759f"}]},"multiregion":{"resources":[{"id":"e685b29e-41da-48ee-8369-b8e62e0f759f"}]}}},{"policy":"ecc-azure-108","resource_type":"Azure Storage Accounts","description":"Storage account without access from/to \"Trusted Microsoft Services\"\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"fab9f44e-7b51-4046-abc4-26fae592a0b6"}]},"multiregion":{"resources":[{"id":"fab9f44e-7b51-4046-abc4-26fae592a0b6"}]}}},{"policy":"ecc-azure-109","resource_type":"Azure Storage Accounts","description":"Storage account without logging enabled for Blobs\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"669ea92e-d04f-4680-b8a9-08da372c4901"}]},"multiregion":{"resources":[{"id":"669ea92e-d04f-4680-b8a9-08da372c4901"}]}}},{"policy":"ecc-azure-110","resource_type":"Azure Storage Accounts","description":"Storage account without logging enabled for Tables\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"2bb7b2b8-d34a-41d2-a0db-ac896f85bdc9"}]},"multiregion":{"resources":[{"id":"2bb7b2b8-d34a-41d2-a0db-ac896f85bdc9"}]}}},{"policy":"ecc-azure-111","resource_type":"Azure Database for PostgreSQL","description":"PostgreSQL Database Server with 'Allow access to Azure services' enabled\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"b3dfeabf-25ee-48dd-8983-2b8555bb3459"}]},"multiregion":{"resources":[{"id":"b3dfeabf-25ee-48dd-8983-2b8555bb3459"}]}}},{"policy":"ecc-azure-112","resource_type":"Azure Subscription","description":"Network Watcher is disabled across the subscription\n","severity":"Medium","regions_data":{"westeurope":{"resources":[{"id":"1c9046bb-a91c-444c-91a8-2a2c54fdf2b7"}]},"multiregion":{"resources":[{"id":"1c9046bb-a91c-444c-91a8-2a2c54fdf2b7"}]}}},{"policy":"ecc-azure-113","resource_type":"Virtual Machines","description":"Virtual machine that utilizes unmanaged disks\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"f65a3121-f4d3-4301-b2de-63af6530771a"}]},"multiregion":{"resources":[{"id":"f65a3121-f4d3-4301-b2de-63af6530771a"}]}}},{"policy":"ecc-azure-116","resource_type":"Virtual Machines","description":"Virtual machine without endpoint protection installed\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"7738daf2-e3f5-4fd2-9ae1-8a2df7781ced"}]},"multiregion":{"resources":[{"id":"7738daf2-e3f5-4fd2-9ae1-8a2df7781ced"}]}}},{"policy":"ecc-azure-117","resource_type":"Virtual Machines","description":"[Legacy] Virtual machine utilizes unmanaged disks without encryption\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"0ea5877d-f415-4bdf-b7c6-18a79a732b22"}]},"multiregion":{"resources":[{"id":"0ea5877d-f415-4bdf-b7c6-18a79a732b22"}]}}},{"policy":"ecc-azure-119","resource_type":"Network security groups","description":"Network Security Group with inbound rule that allows all traffic from the Internet\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"309923b5-b9b4-46aa-a18a-7ffdc8890805"}]},"multiregion":{"resources":[{"id":"309923b5-b9b4-46aa-a18a-7ffdc8890805"}]}}},{"policy":"ecc-azure-120","resource_type":"Network security groups","description":"Network Security Group with inbound rule that allows DNS traffic from the Internet\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"cd3c7a11-8f09-4235-965e-5ccdd9f59eab"}]},"multiregion":{"resources":[{"id":"cd3c7a11-8f09-4235-965e-5ccdd9f59eab"}]}}},{"policy":"ecc-azure-121","resource_type":"Network security groups","description":"Network Security Group with inbound rule that allows FTP traffic from the Internet\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"92bd8a27-62df-4839-acd1-c46f85d0e183"}]},"multiregion":{"resources":[{"id":"92bd8a27-62df-4839-acd1-c46f85d0e183"}]}}},{"policy":"ecc-azure-122","resource_type":"Network security groups","description":"Network Security Group with inbound rule that allows HTTP traffic from the Internet\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"de03e671-eb62-494f-b63d-9977beb65327"}]},"multiregion":{"resources":[{"id":"de03e671-eb62-494f-b63d-9977beb65327"}]}}},{"policy":"ecc-azure-123","resource_type":"Network security groups","description":"Network Security Group with inbound rule that allows SMB traffic from the Internet\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"96f01efd-a245-4944-8090-28205af317ed"}]},"multiregion":{"resources":[{"id":"96f01efd-a245-4944-8090-28205af317ed"}]}}},{"policy":"ecc-azure-124","resource_type":"Network security groups","description":"Network Security Group with inbound rule that allows MySQL traffic from the Internet\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"a6b152d8-eca3-418c-ae2c-dbe0c68d4cbf"}]},"multiregion":{"resources":[{"id":"a6b152d8-eca3-418c-ae2c-dbe0c68d4cbf"}]}}},{"policy":"ecc-azure-125","resource_type":"Network security groups","description":"Network Security Group with inbound rule that allows MongoDB traffic from the Internet\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"7a6f42b2-f453-467a-9617-1d34411e6f35"}]},"multiregion":{"resources":[{"id":"7a6f42b2-f453-467a-9617-1d34411e6f35"}]}}},{"policy":"ecc-azure-126","resource_type":"Network security groups","description":"Network Security Group with inbound rule that allows NetBIOS traffic from the Internet\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"c6f55baa-b525-480b-8ed7-2ffc5fba4b41"}]},"multiregion":{"resources":[{"id":"c6f55baa-b525-480b-8ed7-2ffc5fba4b41"}]}}},{"policy":"ecc-azure-127","resource_type":"Network security groups","description":"Network Security Group with inbound rule that allows OracleDB traffic from the Internet\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"ecc36f8c-6260-4645-88e0-971a2cdf5e6d"}]},"multiregion":{"resources":[{"id":"ecc36f8c-6260-4645-88e0-971a2cdf5e6d"}]}}},{"policy":"ecc-azure-128","resource_type":"Network security groups","description":"Network Security Group with inbound rule that allows POP3 traffic from the Internet\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"4a45b893-0f10-448a-9e7e-a0fb6bbd68d8"}]},"multiregion":{"resources":[{"id":"4a45b893-0f10-448a-9e7e-a0fb6bbd68d8"}]}}},{"policy":"ecc-azure-129","resource_type":"Network security groups","description":"Network Security Group with inbound rule that allows PostgreSQL traffic from the Internet\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"ed42b617-7d62-4328-88cb-b18d64b565a5"}]},"multiregion":{"resources":[{"id":"ed42b617-7d62-4328-88cb-b18d64b565a5"}]}}},{"policy":"ecc-azure-130","resource_type":"Network security groups","description":"Network Security Group with inbound rule that allows SMTP traffic from the Internet\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"c439bef4-ca5f-42ff-93fe-d18169697352"}]},"multiregion":{"resources":[{"id":"c439bef4-ca5f-42ff-93fe-d18169697352"}]}}},{"policy":"ecc-azure-131","resource_type":"Network security groups","description":"Network Security Group with inbound rule that allows Telnet traffic from the Internet\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"441f6c0d-b7c9-4a11-bbec-895f82d10cbc"}]},"multiregion":{"resources":[{"id":"441f6c0d-b7c9-4a11-bbec-895f82d10cbc"}]}}},{"policy":"ecc-azure-132","resource_type":"Virtual Machines","description":"Instance without deletion protection\n","severity":"Medium","regions_data":{"westeurope":{"resources":[{"id":"cdc256d0-67c9-4389-911c-120f918b69ce"}]},"multiregion":{"resources":[{"id":"cdc256d0-67c9-4389-911c-120f918b69ce"}]}}},{"policy":"ecc-azure-133","resource_type":"Virtual Machines","description":"Instance Without Any Tags\n","severity":"Medium","regions_data":{"westeurope":{"resources":[{"id":"d4acad5d-8bb4-4418-aa6e-63e8bab63def"}]},"multiregion":{"resources":[{"id":"d4acad5d-8bb4-4418-aa6e-63e8bab63def"}]}}},{"policy":"ecc-azure-137","resource_type":"Azure Storage Accounts","description":"Storage account without replication enabled\n","severity":"Info","regions_data":{"westeurope":{"resources":[{"id":"6dd3be97-ec90-4c6d-b3a2-cd1973101f3a"}]},"multiregion":{"resources":[{"id":"6dd3be97-ec90-4c6d-b3a2-cd1973101f3a"}]}}},{"policy":"ecc-azure-139","resource_type":"Azure Disk Storage","description":"Disk without recent snapshots taken in the last 14 days\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"70f6d76c-63d4-494d-a5d4-e09cb583e7f2"}]},"multiregion":{"resources":[{"id":"70f6d76c-63d4-494d-a5d4-e09cb583e7f2"}]}}},{"policy":"ecc-azure-141","resource_type":"Virtual Network","description":"Virtual network with network interface assigned to virtual machine where firewall subnet resides and no route tables configured\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"16f66fb5-0913-4a2b-91d1-f6b1b9c3e3a1"}]},"multiregion":{"resources":[{"id":"16f66fb5-0913-4a2b-91d1-f6b1b9c3e3a1"}]}}},{"policy":"ecc-azure-142","resource_type":"Network security groups","description":"Network Security Group assigned to network interface or subnet with inbound rule that allows all traffic from the Internet\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"2685f401-a908-4436-baac-9e610bc0ced4"}]},"multiregion":{"resources":[{"id":"2685f401-a908-4436-baac-9e610bc0ced4"}]}}},{"policy":"ecc-azure-143","resource_type":"API Management","description":"API Management service without virtual network configured\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"d1c6a788-c701-4a37-8dae-a534d134fca6"}]},"multiregion":{"resources":[{"id":"d1c6a788-c701-4a37-8dae-a534d134fca6"}]}}},{"policy":"ecc-azure-144","resource_type":"Azure Kubernetes Service","description":"Kubernetes cluster without authorized IP access or/and exposed to the public Internet\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"4e1ec643-543c-4b61-ad46-27917f4e6cb3"}]},"multiregion":{"resources":[{"id":"4e1ec643-543c-4b61-ad46-27917f4e6cb3"}]}}},{"policy":"ecc-azure-145","resource_type":"Azure Cosmos DB","description":"Cosmos DB accounts without firewall rules\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"adb9864c-736c-44c7-9f38-0cfeb6dffd21"}]},"multiregion":{"resources":[{"id":"adb9864c-736c-44c7-9f38-0cfeb6dffd21"}]}}},{"policy":"ecc-azure-146","resource_type":"Key Vault","description":"Key Vault with enabled public access\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"b2a6bc7e-aca3-4f0d-a038-f5fb0a9e375d"}]},"multiregion":{"resources":[{"id":"b2a6bc7e-aca3-4f0d-a038-f5fb0a9e375d"}]}}},{"policy":"ecc-azure-147","resource_type":"Cognitive Services","description":"Cognitive service with enabled public access\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"82a37aa2-541a-4a3a-8c11-fa8dad0136b0"}]},"multiregion":{"resources":[{"id":"82a37aa2-541a-4a3a-8c11-fa8dad0136b0"}]}}},{"policy":"ecc-azure-148","resource_type":"Cognitive Services","description":"Cognitive service with defaultAction set to \"Allow\"\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"0a4c1dfb-635d-4c5f-814f-cd843108e7d2"}]},"multiregion":{"resources":[{"id":"0a4c1dfb-635d-4c5f-814f-cd843108e7d2"}]}}},{"policy":"ecc-azure-149","resource_type":"Azure Container Registry","description":"Azure Container Registry which accepts connections over the Internet from hosts on any network.\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"861d927a-8ea0-4b52-a858-373c55df02c4"}]},"multiregion":{"resources":[{"id":"861d927a-8ea0-4b52-a858-373c55df02c4"}]}}},{"policy":"ecc-azure-150","resource_type":"Virtual Network","description":"Primary virtual machine network interface with public ip assigned without Network Security Group assignment\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"8c1e215b-68d0-4eab-a0df-2909a2d7f319"}]},"multiregion":{"resources":[{"id":"8c1e215b-68d0-4eab-a0df-2909a2d7f319"}]}}},{"policy":"ecc-azure-151","resource_type":"Virtual Network","description":"Virtual machine network interface with IP Forwarding enabled\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"b7baa805-dced-445a-a53d-880f22e8e9c6"}]},"multiregion":{"resources":[{"id":"b7baa805-dced-445a-a53d-880f22e8e9c6"}]}}},{"policy":"ecc-azure-152","resource_type":"Virtual Machines","description":"VM without JIT policy enabled for SSH or RDP ports\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"9cb8ddb6-7c3c-43e5-8ad9-e88a3eb468e2"}]},"multiregion":{"resources":[{"id":"9cb8ddb6-7c3c-43e5-8ad9-e88a3eb468e2"}]}}},{"policy":"ecc-azure-155","resource_type":"Azure SQL Database","description":"Azure SQL instance with public access enabled\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"f97375b2-ac33-4e55-b504-c99a64dc5795"}]},"multiregion":{"resources":[{"id":"f97375b2-ac33-4e55-b504-c99a64dc5795"}]}}},{"policy":"ecc-azure-156","resource_type":"Azure Database for MariaDB","description":"MariaDB instance with public access enabled\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"1d6db14d-08cd-4fa2-93ad-567ab4323bf6"}]},"multiregion":{"resources":[{"id":"1d6db14d-08cd-4fa2-93ad-567ab4323bf6"}]}}},{"policy":"ecc-azure-157","resource_type":"Azure Database for MySQL","description":"MySQL instance with public access enabled\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"7207fb00-9fe7-4025-b80a-6251e320fee7"}]},"multiregion":{"resources":[{"id":"7207fb00-9fe7-4025-b80a-6251e320fee7"}]}}},{"policy":"ecc-azure-158","resource_type":"Azure Database for PostgreSQL","description":"PostgreSQL instance with public access enabled\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"38b0175c-8528-4627-866e-f437ad99cf91"}]},"multiregion":{"resources":[{"id":"38b0175c-8528-4627-866e-f437ad99cf91"}]}}},{"policy":"ecc-azure-159","resource_type":"Azure Storage Accounts","description":"Storage accounts without virtual network IP rules\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"6b6c7b63-b19c-433d-8c8c-58d27feaa71a"}]},"multiregion":{"resources":[{"id":"6b6c7b63-b19c-433d-8c8c-58d27feaa71a"}]}}},{"policy":"ecc-azure-160","resource_type":"Virtual Network","description":"Virtual network with network security groups not assigned to subnets\n","severity":"Info","regions_data":{"westeurope":{"resources":[{"id":"350564df-4a3c-4808-8e60-3bb20663ccf1"}]},"multiregion":{"resources":[{"id":"350564df-4a3c-4808-8e60-3bb20663ccf1"}]}}},{"policy":"ecc-azure-161","resource_type":"App Configuration","description":"App Configuration service without Private Endpoint connection configured\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"2fe7144f-00a1-416f-b157-9a27a467da41"}]},"multiregion":{"resources":[{"id":"2fe7144f-00a1-416f-b157-9a27a467da41"}]}}},{"policy":"ecc-azure-162","resource_type":"Azure Cache for Redis","description":"Redis cache that does not reside in a subnet\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"71293998-65e0-403f-8547-188877217399"}]},"multiregion":{"resources":[{"id":"71293998-65e0-403f-8547-188877217399"}]}}},{"policy":"ecc-azure-163","resource_type":"Event Grid","description":"Event Grid Domains service without Private Endpoint connection configured\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"e5691239-5256-4165-a2a5-0728134b4187"}]},"multiregion":{"resources":[{"id":"e5691239-5256-4165-a2a5-0728134b4187"}]}}},{"policy":"ecc-azure-164","resource_type":"Event Grid","description":"Event Grid Topics service without Private Endpoint connection configured\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"1443d720-e8ab-4104-bb0e-ad4ff288c9de"}]},"multiregion":{"resources":[{"id":"1443d720-e8ab-4104-bb0e-ad4ff288c9de"}]}}},{"policy":"ecc-azure-165","resource_type":"Azure Machine Learning","description":"Machine Learning workspace without Private Endpoint connection configured\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"b4490f1b-fe57-4f98-b5d2-7725e900f0bb"}]},"multiregion":{"resources":[{"id":"b4490f1b-fe57-4f98-b5d2-7725e900f0bb"}]}}},{"policy":"ecc-azure-166","resource_type":"Azure SignalR Service","description":"SignalR service without Private Endpoint connection configured\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"425b7f5f-ca0e-4029-8392-e91a5a579dd7"}]},"multiregion":{"resources":[{"id":"425b7f5f-ca0e-4029-8392-e91a5a579dd7"}]}}},{"policy":"ecc-azure-167","resource_type":"Azure Spring Apps","description":"Spring Cloud service without runtime subnet configured\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"44545b87-d173-4e68-898e-e5f4d7bf699f"}]},"multiregion":{"resources":[{"id":"44545b87-d173-4e68-898e-e5f4d7bf699f"}]}}},{"policy":"ecc-azure-168","resource_type":"Azure Container Registry","description":"Container Registry without Private Endpoint connection configured\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"ff4f80ec-8cf5-4d60-b234-6c0978348d9f"}]},"multiregion":{"resources":[{"id":"ff4f80ec-8cf5-4d60-b234-6c0978348d9f"}]}}},{"policy":"ecc-azure-170","resource_type":"Key Vault","description":"Key Vault without Private Endpoint connection configured\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"d445e783-1842-4cdb-82a6-3dff74ecb389"}]},"multiregion":{"resources":[{"id":"d445e783-1842-4cdb-82a6-3dff74ecb389"}]}}},{"policy":"ecc-azure-171","resource_type":"Azure Database for MariaDB","description":"MariaDB instance without Private Endpoint connection configured\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"c2196b41-ea8e-404e-882e-27f508947e3b"}]},"multiregion":{"resources":[{"id":"c2196b41-ea8e-404e-882e-27f508947e3b"}]}}},{"policy":"ecc-azure-172","resource_type":"Azure Database for MySQL","description":"MySQL instance without Private Endpoint connection configured\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"0c72c5e6-3d3c-4657-bf48-a5fab522e8c5"}]},"multiregion":{"resources":[{"id":"0c72c5e6-3d3c-4657-bf48-a5fab522e8c5"}]}}},{"policy":"ecc-azure-173","resource_type":"Azure Database for PostgreSQL","description":"PostgreSQL instance without Private Endpoint connection configured\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"862edf41-b118-4a4b-a842-72e13acb0afa"}]},"multiregion":{"resources":[{"id":"862edf41-b118-4a4b-a842-72e13acb0afa"}]}}},{"policy":"ecc-azure-174","resource_type":"Azure Storage Accounts","description":"Storage Account without Private Endpoint connection configured\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"c981bdb3-0c30-4d4a-ab8e-498e0c1d23de"}]},"multiregion":{"resources":[{"id":"c981bdb3-0c30-4d4a-ab8e-498e0c1d23de"}]}}},{"policy":"ecc-azure-176","resource_type":"Virtual Network","description":"Virtual network without DDoS protection enabled which contains application gateway subnet\n","severity":"Info","regions_data":{"westeurope":{"resources":[{"id":"7f55715c-06a3-4fd4-9c63-d488682968a5"}]},"multiregion":{"resources":[{"id":"7f55715c-06a3-4fd4-9c63-d488682968a5"}]}}},{"policy":"ecc-azure-177","resource_type":"Application Gateway","description":"Application Gateway without Web Application Firewall enabled\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"675f4b48-c4b3-4d9a-a2a2-36f1374dd8dc"}]},"multiregion":{"resources":[{"id":"675f4b48-c4b3-4d9a-a2a2-36f1374dd8dc"}]}}},{"policy":"ecc-azure-178","resource_type":"Azure Front Door","description":"Azure Front Door service without Web Application Firewall enabled\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"67084cd3-f1af-4c1f-a10b-e64edc469f5e"}]},"multiregion":{"resources":[{"id":"67084cd3-f1af-4c1f-a10b-e64edc469f5e"}]}}},{"policy":"ecc-azure-179","resource_type":"App Service","description":"API app without Managed identity configured (both SystemAssigned and UserAssigned)\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"b2feb84d-068d-4d0f-b9d7-a2dcd5451d59"}]},"multiregion":{"resources":[{"id":"b2feb84d-068d-4d0f-b9d7-a2dcd5451d59"}]}}},{"policy":"ecc-azure-180","resource_type":"App Service","description":"Function app without Managed identity configured (both SystemAssigned and UserAssigned)\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"adcafa32-72c8-4089-b3a1-ddee395ad2ca"}]},"multiregion":{"resources":[{"id":"adcafa32-72c8-4089-b3a1-ddee395ad2ca"}]}}},{"policy":"ecc-azure-181","resource_type":"App Service","description":"Web app without Managed identity configured (both SystemAssigned and UserAssigned)\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"ead75bec-c316-4a25-9e6c-4c8554eb281e"}]},"multiregion":{"resources":[{"id":"ead75bec-c316-4a25-9e6c-4c8554eb281e"}]}}},{"policy":"ecc-azure-182","resource_type":"Azure Service Fabric","description":"Service Frabric clusters without AAD client authentication\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"be545a91-f9a3-40f8-951e-2bf00473e125"}]},"multiregion":{"resources":[{"id":"be545a91-f9a3-40f8-951e-2bf00473e125"}]}}},{"policy":"ecc-azure-184","resource_type":"Virtual Machines","description":"Linux virtual machine without SSH authentication method as primary configured (Allows password authentication)\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"d6586bd0-22eb-4f6d-8626-d20461a05cb5"}]},"multiregion":{"resources":[{"id":"d6586bd0-22eb-4f6d-8626-d20461a05cb5"}]}}},{"policy":"ecc-azure-196","resource_type":"Azure SQL Managed Instance","description":"Advanced Threat Protection is disabled on SQL managed instance\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"b517f6d2-55a1-47f3-a846-c3e3365689a4"}]},"multiregion":{"resources":[{"id":"b517f6d2-55a1-47f3-a846-c3e3365689a4"}]}}},{"policy":"ecc-azure-197","resource_type":"Virtual Machines","description":"Virtual machine without Azure Disk Encryption configured\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"df1dd248-5e7e-410a-bdfa-94b67fb6fe49"}]},"multiregion":{"resources":[{"id":"df1dd248-5e7e-410a-bdfa-94b67fb6fe49"}]}}},{"policy":"ecc-azure-199","resource_type":"Azure Cache for Redis","description":"SSL connection is disabled on Redis Cache\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"8debafce-00dd-4bc9-b13e-fb1215e7c8d9"}]},"multiregion":{"resources":[{"id":"8debafce-00dd-4bc9-b13e-fb1215e7c8d9"}]}}},{"policy":"ecc-azure-200","resource_type":"Automation","description":"Automation account with unencrypted variable\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"4668b52b-070a-4ca5-ae4e-ac483a6ff513"}]},"multiregion":{"resources":[{"id":"4668b52b-070a-4ca5-ae4e-ac483a6ff513"}]}}},{"policy":"ecc-azure-201","resource_type":"Azure Cosmos DB","description":"Cosmos DB accounts without CMK encryption configured\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"66d92f74-1405-48e8-962f-295dbbb417a6"}]},"multiregion":{"resources":[{"id":"66d92f74-1405-48e8-962f-295dbbb417a6"}]}}},{"policy":"ecc-azure-202","resource_type":"Azure Machine Learning","description":"Machine Learning workspace without CMK encryption configured\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"0a1f63ba-06ac-4738-a780-4e2029e31f3e"}]},"multiregion":{"resources":[{"id":"0a1f63ba-06ac-4738-a780-4e2029e31f3e"}]}}},{"policy":"ecc-azure-203","resource_type":"Azure Database for PostgreSQL","description":"PostgreSQL instance without CMK encryption configured\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"118eeff6-873d-47dc-8ea1-6b591bc20723"}]},"multiregion":{"resources":[{"id":"118eeff6-873d-47dc-8ea1-6b591bc20723"}]}}},{"policy":"ecc-azure-204","resource_type":"Cognitive Services","description":"Cognitive Services without CMK encryption configured\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"b68f6092-6f7b-4e6a-8feb-547dac5a287b"}]},"multiregion":{"resources":[{"id":"b68f6092-6f7b-4e6a-8feb-547dac5a287b"}]}}},{"policy":"ecc-azure-205","resource_type":"Azure Container Registry","description":"Container Registry without CMK encryption configured\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"2c363603-f8fa-4695-a694-21464f5131da"}]},"multiregion":{"resources":[{"id":"2c363603-f8fa-4695-a694-21464f5131da"}]}}},{"policy":"ecc-azure-206","resource_type":"Azure Service Fabric","description":"Service Fabric cluster without configured ClusterProtectionLevel property set to EncryptAndSign\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"90894bd0-0a4b-4abd-843b-daea99637dfc"}]},"multiregion":{"resources":[{"id":"90894bd0-0a4b-4abd-843b-daea99637dfc"}]}}},{"policy":"ecc-azure-207","resource_type":"Azure SQL Managed Instance","description":"SQL managed instance without CMK encryption configured\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"26ae4e1d-94e8-4c87-8cf6-57a3b83c0fdb"}]},"multiregion":{"resources":[{"id":"26ae4e1d-94e8-4c87-8cf6-57a3b83c0fdb"}]}}},{"policy":"ecc-azure-213","resource_type":"Microsoft Defender for Cloud","description":"Azure Defender for DNS is set to \"Off\"\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"4f2f30b9-d2a2-4646-9b56-3650086b5c09"}]},"multiregion":{"resources":[{"id":"4f2f30b9-d2a2-4646-9b56-3650086b5c09"}]}}},{"policy":"ecc-azure-214","resource_type":"Microsoft Defender for Cloud","description":"Azure Defender for Resource Manager is set to \"Off\"\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"2ba951c2-1aa1-4f9a-a20d-fa96ce57b0e0"}]},"multiregion":{"resources":[{"id":"2ba951c2-1aa1-4f9a-a20d-fa96ce57b0e0"}]}}},{"policy":"ecc-azure-215","resource_type":"Virtual Machines","description":"Linux virtual machines without Dependency Agent installed\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"488bbde1-1aec-4724-b7ec-c06284d0774c"}]},"multiregion":{"resources":[{"id":"488bbde1-1aec-4724-b7ec-c06284d0774c"}]}}},{"policy":"ecc-azure-216","resource_type":"Virtual Machines","description":"Windows virtual machines without Dependency Agent installed\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"34e6c7e8-201a-4461-b147-d3cbb273fe9a"}]},"multiregion":{"resources":[{"id":"34e6c7e8-201a-4461-b147-d3cbb273fe9a"}]}}},{"policy":"ecc-azure-217","resource_type":"Azure Data Lake Storage","description":"Data Lake Store with logging and retention policy disabled\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"0ad502c2-ca47-460c-8604-80556cb199f4"}]},"multiregion":{"resources":[{"id":"0ad502c2-ca47-460c-8604-80556cb199f4"}]}}},{"policy":"ecc-azure-218","resource_type":"Azure Stream Analytics","description":"Azure Stream with logging and retention policy disabled\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"e486564b-f592-4b64-ab78-cb520a1e1555"}]},"multiregion":{"resources":[{"id":"e486564b-f592-4b64-ab78-cb520a1e1555"}]}}},{"policy":"ecc-azure-219","resource_type":"Batch","description":"Batch account with logging and retention policy disabled\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"591c518b-8d71-45f2-9be8-4b1e8aa70dd6"}]},"multiregion":{"resources":[{"id":"591c518b-8d71-45f2-9be8-4b1e8aa70dd6"}]}}},{"policy":"ecc-azure-220","resource_type":"Data Lake Analytics","description":"Data Lake Analytics with logging and retention policy disabled\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"ecb492ba-9aad-4abc-9009-abd56cb71d5e"}]},"multiregion":{"resources":[{"id":"ecb492ba-9aad-4abc-9009-abd56cb71d5e"}]}}},{"policy":"ecc-azure-222","resource_type":"Azure IoT Hub","description":"IoT Hub with logging and retention policy disabled\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"c471eaf8-ef27-4715-a300-a2fc40cc51d6"}]},"multiregion":{"resources":[{"id":"c471eaf8-ef27-4715-a300-a2fc40cc51d6"}]}}},{"policy":"ecc-azure-224","resource_type":"Azure Logic Apps","description":"Logic Apps service with logging and retention policy disabled\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"c3d0bf41-109c-473e-b858-5290d8389d19"}]},"multiregion":{"resources":[{"id":"c3d0bf41-109c-473e-b858-5290d8389d19"}]}}},{"policy":"ecc-azure-225","resource_type":"Azure Cognitive Search","description":"Azure Search with logging and retention policy disabled\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"27e29873-95d9-416f-b06f-2f3644b92727"}]},"multiregion":{"resources":[{"id":"27e29873-95d9-416f-b06f-2f3644b92727"}]}}},{"policy":"ecc-azure-226","resource_type":"Service Bus","description":"Service Bus with logging and retention policy disabled\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"953e6ce8-a9ca-4f46-8d13-d78029bf2d3a"}]},"multiregion":{"resources":[{"id":"953e6ce8-a9ca-4f46-8d13-d78029bf2d3a"}]}}},{"policy":"ecc-azure-227","resource_type":"Azure Virtual Machine Scale Sets","description":"Virtual machine scale sets without LinuxDiagnostic or IaaSDiangostics extension installed\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"a290671d-506b-4fc9-ba4a-e3076744895e"}]},"multiregion":{"resources":[{"id":"a290671d-506b-4fc9-ba4a-e3076744895e"}]}}},{"policy":"ecc-azure-228","resource_type":"Virtual Machines","description":"Virtual machine without Guest Configuration extension installed\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"64a293e5-ce94-4192-868e-c9ffd22cbffb"}]},"multiregion":{"resources":[{"id":"64a293e5-ce94-4192-868e-c9ffd22cbffb"}]}}},{"policy":"ecc-azure-231","resource_type":"Virtual Machines","description":"Virtual machine without MicrosoftMonitoringAgent or OmsAgentForLinux extension installed\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"73adec1c-39ca-4277-9b15-d6fb5e57b54e"}]},"multiregion":{"resources":[{"id":"73adec1c-39ca-4277-9b15-d6fb5e57b54e"}]}}},{"policy":"ecc-azure-232","resource_type":"Azure Virtual Machine Scale Sets","description":"Virtual machine scale sets without MicrosoftMonitoringAgent or OmsAgentForLinux extension installed\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"2bfe232b-b647-4880-b906-6182382d726c"}]},"multiregion":{"resources":[{"id":"2bfe232b-b647-4880-b906-6182382d726c"}]}}},{"policy":"ecc-azure-234","resource_type":"Virtual Machines","description":"Virtual machine with Guest Configuration extension installed without utilizing Managed Identity (SystemAssigned)\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"5631a909-1bfc-4f3a-a76d-5b4b5631c3e8"}]},"multiregion":{"resources":[{"id":"5631a909-1bfc-4f3a-a76d-5b4b5631c3e8"}]}}},{"policy":"ecc-azure-235","resource_type":"Azure Kubernetes Service","description":"Kubernetes cluster with Azure Policy for AKS disabled\n","severity":"Info","regions_data":{"westeurope":{"resources":[{"id":"983b484f-32e6-4928-8962-e480274e50cb"}]},"multiregion":{"resources":[{"id":"983b484f-32e6-4928-8962-e480274e50cb"}]}}},{"policy":"ecc-azure-236","resource_type":"App Service","description":"API app with CORS rule that allows every resource to access the service\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"f4820dcd-9aec-4206-9aa3-90fe3cd9ab5e"}]},"multiregion":{"resources":[{"id":"f4820dcd-9aec-4206-9aa3-90fe3cd9ab5e"}]}}},{"policy":"ecc-azure-237","resource_type":"App Service","description":"Function app with CORS rule that allows every resource to access the service\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"164a529b-88c0-4a80-9dc0-9b7d3908addf"}]},"multiregion":{"resources":[{"id":"164a529b-88c0-4a80-9dc0-9b7d3908addf"}]}}},{"policy":"ecc-azure-238","resource_type":"App Service","description":"Web app with CORS rule that allows every resource to access the service\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"1ec8f101-72aa-4d34-9cbe-79fbdbbbd7bf"}]},"multiregion":{"resources":[{"id":"1ec8f101-72aa-4d34-9cbe-79fbdbbbd7bf"}]}}},{"policy":"ecc-azure-239","resource_type":"App Service","description":"API app with 'Incoming client certificates' disabled\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"990e1b69-a02d-4acc-9313-50525aa96287"}]},"multiregion":{"resources":[{"id":"990e1b69-a02d-4acc-9313-50525aa96287"}]}}},{"policy":"ecc-azure-240","resource_type":"App Service","description":"Web app with 'Incoming client certificates' disabled\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"dda0b459-9af2-4bb8-a507-9879f055de64"}]},"multiregion":{"resources":[{"id":"dda0b459-9af2-4bb8-a507-9879f055de64"}]}}},{"policy":"ecc-azure-241","resource_type":"App Service","description":"Function app with 'Incoming client certificates' disabled\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"38ddf3b8-5f3d-43ac-80cc-3a81c4407e1f"}]},"multiregion":{"resources":[{"id":"38ddf3b8-5f3d-43ac-80cc-3a81c4407e1f"}]}}},{"policy":"ecc-azure-256","resource_type":"App Service","description":"API app with Remote debugging enabled\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"e8c777a4-0fec-40af-a6b3-0398ffe39b60"}]},"multiregion":{"resources":[{"id":"e8c777a4-0fec-40af-a6b3-0398ffe39b60"}]}}},{"policy":"ecc-azure-257","resource_type":"App Service","description":"Function app with Remote debugging enabled\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"80288aee-1f2c-481a-a3e8-1878adda6746"}]},"multiregion":{"resources":[{"id":"80288aee-1f2c-481a-a3e8-1878adda6746"}]}}},{"policy":"ecc-azure-258","resource_type":"App Service","description":"Web app with Remote debugging enabled\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"df6f0c42-e519-42a3-8dc1-56f631027455"}]},"multiregion":{"resources":[{"id":"df6f0c42-e519-42a3-8dc1-56f631027455"}]}}},{"policy":"ecc-azure-265","resource_type":"Azure SQL Managed Instance","description":"Azure SQL Vulnerability assessment is disabled for a managed instance\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"e37d1f3a-c039-4d63-a040-54b4fc7322a7"}]},"multiregion":{"resources":[{"id":"e37d1f3a-c039-4d63-a040-54b4fc7322a7"}]}}},{"policy":"ecc-azure-267","resource_type":"App Service","description":"Function app has an outdated Java version\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"fcd76500-0cc9-4d8a-ba24-8adf0aa869a7"}]},"multiregion":{"resources":[{"id":"fcd76500-0cc9-4d8a-ba24-8adf0aa869a7"}]}}},{"policy":"ecc-azure-270","resource_type":"App Service","description":"Function app has an outdated Python version\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"8b44a3fe-5f66-4b38-9834-1a6fd4981051"}]},"multiregion":{"resources":[{"id":"8b44a3fe-5f66-4b38-9834-1a6fd4981051"}]}}},{"policy":"ecc-azure-272","resource_type":"Azure Virtual Machine Scale Sets","description":"Virtual machine scale sets without endpoint protection installed\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"38833c82-2bb6-44fe-aaee-e12344444d53"}]},"multiregion":{"resources":[{"id":"38833c82-2bb6-44fe-aaee-e12344444d53"}]}}},{"policy":"ecc-azure-275","resource_type":"Virtual Machines","description":"Virtual machine without Backup configured\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"e9ea3303-142a-4d75-a213-f09649e26f5d"}]},"multiregion":{"resources":[{"id":"e9ea3303-142a-4d75-a213-f09649e26f5d"}]}}},{"policy":"ecc-azure-276","resource_type":"Azure Database for MariaDB","description":"MariaDB instance without Geo-redundant backup\n","severity":"Info","regions_data":{"westeurope":{"resources":[{"id":"0ec8eaee-1a0b-4ed8-957b-53273f47cd7e"}]},"multiregion":{"resources":[{"id":"0ec8eaee-1a0b-4ed8-957b-53273f47cd7e"}]}}},{"policy":"ecc-azure-277","resource_type":"Azure Database for MySQL","description":"MySQL instance without Geo-redundant backup\n","severity":"Info","regions_data":{"westeurope":{"resources":[{"id":"f00ea759-1d5a-403d-bde2-45458e0ba7a0"}]},"multiregion":{"resources":[{"id":"f00ea759-1d5a-403d-bde2-45458e0ba7a0"}]}}},{"policy":"ecc-azure-278","resource_type":"Azure Database for PostgreSQL","description":"PostgreSQL instance without Geo-redundant backup\n","severity":"Info","regions_data":{"westeurope":{"resources":[{"id":"fcac81d0-238e-4f31-8b11-562487ec5b4b"}]},"multiregion":{"resources":[{"id":"fcac81d0-238e-4f31-8b11-562487ec5b4b"}]}}},{"policy":"ecc-azure-279","resource_type":"Azure Kubernetes Service","description":"Kubernetes cluster with local authentication methods enabled\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"f8fc9d80-f599-426b-b8b3-a3dea2caec65"}]},"multiregion":{"resources":[{"id":"f8fc9d80-f599-426b-b8b3-a3dea2caec65"}]}}},{"policy":"ecc-azure-280","resource_type":"Azure Kubernetes Service","description":"Kubernetes cluster with private cluster feature disabled\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"5fa5d3ed-0c04-44d9-8335-30100148d367"}]},"multiregion":{"resources":[{"id":"5fa5d3ed-0c04-44d9-8335-30100148d367"}]}}},{"policy":"ecc-azure-281","resource_type":"Azure Kubernetes Service","description":"Kubernetes cluster that utilizes one of the vulnerable k8s versions\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"aa3105fe-b6c0-4436-9402-18a5192b0f37"}]},"multiregion":{"resources":[{"id":"aa3105fe-b6c0-4436-9402-18a5192b0f37"}]}}},{"policy":"ecc-azure-282","resource_type":"Azure Kubernetes Service","description":"Kubernetes cluster without EncryptionAtHost enabled\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"bd15d6c3-a2bd-43c8-aca9-e8892675ff3b"}]},"multiregion":{"resources":[{"id":"bd15d6c3-a2bd-43c8-aca9-e8892675ff3b"}]}}},{"policy":"ecc-azure-283","resource_type":"Azure Kubernetes Service","description":"Kubernetes cluster with logging and retention policy disabled\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"039e7fa7-142d-4a61-8fe6-21b14bbd2704"}]},"multiregion":{"resources":[{"id":"039e7fa7-142d-4a61-8fe6-21b14bbd2704"}]}}},{"policy":"ecc-azure-284","resource_type":"Azure Kubernetes Service","description":"Kubernetes cluster without OS and Data disks CMK encryption configured\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"6a136811-bc3b-4d0d-bb4c-d4122f7bcc9e"}]},"multiregion":{"resources":[{"id":"6a136811-bc3b-4d0d-bb4c-d4122f7bcc9e"}]}}},{"policy":"ecc-azure-286","resource_type":"Azure Kubernetes Service","description":"A network policy is not in place to secure traffic between pods\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"0cdcc87c-8676-4475-8acc-dafab7595bc1"}]},"multiregion":{"resources":[{"id":"0cdcc87c-8676-4475-8acc-dafab7595bc1"}]}}},{"policy":"ecc-azure-287","resource_type":"Azure Kubernetes Service","description":"Azure CNI Networking is disabled\n","severity":"Info","regions_data":{"westeurope":{"resources":[{"id":"34071817-d356-4e66-a871-cf976951a86d"}]},"multiregion":{"resources":[{"id":"34071817-d356-4e66-a871-cf976951a86d"}]}}},{"policy":"ecc-azure-288","resource_type":"Azure Kubernetes Service","description":"Cluster Pool contains less than 3 Nodes\n","severity":"Info","regions_data":{"westeurope":{"resources":[{"id":"71e8e48b-f117-4bc2-8f3b-e88fe52c8c20"}]},"multiregion":{"resources":[{"id":"71e8e48b-f117-4bc2-8f3b-e88fe52c8c20"}]}}},{"policy":"ecc-azure-289","resource_type":"Azure Container Registry","description":"Admin user is enabled for Container Registry\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"000dab24-b4af-430b-a20b-bc7620e01014"}]},"multiregion":{"resources":[{"id":"000dab24-b4af-430b-a20b-bc7620e01014"}]}}},{"policy":"ecc-azure-290","resource_type":"Azure Container Registry","description":"Container Registry has no locks\n","severity":"Medium","regions_data":{"westeurope":{"resources":[{"id":"82238a46-b9b3-4886-be33-1a1a65005e2b"}]},"multiregion":{"resources":[{"id":"82238a46-b9b3-4886-be33-1a1a65005e2b"}]}}},{"policy":"ecc-azure-291","resource_type":"Azure Storage Accounts","description":"Storage Accounts outside Europe\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"a18b840e-6f8c-4f0d-a684-b5b8d2d82883"}]},"multiregion":{"resources":[{"id":"a18b840e-6f8c-4f0d-a684-b5b8d2d82883"}]}}},{"policy":"ecc-azure-293","resource_type":"Azure SQL Database","description":"Azure SQL Server data replication with Fail Over groups\n","severity":"Info","regions_data":{"westeurope":{"resources":[{"id":"15d317d8-8ab0-4fb9-8847-8413b64bd98d"}]},"multiregion":{"resources":[{"id":"15d317d8-8ab0-4fb9-8847-8413b64bd98d"}]}}},{"policy":"ecc-azure-294","resource_type":"Virtual Machines","description":"Azure Virtual Machine is not assigned to an availability set\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"692ff955-ebe4-40c3-bd84-f4d37e6b5e39"}]},"multiregion":{"resources":[{"id":"692ff955-ebe4-40c3-bd84-f4d37e6b5e39"}]}}},{"policy":"ecc-azure-295","resource_type":"Azure SQL Database","description":"Name like 'Admin' for an Azure SQL Server Active Directory Administrator account is found\n","severity":"Info","regions_data":{"westeurope":{"resources":[{"id":"d8d1d977-7ee3-4f2c-92e8-99c411ca3727"}]},"multiregion":{"resources":[{"id":"d8d1d977-7ee3-4f2c-92e8-99c411ca3727"}]}}},{"policy":"ecc-azure-296","resource_type":"Azure SQL Database","description":"Name like 'Admin' for an Azure SQL Server Administrator account is found\n","severity":"Info","regions_data":{"westeurope":{"resources":[{"id":"9d9ef700-a9f2-437b-9521-08dcab8faabc"}]},"multiregion":{"resources":[{"id":"9d9ef700-a9f2-437b-9521-08dcab8faabc"}]}}},{"policy":"ecc-azure-298","resource_type":"App Service","description":"Application Service Logs are Disabled for Containerized Function Apps\n","severity":"Info","regions_data":{"westeurope":{"resources":[{"id":"100779ee-f742-42bf-94b5-e8dccbadd002"}]},"multiregion":{"resources":[{"id":"100779ee-f742-42bf-94b5-e8dccbadd002"}]}}},{"policy":"ecc-azure-299","resource_type":"App Service","description":"Health Check is disabled for your Function App\n","severity":"Info","regions_data":{"westeurope":{"resources":[{"id":"17db3794-253e-4684-955f-4c8965ed12fb"}]},"multiregion":{"resources":[{"id":"17db3794-253e-4684-955f-4c8965ed12fb"}]}}},{"policy":"ecc-azure-300","resource_type":"Application Gateway","description":"Application Gateway with vulnerable and outdated TLS version\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"9975b06c-5595-4619-8dc0-528ab4a7ccdc"}]},"multiregion":{"resources":[{"id":"9975b06c-5595-4619-8dc0-528ab4a7ccdc"}]}}},{"policy":"ecc-azure-301","resource_type":"Azure Cache for Redis","description":"Redis Cache without exposed to the public Internet\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"37924b28-e52d-4f9b-b776-61bf3750f6e8"}]},"multiregion":{"resources":[{"id":"37924b28-e52d-4f9b-b776-61bf3750f6e8"}]}}},{"policy":"ecc-azure-302","resource_type":"Azure Cache for Redis","description":"Redis Cache with enabled public access\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"1eb0ec7a-a332-4d83-8c04-516c229f36f0"}]},"multiregion":{"resources":[{"id":"1eb0ec7a-a332-4d83-8c04-516c229f36f0"}]}}},{"policy":"ecc-azure-304","resource_type":"Application Gateway","description":"Application Gateway is using Http protocol\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"26ed67ad-e32f-4b26-b0c4-8e64c69b64e3"}]},"multiregion":{"resources":[{"id":"26ed67ad-e32f-4b26-b0c4-8e64c69b64e3"}]}}},{"policy":"ecc-azure-305","resource_type":"Azure Storage Accounts","description":"Storage account with vulnerable and outdated TLS version\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"c9fb038b-58bd-4584-b97b-0de6c6ecab6e"}]},"multiregion":{"resources":[{"id":"c9fb038b-58bd-4584-b97b-0de6c6ecab6e"}]}}},{"policy":"ecc-azure-306","resource_type":"Azure Database for PostgreSQL","description":"PostgreSQL instance with disabled Infrastructure double encryption\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"fc251352-119b-4c02-9825-91946da2cf39"}]},"multiregion":{"resources":[{"id":"fc251352-119b-4c02-9825-91946da2cf39"}]}}},{"policy":"ecc-azure-310","resource_type":"Microsoft Defender for Cloud","description":"Azure Defender for OpenSource Relational Databases is set to \"Off\"\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"93eb5ddc-c8bf-49ca-9cd7-61643cb5a199"}]},"multiregion":{"resources":[{"id":"93eb5ddc-c8bf-49ca-9cd7-61643cb5a199"}]}}},{"policy":"ecc-azure-311","resource_type":"Azure Database for PostgreSQL","description":"PostgreSQL instance with server parameter 'logging collector' disabled\n","severity":"Info","regions_data":{"westeurope":{"resources":[{"id":"76ad1442-55d2-4c7c-be07-d688dfae7895"}]},"multiregion":{"resources":[{"id":"76ad1442-55d2-4c7c-be07-d688dfae7895"}]}}},{"policy":"ecc-azure-313","resource_type":"Azure Database for PostgreSQL","description":"PostgreSQL instance without server parameter 'log_min_messages' set to WARNING\n","severity":"Info","regions_data":{"westeurope":{"resources":[{"id":"7f2f4c78-52ee-4103-ba49-7139903746a6"}]},"multiregion":{"resources":[{"id":"7f2f4c78-52ee-4103-ba49-7139903746a6"}]}}},{"policy":"ecc-azure-314","resource_type":"Azure Database for PostgreSQL","description":"PostgreSQL instance with server parameter 'debug_print_plan' enabled\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"0f13387f-512d-4e41-b5e7-75e04ec204c0"}]},"multiregion":{"resources":[{"id":"0f13387f-512d-4e41-b5e7-75e04ec204c0"}]}}},{"policy":"ecc-azure-317","resource_type":"Azure Database for PostgreSQL","description":"PostgreSQL instance without server parameter 'log_error_verbosity' set to VERBOSE\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"cb74c8a3-e0fe-4afb-a071-c75ed5aad896"}]},"multiregion":{"resources":[{"id":"cb74c8a3-e0fe-4afb-a071-c75ed5aad896"}]}}},{"policy":"ecc-azure-318","resource_type":"Azure Database for PostgreSQL","description":"PostgreSQL instance with server parameter 'log_line_prefix' set incorrectly\n","severity":"Info","regions_data":{"westeurope":{"resources":[{"id":"d9e3b979-56df-457c-a8c4-469f5b28c37f"}]},"multiregion":{"resources":[{"id":"d9e3b979-56df-457c-a8c4-469f5b28c37f"}]}}},{"policy":"ecc-azure-319","resource_type":"Azure Database for PostgreSQL","description":"PostgreSQL instance without server parameter 'log_min_error_statement' set to ERROR\n","severity":"Info","regions_data":{"westeurope":{"resources":[{"id":"24343122-47c1-4943-8835-7598a38a619e"}]},"multiregion":{"resources":[{"id":"24343122-47c1-4943-8835-7598a38a619e"}]}}},{"policy":"ecc-azure-321","resource_type":"Azure Database for PostgreSQL","description":"PostgreSQL instance with server parameter 'log_statement' set incorrectly\n","severity":"Info","regions_data":{"westeurope":{"resources":[{"id":"25cc3706-b33e-46ed-bede-bae86ba61823"}]},"multiregion":{"resources":[{"id":"25cc3706-b33e-46ed-bede-bae86ba61823"}]}}},{"policy":"ecc-azure-323","resource_type":"Azure Virtual Machine Scale Sets","description":"Azure Linux virtual machines scale set doesn't use an SSH key\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"9a1e3eb1-4f77-462e-aabc-482430f9c1d1"}]},"multiregion":{"resources":[{"id":"9a1e3eb1-4f77-462e-aabc-482430f9c1d1"}]}}},{"policy":"ecc-azure-324","resource_type":"Azure Data Explorer","description":"Azure Kusto cluster without double encryption enabled\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"049ba1e8-11ff-49d9-854d-886d9dd8eaa9"}]},"multiregion":{"resources":[{"id":"049ba1e8-11ff-49d9-854d-886d9dd8eaa9"}]}}},{"policy":"ecc-azure-325","resource_type":"Azure Data Explorer","description":"Azure Kusto cluster without disk encryption\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"f49f8b0d-1e70-4b08-a892-97dbc86b7146"}]},"multiregion":{"resources":[{"id":"f49f8b0d-1e70-4b08-a892-97dbc86b7146"}]}}},{"policy":"ecc-azure-326","resource_type":"Azure Data Explorer","description":"Azure Kusto cluster without CMK configured\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"65e0eb4c-07f8-46bc-90a8-2b756a82494b"}]},"multiregion":{"resources":[{"id":"65e0eb4c-07f8-46bc-90a8-2b756a82494b"}]}}},{"policy":"ecc-azure-327","resource_type":"Azure Data Factory","description":"Azure Data Factory doesn't use Git repository for source control\n","severity":"Info","regions_data":{"westeurope":{"resources":[{"id":"06ecbbd4-1b94-4216-b062-2b276cc6c27b"}]},"multiregion":{"resources":[{"id":"06ecbbd4-1b94-4216-b062-2b276cc6c27b"}]}}},{"policy":"ecc-azure-328","resource_type":"Azure Data Factory","description":"Azure data factories are not encrypted with a customer-managed key\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"289417d4-3b1a-4571-bbb6-6034eb00a18e"}]},"multiregion":{"resources":[{"id":"289417d4-3b1a-4571-bbb6-6034eb00a18e"}]}}},{"policy":"ecc-azure-329","resource_type":"Batch","description":"Azure Batch account doesn't use key vault to encrypt data\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"38e07afa-b8e1-47e2-83a0-17723f4d42dc"}]},"multiregion":{"resources":[{"id":"38e07afa-b8e1-47e2-83a0-17723f4d42dc"}]}}},{"policy":"ecc-azure-331","resource_type":"App Service","description":"App service with disabled detailed logging of error messages\n","severity":"Info","regions_data":{"westeurope":{"resources":[{"id":"10d793db-9c76-42a2-ac12-2ea89b3b96a0"}]},"multiregion":{"resources":[{"id":"10d793db-9c76-42a2-ac12-2ea89b3b96a0"}]}}},{"policy":"ecc-azure-332","resource_type":"App Service","description":"App service without configured failed requests tracings\n","severity":"Info","regions_data":{"westeurope":{"resources":[{"id":"8e374e0c-39d5-4c86-97d1-9bde876f4295"}]},"multiregion":{"resources":[{"id":"8e374e0c-39d5-4c86-97d1-9bde876f4295"}]}}},{"policy":"ecc-azure-333","resource_type":"Azure IoT Hub","description":"Public network access enabled for Azure IoT Hub\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"f0bda1f4-1288-4f39-91f6-b59d379d4576"}]},"multiregion":{"resources":[{"id":"f0bda1f4-1288-4f39-91f6-b59d379d4576"}]}}},{"policy":"ecc-azure-334","resource_type":"Azure Cosmos DB","description":"Cosmos DB account with unrestricted write access to the management plane\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"3d57da42-1085-4ff6-987a-671f8b5cf143"}]},"multiregion":{"resources":[{"id":"3d57da42-1085-4ff6-987a-671f8b5cf143"}]}}},{"policy":"ecc-azure-336","resource_type":"Azure Virtual Machine Scale Sets","description":"Virtual machine scale sets without EncryptionAtHost enabled\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"e12f78b8-8757-435d-8f89-ebb2e709f66b"}]},"multiregion":{"resources":[{"id":"e12f78b8-8757-435d-8f89-ebb2e709f66b"}]}}},{"policy":"ecc-azure-337","resource_type":"Virtual Machines","description":"Microsoft Antimalware is not configured to automatically update Virtual Machines\n","severity":"Medium","regions_data":{"westeurope":{"resources":[{"id":"40714622-b8e4-4964-9edf-11d12edfb797"}]},"multiregion":{"resources":[{"id":"40714622-b8e4-4964-9edf-11d12edfb797"}]}}},{"policy":"ecc-azure-339","resource_type":"Key Vault","description":"Secret without 'content_type' set\n","severity":"Medium","regions_data":{"westeurope":{"resources":[{"id":"643be613-4af7-4782-8094-632657b2aa87"}]},"multiregion":{"resources":[{"id":"643be613-4af7-4782-8094-632657b2aa87"}]}}},{"policy":"ecc-azure-340","resource_type":"Application Gateway","description":"Application Gateway without Log4j WAF rule enabled or applied Ruleset version 3.0 or above\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"9a1d0684-c58d-448d-ac24-3a088028fc0a"}]},"multiregion":{"resources":[{"id":"9a1d0684-c58d-448d-ac24-3a088028fc0a"}]}}},{"policy":"ecc-azure-341","resource_type":"Azure Front Door","description":"Azure Front Door without Log4j WAF rule enabled\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"a824151f-8ccd-4700-973c-0da58c0f0fc7"}]},"multiregion":{"resources":[{"id":"a824151f-8ccd-4700-973c-0da58c0f0fc7"}]}}},{"policy":"ecc-azure-342","resource_type":"Azure SQL Database","description":"Azure SQL instance with vulnerable and outdated TLS version\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"d672ba84-80fe-4d7f-b1c9-35106629dac3"}]},"multiregion":{"resources":[{"id":"d672ba84-80fe-4d7f-b1c9-35106629dac3"}]}}},{"policy":"ecc-azure-343","resource_type":"Azure Database for PostgreSQL","description":"Advanced Threat Protection is disabled on PostgreSQL server\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"345a425f-7e92-4648-b579-3c2695897d98"}]},"multiregion":{"resources":[{"id":"345a425f-7e92-4648-b579-3c2695897d98"}]}}},{"policy":"ecc-azure-344","resource_type":"Azure Database for MySQL","description":"Advanced Threat Protection is disabled on MySQL server\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"a571a66e-82ab-43c1-a2bf-74d18958d6f3"}]},"multiregion":{"resources":[{"id":"a571a66e-82ab-43c1-a2bf-74d18958d6f3"}]}}},{"policy":"ecc-azure-345","resource_type":"Azure Database for MySQL","description":"MySQL instance with disabled Infrastructure double encryption\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"89f6beae-530d-4363-8745-3dcaf00e0d4b"}]},"multiregion":{"resources":[{"id":"89f6beae-530d-4363-8745-3dcaf00e0d4b"}]}}},{"policy":"ecc-azure-346","resource_type":"Azure Database for MySQL","description":"MySQL instance with vulnerable and outdated TLS version\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"0cf8983b-f67a-4919-a4f2-ce996a9adc48"}]},"multiregion":{"resources":[{"id":"0cf8983b-f67a-4919-a4f2-ce996a9adc48"}]}}},{"policy":"ecc-azure-347","resource_type":"Azure Database for MySQL","description":"MySQL instance without CMK encryption configured\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"b19a8ec8-7592-47a9-8676-10625747ccea"}]},"multiregion":{"resources":[{"id":"b19a8ec8-7592-47a9-8676-10625747ccea"}]}}},{"policy":"ecc-azure-348","resource_type":"Azure Database for MySQL","description":"MySQL instance with server parameter 'local_infile' enabled\n","severity":"Info","regions_data":{"westeurope":{"resources":[{"id":"009474fc-cbe1-40f5-8440-579eb3c3d74b"}]},"multiregion":{"resources":[{"id":"009474fc-cbe1-40f5-8440-579eb3c3d74b"}]}}},{"policy":"ecc-azure-349","resource_type":"Azure Database for MySQL","description":"MySQL instance without server setting \"max_user_connections\" limits\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"57982ada-1a5c-438a-b23b-ed6e40c48c6b"}]},"multiregion":{"resources":[{"id":"57982ada-1a5c-438a-b23b-ed6e40c48c6b"}]}}},{"policy":"ecc-azure-350","resource_type":"Azure Database for MySQL","description":"MySQL instance with server parameter 'slow_query_log' disabled\n","severity":"Medium","regions_data":{"westeurope":{"resources":[{"id":"6f540f5f-68ec-48b9-a484-7e2b4d431e85"}]},"multiregion":{"resources":[{"id":"6f540f5f-68ec-48b9-a484-7e2b4d431e85"}]}}},{"policy":"ecc-azure-351","resource_type":"Azure Database for MySQL","description":"MySQL instance without sql_mode parameter set to \"STRICT_ALL_TABLES\" value\n","severity":"Info","regions_data":{"westeurope":{"resources":[{"id":"08bf74e2-14e5-4e2c-8db0-cc03e04e735b"}]},"multiregion":{"resources":[{"id":"08bf74e2-14e5-4e2c-8db0-cc03e04e735b"}]}}},{"policy":"ecc-azure-353","resource_type":"Azure Virtual Machine Scale Sets","description":"Virtual machine scale sets without OS image autoupgrade enabled\n","severity":"Info","regions_data":{"westeurope":{"resources":[{"id":"d0dfbd46-74d3-4979-9eb1-f3462f5d90c8"}]},"multiregion":{"resources":[{"id":"d0dfbd46-74d3-4979-9eb1-f3462f5d90c8"}]}}},{"policy":"ecc-azure-354","resource_type":"Azure Container Registry","description":"Container registry with anonymous pull enabled\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"3a143807-199c-4dac-a4fd-1adcf3323660"}]},"multiregion":{"resources":[{"id":"3a143807-199c-4dac-a4fd-1adcf3323660"}]}}},{"policy":"ecc-azure-355","resource_type":"Azure Machine Learning","description":"Azure Machine Learning Compute cluster have minNodeCount property not equal to 0\n","severity":"Medium","regions_data":{"westeurope":{"resources":[{"id":"0141b6ff-5963-4c1d-a988-64f7c229b666"}]},"multiregion":{"resources":[{"id":"0141b6ff-5963-4c1d-a988-64f7c229b666"}]}}},{"policy":"ecc-azure-356","resource_type":"API Management","description":"API Managment service without configured client certificates\n","severity":"Medium","regions_data":{"westeurope":{"resources":[{"id":"08de3dae-8de1-42f4-8f5b-fed7da6617d6"}]},"multiregion":{"resources":[{"id":"08de3dae-8de1-42f4-8f5b-fed7da6617d6"}]}}},{"policy":"ecc-azure-357","resource_type":"Azure Databricks","description":"Azure Databricks workspace with enabled public access\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"b2f70bba-a58c-4bda-ba9c-a6b98ca85405"}]},"multiregion":{"resources":[{"id":"b2f70bba-a58c-4bda-ba9c-a6b98ca85405"}]}}},{"policy":"ecc-azure-358","resource_type":"Azure Synapse Analytics","description":"Azure Synapse workspace without managed virtual network\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"c09cbb08-8f1b-48f4-9b35-8dfe4e86efcd"}]},"multiregion":{"resources":[{"id":"c09cbb08-8f1b-48f4-9b35-8dfe4e86efcd"}]}}},{"policy":"ecc-azure-359","resource_type":"Azure Synapse Analytics","description":"Azure Synapse workspace without data exfiltration enabled\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"d17d0d46-e463-471a-bfe2-1179b5383997"}]},"multiregion":{"resources":[{"id":"d17d0d46-e463-471a-bfe2-1179b5383997"}]}}},{"policy":"ecc-azure-362","resource_type":"Microsoft Defender for Cloud","description":"Azure Virtual Machines without Vulnerability Assessment solution\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"45eeaa3f-d4a8-48c8-be5d-25883b5dbc9a"}]},"multiregion":{"resources":[{"id":"45eeaa3f-d4a8-48c8-be5d-25883b5dbc9a"}]}}},{"policy":"ecc-azure-364","resource_type":"Azure Monitor","description":"Activity Log Alert without tags\n","severity":"Medium","regions_data":{"westeurope":{"resources":[{"id":"2cc29a1a-6995-483d-9a93-e3e49d6e9e61"}]},"multiregion":{"resources":[{"id":"2cc29a1a-6995-483d-9a93-e3e49d6e9e61"}]}}},{"policy":"ecc-azure-365","resource_type":"API Management","description":"API Management without tags\n","severity":"Medium","regions_data":{"westeurope":{"resources":[{"id":"3b53e844-d26c-486c-9a38-898c7d426a3d"}]},"multiregion":{"resources":[{"id":"3b53e844-d26c-486c-9a38-898c7d426a3d"}]}}},{"policy":"ecc-azure-367","resource_type":"Virtual Machines","description":"Linux virtual machine affected to OMI vulnerability (CVE-2021-38645)\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"16f8ae09-7bcd-4ab0-9d29-a3ca1998de0e"}]},"multiregion":{"resources":[{"id":"16f8ae09-7bcd-4ab0-9d29-a3ca1998de0e"}]}}},{"policy":"ecc-azure-368","resource_type":"Azure Virtual Machine Scale Sets","description":"Linux virtual machine scale set affected to OMI vulnerability (CVE-2021-38645)\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"052b2da0-6f94-4872-afce-b9feb31102f0"}]},"multiregion":{"resources":[{"id":"052b2da0-6f94-4872-afce-b9feb31102f0"}]}}},{"policy":"ecc-azure-369","resource_type":"Azure Storage Accounts","description":"Storage Account without Infrastructure Encryption enabled\n","severity":"Info","regions_data":{"westeurope":{"resources":[{"id":"1fa2470c-ca29-40f4-9f19-e27290a32e34"}]},"multiregion":{"resources":[{"id":"1fa2470c-ca29-40f4-9f19-e27290a32e34"}]}}},{"policy":"ecc-azure-370","resource_type":"Azure Cosmos DB","description":"CosmosDB account without Private Endpoint connection configured\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"31a25d84-46d4-4e07-8d83-af2ce28a755f"}]},"multiregion":{"resources":[{"id":"31a25d84-46d4-4e07-8d83-af2ce28a755f"}]}}},{"policy":"ecc-azure-371","resource_type":"Azure Database for MySQL","description":"MySQL instance with server setting \"audit_log_enabled\" set to \"off\"\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"c5aabc5f-f014-419d-ad70-fe009a42876a"}]},"multiregion":{"resources":[{"id":"c5aabc5f-f014-419d-ad70-fe009a42876a"}]}}},{"policy":"ecc-azure-372","resource_type":"Azure Database for MySQL","description":"MySQL instance with server setting \"audit_log_events\" set to \"off\"\n","severity":"Info","regions_data":{"westeurope":{"resources":[{"id":"3266e8a9-97e3-4ff0-ad7a-a37ff5a90f52"}]},"multiregion":{"resources":[{"id":"3266e8a9-97e3-4ff0-ad7a-a37ff5a90f52"}]}}},{"policy":"ecc-azure-373","resource_type":"Azure Subscription","description":"Subscription where Activity Log Alert does not exist for Create or Update Public IP Address rule\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"e54f2b9b-a180-49f6-95fd-db70ab9bcec6"}]},"multiregion":{"resources":[{"id":"e54f2b9b-a180-49f6-95fd-db70ab9bcec6"}]}}},{"policy":"ecc-azure-374","resource_type":"Azure Subscription","description":"Subscription where Activity Log Alert does not exist for Delete Public IP Address rule\n","severity":"Low","regions_data":{"westeurope":{"resources":[{"id":"292ed1d9-6fb8-4f79-87e1-72ca707ea4f7"}]},"multiregion":{"resources":[{"id":"292ed1d9-6fb8-4f79-87e1-72ca707ea4f7"}]}}},{"policy":"ecc-azure-378","resource_type":"Network security groups","description":"Network Security Group Flow Log Analytics disabled\n","severity":"Info","regions_data":{"westeurope":{"resources":[{"id":"dc1b42b9-9ee9-4dd0-8514-c25e748035a5"}]},"multiregion":{"resources":[{"id":"dc1b42b9-9ee9-4dd0-8514-c25e748035a5"}]}}},{"policy":"ecc-azure-379","resource_type":"App Service","description":"App Service with web requests logging disabled\n","severity":"High","regions_data":{"westeurope":{"resources":[{"id":"7bd18c7f-21da-4951-810b-96f8d2925dd7"}]},"multiregion":{"resources":[{"id":"7bd18c7f-21da-4951-810b-96f8d2925dd7"}]}}}] \ No newline at end of file diff --git a/tests/tests_metrics/expected_metrics/customer_metrics.json b/tests/tests_metrics/expected_metrics/customer_metrics.json new file mode 100644 index 000000000..ba16d2934 --- /dev/null +++ b/tests/tests_metrics/expected_metrics/customer_metrics.json @@ -0,0 +1,361 @@ +{ + "ATTACK_VECTOR": { + "average": {}, + "aws": { + "data": [ + { + "tactic_id": "TA0006", + "tactic": "Credential Access", + "severity_data": { + "Low": 76, + "High": 30, + "Info": 6 + } + }, + { + "tactic_id": "TA0007", + "tactic": "Discovery", + "severity_data": { + "Low": 62, + "High": 16, + "Info": 3 + } + }, + { + "tactic_id": "TA0009", + "tactic": "Collection", + "severity_data": { + "Low": 91, + "High": 37, + "Info": 6, + "Medium": 1 + } + }, + { + "tactic_id": "TA0010", + "tactic": "Exfiltration", + "severity_data": { + "Low": 24, + "High": 9, + "Info": 2 + } + }, + { + "tactic_id": "TA0040", + "tactic": "Impact", + "severity_data": { + "Low": 116, + "High": 72, + "Info": 10, + "Medium": 2 + } + }, + { + "tactic_id": "TA0001", + "tactic": "Initial Access", + "severity_data": { + "Low": 59, + "High": 29, + "Info": 5 + } + }, + { + "tactic_id": "TA0003", + "tactic": "Persistence", + "severity_data": { + "Low": 16, + "High": 13 + } + }, + { + "tactic_id": "TA0004", + "tactic": "Privilege Escalation", + "severity_data": { + "Low": 14, + "High": 10, + "Info": 1 + } + }, + { + "tactic_id": "TA0008", + "tactic": "Lateral Movement", + "severity_data": { + "Low": 45, + "High": 15, + "Info": 6 + } + }, + { + "tactic_id": "TA0002", + "tactic": "Execution", + "severity_data": { + "Low": 12, + "High": 10, + "Medium": 1 + } + }, + { + "tactic_id": "TA0005", + "tactic": "Defense Evasion", + "severity_data": { + "Low": 12, + "High": 12, + "Info": 3 + } + }, + { + "tactic_id": "TA0042", + "tactic": "Resource Development", + "severity_data": { + "Low": 2, + "High": 2 + } + }, + { + "tactic_id": "TA0043", + "tactic": "Reconnaissance", + "severity_data": { + "High": 2, + "Low": 5, + "Info": 1 + } + }, + { + "tactic_id": "TA0011", + "tactic": "Command and Control", + "severity_data": { + "Low": 2, + "High": 2 + } + } + ] + }, + "azure": { + "data": [ + { + "tactic_id": "TA0004", + "tactic": "Privilege Escalation", + "severity_data": { + "Low": 9, + "High": 1 + } + }, + { + "tactic_id": "TA0005", + "tactic": "Defense Evasion", + "severity_data": { + "Medium": 1, + "High": 1, + "Info": 1, + "Low": 4 + } + }, + { + "tactic_id": "TA0040", + "tactic": "Impact", + "severity_data": { + "Low": 72, + "High": 28, + "Info": 4 + } + }, + { + "tactic_id": "TA0009", + "tactic": "Collection", + "severity_data": { + "Low": 18, + "High": 1 + } + }, + { + "tactic_id": "TA0007", + "tactic": "Discovery", + "severity_data": { + "Low": 5 + } + }, + { + "tactic_id": "TA0008", + "tactic": "Lateral Movement", + "severity_data": { + "Low": 23, + "High": 2, + "Medium": 1 + } + }, + { + "tactic_id": "TA0001", + "tactic": "Initial Access", + "severity_data": { + "Low": 28, + "High": 2, + "Medium": 1 + } + }, + { + "tactic_id": "TA0006", + "tactic": "Credential Access", + "severity_data": { + "Low": 29, + "Medium": 1, + "High": 1, + "Info": 2 + } + }, + { + "tactic_id": "TA0003", + "tactic": "Persistence", + "severity_data": { + "Low": 4, + "High": 1 + } + }, + { + "tactic_id": "TA0002", + "tactic": "Execution", + "severity_data": { + "Low": 7 + } + } + ] + }, + "c": "EPAM Systems", + "d": "2023-10-01", + "google": { + "data": [] + }, + "id": "", + "ot": [], + "t": "ATTACK_VECTOR" + }, + "COMPLIANCE": { + "average": { + "aws": 0.8636666666666668, + "azure": 0.0, + "google": 0.0 + }, + "aws": { + "average_data": [{"name": "DA", "value": 0.0}, {"name": "s202", "value": 2.12}, {"name": "Standard3 Standard11_800_53_Rev5", "value": 0.06}, {"name": "Standard2 19", "value": 0.0}, {"name": "CJIS", "value": 0.86}, {"name": "Standard14 27701_2019", "value": 0.79}, {"name": "Standard7", "value": 0.0}, {"name": "Standard15", "value": 0.02}, {"name": "CIS AWS Foundations Benchmark v1.5.0", "value": 0.0}, {"name": "Standard5", "value": 0.0}, {"name": "CMMC v2.0", "value": 0.0}, {"name": "Standard14 27002_2013", "value": 0.79}, {"name": "Standard9 v1.1", "value": 0.0}, {"name": "Standard6 2016_679", "value": 0.49}, {"name": "Standard14 27002_2022", "value": 0.57}, {"name": "Standard8 v3.2.1", "value": 0.78}, {"name": "Standard4 v4", "value": 0.06}, {"name": "Standard1 v8", "value": 0.21}, {"name": "Standard10", "value": 0.67}, {"name": "BSA v3", "value": 0.55}, {"name": "Standard14 27018_2019", "value": 2.08}, {"name": "Standard12", "value": 0.07}, {"name": "Standard1 v7", "value": 2.08}, {"name": "Standard11 800_53 Rev5", "value": 0.05}, {"name": "CIS AWS Foundations Benchmark v1.4.0", "value": 0.0}, {"name": "Standard14 27017_2015", "value": 0.79}, {"name": "Standard11 800-171 Rev2", "value": 0.0}, {"name": "Standard14 27001_2013", "value": 0.79}, {"name": "CE v2.2", "value": 0.0}], + "total_scanned_tenants": 1, + "last_scan_date": "2023-09-26T16:18:00.185909Z" + }, + "azure": { + "average_data": [{"name": "CE v2.2", "value": 0.0}, {"name": "CFB v1.5.0", "value": 0.0}, {"name": "DA", "value": 0.0}, {"name": "s202", "value": 0.0}, {"name": "Standard3 Standard11_800_53_Rev5", "value": 0.0}, {"name": "Standard2 19", "value": 0.0}, {"name": "CJIS", "value": 0.0}, {"name": "Standard14 27701_2019", "value": 0.0}, {"name": "Standard15", "value": 0.0}, {"name": "CFB v1.4.0", "value": 0.0}, {"name": "Standard14 27002_2013", "value": 0.0}, {"name": "CMMC v2.0", "value": 0.0}, {"name": "Standard9 v1.1", "value": 0.0}, {"name": "Standard6 2016_679", "value": 0.0}, {"name": "Standard14 27002_2022", "value": 0.0}, {"name": "Standard4 v4", "value": 0.0}, {"name": "Standard1 v8", "value": 0.0}, {"name": "Standard10", "value": 0.0}, {"name": "BSA v3", "value": 0.0}, {"name": "Standard14 27018_2019", "value": 0.0}, {"name": "Standard1 v7", "value": 0.0}, {"name": "Standard11 800_53 Rev5", "value": 0.0}, {"name": "Standard14 27017_2015", "value": 0.0}, {"name": "Standard11 800-171 Rev2", "value": 0.0}, {"name": "Standard14 27001_2013", "value": 0.0}, {"name": "Standard7", "value": 0.0}, {"name": "Standard5", "value": 0.0}, {"name": "Standard12", "value": 0.0}], + "total_scanned_tenants": 0, + "last_scan_date": null + }, + "c": "EPAM Systems", + "d": "2023-10-01", + "google": { + "average_data": [{"name": "Standard1 v8", "value": 0.0}, {"name": "Standard14 27002_2013", "value": 0.0}, {"name": "Standard10", "value": 0.0}, {"name": "BSA v3", "value": 0.0}, {"name": "Standard15", "value": 0.0}, {"name": "Standard14 27018_2019", "value": 0.0}, {"name": "Standard14 27002_2022", "value": 0.0}, {"name": "Standard14 27701_2019", "value": 0.0}, {"name": "Standard9 v1.1", "value": 0.0}, {"name": "Standard1 v7", "value": 0.0}, {"name": "s202", "value": 0.0}, {"name": "Standard11 800_53 Rev5", "value": 0.0}, {"name": "Standard3 Standard11_800_53_Rev5", "value": 0.0}, {"name": "Standard14 27017_2015", "value": 0.0}, {"name": "Standard14 27001_2013", "value": 0.0}, {"name": "CE v2.2", "value": 0.0}, {"name": "Standard2 19", "value": 0.0}, {"name": "CMMC v2.0", "value": 0.0}, {"name": "Standard6 2016_679", "value": 0.0}, {"name": "Standard4 v4", "value": 0.0}, {"name": "Standard12", "value": 0.0}, {"name": "Standard11 800-171 Rev2", "value": 0.0}, {"name": "Standard7", "value": 0.0}, {"name": "Standard5", "value": 0.0}], + "total_scanned_tenants": 0, + "last_scan_date": null + }, + "id": "", + "ot": [], + "t": "COMPLIANCE" + }, + "OVERVIEW": { + "average": {}, + "aws": { + "total_scanned_tenants": 1, + "last_scan_date": "2023-09-26T16:18:00.185909Z", + "resources_violated": 486, + "succeeded_scans": 5, + "failed_scans": 5, + "severity_data": { + "Low": 183, + "Info": 50, + "High": 187, + "Medium": 67 + }, + "resource_types_data": { + "AWS Identity and Access Management": 15, + "AWS Identity and Access Management Access Analyzer": 1, + "Amazon S3": 14, + "Amazon CloudFront": 16, + "AWS Account": 4, + "Amazon Route 53": 6, + "AWS Web Application Firewall": 4, + "Amazon Virtual Private Cloud": 17, + "Amazon Relational Database Service": 68, + "Amazon Elastic Load Balancing": 15, + "AWS CloudFormation": 3, + "Amazon EC2": 50, + "Amazon Elastic Block Store": 10, + "Amazon Simple Queue Service": 4, + "Amazon Elastic Kubernetes Service": 8, + "AWS CloudTrail": 9, + "AWS Key Management Service": 3, + "AWS CodeBuild": 7, + "Amazon EC2 Auto Scaling": 11, + "Amazon OpenSearch Service": 14, + "AWS Lambda": 10, + "Amazon Redshift": 15, + "Amazon SageMaker": 7, + "Amazon Kinesis": 7, + "AWS Certificate Manager": 8, + "Amazon Elastic Container Service": 13, + "Amazon API Gateway": 12, + "Amazon DynamoDB": 3, + "Amazon Elastic File System": 5, + "Amazon ElastiCache": 14, + "AWS Database Migration Service": 6, + "Amazon DynamoDB Accelerator": 3, + "AWS Elastic Beanstalk": 7, + "Amazon EMR": 8, + "AWS Secrets Manager": 3, + "Amazon Simple Notification Service": 4, + "Amazon Elastic Container Registry": 4, + "AWS Transit Gateway": 4, + "Amazon AppFlow": 2, + "AWS Glue": 11, + "Amazon DocumentDB": 1, + "Amazon WorkSpaces Family": 11, + "AWS Backup": 2, + "Amazon EventBridge": 1, + "Amazon S3 Glacier": 2, + "AWS Config": 1, + "Amazon FSx": 8, + "Amazon MQ": 6, + "Amazon Managed Streaming for Apache Kafka": 4, + "Amazon Managed Workflows for Apache Airflow": 8, + "AWS Directory": 1, + "Amazon Data Lifecycle Manager": 1, + "Amazon Lightsail": 1, + "Amazon CloudWatch": 3, + "Amazon QLDB": 3, + "AWS AppSync": 4, + "AWS CodeDeploy": 3, + "AWS CodePipeline": 1, + "AWS Step Functions": 1 + }, + "total_scans": 10 + }, + "azure": { + "total_scanned_tenants": 0, + "last_scan_date": null, + "resources_violated": 0, + "succeeded_scans": 0, + "failed_scans": 0, + "severity_data": {}, + "resource_types_data": {}, + "total_scans": 0 + }, + "c": "EPAM Systems", + "d": "2023-10-01", + "google": { + "total_scanned_tenants": 0, + "last_scan_date": null, + "resources_violated": 0, + "succeeded_scans": 0, + "failed_scans": 0, + "severity_data": {}, + "resource_types_data": {}, + "total_scans": 0 + }, + "id": "", + "ot": [], + "t": "OVERVIEW" + } +} \ No newline at end of file diff --git a/tests/tests_metrics/expected_metrics/department_metrics.json b/tests/tests_metrics/expected_metrics/department_metrics.json new file mode 100644 index 000000000..09e4f2c99 --- /dev/null +++ b/tests/tests_metrics/expected_metrics/department_metrics.json @@ -0,0 +1,2464 @@ +{ + "RESOURCES_BY_TENANT": { + "aws": { + "total_scans": 0, + "failed_scans": 0, + "succeeded_scans": 0, + "resources_violated": 484, + "last_scan_date": "2023-10-21T11:11:00", + "activated_regions": "", + "tenant_name": "AWS-TEST", + "account_id": "AWS-1234567890123", + "resource_types_data": { + "AWS Identity and Access Management": 15, + "AWS Identity and Access Management Access Analyzer": 1, + "Amazon S3": 14, + "Amazon CloudFront": 16, + "AWS Account": 4, + "Amazon Route 53": 6, + "AWS Web Application Firewall": 4, + "Amazon Virtual Private Cloud": 17, + "Amazon Relational Database Service": 68, + "Amazon Elastic Load Balancing": 15, + "AWS CloudFormation": 3, + "Amazon EC2": 50, + "Amazon Elastic Block Store": 10, + "Amazon Simple Queue Service": 4, + "Amazon Elastic Kubernetes Service": 8, + "AWS CloudTrail": 9, + "AWS Key Management Service": 3, + "AWS CodeBuild": 7, + "Amazon EC2 Auto Scaling": 11, + "Amazon OpenSearch Service": 14, + "AWS Lambda": 10, + "Amazon Redshift": 15, + "Amazon SageMaker": 7, + "Amazon Kinesis": 7, + "AWS Certificate Manager": 8, + "Amazon Elastic Container Service": 13, + "Amazon API Gateway": 12, + "Amazon DynamoDB": 3, + "Amazon Elastic File System": 5, + "Amazon ElastiCache": 14, + "AWS Database Migration Service": 6, + "Amazon DynamoDB Accelerator": 3, + "AWS Elastic Beanstalk": 7, + "Amazon EMR": 8, + "AWS Secrets Manager": 3, + "Amazon Simple Notification Service": 4, + "Amazon Elastic Container Registry": 4, + "AWS Transit Gateway": 4, + "Amazon AppFlow": 2, + "AWS Glue": 11, + "Amazon DocumentDB": 1, + "Amazon WorkSpaces Family": 11, + "AWS Backup": 2, + "Amazon EventBridge": 1, + "Amazon S3 Glacier": 2, + "AWS Config": 1, + "Amazon FSx": 8, + "Amazon MQ": 6, + "Amazon Managed Streaming for Apache Kafka": 4, + "Amazon Managed Workflows for Apache Airflow": 8, + "AWS Directory": 1, + "Amazon Data Lifecycle Manager": 1, + "Amazon Lightsail": 1, + "Amazon CloudWatch": 3, + "Amazon QLDB": 3, + "AWS AppSync": 4, + "AWS CodeDeploy": 3, + "AWS CodePipeline": 1, + "AWS Step Functions": 1 + }, + "severity_data": { + "Low": 183, + "Info": 50, + "High": 187, + "Medium": 67 + } + }, + "azure": { + "total_scans": 0, + "failed_scans": 0, + "succeeded_scans": 0, + "resources_violated": 258, + "last_scan_date": "2023-10-21T11:11:00", + "activated_regions": "", + "tenant_name": "AZURE-TEST", + "account_id": "AZURE-1234567890123", + "resource_types_data": { + "Azure RBAC": 2, + "Microsoft Defender for Cloud": 36, + "Azure Storage Accounts": 36, + "Azure SQL Database": 32, + "Azure Database for PostgreSQL": 40, + "Azure Database for MySQL": 28, + "Key Vault": 14, + "Azure Subscription": 24, + "Network security groups": 36, + "Azure Disk Storage": 6, + "Azure Kubernetes Service": 24, + "App Service": 56, + "Virtual Machines": 34, + "Virtual Network": 10, + "API Management": 6, + "Azure Cosmos DB": 8, + "Cognitive Services": 6, + "Azure Container Registry": 12, + "Azure Database for MariaDB": 6, + "App Configuration": 2, + "Azure Cache for Redis": 8, + "Event Grid": 4, + "Azure Machine Learning": 6, + "Azure SignalR Service": 2, + "Azure Spring Apps": 2, + "Application Gateway": 8, + "Azure Front Door": 4, + "Azure Service Fabric": 4, + "Azure SQL Managed Instance": 6, + "Automation": 2, + "Azure Data Lake Storage": 2, + "Azure Stream Analytics": 2, + "Batch": 4, + "Data Lake Analytics": 2, + "Azure IoT Hub": 4, + "Azure Logic Apps": 2, + "Azure Cognitive Search": 2, + "Service Bus": 2, + "Azure Virtual Machine Scale Sets": 14, + "Azure Data Explorer": 6, + "Azure Data Factory": 4, + "Azure Databricks": 2, + "Azure Synapse Analytics": 4, + "Azure Monitor": 2 + }, + "severity_data": { + "Low": 316, + "Medium": 26, + "Info": 68, + "High": 106 + } + }, + "google": { + "total_scans": 0, + "failed_scans": 0, + "succeeded_scans": 0, + "resources_violated": 276, + "last_scan_date": "2023-10-21T11:11:00", + "activated_regions": "", + "tenant_name": "GOOGLE-TEST", + "account_id": "109876543210", + "resource_types_data": { + "Cloud IAM": 12, + "Cloud KMS": 5, + "Cloud APIs": 4, + "Cloud Logging": 10, + "Cloud Storage": 14, + "Virtual Private Cloud": 39, + "Cloud DNS": 4, + "Compute Engine": 38, + "Cloud SQL": 46, + "Google Kubernetes Engine": 39, + "Secret Manager": 2, + "Cloud Load Balancing": 12, + "BigQuery": 4, + "Cloud Functions": 10, + "App Engine": 3, + "Cloud Bigtable": 3, + "Dataproc": 4, + "Cloud Run": 6, + "Cloud Armor": 4, + "Pub/Sub": 3, + "Cloud Spanner": 5, + "Cloud Memorystore": 2, + "Dataflow": 1, + "Vertex AI Workbench": 1, + "Cloud Data Fusion": 3, + "Access Transparency": 1, + "Access Approval": 1, + "Cloud Asset Inventory": 1 + }, + "severity_data": { + "High": 99, + "Low": 121, + "Medium": 10, + "Info": 47 + } + }, + "outdated_tenants": [], + "tenant_display_name": "test", + "customer": "EPAM Systems", + "date": "2023-10-01", + "type": "RESOURCES_BY_TENANT", + "defining_attribute": 1018, + "id": "" + }, + "COMPLIANCE_BY_TENANT": { + "aws": { + "regions_data": [ + { + "region": "eu-west-1", + "standards_data": [ + { + "name": "Standard7", + "value": 0 + }, + { + "name": "Standard14 27017_2015", + "value": 1.69 + }, + { + "name": "Standard8 v3.2.1", + "value": 1.67 + }, + { + "name": "Standard14 27018_2019", + "value": 4.46 + }, + { + "name": "Standard14 27002_2013", + "value": 1.69 + }, + { + "name": "Standard14 27001_2013", + "value": 1.69 + }, + { + "name": "Standard3 Standard11_800_53_Rev5", + "value": 0.0 + }, + { + "name": "Standard11 800-171 Rev2", + "value": 0 + }, + { + "name": "Standard1 v7", + "value": 3.12 + }, + { + "name": "Standard1 v8", + "value": 0.46 + }, + { + "name": "Standard12", + "value": 0.0 + }, + { + "name": "CIS AWS Foundations Benchmark v1.5.0", + "value": 0 + }, + { + "name": "DA", + "value": 0 + }, + { + "name": "CJIS", + "value": 1.43 + }, + { + "name": "Standard9 v1.1", + "value": 0 + }, + { + "name": "Azure Security Benchmark v3", + "value": 1.19 + }, + { + "name": "Standard2 19", + "value": 0 + }, + { + "name": "SOX 2002 (2017 Trust Services Criteria)", + "value": 4.55 + }, + { + "name": "Standard10", + "value": 0.0 + }, + { + "name": "Standard15", + "value": 0.0 + }, + { + "name": "Standard14 27002_2022", + "value": 1.23 + }, + { + "name": "Standard14 27701_2019", + "value": 1.69 + }, + { + "name": "Standard11 800_53 Rev5", + "value": 0.0 + }, + { + "name": "Standard4 v4", + "value": 0.0 + }, + { + "name": "CMMC v2.0", + "value": 0 + }, + { + "name": "Standard5", + "value": 0 + }, + { + "name": "Standard6 2016_679", + "value": 1.04 + }, + { + "name": "CIS AWS Foundations Benchmark v1.4.0", + "value": 0 + }, + { + "name": "Cyber Essentials v2.2", + "value": 0 + }, + { + "name": "CIS Oracle Database 19 Benchmark v1.0.0", + "value": 0 + } + ] + }, + { + "region": "us-west-1", + "standards_data": [ + { + "name": "Standard7", + "value": 0 + }, + { + "name": "Standard14 27017_2015", + "value": 1.69 + }, + { + "name": "Standard8 v3.2.1", + "value": 1.67 + }, + { + "name": "Standard14 27018_2019", + "value": 4.46 + }, + { + "name": "Standard14 27002_2013", + "value": 1.69 + }, + { + "name": "Standard14 27001_2013", + "value": 1.69 + }, + { + "name": "Standard3 Standard11_800_53_Rev5", + "value": 0.0 + }, + { + "name": "Standard11 800-171 Rev2", + "value": 0 + }, + { + "name": "Standard1 v7", + "value": 3.12 + }, + { + "name": "Standard1 v8", + "value": 0.46 + }, + { + "name": "Standard12", + "value": 0.0 + }, + { + "name": "CIS AWS Foundations Benchmark v1.5.0", + "value": 0 + }, + { + "name": "DA", + "value": 0 + }, + { + "name": "CJIS", + "value": 1.43 + }, + { + "name": "Standard9 v1.1", + "value": 0 + }, + { + "name": "Azure Security Benchmark v3", + "value": 1.19 + }, + { + "name": "Standard2 19", + "value": 0 + }, + { + "name": "SOX 2002 (2017 Trust Services Criteria)", + "value": 4.55 + }, + { + "name": "Standard10", + "value": 0.0 + }, + { + "name": "Standard15", + "value": 0.0 + }, + { + "name": "Standard14 27002_2022", + "value": 1.23 + }, + { + "name": "Standard14 27701_2019", + "value": 1.69 + }, + { + "name": "Standard11 800_53 Rev5", + "value": 0.0 + }, + { + "name": "Standard4 v4", + "value": 0.0 + }, + { + "name": "CMMC v2.0", + "value": 0 + }, + { + "name": "Standard5", + "value": 0 + }, + { + "name": "Standard6 2016_679", + "value": 1.04 + }, + { + "name": "CIS AWS Foundations Benchmark v1.4.0", + "value": 0 + }, + { + "name": "Cyber Essentials v2.2", + "value": 0 + }, + { + "name": "CIS Oracle Database 19 Benchmark v1.0.0", + "value": 0 + } + ] + } + ], + "average_data": [ + { + "name": "DA", + "value": 0.0 + }, + { + "name": "CMMC v2.0", + "value": 0.0 + }, + { + "name": "Standard1 v8", + "value": 0.21 + }, + { + "name": "Standard3 Standard11_800_53_Rev5", + "value": 0.06 + }, + { + "name": "Standard11 800_53 Rev5", + "value": 0.05 + }, + { + "name": "Standard7", + "value": 0.0 + }, + { + "name": "Standard2 19", + "value": 0.0 + }, + { + "name": "Standard14 27001_2013", + "value": 0.79 + }, + { + "name": "Standard8 v3.2.1", + "value": 0.78 + }, + { + "name": "Standard14 27017_2015", + "value": 0.79 + }, + { + "name": "Standard15", + "value": 0.02 + }, + { + "name": "BSA v3", + "value": 0.55 + }, + { + "name": "Standard11 800-171 Rev2", + "value": 0.0 + }, + { + "name": "Standard1 v7", + "value": 2.08 + }, + { + "name": "Standard14 27701_2019", + "value": 0.79 + }, + { + "name": "CIS AWS Foundations Benchmark v1.4.0", + "value": 0.0 + }, + { + "name": "Standard9 v1.1", + "value": 0.0 + }, + { + "name": "s202", + "value": 2.12 + }, + { + "name": "Standard6 2016_679", + "value": 0.49 + }, + { + "name": "Standard12", + "value": 0.07 + }, + { + "name": "CJIS", + "value": 0.86 + }, + { + "name": "Standard5", + "value": 0.0 + }, + { + "name": "CIS AWS Foundations Benchmark v1.5.0", + "value": 0.0 + }, + { + "name": "Standard14 27002_2013", + "value": 0.79 + }, + { + "name": "Standard10", + "value": 0.67 + }, + { + "name": "Standard14 27018_2019", + "value": 2.08 + }, + { + "name": "Standard14 27002_2022", + "value": 0.57 + }, + { + "name": "Standard4 v4", + "value": 0.06 + }, + { + "name": "CE v2.2", + "value": 0.0 + } + ], + "last_scan_date": "2023-10-21T11:11:00", + "activated_regions": "", + "tenant_name": "AWS-TEST", + "account_id": "AWS-1234567890123" + }, + "azure": { + "average_data": [ + { + "name": "Standard14 27701_2019", + "value": 0.0 + }, + { + "name": "Standard14 27002_2022", + "value": 0.0 + }, + { + "name": "Standard14 27002_2013", + "value": 0.0 + }, + { + "name": "Standard11 800_53 Rev5", + "value": 0.0 + }, + { + "name": "CFB v1.4.0", + "value": 0.0 + }, + { + "name": "CMMC v2.0", + "value": 0.0 + }, + { + "name": "CJIS", + "value": 0.0 + }, + { + "name": "Standard11 800-171 Rev2", + "value": 0.0 + }, + { + "name": "Standard15", + "value": 0.0 + }, + { + "name": "BSA v3", + "value": 0.0 + }, + { + "name": "CE v2.2", + "value": 0.0 + }, + { + "name": "Standard14 27001_2013", + "value": 0.0 + }, + { + "name": "DA", + "value": 0.0 + }, + { + "name": "Standard2 19", + "value": 0.0 + }, + { + "name": "Standard14 27017_2015", + "value": 0.0 + }, + { + "name": "Standard1 v7", + "value": 0.0 + }, + { + "name": "Standard10", + "value": 0.0 + }, + { + "name": "Standard4 v4", + "value": 0.0 + }, + { + "name": "Standard3 Standard11_800_53_Rev5", + "value": 0.0 + }, + { + "name": "CFB v1.5.0", + "value": 0.0 + }, + { + "name": "Standard1 v8", + "value": 0.0 + }, + { + "name": "Standard6 2016_679", + "value": 0.0 + }, + { + "name": "Standard14 27018_2019", + "value": 0.0 + }, + { + "name": "Standard9 v1.1", + "value": 0.0 + }, + { + "name": "s202", + "value": 0.0 + }, + { + "name": "Standard7", + "value": 0.0 + }, + { + "name": "Standard12", + "value": 0.0 + }, + { + "name": "Standard5", + "value": 0.0 + } + ], + "last_scan_date": "2023-10-21T11:11:00", + "activated_regions": "", + "tenant_name": "AZURE-TEST", + "account_id": "AZURE-1234567890123" + }, + "google": { + "regions_data": [ + { + "region": "eu-west-1", + "standards_data": [ + { + "name": "Standard14 27701_2019", + "value": 0 + }, + { + "name": "Standard1 v8", + "value": 0 + }, + { + "name": "Standard11 800_53 Rev5", + "value": 0 + }, + { + "name": "Standard14 27017_2015", + "value": 0 + }, + { + "name": "Standard14 27018_2019", + "value": 0 + }, + { + "name": "Standard9 v1.1", + "value": 0 + }, + { + "name": "Standard14 27001_2013", + "value": 0 + }, + { + "name": "Standard14 27002_2013", + "value": 0 + }, + { + "name": "Azure Security Benchmark v3", + "value": 0 + }, + { + "name": "Standard3 Standard11_800_53_Rev5", + "value": 0 + }, + { + "name": "SOX 2002 (2017 Trust Services Criteria)", + "value": 0 + }, + { + "name": "Standard10", + "value": 0 + }, + { + "name": "Standard15", + "value": 0 + }, + { + "name": "Standard14 27002_2022", + "value": 0 + }, + { + "name": "Standard1 v7", + "value": 0 + }, + { + "name": "Standard11 800-171 Rev2", + "value": 0 + }, + { + "name": "Standard12", + "value": 0 + }, + { + "name": "Standard2 19", + "value": 0 + }, + { + "name": "Cyber Essentials v2.2", + "value": 0 + }, + { + "name": "Standard4 v4", + "value": 0 + }, + { + "name": "CMMC v2.0", + "value": 0 + }, + { + "name": "Standard6 2016_679", + "value": 0 + }, + { + "name": "Standard7", + "value": 0 + }, + { + "name": "Standard5", + "value": 0 + } + ] + }, + { + "region": "us-west-1", + "standards_data": [ + { + "name": "Standard14 27701_2019", + "value": 0 + }, + { + "name": "Standard1 v8", + "value": 0 + }, + { + "name": "Standard11 800_53 Rev5", + "value": 0 + }, + { + "name": "Standard14 27017_2015", + "value": 0 + }, + { + "name": "Standard14 27018_2019", + "value": 0 + }, + { + "name": "Standard9 v1.1", + "value": 0 + }, + { + "name": "Standard14 27001_2013", + "value": 0 + }, + { + "name": "Standard14 27002_2013", + "value": 0 + }, + { + "name": "Azure Security Benchmark v3", + "value": 0 + }, + { + "name": "Standard3 Standard11_800_53_Rev5", + "value": 0 + }, + { + "name": "SOX 2002 (2017 Trust Services Criteria)", + "value": 0 + }, + { + "name": "Standard10", + "value": 0 + }, + { + "name": "Standard15", + "value": 0 + }, + { + "name": "Standard14 27002_2022", + "value": 0 + }, + { + "name": "Standard1 v7", + "value": 0 + }, + { + "name": "Standard11 800-171 Rev2", + "value": 0 + }, + { + "name": "Standard12", + "value": 0 + }, + { + "name": "Standard2 19", + "value": 0 + }, + { + "name": "Cyber Essentials v2.2", + "value": 0 + }, + { + "name": "Standard4 v4", + "value": 0 + }, + { + "name": "CMMC v2.0", + "value": 0 + }, + { + "name": "Standard6 2016_679", + "value": 0 + }, + { + "name": "Standard7", + "value": 0 + }, + { + "name": "Standard5", + "value": 0 + } + ] + } + ], + "average_data": [ + { + "name": "BSA v3", + "value": 0.0 + }, + { + "name": "Standard14 27701_2019", + "value": 0.0 + }, + { + "name": "Standard14 27001_2013", + "value": 0.0 + }, + { + "name": "Standard14 27017_2015", + "value": 0.0 + }, + { + "name": "Standard1 v7", + "value": 0.0 + }, + { + "name": "Standard10", + "value": 0.0 + }, + { + "name": "Standard3 Standard11_800_53_Rev5", + "value": 0.0 + }, + { + "name": "Standard14 27018_2019", + "value": 0.0 + }, + { + "name": "Standard9 v1.1", + "value": 0.0 + }, + { + "name": "Standard1 v8", + "value": 0.0 + }, + { + "name": "Standard14 27002_2022", + "value": 0.0 + }, + { + "name": "Standard14 27002_2013", + "value": 0.0 + }, + { + "name": "Standard11 800_53 Rev5", + "value": 0.0 + }, + { + "name": "s202", + "value": 0.0 + }, + { + "name": "Standard15", + "value": 0.0 + }, + { + "name": "Standard12", + "value": 0.0 + }, + { + "name": "CMMC v2.0", + "value": 0.0 + }, + { + "name": "Standard11 800-171 Rev2", + "value": 0.0 + }, + { + "name": "CE v2.2", + "value": 0.0 + }, + { + "name": "Standard2 19", + "value": 0.0 + }, + { + "name": "Standard4 v4", + "value": 0.0 + }, + { + "name": "Standard6 2016_679", + "value": 0.0 + }, + { + "name": "Standard5", + "value": 0.0 + }, + { + "name": "Standard7", + "value": 0.0 + } + ], + "last_scan_date": "2023-10-21T11:11:00", + "activated_regions": "", + "tenant_name": "GOOGLE-TEST", + "account_id": "109876543210" + }, + "outdated_tenants": [], + "tenant_display_name": "test", + "customer": "EPAM Systems", + "date": "2023-10-01", + "type": "COMPLIANCE_BY_TENANT", + "defining_attribute": 0.3159756097560976, + "id": "" + }, + "ATTACK_BY_TENANT": { + "aws": [ + { + "tactic_id": "TA0006", + "tactic": "Credential Access", + "severity_data": { + "Low": 76, + "High": 30, + "Info": 6 + } + }, + { + "tactic_id": "TA0007", + "tactic": "Discovery", + "severity_data": { + "Low": 62, + "High": 16, + "Info": 3 + } + }, + { + "tactic_id": "TA0009", + "tactic": "Collection", + "severity_data": { + "Low": 91, + "High": 37, + "Info": 6, + "Medium": 1 + } + }, + { + "tactic_id": "TA0010", + "tactic": "Exfiltration", + "severity_data": { + "Low": 24, + "High": 9, + "Info": 2 + } + }, + { + "tactic_id": "TA0040", + "tactic": "Impact", + "severity_data": { + "Low": 116, + "High": 72, + "Info": 10, + "Medium": 2 + } + }, + { + "tactic_id": "TA0001", + "tactic": "Initial Access", + "severity_data": { + "Low": 59, + "High": 29, + "Info": 5 + } + }, + { + "tactic_id": "TA0003", + "tactic": "Persistence", + "severity_data": { + "Low": 16, + "High": 13 + } + }, + { + "tactic_id": "TA0004", + "tactic": "Privilege Escalation", + "severity_data": { + "Low": 14, + "High": 10, + "Info": 1 + } + }, + { + "tactic_id": "TA0008", + "tactic": "Lateral Movement", + "severity_data": { + "Low": 45, + "High": 15, + "Info": 6 + } + }, + { + "tactic_id": "TA0002", + "tactic": "Execution", + "severity_data": { + "Low": 12, + "High": 10, + "Medium": 1 + } + }, + { + "tactic_id": "TA0005", + "tactic": "Defense Evasion", + "severity_data": { + "Low": 12, + "High": 12, + "Info": 3 + } + }, + { + "tactic_id": "TA0042", + "tactic": "Resource Development", + "severity_data": { + "Low": 2, + "High": 2 + } + }, + { + "tactic_id": "TA0043", + "tactic": "Reconnaissance", + "severity_data": { + "High": 2, + "Low": 5, + "Info": 1 + } + }, + { + "tactic_id": "TA0011", + "tactic": "Command and Control", + "severity_data": { + "Low": 2, + "High": 2 + } + } + ], + "azure": [ + { + "tactic_id": "TA0004", + "tactic": "Privilege Escalation", + "severity_data": { + "Low": 9, + "High": 1 + } + }, + { + "tactic_id": "TA0005", + "tactic": "Defense Evasion", + "severity_data": { + "Medium": 1, + "High": 1, + "Info": 1, + "Low": 4 + } + }, + { + "tactic_id": "TA0040", + "tactic": "Impact", + "severity_data": { + "Low": 72, + "High": 28, + "Info": 4 + } + }, + { + "tactic_id": "TA0009", + "tactic": "Collection", + "severity_data": { + "Low": 18, + "High": 1 + } + }, + { + "tactic_id": "TA0007", + "tactic": "Discovery", + "severity_data": { + "Low": 5 + } + }, + { + "tactic_id": "TA0008", + "tactic": "Lateral Movement", + "severity_data": { + "Low": 23, + "High": 2, + "Medium": 1 + } + }, + { + "tactic_id": "TA0001", + "tactic": "Initial Access", + "severity_data": { + "Low": 28, + "High": 2, + "Medium": 1 + } + }, + { + "tactic_id": "TA0006", + "tactic": "Credential Access", + "severity_data": { + "Low": 29, + "Medium": 1, + "High": 1, + "Info": 2 + } + }, + { + "tactic_id": "TA0003", + "tactic": "Persistence", + "severity_data": { + "Low": 4, + "High": 1 + } + }, + { + "tactic_id": "TA0002", + "tactic": "Execution", + "severity_data": { + "Low": 7 + } + } + ], + "google": [], + "outdated_tenants": [], + "tenant_display_name": "test", + "customer": "EPAM Systems", + "date": "2023-10-01", + "type": "ATTACK_BY_TENANT", + "defining_attribute": 1, + "id": "" + }, + "RESOURCES_BY_CLOUD": { + "aws": { + "total_scans": 0, + "failed_scans": 0, + "succeeded_scans": 0, + "resources_violated": 484, + "last_scan_date": "2023-10-21T11:11:00", + "activated_regions": "", + "tenant_name": "AWS-TEST", + "account_id": "AWS-1234567890123", + "resource_types_data": { + "AWS Identity and Access Management": 15, + "AWS Identity and Access Management Access Analyzer": 1, + "Amazon S3": 14, + "Amazon CloudFront": 16, + "AWS Account": 4, + "Amazon Route 53": 6, + "AWS Web Application Firewall": 4, + "Amazon Virtual Private Cloud": 17, + "Amazon Relational Database Service": 68, + "Amazon Elastic Load Balancing": 15, + "AWS CloudFormation": 3, + "Amazon EC2": 50, + "Amazon Elastic Block Store": 10, + "Amazon Simple Queue Service": 4, + "Amazon Elastic Kubernetes Service": 8, + "AWS CloudTrail": 9, + "AWS Key Management Service": 3, + "AWS CodeBuild": 7, + "Amazon EC2 Auto Scaling": 11, + "Amazon OpenSearch Service": 14, + "AWS Lambda": 10, + "Amazon Redshift": 15, + "Amazon SageMaker": 7, + "Amazon Kinesis": 7, + "AWS Certificate Manager": 8, + "Amazon Elastic Container Service": 13, + "Amazon API Gateway": 12, + "Amazon DynamoDB": 3, + "Amazon Elastic File System": 5, + "Amazon ElastiCache": 14, + "AWS Database Migration Service": 6, + "Amazon DynamoDB Accelerator": 3, + "AWS Elastic Beanstalk": 7, + "Amazon EMR": 8, + "AWS Secrets Manager": 3, + "Amazon Simple Notification Service": 4, + "Amazon Elastic Container Registry": 4, + "AWS Transit Gateway": 4, + "Amazon AppFlow": 2, + "AWS Glue": 11, + "Amazon DocumentDB": 1, + "Amazon WorkSpaces Family": 11, + "AWS Backup": 2, + "Amazon EventBridge": 1, + "Amazon S3 Glacier": 2, + "AWS Config": 1, + "Amazon FSx": 8, + "Amazon MQ": 6, + "Amazon Managed Streaming for Apache Kafka": 4, + "Amazon Managed Workflows for Apache Airflow": 8, + "AWS Directory": 1, + "Amazon Data Lifecycle Manager": 1, + "Amazon Lightsail": 1, + "Amazon CloudWatch": 3, + "Amazon QLDB": 3, + "AWS AppSync": 4, + "AWS CodeDeploy": 3, + "AWS CodePipeline": 1, + "AWS Step Functions": 1 + }, + "severity_data": { + "Low": 183, + "Info": 50, + "High": 187, + "Medium": 67 + } + }, + "azure": { + "total_scans": 0, + "failed_scans": 0, + "succeeded_scans": 0, + "resources_violated": 258, + "last_scan_date": "2023-10-21T11:11:00", + "activated_regions": "", + "tenant_name": "AZURE-TEST", + "account_id": "AZURE-1234567890123", + "resource_types_data": { + "Azure RBAC": 2, + "Microsoft Defender for Cloud": 36, + "Azure Storage Accounts": 36, + "Azure SQL Database": 32, + "Azure Database for PostgreSQL": 40, + "Azure Database for MySQL": 28, + "Key Vault": 14, + "Azure Subscription": 24, + "Network security groups": 36, + "Azure Disk Storage": 6, + "Azure Kubernetes Service": 24, + "App Service": 56, + "Virtual Machines": 34, + "Virtual Network": 10, + "API Management": 6, + "Azure Cosmos DB": 8, + "Cognitive Services": 6, + "Azure Container Registry": 12, + "Azure Database for MariaDB": 6, + "App Configuration": 2, + "Azure Cache for Redis": 8, + "Event Grid": 4, + "Azure Machine Learning": 6, + "Azure SignalR Service": 2, + "Azure Spring Apps": 2, + "Application Gateway": 8, + "Azure Front Door": 4, + "Azure Service Fabric": 4, + "Azure SQL Managed Instance": 6, + "Automation": 2, + "Azure Data Lake Storage": 2, + "Azure Stream Analytics": 2, + "Batch": 4, + "Data Lake Analytics": 2, + "Azure IoT Hub": 4, + "Azure Logic Apps": 2, + "Azure Cognitive Search": 2, + "Service Bus": 2, + "Azure Virtual Machine Scale Sets": 14, + "Azure Data Explorer": 6, + "Azure Data Factory": 4, + "Azure Databricks": 2, + "Azure Synapse Analytics": 4, + "Azure Monitor": 2 + }, + "severity_data": { + "Low": 316, + "Medium": 26, + "Info": 68, + "High": 106 + } + }, + "google": { + "total_scans": 0, + "failed_scans": 0, + "succeeded_scans": 0, + "resources_violated": 276, + "last_scan_date": "2023-10-21T11:11:00", + "activated_regions": "", + "tenant_name": "GOOGLE-TEST", + "account_id": "109876543210", + "resource_types_data": { + "Cloud IAM": 12, + "Cloud KMS": 5, + "Cloud APIs": 4, + "Cloud Logging": 10, + "Cloud Storage": 14, + "Virtual Private Cloud": 39, + "Cloud DNS": 4, + "Compute Engine": 38, + "Cloud SQL": 46, + "Google Kubernetes Engine": 39, + "Secret Manager": 2, + "Cloud Load Balancing": 12, + "BigQuery": 4, + "Cloud Functions": 10, + "App Engine": 3, + "Cloud Bigtable": 3, + "Dataproc": 4, + "Cloud Run": 6, + "Cloud Armor": 4, + "Pub/Sub": 3, + "Cloud Spanner": 5, + "Cloud Memorystore": 2, + "Dataflow": 1, + "Vertex AI Workbench": 1, + "Cloud Data Fusion": 3, + "Access Transparency": 1, + "Access Approval": 1, + "Cloud Asset Inventory": 1 + }, + "severity_data": { + "High": 99, + "Low": 121, + "Medium": 10, + "Info": 47 + } + }, + "outdated_tenants": [], + "tenant_display_name": "test", + "customer": "EPAM Systems", + "date": "2023-10-01", + "type": "RESOURCES_BY_CLOUD", + "defining_attribute": 276, + "id": "" + }, + "COMPLIANCE_BY_CLOUD": { + "aws": { + "regions_data": [ + { + "region": "eu-west-1", + "standards_data": [ + { + "name": "Standard7", + "value": 0 + }, + { + "name": "Standard14 27017_2015", + "value": 1.69 + }, + { + "name": "Standard8 v3.2.1", + "value": 1.67 + }, + { + "name": "Standard14 27018_2019", + "value": 4.46 + }, + { + "name": "Standard14 27002_2013", + "value": 1.69 + }, + { + "name": "Standard14 27001_2013", + "value": 1.69 + }, + { + "name": "Standard3 Standard11_800_53_Rev5", + "value": 0.0 + }, + { + "name": "Standard11 800-171 Rev2", + "value": 0 + }, + { + "name": "Standard1 v7", + "value": 3.12 + }, + { + "name": "Standard1 v8", + "value": 0.46 + }, + { + "name": "Standard12", + "value": 0.0 + }, + { + "name": "CIS AWS Foundations Benchmark v1.5.0", + "value": 0 + }, + { + "name": "DA", + "value": 0 + }, + { + "name": "CJIS", + "value": 1.43 + }, + { + "name": "Standard9 v1.1", + "value": 0 + }, + { + "name": "Azure Security Benchmark v3", + "value": 1.19 + }, + { + "name": "Standard2 19", + "value": 0 + }, + { + "name": "SOX 2002 (2017 Trust Services Criteria)", + "value": 4.55 + }, + { + "name": "Standard10", + "value": 0.0 + }, + { + "name": "Standard15", + "value": 0.0 + }, + { + "name": "Standard14 27002_2022", + "value": 1.23 + }, + { + "name": "Standard14 27701_2019", + "value": 1.69 + }, + { + "name": "Standard11 800_53 Rev5", + "value": 0.0 + }, + { + "name": "Standard4 v4", + "value": 0.0 + }, + { + "name": "CMMC v2.0", + "value": 0 + }, + { + "name": "Standard5", + "value": 0 + }, + { + "name": "Standard6 2016_679", + "value": 1.04 + }, + { + "name": "CIS AWS Foundations Benchmark v1.4.0", + "value": 0 + }, + { + "name": "Cyber Essentials v2.2", + "value": 0 + }, + { + "name": "CIS Oracle Database 19 Benchmark v1.0.0", + "value": 0 + } + ] + }, + { + "region": "us-west-1", + "standards_data": [ + { + "name": "Standard7", + "value": 0 + }, + { + "name": "Standard14 27017_2015", + "value": 1.69 + }, + { + "name": "Standard8 v3.2.1", + "value": 1.67 + }, + { + "name": "Standard14 27018_2019", + "value": 4.46 + }, + { + "name": "Standard14 27002_2013", + "value": 1.69 + }, + { + "name": "Standard14 27001_2013", + "value": 1.69 + }, + { + "name": "Standard3 Standard11_800_53_Rev5", + "value": 0.0 + }, + { + "name": "Standard11 800-171 Rev2", + "value": 0 + }, + { + "name": "Standard1 v7", + "value": 3.12 + }, + { + "name": "Standard1 v8", + "value": 0.46 + }, + { + "name": "Standard12", + "value": 0.0 + }, + { + "name": "CIS AWS Foundations Benchmark v1.5.0", + "value": 0 + }, + { + "name": "DA", + "value": 0 + }, + { + "name": "CJIS", + "value": 1.43 + }, + { + "name": "Standard9 v1.1", + "value": 0 + }, + { + "name": "Azure Security Benchmark v3", + "value": 1.19 + }, + { + "name": "Standard2 19", + "value": 0 + }, + { + "name": "SOX 2002 (2017 Trust Services Criteria)", + "value": 4.55 + }, + { + "name": "Standard10", + "value": 0.0 + }, + { + "name": "Standard15", + "value": 0.0 + }, + { + "name": "Standard14 27002_2022", + "value": 1.23 + }, + { + "name": "Standard14 27701_2019", + "value": 1.69 + }, + { + "name": "Standard11 800_53 Rev5", + "value": 0.0 + }, + { + "name": "Standard4 v4", + "value": 0.0 + }, + { + "name": "CMMC v2.0", + "value": 0 + }, + { + "name": "Standard5", + "value": 0 + }, + { + "name": "Standard6 2016_679", + "value": 1.04 + }, + { + "name": "CIS AWS Foundations Benchmark v1.4.0", + "value": 0 + }, + { + "name": "Cyber Essentials v2.2", + "value": 0 + }, + { + "name": "CIS Oracle Database 19 Benchmark v1.0.0", + "value": 0 + } + ] + } + ], + "average_data": [ + { + "name": "Standard11 800-171 Rev2", + "value": 0.0 + }, + { + "name": "Standard14 27017_2015", + "value": 0.79 + }, + { + "name": "Standard14 27018_2019", + "value": 2.08 + }, + { + "name": "Standard9 v1.1", + "value": 0.0 + }, + { + "name": "Standard14 27701_2019", + "value": 0.79 + }, + { + "name": "Standard11 800_53 Rev5", + "value": 0.05 + }, + { + "name": "CIS AWS Foundations Benchmark v1.4.0", + "value": 0.0 + }, + { + "name": "Standard4 v4", + "value": 0.06 + }, + { + "name": "Standard5", + "value": 0.0 + }, + { + "name": "Standard6 2016_679", + "value": 0.49 + }, + { + "name": "CIS AWS Foundations Benchmark v1.5.0", + "value": 0.0 + }, + { + "name": "DA", + "value": 0.0 + }, + { + "name": "s202", + "value": 2.12 + }, + { + "name": "Standard1 v7", + "value": 2.08 + }, + { + "name": "Standard15", + "value": 0.02 + }, + { + "name": "BSA v3", + "value": 0.55 + }, + { + "name": "Standard8 v3.2.1", + "value": 0.78 + }, + { + "name": "CJIS", + "value": 0.86 + }, + { + "name": "Standard14 27001_2013", + "value": 0.79 + }, + { + "name": "Standard2 19", + "value": 0.0 + }, + { + "name": "Standard10", + "value": 0.67 + }, + { + "name": "Standard14 27002_2013", + "value": 0.79 + }, + { + "name": "Standard3 Standard11_800_53_Rev5", + "value": 0.06 + }, + { + "name": "CMMC v2.0", + "value": 0.0 + }, + { + "name": "Standard7", + "value": 0.0 + }, + { + "name": "Standard14 27002_2022", + "value": 0.57 + }, + { + "name": "Standard12", + "value": 0.07 + }, + { + "name": "Standard1 v8", + "value": 0.21 + }, + { + "name": "CE v2.2", + "value": 0.0 + } + ], + "last_scan_date": "2023-10-21T11:11:00", + "activated_regions": "", + "tenant_name": "AWS-TEST", + "account_id": "AWS-1234567890123" + }, + "azure": { + "average_data": [ + { + "name": "CFB v1.5.0", + "value": 0.0 + }, + { + "name": "s202", + "value": 0.0 + }, + { + "name": "Standard2 19", + "value": 0.0 + }, + { + "name": "Standard11 800_53 Rev5", + "value": 0.0 + }, + { + "name": "Standard1 v7", + "value": 0.0 + }, + { + "name": "CMMC v2.0", + "value": 0.0 + }, + { + "name": "CFB v1.4.0", + "value": 0.0 + }, + { + "name": "Standard10", + "value": 0.0 + }, + { + "name": "Standard15", + "value": 0.0 + }, + { + "name": "Standard14 27017_2015", + "value": 0.0 + }, + { + "name": "Standard9 v1.1", + "value": 0.0 + }, + { + "name": "Standard14 27001_2013", + "value": 0.0 + }, + { + "name": "Standard14 27018_2019", + "value": 0.0 + }, + { + "name": "Standard6 2016_679", + "value": 0.0 + }, + { + "name": "Standard1 v8", + "value": 0.0 + }, + { + "name": "Standard14 27701_2019", + "value": 0.0 + }, + { + "name": "Standard4 v4", + "value": 0.0 + }, + { + "name": "CE v2.2", + "value": 0.0 + }, + { + "name": "Standard14 27002_2013", + "value": 0.0 + }, + { + "name": "Standard14 27002_2022", + "value": 0.0 + }, + { + "name": "CJIS", + "value": 0.0 + }, + { + "name": "Standard11 800-171 Rev2", + "value": 0.0 + }, + { + "name": "BSA v3", + "value": 0.0 + }, + { + "name": "Standard3 Standard11_800_53_Rev5", + "value": 0.0 + }, + { + "name": "DA", + "value": 0.0 + }, + { + "name": "Standard7", + "value": 0.0 + }, + { + "name": "Standard12", + "value": 0.0 + }, + { + "name": "Standard5", + "value": 0.0 + } + ], + "last_scan_date": "2023-10-21T11:11:00", + "activated_regions": "", + "tenant_name": "AZURE-TEST", + "account_id": "AZURE-1234567890123" + }, + "google": { + "regions_data": [ + { + "region": "eu-west-1", + "standards_data": [ + { + "name": "Standard14 27701_2019", + "value": 0 + }, + { + "name": "Standard1 v8", + "value": 0 + }, + { + "name": "Standard11 800_53 Rev5", + "value": 0 + }, + { + "name": "Standard14 27017_2015", + "value": 0 + }, + { + "name": "Standard14 27018_2019", + "value": 0 + }, + { + "name": "Standard9 v1.1", + "value": 0 + }, + { + "name": "Standard14 27001_2013", + "value": 0 + }, + { + "name": "Standard14 27002_2013", + "value": 0 + }, + { + "name": "Azure Security Benchmark v3", + "value": 0 + }, + { + "name": "Standard3 Standard11_800_53_Rev5", + "value": 0 + }, + { + "name": "SOX 2002 (2017 Trust Services Criteria)", + "value": 0 + }, + { + "name": "Standard10", + "value": 0 + }, + { + "name": "Standard15", + "value": 0 + }, + { + "name": "Standard14 27002_2022", + "value": 0 + }, + { + "name": "Standard1 v7", + "value": 0 + }, + { + "name": "Standard11 800-171 Rev2", + "value": 0 + }, + { + "name": "Standard12", + "value": 0 + }, + { + "name": "Standard2 19", + "value": 0 + }, + { + "name": "Cyber Essentials v2.2", + "value": 0 + }, + { + "name": "Standard4 v4", + "value": 0 + }, + { + "name": "CMMC v2.0", + "value": 0 + }, + { + "name": "Standard6 2016_679", + "value": 0 + }, + { + "name": "Standard7", + "value": 0 + }, + { + "name": "Standard5", + "value": 0 + } + ] + }, + { + "region": "us-west-1", + "standards_data": [ + { + "name": "Standard14 27701_2019", + "value": 0 + }, + { + "name": "Standard1 v8", + "value": 0 + }, + { + "name": "Standard11 800_53 Rev5", + "value": 0 + }, + { + "name": "Standard14 27017_2015", + "value": 0 + }, + { + "name": "Standard14 27018_2019", + "value": 0 + }, + { + "name": "Standard9 v1.1", + "value": 0 + }, + { + "name": "Standard14 27001_2013", + "value": 0 + }, + { + "name": "Standard14 27002_2013", + "value": 0 + }, + { + "name": "Azure Security Benchmark v3", + "value": 0 + }, + { + "name": "Standard3 Standard11_800_53_Rev5", + "value": 0 + }, + { + "name": "SOX 2002 (2017 Trust Services Criteria)", + "value": 0 + }, + { + "name": "Standard10", + "value": 0 + }, + { + "name": "Standard15", + "value": 0 + }, + { + "name": "Standard14 27002_2022", + "value": 0 + }, + { + "name": "Standard1 v7", + "value": 0 + }, + { + "name": "Standard11 800-171 Rev2", + "value": 0 + }, + { + "name": "Standard12", + "value": 0 + }, + { + "name": "Standard2 19", + "value": 0 + }, + { + "name": "Cyber Essentials v2.2", + "value": 0 + }, + { + "name": "Standard4 v4", + "value": 0 + }, + { + "name": "CMMC v2.0", + "value": 0 + }, + { + "name": "Standard6 2016_679", + "value": 0 + }, + { + "name": "Standard7", + "value": 0 + }, + { + "name": "Standard5", + "value": 0 + } + ] + } + ], + "average_data": [ + { + "name": "Standard14 27002_2022", + "value": 0.0 + }, + { + "name": "s202", + "value": 0.0 + }, + { + "name": "Standard11 800_53 Rev5", + "value": 0.0 + }, + { + "name": "Standard14 27018_2019", + "value": 0.0 + }, + { + "name": "Standard14 27001_2013", + "value": 0.0 + }, + { + "name": "Standard9 v1.1", + "value": 0.0 + }, + { + "name": "BSA v3", + "value": 0.0 + }, + { + "name": "Standard14 27002_2013", + "value": 0.0 + }, + { + "name": "Standard3 Standard11_800_53_Rev5", + "value": 0.0 + }, + { + "name": "Standard10", + "value": 0.0 + }, + { + "name": "Standard1 v8", + "value": 0.0 + }, + { + "name": "Standard14 27017_2015", + "value": 0.0 + }, + { + "name": "Standard1 v7", + "value": 0.0 + }, + { + "name": "Standard14 27701_2019", + "value": 0.0 + }, + { + "name": "Standard15", + "value": 0.0 + }, + { + "name": "Standard11 800-171 Rev2", + "value": 0.0 + }, + { + "name": "CMMC v2.0", + "value": 0.0 + }, + { + "name": "Standard2 19", + "value": 0.0 + }, + { + "name": "Standard6 2016_679", + "value": 0.0 + }, + { + "name": "Standard12", + "value": 0.0 + }, + { + "name": "Standard4 v4", + "value": 0.0 + }, + { + "name": "CE v2.2", + "value": 0.0 + }, + { + "name": "Standard5", + "value": 0.0 + }, + { + "name": "Standard7", + "value": 0.0 + } + ], + "last_scan_date": "2023-10-21T11:11:00", + "activated_regions": "", + "tenant_name": "GOOGLE-TEST", + "account_id": "109876543210" + }, + "outdated_tenants": [], + "tenant_display_name": "test", + "customer": "EPAM Systems", + "date": "2023-10-01", + "type": "COMPLIANCE_BY_CLOUD", + "defining_attribute": 0.0, + "id": "" + }, + "ATTACK_BY_CLOUD": { + "aws": [ + { + "tactic_id": "TA0006", + "tactic": "Credential Access", + "severity_data": { + "Low": 76, + "High": 30, + "Info": 6 + } + }, + { + "tactic_id": "TA0007", + "tactic": "Discovery", + "severity_data": { + "Low": 62, + "High": 16, + "Info": 3 + } + }, + { + "tactic_id": "TA0009", + "tactic": "Collection", + "severity_data": { + "Low": 91, + "High": 37, + "Info": 6, + "Medium": 1 + } + }, + { + "tactic_id": "TA0010", + "tactic": "Exfiltration", + "severity_data": { + "Low": 24, + "High": 9, + "Info": 2 + } + }, + { + "tactic_id": "TA0040", + "tactic": "Impact", + "severity_data": { + "Low": 116, + "High": 72, + "Info": 10, + "Medium": 2 + } + }, + { + "tactic_id": "TA0001", + "tactic": "Initial Access", + "severity_data": { + "Low": 59, + "High": 29, + "Info": 5 + } + }, + { + "tactic_id": "TA0003", + "tactic": "Persistence", + "severity_data": { + "Low": 16, + "High": 13 + } + }, + { + "tactic_id": "TA0004", + "tactic": "Privilege Escalation", + "severity_data": { + "Low": 14, + "High": 10, + "Info": 1 + } + }, + { + "tactic_id": "TA0008", + "tactic": "Lateral Movement", + "severity_data": { + "Low": 45, + "High": 15, + "Info": 6 + } + }, + { + "tactic_id": "TA0002", + "tactic": "Execution", + "severity_data": { + "Low": 12, + "High": 10, + "Medium": 1 + } + }, + { + "tactic_id": "TA0005", + "tactic": "Defense Evasion", + "severity_data": { + "Low": 12, + "High": 12, + "Info": 3 + } + }, + { + "tactic_id": "TA0042", + "tactic": "Resource Development", + "severity_data": { + "Low": 2, + "High": 2 + } + }, + { + "tactic_id": "TA0043", + "tactic": "Reconnaissance", + "severity_data": { + "High": 2, + "Low": 5, + "Info": 1 + } + }, + { + "tactic_id": "TA0011", + "tactic": "Command and Control", + "severity_data": { + "Low": 2, + "High": 2 + } + } + ], + "azure": [ + { + "tactic_id": "TA0004", + "tactic": "Privilege Escalation", + "severity_data": { + "Low": 9, + "High": 1 + } + }, + { + "tactic_id": "TA0005", + "tactic": "Defense Evasion", + "severity_data": { + "Medium": 1, + "High": 1, + "Info": 1, + "Low": 4 + } + }, + { + "tactic_id": "TA0040", + "tactic": "Impact", + "severity_data": { + "Low": 72, + "High": 28, + "Info": 4 + } + }, + { + "tactic_id": "TA0009", + "tactic": "Collection", + "severity_data": { + "Low": 18, + "High": 1 + } + }, + { + "tactic_id": "TA0007", + "tactic": "Discovery", + "severity_data": { + "Low": 5 + } + }, + { + "tactic_id": "TA0008", + "tactic": "Lateral Movement", + "severity_data": { + "Low": 23, + "High": 2, + "Medium": 1 + } + }, + { + "tactic_id": "TA0001", + "tactic": "Initial Access", + "severity_data": { + "Low": 28, + "High": 2, + "Medium": 1 + } + }, + { + "tactic_id": "TA0006", + "tactic": "Credential Access", + "severity_data": { + "Low": 29, + "Medium": 1, + "High": 1, + "Info": 2 + } + }, + { + "tactic_id": "TA0003", + "tactic": "Persistence", + "severity_data": { + "Low": 4, + "High": 1 + } + }, + { + "tactic_id": "TA0002", + "tactic": "Execution", + "severity_data": { + "Low": 7 + } + } + ], + "google": [], + "outdated_tenants": [], + "tenant_display_name": "test", + "customer": "EPAM Systems", + "date": "2023-10-01", + "type": "ATTACK_BY_CLOUD", + "defining_attribute": 1, + "id": "" + } +} \ No newline at end of file diff --git a/tests/tests_metrics/expected_metrics/google_tenant_group_finops.json b/tests/tests_metrics/expected_metrics/google_tenant_group_finops.json new file mode 100644 index 000000000..c0b2db3be --- /dev/null +++ b/tests/tests_metrics/expected_metrics/google_tenant_group_finops.json @@ -0,0 +1 @@ +[{"service_section": "aypqzp8esncdsun69k98m68hjcq", "rules_data": [{"rule": "Buckets have no set life cycle value\n", "service": "Cloud Storage", "category": "bbu8whyk9omoetc9lqqks8l74el", "severity": "Info", "resource_type": "Cloud Storage", "regions_data": {"multiregion": {"total_violated_resources": {"value": 1}}}}]}] \ No newline at end of file diff --git a/tests/tests_metrics/expected_metrics/google_tenant_resources.json b/tests/tests_metrics/expected_metrics/google_tenant_resources.json new file mode 100644 index 000000000..2e0f5a066 --- /dev/null +++ b/tests/tests_metrics/expected_metrics/google_tenant_resources.json @@ -0,0 +1 @@ +[{"policy": "ecc-gcp-001", "resource_type": "Cloud IAM", "description": "Personal accounts are used instead of corporate login credentials\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"member": "dac559d6-2824-4dc6-a06a-05e267aed200"}]}}}, {"policy": "ecc-gcp-003", "resource_type": "Cloud IAM", "description": "There are not only GCP-managed service account keys for service accounts\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"keys[].name": "668c553e-c891-4eab-b4b4-ffc1e7faa05b"}]}}}, {"policy": "ecc-gcp-004", "resource_type": "Cloud IAM", "description": "Service Accounts has Admin privileges\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"member": "c049e628-834c-4ddb-aaa9-3c705ce33834", "roles": "926cb034-0b24-458c-aa58-bd98890e6006"}]}}}, {"policy": "ecc-gcp-005", "resource_type": "Cloud IAM", "description": "IAM users are assigned the Service Account User or Service Account Token Creator roles at the project level\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"role": "afdc5b3c-451d-4cf2-b89a-25432265fb68", "members": "c57e282c-7365-4bed-b06f-8e0a12f5ab43"}]}}}, {"policy": "ecc-gcp-006", "resource_type": "Cloud IAM", "description": "User-managed/external keys for service accounts are not rotated every 90 days\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"name": "b3191836-e796-402f-ad58-6b478736b844"}]}}}, {"policy": "ecc-gcp-007", "resource_type": "Cloud IAM", "description": "Separation of duties is not enforced while assigning service account related roles to users\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"member": "08e3460c-6a3d-4e34-b254-c3cfcc7a32c2"}]}}}, {"policy": "ecc-gcp-008", "resource_type": "Cloud KMS", "description": "KMS encryption keys are not rotated within a period of 90 days\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"name": "60f447eb-2248-4ce9-97a8-f512c1ef97a0"}]}}}, {"policy": "ecc-gcp-009", "resource_type": "Cloud IAM", "description": "Separation of duties is not enforced while assigning KMS related roles to users\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"member": "69e981fe-444f-46dc-a7fe-08e65ecb62b2"}]}}}, {"policy": "ecc-gcp-010", "resource_type": "Cloud APIs", "description": "API keys are created for a project\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"displayName": "be8eef92-669b-4685-a171-17689102d540", "name": "9dd07614-3713-4d91-848e-a2ca2b51bd45"}]}}}, {"policy": "ecc-gcp-011", "resource_type": "Cloud APIs", "description": "API keys are not restricted to use by only specified Hosts and Apps\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"displayName": "e7f4faab-ced0-4f7e-b42d-be60729e13f5", "name": "c0406e25-47cd-4898-8786-495b85f72470"}]}}}, {"policy": "ecc-gcp-012", "resource_type": "Cloud APIs", "description": "API Keys are not restricted to only APIs that application needs access\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"displayName": "710bcf71-59db-4604-b573-c36dabfb02ee", "name": "a415b6b6-7461-435d-b59e-4080638037aa"}]}}}, {"policy": "ecc-gcp-013", "resource_type": "Cloud APIs", "description": "API keys are not rotated every 90 days\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"displayName": "b331ebd7-42b8-4c12-ae51-259acf3c10fa", "name": "d1d6f259-e2ed-40f0-954b-9b1de1d77437"}]}}}, {"policy": "ecc-gcp-014", "resource_type": "Cloud Logging", "description": "Cloud Audit Logging is not configured properly across all services and all users from a project\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"projectId": "b32772c5-7557-4947-9c67-10dc5d90cc4a"}]}}}, {"policy": "ecc-gcp-015", "resource_type": "Cloud Logging", "description": "The project does not have a sink configured for all log entries.\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"projectId": "76ba5791-a2cb-4ec9-99e7-a14d89d52de5"}]}}}, {"policy": "ecc-gcp-016", "resource_type": "Cloud Storage", "description": "Object versioning is disabled on log-buckets\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "42e0e6eb-57f6-498a-8523-485818eed612"}]}}}, {"policy": "ecc-gcp-017", "resource_type": "Cloud Logging", "description": "The project does not have Log metric filter and alerts for Project Ownership assignments/changes\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"projectId": "77619523-36ad-4c48-8db1-2f4e960b324b"}]}}}, {"policy": "ecc-gcp-018", "resource_type": "Cloud Logging", "description": "The project does not have Log metric filter and alerts for Audit Configuration Changes\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"projectId": "623a793e-ce46-4ea4-8d57-b59e487c0af8"}]}}}, {"policy": "ecc-gcp-019", "resource_type": "Cloud Logging", "description": "The project does not have Log metric filter and alerts for Custom Role changes\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"projectId": "b428c271-7f82-4d27-9391-5992771aa5e1"}]}}}, {"policy": "ecc-gcp-020", "resource_type": "Cloud Logging", "description": "The project does not have Log metric filter and alerts for VPC Network Firewall rule changes\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"projectId": "66248d7c-a72d-47f3-b2aa-5f72b575bd31"}]}}}, {"policy": "ecc-gcp-021", "resource_type": "Cloud Logging", "description": "The project does not have Log metric filter and alerts for VPC network route changes\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"projectId": "5987b639-c080-48f4-9a7d-917390cf20a5"}]}}}, {"policy": "ecc-gcp-022", "resource_type": "Cloud Logging", "description": "The project does not have Log metric filter and alerts for VPC network changes\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"projectId": "63f88a37-ed78-4b0b-b882-6dac5815c9ba"}]}}}, {"policy": "ecc-gcp-023", "resource_type": "Cloud Logging", "description": "The project does not have Log metric filter and alerts for Cloud Storage IAM permission changes\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"projectId": "50a2bf8c-719c-445a-803b-98a1d3fb7a89"}]}}}, {"policy": "ecc-gcp-024", "resource_type": "Cloud Logging", "description": "The project does not have Log metric filter and alerts for SQL instance configuration changes\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"projectId": "f874ff8a-ec69-4e42-858c-d03c1ae6a753"}]}}}, {"policy": "ecc-gcp-025", "resource_type": "Virtual Private Cloud", "description": "Default network exists in the project\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "2eb7dea2-a1a2-434d-a2a2-067d811006b8"}]}}}, {"policy": "ecc-gcp-027", "resource_type": "Cloud DNS", "description": "DNSSEC is not enabled for Cloud DNS\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"id": "4398a0a1-091a-4027-948c-e435f97884ba", "name": "5c3e4306-06d0-4108-8638-b958030ff712"}]}}}, {"policy": "ecc-gcp-028", "resource_type": "Cloud DNS", "description": "RSASHA1 is used for key-signing key in Cloud DNS DNSSEC\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"id": "2a966bcf-ef4d-4fe1-b584-6a2a62fd2d3d", "name": "032ff55a-1e73-4b01-84e1-8806e6932b21"}]}}}, {"policy": "ecc-gcp-029", "resource_type": "Cloud DNS", "description": "RSASHA1 is used for zone-signing key in Cloud DNS DNSSEC\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"id": "d89f24d0-8e91-41ea-a5e9-a93f3dc1510f", "name": "d2c1d341-d9a7-45a3-832e-dcc1f55eb6e6"}]}}}, {"policy": "ecc-gcp-030", "resource_type": "Virtual Private Cloud", "description": "SSH access is not restricted from the internet\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "01f26bee-208c-498d-b187-7cd629802ece"}]}}}, {"policy": "ecc-gcp-031", "resource_type": "Virtual Private Cloud", "description": "RDP access is not restricted from the internet\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "e56e7e69-aaae-4f33-9803-05d759ede4ad"}]}}}, {"policy": "ecc-gcp-032", "resource_type": "Virtual Private Cloud", "description": "Private Google Access is not enabled for all subnetworks in a VPC Network\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "2d14b4f5-0b3f-4f9c-be77-b8922cb7fb4c"}]}}}, {"policy": "ecc-gcp-033", "resource_type": "Virtual Private Cloud", "description": "VPC Flow logs are not enabled for every subnet in a VPC Network\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "f7840f49-8bf0-45c9-800e-82a34fe12169"}]}}}, {"policy": "ecc-gcp-034", "resource_type": "Compute Engine", "description": "Instances are configured to use the default service account with full access to all Cloud APIs\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "87433d12-be8f-4679-bd0b-3c40d17d712d"}]}}}, {"policy": "ecc-gcp-035", "resource_type": "Compute Engine", "description": "\"Block Project-wide SSH keys\" is not enabled for VM instances\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "41364b39-9a47-4d69-8d2a-d096a63de00b"}]}}}, {"policy": "ecc-gcp-036", "resource_type": "Compute Engine", "description": "OS Login is not enabled for a Project\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "5f53cb30-d360-4b11-bb58-6f3cc60a0ec9"}]}}}, {"policy": "ecc-gcp-037", "resource_type": "Compute Engine", "description": "'Enable connecting to serial ports' is enabled for a VM Instance\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "69056793-ca4f-4234-90ca-f7def9516805"}]}}}, {"policy": "ecc-gcp-038", "resource_type": "Compute Engine", "description": "IP forwarding is enabled on Instances\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "f0395a41-8d05-4100-861f-ef31b5edfc99"}]}}}, {"policy": "ecc-gcp-039", "resource_type": "Compute Engine", "description": "VM disks for critical VMs are not encrypted with Customer-Supplied Encryption Keys (CSEK)\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "4b9c7aa8-95fb-42e2-a3c3-8bd66c0365a4"}]}}}, {"policy": "ecc-gcp-040", "resource_type": "Cloud Storage", "description": "Cloud Storage bucket is anonymously or publicly accessible\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "9848f5b9-b87b-4e0b-9089-1ddc182a04a8"}]}}}, {"policy": "ecc-gcp-042", "resource_type": "Cloud Storage", "description": "Logging is disabled for Cloud storage buckets\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "652f72a0-b33a-449d-84dd-f71cef073858"}]}}}, {"policy": "ecc-gcp-043", "resource_type": "Cloud SQL", "description": "Cloud SQL database instance does not require all incoming connections to use SSL\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "afc4cd69-f84a-48cf-b90e-6da6137d0147"}]}}}, {"policy": "ecc-gcp-044", "resource_type": "Cloud SQL", "description": "Cloud SQL database Instances are open to the world\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "afdfe814-7c79-4021-a13a-c96747040dd4"}]}}}, {"policy": "ecc-gcp-046", "resource_type": "Cloud SQL", "description": "MySQL Database Instance allows root login from any Host\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"instance": "f152fa5f-552c-49e4-8787-d8f59ed4cd6d"}]}}}, {"policy": "ecc-gcp-047", "resource_type": "Google Kubernetes Engine", "description": "Stackdriver Kubernetes Logging is not Enabled\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "3cee14f9-de0e-4906-8ea1-03b5bda112d0"}]}}}, {"policy": "ecc-gcp-048", "resource_type": "Google Kubernetes Engine", "description": "Stackdriver Kubernetes Monitoring is not Enabled\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "26be8ea4-746d-40a1-a34b-c6609fc7b24e"}]}}}, {"policy": "ecc-gcp-049", "resource_type": "Google Kubernetes Engine", "description": "Legacy Authorization (ABAC) is Enabled\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "6c551816-c5aa-4913-84ea-fdabf88aa705"}]}}}, {"policy": "ecc-gcp-050", "resource_type": "Google Kubernetes Engine", "description": "Master Authorized Networks is not Enabled\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "551d1fc0-c450-445f-98fa-479abd9ba0ef"}]}}}, {"policy": "ecc-gcp-051", "resource_type": "Google Kubernetes Engine", "description": "Kubernetes Clusters are not configured with Labels\n", "severity": "Medium", "regions_data": {"multiregion": {"resources": [{"selfLink": "98179692-535b-48c5-8df5-f3cab9c5a7c7"}]}}}, {"policy": "ecc-gcp-053", "resource_type": "Google Kubernetes Engine", "description": "Node Auto-Repair is not enabled for GKE nodes\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "bd3129e5-36e3-4d07-a216-ec4ff62392a0"}]}}}, {"policy": "ecc-gcp-054", "resource_type": "Google Kubernetes Engine", "description": "Node Auto-Upgrade is not enabled for GKE nodes\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "0ff29cf2-a364-4b61-8ded-5b46d122d215"}]}}}, {"policy": "ecc-gcp-055", "resource_type": "Google Kubernetes Engine", "description": "Container-Optimized OS (cos_containerd) is not used for GKE node images\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "158c2234-5dbc-4b78-b876-71546650e678"}]}}}, {"policy": "ecc-gcp-057", "resource_type": "Google Kubernetes Engine", "description": "Network Policy is not Enabled and set as appropriate\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "33788d92-dc04-4a1c-9d12-2a54baf57c57"}]}}}, {"policy": "ecc-gcp-058", "resource_type": "Google Kubernetes Engine", "description": "Authentication using Client Certificates is Enabled\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "66b1a6af-19fd-42cb-b7c5-0a327e852f85"}]}}}, {"policy": "ecc-gcp-059", "resource_type": "Google Kubernetes Engine", "description": "Kubernetes Cluster is created without Alias IP ranges enabled\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "e5ecbafc-775e-49bc-98fe-7c27205a4932"}]}}}, {"policy": "ecc-gcp-060", "resource_type": "Google Kubernetes Engine", "description": "Pod Security Policy is not Enabled and set as appropriate\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "fa8369f2-869f-40e5-be6b-fce86b8303d9"}]}}}, {"policy": "ecc-gcp-061", "resource_type": "Google Kubernetes Engine", "description": "Kubernetes Cluster is created without Private cluster enabled\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "ea52380d-02a9-41c1-ad95-29dad2f83a01"}]}}}, {"policy": "ecc-gcp-062", "resource_type": "Virtual Private Cloud", "description": "Private Google Access is not set on Kubernetes Engine Cluster Subnets\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "7dceda21-11b5-4ba8-9c68-fca3f42397d0"}]}}}, {"policy": "ecc-gcp-063", "resource_type": "Google Kubernetes Engine", "description": "GKE clusters are running using the Compute Engine default service account\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "a987f22c-ba9c-4816-aaab-1950b30ea4a7"}]}}}, {"policy": "ecc-gcp-065", "resource_type": "Cloud KMS", "description": "There are regions in projects where Key Management Service is not in use\n", "severity": "Medium", "regions_data": {"multiregion": {"resources": [{"name": "eb46df3d-35d8-4342-8775-c6d919085288"}]}}}, {"policy": "ecc-gcp-066", "resource_type": "Cloud KMS", "description": "KMS cryptokeys is \"Not avaliable\"\n", "severity": "Medium", "regions_data": {"multiregion": {"resources": [{"name": "423aa6bb-dd61-4d7a-801f-2a00a96ff221"}]}}}, {"policy": "ecc-gcp-067", "resource_type": "Secret Manager", "description": "Expiry date is not set for all secrets\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"name": "ff88ad74-f149-445d-96a5-383ecceb4660"}]}}}, {"policy": "ecc-gcp-068", "resource_type": "Cloud KMS", "description": "Expiry date is not set on all keys\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"name": "65f07041-c618-49d2-ba91-3bbf52f75ca1"}]}}}, {"policy": "ecc-gcp-070", "resource_type": "Cloud Load Balancing", "description": "Armor is disabled\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"selfLink": "ce39bb0a-bf21-400d-bb13-be17dac23c60"}]}}}, {"policy": "ecc-gcp-071", "resource_type": "Virtual Private Cloud", "description": "Inbound traffic is not restricted to only that, which is necessary, and traffic is allowed for all protocols from the entire Internet (0.0.0.0/0)\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "0cf83144-a59e-48af-b245-839ea5596ddf"}]}}}, {"policy": "ecc-gcp-072", "resource_type": "Virtual Private Cloud", "description": "Outbound traffic is not restricted to only that, which is necessary, and traffic is allowed for all protocols from the entire Internet (0.0.0.0/0)\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "0fd0ce9b-6d32-4d7d-8058-bba9fa7a49f3"}]}}}, {"policy": "ecc-gcp-076", "resource_type": "Virtual Private Cloud", "description": "VPC network peering is not connected\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"selfLink": "bda02453-5871-4f6c-b0b9-06efe8b2a71f"}]}}}, {"policy": "ecc-gcp-077", "resource_type": "Cloud SQL", "description": "Names like 'Admin' are used for Google SQL Instance admin account login\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"instance": "602bb193-f2e5-4262-91c4-1bb90f863057"}]}}}, {"policy": "ecc-gcp-079", "resource_type": "Cloud Storage", "description": "Google Cloud Storage bucket hosts a static website\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "e69f22eb-9716-4466-9a6d-20dab875a96a"}]}}}, {"policy": "ecc-gcp-082", "resource_type": "Cloud SQL", "description": "Cloud SQL retention policy is less then 7 days\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "6ca13134-e929-436a-82cd-0451bc047f94"}]}}}, {"policy": "ecc-gcp-083", "resource_type": "Cloud SQL", "description": "Cloud SQL instances have not High-Availability Zone enabled\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "aeb4136a-2881-4a97-a816-5003493810d8"}]}}}, {"policy": "ecc-gcp-086", "resource_type": "Cloud SQL", "description": "SSL/TLS certificates in Cloud SQL expire in one month\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"selfLink": "8c39886e-ce66-4128-a20e-ff2c4a1ae544"}]}}}, {"policy": "ecc-gcp-087", "resource_type": "Cloud SQL", "description": "SSL/TLS certificates in Cloud SQL expire in one week\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "dca252dc-fd3e-43a4-baee-d31a49eb7a93"}]}}}, {"policy": "ecc-gcp-088", "resource_type": "Cloud Load Balancing", "description": "HTTP Load Balancer secured certificate expires in one month\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"selfLink": "60c82346-1478-47ba-a85d-c425976890dd"}]}}}, {"policy": "ecc-gcp-089", "resource_type": "Cloud Load Balancing", "description": "HTTP Load Balancer secured certificate expires in one week\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "11509042-d4fe-445b-bf94-ee02b9e3feff"}]}}}, {"policy": "ecc-gcp-090", "resource_type": "Cloud Load Balancing", "description": "Weak ciphers are used in CDN\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "a1b9d41a-64e1-4465-8f25-c092f195dbe5"}]}}}, {"policy": "ecc-gcp-091", "resource_type": "Cloud Load Balancing", "description": "Connection used between CDN and an origin server is not encrypted\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "29789e15-f2c9-4335-8255-2fdf7897a21f"}]}}}, {"policy": "ecc-gcp-092", "resource_type": "Cloud Load Balancing", "description": "Weak ciphers are used for Load Balancer\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "db13f700-b42e-4140-9149-7c0e6808a8e0"}]}}}, {"policy": "ecc-gcp-093", "resource_type": "Cloud Load Balancing", "description": "GCP Load Balancer uses HTTP instead of HTTPS\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "4de9c9f1-b56b-4d08-9a63-01a40f39c559"}]}}}, {"policy": "ecc-gcp-099", "resource_type": "Compute Engine", "description": "There is an instance without any tags\n", "severity": "Medium", "regions_data": {"multiregion": {"resources": [{"selfLink": "f7579cbc-4e17-435c-8b8a-c171fc0a0d72"}]}}}, {"policy": "ecc-gcp-101", "resource_type": "Cloud Load Balancing", "description": "Load Balancer Access Logging is Disabled\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "ab69333a-8d2e-454f-907d-fb83b6a87a7f"}]}}}, {"policy": "ecc-gcp-103", "resource_type": "Compute Engine", "description": "There is an instance without deletion protection\n", "severity": "Medium", "regions_data": {"multiregion": {"resources": [{"selfLink": "3eb2fe35-b55d-4224-b082-7045f7ac1929"}]}}}, {"policy": "ecc-gcp-104", "resource_type": "Virtual Private Cloud", "description": "There is a default firewall rule in use\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "3733f3d5-dc88-484e-9b39-870ced011eba"}]}}}, {"policy": "ecc-gcp-107", "resource_type": "Compute Engine", "description": "There are volumes that have not had a backup or snapshot in the past 14 days\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"selfLink": "c36170f7-e8d2-4083-83e7-52806c94fcef"}]}}}, {"policy": "ecc-gcp-109", "resource_type": "Virtual Private Cloud", "description": "Ingress access is allowed\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "489f9fc7-f883-41f0-ae39-948be157a564"}]}}}, {"policy": "ecc-gcp-110", "resource_type": "Virtual Private Cloud", "description": "Firewall rules allow internet traffic to DNS port (53)\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "5fa1ef09-5022-42a8-8d82-e53acdbc533f"}]}}}, {"policy": "ecc-gcp-111", "resource_type": "Virtual Private Cloud", "description": "Firewall rules allow internet traffic to FTP port (21)\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "cccdb085-1863-4129-9e08-b80f9ff1b73a"}]}}}, {"policy": "ecc-gcp-112", "resource_type": "Virtual Private Cloud", "description": "Firewall rules allow internet traffic to HTTP port (80)\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "09c64ff8-7380-45ae-933a-c84fc173be28"}]}}}, {"policy": "ecc-gcp-113", "resource_type": "Virtual Private Cloud", "description": "Firewall rules allow internet traffic to Microsoft-DS port (445)\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "75d40d92-1ef9-4ffc-bc3d-d2ced3e18807"}]}}}, {"policy": "ecc-gcp-114", "resource_type": "Virtual Private Cloud", "description": "Firewall rules allow internet traffic to MongoDB port (27017)\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "8cd02aea-8d49-4004-8590-fdbf382e0dcc"}]}}}, {"policy": "ecc-gcp-115", "resource_type": "Virtual Private Cloud", "description": "Firewall rules allow internet traffic to MySQL DB port (3306)\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "9c492808-4619-47f3-9a65-9c17467e3881"}]}}}, {"policy": "ecc-gcp-116", "resource_type": "Virtual Private Cloud", "description": "Firewall rules allow internet traffic to NetBIOS-SSN port (139)\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "cdea7826-a643-4404-a224-a2b431a78ff8"}]}}}, {"policy": "ecc-gcp-117", "resource_type": "Virtual Private Cloud", "description": "Firewall rules allow internet traffic to Oracle DB port (1521)\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "4ef5b0d0-490b-4769-b89f-f2183bcdda73"}]}}}, {"policy": "ecc-gcp-118", "resource_type": "Virtual Private Cloud", "description": "Firewall rules allow internet traffic to POP3 port (110)\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "312ee341-e4c2-4931-85a6-0f139198718f"}]}}}, {"policy": "ecc-gcp-119", "resource_type": "Virtual Private Cloud", "description": "Firewall rules allow internet traffic to PostgreSQL port (5432)\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "ec404ed7-2239-4d90-82d5-4675070a1bc7"}]}}}, {"policy": "ecc-gcp-120", "resource_type": "Virtual Private Cloud", "description": "Firewall rules allow internet traffic to SMTP port (25)\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "5268aace-a78b-4673-94e1-458d362fd23e"}]}}}, {"policy": "ecc-gcp-121", "resource_type": "Virtual Private Cloud", "description": "Firewall rules allow internet traffic to Telnet port (23)\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "082376ed-a1bc-4d7f-8402-6207d38180d2"}]}}}, {"policy": "ecc-gcp-122", "resource_type": "Virtual Private Cloud", "description": "Firewall rules allow inbound traffic from anywhere with no target tags set\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "93ea74f2-ceae-4935-82c9-b9077aad63c5"}]}}}, {"policy": "ecc-gcp-123", "resource_type": "Google Kubernetes Engine", "description": "There is unsupported GKE Master node version\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "e117c283-ec17-4c71-8279-d7ac6445885a"}]}}}, {"policy": "ecc-gcp-124", "resource_type": "Google Kubernetes Engine", "description": "There is unsupported GKE node version\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "402be357-3e03-4f54-bcf6-85b0ceb60f56"}]}}}, {"policy": "ecc-gcp-125", "resource_type": "Cloud Load Balancing", "description": "HTTPS Load balancer SSL Policy is not using a restrictive profile\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "1f010e13-cb49-4ad6-b34a-9f8a15666855"}]}}}, {"policy": "ecc-gcp-126", "resource_type": "Google Kubernetes Engine", "description": "Alpha clusters are used for production workloads\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "afa60137-57af-446d-8916-80553e9a95fd"}]}}}, {"policy": "ecc-gcp-127", "resource_type": "Google Kubernetes Engine", "description": "Binary Authorization is not used\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"selfLink": "ec4f00ff-ded4-435a-b816-4a38944bd681"}]}}}, {"policy": "ecc-gcp-128", "resource_type": "Google Kubernetes Engine", "description": "Legacy Compute Engine instance metadata APIs are Enabled\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "a810757c-ac72-42b2-98b9-6c49e168854b"}]}}}, {"policy": "ecc-gcp-129", "resource_type": "Virtual Private Cloud", "description": "Kubernetes Engine Clusters network firewall inbound rule is overly permissive to all traffic\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "a5d12233-d85f-4a5f-8211-5fe872606034"}]}}}, {"policy": "ecc-gcp-130", "resource_type": "Google Kubernetes Engine", "description": "Kubernetes Engine Clusters are not configured with network traffic egress metering\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"selfLink": "af89e968-9a98-4f1f-be54-5bde881592c5"}]}}}, {"policy": "ecc-gcp-131", "resource_type": "Google Kubernetes Engine", "description": "Kubernetes Engine Clusters are not configured with private nodes feature\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "5fc8dfea-d4c2-4f80-9ced-23027d837fb8"}]}}}, {"policy": "ecc-gcp-132", "resource_type": "Google Kubernetes Engine", "description": "Kubernetes cluster Application-layer Secrets are not encrypted\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "91fe1422-80e2-4196-a497-fe1633c1d4e1"}]}}}, {"policy": "ecc-gcp-133", "resource_type": "Google Kubernetes Engine", "description": "Kubernetes cluster intranode visibility is Disabled\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "dd0c4702-5745-47f9-93d1-4f6a22586bba"}]}}}, {"policy": "ecc-gcp-134", "resource_type": "Google Kubernetes Engine", "description": "Kubernetes cluster istioConfig is not Enabled\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "63023a7c-ddd5-4e8e-bcc3-674453811d84"}]}}}, {"policy": "ecc-gcp-135", "resource_type": "Google Kubernetes Engine", "description": "Kubernetes cluster is not in redundant zones\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "3f241507-1fe4-4f90-9e82-b5005cf19fc2"}]}}}, {"policy": "ecc-gcp-136", "resource_type": "Google Kubernetes Engine", "description": "Kubernetes cluster size contains less than 3 nodes with auto-upgrade enabled\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"selfLink": "cb45dcc3-3b77-4bff-810b-d183c9cfe8ed"}]}}}, {"policy": "ecc-gcp-137", "resource_type": "Cloud Load Balancing", "description": "Load balancer HTTPS target proxy is configured with default SSL policy instead of custom SSL policy\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"selfLink": "3170e0ea-ad19-4fa3-afd1-a6b8cc77576e"}]}}}, {"policy": "ecc-gcp-138", "resource_type": "Cloud Load Balancing", "description": "Load balancer HTTPS target proxy is not configured with QUIC protocol\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "646b13ba-4241-4c28-846b-5d85ccb41b56"}]}}}, {"policy": "ecc-gcp-140", "resource_type": "Cloud SQL", "description": "There are SQL Instances without any Label information\n", "severity": "Medium", "regions_data": {"multiregion": {"resources": [{"selfLink": "5a3306fc-ac61-42c4-b5a7-fb58f7c4fff2"}]}}}, {"policy": "ecc-gcp-141", "resource_type": "Cloud Storage", "description": "Storage bucket is encrypted using google-managed key instead of customer-managed key\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "11e7d351-09b8-41e2-a51b-f609484d47fc"}]}}}, {"policy": "ecc-gcp-142", "resource_type": "Cloud Storage", "description": "Logging on the Stackdriver exported Bucket is disabled\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"selfLink": "30fc3177-b735-4ee9-b421-07eb46fd5fd2"}]}}}, {"policy": "ecc-gcp-143", "resource_type": "Cloud IAM", "description": "Primitive IAM roles are used\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"member": "e50cb5c1-ee9e-4bfd-988c-2f69b19eadfb", "roles": "1949336d-6d58-4e06-a65a-5819a6f91ef3"}]}}}, {"policy": "ecc-gcp-144", "resource_type": "Cloud SQL", "description": "MySQL DB Instance backup Binary logs configuration is not enabled\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "5779b56f-048b-4e94-be01-e355b21cfe2c"}]}}}, {"policy": "ecc-gcp-150", "resource_type": "Cloud Storage", "description": "There are Storage Buckets with publicly accessible Stackdriver logs\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "b8e066fb-7045-45cd-b1c9-6a8960a50ce2"}]}}}, {"policy": "ecc-gcp-151", "resource_type": "Compute Engine", "description": "VM Instances are enabled with Pre-Emptible termination\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "b1d726bd-3963-49c9-b5b6-ca117a5dad10"}]}}}, {"policy": "ecc-gcp-152", "resource_type": "Compute Engine", "description": "There are VM Instances without any custom metadata information\n", "severity": "Medium", "regions_data": {"multiregion": {"resources": [{"selfLink": "b772db17-d627-4538-8d13-1d8c8d80a97f"}]}}}, {"policy": "ecc-gcp-153", "resource_type": "Compute Engine", "description": "There are VM Instances without any Label information\n", "severity": "Medium", "regions_data": {"multiregion": {"resources": [{"selfLink": "f1724922-e89c-43e3-883c-56207f45cfc4"}]}}}, {"policy": "ecc-gcp-162", "resource_type": "Cloud Storage", "description": "Buckets have no set life cycle value\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"selfLink": "11d89a70-a759-48c9-9341-609f4d5fb8b4"}]}}}, {"policy": "ecc-gcp-163", "resource_type": "Cloud Storage", "description": "Buckets have no labels\n", "severity": "Medium", "regions_data": {"multiregion": {"resources": [{"selfLink": "0d8b5960-bf26-4cd1-9406-ac762e19ef21"}]}}}, {"policy": "ecc-gcp-165", "resource_type": "Google Kubernetes Engine", "description": "GKE Cluster HTTP Load balancing is not enabled\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "04ceaca7-5404-42e0-8d34-04e6789aa748"}]}}}, {"policy": "ecc-gcp-166", "resource_type": "Google Kubernetes Engine", "description": "GKE Clusters use default network instead of the specific purpose-designed networks\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "f32c458c-24e6-41d1-9faf-98015fbec3bf"}]}}}, {"policy": "ecc-gcp-167", "resource_type": "BigQuery", "description": "BigQuery IAM role grants an individual Google account access to a dataset\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "0bb02904-c984-46a4-9233-737704b1419d"}]}}}, {"policy": "ecc-gcp-169", "resource_type": "Cloud KMS", "description": "Cloud KMS cryptokeys are anonymously or publicly accessible\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"name": "2b51b9b9-6eab-4913-9462-8bda87a00de3"}]}}}, {"policy": "ecc-gcp-170", "resource_type": "Cloud Load Balancing", "description": "HTTPS or SSL proxy load balancers permit SSL policies with weak cipher suites\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "bea40735-0ef7-43f0-aa8f-a93e215a86a9"}]}}}, {"policy": "ecc-gcp-171", "resource_type": "Compute Engine", "description": "Instances are configured to use the default service account\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "4386e596-1ee8-497a-b5ad-6de749bcec39"}]}}}, {"policy": "ecc-gcp-172", "resource_type": "Compute Engine", "description": "Compute instances are launched without Shielded VM enabled\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "a73bdcee-f77d-46fd-bc60-511ea3b5165a"}]}}}, {"policy": "ecc-gcp-173", "resource_type": "Compute Engine", "description": "Compute instances have public IP addresses\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "3af3bff4-0a32-4b01-ae34-4b5f364f721f"}]}}}, {"policy": "ecc-gcp-175", "resource_type": "Cloud Storage", "description": "Cloud Storage buckets have not uniform bucket-level access enabled\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"selfLink": "68526b74-f49a-4103-9b61-3861bc148fd0"}]}}}, {"policy": "ecc-gcp-176", "resource_type": "Cloud SQL", "description": "The 'local_infile' database flag for a Cloud SQL MySQL instance is not set to 'off'\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "b17fed92-eb1c-446a-bdae-e22820dc6faf"}]}}}, {"policy": "ecc-gcp-177", "resource_type": "Cloud SQL", "description": "The 'log_checkpoints' database flag for Cloud SQL PostgreSQL instance is not set to 'on'\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"selfLink": "0b401991-905c-44bf-aee6-ed3215d25da3"}]}}}, {"policy": "ecc-gcp-178", "resource_type": "Cloud SQL", "description": "The 'log_connections' database flag for Cloud SQL PostgreSQL instance is not set to 'on'\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "e5d74e0c-eda5-4f62-9fc3-9640c8f65e4d"}]}}}, {"policy": "ecc-gcp-179", "resource_type": "Cloud SQL", "description": "The 'log_disconnections' database flag for Cloud SQL PostgreSQL instance is not set to 'on'\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"selfLink": "7e0a9e1d-ba06-49c3-8f7a-405d99e5ef94"}]}}}, {"policy": "ecc-gcp-180", "resource_type": "Cloud SQL", "description": "The 'log_lock_waits' database flag for Cloud SQL PostgreSQL instance is not set to 'on'\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"selfLink": "9a7bde45-7dc0-4045-90a0-531ddd5313e9"}]}}}, {"policy": "ecc-gcp-181", "resource_type": "Cloud SQL", "description": "The 'log_min_messages' database flag for Cloud SQL PostgreSQL instance is not set appropriately\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"selfLink": "af99c6d7-0e56-4ede-96e0-27d73e326dbe"}]}}}, {"policy": "ecc-gcp-182", "resource_type": "Cloud SQL", "description": "The 'log_temp_files' database flag for Cloud SQL PostgreSQL instance is not set to '0' (on)\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"selfLink": "16201e93-4e25-4b22-97e8-60d8963bc69e"}]}}}, {"policy": "ecc-gcp-183", "resource_type": "Cloud SQL", "description": "The 'log_min_duration_statement' database flag for Cloud SQL PostgreSQL instance is not set to '-1' (disabled)\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "879cb5d3-4218-4533-91c7-5f598772eea9"}]}}}, {"policy": "ecc-gcp-184", "resource_type": "Cloud SQL", "description": "The 'cross db ownership chaining' database flag for Cloud SQL SQL Server instance is not set to 'off'\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "6106ad45-4452-4843-91a0-93cfb6fa9f73"}]}}}, {"policy": "ecc-gcp-185", "resource_type": "Cloud SQL", "description": "The 'contained database authentication' database flag for Cloud SQL on the SQL Server instance is not set to 'off'\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "face2c40-189a-418c-b351-a471093ffb4f"}]}}}, {"policy": "ecc-gcp-186", "resource_type": "Cloud SQL", "description": "Cloud SQL database instances have public IPs\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "8bdd8ae6-0ecf-4e3a-a726-55957f335d4c"}]}}}, {"policy": "ecc-gcp-187", "resource_type": "Cloud SQL", "description": "Cloud SQL database instances are configured without automated backups\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "5789232d-fd08-4a11-a989-bebb6097d1aa"}]}}}, {"policy": "ecc-gcp-188", "resource_type": "BigQuery", "description": "BigQuery datasets are anonymously or publicly accessible\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "0dc0df4e-d138-47f4-9522-a06acde10cc1"}]}}}, {"policy": "ecc-gcp-189", "resource_type": "Cloud Storage", "description": "Cloud Storage has not multi-region bucket location\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"selfLink": "e4594c2e-a375-4ea3-88ad-592f756c6395"}]}}}, {"policy": "ecc-gcp-190", "resource_type": "Cloud Storage", "description": "Retention policies on log buckets are not configured using Bucket Lock\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "10edf3bb-0e10-4807-8d0c-2476a5eedc05"}]}}}, {"policy": "ecc-gcp-191", "resource_type": "Google Kubernetes Engine", "description": "Integrity Monitoring for Shielded GKE Nodes is not Enabled\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"selfLink": "2e818a36-8462-4ef8-9836-a9db932c7e1f"}]}}}, {"policy": "ecc-gcp-192", "resource_type": "Google Kubernetes Engine", "description": "Secure Boot for Shielded GKE Nodes is not Enabled\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "f4b7e3c0-7a68-4b3d-ae37-e933a5deeab5"}]}}}, {"policy": "ecc-gcp-193", "resource_type": "Google Kubernetes Engine", "description": "VPC Flow Logs and Intranode Visibility are Disabled\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "863c1049-f765-44ec-9b08-c20a4aa8e3af"}]}}}, {"policy": "ecc-gcp-194", "resource_type": "Compute Engine", "description": "OS Login is not enabled for an instance\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "58f8dd11-4f38-49b6-8706-2aa0b2cf9e25"}]}}}, {"policy": "ecc-gcp-195", "resource_type": "Virtual Private Cloud", "description": "Cloud DNS logging is not enabled for all VPC networks\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "8c56747b-cd7b-429f-a4c3-c6dd313e5e78"}]}}}, {"policy": "ecc-gcp-197", "resource_type": "Compute Engine", "description": "Compute instances have not Confidential Computing enabled\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"selfLink": "b39e2327-e192-46ca-9ee2-adc944d3dbc9"}]}}}, {"policy": "ecc-gcp-198", "resource_type": "Cloud SQL", "description": "The 'skip_show_database' database flag for Cloud SQL MySQL instance is not set to 'on'\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "5a99c933-bf9d-4bde-8718-346b7c0b972f"}]}}}, {"policy": "ecc-gcp-199", "resource_type": "Cloud SQL", "description": "The 'log_error_verbosity' database flag for Cloud SQL PostgreSQL instance is not set to 'DEFAULT' or stricter\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"selfLink": "8b521f27-5a9f-4ba3-9f1f-bd020de22de5"}]}}}, {"policy": "ecc-gcp-200", "resource_type": "Cloud SQL", "description": "The 'log_duration' database flag for Cloud SQL PostgreSQL instance is not set to 'on'\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"selfLink": "a7b5bc48-4ffc-4323-95ee-4702247fc976"}]}}}, {"policy": "ecc-gcp-201", "resource_type": "Cloud SQL", "description": "The 'log_statement' database flag for Cloud SQL PostgreSQL instance is not set appropriately\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "ba06415b-e2bd-4adc-8f12-2a583e55ebc3"}]}}}, {"policy": "ecc-gcp-202", "resource_type": "Cloud SQL", "description": "The 'log_hostname' database flag for Cloud SQL PostgreSQL instance is not set appropriately\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"selfLink": "1658ce5d-8f91-429e-a77d-172f9288aaf9"}]}}}, {"policy": "ecc-gcp-203", "resource_type": "Cloud SQL", "description": "The 'log_parser_stats' database flag for Cloud SQL PostgreSQL instance is not set to 'off'\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "44e16bae-1b51-4ae5-8cbc-73ab263a5b34"}]}}}, {"policy": "ecc-gcp-204", "resource_type": "Cloud SQL", "description": "The 'log_planner_stats' database flag for Cloud SQL PostgreSQL instance is not set to 'off'\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "a6384d71-c654-4059-9d53-08efc85abc4d"}]}}}, {"policy": "ecc-gcp-205", "resource_type": "Cloud SQL", "description": "The 'log_executor_stats' database flag for Cloud SQL PostgreSQL instance is not set to 'off'\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "0af64600-99fa-4e7e-966e-d69af30a0cdd"}]}}}, {"policy": "ecc-gcp-206", "resource_type": "Cloud SQL", "description": "The 'log_statement_stats' database flag for Cloud SQL PostgreSQL instance is not set to 'off'\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "d6d30c5f-1a03-4655-873e-24478b7eb453"}]}}}, {"policy": "ecc-gcp-207", "resource_type": "Cloud SQL", "description": "The 'log_min_error_statement' database flag for Cloud SQL PostgreSQL instance is not set to 'Error' or stricter\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "46a5c0d7-50bb-4b92-b509-9d927e646635"}]}}}, {"policy": "ecc-gcp-208", "resource_type": "Cloud SQL", "description": "The 'external scripts enabled' database flag for Cloud SQL SQL Server instance is not set to 'off'\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "79dc703f-f959-41c5-b51d-42ff3a62baf1"}]}}}, {"policy": "ecc-gcp-209", "resource_type": "Cloud SQL", "description": "The 'user connections' database flag for Cloud SQL SQL Server instance is not Set to a Non-limiting Value\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"selfLink": "38eae8e1-777b-4aa2-a071-448b949c022e"}]}}}, {"policy": "ecc-gcp-210", "resource_type": "Cloud SQL", "description": "The 'user options' database flag for Cloud SQL SQL Server instance is configured\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"selfLink": "41967c70-a9df-4138-ae63-a9b8f4542ac1"}]}}}, {"policy": "ecc-gcp-211", "resource_type": "Cloud SQL", "description": "The 'remote access' database flag for Cloud SQL SQL Server instance is not set to 'off'\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "b3b78070-66ad-4040-a215-4ce0ce0daae9"}]}}}, {"policy": "ecc-gcp-212", "resource_type": "Cloud SQL", "description": "The '3625 (trace flag)' database flag for Cloud SQL SQL Server instance is not set to 'on'\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "d7a43cca-a02c-455d-922c-a1ca0169b14c"}]}}}, {"policy": "ecc-gcp-213", "resource_type": "BigQuery", "description": "Some BigQuery Tables are not encrypted with Customer-managed encryption key (CMEK)\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"id": "dbc0a015-efd0-4f11-8901-e2d8d3706959"}]}}}, {"policy": "ecc-gcp-214", "resource_type": "BigQuery", "description": "Default Customer-managed encryption key (CMEK) is not specified for some BigQuery Data Sets\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "10064e0a-dbc6-4644-b129-7e01812220dd"}]}}}, {"policy": "ecc-gcp-215", "resource_type": "Compute Engine", "description": "'Block Project-wide SSH keys' is not enabled for instance template\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"selfLink": "180652b7-07a8-4508-aa37-5157a320116e"}]}}}, {"policy": "ecc-gcp-216", "resource_type": "Compute Engine", "description": "IP forwarding is enabled on Instance templates\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"selfLink": "99bf9140-254b-4962-a0e4-818dfda55759"}]}}}, {"policy": "ecc-gcp-217", "resource_type": "Compute Engine", "description": "'Enable connecting to serial ports' is enabled for Instance template\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"selfLink": "490982b4-0359-4c7d-bf6f-b6c05b1adc02"}]}}}, {"policy": "ecc-gcp-218", "resource_type": "Compute Engine", "description": "Oslogin is not enabled for an instance template\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"selfLink": "c18a9a8e-5fe1-4751-9f6b-59c1d31159e1"}]}}}, {"policy": "ecc-gcp-219", "resource_type": "Compute Engine", "description": "Instance template configured without Shielded VM features\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "1b50787c-919c-4f63-aa93-c5aba0a826e6"}]}}}, {"policy": "ecc-gcp-220", "resource_type": "Compute Engine", "description": "Instance template configured with external IP addresses\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "ab490a69-8504-49da-87fa-068a2d63e88d"}]}}}, {"policy": "ecc-gcp-221", "resource_type": "Compute Engine", "description": "Instance template use default service account\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "fb030b31-9417-4769-89c7-2d7d0ae74499"}]}}}, {"policy": "ecc-gcp-222", "resource_type": "Compute Engine", "description": "Instance templates are configured to use the default service account with full access to all Cloud APIs\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"selfLink": "ee4c59c8-fed0-430d-a7c8-959fb712d539"}]}}}, {"policy": "ecc-gcp-223", "resource_type": "Compute Engine", "description": "VM instance is configured with default network\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "46af3161-68d4-462e-9f38-a78e49a1139d"}]}}}, {"policy": "ecc-gcp-225", "resource_type": "Compute Engine", "description": "VM instance with public IP address has access to GCS buckets\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "c2468010-b814-489a-8684-c0ed9974d6af"}]}}}, {"policy": "ecc-gcp-227", "resource_type": "Compute Engine", "description": "Compute engine image is not encrypted using customer-managed key\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "4c58ada2-451c-436d-99ba-7e4b5161b210"}]}}}, {"policy": "ecc-gcp-228", "resource_type": "Compute Engine", "description": "'On Host Maintenance' configuration setting is not set to 'Migrate' for all VM instances\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "7ad6d605-dc03-4b3a-8e7d-6304147dfe2e"}]}}}, {"policy": "ecc-gcp-229", "resource_type": "Compute Engine", "description": "Auto-Delete feature is not disabled for the disks attached to the VM instances\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "a279aded-e2be-40d1-be49-b4d587d1ef5e"}]}}}, {"policy": "ecc-gcp-230", "resource_type": "Compute Engine", "description": "Automatic restart is not enabled for Google Cloud virtual machine (VM) instances\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "ead8aa80-7281-4537-90a2-a2c0199ec775"}]}}}, {"policy": "ecc-gcp-231", "resource_type": "Compute Engine", "description": "Google Cloud Managed Instance Groups (MIGs) are configured without Autohealing feature.\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "005e0b4b-f924-416d-8db5-143eb37a7665"}]}}}, {"policy": "ecc-gcp-232", "resource_type": "Compute Engine", "description": "OS Login is not configured with Two-Factor Authentication (2FA) for production VM instances\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "6b29d2d9-d535-426a-bfdd-8c9b587c3116"}]}}}, {"policy": "ecc-gcp-233", "resource_type": "Compute Engine", "description": "VM disk images are publicly shared with all other GCP accounts\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "58ca8bc2-7a44-48bf-89c0-95a237e506c9"}]}}}, {"policy": "ecc-gcp-234", "resource_type": "Cloud SQL", "description": "Cloud SQL database instance data encryption is not set to customer managed encryption key\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "20416141-dc8b-46d7-98f5-2ec4bf733662"}]}}}, {"policy": "ecc-gcp-236", "resource_type": "Cloud SQL", "description": "MySQL database servers are not using the latest major version of MySQL database\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"selfLink": "959731c7-d57e-4350-8ece-1ff291fc3ec3"}]}}}, {"policy": "ecc-gcp-237", "resource_type": "Cloud SQL", "description": "PostgreSQL database servers are not using the latest major version of PostgreSQL database\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"selfLink": "3fe3e2d2-7b86-4336-b779-c44a263908f2"}]}}}, {"policy": "ecc-gcp-239", "resource_type": "Cloud SQL", "description": "Automatic storage increase is not enabled for your Cloud SQL database instances\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "f3a0ce62-8d1b-46ae-bba2-80d91b13d1b1"}]}}}, {"policy": "ecc-gcp-240", "resource_type": "Cloud SQL", "description": "MySQL database instance has the 'slow_query_log' flag set to Off\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "a57fb70f-207a-45a8-91ef-2ed436a1de99"}]}}}, {"policy": "ecc-gcp-241", "resource_type": "Cloud Functions", "description": "Cloud Functions function is configured with privileged service accounts\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"name": "77cf91e6-b3c6-443a-ae1e-53ecad74ed66"}]}}}, {"policy": "ecc-gcp-242", "resource_type": "Cloud Functions", "description": "Cloud Functions function does not restrict public access\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"name": "030ade0a-68f3-4c0e-b928-0712fbf9271e"}]}}}, {"policy": "ecc-gcp-243", "resource_type": "Cloud Functions", "description": "Cloud Functions function is configured with the allow all traffic ingress setting\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"name": "c07c8a98-b278-4260-b401-932a6ab72690"}]}}}, {"policy": "ecc-gcp-244", "resource_type": "Cloud Functions", "description": "Cloud Functions function allows unauthenticated invocation\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"name": "bcc2c3f7-1148-403b-9cb3-fbf4d3c57a14"}]}}}, {"policy": "ecc-gcp-245", "resource_type": "Cloud Functions", "description": "Cloud Functions function is configured with default service account\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"name": "72b20c9c-a42a-45d6-90ef-0a67df3554c7"}]}}}, {"policy": "ecc-gcp-246", "resource_type": "Cloud Functions", "description": "Cloud Functions function is not configured with a VPC connector\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"name": "1a929b53-f02b-4239-90b5-6cdd3ed39f34"}]}}}, {"policy": "ecc-gcp-247", "resource_type": "Cloud Functions", "description": "The deployed Cloud Function is not in 'ACTIVE' mode\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"name": "4517d322-12a5-4356-ac83-5c7209c7491a"}]}}}, {"policy": "ecc-gcp-248", "resource_type": "Cloud Functions", "description": "Event trigger is not configured in your function\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"name": "8e5fa20d-aa04-4926-853e-3314aee8e1ee"}]}}}, {"policy": "ecc-gcp-249", "resource_type": "Cloud Functions", "description": "GCP Cloud Function HTTP trigger is not secured\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"name": "4ed6abcd-8764-44eb-a819-3bd80772f1ba"}]}}}, {"policy": "ecc-gcp-250", "resource_type": "Google Kubernetes Engine", "description": "GKE cluster container scanning disabled\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"projectId": "902562dc-4331-49d6-af83-fd843ce22165"}]}}}, {"policy": "ecc-gcp-251", "resource_type": "Google Kubernetes Engine", "description": "GKE node is configured with privileged service account\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "b9637ee9-4234-4cf5-b1c9-ed6dfe7be7d8"}]}}}, {"policy": "ecc-gcp-252", "resource_type": "Google Kubernetes Engine", "description": "GCP Kubernetes Engine cluster not using Release Channel for version management\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "b3f28007-7761-4c68-abe9-e3a3bb39b8f9"}]}}}, {"policy": "ecc-gcp-253", "resource_type": "Google Kubernetes Engine", "description": "GKE Metadata Server is not Enabled\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "b315d060-638f-47cb-a688-9500d8bce60f"}]}}}, {"policy": "ecc-gcp-254", "resource_type": "App Engine", "description": "Cloud App Engine application custom domain SSL certificate expires in less than 30 days\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"name": "77761cd2-df5b-4b39-a7ee-1de3251f0feb"}]}}}, {"policy": "ecc-gcp-256", "resource_type": "App Engine", "description": "Cloud App Engine application firewall does not restrict public access\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"description": "687a6bfe-d11e-4d23-ab0a-000e852ec369"}]}}}, {"policy": "ecc-gcp-257", "resource_type": "App Engine", "description": "App Engine Identity-Aware Proxy is disabled\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"name": "40e82d3a-ee7f-4859-94ed-5ba60ce75794"}]}}}, {"policy": "ecc-gcp-258", "resource_type": "Cloud Bigtable", "description": "Cloud Bigtable backup expiration time is 29 days or less\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"sourceTable": "92ff8002-c2df-49fa-a98e-420b1edd4d38", "name": "9dac9a33-26e2-442f-a32b-917f911961bd"}]}}}, {"policy": "ecc-gcp-260", "resource_type": "Cloud Bigtable", "description": "Cloud Bigtable table configured with privileged service account\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "c9d666b0-6646-428a-8180-6acd835aa846"}]}}}, {"policy": "ecc-gcp-261", "resource_type": "Dataproc", "description": "Dataproc cluster is not encrypted with Customer-Managed Key\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"clusterName": "23581387-67ba-44b7-be35-756c3225aece", "projectId": "d2769103-cb6e-4360-b061-156759b7a8c8"}]}}}, {"policy": "ecc-gcp-262", "resource_type": "Cloud Run", "description": "Cloud Run revision is configured with privileged service accounts\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"metadata.selfLink": "403a6995-c538-43fd-a329-2b8c53b7aca5"}]}}}, {"policy": "ecc-gcp-263", "resource_type": "Cloud Run", "description": "Cloud Run revision is configured with default service account\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"metadata.selfLink": "3db771f0-62ff-40b2-a301-a5e44aff2eb4"}]}}}, {"policy": "ecc-gcp-264", "resource_type": "Cloud Run", "description": "Cloud Run revision is configured without a VPC connector\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"metadata.selfLink": "6445468d-a166-4a3a-af12-35e8c00ab328"}]}}}, {"policy": "ecc-gcp-265", "resource_type": "Cloud Run", "description": "Cloud Run service is configured with privileged service accounts\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"metadata.selfLink": "9b1bb83f-421b-428d-ab2d-4e240d519c9e"}]}}}, {"policy": "ecc-gcp-266", "resource_type": "Cloud Run", "description": "Cloud Run service is configured with the allow all traffic ingress setting\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"metadata.selfLink": "b8bb80fa-d07b-4e06-85f4-194f593280d0"}]}}}, {"policy": "ecc-gcp-268", "resource_type": "Cloud Run", "description": "Cloud Run service allows public access\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"metadata.selfLink": "a39e9bd8-d67f-41c3-aad0-a5e2cdf994c5"}]}}}, {"policy": "ecc-gcp-271", "resource_type": "Virtual Private Cloud", "description": "Network is not configured with default deny egress rule in firewall\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "4092e167-286c-406e-9a1d-a6ac14f08f79"}]}}}, {"policy": "ecc-gcp-272", "resource_type": "Virtual Private Cloud", "description": "Firewall logging is not enabled\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"selfLink": "db96fddd-65ac-40dd-abd6-dc30c32a0696"}]}}}, {"policy": "ecc-gcp-273", "resource_type": "Cloud Armor", "description": "Cloud Armor Security Policy have not Adaptive Protection enabled\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "7004cc26-844a-4f2e-ba43-57b86555955a"}]}}}, {"policy": "ecc-gcp-274", "resource_type": "Cloud Armor", "description": "Cloud Armor Security Policy does not have deny as default action\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "ed0b7534-9649-4e1e-bdea-37b2c24ecc8a"}]}}}, {"policy": "ecc-gcp-276", "resource_type": "Cloud Armor", "description": "Cloud Armor Security Policy have not non-default rules defined\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "075aca83-ecdc-47ff-8adc-3664eef34cab"}]}}}, {"policy": "ecc-gcp-277", "resource_type": "Cloud DNS", "description": "Cloud DNS managed zone didn't contain TXT type record\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"name": "020e2968-f004-440d-b514-43d4d52bb23e"}]}}}, {"policy": "ecc-gcp-278", "resource_type": "Virtual Private Cloud", "description": "Firewall rules allow internet traffic to Elastic Search port (9200 or 9300)\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "14ba9e8e-ef35-4ace-91bb-3824b5a5b772"}]}}}, {"policy": "ecc-gcp-279", "resource_type": "Virtual Private Cloud", "description": "Firewall rules allow internet traffic to FTP port (20)\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "9df01291-9024-45fb-924b-b76a69158fab"}]}}}, {"policy": "ecc-gcp-280", "resource_type": "Virtual Private Cloud", "description": "Firewall rules allow internet traffic to Kibana port (5601)\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "938fbada-84e7-465d-ad9a-f0143161d175"}]}}}, {"policy": "ecc-gcp-281", "resource_type": "Virtual Private Cloud", "description": "Firewall rules allow public access to Memcached port (11211)\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "55d0ad01-cb5e-43c1-ae6d-89d39f33ed53"}]}}}, {"policy": "ecc-gcp-282", "resource_type": "Virtual Private Cloud", "description": "Firewall rules allow internet traffic to Redis port (6379)\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "585455b5-99d2-4d65-80cd-854dccf7ea63"}]}}}, {"policy": "ecc-gcp-283", "resource_type": "Virtual Private Cloud", "description": "Firewall rules allow internet traffic to SQL server port (1433)\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "79e6efbb-634d-460a-af91-de79068da8f7"}]}}}, {"policy": "ecc-gcp-285", "resource_type": "Virtual Private Cloud", "description": "Firewall rules allow internet traffic to WinRM port (5985 or 5986)\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "364bde2d-4c69-4ad4-9037-581bd53ab7d9"}]}}}, {"policy": "ecc-gcp-286", "resource_type": "Pub/Sub", "description": "Pub/Sub topic is not encrypted using a customer-managed encryption key\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"name": "592b1a67-e654-4d38-8c87-7a894edec15b"}]}}}, {"policy": "ecc-gcp-287", "resource_type": "Pub/Sub", "description": "There is not a dead-letter topic configured for each Pub/Sub subscription\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"name": "ea10b519-77a2-45be-b395-e6e4005404ce"}]}}}, {"policy": "ecc-gcp-288", "resource_type": "Virtual Private Cloud", "description": "Firewall rules allow internet traffic to ICMP\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "bd3a9f46-3d0c-4418-8eda-e419d6aee01a"}]}}}, {"policy": "ecc-gcp-289", "resource_type": "Virtual Private Cloud", "description": "Firewall rules allow public access to RPC port (135)\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "dfdc5b80-ba62-4f09-a350-491eeac96768"}]}}}, {"policy": "ecc-gcp-291", "resource_type": "Secret Manager", "description": "Secret Manager secret is not encrypted with customer managed encryption key\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"name": "81aff71c-4748-4f0c-ad23-0d922e5f32f2"}]}}}, {"policy": "ecc-gcp-292", "resource_type": "Cloud Spanner", "description": "Cloud Spanner backup is configured with privileged service account\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"name": "2c795259-c476-467d-ba92-675ff651b0c5"}]}}}, {"policy": "ecc-gcp-293", "resource_type": "Cloud Spanner", "description": "Cloud Spanner backup is created with an expiration date of 29 days or less\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"name": "e939c47e-9b4a-4f97-9dbd-94d00f5758cf"}]}}}, {"policy": "ecc-gcp-294", "resource_type": "Cloud Spanner", "description": "Cloud Spanner database is configured with privileged service account\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"name": "dbabc9e8-470c-442f-ab28-4362534125ec"}]}}}, {"policy": "ecc-gcp-295", "resource_type": "Cloud Spanner", "description": "Cloud Spanner instance is configured with privileged service accounts\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"name": "0fa309da-5505-49fb-99bb-652ac6d80de4"}]}}}, {"policy": "ecc-gcp-298", "resource_type": "Cloud Storage", "description": "GCP storage bucket is not configured with default Event-Based Hold\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"selfLink": "a49c747f-c9e8-4c1e-8ec2-a59241e56b51"}]}}}, {"policy": "ecc-gcp-299", "resource_type": "Cloud Storage", "description": "There is no sufficient retention period configured for Google Cloud Storage objects\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "66cf0a20-1baf-4ebb-a77a-273fc1469f35"}]}}}, {"policy": "ecc-gcp-300", "resource_type": "Cloud Memorystore", "description": "GCP Memorystore for Redis has AUTH disabled\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"name": "84720e99-05f3-4df1-a119-ce13fe38b3d5"}]}}}, {"policy": "ecc-gcp-302", "resource_type": "Cloud Armor", "description": "Cloud Armor does not prevent message lookup in Log4j2\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{}]}}}, {"policy": "ecc-gcp-303", "resource_type": "Dataflow", "description": "GCP Cloud Dataflow job has public IP\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"projectId": "e5b60f17-ead6-4ddf-9de1-3285cba8879b", "id": "31c0e8a4-eefd-40aa-8fc7-29680b8af3fc"}]}}}, {"policy": "ecc-gcp-304", "resource_type": "Vertex AI Workbench", "description": "GCP Vertex AI Workbench has public IPs\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"name": "365bd848-59b2-4be1-aa8b-f5fa7c62ec26"}]}}}, {"policy": "ecc-gcp-305", "resource_type": "Dataproc", "description": "GCP Dataproc cluster is anonymously or publicly accessible\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"clusterName": "7f15204e-ace8-4991-a760-35b019f9e609", "projectId": "258fe777-7044-4ee2-89cb-281707ee0f9b"}]}}}, {"policy": "ecc-gcp-306", "resource_type": "Pub/Sub", "description": "GCP Pub/Sub Topic is anonymously or publicly accessible\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"name": "793ca9c4-3a5c-4444-bd0b-30ed55060daa"}]}}}, {"policy": "ecc-gcp-307", "resource_type": "Dataproc", "description": "GCP Artifact Registry repository is anonymously or publicly accessible\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"name": "8ad44856-a8b5-45fb-a2b3-966e288166be"}]}}}, {"policy": "ecc-gcp-309", "resource_type": "Dataproc", "description": "Google Cloud Dataproc cluster has a public IP\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"clusterName": "af5f4c93-50ad-4f20-b10e-b6addbeb9d69", "projectId": "740a7dff-cc96-4eb0-9b12-06a3e2a0508c"}]}}}, {"policy": "ecc-gcp-310", "resource_type": "Cloud Memorystore", "description": "GCP Memorystore for Redis has in-transit encryption disabled\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"name": "043cdb06-203f-40ef-b976-e1bac6a74b1f"}]}}}, {"policy": "ecc-gcp-311", "resource_type": "Cloud Data Fusion", "description": "Datafusion does not have stack driver logging enabled\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"name": "3947fe84-045a-4f75-91f4-47aadde0b141"}]}}}, {"policy": "ecc-gcp-312", "resource_type": "Cloud Data Fusion", "description": "Datafusion does not have stack driver monitoring enabled\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"name": "d0026a73-7c0b-4d55-9b4b-c881a85475bb"}]}}}, {"policy": "ecc-gcp-313", "resource_type": "Access Transparency", "description": "'Access Transparency' is not Enabled\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"projectId": "86d79c71-85c5-46c3-b3a9-d8187d3cb9b4"}]}}}, {"policy": "ecc-gcp-314", "resource_type": "Access Approval", "description": "'Access Approval' is not Enabled\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"projectId": "b096be14-b653-467b-8030-98d17c1b42ca"}]}}}, {"policy": "ecc-gcp-315", "resource_type": "Cloud SQL", "description": "The 'cloudsql.enable_pgaudit' Database Flag for Cloud Sql Postgresql Instance is not Set to 'on' For Centralized Logging\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "b87f92ec-82f1-46e2-bf4a-2d899196998c"}]}}}, {"policy": "ecc-gcp-316", "resource_type": "Cloud Asset Inventory", "description": "Cloud Asset Inventory is not Enabled\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"name": "ad5b5df9-77cd-45c7-8991-7549110af482"}]}}}, {"policy": "ecc-gcp-317", "resource_type": "Cloud Bigtable", "description": "Cloud Bigtable instance cluster encryption not using Customer-Managed Keys\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"name": "2bd281f4-9aba-43c9-861b-baa4b64aa514"}]}}}, {"policy": "ecc-gcp-318", "resource_type": "Cloud Functions", "description": "Cloud function runtime is outdated\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"name": "ed70f364-15e4-4a28-8ce5-ec69c8786bbc"}]}}}, {"policy": "ecc-gcp-323", "resource_type": "Cloud IAM", "description": "Service account has privileges escalation - Impersonation (Project scope)\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"member": "9ed93735-a136-4e96-9369-48c46db1e159", "roles": "1d28d162-129a-4fec-a46c-1e4846eee626"}]}}}, {"policy": "ecc-gcp-324", "resource_type": "Cloud IAM", "description": "User has privileges escalation - Impersonation (Project scope)\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"member": "8083ee8d-9700-44cd-9cc9-08ec58dec4cf", "roles": "0d84084e-ba0c-413d-ad9b-8808ace08310"}]}}}, {"policy": "ecc-gcp-334", "resource_type": "Cloud IAM", "description": "User has privileges escalation - Impersonation (Resourse scope)\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"c7n:service-account.name": "9e81a89a-9ce6-4fb8-a57e-4d1c5559b615", "members": "768929ee-4de6-44cf-9925-d7afeb84f553"}]}}}, {"policy": "ecc-gcp-335", "resource_type": "Cloud IAM", "description": "Service account has privileges escalation - Impersonation (Resourse scope)\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"c7n:service-account.name": "30350958-94f1-4357-aeeb-67123f9f8bba", "members": "6f256d99-5054-491b-a2e7-1318baffc5cc"}]}}}, {"policy": "ecc-gcp-337", "resource_type": "Virtual Private Cloud", "description": "Firewall rules allow internet traffic to VNC-Server port (5900)\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "5059c8cc-39e5-499c-9f31-783e59471748"}]}}}, {"policy": "ecc-gcp-340", "resource_type": "Cloud Armor", "description": "Cloud Armor has packet size bypass\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{}]}}}, {"policy": "ecc-gcp-342", "resource_type": "Virtual Private Cloud", "description": "Firewall rules allow internet traffic to Hadoop/HDFS port (8020)\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "90103b34-c64c-4b25-b67f-81fd90530ce7"}]}}}, {"policy": "ecc-gcp-346", "resource_type": "Compute Engine", "description": "Asset does not contain a network tag\n", "severity": "Medium", "regions_data": {"multiregion": {"resources": [{"selfLink": "77990d7b-0c45-4b87-8583-3db6582d94ea"}]}}}, {"policy": "ecc-gcp-347", "resource_type": "Cloud Spanner", "description": "Cloud Spanner Instance is deployed without multi-region configuration\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"name": "578a70f5-3ce5-4ab2-a953-0c3544f8e4ed"}]}}}, {"policy": "ecc-gcp-385", "resource_type": "Compute Engine", "description": "Instance template is configured with default network\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "b0b7685c-06f0-4e26-90c7-fe894f21cd31"}]}}}, {"policy": "ecc-gcp-386", "resource_type": "Compute Engine", "description": "Compute Instance Templates have not Confidential Computing enabled\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "3042ad6b-9a12-4754-8b5f-68be91047de0"}]}}}, {"policy": "ecc-gcp-400", "resource_type": "Cloud SQL", "description": "The 'password_history' database flags for Cloud SQL MySQL instance are not configured\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "371288f7-3f83-486c-89ae-a99b81da885b"}]}}}, {"policy": "ecc-gcp-401", "resource_type": "Cloud SQL", "description": "The 'password_reuse_interval' database flags for Cloud SQL MySQL instance are not configured\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "b76f7608-10de-4356-a333-f1d60362e18d"}]}}}, {"policy": "ecc-gcp-409", "resource_type": "Compute Engine", "description": "OS Patch Deployment is not configured with a recurring schedule\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "6e5870ce-bf38-4287-be79-1dd0a16b8d48"}]}}}, {"policy": "ecc-gcp-412", "resource_type": "Cloud Data Fusion", "description": "Datafusion instance has public ip\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"name": "c67027f0-c96d-47b4-9e74-3e600d333e79"}]}}}, {"policy": "ecc-gcp-415", "resource_type": "Google Kubernetes Engine", "description": "GKE Sandbox is not enabled\n", "severity": "High", "regions_data": {"multiregion": {"resources": [{"selfLink": "c0004371-f871-42e2-b2f2-9b733b700341"}]}}}, {"policy": "ecc-gcp-432", "resource_type": "Google Kubernetes Engine", "description": "Clusters are created without Private Endpoint Enabled and Public Access Disabled\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "b86134ab-7dd4-44aa-8a5c-a538f0aa68b1"}]}}}, {"policy": "ecc-gcp-434", "resource_type": "Google Kubernetes Engine", "description": "Customer-Managed Encryption Keys (CMEK) is not Enabled for GKE Persistent Disks (PD) (FOR NODE BOOT DISKS)\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"selfLink": "d0f6f1c0-e056-4bbb-9b53-90eccd89716f"}]}}}, {"policy": "ecc-gcp-436", "resource_type": "Cloud SQL", "description": "Instance IP assignment is not set to private\n", "severity": "Low", "regions_data": {"multiregion": {"resources": [{"name": "ada96e3a-5397-4389-b78e-80bc18820ab4"}]}}}, {"policy": "ecc-gcp-438", "resource_type": "Google Kubernetes Engine", "description": "Shielded GKE Nodes are disabled\n", "severity": "Info", "regions_data": {"multiregion": {"resources": [{"selfLink": "9d021183-40a5-44a0-bbd5-d93abe8ec293"}]}}}] \ No newline at end of file diff --git a/tests/tests_metrics/mock_files/old_metrics.json b/tests/tests_metrics/mock_files/old_metrics.json new file mode 100644 index 000000000..ae4111370 --- /dev/null +++ b/tests/tests_metrics/mock_files/old_metrics.json @@ -0,0 +1,1795 @@ +{ + "customer": "EPAM Systems", + "from": "2023-09-25", + "to": "2023-10-01T00:00:00+00:00", + "tenant_display_name": "test", + "compliance": { + "aws": [ + { + "regions_data": [ + { + "region": "eu-central-1", + "standards_data": [ + { + "name": "COBIT 19", + "value": 20.0 + }, + { + "name": "PCI DSS v3.2.1", + "value": 50.68 + }, + { + "name": "CIS Controls v7", + "value": 23.33 + }, + { + "name": "FedRAMP NIST_800_53_Rev5", + "value": 24.2 + }, + { + "name": "NIST 800-171 Rev2", + "value": 24.08 + }, + { + "name": "NYMITY", + "value": 0 + }, + { + "name": "HITRUST", + "value": 26.42 + }, + { + "name": "CSA Cloud Controls Matrix v4", + "value": 16.07 + }, + { + "name": "HIPAA", + "value": 14.04 + }, + { + "name": "ISO 27701_2019", + "value": 34.4 + }, + { + "name": "SOX 2002 (2017 Trust Services Criteria)", + "value": 44.85 + }, + { + "name": "NIST 800_53 Rev5", + "value": 17.16 + }, + { + "name": "CMMC v2.0", + "value": 27.26 + }, + { + "name": "GDPR 2016_679", + "value": 45.05 + }, + { + "name": "CJIS", + "value": 17.59 + }, + { + "name": "ISO 27018_2019", + "value": 50.64 + }, + { + "name": "FFIEC CAT", + "value": 13.84 + }, + { + "name": "ISO 27017_2015", + "value": 34.4 + }, + { + "name": "ISO 27001_2013", + "value": 34.4 + }, + { + "name": "NERC-CIP", + "value": 23.0 + }, + { + "name": "DORA", + "value": 19.53 + }, + { + "name": "Azure Security Benchmark v3", + "value": 49.75 + }, + { + "name": "NIST CSF v1.1", + "value": 19.86 + }, + { + "name": "ISO 27002_2022", + "value": 39.77 + }, + { + "name": "CIS Controls v8", + "value": 22.58 + }, + { + "name": "ISO 27002_2013", + "value": 34.4 + }, + { + "name": "Cyber Essentials v2.2", + "value": 17.16 + }, + { + "name": "CIS AWS Foundations Benchmark v1.5.0", + "value": 26.65 + }, + { + "name": "CIS AWS Foundations Benchmark v1.4.0", + "value": 21.65 + }, + { + "name": "CIS Oracle Database 19 Benchmark v1.0.0", + "value": 5.73 + } + ] + } + ], + "average_data": [], + "last_scan_date": "2023-08-14T13:28:00.481518Z", + "activated_regions": [ + "eu-west-1", + "eu-central-1" + ], + "tenant_name": "ONSHA", + "account_id": "267745785661" + } + ], + "azure": [], + "google": [] + }, + "overview": { + "aws": [ + { + "total_scans": 10, + "failed_scans": 2, + "succeeded_scans": 8, + "resources_violated": 162, + "regions_data": { + "multiregion": { + "severity_data": {}, + "resource_types_data": {} + }, + "eu-central-1": { + "resource_types_data": { + "AWS Account": 4, + "Amazon Elastic Block Store": 5, + "Amazon CloudWatch": 15, + "Amazon API Gateway": 20, + "AWS Glue": 2, + "Amazon Virtual Private Cloud": 14, + "Amazon Simple Notification Service": 1, + "Amazon S3": 25, + "AWS Identity and Access Management": 32, + "Amazon EC2": 23, + "AWS Key Management Service": 1, + "Amazon DynamoDB": 5, + "AWS X-Ray": 1, + "AWS Lambda": 12, + "AWS Config": 1, + "Amazon Route 53": 1 + } + } + }, + "last_scan_date": "2023-08-14T13:28:00.481518Z", + "activated_regions": [ + "eu-west-1", + "eu-central-1" + ], + "tenant_name": "ONSHA", + "account_id": "267745785661" + } + ], + "azure": [], + "google": [] + }, + "resources": { + "aws": [ + { + "data": [ + { + "policy": "ecc-aws-139-iam_access_analyzer_is_enabled", + "resource_type": "AWS Account", + "description": "IAM Access analyzer is not enabled for all regions", + "severity": "Low", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 1 + } + } + }, + { + "policy": "ecc-aws-022-ebs_volumes_too_old_snapshots", + "resource_type": "Amazon Elastic Block Store", + "description": "EBS Snapshots older than 30 days", + "severity": "Info", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 1 + } + } + }, + { + "policy": "ecc-aws-488-cloudwatch_log_group_retention_period_check", + "resource_type": "Amazon CloudWatch", + "description": "CloudWatch Log Group does not have retention period set correctly", + "severity": "Low", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 15 + } + } + }, + { + "policy": "ecc-aws-138-eliminate_use_root_user_for_administrative_and_daily_tasks", + "resource_type": "AWS Account", + "description": "Root user is used for administrative and daily tasks", + "severity": "High", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 1 + } + } + }, + { + "policy": "ecc-aws-116-rest_api_gateway_is_set_to_private", + "resource_type": "Amazon API Gateway", + "description": "API endpoint type in the API gateway is not private and exposed to the public internet", + "severity": "High", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 2 + } + } + }, + { + "policy": "ecc-aws-252-glue_data_catalog_encrypted_at_rest", + "resource_type": "AWS Glue", + "description": "Data catalog encryption is not enabled for AWS Glue", + "severity": "High", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 1 + } + } + }, + { + "policy": "ecc-aws-101-vpc-subnets_automatic_public_ip_assignment", + "resource_type": "Amazon Virtual Private Cloud", + "description": "VPC subnets automatic public ip assignment is enabled", + "severity": "Medium", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 3 + } + } + }, + { + "policy": "ecc-aws-507-sns_topic_message_delivery_notification_enabled", + "resource_type": "Amazon Simple Notification Service", + "description": "Amazon SNS topic message delivery notification is disabled", + "severity": "Medium", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 1 + } + } + }, + { + "policy": "ecc-aws-391-vpc_without_tag_information", + "resource_type": "Amazon Virtual Private Cloud", + "description": "VPC without tag information", + "severity": "Info", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 1 + } + } + }, + { + "policy": "ecc-aws-148-logging_for_s3_enabled", + "resource_type": "Amazon S3", + "description": "Logging for S3 bucket is disabled", + "severity": "High", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 25 + } + } + }, + { + "policy": "ecc-aws-415-iam_user_without_tag_information", + "resource_type": "AWS Identity and Access Management", + "description": "IAM User without tag information", + "severity": "Info", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 1 + } + } + }, + { + "policy": "ecc-aws-416-iam_role_without_tag_information", + "resource_type": "AWS Identity and Access Management", + "description": "IAM Role without tag information", + "severity": "Info", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 30 + } + } + }, + { + "policy": "ecc-aws-224-ec2_instance_imdsv2_enabled", + "resource_type": "Amazon EC2", + "description": "EC2 instances do not use IMDSv2", + "severity": "High", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 2 + } + } + }, + { + "policy": "ecc-aws-121-restrict_outbound_traffic", + "resource_type": "Amazon EC2", + "description": "Outbound traffic is allowed to all ports", + "severity": "Medium", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 6 + } + } + }, + { + "policy": "ecc-aws-326-ebs_volume_encrypted_with_kms_cmk", + "resource_type": "Amazon Elastic Block Store", + "description": "EBS volume not encrypted with KMS CMK", + "severity": "Medium", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 2 + } + } + }, + { + "policy": "ecc-aws-533-key_pair_without_tag_information", + "resource_type": "Amazon EC2", + "description": "Amazon Key pair without tag information", + "severity": "Info", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 2 + } + } + }, + { + "policy": "ecc-aws-051-iam_password_policy_passwd_expires_le_90", + "resource_type": "AWS Account", + "description": "IAM password policy is not configured to expire passwords after 90 days or less", + "severity": "Low", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 1 + } + } + }, + { + "policy": "ecc-aws-003-ensure_vpc_flow_logging_enabled_for_every_vpc", + "resource_type": "Amazon Virtual Private Cloud", + "description": "VPC flow logging is not enabled in all VPCs", + "severity": "High", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 2 + } + } + }, + { + "policy": "ecc-aws-463-bucket_not_dns_compliant", + "resource_type": "Amazon S3", + "description": "S3 bucket is not DNS compliant", + "severity": "Low", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 1 + } + } + }, + { + "policy": "ecc-aws-531-ebs_default_encryption_enabled", + "resource_type": "Amazon Elastic Block Store", + "description": "EBS volume default encryption disabled", + "severity": "Medium", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 1 + } + } + }, + { + "policy": "ecc-aws-378-ebs_without_tag_information", + "resource_type": "Amazon Elastic Block Store", + "description": "EBS volumes without tag information", + "severity": "Info", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 2 + } + } + }, + { + "policy": "ecc-aws-050-iam_password_min_length_ge_14", + "resource_type": "AWS Account", + "description": "Password policy does not require minimum length of 14 characters or greater", + "severity": "Medium", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 1 + } + } + }, + { + "policy": "ecc-aws-117-api_key_is_required_on_method_request", + "resource_type": "Amazon API Gateway", + "description": "API Key is not required on Method Request", + "severity": "Medium", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 12 + } + } + }, + { + "policy": "ecc-aws-047-iam_password_policy_one_lowercase_letter", + "resource_type": "AWS Account", + "description": "Password policy does not require at least one lowercase letter", + "severity": "Low", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 1 + } + } + }, + { + "policy": "ecc-aws-221-sns_kms_encryption_enabled", + "resource_type": "Amazon Simple Notification Service", + "description": "SNS topics are not encrypted at rest using AWS KMS", + "severity": "High", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 1 + } + } + }, + { + "policy": "ecc-aws-423-cloudwatch_log_groups_without_tag_information", + "resource_type": "Amazon CloudWatch", + "description": "Amazon Log group without tag information", + "severity": "Info", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 15 + } + } + }, + { + "policy": "ecc-aws-061-kms_key_rotation_is_enabled", + "resource_type": "AWS Key Management Service", + "description": "Rotation for symmetric customer-created CMKs is not enabled", + "severity": "Low", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 1 + } + } + }, + { + "policy": "ecc-aws-183-dynamodb_tables_pitr_enabled", + "resource_type": "Amazon DynamoDB", + "description": "DynamoDB tables do not have point-in-time recovery enabled", + "severity": "High", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 5 + } + } + }, + { + "policy": "ecc-aws-329-unused_ec2_access_keys", + "resource_type": "Amazon EC2", + "description": "Unused key pairs exist", + "severity": "Medium", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 2 + } + } + }, + { + "policy": "ecc-aws-057-ensure_iam_instance_roles_are_used_for_resource_access_from_instance", + "resource_type": "Amazon EC2", + "description": "IAM instance roles are not used for AWS resource access from instances", + "severity": "Medium", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 2 + } + } + }, + { + "policy": "ecc-aws-019-iam_password_policy_password_reuse", + "resource_type": "AWS Account", + "description": "IAM password policy does not prevent password reuse", + "severity": "High", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 1 + } + } + }, + { + "policy": "ecc-aws-004-bucket_policy_allows_https_requests", + "resource_type": "Amazon S3", + "description": "S3 Bucket Policy allows HTTP requests", + "severity": "High", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 25 + } + } + }, + { + "policy": "ecc-aws-420-kms_key_without_tag_information", + "resource_type": "AWS Key Management Service", + "description": "Customer manages key without tag information", + "severity": "Info", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 1 + } + } + }, + { + "policy": "ecc-aws-386-security_group_without_tag_information", + "resource_type": "Amazon EC2", + "description": "Security group without tag information", + "severity": "Info", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 6 + } + } + }, + { + "policy": "ecc-aws-516-s3_event_notifications_enabled", + "resource_type": "Amazon S3", + "description": "S3 buckets should have event notifications enabled", + "severity": "Low", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 25 + } + } + }, + { + "policy": "ecc-aws-046-ensure_no_root_account_access_key_exists", + "resource_type": "AWS Account", + "description": "Root user account access key exists", + "severity": "High", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 1 + } + } + }, + { + "policy": "ecc-aws-382-internet_gateway_without_tag_information", + "resource_type": "Amazon Virtual Private Cloud", + "description": "Amazon Internet Gateway without tag information", + "severity": "Info", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 1 + } + } + }, + { + "policy": "ecc-aws-222-ec2_instance_managed_by_systems_manager", + "resource_type": "Amazon EC2", + "description": "EC2 instances are not managed by AWS Systems Manager", + "severity": "Medium", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 2 + } + } + }, + { + "policy": "ecc-aws-381-eni_without_tag_information", + "resource_type": "Amazon EC2", + "description": "ENI without tag information", + "severity": "Info", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 6 + } + } + }, + { + "policy": "ecc-aws-352-sns_encrypted_with_kms_cmk", + "resource_type": "Amazon Simple Notification Service", + "description": "SNS topics are not encrypted at rest using KMS CMK", + "severity": "Medium", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 1 + } + } + }, + { + "policy": "ecc-aws-489-ec2_instance_detailed_monitoring_enabled", + "resource_type": "Amazon EC2", + "description": "EC2 instances detailed monitoring disabled", + "severity": "Low", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 2 + } + } + }, + { + "policy": "ecc-aws-431-sns_without_tag_information", + "resource_type": "Amazon Simple Notification Service", + "description": "Amazon SNS topic without tag information", + "severity": "Info", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 1 + } + } + }, + { + "policy": "ecc-aws-250-rest_api_gateway_cache_enabled", + "resource_type": "Amazon API Gateway", + "description": "Cache is not enabled for api gateway", + "severity": "Low", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 2 + } + } + }, + { + "policy": "ecc-aws-027-prevent_0-65535_ingress_and_all", + "resource_type": "Amazon EC2", + "description": "Security groups do not prevent all incoming traffic from 0-65535", + "severity": "High", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 2 + } + } + }, + { + "policy": "ecc-aws-122-dynamodb_is_encrypted_using_managed_cmk", + "resource_type": "Amazon DynamoDB", + "description": "DynamoDB is not encrypted using KMS CMK", + "severity": "High", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 5 + } + } + }, + { + "policy": "ecc-aws-177-api_gateway_rest_api_stages_ssl_certificates_configured", + "resource_type": "Amazon API Gateway", + "description": "API Gateway REST API stages are not configured to use SSL certificates for backend authentication", + "severity": "High", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 2 + } + } + }, + { + "policy": "ecc-aws-285-xray-encrypted_with_kms_cmk", + "resource_type": "AWS X-Ray", + "description": "AWS X-Ray is not encrypted using KMS CMK", + "severity": "Medium", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 1 + } + } + }, + { + "policy": "ecc-aws-140-only_one_active_access_key_available_for_any_single_iam_user", + "resource_type": "AWS Identity and Access Management", + "description": "More than one active access key is available for a single IAM user", + "severity": "High", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 1 + } + } + }, + { + "policy": "ecc-aws-085-lambda_in_vpc", + "resource_type": "AWS Lambda", + "description": "Lambda functions are not in a VPC", + "severity": "Low", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 2 + } + } + }, + { + "policy": "ecc-aws-186-ec2_instance_no_public_ip", + "resource_type": "Amazon EC2", + "description": "EC2 instances have public IP address", + "severity": "High", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 2 + } + } + }, + { + "policy": "ecc-aws-421-lambda_functions_without_tag_information", + "resource_type": "AWS Lambda", + "description": "Lambda functions without tag information", + "severity": "Info", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 2 + } + } + }, + { + "policy": "ecc-aws-044-s3_buckets_without_tags", + "resource_type": "Amazon S3", + "description": "S3 Buckets without tags", + "severity": "Info", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 24 + } + } + }, + { + "policy": "ecc-aws-249-rest_api_gateway_contend_encoding_enabled", + "resource_type": "Amazon API Gateway", + "description": "Content encoding is not enabled for API Gateway", + "severity": "Low", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 2 + } + } + }, + { + "policy": "ecc-aws-001-ensure_mfa_is_enabled_for_all_iam_users_with_console_password", + "resource_type": "AWS Identity and Access Management", + "description": "Multi-factor authentication (MFA) is not enabled for all IAM users that have console password", + "severity": "High", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 1 + } + } + }, + { + "policy": "ecc-aws-517-s3_bucket_acl_prohibited", + "resource_type": "Amazon S3", + "description": "S3 access control lists (ACLs) are used to manage user access to buckets", + "severity": "Medium", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 16 + } + } + }, + { + "policy": "ecc-aws-112-s3_bucket_versioning_mfa_delete_enabled", + "resource_type": "Amazon S3", + "description": "S3 bucket versioning MFA delete is disabled", + "severity": "Medium", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 25 + } + } + }, + { + "policy": "ecc-aws-178-rest_api_aws_x_ray_enabled", + "resource_type": "Amazon API Gateway", + "description": "API Gateway REST API stages do not have AWS X-Ray tracing enabled", + "severity": "Low", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 2 + } + } + }, + { + "policy": "ecc-aws-479-cloudwatch_log_group_encrypted_with_kms_cmk", + "resource_type": "Amazon CloudWatch", + "description": "AWS CloudWatch log groups are not encrypted with KMS CMK", + "severity": "Low", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 15 + } + } + }, + { + "policy": "ecc-aws-049-iam_password_policy_one_number", + "resource_type": "AWS Account", + "description": "Password policy does not require at least one number", + "severity": "Low", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 1 + } + } + }, + { + "policy": "ecc-aws-002-ensure_access_keys_are_rotated_every_90_days", + "resource_type": "AWS Identity and Access Management", + "description": "Access keys are not rotated every 90 days or less", + "severity": "High", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 1 + } + } + }, + { + "policy": "ecc-aws-385-route_table_without_tag_information", + "resource_type": "Amazon Virtual Private Cloud", + "description": "Amazon Route table without tag information", + "severity": "Info", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 2 + } + } + }, + { + "policy": "ecc-aws-458-lambda_functions_enhanced_monitoring_enabled", + "resource_type": "AWS Lambda", + "description": "Enhanced Monitoring for Lambda Functions disabled", + "severity": "Low", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 6 + } + } + }, + { + "policy": "ecc-aws-017-credentials_unused_for_45_days", + "resource_type": "AWS Identity and Access Management", + "description": "Credentials unused for 45 days or more are not disabled", + "severity": "Medium", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 1 + } + } + }, + { + "policy": "ecc-aws-335-lambda_active_tracing_enabled", + "resource_type": "AWS Lambda", + "description": "Lambda has active tracing disabled", + "severity": "Low", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 6 + } + } + }, + { + "policy": "ecc-aws-142-s3_buckets_configured_with_block_public_access", + "resource_type": "Amazon S3", + "description": "S3 Buckets are not configured with 'Block public access' bucket settings", + "severity": "High", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 5 + } + } + }, + { + "policy": "ecc-aws-045-iam_password_policy_one_uppercase_letter", + "resource_type": "AWS Account", + "description": "Password policy does not require at least one uppercase letter", + "severity": "Low", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 1 + } + } + }, + { + "policy": "ecc-aws-048-iam_password_policy_one_symbol", + "resource_type": "AWS Account", + "description": "Password policy does not require at least one symbol", + "severity": "Low", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 1 + } + } + }, + { + "policy": "ecc-aws-379-ebs_snapshot_without_tag_information", + "resource_type": "Amazon Elastic Block Store", + "description": "EBS snapshot without tag information", + "severity": "Info", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 1 + } + } + }, + { + "policy": "ecc-aws-337-lambda_variables_encrypted_with_kms_cmk", + "resource_type": "AWS Lambda", + "description": "Lambda environment variables not encrypted with KMS CMK", + "severity": "Medium", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 6 + } + } + }, + { + "policy": "ecc-aws-248-rest_api_gateway_is_protected_by_waf", + "resource_type": "Amazon API Gateway", + "description": "Api gateway is not protected by WAF", + "severity": "High", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 2 + } + } + }, + { + "policy": "ecc-aws-025-instance_without_termination_protection", + "resource_type": "Amazon EC2", + "description": "Instances without termination protection", + "severity": "Medium", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 2 + } + } + }, + { + "policy": "ecc-aws-461-lambda_latest_runtime_environment_version", + "resource_type": "AWS Lambda", + "description": "Lambda functions not are not using latest runtime environment versions", + "severity": "Medium", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 2 + } + } + }, + { + "policy": "ecc-aws-387-subnet_without_tag_information", + "resource_type": "Amazon Virtual Private Cloud", + "description": "Amazon Subnet without tag information", + "severity": "Info", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 3 + } + } + }, + { + "policy": "ecc-aws-059-config_enabled_all_regions", + "resource_type": "AWS Config", + "description": "AWS Config is not enabled in all regions", + "severity": "Medium", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 1 + } + } + }, + { + "policy": "ecc-aws-043-s3_bucket_lifecycle", + "resource_type": "Amazon S3", + "description": "S3 Bucket life cycle is not configured", + "severity": "Info", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 25 + } + } + }, + { + "policy": "ecc-aws-460-lambda_environment_variables_encrypted_in_transit", + "resource_type": "AWS Lambda", + "description": "Lambda environment variables are not encrypted in transit", + "severity": "Medium", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 4 + } + } + }, + { + "policy": "ecc-aws-018-iam_users_receive_permissions_only_through_groups", + "resource_type": "AWS Identity and Access Management", + "description": "IAM Users receive permissions not only through groups", + "severity": "Info", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 1 + } + } + }, + { + "policy": "ecc-aws-064-default_security_group_every_vpc_restricts_all_traffic", + "resource_type": "Amazon EC2", + "description": "VPC default security group does not restrict all traffic", + "severity": "Medium", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 2 + } + } + }, + { + "policy": "ecc-aws-147-ebs_volume_without_encrypt", + "resource_type": "Amazon Elastic Block Store", + "description": "EBS volume encryption is disabled", + "severity": "High", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 2 + } + } + }, + { + "policy": "ecc-aws-187-ec2_service_use_vpc_endpoints", + "resource_type": "Amazon Virtual Private Cloud", + "description": "EC2 is not configured to use VPC endpoints that are created for the EC2 service", + "severity": "Medium", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 2 + } + } + }, + { + "policy": "ecc-aws-365-glue_connection_passwords_encrypted", + "resource_type": "AWS Glue", + "description": "Glue connection password is not encrypted", + "severity": "High", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 1 + } + } + }, + { + "policy": "ecc-aws-088-s3_bucket_cross_region_replication_enabled", + "resource_type": "Amazon S3", + "description": "S3 bucket cross-region replication is disabled", + "severity": "Medium", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 25 + } + } + }, + { + "policy": "ecc-aws-133-guardduty_service_is_enabled", + "resource_type": "AWS Account", + "description": "Amazon GuardDuty service is not enabled", + "severity": "Medium", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 1 + } + } + }, + { + "policy": "ecc-aws-327-ebs_snapshot_encrypted", + "resource_type": "Amazon Elastic Block Store", + "description": "EBS snapshot encryption is disabled", + "severity": "Medium", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 1 + } + } + }, + { + "policy": "ecc-aws-349-route53_query_logging_enabled", + "resource_type": "Amazon Route 53", + "description": "Route53 query logging not enabled", + "severity": "Medium", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 1 + } + } + }, + { + "policy": "ecc-aws-359-rest_api_gateway_access_logging_enabled", + "resource_type": "Amazon API Gateway", + "description": "API Gateway REST API have access logging disabled", + "severity": "Medium", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 2 + } + } + }, + { + "policy": "ecc-aws-384-network_acl_without_tag_information", + "resource_type": "Amazon Virtual Private Cloud", + "description": "Amazon Network ACLs without tag information", + "severity": "Info", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 2 + } + } + }, + { + "policy": "ecc-aws-042-s3_encrypted_using_kms", + "resource_type": "Amazon S3", + "description": "S3 is not using a KMS key for encryption", + "severity": "High", + "regions_data": { + "eu-central-1": { + "total_violated_resources": 25 + } + } + } + ], + "last_scan_date": "2023-08-14T13:28:00.481518Z", + "activated_regions": [ + "eu-west-1", + "eu-central-1" + ], + "tenant_name": "ONSHA", + "account_id": "267745785661" + } + ], + "azure": [], + "google": [] + }, + "attack_vector": { + "aws": [ + { + "mitre_data": [ + { + "tactic_id": "TA0001", + "tactic": "Initial Access", + "techniques_data": [ + { + "technique_id": "T1189", + "technique": "Drive-by Compromise", + "regions_data": { + "eu-central-1": { + "severity_data": { + "High": 4 + } + } + } + }, + { + "technique_id": "T1190", + "technique": "Exploit Public-Facing Application", + "regions_data": { + "eu-central-1": { + "severity_data": { + "High": 8, + "Medium": 5 + } + } + } + }, + { + "technique_id": "T1078", + "technique": "Valid Accounts", + "regions_data": { + "eu-central-1": { + "severity_data": { + "Medium": 3, + "High": 3 + } + } + } + } + ] + }, + { + "tactic_id": "TA0006", + "tactic": "Credential Access", + "techniques_data": [ + { + "technique_id": "T1040", + "technique": "Network Sniffing", + "regions_data": { + "eu-central-1": { + "severity_data": { + "High": 31, + "Medium": 6 + } + } + } + }, + { + "technique_id": "T1557", + "technique": "Adversary-in-the-Middle", + "regions_data": { + "eu-central-1": { + "severity_data": { + "High": 31, + "Medium": 6 + } + } + } + }, + { + "technique_id": "T1555", + "technique": "Credentials from Password Stores", + "regions_data": { + "eu-central-1": { + "severity_data": { + "Medium": 3, + "Low": 0, + "High": 4 + } + } + } + }, + { + "technique_id": "T1110", + "technique": "Brute Force", + "regions_data": { + "eu-central-1": { + "severity_data": { + "Medium": 3, + "Low": 0, + "High": 8 + } + } + } + }, + { + "technique_id": "T1187", + "technique": "Forced Authentication", + "regions_data": { + "eu-central-1": { + "severity_data": { + "Medium": 3, + "High": 3 + } + } + } + }, + { + "technique_id": "T1212", + "technique": "Exploitation for Credential Access", + "regions_data": { + "eu-central-1": { + "severity_data": { + "High": 2 + } + } + } + }, + { + "technique_id": "T1552", + "technique": "Unsecured Credentials", + "regions_data": { + "eu-central-1": { + "severity_data": { + "High": 28, + "Medium": 8 + } + } + } + } + ] + }, + { + "tactic_id": "TA0007", + "tactic": "Discovery", + "techniques_data": [ + { + "technique_id": "T1040", + "technique": "Network Sniffing", + "regions_data": { + "eu-central-1": { + "severity_data": { + "High": 31, + "Medium": 6 + } + } + } + }, + { + "technique_id": "T1201", + "technique": "Password Policy Discovery", + "regions_data": { + "eu-central-1": { + "severity_data": { + "High": 1 + } + } + } + }, + { + "technique_id": "T1518", + "technique": "Software Discovery", + "regions_data": { + "eu-central-1": { + "severity_data": { + "High": 1 + } + } + } + }, + { + "technique_id": "T1580", + "technique": "Cloud Infrastructure Discovery", + "regions_data": { + "eu-central-1": { + "severity_data": { + "High": 1 + } + } + } + }, + { + "technique_id": "T1619", + "technique": "Cloud Storage Object Discovery", + "regions_data": { + "eu-central-1": { + "severity_data": { + "High": 26 + } + } + } + } + ] + }, + { + "tactic_id": "TA0008", + "tactic": "Lateral Movement", + "techniques_data": [ + { + "technique_id": "T1021", + "technique": "Remote Services", + "regions_data": { + "eu-central-1": { + "severity_data": { + "High": 6, + "Medium": 2 + } + } + } + }, + { + "technique_id": "T1210", + "technique": "Exploitation of Remote Services", + "regions_data": { + "eu-central-1": { + "severity_data": { + "High": 2 + } + } + } + } + ] + }, + { + "tactic_id": "TA0009", + "tactic": "Collection", + "techniques_data": [ + { + "technique_id": "T1557", + "technique": "Adversary-in-the-Middle", + "regions_data": { + "eu-central-1": { + "severity_data": { + "High": 31, + "Medium": 6 + } + } + } + }, + { + "technique_id": "T1213", + "technique": "Data from Information Repositories", + "regions_data": { + "eu-central-1": { + "severity_data": { + "High": 9, + "Medium": 4 + } + } + } + }, + { + "technique_id": "T1530", + "technique": "Data from Cloud Storage", + "regions_data": { + "eu-central-1": { + "severity_data": { + "High": 31 + } + } + } + } + ] + }, + { + "tactic_id": "TA0040", + "tactic": "Impact", + "techniques_data": [ + { + "technique_id": "T1499", + "technique": "Endpoint Denial of Service", + "regions_data": { + "eu-central-1": { + "severity_data": { + "High": 8, + "Medium": 5 + } + } + } + }, + { + "technique_id": "T1565", + "technique": "Data Manipulation", + "regions_data": { + "eu-central-1": { + "severity_data": { + "High": 40, + "Medium": 3 + } + } + } + }, + { + "technique_id": "T1485", + "technique": "Data Destruction", + "regions_data": { + "eu-central-1": { + "severity_data": { + "High": 7, + "Medium": 30 + } + } + } + }, + { + "technique_id": "T1491", + "technique": "Defacement", + "regions_data": { + "eu-central-1": { + "severity_data": { + "Medium": 1 + } + } + } + }, + { + "technique_id": "T1486", + "technique": "Data Encrypted for Impact", + "regions_data": { + "eu-central-1": { + "severity_data": { + "High": 1 + } + } + } + }, + { + "technique_id": "T1531", + "technique": "Account Access Removal", + "regions_data": { + "eu-central-1": { + "severity_data": { + "High": 1 + } + } + } + }, + { + "technique_id": "T1496", + "technique": "Resource Hijacking", + "regions_data": { + "eu-central-1": { + "severity_data": { + "Low": 2 + } + } + } + }, + { + "technique_id": "T1498", + "technique": "Network Denial of Service", + "regions_data": { + "eu-central-1": { + "severity_data": { + "High": 4, + "Medium": 0 + } + } + } + } + ] + }, + { + "tactic_id": "TA0003", + "tactic": "Persistence", + "techniques_data": [ + { + "technique_id": "T1098", + "technique": "Account Manipulation", + "regions_data": { + "eu-central-1": { + "severity_data": { + "Medium": 3, + "High": 3 + } + } + } + }, + { + "technique_id": "T1136", + "technique": "Create Account", + "regions_data": { + "eu-central-1": { + "severity_data": { + "Medium": 3, + "High": 3 + } + } + } + }, + { + "technique_id": "T1078", + "technique": "Valid Accounts", + "regions_data": { + "eu-central-1": { + "severity_data": { + "Medium": 3, + "High": 3 + } + } + } + } + ] + }, + { + "tactic_id": "TA0004", + "tactic": "Privilege Escalation", + "techniques_data": [ + { + "technique_id": "T1078", + "technique": "Valid Accounts", + "regions_data": { + "eu-central-1": { + "severity_data": { + "Medium": 3, + "High": 3 + } + } + } + } + ] + }, + { + "tactic_id": "TA0010", + "tactic": "Exfiltration", + "techniques_data": [ + { + "technique_id": "T1020", + "technique": "Automated Exfiltration", + "regions_data": { + "eu-central-1": { + "severity_data": { + "High": 27, + "Medium": 4 + } + } + } + } + ] + }, + { + "tactic_id": "TA0002", + "tactic": "Execution", + "techniques_data": [ + { + "technique_id": "T1648", + "technique": "Serverless Execution", + "regions_data": { + "eu-central-1": { + "severity_data": { + "High": 1 + } + } + } + }, + { + "technique_id": "T1203", + "technique": "Exploitation for Client Execution", + "regions_data": { + "eu-central-1": { + "severity_data": { + "Medium": 2 + } + } + } + } + ] + }, + { + "tactic_id": "TA0005", + "tactic": "Defense Evasion", + "techniques_data": [ + { + "technique_id": "T1578", + "technique": "Modify Cloud Compute Infrastructure", + "regions_data": { + "eu-central-1": { + "severity_data": { + "High": 1 + } + } + } + }, + { + "technique_id": "T1535", + "technique": "Unused/Unsupported Cloud Regions", + "regions_data": { + "eu-central-1": { + "severity_data": { + "High": 1 + } + } + } + }, + { + "technique_id": "T1078", + "technique": "Valid Accounts", + "regions_data": { + "eu-central-1": { + "severity_data": { + "High": 1 + } + } + } + } + ] + } + ], + "last_scan_date": "2023-08-14T13:28:00.481518Z", + "activated_regions": [ + "eu-west-1", + "eu-central-1" + ], + "tenant_name": "ONSHA", + "account_id": "267745785661" + } + ], + "azure": [], + "google": [] + }, + "finops": { + "aws": [ + { + "service_data": [ + { + "service_section": "Storage", + "rules_data": [ + { + "rule": "EBS Snapshots older than 30 days", + "service": "Amazon Elastic Block Store", + "category": "Unutilized resources", + "severity": "Info", + "resource_type": "Amazon Elastic Block Store", + "regions_data": { + "eu-central-1": { + "total_violated_resources": { + "value": 1, + "diff": null + } + } + } + }, + { + "rule": "S3 Bucket life cycle is not configured", + "service": "Amazon S3", + "category": "Lifecycle management", + "severity": "Info", + "resource_type": "Amazon S3", + "regions_data": { + "multiregion": { + "total_violated_resources": { + "value": 25, + "diff": null + } + } + } + } + ] + }, + { + "service_section": "Logging and Monitoring", + "rules_data": [ + { + "rule": "CloudWatch Log Group does not have retention period set correctly", + "service": "Amazon CloudWatch", + "category": "Lifecycle management", + "severity": "Low", + "resource_type": "Amazon CloudWatch", + "regions_data": { + "eu-central-1": { + "total_violated_resources": { + "value": 15, + "diff": null + } + } + } + } + ] + }, + { + "service_section": "Networking & Content Delivery", + "rules_data": [ + { + "rule": "Cache is not enabled for api gateway", + "service": "Amazon API Gateway", + "category": "Other cost optimization checks", + "severity": "Low", + "resource_type": "Amazon API Gateway", + "regions_data": { + "eu-central-1": { + "total_violated_resources": { + "value": 2, + "diff": null + } + } + } + } + ] + } + ], + "last_scan_date": "2023-08-14T13:28:00.481518Z", + "activated_regions": [ + "eu-west-1", + "eu-central-1" + ], + "tenant_name": "ONSHA", + "account_id": "267745785661" + } + ], + "azure": [], + "google": [] + } +} \ No newline at end of file diff --git a/tests/tests_metrics/mock_files/reports/raw/EPAM Systems/AWS/AWS-1234567890123/latest/0.json b/tests/tests_metrics/mock_files/reports/raw/EPAM Systems/AWS/AWS-1234567890123/latest/0.json new file mode 100644 index 000000000..de16a6983 --- /dev/null +++ b/tests/tests_metrics/mock_files/reports/raw/EPAM Systems/AWS/AWS-1234567890123/latest/0.json @@ -0,0 +1,10565 @@ +[ + { + "p": "ecc-aws-003", + "l": "eu-west-1", + "r": [ + { + "VpcId": "cd09e5a7-4e9d-43a3-bf0c-501ec2c86350", + "OwnerId": "ca97d147-2529-4471-bbac-71dc7aa0237f" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-003", + "l": "us-west-1", + "r": [ + { + "VpcId": "cd09e5a7-4e9d-43a3-bf0c-501ec2c86350", + "OwnerId": "ca97d147-2529-4471-bbac-71dc7aa0237f" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-004", + "l": "multiregion", + "r": [ + { + "Name": "1793de02-f3ce-4f8a-afed-587b5461027c" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-005", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "6018766b-090e-4dd6-a1af-ead984e34921" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-005", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "6018766b-090e-4dd6-a1af-ead984e34921" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-006", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "a67221b3-48ca-4789-9ebb-a407b836f629" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-006", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "a67221b3-48ca-4789-9ebb-a407b836f629" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-007", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "898ea4b1-f070-4dab-b8a8-768cdfcee359" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-007", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "898ea4b1-f070-4dab-b8a8-768cdfcee359" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-008", + "l": "multiregion", + "r": [ + { + "Arn": "f12a9539-f6f2-4403-b967-cf278accbe19", + "Expiration": "ea8365f4-2883-4c44-aceb-8a61bb1edc02" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-009", + "l": "multiregion", + "r": [ + { + "Arn": "58fd1762-0231-4fa6-882b-f41f62f0c387", + "Expiration": "2c42a283-610e-48a6-b24d-05a8429c4719" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-010", + "l": "eu-west-1", + "r": [ + { + "LoadBalancerArn": "f919f844-ec93-4f47-b20c-b81b391ea16f" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-010", + "l": "us-west-1", + "r": [ + { + "LoadBalancerArn": "f919f844-ec93-4f47-b20c-b81b391ea16f" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-011", + "l": "eu-west-1", + "r": [ + { + "LoadBalancerArn": "d90f56db-0b6f-42ad-b0d1-edc2943ae761" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-011", + "l": "us-west-1", + "r": [ + { + "LoadBalancerArn": "d90f56db-0b6f-42ad-b0d1-edc2943ae761" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-012", + "l": "multiregion", + "r": [ + { + "ARN": "f9318c01-1fc5-4a27-acd3-bfb1e3398268" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-013", + "l": "eu-west-1", + "r": [ + { + "LoadBalancerName": "fa665f29-ddfa-4806-a992-15a504dd8dbf" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-013", + "l": "us-west-1", + "r": [ + { + "LoadBalancerName": "fa665f29-ddfa-4806-a992-15a504dd8dbf" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-014", + "l": "eu-west-1", + "r": [ + { + "LoadBalancerName": "648dd963-be5f-4321-9180-ba6bf113f767" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-014", + "l": "us-west-1", + "r": [ + { + "LoadBalancerName": "648dd963-be5f-4321-9180-ba6bf113f767" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-015", + "l": "multiregion", + "r": [ + { + "account_id": "21fc357d-4ebb-4078-bd90-95f958aa5355", + "account_name": "633d311f-b202-4afc-acba-b1f1ff91753b" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-016", + "l": "multiregion", + "r": [ + { + "field": "6fdeeb16-6d18-49bb-a3c1-92a79926113a" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-017", + "l": "multiregion", + "r": [ + { + "Arn": "59514406-4995-421a-94d6-ab0ecf7c436e" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-018", + "l": "multiregion", + "r": [ + { + "Arn": "af948c20-2f3a-4f0c-87dc-5df5e1b3dc09" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-019", + "l": "multiregion", + "r": [ + { + "field": "940786db-c141-4869-8621-f30e96c7399b" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-020", + "l": "eu-west-1", + "r": [ + { + "InstanceId": "e568ad44-6c39-42f2-b234-b99c8751a054", + "OwnerId": "eff9d7a5-6e06-4fc6-85b7-43e9bbb7c08f" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-020", + "l": "us-west-1", + "r": [ + { + "InstanceId": "e568ad44-6c39-42f2-b234-b99c8751a054", + "OwnerId": "eff9d7a5-6e06-4fc6-85b7-43e9bbb7c08f" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-021", + "l": "eu-west-1", + "r": [ + { + "VolumeId": "fecebf61-2ca8-4cd6-8538-2fc400ab7d4b" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-021", + "l": "us-west-1", + "r": [ + { + "VolumeId": "fecebf61-2ca8-4cd6-8538-2fc400ab7d4b" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-022", + "l": "eu-west-1", + "r": [ + { + "SnapshotId": "e10ba805-c214-4641-a293-e817ebc7083a" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-022", + "l": "us-west-1", + "r": [ + { + "SnapshotId": "e10ba805-c214-4641-a293-e817ebc7083a" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-023", + "l": "eu-west-1", + "r": [ + { + "LoadBalancerName": "5ba15020-bbcc-4f98-acca-c86a9d59289d" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-023", + "l": "us-west-1", + "r": [ + { + "LoadBalancerName": "5ba15020-bbcc-4f98-acca-c86a9d59289d" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-024", + "l": "eu-west-1", + "r": [ + { + "QueueArn": "658c5ef1-ae43-4b88-942c-e10e7301ca8d" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-024", + "l": "us-west-1", + "r": [ + { + "QueueArn": "658c5ef1-ae43-4b88-942c-e10e7301ca8d" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-025", + "l": "eu-west-1", + "r": [ + { + "InstanceId": "6776b493-db92-45db-b5ae-159694afaefc", + "OwnerId": "f8c741d1-5e64-41c7-a5d2-975934b2fcd1" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-025", + "l": "us-west-1", + "r": [ + { + "InstanceId": "6776b493-db92-45db-b5ae-159694afaefc", + "OwnerId": "f8c741d1-5e64-41c7-a5d2-975934b2fcd1" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-026", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "aaa0039b-c18f-46d7-bedb-f5214df05880" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-026", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "aaa0039b-c18f-46d7-bedb-f5214df05880" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-027", + "l": "eu-west-1", + "r": [ + { + "GroupId": "3aebf18f-1c51-465e-9d67-23aa4973c939", + "VpcId": "1155b1dc-e322-46bc-a608-602e5d1eab91", + "OwnerId": "3361fe15-acea-43b4-b04e-53928bcf8fb5" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-027", + "l": "us-west-1", + "r": [ + { + "GroupId": "3aebf18f-1c51-465e-9d67-23aa4973c939", + "VpcId": "1155b1dc-e322-46bc-a608-602e5d1eab91", + "OwnerId": "3361fe15-acea-43b4-b04e-53928bcf8fb5" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-028", + "l": "eu-west-1", + "r": [ + { + "GroupId": "a1b29424-15f5-4aca-aecb-0a07ce54a345", + "VpcId": "eed1400e-e66b-49cf-a7aa-b23477221267", + "OwnerId": "bffddd9a-7dd4-412c-97e0-0dc695bcd852" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-028", + "l": "us-west-1", + "r": [ + { + "GroupId": "a1b29424-15f5-4aca-aecb-0a07ce54a345", + "VpcId": "eed1400e-e66b-49cf-a7aa-b23477221267", + "OwnerId": "bffddd9a-7dd4-412c-97e0-0dc695bcd852" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-029", + "l": "eu-west-1", + "r": [ + { + "GroupId": "ca53efbc-6c17-4f9f-b957-1873938cd700", + "VpcId": "6ce306de-9d62-4df8-8fe6-cb982c6a62e4", + "OwnerId": "edbf55a6-1839-4839-be36-62855a2b6860" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-029", + "l": "us-west-1", + "r": [ + { + "GroupId": "ca53efbc-6c17-4f9f-b957-1873938cd700", + "VpcId": "6ce306de-9d62-4df8-8fe6-cb982c6a62e4", + "OwnerId": "edbf55a6-1839-4839-be36-62855a2b6860" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-030", + "l": "eu-west-1", + "r": [ + { + "GroupId": "97e5e150-da6a-4eef-99d6-c8d80ae27fdd", + "VpcId": "424a1933-c1cd-4690-8be7-03cb0552b78d", + "OwnerId": "056b7394-70f0-46b7-8217-d52d523c58aa" + } + ], + "t": 1700042979.466269 + }, + { + "p": "ecc-aws-030", + "l": "us-west-1", + "r": [ + { + "GroupId": "97e5e150-da6a-4eef-99d6-c8d80ae27fdd", + "VpcId": "424a1933-c1cd-4690-8be7-03cb0552b78d", + "OwnerId": "056b7394-70f0-46b7-8217-d52d523c58aa" + } + ], + "t": 1700042979.46628 + }, + { + "p": "ecc-aws-031", + "l": "eu-west-1", + "r": [ + { + "GroupId": "df102f56-6ff5-4790-b8e2-cce42c310bc8", + "VpcId": "11083019-654a-4f23-975f-834cbad2ee7e", + "OwnerId": "87f8e252-a750-4fef-a990-a6a9a051eb1d" + } + ], + "t": 1700042979.46628 + }, + { + "p": "ecc-aws-031", + "l": "us-west-1", + "r": [ + { + "GroupId": "df102f56-6ff5-4790-b8e2-cce42c310bc8", + "VpcId": "11083019-654a-4f23-975f-834cbad2ee7e", + "OwnerId": "87f8e252-a750-4fef-a990-a6a9a051eb1d" + } + ], + "t": 1700042979.46628 + }, + { + "p": "ecc-aws-032", + "l": "eu-west-1", + "r": [ + { + "GroupId": "337d136c-ea8a-439a-b66b-75556a38d837", + "VpcId": "c74144fb-ed7e-419c-a4cb-39c30a20d74a", + "OwnerId": "6d92b0e4-1918-46b5-a0c3-e5d63b1e6455" + } + ], + "t": 1700042979.46628 + }, + { + "p": "ecc-aws-032", + "l": "us-west-1", + "r": [ + { + "GroupId": "337d136c-ea8a-439a-b66b-75556a38d837", + "VpcId": "c74144fb-ed7e-419c-a4cb-39c30a20d74a", + "OwnerId": "6d92b0e4-1918-46b5-a0c3-e5d63b1e6455" + } + ], + "t": 1700042979.46628 + }, + { + "p": "ecc-aws-033", + "l": "eu-west-1", + "r": [ + { + "GroupId": "3b4e878e-af6e-4624-93c0-26234dff53c1", + "VpcId": "e63ecc79-83c2-4f11-b240-8d66439ecf87", + "OwnerId": "9b0d6528-c8e0-4126-ae07-59a54521e0a7" + } + ], + "t": 1700042979.46628 + }, + { + "p": "ecc-aws-033", + "l": "us-west-1", + "r": [ + { + "GroupId": "3b4e878e-af6e-4624-93c0-26234dff53c1", + "VpcId": "e63ecc79-83c2-4f11-b240-8d66439ecf87", + "OwnerId": "9b0d6528-c8e0-4126-ae07-59a54521e0a7" + } + ], + "t": 1700042979.46628 + }, + { + "p": "ecc-aws-034", + "l": "eu-west-1", + "r": [ + { + "GroupId": "389d2ba7-d415-4471-a590-b715670ce34f", + "VpcId": "2f671bca-537d-4fb0-8013-c76ef0ba55d6", + "OwnerId": "47435021-14d4-4cd2-842c-12024f52708a" + } + ], + "t": 1700042979.46628 + }, + { + "p": "ecc-aws-034", + "l": "us-west-1", + "r": [ + { + "GroupId": "389d2ba7-d415-4471-a590-b715670ce34f", + "VpcId": "2f671bca-537d-4fb0-8013-c76ef0ba55d6", + "OwnerId": "47435021-14d4-4cd2-842c-12024f52708a" + } + ], + "t": 1700042979.46628 + }, + { + "p": "ecc-aws-035", + "l": "eu-west-1", + "r": [ + { + "GroupId": "d340e1b3-7f7b-4e78-a86f-8b7221d84d22", + "VpcId": "a5ff2b5f-45f3-40eb-8919-0f199efe31a9", + "OwnerId": "7f494915-72f4-4dfb-b47f-6e64af694847" + } + ], + "t": 1700042979.46628 + }, + { + "p": "ecc-aws-035", + "l": "us-west-1", + "r": [ + { + "GroupId": "d340e1b3-7f7b-4e78-a86f-8b7221d84d22", + "VpcId": "a5ff2b5f-45f3-40eb-8919-0f199efe31a9", + "OwnerId": "7f494915-72f4-4dfb-b47f-6e64af694847" + } + ], + "t": 1700042979.46628 + }, + { + "p": "ecc-aws-036", + "l": "eu-west-1", + "r": [ + { + "GroupId": "5a5fa5aa-e3d2-480e-8631-eeb5358cd2b9", + "VpcId": "f6f31ab2-8d84-40f9-be23-29d53bf53c6f", + "OwnerId": "cc7fb74c-be26-47d0-8820-e7b3a969dcf0" + } + ], + "t": 1700042979.46628 + }, + { + "p": "ecc-aws-036", + "l": "us-west-1", + "r": [ + { + "GroupId": "5a5fa5aa-e3d2-480e-8631-eeb5358cd2b9", + "VpcId": "f6f31ab2-8d84-40f9-be23-29d53bf53c6f", + "OwnerId": "cc7fb74c-be26-47d0-8820-e7b3a969dcf0" + } + ], + "t": 1700042979.46628 + }, + { + "p": "ecc-aws-037", + "l": "eu-west-1", + "r": [ + { + "GroupId": "31e6ab3d-b13a-4c8c-8419-01a64c3c432b", + "VpcId": "925ecca9-a3b1-4986-b61b-a3a458582f46", + "OwnerId": "f26b8b18-9b2d-4f21-a292-e34758e1a103" + } + ], + "t": 1700042979.46628 + }, + { + "p": "ecc-aws-037", + "l": "us-west-1", + "r": [ + { + "GroupId": "31e6ab3d-b13a-4c8c-8419-01a64c3c432b", + "VpcId": "925ecca9-a3b1-4986-b61b-a3a458582f46", + "OwnerId": "f26b8b18-9b2d-4f21-a292-e34758e1a103" + } + ], + "t": 1700042979.46628 + }, + { + "p": "ecc-aws-038", + "l": "eu-west-1", + "r": [ + { + "GroupId": "431eefe6-50c5-42cd-9856-fc89d086ae7c", + "VpcId": "5fb915b1-c617-4528-aefb-57bd983b10a5", + "OwnerId": "7fa49186-ec5c-40d1-84ab-a5533c966b3a" + } + ], + "t": 1700042979.46628 + }, + { + "p": "ecc-aws-038", + "l": "us-west-1", + "r": [ + { + "GroupId": "431eefe6-50c5-42cd-9856-fc89d086ae7c", + "VpcId": "5fb915b1-c617-4528-aefb-57bd983b10a5", + "OwnerId": "7fa49186-ec5c-40d1-84ab-a5533c966b3a" + } + ], + "t": 1700042979.46628 + }, + { + "p": "ecc-aws-039", + "l": "eu-west-1", + "r": [ + { + "GroupId": "ed206221-2a8f-4572-97e3-e5ab1a63c881", + "VpcId": "2916083b-5fe9-44bd-a71e-4bac900383fc", + "OwnerId": "4db4913b-a420-477a-8650-799819062ebf" + } + ], + "t": 1700042979.46628 + }, + { + "p": "ecc-aws-039", + "l": "us-west-1", + "r": [ + { + "GroupId": "ed206221-2a8f-4572-97e3-e5ab1a63c881", + "VpcId": "2916083b-5fe9-44bd-a71e-4bac900383fc", + "OwnerId": "4db4913b-a420-477a-8650-799819062ebf" + } + ], + "t": 1700042979.46628 + }, + { + "p": "ecc-aws-040", + "l": "eu-west-1", + "r": [ + { + "arn": "73029953-0950-4f8d-a3d2-7c15c1a96c6a" + } + ], + "t": 1700042979.46628 + }, + { + "p": "ecc-aws-040", + "l": "us-west-1", + "r": [ + { + "arn": "73029953-0950-4f8d-a3d2-7c15c1a96c6a" + } + ], + "t": 1700042979.46628 + }, + { + "p": "ecc-aws-041", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "38fcf76d-5b8c-4f74-a434-49c396438726" + } + ], + "t": 1700042979.4665542 + }, + { + "p": "ecc-aws-041", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "38fcf76d-5b8c-4f74-a434-49c396438726" + } + ], + "t": 1700042979.4665542 + }, + { + "p": "ecc-aws-042", + "l": "multiregion", + "r": [ + { + "Name": "7e2f953c-e2bb-4b9a-bac6-19e4c54faf22" + } + ], + "t": 1700042979.4665542 + }, + { + "p": "ecc-aws-043", + "l": "multiregion", + "r": [ + { + "Name": "6becded8-5542-4e9c-a9e1-c26f5fe03cf7" + } + ], + "t": 1700042979.4665542 + }, + { + "p": "ecc-aws-044", + "l": "multiregion", + "r": [ + { + "Name": "ed2863a6-a0b2-4f36-9efb-7927452d9c04" + } + ], + "t": 1700042979.4665542 + }, + { + "p": "ecc-aws-045", + "l": "multiregion", + "r": [ + { + "field": "d4f49dbf-992c-42ff-99da-2772211419a4" + } + ], + "t": 1700042979.4665542 + }, + { + "p": "ecc-aws-046", + "l": "multiregion", + "r": [ + { + "field": "ab0906c7-06b3-4e0c-83c1-6b798913ce44" + } + ], + "t": 1700042979.4665542 + }, + { + "p": "ecc-aws-047", + "l": "multiregion", + "r": [ + { + "field": "6109763f-53bf-4f77-a0d0-0d21a24e8a03" + } + ], + "t": 1700042979.466654 + }, + { + "p": "ecc-aws-048", + "l": "multiregion", + "r": [ + { + "field": "0c8fa9ad-472a-47cb-bb1e-523714eca795" + } + ], + "t": 1700042979.466654 + }, + { + "p": "ecc-aws-049", + "l": "multiregion", + "r": [ + { + "field": "75a3ff2b-a7e2-4275-aaff-8cc36847bc2d" + } + ], + "t": 1700042979.466654 + }, + { + "p": "ecc-aws-050", + "l": "multiregion", + "r": [ + { + "field": "a1f07a75-441f-4aaf-902d-920df825e61b" + } + ], + "t": 1700042979.466654 + }, + { + "p": "ecc-aws-051", + "l": "multiregion", + "r": [ + { + "field": "319367d8-c0ed-48a4-8e66-14554b13b02e" + } + ], + "t": 1700042979.466654 + }, + { + "p": "ecc-aws-052", + "l": "multiregion", + "r": [ + { + "field": "a8725f25-61f6-499d-aa10-07186f5635bd" + } + ], + "t": 1700042979.466654 + }, + { + "p": "ecc-aws-052", + "l": "multiregion", + "r": [ + { + "field": "a8725f25-61f6-499d-aa10-07186f5635bd" + } + ], + "t": 1700042979.466654 + }, + { + "p": "ecc-aws-053", + "l": "eu-west-1", + "r": [ + { + "TrailARN": "695dc42e-9eb4-4419-bc12-d0782df93fc8" + } + ], + "t": 1700042979.466654 + }, + { + "p": "ecc-aws-053", + "l": "us-west-1", + "r": [ + { + "TrailARN": "695dc42e-9eb4-4419-bc12-d0782df93fc8" + } + ], + "t": 1700042979.466654 + }, + { + "p": "ecc-aws-054", + "l": "multiregion", + "r": [ + { + "field": "460e82e1-bbb2-499d-aa05-cceef2217ae5" + } + ], + "t": 1700042979.466654 + }, + { + "p": "ecc-aws-055", + "l": "eu-west-1", + "r": [ + { + "TrailARN": "c3da1c55-75c0-463d-bc36-b6a0df063a53" + } + ], + "t": 1700042979.4667995 + }, + { + "p": "ecc-aws-055", + "l": "us-west-1", + "r": [ + { + "TrailARN": "c3da1c55-75c0-463d-bc36-b6a0df063a53" + } + ], + "t": 1700042979.4667995 + }, + { + "p": "ecc-aws-056", + "l": "multiregion", + "r": [ + { + "Arn": "8ea377b1-c317-4109-974d-a6822e748182" + } + ], + "t": 1700042979.4667995 + }, + { + "p": "ecc-aws-057", + "l": "eu-west-1", + "r": [ + { + "InstanceId": "4a1235e7-00ab-4e0a-ba48-db9304f3d870", + "OwnerId": "e7c94ec2-67cd-4da9-a522-2766c836692b" + } + ], + "t": 1700042979.4667995 + }, + { + "p": "ecc-aws-057", + "l": "us-west-1", + "r": [ + { + "InstanceId": "4a1235e7-00ab-4e0a-ba48-db9304f3d870", + "OwnerId": "e7c94ec2-67cd-4da9-a522-2766c836692b" + } + ], + "t": 1700042979.4667995 + }, + { + "p": "ecc-aws-058", + "l": "multiregion", + "r": [ + { + "field": "a97487d1-4d34-46a6-b6b9-f65c7d973a92" + } + ], + "t": 1700042979.4668665 + }, + { + "p": "ecc-aws-059", + "l": "eu-west-1", + "r": [ + { + "field": "34ad4d38-351a-445d-b9ea-99083ea3c0eb" + } + ], + "t": 1700042979.4668665 + }, + { + "p": "ecc-aws-059", + "l": "us-west-1", + "r": [ + { + "field": "34ad4d38-351a-445d-b9ea-99083ea3c0eb" + } + ], + "t": 1700042979.4668665 + }, + { + "p": "ecc-aws-060", + "l": "eu-west-1", + "r": [ + { + "TrailARN": "76b424bf-06e9-4567-bf31-eb7ecff8e005" + } + ], + "t": 1700042979.4668665 + }, + { + "p": "ecc-aws-060", + "l": "us-west-1", + "r": [ + { + "TrailARN": "76b424bf-06e9-4567-bf31-eb7ecff8e005" + } + ], + "t": 1700042979.4668665 + }, + { + "p": "ecc-aws-061", + "l": "eu-west-1", + "r": [ + { + "KeyArn": "c52e0ca8-4517-43cd-80f7-762da170ed3b", + "AWSAccountId": "4320a709-cae8-4320-b8ea-a63c36c1c472" + } + ], + "t": 1700042979.4668665 + }, + { + "p": "ecc-aws-061", + "l": "us-west-1", + "r": [ + { + "KeyArn": "c52e0ca8-4517-43cd-80f7-762da170ed3b", + "AWSAccountId": "4320a709-cae8-4320-b8ea-a63c36c1c472" + } + ], + "t": 1700042979.4668665 + }, + { + "p": "ecc-aws-062", + "l": "eu-west-1", + "r": [ + { + "GroupId": "bc615100-3b03-428e-93c8-a6af44130e5f", + "VpcId": "e5457ad0-2b12-438b-a0e2-10f34d443d3e", + "OwnerId": "fb22e019-afe3-4701-be91-828aef3ba5c8" + } + ], + "t": 1700042979.4668665 + }, + { + "p": "ecc-aws-062", + "l": "us-west-1", + "r": [ + { + "GroupId": "bc615100-3b03-428e-93c8-a6af44130e5f", + "VpcId": "e5457ad0-2b12-438b-a0e2-10f34d443d3e", + "OwnerId": "fb22e019-afe3-4701-be91-828aef3ba5c8" + } + ], + "t": 1700042979.4668665 + }, + { + "p": "ecc-aws-063", + "l": "eu-west-1", + "r": [ + { + "GroupId": "7cc3098d-13a1-452d-898f-83fd92e81a6d", + "VpcId": "761df9af-74c0-47ad-a398-78277a685ffa", + "OwnerId": "1df61d00-b720-41d7-99cb-5ec4dd17a81c" + } + ], + "t": 1700042979.4668665 + }, + { + "p": "ecc-aws-063", + "l": "us-west-1", + "r": [ + { + "GroupId": "7cc3098d-13a1-452d-898f-83fd92e81a6d", + "VpcId": "761df9af-74c0-47ad-a398-78277a685ffa", + "OwnerId": "1df61d00-b720-41d7-99cb-5ec4dd17a81c" + } + ], + "t": 1700042979.4668665 + }, + { + "p": "ecc-aws-064", + "l": "eu-west-1", + "r": [ + { + "GroupId": "5bbc48b1-4f9b-4b49-9ff2-b0651d025c79", + "VpcId": "a742807f-13e3-4c4c-aba5-99a5f3f839f8", + "OwnerId": "175720ce-a89b-4326-90f9-bc17f62796eb" + } + ], + "t": 1700042979.4668665 + }, + { + "p": "ecc-aws-064", + "l": "us-west-1", + "r": [ + { + "GroupId": "5bbc48b1-4f9b-4b49-9ff2-b0651d025c79", + "VpcId": "a742807f-13e3-4c4c-aba5-99a5f3f839f8", + "OwnerId": "175720ce-a89b-4326-90f9-bc17f62796eb" + } + ], + "t": 1700042979.4668665 + }, + { + "p": "ecc-aws-065", + "l": "multiregion", + "r": [ + { + "ARN": "64cf5f63-e8e9-4505-aa23-e35075f6b4da" + } + ], + "t": 1700042979.4668665 + }, + { + "p": "ecc-aws-066", + "l": "eu-west-1", + "r": [ + { + "arn": "89d714e6-2a7a-45bf-8d67-3efe7f324686" + } + ], + "t": 1700042979.4668665 + }, + { + "p": "ecc-aws-066", + "l": "us-west-1", + "r": [ + { + "arn": "89d714e6-2a7a-45bf-8d67-3efe7f324686" + } + ], + "t": 1700042979.4668665 + }, + { + "p": "ecc-aws-067", + "l": "eu-west-1", + "r": [ + { + "field": "d6523d9f-efe4-4237-aece-a2423812e96d" + } + ], + "t": 1700042979.4668665 + }, + { + "p": "ecc-aws-067", + "l": "us-west-1", + "r": [ + { + "field": "d6523d9f-efe4-4237-aece-a2423812e96d" + } + ], + "t": 1700042979.4668665 + }, + { + "p": "ecc-aws-068", + "l": "eu-west-1", + "r": [ + { + "TrailARN": "b1420115-845a-44b5-8696-83a2665a64ba", + "S3BucketName": "8e27b91a-e854-4529-802f-157d21cd7379" + } + ], + "t": 1700042979.4668665 + }, + { + "p": "ecc-aws-068", + "l": "us-west-1", + "r": [ + { + "TrailARN": "b1420115-845a-44b5-8696-83a2665a64ba", + "S3BucketName": "8e27b91a-e854-4529-802f-157d21cd7379" + } + ], + "t": 1700042979.4668665 + }, + { + "p": "ecc-aws-069", + "l": "multiregion", + "r": [ + { + "Name": "7c0ac491-da8f-4c52-88d7-fe4993b532c6" + } + ], + "t": 1700042979.4668665 + }, + { + "p": "ecc-aws-070", + "l": "eu-west-1", + "r": [ + { + "GroupId": "137ae1fe-c9b6-4f30-b74d-602b6d68d3f3", + "VpcId": "b1847fba-3753-4fca-9f29-a812284d9a8e", + "OwnerId": "22ec2b9a-f6a8-4824-9a9c-71f931091661" + } + ], + "t": 1700042979.4668665 + }, + { + "p": "ecc-aws-070", + "l": "us-west-1", + "r": [ + { + "GroupId": "137ae1fe-c9b6-4f30-b74d-602b6d68d3f3", + "VpcId": "b1847fba-3753-4fca-9f29-a812284d9a8e", + "OwnerId": "22ec2b9a-f6a8-4824-9a9c-71f931091661" + } + ], + "t": 1700042979.4668665 + }, + { + "p": "ecc-aws-071", + "l": "eu-west-1", + "r": [ + { + "arn": "d8b4bcc2-03a9-4a45-b8de-5db717fa6b10" + } + ], + "t": 1700042979.4668665 + }, + { + "p": "ecc-aws-071", + "l": "us-west-1", + "r": [ + { + "arn": "d8b4bcc2-03a9-4a45-b8de-5db717fa6b10" + } + ], + "t": 1700042979.4668665 + }, + { + "p": "ecc-aws-072", + "l": "eu-west-1", + "r": [ + { + "AutoScalingGroupARN": "173dcb0a-84d6-4dd5-9a31-3d6eea701855" + } + ], + "t": 1700042979.4668665 + }, + { + "p": "ecc-aws-072", + "l": "us-west-1", + "r": [ + { + "AutoScalingGroupARN": "173dcb0a-84d6-4dd5-9a31-3d6eea701855" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-073", + "l": "eu-west-1", + "r": [ + { + "AllocationId": "e5aff0d8-324c-4caa-88b3-a925b7e7f6bd" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-073", + "l": "us-west-1", + "r": [ + { + "AllocationId": "e5aff0d8-324c-4caa-88b3-a925b7e7f6bd" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-074", + "l": "eu-west-1", + "r": [ + { + "ARN": "125eef8c-a902-4244-8b3a-b11cf0b2900e" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-074", + "l": "us-west-1", + "r": [ + { + "ARN": "125eef8c-a902-4244-8b3a-b11cf0b2900e" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-075", + "l": "eu-west-1", + "r": [ + { + "ARN": "8211d2a0-ff26-4af9-a6b9-d5cf084b01ef" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-075", + "l": "us-west-1", + "r": [ + { + "ARN": "8211d2a0-ff26-4af9-a6b9-d5cf084b01ef" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-076", + "l": "eu-west-1", + "r": [ + { + "SnapshotId": "6eab556b-f8e4-4332-9986-758c12cae095", + "VolumeId": "54ac58f8-f443-4777-a4ee-5dbbe3a1e4c0", + "OwnerId": "2394a21c-0ef4-47f3-9604-0f9ed9c7c4b6" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-076", + "l": "us-west-1", + "r": [ + { + "SnapshotId": "6eab556b-f8e4-4332-9986-758c12cae095", + "VolumeId": "54ac58f8-f443-4777-a4ee-5dbbe3a1e4c0", + "OwnerId": "2394a21c-0ef4-47f3-9604-0f9ed9c7c4b6" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-077", + "l": "eu-west-1", + "r": [ + { + "field": "64e8fbfb-3d9d-45ff-8eee-8e2df55bb9ca" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-077", + "l": "us-west-1", + "r": [ + { + "field": "64e8fbfb-3d9d-45ff-8eee-8e2df55bb9ca" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-078", + "l": "eu-west-1", + "r": [ + { + "field": "e0c1f2ba-7881-41ff-a3e3-73108b443477" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-078", + "l": "us-west-1", + "r": [ + { + "field": "e0c1f2ba-7881-41ff-a3e3-73108b443477" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-079", + "l": "eu-west-1", + "r": [ + { + "field": "5092be94-eb3a-49e8-be8a-f654d8fa9605" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-079", + "l": "us-west-1", + "r": [ + { + "field": "5092be94-eb3a-49e8-be8a-f654d8fa9605" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-080", + "l": "eu-west-1", + "r": [ + { + "field": "f54e5883-3df4-43c2-9b65-fc9318b0adbb" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-080", + "l": "us-west-1", + "r": [ + { + "field": "f54e5883-3df4-43c2-9b65-fc9318b0adbb" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-081", + "l": "eu-west-1", + "r": [ + { + "field": "ed43fd90-5e88-4748-8705-745cd05d9f88" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-081", + "l": "us-west-1", + "r": [ + { + "field": "ed43fd90-5e88-4748-8705-745cd05d9f88" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-082", + "l": "eu-west-1", + "r": [ + { + "field": "d1461fae-b8ff-4600-893a-964af7317eda" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-082", + "l": "us-west-1", + "r": [ + { + "field": "d1461fae-b8ff-4600-893a-964af7317eda" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-083", + "l": "multiregion", + "r": [ + { + "ARN": "e12bfe42-e97e-4249-9ef7-8ce92e421ad3" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-084", + "l": "eu-west-1", + "r": [ + { + "TrailARN": "de4ec41a-a5ac-466b-8978-f954604dff8a", + "S3BucketName": "728627bf-085b-4115-bfc2-1691fb130976" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-084", + "l": "us-west-1", + "r": [ + { + "TrailARN": "de4ec41a-a5ac-466b-8978-f954604dff8a", + "S3BucketName": "728627bf-085b-4115-bfc2-1691fb130976" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-085", + "l": "eu-west-1", + "r": [ + { + "FunctionArn": "0d80c469-2e44-4895-8ca5-3c848e2eb48e" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-085", + "l": "us-west-1", + "r": [ + { + "FunctionArn": "0d80c469-2e44-4895-8ca5-3c848e2eb48e" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-086", + "l": "eu-west-1", + "r": [ + { + "FunctionArn": "ff148746-a8bd-474c-be71-314c4f1d633a" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-086", + "l": "us-west-1", + "r": [ + { + "FunctionArn": "ff148746-a8bd-474c-be71-314c4f1d633a" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-087", + "l": "eu-west-1", + "r": [ + { + "ClusterIdentifier": "657c7437-ab75-4b16-bcef-d6f853e9e003" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-087", + "l": "us-west-1", + "r": [ + { + "ClusterIdentifier": "657c7437-ab75-4b16-bcef-d6f853e9e003" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-088", + "l": "multiregion", + "r": [ + { + "Name": "96a2b694-14ae-4172-834c-64d6819934fc" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-089", + "l": "eu-west-1", + "r": [ + { + "arn": "8151bf1c-2623-4fbb-8b86-bb53b3ec9b24" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-089", + "l": "us-west-1", + "r": [ + { + "arn": "8151bf1c-2623-4fbb-8b86-bb53b3ec9b24" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-090", + "l": "eu-west-1", + "r": [ + { + "DBSnapshotArn": "1017dedf-4956-472b-a2b5-08727d593190", + "DBInstanceIdentifier": "7f122a39-ecbe-4f62-a62b-9343c24c5610" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-090", + "l": "us-west-1", + "r": [ + { + "DBSnapshotArn": "1017dedf-4956-472b-a2b5-08727d593190", + "DBInstanceIdentifier": "7f122a39-ecbe-4f62-a62b-9343c24c5610" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-091", + "l": "eu-west-1", + "r": [ + { + "InstanceId": "879147c9-0340-4eff-bf96-b53710245642", + "OwnerId": "8d9300cd-69ad-46bf-a9d5-040ec097e242" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-091", + "l": "us-west-1", + "r": [ + { + "InstanceId": "879147c9-0340-4eff-bf96-b53710245642", + "OwnerId": "8d9300cd-69ad-46bf-a9d5-040ec097e242" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-092", + "l": "eu-west-1", + "r": [ + { + "ImageId": "8eed3bfa-eee0-483d-8179-0f1f7d801e0e", + "OwnerId": "06f99e69-4dc7-462f-bf50-c3c1256f34f2" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-092", + "l": "us-west-1", + "r": [ + { + "ImageId": "8eed3bfa-eee0-483d-8179-0f1f7d801e0e", + "OwnerId": "06f99e69-4dc7-462f-bf50-c3c1256f34f2" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-093", + "l": "eu-west-1", + "r": [ + { + "NotebookInstanceArn": "853dcd69-0cdd-47cc-a4ed-218d76e4d6e1" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-093", + "l": "us-west-1", + "r": [ + { + "NotebookInstanceArn": "853dcd69-0cdd-47cc-a4ed-218d76e4d6e1" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-094", + "l": "eu-west-1", + "r": [ + { + "field": "cc7cba3f-f108-4126-921c-5cf5c437c765" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-094", + "l": "us-west-1", + "r": [ + { + "field": "cc7cba3f-f108-4126-921c-5cf5c437c765" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-095", + "l": "eu-west-1", + "r": [ + { + "field": "0b75d80a-72f1-46d3-a6af-88d2255f74ad" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-095", + "l": "us-west-1", + "r": [ + { + "field": "0b75d80a-72f1-46d3-a6af-88d2255f74ad" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-096", + "l": "eu-west-1", + "r": [ + { + "field": "1bbb1398-e718-4c43-be63-742edab75df3" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-096", + "l": "us-west-1", + "r": [ + { + "field": "1bbb1398-e718-4c43-be63-742edab75df3" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-097", + "l": "eu-west-1", + "r": [ + { + "field": "e8f60fa9-54f0-46ac-a07c-7c431affc96b" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-097", + "l": "us-west-1", + "r": [ + { + "field": "e8f60fa9-54f0-46ac-a07c-7c431affc96b" + } + ], + "t": 1700042979.4672031 + }, + { + "p": "ecc-aws-098", + "l": "eu-west-1", + "r": [ + { + "field": "fd0ffe2a-d3a3-4555-8d26-aef7951a4d98" + } + ], + "t": 1700042979.4677956 + }, + { + "p": "ecc-aws-098", + "l": "us-west-1", + "r": [ + { + "field": "fd0ffe2a-d3a3-4555-8d26-aef7951a4d98" + } + ], + "t": 1700042979.4677956 + }, + { + "p": "ecc-aws-099", + "l": "eu-west-1", + "r": [ + { + "field": "21595abb-026f-4a15-9308-c0faffe438b4" + } + ], + "t": 1700042979.4677956 + }, + { + "p": "ecc-aws-099", + "l": "us-west-1", + "r": [ + { + "field": "21595abb-026f-4a15-9308-c0faffe438b4" + } + ], + "t": 1700042979.4677956 + }, + { + "p": "ecc-aws-100", + "l": "eu-west-1", + "r": [ + { + "field": "29fa60fa-33b3-49a0-9269-6077b4c0743e" + } + ], + "t": 1700042979.4677956 + }, + { + "p": "ecc-aws-100", + "l": "us-west-1", + "r": [ + { + "field": "29fa60fa-33b3-49a0-9269-6077b4c0743e" + } + ], + "t": 1700042979.4677956 + }, + { + "p": "ecc-aws-101", + "l": "eu-west-1", + "r": [ + { + "SubnetId": "fe19141f-26bc-4afd-a636-41b073ac07a2", + "VpcId": "fdd3f732-aa8b-41a4-be4a-33fb3443e2ea", + "OwnerId": "a485470d-5dde-45f1-af51-16f2c80c99e7" + } + ], + "t": 1700042979.4677956 + }, + { + "p": "ecc-aws-101", + "l": "us-west-1", + "r": [ + { + "SubnetId": "fe19141f-26bc-4afd-a636-41b073ac07a2", + "VpcId": "fdd3f732-aa8b-41a4-be4a-33fb3443e2ea", + "OwnerId": "a485470d-5dde-45f1-af51-16f2c80c99e7" + } + ], + "t": 1700042979.467908 + }, + { + "p": "ecc-aws-102", + "l": "eu-west-1", + "r": [ + { + "NotebookInstanceArn": "62fe59bb-a105-4c5b-87fd-8d8df97d2c39" + } + ], + "t": 1700042979.467908 + }, + { + "p": "ecc-aws-102", + "l": "us-west-1", + "r": [ + { + "NotebookInstanceArn": "62fe59bb-a105-4c5b-87fd-8d8df97d2c39" + } + ], + "t": 1700042979.467908 + }, + { + "p": "ecc-aws-103", + "l": "multiregion", + "r": [ + { + "ARN": "52c630ce-0a73-4c65-867e-872718d2e2d4" + } + ], + "t": 1700042979.467908 + }, + { + "p": "ecc-aws-104", + "l": "multiregion", + "r": [ + { + "ARN": "c5a5857e-c845-4ac0-9ddb-c5c05c6afb72" + } + ], + "t": 1700042979.467908 + }, + { + "p": "ecc-aws-105", + "l": "eu-west-1", + "r": [ + { + "StreamARN": "2ed5972b-a589-4e80-93d2-d96e257be234" + } + ], + "t": 1700042979.467908 + }, + { + "p": "ecc-aws-105", + "l": "us-west-1", + "r": [ + { + "StreamARN": "2ed5972b-a589-4e80-93d2-d96e257be234" + } + ], + "t": 1700042979.467908 + }, + { + "p": "ecc-aws-106", + "l": "eu-west-1", + "r": [ + { + "CertificateArn": "7bc8f2a4-38c6-47e5-9ec5-7794a375bd85", + "DomainName": "5595e8a6-dc9b-4057-942e-0f0bc8db3606" + } + ], + "t": 1700042979.467908 + }, + { + "p": "ecc-aws-106", + "l": "us-west-1", + "r": [ + { + "CertificateArn": "7bc8f2a4-38c6-47e5-9ec5-7794a375bd85", + "DomainName": "5595e8a6-dc9b-4057-942e-0f0bc8db3606" + } + ], + "t": 1700042979.467908 + }, + { + "p": "ecc-aws-107", + "l": "eu-west-1", + "r": [ + { + "CertificateArn": "5d58157a-f86d-46ab-81cd-1ba2738e7e6e", + "DomainName": "04d1edb8-85af-4213-918b-318d5d23a66f" + } + ], + "t": 1700042979.467908 + }, + { + "p": "ecc-aws-107", + "l": "us-west-1", + "r": [ + { + "CertificateArn": "5d58157a-f86d-46ab-81cd-1ba2738e7e6e", + "DomainName": "04d1edb8-85af-4213-918b-318d5d23a66f" + } + ], + "t": 1700042979.467908 + }, + { + "p": "ecc-aws-108", + "l": "multiregion", + "r": [ + { + "ARN": "6df84dc2-6098-4565-98d3-2ac8617b9804" + } + ], + "t": 1700042979.467908 + }, + { + "p": "ecc-aws-109", + "l": "eu-west-1", + "r": [ + { + "CertificateArn": "e8258d9f-65b8-4dc5-b9d7-9be121af3c02", + "DomainName": "bb6b835f-63bf-46e6-ae02-6d64349f5f7f" + } + ], + "t": 1700042979.467908 + }, + { + "p": "ecc-aws-109", + "l": "us-west-1", + "r": [ + { + "CertificateArn": "e8258d9f-65b8-4dc5-b9d7-9be121af3c02", + "DomainName": "bb6b835f-63bf-46e6-ae02-6d64349f5f7f" + } + ], + "t": 1700042979.467908 + }, + { + "p": "ecc-aws-110", + "l": "eu-west-1", + "r": [ + { + "clusterArn": "f8fa2e0a-4490-4d9c-8b36-54ec670bc837" + } + ], + "t": 1700042979.467908 + }, + { + "p": "ecc-aws-110", + "l": "us-west-1", + "r": [ + { + "clusterArn": "f8fa2e0a-4490-4d9c-8b36-54ec670bc837" + } + ], + "t": 1700042979.467908 + }, + { + "p": "ecc-aws-111", + "l": "eu-west-1", + "r": [ + { + "LoadBalancerArn": "85bbb7d7-4578-4004-a0a7-637dc818e34e" + } + ], + "t": 1700042979.467908 + }, + { + "p": "ecc-aws-111", + "l": "us-west-1", + "r": [ + { + "LoadBalancerArn": "85bbb7d7-4578-4004-a0a7-637dc818e34e" + } + ], + "t": 1700042979.467908 + }, + { + "p": "ecc-aws-112", + "l": "multiregion", + "r": [ + { + "Name": "44a99c58-5c95-4056-9410-5d2d5174a7ec" + } + ], + "t": 1700042979.467908 + }, + { + "p": "ecc-aws-113", + "l": "multiregion", + "r": [ + { + "Arn": "3c2bee50-49d4-40f4-91d5-05be2a83ff0f" + } + ], + "t": 1700042979.467908 + }, + { + "p": "ecc-aws-114", + "l": "eu-west-1", + "r": [ + { + "arn": "65908d46-5018-4c55-a4c6-c738bbe88841" + } + ], + "t": 1700042979.467908 + }, + { + "p": "ecc-aws-114", + "l": "us-west-1", + "r": [ + { + "arn": "65908d46-5018-4c55-a4c6-c738bbe88841" + } + ], + "t": 1700042979.467908 + }, + { + "p": "ecc-aws-115", + "l": "eu-west-1", + "r": [ + { + "CertificateArn": "a497e11d-caf7-49e6-8660-583a3bc73a8a", + "DomainName": "813d6c7b-0f94-4d2a-89ad-f146c3155e57" + } + ], + "t": 1700042979.467908 + }, + { + "p": "ecc-aws-115", + "l": "us-west-1", + "r": [ + { + "CertificateArn": "a497e11d-caf7-49e6-8660-583a3bc73a8a", + "DomainName": "813d6c7b-0f94-4d2a-89ad-f146c3155e57" + } + ], + "t": 1700042979.467908 + }, + { + "p": "ecc-aws-116", + "l": "eu-west-1", + "r": [ + { + "id": "e346e268-cefc-4de9-8f3a-dc009d28d9f6", + "name": "8acb14a9-b421-42b7-959d-3308ecff002f" + } + ], + "t": 1700042979.467908 + }, + { + "p": "ecc-aws-116", + "l": "us-west-1", + "r": [ + { + "id": "e346e268-cefc-4de9-8f3a-dc009d28d9f6", + "name": "8acb14a9-b421-42b7-959d-3308ecff002f" + } + ], + "t": 1700042979.467908 + }, + { + "p": "ecc-aws-117", + "l": "eu-west-1", + "r": [ + { + "id": "cfecafa7-0d29-46c7-849e-60da110a8194", + "path": "e1eb30cb-c017-41ff-af54-a8ffbec7e40c" + } + ], + "t": 1700042979.467908 + }, + { + "p": "ecc-aws-117", + "l": "us-west-1", + "r": [ + { + "id": "cfecafa7-0d29-46c7-849e-60da110a8194", + "path": "e1eb30cb-c017-41ff-af54-a8ffbec7e40c" + } + ], + "t": 1700042979.467908 + }, + { + "p": "ecc-aws-118", + "l": "eu-west-1", + "r": [ + { + "serviceArn": "f0e53fc3-354b-46f6-b35b-c62e97d4dd4d", + "taskDefinition": "ed1101d0-cdc2-4766-8d7f-4aba657694c5" + } + ], + "t": 1700042979.467908 + }, + { + "p": "ecc-aws-118", + "l": "us-west-1", + "r": [ + { + "serviceArn": "f0e53fc3-354b-46f6-b35b-c62e97d4dd4d", + "taskDefinition": "ed1101d0-cdc2-4766-8d7f-4aba657694c5" + } + ], + "t": 1700042979.467908 + }, + { + "p": "ecc-aws-119", + "l": "eu-west-1", + "r": [ + { + "StreamARN": "0e541a4b-3176-4d35-a1b6-a35f860a8a0b" + } + ], + "t": 1700042979.468283 + }, + { + "p": "ecc-aws-119", + "l": "us-west-1", + "r": [ + { + "StreamARN": "0e541a4b-3176-4d35-a1b6-a35f860a8a0b" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-120", + "l": "eu-west-1", + "r": [ + { + "StreamARN": "752f67ec-3e40-413d-ae20-1beac41049ba" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-120", + "l": "us-west-1", + "r": [ + { + "StreamARN": "752f67ec-3e40-413d-ae20-1beac41049ba" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-121", + "l": "eu-west-1", + "r": [ + { + "GroupId": "b72ffc71-e95c-4387-8a82-33d6c6207773", + "VpcId": "9e581021-347e-4498-94aa-2340365ce4e9", + "OwnerId": "1271270b-1726-43bf-b831-c7a90fc1a757" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-121", + "l": "us-west-1", + "r": [ + { + "GroupId": "b72ffc71-e95c-4387-8a82-33d6c6207773", + "VpcId": "9e581021-347e-4498-94aa-2340365ce4e9", + "OwnerId": "1271270b-1726-43bf-b831-c7a90fc1a757" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-122", + "l": "eu-west-1", + "r": [ + { + "TableArn": "c871aa79-5987-463a-b37a-bceff870664e" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-122", + "l": "us-west-1", + "r": [ + { + "TableArn": "c871aa79-5987-463a-b37a-bceff870664e" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-123", + "l": "eu-west-1", + "r": [ + { + "FileSystemArn": "9216634b-6049-4bcd-bd73-5835e48acd81", + "OwnerId": "ee6b174c-3b83-41ca-9d72-4cafe7201308" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-123", + "l": "us-west-1", + "r": [ + { + "FileSystemArn": "9216634b-6049-4bcd-bd73-5835e48acd81", + "OwnerId": "ee6b174c-3b83-41ca-9d72-4cafe7201308" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-124", + "l": "eu-west-1", + "r": [ + { + "FileSystemArn": "3ab579e1-06c2-4243-922c-9cf97de33990", + "OwnerId": "efdf225e-58f1-48d0-bae0-5e0c4e944c65" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-124", + "l": "us-west-1", + "r": [ + { + "FileSystemArn": "3ab579e1-06c2-4243-922c-9cf97de33990", + "OwnerId": "efdf225e-58f1-48d0-bae0-5e0c4e944c65" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-125", + "l": "eu-west-1", + "r": [ + { + "ARN": "00fb12a2-ca2d-4abe-8e92-d939cb28802b" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-125", + "l": "us-west-1", + "r": [ + { + "ARN": "00fb12a2-ca2d-4abe-8e92-d939cb28802b" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-126", + "l": "eu-west-1", + "r": [ + { + "ClusterIdentifier": "e4f1a452-54a2-4d95-860c-02552ef726dd" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-126", + "l": "us-west-1", + "r": [ + { + "ClusterIdentifier": "e4f1a452-54a2-4d95-860c-02552ef726dd" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-127", + "l": "eu-west-1", + "r": [ + { + "DBClusterArn": "6db36ded-b489-43d5-b8f5-a267309d2306" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-127", + "l": "us-west-1", + "r": [ + { + "DBClusterArn": "6db36ded-b489-43d5-b8f5-a267309d2306" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-128", + "l": "multiregion", + "r": [ + { + "DomainName": "18fed3e3-166b-4eae-9962-8cc33961d28b" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-129", + "l": "eu-west-1", + "r": [ + { + "LoadBalancerArn": "94a176b9-33aa-45f6-8d57-274f5cd2747d" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-129", + "l": "us-west-1", + "r": [ + { + "LoadBalancerArn": "94a176b9-33aa-45f6-8d57-274f5cd2747d" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-130", + "l": "eu-west-1", + "r": [ + { + "LoadBalancerArn": "6e681ece-0210-4494-bd97-320716b352dc" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-130", + "l": "us-west-1", + "r": [ + { + "LoadBalancerArn": "6e681ece-0210-4494-bd97-320716b352dc" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-131", + "l": "eu-west-1", + "r": [ + { + "InstanceId": "4948f389-9dd0-4ec3-a828-e7ccff25a27b", + "OwnerId": "60f8a284-0bfc-4e4a-823c-3f39994f210e" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-131", + "l": "us-west-1", + "r": [ + { + "InstanceId": "4948f389-9dd0-4ec3-a828-e7ccff25a27b", + "OwnerId": "60f8a284-0bfc-4e4a-823c-3f39994f210e" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-132", + "l": "eu-west-1", + "r": [ + { + "InstanceId": "9b95953e-0cd4-4644-b733-ecea902fa432", + "OwnerId": "b5fe3702-739d-4ef7-a941-75909d4c214e" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-132", + "l": "us-west-1", + "r": [ + { + "InstanceId": "9b95953e-0cd4-4644-b733-ecea902fa432", + "OwnerId": "b5fe3702-739d-4ef7-a941-75909d4c214e" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-133", + "l": "eu-west-1", + "r": [ + { + "field": "41c7b83f-e9b6-4878-93f2-411a83bf36c9" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-133", + "l": "us-west-1", + "r": [ + { + "field": "41c7b83f-e9b6-4878-93f2-411a83bf36c9" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-134", + "l": "eu-west-1", + "r": [ + { + "LoadBalancerName": "2a9f584d-2ef2-4724-b65b-262bb3789d5f" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-134", + "l": "us-west-1", + "r": [ + { + "LoadBalancerName": "2a9f584d-2ef2-4724-b65b-262bb3789d5f" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-135", + "l": "eu-west-1", + "r": [ + { + "LoadBalancerName": "0925ea2c-16a1-42a7-a340-a7378d17e38a" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-135", + "l": "us-west-1", + "r": [ + { + "LoadBalancerName": "0925ea2c-16a1-42a7-a340-a7378d17e38a" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-136", + "l": "eu-west-1", + "r": [ + { + "LoadBalancerArn": "d3f86561-05ae-4ef5-9de9-a1451f1398bc" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-136", + "l": "us-west-1", + "r": [ + { + "LoadBalancerArn": "d3f86561-05ae-4ef5-9de9-a1451f1398bc" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-137", + "l": "eu-west-1", + "r": [ + { + "LoadBalancerArn": "87e9ee30-7c4e-4c8e-bb5d-3128e766e93b" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-137", + "l": "us-west-1", + "r": [ + { + "LoadBalancerArn": "87e9ee30-7c4e-4c8e-bb5d-3128e766e93b" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-138", + "l": "multiregion", + "r": [ + { + "Arn": "ed2d9f29-2dd3-4e72-a006-3cc714c28b86" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-139", + "l": "eu-west-1", + "r": [ + { + "field": "462120da-e2de-4f74-9c59-1f6d676ce577" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-139", + "l": "us-west-1", + "r": [ + { + "field": "462120da-e2de-4f74-9c59-1f6d676ce577" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-140", + "l": "multiregion", + "r": [ + { + "Arn": "51aa2680-6074-4d31-8e70-3b9039b9fd7a" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-141", + "l": "multiregion", + "r": [ + { + "Arn": "53684763-c1e0-4cbb-8c19-c15c2d4d0308" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-142", + "l": "multiregion", + "r": [ + { + "Name": "d4b35558-ffa9-4ec8-ba67-fb6c04432ac4" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-143", + "l": "eu-west-1", + "r": [ + { + "field": "a89d0e61-bbdc-429f-9bc1-3f7e16bf240c" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-143", + "l": "us-west-1", + "r": [ + { + "field": "a89d0e61-bbdc-429f-9bc1-3f7e16bf240c" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-144", + "l": "eu-west-1", + "r": [ + { + "field": "c5f8579f-bfc3-4c10-b4a7-fef2583e017e" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-144", + "l": "us-west-1", + "r": [ + { + "field": "c5f8579f-bfc3-4c10-b4a7-fef2583e017e" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-145", + "l": "eu-west-1", + "r": [ + { + "account_id": "e6e7c7af-4348-4b63-8b8e-b81492c33ad7" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-145", + "l": "us-west-1", + "r": [ + { + "account_id": "e6e7c7af-4348-4b63-8b8e-b81492c33ad7" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-146", + "l": "eu-west-1", + "r": [ + { + "NetworkAclId": "8ecb83da-caa7-4b03-8078-8db682713a5e", + "VpcId": "935ac1e8-d0ba-4c09-a277-ae0074ced1cb", + "OwnerId": "5f221c6b-3825-4160-bcb2-ae35e4d0efaf" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-146", + "l": "us-west-1", + "r": [ + { + "NetworkAclId": "8ecb83da-caa7-4b03-8078-8db682713a5e", + "VpcId": "935ac1e8-d0ba-4c09-a277-ae0074ced1cb", + "OwnerId": "5f221c6b-3825-4160-bcb2-ae35e4d0efaf" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-147", + "l": "eu-west-1", + "r": [ + { + "VolumeId": "58b305ab-422d-4486-b8f2-8237f01fd1ea" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-147", + "l": "us-west-1", + "r": [ + { + "VolumeId": "58b305ab-422d-4486-b8f2-8237f01fd1ea" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-148", + "l": "multiregion", + "r": [ + { + "Name": "440d75f8-008d-4e46-8876-da51572011b1" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-149", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "436e80d7-95be-4ccc-9625-36cb3858ca1a" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-149", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "436e80d7-95be-4ccc-9625-36cb3858ca1a" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-150", + "l": "eu-west-1", + "r": [ + { + "stageName": "90fd6810-715e-4ee8-af04-58538c10d18b", + "restApiId": "34264bdc-3926-4ac5-91d4-088dc4964570" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-150", + "l": "us-west-1", + "r": [ + { + "stageName": "90fd6810-715e-4ee8-af04-58538c10d18b", + "restApiId": "34264bdc-3926-4ac5-91d4-088dc4964570" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-151", + "l": "eu-west-1", + "r": [ + { + "GroupId": "36dc074c-fc00-4dad-91bd-63cb23b7b5c0", + "VpcId": "c54e38fa-7241-41ce-b4a2-a20dd4395e0b", + "OwnerId": "e42064e4-d99c-4ff2-80b5-417fe9d47e75" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-151", + "l": "us-west-1", + "r": [ + { + "GroupId": "36dc074c-fc00-4dad-91bd-63cb23b7b5c0", + "VpcId": "c54e38fa-7241-41ce-b4a2-a20dd4395e0b", + "OwnerId": "e42064e4-d99c-4ff2-80b5-417fe9d47e75" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-152", + "l": "eu-west-1", + "r": [ + { + "LoadBalancerName": "b7712452-155f-4213-ad9e-5d6853794b44" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-152", + "l": "us-west-1", + "r": [ + { + "LoadBalancerName": "b7712452-155f-4213-ad9e-5d6853794b44" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-153", + "l": "eu-west-1", + "r": [ + { + "ARN": "51b5430a-e08a-4004-81ee-9da89639a308" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-153", + "l": "us-west-1", + "r": [ + { + "ARN": "51b5430a-e08a-4004-81ee-9da89639a308" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-154", + "l": "eu-west-1", + "r": [ + { + "ARN": "8c3e0eed-0c39-4979-9b58-6be6e3c2df57" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-154", + "l": "us-west-1", + "r": [ + { + "ARN": "8c3e0eed-0c39-4979-9b58-6be6e3c2df57" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-155", + "l": "eu-west-1", + "r": [ + { + "ARN": "4d65ed37-5c71-4ee1-9fc7-ca81828a76de" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-155", + "l": "us-west-1", + "r": [ + { + "ARN": "4d65ed37-5c71-4ee1-9fc7-ca81828a76de" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-156", + "l": "eu-west-1", + "r": [ + { + "ARN": "fb499ba7-4fad-4ad5-a229-115a56d59b61" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-156", + "l": "us-west-1", + "r": [ + { + "ARN": "fb499ba7-4fad-4ad5-a229-115a56d59b61" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-157", + "l": "eu-west-1", + "r": [ + { + "DBClusterArn": "03a7fcab-128b-40a8-a144-3d845e725610" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-157", + "l": "us-west-1", + "r": [ + { + "DBClusterArn": "03a7fcab-128b-40a8-a144-3d845e725610" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-158", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "e78bf0cc-4c03-42cc-9e8d-7fa89a8b1110" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-158", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "e78bf0cc-4c03-42cc-9e8d-7fa89a8b1110" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-159", + "l": "eu-west-1", + "r": [ + { + "field": "117b92bf-2066-43e5-8745-ca7e8f8899c9" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-159", + "l": "us-west-1", + "r": [ + { + "field": "117b92bf-2066-43e5-8745-ca7e8f8899c9" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-160", + "l": "eu-west-1", + "r": [ + { + "field": "d1cc1de2-ca11-4df3-bd33-23d6971a4915" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-160", + "l": "us-west-1", + "r": [ + { + "field": "d1cc1de2-ca11-4df3-bd33-23d6971a4915" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-161", + "l": "eu-west-1", + "r": [ + { + "field": "e404cecb-69a5-451c-b749-46215443dc01" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-161", + "l": "us-west-1", + "r": [ + { + "field": "e404cecb-69a5-451c-b749-46215443dc01" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-162", + "l": "eu-west-1", + "r": [ + { + "field": "f77e4d80-6a83-4774-873e-afe1a68bbc32" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-162", + "l": "us-west-1", + "r": [ + { + "field": "f77e4d80-6a83-4774-873e-afe1a68bbc32" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-163", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "935a1d6a-3920-4b45-b0bf-46b4db33f1c4" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-163", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "935a1d6a-3920-4b45-b0bf-46b4db33f1c4" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-164", + "l": "eu-west-1", + "r": [ + { + "ClusterIdentifier": "c6665bac-93a4-4690-9726-129bf2d0b3c1" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-164", + "l": "us-west-1", + "r": [ + { + "ClusterIdentifier": "c6665bac-93a4-4690-9726-129bf2d0b3c1" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-165", + "l": "eu-west-1", + "r": [ + { + "serviceArn": "1ea0e702-0cec-4ef5-9c48-b741514ff3ed" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-165", + "l": "us-west-1", + "r": [ + { + "serviceArn": "1ea0e702-0cec-4ef5-9c48-b741514ff3ed" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-166", + "l": "eu-west-1", + "r": [ + { + "GroupId": "b8eb9e4f-a8a0-4880-bb03-5e1575179185", + "VpcId": "e4c2d9b0-4be4-46a7-92ef-851fb1bd0ca8", + "OwnerId": "29d1ac64-1eab-4f29-81f9-7dca951b5d9e" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-166", + "l": "us-west-1", + "r": [ + { + "GroupId": "b8eb9e4f-a8a0-4880-bb03-5e1575179185", + "VpcId": "e4c2d9b0-4be4-46a7-92ef-851fb1bd0ca8", + "OwnerId": "29d1ac64-1eab-4f29-81f9-7dca951b5d9e" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-167", + "l": "eu-west-1", + "r": [ + { + "GroupId": "b63534a7-0866-473d-a5fc-ef0edf2c7b14", + "VpcId": "bc9e283b-9a61-490a-ba58-e28ea213e501", + "OwnerId": "2283b697-0320-42d0-88c5-548ccb291212" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-167", + "l": "us-west-1", + "r": [ + { + "GroupId": "b63534a7-0866-473d-a5fc-ef0edf2c7b14", + "VpcId": "bc9e283b-9a61-490a-ba58-e28ea213e501", + "OwnerId": "2283b697-0320-42d0-88c5-548ccb291212" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-168", + "l": "eu-west-1", + "r": [ + { + "GroupId": "db115bcf-a855-4e0a-9894-233600ed5027", + "VpcId": "fbf7f9eb-4cad-47c4-a44f-9f4773d3adb8", + "OwnerId": "25628fa4-a22d-4e33-864c-c5070ea64105" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-168", + "l": "us-west-1", + "r": [ + { + "GroupId": "db115bcf-a855-4e0a-9894-233600ed5027", + "VpcId": "fbf7f9eb-4cad-47c4-a44f-9f4773d3adb8", + "OwnerId": "25628fa4-a22d-4e33-864c-c5070ea64105" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-169", + "l": "eu-west-1", + "r": [ + { + "GroupId": "b283c7e2-9052-458c-958a-5d23e144d422", + "VpcId": "2dfe1520-e338-43ea-a730-320af7eaf905", + "OwnerId": "93d7cf6d-e109-4fee-a43a-e19314f74217" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-169", + "l": "us-west-1", + "r": [ + { + "GroupId": "b283c7e2-9052-458c-958a-5d23e144d422", + "VpcId": "2dfe1520-e338-43ea-a730-320af7eaf905", + "OwnerId": "93d7cf6d-e109-4fee-a43a-e19314f74217" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-170", + "l": "eu-west-1", + "r": [ + { + "GroupId": "c8a3508d-a41e-4405-852e-db700c87d310", + "VpcId": "a7691999-8bf6-4a0d-9d22-0f0b48dfef75", + "OwnerId": "5d8320df-2b11-4521-ac02-1d7bd27843bf" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-170", + "l": "us-west-1", + "r": [ + { + "GroupId": "c8a3508d-a41e-4405-852e-db700c87d310", + "VpcId": "a7691999-8bf6-4a0d-9d22-0f0b48dfef75", + "OwnerId": "5d8320df-2b11-4521-ac02-1d7bd27843bf" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-171", + "l": "eu-west-1", + "r": [ + { + "GroupId": "8558d575-370e-4e0a-98a2-585feb6a2284", + "VpcId": "ffd03cb5-0f64-4316-a129-7232d3cf14ff", + "OwnerId": "10b28b67-be9d-4fa2-ba2b-644ed25035d3" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-171", + "l": "us-west-1", + "r": [ + { + "GroupId": "8558d575-370e-4e0a-98a2-585feb6a2284", + "VpcId": "ffd03cb5-0f64-4316-a129-7232d3cf14ff", + "OwnerId": "10b28b67-be9d-4fa2-ba2b-644ed25035d3" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-172", + "l": "eu-west-1", + "r": [ + { + "GroupId": "d045f109-4bab-4d0d-b41b-b2b9e582cbbe", + "VpcId": "a4605161-28c9-4171-b907-8fdce371f734", + "OwnerId": "66b87f71-f950-4732-8b46-6fe843b9c2bb" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-172", + "l": "us-west-1", + "r": [ + { + "GroupId": "d045f109-4bab-4d0d-b41b-b2b9e582cbbe", + "VpcId": "a4605161-28c9-4171-b907-8fdce371f734", + "OwnerId": "66b87f71-f950-4732-8b46-6fe843b9c2bb" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-173", + "l": "eu-west-1", + "r": [ + { + "GroupId": "ab2f2b15-ec54-4c7f-b0b6-4708046489ea", + "VpcId": "b9818858-bdc6-4230-9766-d1894f67edf2", + "OwnerId": "9ad1d875-aaf4-40d1-a326-ed484bc2cce5" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-173", + "l": "us-west-1", + "r": [ + { + "GroupId": "ab2f2b15-ec54-4c7f-b0b6-4708046489ea", + "VpcId": "b9818858-bdc6-4230-9766-d1894f67edf2", + "OwnerId": "9ad1d875-aaf4-40d1-a326-ed484bc2cce5" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-174", + "l": "eu-west-1", + "r": [ + { + "DBClusterArn": "31a75d7f-b2e7-4d2e-88ec-10bbb844fa69" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-174", + "l": "us-west-1", + "r": [ + { + "DBClusterArn": "31a75d7f-b2e7-4d2e-88ec-10bbb844fa69" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-175", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "6c7c692c-1057-4323-b129-f8c553f74647" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-175", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "6c7c692c-1057-4323-b129-f8c553f74647" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-176", + "l": "eu-west-1", + "r": [ + { + "DBSnapshotArn": "4df4d71b-231b-4533-ae92-3ff89b85f078", + "DBInstanceIdentifier": "2ed2be7b-f2d1-420a-9ad7-eb9b66249535" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-176", + "l": "us-west-1", + "r": [ + { + "DBSnapshotArn": "4df4d71b-231b-4533-ae92-3ff89b85f078", + "DBInstanceIdentifier": "2ed2be7b-f2d1-420a-9ad7-eb9b66249535" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-177", + "l": "eu-west-1", + "r": [ + { + "stageName": "9792edbc-de5a-47a6-9d73-5166f9b8c278", + "restApiId": "4587de23-d66e-422b-9038-46d7be6eda9c" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-177", + "l": "us-west-1", + "r": [ + { + "stageName": "9792edbc-de5a-47a6-9d73-5166f9b8c278", + "restApiId": "4587de23-d66e-422b-9038-46d7be6eda9c" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-178", + "l": "eu-west-1", + "r": [ + { + "stageName": "675c1ad5-aa1f-4cd5-a6c2-b7bed6fa9940", + "restApiIds": "dcfd6add-1543-4d3e-9539-1a78a9f8f3aa" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-178", + "l": "us-west-1", + "r": [ + { + "stageName": "675c1ad5-aa1f-4cd5-a6c2-b7bed6fa9940", + "restApiIds": "dcfd6add-1543-4d3e-9539-1a78a9f8f3aa" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-179", + "l": "multiregion", + "r": [ + { + "ARN": "84bf2c40-dc17-4f88-878b-04a091fced5a" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-180", + "l": "multiregion", + "r": [ + { + "ARN": "267ad91f-4434-46ce-8911-e9fce8b00cfe" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-181", + "l": "eu-west-1", + "r": [ + { + "ReplicationInstanceIdentifier": "5a9a80fc-b1ab-4b90-bc1b-7314fb218120", + "ReplicationInstanceArn": "13970328-97ec-49d5-9d0c-9880cb9976fc" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-181", + "l": "us-west-1", + "r": [ + { + "ReplicationInstanceIdentifier": "5a9a80fc-b1ab-4b90-bc1b-7314fb218120", + "ReplicationInstanceArn": "13970328-97ec-49d5-9d0c-9880cb9976fc" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-182", + "l": "eu-west-1", + "r": [ + { + "TableArn": "79f60372-41f2-4be7-8d35-38b91e8945a6" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-182", + "l": "us-west-1", + "r": [ + { + "TableArn": "79f60372-41f2-4be7-8d35-38b91e8945a6" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-183", + "l": "eu-west-1", + "r": [ + { + "TableArn": "d4d2657f-e4fb-4798-9e78-6804e980b0fa" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-183", + "l": "us-west-1", + "r": [ + { + "TableArn": "d4d2657f-e4fb-4798-9e78-6804e980b0fa" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-184", + "l": "eu-west-1", + "r": [ + { + "ClusterArn": "ef8c27ec-9520-478d-bab7-e3569bba44f1" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-184", + "l": "us-west-1", + "r": [ + { + "ClusterArn": "ef8c27ec-9520-478d-bab7-e3569bba44f1" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-185", + "l": "eu-west-1", + "r": [ + { + "InstanceId": "c0fa8fcd-afaa-45a1-8793-0e67eebc9f1d", + "OwnerId": "e7e48922-eab8-49cb-af92-b6bae901f8fe" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-185", + "l": "us-west-1", + "r": [ + { + "InstanceId": "c0fa8fcd-afaa-45a1-8793-0e67eebc9f1d", + "OwnerId": "e7e48922-eab8-49cb-af92-b6bae901f8fe" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-186", + "l": "eu-west-1", + "r": [ + { + "InstanceId": "07413f32-c35b-43cd-a3a9-f2deec89b1cf", + "OwnerId": "282aa3f9-dd49-4253-b1ed-feab928d9962" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-186", + "l": "us-west-1", + "r": [ + { + "InstanceId": "07413f32-c35b-43cd-a3a9-f2deec89b1cf", + "OwnerId": "282aa3f9-dd49-4253-b1ed-feab928d9962" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-187", + "l": "eu-west-1", + "r": [ + { + "VpcId": "4d6c8240-ef08-437e-98b7-1d8bdb5e92e9", + "OwnerId": "e4f75af3-7725-424b-b55a-43f9ad5224ff" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-187", + "l": "us-west-1", + "r": [ + { + "VpcId": "4d6c8240-ef08-437e-98b7-1d8bdb5e92e9", + "OwnerId": "e4f75af3-7725-424b-b55a-43f9ad5224ff" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-188", + "l": "eu-west-1", + "r": [ + { + "NetworkAclId": "de3d01d9-bc68-4eaa-a4d7-38ae6e69f3a8", + "OwnerId": "f1a93dc4-83ef-407a-a974-9aaf6f051576" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-188", + "l": "us-west-1", + "r": [ + { + "NetworkAclId": "de3d01d9-bc68-4eaa-a4d7-38ae6e69f3a8", + "OwnerId": "f1a93dc4-83ef-407a-a974-9aaf6f051576" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-189", + "l": "eu-west-1", + "r": [ + { + "InstanceId": "6f4b72d5-55bf-4448-b568-e462f6edc6a4", + "OwnerId": "bf5f66c3-7ddb-4f9c-9772-e69e18b3b0dc" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-189", + "l": "us-west-1", + "r": [ + { + "InstanceId": "6f4b72d5-55bf-4448-b568-e462f6edc6a4", + "OwnerId": "bf5f66c3-7ddb-4f9c-9772-e69e18b3b0dc" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-190", + "l": "eu-west-1", + "r": [ + { + "taskDefinitionArn": "f0b11a8e-ce5a-460f-ae06-96940187ffe2" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-190", + "l": "us-west-1", + "r": [ + { + "taskDefinitionArn": "f0b11a8e-ce5a-460f-ae06-96940187ffe2" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-191", + "l": "eu-west-1", + "r": [ + { + "FileSystemArn": "8ff4d86d-44c7-4e57-83ab-56ed1d5efbd7" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-191", + "l": "us-west-1", + "r": [ + { + "FileSystemArn": "8ff4d86d-44c7-4e57-83ab-56ed1d5efbd7" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-192", + "l": "eu-west-1", + "r": [ + { + "EnvironmentArn": "d353dfa7-8c9c-45f2-9c50-a55325604757" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-192", + "l": "us-west-1", + "r": [ + { + "EnvironmentArn": "d353dfa7-8c9c-45f2-9c50-a55325604757" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-193", + "l": "eu-west-1", + "r": [ + { + "LoadBalancerArn": "12751a25-d0ea-45f7-bb65-cce006c18e34" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-193", + "l": "us-west-1", + "r": [ + { + "LoadBalancerArn": "12751a25-d0ea-45f7-bb65-cce006c18e34" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-194", + "l": "eu-west-1", + "r": [ + { + "LoadBalancerArn": "c528b29f-be7e-4bb8-aa18-3ca2510d6d2b" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-194", + "l": "us-west-1", + "r": [ + { + "LoadBalancerArn": "c528b29f-be7e-4bb8-aa18-3ca2510d6d2b" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-195", + "l": "eu-west-1", + "r": [ + { + "LoadBalancerArn": "b976d15e-fc7b-4fd8-82bd-6ad95475a53f" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-195", + "l": "us-west-1", + "r": [ + { + "LoadBalancerArn": "b976d15e-fc7b-4fd8-82bd-6ad95475a53f" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-196", + "l": "eu-west-1", + "r": [ + { + "ClusterArn": "477531a0-2cb9-4037-83f5-b697c3b0ef89" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-196", + "l": "us-west-1", + "r": [ + { + "ClusterArn": "477531a0-2cb9-4037-83f5-b697c3b0ef89" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-197", + "l": "eu-west-1", + "r": [ + { + "ARN": "b9a9505f-a773-4967-916f-e5e07b1a475c" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-197", + "l": "us-west-1", + "r": [ + { + "ARN": "b9a9505f-a773-4967-916f-e5e07b1a475c" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-198", + "l": "eu-west-1", + "r": [ + { + "ARN": "42cc90b4-c804-4206-b8d3-e6bdcb0acd91" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-198", + "l": "us-west-1", + "r": [ + { + "ARN": "42cc90b4-c804-4206-b8d3-e6bdcb0acd91" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-199", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "0847dc92-2847-40a8-8531-b940bea62592" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-199", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "0847dc92-2847-40a8-8531-b940bea62592" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-200", + "l": "eu-west-1", + "r": [ + { + "DBClusterArn": "ca1f7b06-104b-4acc-b542-e716487f204b" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-200", + "l": "us-west-1", + "r": [ + { + "DBClusterArn": "ca1f7b06-104b-4acc-b542-e716487f204b" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-201", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "019fde3f-5ee9-4115-bf3f-dc7347ef75c1" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-201", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "019fde3f-5ee9-4115-bf3f-dc7347ef75c1" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-202", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "9163fe47-f74a-4ab0-9d63-da1fc023545e" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-202", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "9163fe47-f74a-4ab0-9d63-da1fc023545e" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-203", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "35c8a049-8810-4e3e-80ab-af85333ce65e" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-203", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "35c8a049-8810-4e3e-80ab-af85333ce65e" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-204", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "3044fb4b-1e5a-4240-a49a-a5bf182746fb" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-204", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "3044fb4b-1e5a-4240-a49a-a5bf182746fb" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-205", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "d26c9cfa-da8e-4f49-8681-ba0a73553224" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-205", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "d26c9cfa-da8e-4f49-8681-ba0a73553224" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-206", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "f45cb395-13d3-4e51-819c-a038d469f2d3" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-206", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "f45cb395-13d3-4e51-819c-a038d469f2d3" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-207", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "5c60d036-c4b6-4784-b1f3-d94ecb13c6c3" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-207", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "5c60d036-c4b6-4784-b1f3-d94ecb13c6c3" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-208", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "c0f4f105-c4a6-4cff-8766-ae2543cd829a" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-208", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "c0f4f105-c4a6-4cff-8766-ae2543cd829a" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-209", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "ff2792f0-ad87-49c3-b346-f5d278338551" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-209", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "ff2792f0-ad87-49c3-b346-f5d278338551" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-210", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "2d0e1395-fc1f-428e-9f68-e22b7840c825" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-210", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "2d0e1395-fc1f-428e-9f68-e22b7840c825" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-211", + "l": "eu-west-1", + "r": [ + { + "DBClusterArn": "e364d44c-8774-4734-a7ae-728d9676153c" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-211", + "l": "us-west-1", + "r": [ + { + "DBClusterArn": "e364d44c-8774-4734-a7ae-728d9676153c" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-212", + "l": "eu-west-1", + "r": [ + { + "DBClusterArn": "51c4c740-cbbe-4542-83c2-ad39b6992c4b" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-212", + "l": "us-west-1", + "r": [ + { + "DBClusterArn": "51c4c740-cbbe-4542-83c2-ad39b6992c4b" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-213", + "l": "eu-west-1", + "r": [ + { + "DBClusterArn": "e7dc1d2c-d74d-40ad-9ac5-a0ff81530a36" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-213", + "l": "us-west-1", + "r": [ + { + "DBClusterArn": "e7dc1d2c-d74d-40ad-9ac5-a0ff81530a36" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-214", + "l": "eu-west-1", + "r": [ + { + "ClusterIdentifier": "24d880a0-014a-4224-977a-c7cd47a04663" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-214", + "l": "us-west-1", + "r": [ + { + "ClusterIdentifier": "24d880a0-014a-4224-977a-c7cd47a04663" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-215", + "l": "eu-west-1", + "r": [ + { + "ClusterIdentifier": "7e64daa6-eb42-4cdf-aa6f-5dc0c5e5fc3c" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-215", + "l": "us-west-1", + "r": [ + { + "ClusterIdentifier": "7e64daa6-eb42-4cdf-aa6f-5dc0c5e5fc3c" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-216", + "l": "eu-west-1", + "r": [ + { + "ClusterIdentifier": "21de4951-3b21-4a84-a323-b6d1f9bdf367" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-216", + "l": "us-west-1", + "r": [ + { + "ClusterIdentifier": "21de4951-3b21-4a84-a323-b6d1f9bdf367" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-217", + "l": "eu-west-1", + "r": [ + { + "ClusterIdentifier": "b6465f2c-1098-4f45-963f-ab5122b5ff3a" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-217", + "l": "us-west-1", + "r": [ + { + "ClusterIdentifier": "b6465f2c-1098-4f45-963f-ab5122b5ff3a" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-218", + "l": "eu-west-1", + "r": [ + { + "Name": "caeabb1e-a8fd-4e53-9259-302a59cda4a9", + "ARN": "e5546d2a-29ca-47f2-8073-4982e8c7f3ee" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-218", + "l": "us-west-1", + "r": [ + { + "Name": "caeabb1e-a8fd-4e53-9259-302a59cda4a9", + "ARN": "e5546d2a-29ca-47f2-8073-4982e8c7f3ee" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-219", + "l": "eu-west-1", + "r": [ + { + "Name": "8e87633c-2b70-456e-8169-65545b770dd6", + "ARN": "c340c849-ae42-4dd0-9ec1-af09a54bb5e0" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-219", + "l": "us-west-1", + "r": [ + { + "Name": "8e87633c-2b70-456e-8169-65545b770dd6", + "ARN": "c340c849-ae42-4dd0-9ec1-af09a54bb5e0" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-220", + "l": "eu-west-1", + "r": [ + { + "Name": "a94fc5ec-b0f1-4e68-80e7-7d2103910840", + "ARN": "7710cec5-8c5e-4faf-96d2-07ea292dc5e9" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-220", + "l": "us-west-1", + "r": [ + { + "Name": "a94fc5ec-b0f1-4e68-80e7-7d2103910840", + "ARN": "7710cec5-8c5e-4faf-96d2-07ea292dc5e9" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-221", + "l": "eu-west-1", + "r": [ + { + "TopicArn": "0b3700e3-25d6-4702-80d3-37bc531b1701" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-221", + "l": "us-west-1", + "r": [ + { + "TopicArn": "0b3700e3-25d6-4702-80d3-37bc531b1701" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-222", + "l": "eu-west-1", + "r": [ + { + "InstanceId": "7af448a0-10da-4455-ab3d-35e2bd922bc3", + "OwnerId": "864bf74e-1336-41b7-8e5a-540c40e25ccd" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-222", + "l": "us-west-1", + "r": [ + { + "InstanceId": "7af448a0-10da-4455-ab3d-35e2bd922bc3", + "OwnerId": "864bf74e-1336-41b7-8e5a-540c40e25ccd" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-223", + "l": "eu-west-1", + "r": [ + { + "InstanceId": "87c72d4a-ae3e-4f2f-a19f-0a68486bc68f", + "OwnerId": "1c8f4085-5664-4796-a44b-e0579f6edbe6" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-223", + "l": "us-west-1", + "r": [ + { + "InstanceId": "87c72d4a-ae3e-4f2f-a19f-0a68486bc68f", + "OwnerId": "1c8f4085-5664-4796-a44b-e0579f6edbe6" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-224", + "l": "eu-west-1", + "r": [ + { + "InstanceId": "d207f872-e033-4d56-bac4-7c28fa03f755", + "OwnerId": "bc2c921e-d523-4ad7-a4ed-7170a0b59ca9" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-224", + "l": "us-west-1", + "r": [ + { + "InstanceId": "d207f872-e033-4d56-bac4-7c28fa03f755", + "OwnerId": "bc2c921e-d523-4ad7-a4ed-7170a0b59ca9" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-225", + "l": "eu-west-1", + "r": [ + { + "arn": "027d795a-dc91-4905-a354-b21a89a13f9f" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-225", + "l": "us-west-1", + "r": [ + { + "arn": "027d795a-dc91-4905-a354-b21a89a13f9f" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-226", + "l": "eu-west-1", + "r": [ + { + "arn": "58139cb0-6542-4471-ac92-b93d92adfdb0" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-226", + "l": "us-west-1", + "r": [ + { + "arn": "58139cb0-6542-4471-ac92-b93d92adfdb0" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-227", + "l": "eu-west-1", + "r": [ + { + "arn": "f0797ed9-5d85-4fde-864f-b1167d1067d0" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-227", + "l": "us-west-1", + "r": [ + { + "arn": "f0797ed9-5d85-4fde-864f-b1167d1067d0" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-228", + "l": "eu-west-1", + "r": [ + { + "repositoryArn": "411d69ec-1838-45df-ab0c-ca5509922254" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-228", + "l": "us-west-1", + "r": [ + { + "repositoryArn": "411d69ec-1838-45df-ab0c-ca5509922254" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-229", + "l": "eu-west-1", + "r": [ + { + "repositoryArn": "a84a1ab7-fe43-473b-9f22-00642d008785" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-229", + "l": "us-west-1", + "r": [ + { + "repositoryArn": "a84a1ab7-fe43-473b-9f22-00642d008785" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-230", + "l": "eu-west-1", + "r": [ + { + "repositoryArn": "18320840-9400-404c-93a9-2682dd26978b" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-230", + "l": "us-west-1", + "r": [ + { + "repositoryArn": "18320840-9400-404c-93a9-2682dd26978b" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-231", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "457a95be-8232-4d93-96d6-b6f94714565f" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-231", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "457a95be-8232-4d93-96d6-b6f94714565f" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-232", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "f0482119-9b69-43cd-9202-8a0e9ac42599" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-232", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "f0482119-9b69-43cd-9202-8a0e9ac42599" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-233", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "fd838b55-a13d-45c7-bf85-a0d04f8721fc" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-233", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "fd838b55-a13d-45c7-bf85-a0d04f8721fc" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-234", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "366f8cad-897a-4a52-8f86-d432e337b00a" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-234", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "366f8cad-897a-4a52-8f86-d432e337b00a" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-235", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "97d3173e-3c16-49e3-8a53-259f6dc6c82b" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-235", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "97d3173e-3c16-49e3-8a53-259f6dc6c82b" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-236", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "9e0a74af-2193-47d3-8f91-02746d54b132" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-236", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "9e0a74af-2193-47d3-8f91-02746d54b132" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-237", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "2e8edc21-25ac-4533-8285-459c5add8fd7" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-237", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "2e8edc21-25ac-4533-8285-459c5add8fd7" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-238", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "043c8049-10e6-42a9-863c-40e1b8217b57" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-238", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "043c8049-10e6-42a9-863c-40e1b8217b57" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-239", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "13e5b180-5d3d-419b-9b17-c7056e4ccce4" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-239", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "13e5b180-5d3d-419b-9b17-c7056e4ccce4" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-240", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "339dc6e7-412e-41f1-964c-86ad1ebab9b6" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-240", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "339dc6e7-412e-41f1-964c-86ad1ebab9b6" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-241", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "a16cf123-b62e-4ebe-a750-a928641385d8" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-241", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "a16cf123-b62e-4ebe-a750-a928641385d8" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-242", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "e8cc5754-3f41-47c5-b24c-fae46519619f" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-242", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "e8cc5754-3f41-47c5-b24c-fae46519619f" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-243", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "10d0373e-fad8-448a-a721-146f9874f861" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-243", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "10d0373e-fad8-448a-a721-146f9874f861" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-244", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "f5bff5aa-4a7d-4ec1-87e5-6d280d0a3c48" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-244", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "f5bff5aa-4a7d-4ec1-87e5-6d280d0a3c48" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-245", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "d8b457de-eccf-4d17-8b60-dbeb609e5001" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-245", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "d8b457de-eccf-4d17-8b60-dbeb609e5001" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-246", + "l": "eu-west-1", + "r": [ + { + "TransitGatewayArn": "6a47fef0-3dca-4ba7-b735-60281464189f" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-246", + "l": "us-west-1", + "r": [ + { + "TransitGatewayArn": "6a47fef0-3dca-4ba7-b735-60281464189f" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-247", + "l": "eu-west-1", + "r": [ + { + "TransitGatewayArn": "56131c4c-78c9-4601-921e-67eb67105e7a" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-247", + "l": "us-west-1", + "r": [ + { + "TransitGatewayArn": "56131c4c-78c9-4601-921e-67eb67105e7a" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-248", + "l": "eu-west-1", + "r": [ + { + "stageName": "0e32663a-525a-442e-a5a3-ea52af193c08", + "restApiId": "e4616212-b5a0-4341-a4a7-3f0fb2df4db4" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-248", + "l": "us-west-1", + "r": [ + { + "stageName": "0e32663a-525a-442e-a5a3-ea52af193c08", + "restApiId": "e4616212-b5a0-4341-a4a7-3f0fb2df4db4" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-249", + "l": "eu-west-1", + "r": [ + { + "id": "5dfdd140-f48f-4d88-9a19-becf0c1e5c64", + "name": "c95063aa-cc52-4c9b-af6d-8f5c1229c2ba" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-249", + "l": "us-west-1", + "r": [ + { + "id": "5dfdd140-f48f-4d88-9a19-becf0c1e5c64", + "name": "c95063aa-cc52-4c9b-af6d-8f5c1229c2ba" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-250", + "l": "eu-west-1", + "r": [ + { + "stageName": "3be79fd4-353f-4380-9e9e-799bf2ae4a46", + "restApiId": "89e5db50-fa4e-4e5c-a2ce-f9ca74ac8c0f" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-250", + "l": "us-west-1", + "r": [ + { + "stageName": "3be79fd4-353f-4380-9e9e-799bf2ae4a46", + "restApiId": "89e5db50-fa4e-4e5c-a2ce-f9ca74ac8c0f" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-251", + "l": "eu-west-1", + "r": [ + { + "flowArn": "55e56e78-fb2a-46d8-9479-9df642037f28" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-251", + "l": "us-west-1", + "r": [ + { + "flowArn": "55e56e78-fb2a-46d8-9479-9df642037f28" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-252", + "l": "eu-west-1", + "r": [ + { + "CatalogId": "fdfe9a99-3059-412b-a5a9-41fb08b5e17e", + "DataCatalogEncryptionSettings": "3e968d54-1db4-4d75-af5b-814fd36a7b88" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-252", + "l": "us-west-1", + "r": [ + { + "CatalogId": "fdfe9a99-3059-412b-a5a9-41fb08b5e17e", + "DataCatalogEncryptionSettings": "3e968d54-1db4-4d75-af5b-814fd36a7b88" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-253", + "l": "eu-west-1", + "r": [ + { + "CatalogId": "b244dd8d-8ff6-494c-a5ce-8dcf49c9b32d", + "DataCatalogEncryptionSettings": "9e2792e4-3562-46e8-bac7-0f54920e4f39" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-253", + "l": "us-west-1", + "r": [ + { + "CatalogId": "b244dd8d-8ff6-494c-a5ce-8dcf49c9b32d", + "DataCatalogEncryptionSettings": "9e2792e4-3562-46e8-bac7-0f54920e4f39" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-254", + "l": "eu-west-1", + "r": [ + { + "Name": "0bfedca8-be16-4e05-a1aa-c1eb68c2dbf2" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-254", + "l": "us-west-1", + "r": [ + { + "Name": "0bfedca8-be16-4e05-a1aa-c1eb68c2dbf2" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-255", + "l": "eu-west-1", + "r": [ + { + "Name": "4285a3d7-3734-4a1a-b2f3-47836938fd44" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-255", + "l": "us-west-1", + "r": [ + { + "Name": "4285a3d7-3734-4a1a-b2f3-47836938fd44" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-256", + "l": "eu-west-1", + "r": [ + { + "Name": "8d9882d7-cd0c-4b99-8cf5-5e2da4c07c95" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-256", + "l": "us-west-1", + "r": [ + { + "Name": "8d9882d7-cd0c-4b99-8cf5-5e2da4c07c95" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-257", + "l": "eu-west-1", + "r": [ + { + "ClusterArn": "f21fd550-8c02-4130-832c-f2d1b1d35a4d" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-257", + "l": "us-west-1", + "r": [ + { + "ClusterArn": "f21fd550-8c02-4130-832c-f2d1b1d35a4d" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-258", + "l": "eu-west-1", + "r": [ + { + "ClusterArn": "71a11b89-8fc5-4c03-8712-ec472601ac0f" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-258", + "l": "us-west-1", + "r": [ + { + "ClusterArn": "71a11b89-8fc5-4c03-8712-ec472601ac0f" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-259", + "l": "eu-west-1", + "r": [ + { + "ClusterArn": "3ecf10f1-425b-49a5-8e33-97711e641403" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-259", + "l": "us-west-1", + "r": [ + { + "ClusterArn": "3ecf10f1-425b-49a5-8e33-97711e641403" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-260", + "l": "eu-west-1", + "r": [ + { + "ClusterArn": "3318c9f3-29a8-4330-ab73-658cc61ead11" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-260", + "l": "us-west-1", + "r": [ + { + "ClusterArn": "3318c9f3-29a8-4330-ab73-658cc61ead11" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-261", + "l": "eu-west-1", + "r": [ + { + "InternetGatewayId": "1703718e-1276-49de-96b1-ea5e7e7707a3", + "OwnerId": "83b834d2-b0d4-40e6-8a5e-d62ed82547f5" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-261", + "l": "us-west-1", + "r": [ + { + "InternetGatewayId": "1703718e-1276-49de-96b1-ea5e7e7707a3", + "OwnerId": "83b834d2-b0d4-40e6-8a5e-d62ed82547f5" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-262", + "l": "eu-west-1", + "r": [ + { + "ServiceId": "aee6d151-6f43-4174-9abe-ad6c602ecf64" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-262", + "l": "us-west-1", + "r": [ + { + "ServiceId": "aee6d151-6f43-4174-9abe-ad6c602ecf64" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-263", + "l": "eu-west-1", + "r": [ + { + "VpnGatewayId": "3f3e86f4-e0af-4868-91b9-6cbf5702e8cd" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-263", + "l": "us-west-1", + "r": [ + { + "VpnGatewayId": "3f3e86f4-e0af-4868-91b9-6cbf5702e8cd" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-264", + "l": "eu-west-1", + "r": [ + { + "ARN": "ff793976-d728-4119-abe1-d98c10c52359" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-264", + "l": "us-west-1", + "r": [ + { + "ARN": "ff793976-d728-4119-abe1-d98c10c52359" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-265", + "l": "eu-west-1", + "r": [ + { + "ARN": "dcb6a438-c8c0-4830-9dd6-232fca1ff66b" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-265", + "l": "us-west-1", + "r": [ + { + "ARN": "dcb6a438-c8c0-4830-9dd6-232fca1ff66b" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-266", + "l": "eu-west-1", + "r": [ + { + "ARN": "7d45a63f-0ed1-41df-a5fc-5b7608271339" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-266", + "l": "us-west-1", + "r": [ + { + "ARN": "7d45a63f-0ed1-41df-a5fc-5b7608271339" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-267", + "l": "eu-west-1", + "r": [ + { + "ARN": "3b1e325b-0a9c-4dd7-ac5d-e57923d74375" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-267", + "l": "us-west-1", + "r": [ + { + "ARN": "3b1e325b-0a9c-4dd7-ac5d-e57923d74375" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-268", + "l": "eu-west-1", + "r": [ + { + "ARN": "52fe72f2-1c24-48fd-b5fb-ce924d8a5236" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-268", + "l": "us-west-1", + "r": [ + { + "ARN": "52fe72f2-1c24-48fd-b5fb-ce924d8a5236" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-269", + "l": "eu-west-1", + "r": [ + { + "ARN": "8f35e28f-e68b-484f-89cd-20d87b7bd591" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-269", + "l": "us-west-1", + "r": [ + { + "ARN": "8f35e28f-e68b-484f-89cd-20d87b7bd591" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-270", + "l": "eu-west-1", + "r": [ + { + "ARN": "8078c035-e0da-4fdc-b156-750898cd3519" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-270", + "l": "us-west-1", + "r": [ + { + "ARN": "8078c035-e0da-4fdc-b156-750898cd3519" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-271", + "l": "eu-west-1", + "r": [ + { + "ARN": "0e7131c9-a4dd-43cf-9e9c-2e6a59771534" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-271", + "l": "us-west-1", + "r": [ + { + "ARN": "0e7131c9-a4dd-43cf-9e9c-2e6a59771534" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-272", + "l": "eu-west-1", + "r": [ + { + "ARN": "bc099dd8-aa93-4f6b-a068-be00f1c5f3b2" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-272", + "l": "us-west-1", + "r": [ + { + "ARN": "bc099dd8-aa93-4f6b-a068-be00f1c5f3b2" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-273", + "l": "eu-west-1", + "r": [ + { + "DBClusterArn": "38f6f9a2-7090-483e-b971-798fa550b682" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-273", + "l": "us-west-1", + "r": [ + { + "DBClusterArn": "38f6f9a2-7090-483e-b971-798fa550b682" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-274", + "l": "eu-west-1", + "r": [ + { + "DBClusterArn": "513c0425-61ef-48de-a0af-5ba5b633caee" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-274", + "l": "us-west-1", + "r": [ + { + "DBClusterArn": "513c0425-61ef-48de-a0af-5ba5b633caee" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-275", + "l": "eu-west-1", + "r": [ + { + "DBClusterArn": "d9e8dcbe-9512-4930-a27c-d9fadfd446fa" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-275", + "l": "us-west-1", + "r": [ + { + "DBClusterArn": "d9e8dcbe-9512-4930-a27c-d9fadfd446fa" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-276", + "l": "eu-west-1", + "r": [ + { + "DBClusterArn": "b3922f3b-1554-4db4-8e08-012b140ad61f" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-276", + "l": "us-west-1", + "r": [ + { + "DBClusterArn": "b3922f3b-1554-4db4-8e08-012b140ad61f" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-277", + "l": "eu-west-1", + "r": [ + { + "ARN": "3734c29a-f53b-4508-95fc-8b681b79be5d" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-277", + "l": "us-west-1", + "r": [ + { + "ARN": "3734c29a-f53b-4508-95fc-8b681b79be5d" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-278", + "l": "eu-west-1", + "r": [ + { + "id": "641e13e8-68d2-4dfd-9413-e9512f408725", + "resource": "09f72027-8c6e-4b78-8790-74c85e43e9b9" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-278", + "l": "us-west-1", + "r": [ + { + "id": "641e13e8-68d2-4dfd-9413-e9512f408725", + "resource": "09f72027-8c6e-4b78-8790-74c85e43e9b9" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-279", + "l": "eu-west-1", + "r": [ + { + "ARN": "baf3d866-ae46-4ce1-ba99-b418facadb65" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-279", + "l": "us-west-1", + "r": [ + { + "ARN": "baf3d866-ae46-4ce1-ba99-b418facadb65" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-280", + "l": "eu-west-1", + "r": [ + { + "ARN": "8128855f-2bc3-4667-ad58-96b414029531" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-280", + "l": "us-west-1", + "r": [ + { + "ARN": "8128855f-2bc3-4667-ad58-96b414029531" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-281", + "l": "eu-west-1", + "r": [ + { + "AutoScalingGroupARN": "e77792e4-b049-4442-b871-89f708ba6cf6" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-281", + "l": "us-west-1", + "r": [ + { + "AutoScalingGroupARN": "e77792e4-b049-4442-b871-89f708ba6cf6" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-282", + "l": "eu-west-1", + "r": [ + { + "ARN": "b9f0acbf-a682-49d2-adc9-8cea8513bad4" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-282", + "l": "us-west-1", + "r": [ + { + "ARN": "b9f0acbf-a682-49d2-adc9-8cea8513bad4" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-283", + "l": "eu-west-1", + "r": [ + { + "ARN": "2a978ca1-b794-4de6-80aa-9e470ad4337e" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-283", + "l": "us-west-1", + "r": [ + { + "ARN": "2a978ca1-b794-4de6-80aa-9e470ad4337e" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-284", + "l": "eu-west-1", + "r": [ + { + "AutoScalingGroupARN": "0d3a674c-6ec7-4b07-bf79-ec356ab5812a" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-284", + "l": "us-west-1", + "r": [ + { + "AutoScalingGroupARN": "0d3a674c-6ec7-4b07-bf79-ec356ab5812a" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-285", + "l": "eu-west-1", + "r": [ + { + "field": "79bb6d10-421b-474c-9444-6d7d363a8cba" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-285", + "l": "us-west-1", + "r": [ + { + "field": "79bb6d10-421b-474c-9444-6d7d363a8cba" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-286", + "l": "eu-west-1", + "r": [ + { + "WorkspaceId": "b71b131c-561a-4954-b83c-d930a0019c61" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-286", + "l": "us-west-1", + "r": [ + { + "WorkspaceId": "b71b131c-561a-4954-b83c-d930a0019c61" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-287", + "l": "eu-west-1", + "r": [ + { + "AutoScalingGroupARN": "eeafa3e0-2f57-4aa5-b728-9295285174a4" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-287", + "l": "us-west-1", + "r": [ + { + "AutoScalingGroupARN": "eeafa3e0-2f57-4aa5-b728-9295285174a4" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-288", + "l": "eu-west-1", + "r": [ + { + "WorkspaceId": "bde2d323-05aa-4e01-9801-bfd5ce28ad27" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-288", + "l": "us-west-1", + "r": [ + { + "WorkspaceId": "bde2d323-05aa-4e01-9801-bfd5ce28ad27" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-289", + "l": "eu-west-1", + "r": [ + { + "AutoScalingGroupARN": "025e05d0-1a24-4aef-bede-3c7e4e2a7d7c" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-289", + "l": "us-west-1", + "r": [ + { + "AutoScalingGroupARN": "025e05d0-1a24-4aef-bede-3c7e4e2a7d7c" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-290", + "l": "eu-west-1", + "r": [ + { + "WorkspaceId": "e6b8c268-0490-4743-b14b-314e61c347eb" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-290", + "l": "us-west-1", + "r": [ + { + "WorkspaceId": "e6b8c268-0490-4743-b14b-314e61c347eb" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-291", + "l": "eu-west-1", + "r": [ + { + "BackupPlanName": "cfc3d344-d7a9-464b-9533-2aee37063b28", + "BackupPlanId": "456928be-0dcf-4371-873c-ac163d1389ed" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-291", + "l": "us-west-1", + "r": [ + { + "BackupPlanName": "cfc3d344-d7a9-464b-9533-2aee37063b28", + "BackupPlanId": "456928be-0dcf-4371-873c-ac163d1389ed" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-292", + "l": "eu-west-1", + "r": [ + { + "EnvironmentArn": "8001d0b5-4fe1-43e8-88b6-d9489de14fd9" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-292", + "l": "us-west-1", + "r": [ + { + "EnvironmentArn": "8001d0b5-4fe1-43e8-88b6-d9489de14fd9" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-293", + "l": "eu-west-1", + "r": [ + { + "BackupVaultArn": "cf353736-db47-4987-8d88-8f0ee96219a8" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-293", + "l": "us-west-1", + "r": [ + { + "BackupVaultArn": "cf353736-db47-4987-8d88-8f0ee96219a8" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-294", + "l": "eu-west-1", + "r": [ + { + "EnvironmentArn": "6577a7ec-8fec-4ae6-8ddf-ab00bd0801e5" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-294", + "l": "us-west-1", + "r": [ + { + "EnvironmentArn": "6577a7ec-8fec-4ae6-8ddf-ab00bd0801e5" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-295", + "l": "multiregion", + "r": [ + { + "ARN": "dff64cc5-4bea-4cf7-806b-7828bdf787ec" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-296", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "a0488d61-b430-4a2f-b956-177757bcd12c" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-296", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "a0488d61-b430-4a2f-b956-177757bcd12c" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-297", + "l": "eu-west-1", + "r": [ + { + "EnvironmentArn": "7d79c817-3fb7-4dbc-843f-643fce6e78f0" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-297", + "l": "us-west-1", + "r": [ + { + "EnvironmentArn": "7d79c817-3fb7-4dbc-843f-643fce6e78f0" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-298", + "l": "eu-west-1", + "r": [ + { + "QueueArn": "ee423549-3266-445e-8f02-f1e0b5eb6fff" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-298", + "l": "us-west-1", + "r": [ + { + "QueueArn": "ee423549-3266-445e-8f02-f1e0b5eb6fff" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-299", + "l": "multiregion", + "r": [ + { + "ARN": "8fdfcda2-fd6f-42bf-819d-ee58504ade3b" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-300", + "l": "eu-west-1", + "r": [ + { + "QueueArn": "bf72b4fd-aff8-483f-8b5d-29fd6e761f65" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-300", + "l": "us-west-1", + "r": [ + { + "QueueArn": "bf72b4fd-aff8-483f-8b5d-29fd6e761f65" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-301", + "l": "eu-west-1", + "r": [ + { + "QueueArn": "c2d224ac-1931-4a89-9444-ae66e0aaee3f" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-301", + "l": "us-west-1", + "r": [ + { + "QueueArn": "c2d224ac-1931-4a89-9444-ae66e0aaee3f" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-302", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "c3ceb8df-ad63-46c5-8638-c98e267d6afa" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-302", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "c3ceb8df-ad63-46c5-8638-c98e267d6afa" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-303", + "l": "eu-west-1", + "r": [ + { + "TrailARN": "ea503101-74b9-42eb-930d-9a1e01385d4b" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-303", + "l": "us-west-1", + "r": [ + { + "TrailARN": "ea503101-74b9-42eb-930d-9a1e01385d4b" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-304", + "l": "eu-west-1", + "r": [ + { + "Arn": "ce9708cd-bd61-47d9-a9ce-03668cec114c" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-304", + "l": "us-west-1", + "r": [ + { + "Arn": "ce9708cd-bd61-47d9-a9ce-03668cec114c" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-305", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "ec313d5f-3879-49d7-a67d-f633be5b4ee6" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-305", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "ec313d5f-3879-49d7-a67d-f633be5b4ee6" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-306", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "2ca231c3-f155-4114-b9c4-40846977539a" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-306", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "2ca231c3-f155-4114-b9c4-40846977539a" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-307", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "3e82410c-e912-4d61-9a84-901090b7f742" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-307", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "3e82410c-e912-4d61-9a84-901090b7f742" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-308", + "l": "eu-west-1", + "r": [ + { + "VaultARN": "eb7c5637-7173-40b5-9630-2c0c0bd968a6" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-308", + "l": "us-west-1", + "r": [ + { + "VaultARN": "eb7c5637-7173-40b5-9630-2c0c0bd968a6" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-309", + "l": "eu-west-1", + "r": [ + { + "name": "346f9e5e-8f55-4655-8101-d0fb4603199f", + "roleARN": "3a2b8907-a989-44c6-aae4-3ac4ab1bdd06" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-309", + "l": "us-west-1", + "r": [ + { + "name": "346f9e5e-8f55-4655-8101-d0fb4603199f", + "roleARN": "3a2b8907-a989-44c6-aae4-3ac4ab1bdd06" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-310", + "l": "eu-west-1", + "r": [ + { + "ReplicationInstanceArn": "72f87ee0-f64c-4430-b9f3-9396b50faba1" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-310", + "l": "us-west-1", + "r": [ + { + "ReplicationInstanceArn": "72f87ee0-f64c-4430-b9f3-9396b50faba1" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-311", + "l": "eu-west-1", + "r": [ + { + "NotebookInstanceArn": "926f3b50-5322-4359-b90b-4313b99025e4" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-311", + "l": "us-west-1", + "r": [ + { + "NotebookInstanceArn": "926f3b50-5322-4359-b90b-4313b99025e4" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-312", + "l": "eu-west-1", + "r": [ + { + "ReplicationInstanceArn": "c00c209f-0921-4ebf-b24e-23a94010a266" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-312", + "l": "us-west-1", + "r": [ + { + "ReplicationInstanceArn": "c00c209f-0921-4ebf-b24e-23a94010a266" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-313", + "l": "eu-west-1", + "r": [ + { + "ReplicationInstanceArn": "d22d6d62-429b-45e5-b2ea-15b2e8c6272b" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-313", + "l": "us-west-1", + "r": [ + { + "ReplicationInstanceArn": "d22d6d62-429b-45e5-b2ea-15b2e8c6272b" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-314", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "953ab4a8-c53b-4b39-a30a-8cf899d7727b" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-314", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "953ab4a8-c53b-4b39-a30a-8cf899d7727b" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-315", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "a6328717-9067-4119-81e7-2f1efd65af35" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-315", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "a6328717-9067-4119-81e7-2f1efd65af35" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-316", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "36d8c3d0-4669-4149-aa6e-d3c6faba696d" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-316", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "36d8c3d0-4669-4149-aa6e-d3c6faba696d" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-317", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "2a84bf52-e3b2-45ec-a4aa-0a0fba4a60da" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-317", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "2a84bf52-e3b2-45ec-a4aa-0a0fba4a60da" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-318", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "b4716014-b97b-469b-8436-bba2c07373e8" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-318", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "b4716014-b97b-469b-8436-bba2c07373e8" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-319", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "0b66fff2-c188-4c92-8b19-fc1158193828" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-319", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "0b66fff2-c188-4c92-8b19-fc1158193828" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-320", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "7dc2680a-ab98-4e76-bb80-c6c56a8452b3" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-320", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "7dc2680a-ab98-4e76-bb80-c6c56a8452b3" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-321", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "ca6744c4-a94a-4795-8743-dac6f8fa5e00" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-321", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "ca6744c4-a94a-4795-8743-dac6f8fa5e00" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-322", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "60b0c056-9c42-4938-935f-17ae6cc5dd03" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-322", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "60b0c056-9c42-4938-935f-17ae6cc5dd03" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-323", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "aa272f25-c970-40fd-97f7-156d5411c754" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-323", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "aa272f25-c970-40fd-97f7-156d5411c754" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-324", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "afea2df0-6ffe-47a1-8e4a-b25cb021e473" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-324", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "afea2df0-6ffe-47a1-8e4a-b25cb021e473" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-325", + "l": "eu-west-1", + "r": [ + { + "ReplicationInstanceArn": "fc4e9594-c184-4338-8bcc-547c1e08a785" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-325", + "l": "us-west-1", + "r": [ + { + "ReplicationInstanceArn": "fc4e9594-c184-4338-8bcc-547c1e08a785" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-326", + "l": "eu-west-1", + "r": [ + { + "VolumeId": "0f5bf479-0f24-44d8-a41e-489fb9e2407b" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-326", + "l": "us-west-1", + "r": [ + { + "VolumeId": "0f5bf479-0f24-44d8-a41e-489fb9e2407b" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-327", + "l": "eu-west-1", + "r": [ + { + "VolumeId": "87870057-df24-4681-a2b4-8891dfe87115" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-327", + "l": "us-west-1", + "r": [ + { + "VolumeId": "87870057-df24-4681-a2b4-8891dfe87115" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-328", + "l": "eu-west-1", + "r": [ + { + "VolumeId": "a865bd41-317c-4d9f-adf4-b4a9eced1b17" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-328", + "l": "us-west-1", + "r": [ + { + "VolumeId": "a865bd41-317c-4d9f-adf4-b4a9eced1b17" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-329", + "l": "eu-west-1", + "r": [ + { + "KeyPairId": "67af1062-0be5-4061-80d0-bffb5edc0900" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-329", + "l": "us-west-1", + "r": [ + { + "KeyPairId": "67af1062-0be5-4061-80d0-bffb5edc0900" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-330", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "b668743a-d1a8-43c7-9165-0356292760c4" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-330", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "b668743a-d1a8-43c7-9165-0356292760c4" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-331", + "l": "eu-west-1", + "r": [ + { + "ImageId": "08a4d72b-b452-4cb5-bbc9-ab5671407766" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-331", + "l": "us-west-1", + "r": [ + { + "ImageId": "08a4d72b-b452-4cb5-bbc9-ab5671407766" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-332", + "l": "eu-west-1", + "r": [ + { + "DirectoryId": "cdef83c8-a948-4e4c-aa4d-7c6b96fb3b26" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-332", + "l": "us-west-1", + "r": [ + { + "DirectoryId": "cdef83c8-a948-4e4c-aa4d-7c6b96fb3b26" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-333", + "l": "eu-west-1", + "r": [ + { + "ResourceARN": "ad1a7468-58ff-4976-ac65-49ea1d03207d" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-333", + "l": "us-west-1", + "r": [ + { + "ResourceARN": "ad1a7468-58ff-4976-ac65-49ea1d03207d" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-334", + "l": "eu-west-1", + "r": [ + { + "DeliveryStreamARN": "e04218af-a99e-4c0d-bf58-ee0a358efd5a" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-334", + "l": "us-west-1", + "r": [ + { + "DeliveryStreamARN": "e04218af-a99e-4c0d-bf58-ee0a358efd5a" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-335", + "l": "eu-west-1", + "r": [ + { + "FunctionName": "5e9c0049-9320-486e-b6a8-085358d515a6" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-335", + "l": "us-west-1", + "r": [ + { + "FunctionName": "5e9c0049-9320-486e-b6a8-085358d515a6" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-336", + "l": "eu-west-1", + "r": [ + { + "EndpointConfigArn": "8358c649-6648-4ffa-bd39-8c75df267193" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-336", + "l": "us-west-1", + "r": [ + { + "EndpointConfigArn": "8358c649-6648-4ffa-bd39-8c75df267193" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-337", + "l": "eu-west-1", + "r": [ + { + "FunctionArn": "271d4c07-6971-429b-b8d8-12eababae711" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-337", + "l": "us-west-1", + "r": [ + { + "FunctionArn": "271d4c07-6971-429b-b8d8-12eababae711" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-338", + "l": "eu-west-1", + "r": [ + { + "NotebookInstanceArn": "cf4d1a73-e03d-4812-b4ae-151fae9a7a83" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-338", + "l": "us-west-1", + "r": [ + { + "NotebookInstanceArn": "cf4d1a73-e03d-4812-b4ae-151fae9a7a83" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-339", + "l": "eu-west-1", + "r": [ + { + "BrokerArn": "7e21021f-ab73-4353-9824-685591d434e5" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-339", + "l": "us-west-1", + "r": [ + { + "BrokerArn": "7e21021f-ab73-4353-9824-685591d434e5" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-340", + "l": "eu-west-1", + "r": [ + { + "BrokerArn": "ff0e1dda-d4fc-4844-b23c-1a069774d256" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-340", + "l": "us-west-1", + "r": [ + { + "BrokerArn": "ff0e1dda-d4fc-4844-b23c-1a069774d256" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-341", + "l": "eu-west-1", + "r": [ + { + "ModelArn": "37b688a2-db58-48f5-804b-d5c513d991be" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-341", + "l": "us-west-1", + "r": [ + { + "ModelArn": "37b688a2-db58-48f5-804b-d5c513d991be" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-342", + "l": "multiregion", + "r": [ + { + "DomainName": "55d6fe80-e87a-47ea-9b5f-456f14840424" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-343", + "l": "eu-west-1", + "r": [ + { + "BrokerArn": "f0c7718c-737a-4ac5-8c5a-bf768bb08a74" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-343", + "l": "us-west-1", + "r": [ + { + "BrokerArn": "f0c7718c-737a-4ac5-8c5a-bf768bb08a74" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-344", + "l": "multiregion", + "r": [ + { + "DomainName": "3ce2be8f-1eb8-4aa4-9fb6-19bc7a4bf377" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-345", + "l": "eu-west-1", + "r": [ + { + "BrokerArn": "26a33c50-1b7a-490b-a715-01a1bb173665" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-345", + "l": "us-west-1", + "r": [ + { + "BrokerArn": "26a33c50-1b7a-490b-a715-01a1bb173665" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-346", + "l": "eu-west-1", + "r": [ + { + "c7n:parent-id": "2b0aef5b-6179-4469-89b7-d5f7e4833d9b", + "Name": "24ac2b91-3e9f-4749-adf9-f52deded0ae5", + "Type": "3a51370e-badf-4927-95e6-8a7d50d0f88a", + "SetIdentifier": "93474a4b-5b8f-4643-89d8-fc14ca536918" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-346", + "l": "us-west-1", + "r": [ + { + "c7n:parent-id": "2b0aef5b-6179-4469-89b7-d5f7e4833d9b", + "Name": "24ac2b91-3e9f-4749-adf9-f52deded0ae5", + "Type": "3a51370e-badf-4927-95e6-8a7d50d0f88a", + "SetIdentifier": "93474a4b-5b8f-4643-89d8-fc14ca536918" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-347", + "l": "eu-west-1", + "r": [ + { + "ClusterArn": "c84b952c-0514-4b73-b563-46af24ae80f5" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-347", + "l": "us-west-1", + "r": [ + { + "ClusterArn": "c84b952c-0514-4b73-b563-46af24ae80f5" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-348", + "l": "eu-west-1", + "r": [ + { + "ClusterArn": "0c52e58e-9596-479a-a949-3f17ed5a996b" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-348", + "l": "us-west-1", + "r": [ + { + "ClusterArn": "0c52e58e-9596-479a-a949-3f17ed5a996b" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-349", + "l": "eu-west-1", + "r": [ + { + "Id": "10eed707-780a-484a-b190-588f22cc5f4a", + "Name": "1e6ff34a-8794-4ee2-8a79-1d66cce95b4a" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-349", + "l": "us-west-1", + "r": [ + { + "Id": "10eed707-780a-484a-b190-588f22cc5f4a", + "Name": "1e6ff34a-8794-4ee2-8a79-1d66cce95b4a" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-350", + "l": "eu-west-1", + "r": [ + { + "ClusterArn": "2b4e8dbf-2d5b-4961-9930-20a0982877ad" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-350", + "l": "us-west-1", + "r": [ + { + "ClusterArn": "2b4e8dbf-2d5b-4961-9930-20a0982877ad" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-351", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "0f3fe4d2-0fa5-4122-b577-ab9c7f98c2cd" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-351", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "0f3fe4d2-0fa5-4122-b577-ab9c7f98c2cd" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-352", + "l": "eu-west-1", + "r": [ + { + "TopicArn": "18463dfd-a672-4a3d-a63a-17d2654f9db3" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-352", + "l": "us-west-1", + "r": [ + { + "TopicArn": "18463dfd-a672-4a3d-a63a-17d2654f9db3" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-353", + "l": "eu-west-1", + "r": [ + { + "ClusterIdentifier": "aefd515f-734d-4eb5-9c50-874b163588fa" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-353", + "l": "us-west-1", + "r": [ + { + "ClusterIdentifier": "aefd515f-734d-4eb5-9c50-874b163588fa" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-354", + "l": "eu-west-1", + "r": [ + { + "ClusterIdentifier": "092e2223-ddd2-4b31-aa9f-83421f103b15" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-354", + "l": "us-west-1", + "r": [ + { + "ClusterIdentifier": "092e2223-ddd2-4b31-aa9f-83421f103b15" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-355", + "l": "eu-west-1", + "r": [ + { + "ClusterIdentifier": "96970797-a87a-4cea-9ef3-72fb27bc7d07" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-355", + "l": "us-west-1", + "r": [ + { + "ClusterIdentifier": "96970797-a87a-4cea-9ef3-72fb27bc7d07" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-356", + "l": "eu-west-1", + "r": [ + { + "ClusterIdentifier": "d950989f-4ec0-495b-b583-008d1aea93bf" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-356", + "l": "us-west-1", + "r": [ + { + "ClusterIdentifier": "d950989f-4ec0-495b-b583-008d1aea93bf" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-357", + "l": "multiregion", + "r": [ + { + "DomainName": "7a7b2f84-34e2-48d7-bc7d-c039500fa486" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-358", + "l": "eu-west-1", + "r": [ + { + "field": "b48e0ab1-e626-46ac-a0f1-ae288ed4f8ab" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-358", + "l": "us-west-1", + "r": [ + { + "field": "b48e0ab1-e626-46ac-a0f1-ae288ed4f8ab" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-359", + "l": "eu-west-1", + "r": [ + { + "deploymentId": "0cbd7468-aa6c-42f0-a8ce-57d421a81f51", + "stageName": "4b1f9bff-f42b-426b-82cd-ffa1277fcb0e" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-359", + "l": "us-west-1", + "r": [ + { + "deploymentId": "0cbd7468-aa6c-42f0-a8ce-57d421a81f51", + "stageName": "4b1f9bff-f42b-426b-82cd-ffa1277fcb0e" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-360", + "l": "eu-west-1", + "r": [ + { + "clusterArn": "2d2b271d-2391-4af2-8fb1-751f9ce12cc6" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-360", + "l": "us-west-1", + "r": [ + { + "clusterArn": "2d2b271d-2391-4af2-8fb1-751f9ce12cc6" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-361", + "l": "eu-west-1", + "r": [ + { + "deploymentId": "49956722-cb5f-41f4-a77a-d6c780c46ab8", + "stageName": "12820a45-d8ca-4091-9a70-3cdf6933514a" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-361", + "l": "us-west-1", + "r": [ + { + "deploymentId": "49956722-cb5f-41f4-a77a-d6c780c46ab8", + "stageName": "12820a45-d8ca-4091-9a70-3cdf6933514a" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-362", + "l": "eu-west-1", + "r": [ + { + "Arn": "87388c9f-54ab-460b-afbc-e3a1d85b21c6" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-362", + "l": "us-west-1", + "r": [ + { + "Arn": "87388c9f-54ab-460b-afbc-e3a1d85b21c6" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-363", + "l": "eu-west-1", + "r": [ + { + "StreamARN": "44b59f02-547f-43a7-a209-e14eb36d53bc" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-363", + "l": "us-west-1", + "r": [ + { + "StreamARN": "44b59f02-547f-43a7-a209-e14eb36d53bc" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-364", + "l": "eu-west-1", + "r": [ + { + "LaunchConfigurationName": "8d2c36c6-48b0-4a59-a372-3f1f7dda9575" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-364", + "l": "us-west-1", + "r": [ + { + "LaunchConfigurationName": "8d2c36c6-48b0-4a59-a372-3f1f7dda9575" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-365", + "l": "eu-west-1", + "r": [ + { + "CatalogId": "bcdef47d-6f31-4ed3-be38-76ef19f0ba47" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-365", + "l": "us-west-1", + "r": [ + { + "CatalogId": "bcdef47d-6f31-4ed3-be38-76ef19f0ba47" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-366", + "l": "eu-west-1", + "r": [ + { + "FileSystemId": "3e81470c-fbe1-4019-bfa8-cb601c4e74eb" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-366", + "l": "us-west-1", + "r": [ + { + "FileSystemId": "3e81470c-fbe1-4019-bfa8-cb601c4e74eb" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-367", + "l": "eu-west-1", + "r": [ + { + "DirectoryId": "98a1fac4-6ac6-417f-a950-0660756269c1" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-367", + "l": "us-west-1", + "r": [ + { + "DirectoryId": "98a1fac4-6ac6-417f-a950-0660756269c1" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-368", + "l": "eu-west-1", + "r": [ + { + "ResourceARN": "988bfaae-808d-48ec-8ed0-ac9ef5712ea6" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-368", + "l": "us-west-1", + "r": [ + { + "ResourceARN": "988bfaae-808d-48ec-8ed0-ac9ef5712ea6" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-369", + "l": "eu-west-1", + "r": [ + { + "account_id": "54a744d3-ec94-4aa1-9432-20058507ef81" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-369", + "l": "us-west-1", + "r": [ + { + "account_id": "54a744d3-ec94-4aa1-9432-20058507ef81" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-370", + "l": "eu-west-1", + "r": [ + { + "DirectoryId": "760e175c-a46f-4813-bb36-39aa3cfce0d1" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-370", + "l": "us-west-1", + "r": [ + { + "DirectoryId": "760e175c-a46f-4813-bb36-39aa3cfce0d1" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-371", + "l": "eu-west-1", + "r": [ + { + "WorkspaceId": "d56ff33d-845e-47da-b950-fcd0e6b53d72" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-371", + "l": "us-west-1", + "r": [ + { + "WorkspaceId": "d56ff33d-845e-47da-b950-fcd0e6b53d72" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-372", + "l": "eu-west-1", + "r": [ + { + "DirectoryId": "6bde7ace-ee8e-428e-977f-dc7d0fc0b2ab" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-372", + "l": "us-west-1", + "r": [ + { + "DirectoryId": "6bde7ace-ee8e-428e-977f-dc7d0fc0b2ab" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-373", + "l": "eu-west-1", + "r": [ + { + "DirectoryId": "64c3cf0f-552e-4467-808e-96c4ed8d5e29" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-373", + "l": "us-west-1", + "r": [ + { + "DirectoryId": "64c3cf0f-552e-4467-808e-96c4ed8d5e29" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-374", + "l": "eu-west-1", + "r": [ + { + "TrailARN": "4661238f-9b6b-447a-bc79-ffd2a3f0d9c2" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-374", + "l": "us-west-1", + "r": [ + { + "TrailARN": "4661238f-9b6b-447a-bc79-ffd2a3f0d9c2" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-375", + "l": "eu-west-1", + "r": [ + { + "WorkspaceId": "e7d15a2d-934e-4050-b67b-f2e9b13eba0a" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-375", + "l": "us-west-1", + "r": [ + { + "WorkspaceId": "e7d15a2d-934e-4050-b67b-f2e9b13eba0a" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-376", + "l": "eu-west-1", + "r": [ + { + "restApiId": "74a1886d-e640-4b37-b2cf-e8b28e8aa7db", + "StageName": "45c81fd1-ac11-4b90-b7a6-e6080f5360ef" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-376", + "l": "us-west-1", + "r": [ + { + "restApiId": "74a1886d-e640-4b37-b2cf-e8b28e8aa7db", + "StageName": "45c81fd1-ac11-4b90-b7a6-e6080f5360ef" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-377", + "l": "eu-west-1", + "r": [ + { + "ImageId": "e3f876ee-121c-4409-b772-026860e95bae" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-377", + "l": "us-west-1", + "r": [ + { + "ImageId": "e3f876ee-121c-4409-b772-026860e95bae" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-378", + "l": "eu-west-1", + "r": [ + { + "VolumeId": "943c4198-2834-4684-8b2f-b1dd1a3fdded" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-378", + "l": "us-west-1", + "r": [ + { + "VolumeId": "943c4198-2834-4684-8b2f-b1dd1a3fdded" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-379", + "l": "eu-west-1", + "r": [ + { + "SnapshotId": "59680382-0fc6-4001-a856-5137039e3f54" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-379", + "l": "us-west-1", + "r": [ + { + "SnapshotId": "59680382-0fc6-4001-a856-5137039e3f54" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-380", + "l": "eu-west-1", + "r": [ + { + "AllocationId": "e1be238b-d6b2-4f53-ae82-ce59adf12034" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-380", + "l": "us-west-1", + "r": [ + { + "AllocationId": "e1be238b-d6b2-4f53-ae82-ce59adf12034" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-381", + "l": "eu-west-1", + "r": [ + { + "NetworkInterfaceId": "413d86ca-06db-4bc7-aa20-3cc1bf317c15" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-381", + "l": "us-west-1", + "r": [ + { + "NetworkInterfaceId": "413d86ca-06db-4bc7-aa20-3cc1bf317c15" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-382", + "l": "eu-west-1", + "r": [ + { + "InternetGatewayId": "9a346f72-3070-4ec5-9ba0-f6f9b676e9a0" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-382", + "l": "us-west-1", + "r": [ + { + "InternetGatewayId": "9a346f72-3070-4ec5-9ba0-f6f9b676e9a0" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-383", + "l": "eu-west-1", + "r": [ + { + "NatGatewayId": "bde7d900-4bd9-4783-92bb-e9da947cbb52" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-383", + "l": "us-west-1", + "r": [ + { + "NatGatewayId": "bde7d900-4bd9-4783-92bb-e9da947cbb52" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-384", + "l": "eu-west-1", + "r": [ + { + "NetworkAclId": "8810d16a-64ae-4e59-88f0-f0f56d24bd3e", + "OwnerId": "6229c739-8fef-4809-99c4-e14e959241b3" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-384", + "l": "us-west-1", + "r": [ + { + "NetworkAclId": "8810d16a-64ae-4e59-88f0-f0f56d24bd3e", + "OwnerId": "6229c739-8fef-4809-99c4-e14e959241b3" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-385", + "l": "eu-west-1", + "r": [ + { + "RouteTableId": "25de385e-2130-4ca7-9e0b-3a5c61c3c6b7" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-385", + "l": "us-west-1", + "r": [ + { + "RouteTableId": "25de385e-2130-4ca7-9e0b-3a5c61c3c6b7" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-386", + "l": "eu-west-1", + "r": [ + { + "GroupId": "161ee990-3209-4508-ac25-dc0e3959ed3c" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-386", + "l": "us-west-1", + "r": [ + { + "GroupId": "161ee990-3209-4508-ac25-dc0e3959ed3c" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-387", + "l": "eu-west-1", + "r": [ + { + "SubnetId": "8897c26f-ec1f-4c36-9699-354b5b24f406" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-387", + "l": "us-west-1", + "r": [ + { + "SubnetId": "8897c26f-ec1f-4c36-9699-354b5b24f406" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-388", + "l": "eu-west-1", + "r": [ + { + "TransitGatewayId": "ea42d216-31dc-4566-b416-a2498902e7c2" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-388", + "l": "us-west-1", + "r": [ + { + "TransitGatewayId": "ea42d216-31dc-4566-b416-a2498902e7c2" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-389", + "l": "eu-west-1", + "r": [ + { + "TransitGatewayAttachmentId": "9c1a6893-a599-4126-ac2c-cd2a3d042c29" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-389", + "l": "us-west-1", + "r": [ + { + "TransitGatewayAttachmentId": "9c1a6893-a599-4126-ac2c-cd2a3d042c29" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-390", + "l": "eu-west-1", + "r": [ + { + "VpcPeeringConnectionId": "67fcceff-cf00-41ca-a874-466481338e66" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-390", + "l": "us-west-1", + "r": [ + { + "VpcPeeringConnectionId": "67fcceff-cf00-41ca-a874-466481338e66" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-391", + "l": "eu-west-1", + "r": [ + { + "VpcId": "c4125ab4-9169-4b9b-b348-8c614f210e8c" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-391", + "l": "us-west-1", + "r": [ + { + "VpcId": "c4125ab4-9169-4b9b-b348-8c614f210e8c" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-392", + "l": "eu-west-1", + "r": [ + { + "VpcEndpointId": "3d973ebe-d5d5-4d81-82c8-ec7adc113285" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-392", + "l": "us-west-1", + "r": [ + { + "VpcEndpointId": "3d973ebe-d5d5-4d81-82c8-ec7adc113285" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-393", + "l": "eu-west-1", + "r": [ + { + "CertificateArn": "846826bf-bf9a-496d-a206-05b95bcba22e", + "DomainName": "b7a90f64-a007-44d9-9eff-9b7ab6e056bd" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-393", + "l": "us-west-1", + "r": [ + { + "CertificateArn": "846826bf-bf9a-496d-a206-05b95bcba22e", + "DomainName": "b7a90f64-a007-44d9-9eff-9b7ab6e056bd" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-394", + "l": "eu-west-1", + "r": [ + { + "flowArn": "4ee29d82-eda6-44fd-990e-a3a128178b33" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-394", + "l": "us-west-1", + "r": [ + { + "flowArn": "4ee29d82-eda6-44fd-990e-a3a128178b33" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-395", + "l": "eu-west-1", + "r": [ + { + "AutoScalingGroupARN": "700cd465-cc1a-408c-a953-3939adc20c2a" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-395", + "l": "us-west-1", + "r": [ + { + "AutoScalingGroupARN": "700cd465-cc1a-408c-a953-3939adc20c2a" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-396", + "l": "eu-west-1", + "r": [ + { + "StackName": "e0761825-839f-4720-8737-6bf6f9eb63be" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-396", + "l": "us-west-1", + "r": [ + { + "StackName": "e0761825-839f-4720-8737-6bf6f9eb63be" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-397", + "l": "multiregion", + "r": [ + { + "ARN": "ad08d7dd-0cf4-455d-8ac0-59978a080766" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-398", + "l": "eu-west-1", + "r": [ + { + "TrailARN": "4b0beb55-57eb-4a64-ab80-997c6774adc4" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-398", + "l": "us-west-1", + "r": [ + { + "TrailARN": "4b0beb55-57eb-4a64-ab80-997c6774adc4" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-399", + "l": "eu-west-1", + "r": [ + { + "arn": "6bb6263e-07ee-49e7-aae5-fe2297c094af" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-399", + "l": "us-west-1", + "r": [ + { + "arn": "6bb6263e-07ee-49e7-aae5-fe2297c094af" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-400", + "l": "eu-west-1", + "r": [ + { + "ClusterArn": "df49afd2-1212-4ddb-b099-bba681d7525d" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-400", + "l": "us-west-1", + "r": [ + { + "ClusterArn": "df49afd2-1212-4ddb-b099-bba681d7525d" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-401", + "l": "eu-west-1", + "r": [ + { + "PolicyId": "6ec921f2-6db6-45e8-97a4-e61fdc6f8455" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-401", + "l": "us-west-1", + "r": [ + { + "PolicyId": "6ec921f2-6db6-45e8-97a4-e61fdc6f8455" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-402", + "l": "eu-west-1", + "r": [ + { + "ReplicationInstanceIdentifier": "f9266498-df72-478d-89e0-8e271b280963", + "ReplicationInstanceArn": "77717486-2a92-4728-877d-0eca18915707" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-402", + "l": "us-west-1", + "r": [ + { + "ReplicationInstanceIdentifier": "f9266498-df72-478d-89e0-8e271b280963", + "ReplicationInstanceArn": "77717486-2a92-4728-877d-0eca18915707" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-403", + "l": "eu-west-1", + "r": [ + { + "clusterArn": "1dafbb09-4c73-4f4a-bee3-95c69460fcbe" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-403", + "l": "us-west-1", + "r": [ + { + "clusterArn": "1dafbb09-4c73-4f4a-bee3-95c69460fcbe" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-404", + "l": "eu-west-1", + "r": [ + { + "arn": "1d2aa220-66be-48a2-bed5-c5bbddbfbdec" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-404", + "l": "us-west-1", + "r": [ + { + "arn": "1d2aa220-66be-48a2-bed5-c5bbddbfbdec" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-405", + "l": "eu-west-1", + "r": [ + { + "FileSystemArn": "45a498d4-24bf-459b-825e-4f553a911096", + "OwnerId": "b0034154-a08a-474b-831c-ecddecca7a29" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-405", + "l": "us-west-1", + "r": [ + { + "FileSystemArn": "45a498d4-24bf-459b-825e-4f553a911096", + "OwnerId": "b0034154-a08a-474b-831c-ecddecca7a29" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-406", + "l": "eu-west-1", + "r": [ + { + "ARN": "8eddfd6c-7a33-4dc1-a190-a4bca90ea6be" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-406", + "l": "us-west-1", + "r": [ + { + "ARN": "8eddfd6c-7a33-4dc1-a190-a4bca90ea6be" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-407", + "l": "eu-west-1", + "r": [ + { + "TopicArn": "0d6593c7-5974-43a9-8f16-5961431ed459" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-407", + "l": "us-west-1", + "r": [ + { + "TopicArn": "0d6593c7-5974-43a9-8f16-5961431ed459" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-408", + "l": "eu-west-1", + "r": [ + { + "LoadBalancerName": "b70af536-7ccb-4abb-82cb-0b8ba196e0f0" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-408", + "l": "us-west-1", + "r": [ + { + "LoadBalancerName": "b70af536-7ccb-4abb-82cb-0b8ba196e0f0" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-409", + "l": "eu-west-1", + "r": [ + { + "ClusterArn": "24441755-fa17-41bb-bf0f-fe7ca841b6bb" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-409", + "l": "us-west-1", + "r": [ + { + "ClusterArn": "24441755-fa17-41bb-bf0f-fe7ca841b6bb" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-410", + "l": "eu-west-1", + "r": [ + { + "ARN": "5dd5789b-0972-4040-8d96-aba87ff5660c" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-410", + "l": "us-west-1", + "r": [ + { + "ARN": "5dd5789b-0972-4040-8d96-aba87ff5660c" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-411", + "l": "eu-west-1", + "r": [ + { + "ResourceARN": "57d08f8b-9c13-40ed-8d1f-66dfa24f52b9" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-411", + "l": "us-west-1", + "r": [ + { + "ResourceARN": "57d08f8b-9c13-40ed-8d1f-66dfa24f52b9" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-412", + "l": "eu-west-1", + "r": [ + { + "FileSystemId": "e15087f9-8536-4075-9acd-552faaa568ab" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-412", + "l": "us-west-1", + "r": [ + { + "FileSystemId": "e15087f9-8536-4075-9acd-552faaa568ab" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-413", + "l": "eu-west-1", + "r": [ + { + "VaultARN": "47ff8d33-a311-48dd-8a71-c88af5025086" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-413", + "l": "us-west-1", + "r": [ + { + "VaultARN": "47ff8d33-a311-48dd-8a71-c88af5025086" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-414", + "l": "eu-west-1", + "r": [ + { + "Name": "f4212040-427f-4067-82a7-bb6eb5ae1dcb" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-414", + "l": "us-west-1", + "r": [ + { + "Name": "f4212040-427f-4067-82a7-bb6eb5ae1dcb" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-415", + "l": "multiregion", + "r": [ + { + "Arn": "02e6c5e8-b6ea-4e47-a210-9e1c7b974584" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-416", + "l": "multiregion", + "r": [ + { + "Arn": "9abed709-1a76-4019-ba2e-65c8f00b3c0a" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-417", + "l": "eu-west-1", + "r": [ + { + "ClusterArn": "d044a2e7-3d97-4ebc-836a-511ccb830d8f" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-417", + "l": "us-west-1", + "r": [ + { + "ClusterArn": "d044a2e7-3d97-4ebc-836a-511ccb830d8f" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-418", + "l": "eu-west-1", + "r": [ + { + "TopicArn": "82aad833-940a-4b2f-bad1-3cd893525bef" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-418", + "l": "us-west-1", + "r": [ + { + "TopicArn": "82aad833-940a-4b2f-bad1-3cd893525bef" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-419", + "l": "eu-west-1", + "r": [ + { + "StreamARN": "97e42c3e-4200-4574-b20f-f1f8bf16ad09" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-419", + "l": "us-west-1", + "r": [ + { + "StreamARN": "97e42c3e-4200-4574-b20f-f1f8bf16ad09" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-420", + "l": "eu-west-1", + "r": [ + { + "KeyArn": "fedb23c2-6201-4997-a157-66b334173b4b", + "AWSAccountId": "adaddb00-0e89-4876-bedd-71da8fe30cfb" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-420", + "l": "us-west-1", + "r": [ + { + "KeyArn": "fedb23c2-6201-4997-a157-66b334173b4b", + "AWSAccountId": "adaddb00-0e89-4876-bedd-71da8fe30cfb" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-421", + "l": "eu-west-1", + "r": [ + { + "FunctionArn": "79135784-c303-492a-a0cf-466586b73b9d" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-421", + "l": "us-west-1", + "r": [ + { + "FunctionArn": "79135784-c303-492a-a0cf-466586b73b9d" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-422", + "l": "eu-west-1", + "r": [ + { + "arn": "dfc45b36-a33a-4376-9ad9-502a8ffcb88d" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-422", + "l": "us-west-1", + "r": [ + { + "arn": "dfc45b36-a33a-4376-9ad9-502a8ffcb88d" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-423", + "l": "eu-west-1", + "r": [ + { + "arn": "803b7990-cab8-43bd-8641-0bda919021d0" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-423", + "l": "us-west-1", + "r": [ + { + "arn": "803b7990-cab8-43bd-8641-0bda919021d0" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-424", + "l": "eu-west-1", + "r": [ + { + "ClusterIdentifier": "94225a8b-97e7-4e3c-8039-a3d1120e9ec3" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-424", + "l": "us-west-1", + "r": [ + { + "ClusterIdentifier": "94225a8b-97e7-4e3c-8039-a3d1120e9ec3" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-425", + "l": "eu-west-1", + "r": [ + { + "Arn": "b6ac6c4c-9cb7-4368-9d6f-364c3c2533de" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-425", + "l": "us-west-1", + "r": [ + { + "Arn": "b6ac6c4c-9cb7-4368-9d6f-364c3c2533de" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-426", + "l": "eu-west-1", + "r": [ + { + "EnvironmentArn": "228385d6-92f3-45df-a7ef-8048343101e9" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-426", + "l": "us-west-1", + "r": [ + { + "EnvironmentArn": "228385d6-92f3-45df-a7ef-8048343101e9" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-427", + "l": "eu-west-1", + "r": [ + { + "DBClusterIdentifier": "21db906a-553b-44c1-88c2-eff9d2badb2a" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-427", + "l": "us-west-1", + "r": [ + { + "DBClusterIdentifier": "21db906a-553b-44c1-88c2-eff9d2badb2a" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-428", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "f36ef7c1-09a0-4716-83e6-b3518f53edc1" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-428", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "f36ef7c1-09a0-4716-83e6-b3518f53edc1" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-429", + "l": "eu-west-1", + "r": [ + { + "ClusterIdentifier": "20a1814d-3770-4396-85b1-fdc7737e09c4" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-429", + "l": "us-west-1", + "r": [ + { + "ClusterIdentifier": "20a1814d-3770-4396-85b1-fdc7737e09c4" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-430", + "l": "eu-west-1", + "r": [ + { + "NotebookInstanceArn": "2fc591ac-bc61-49f9-8c66-aa1b2f10448b" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-430", + "l": "us-west-1", + "r": [ + { + "NotebookInstanceArn": "2fc591ac-bc61-49f9-8c66-aa1b2f10448b" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-431", + "l": "eu-west-1", + "r": [ + { + "TopicArn": "b311fd01-7303-4497-8f26-66a5ced561a8" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-431", + "l": "us-west-1", + "r": [ + { + "TopicArn": "b311fd01-7303-4497-8f26-66a5ced561a8" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-432", + "l": "eu-west-1", + "r": [ + { + "ClusterArn": "65c3ddf8-a577-4438-a889-5cf5b6da3201" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-432", + "l": "us-west-1", + "r": [ + { + "ClusterArn": "65c3ddf8-a577-4438-a889-5cf5b6da3201" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-433", + "l": "eu-west-1", + "r": [ + { + "BrokerArn": "558d3d83-7ac4-4d2e-87ad-c62167e31bb0" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-433", + "l": "us-west-1", + "r": [ + { + "BrokerArn": "558d3d83-7ac4-4d2e-87ad-c62167e31bb0" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-434", + "l": "eu-west-1", + "r": [ + { + "BrokerArn": "cf16ef6b-f26f-4ac9-b712-d159bbb78715" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-434", + "l": "us-west-1", + "r": [ + { + "BrokerArn": "cf16ef6b-f26f-4ac9-b712-d159bbb78715" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-435", + "l": "eu-west-1", + "r": [ + { + "ClusterIdentifier": "e88b1a96-d91d-4d3c-8c08-fdfa5e56263e" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-435", + "l": "us-west-1", + "r": [ + { + "ClusterIdentifier": "e88b1a96-d91d-4d3c-8c08-fdfa5e56263e" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-436", + "l": "eu-west-1", + "r": [ + { + "StreamARN": "b53f3f44-453e-49a7-bf40-0fcd2ffff6da" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-436", + "l": "us-west-1", + "r": [ + { + "StreamARN": "b53f3f44-453e-49a7-bf40-0fcd2ffff6da" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-437", + "l": "multiregion", + "r": [ + { + "Name": "90958420-7917-4490-b0cc-7d6fd58567d6" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-438", + "l": "eu-west-1", + "r": [ + { + "EnvironmentArn": "e663eb43-e8b6-4bc1-a414-fc7566091e7d" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-438", + "l": "us-west-1", + "r": [ + { + "EnvironmentArn": "e663eb43-e8b6-4bc1-a414-fc7566091e7d" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-439", + "l": "eu-west-1", + "r": [ + { + "EnvironmentArn": "a11e9a79-3439-474c-b23a-fc1ed97ecec8" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-439", + "l": "us-west-1", + "r": [ + { + "EnvironmentArn": "a11e9a79-3439-474c-b23a-fc1ed97ecec8" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-440", + "l": "eu-west-1", + "r": [ + { + "arn": "47495d7e-a953-4e00-acae-9dda14688218", + "name": "6a3611b6-fe20-46e9-b942-2422acd6be78" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-440", + "l": "us-west-1", + "r": [ + { + "arn": "47495d7e-a953-4e00-acae-9dda14688218", + "name": "6a3611b6-fe20-46e9-b942-2422acd6be78" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-441", + "l": "eu-west-1", + "r": [ + { + "name": "5bd8cb2d-f73c-41fc-b790-4c5d395eb21f", + "arn": "66edfe86-4266-4c5f-8f6a-b4455edf34e0" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-441", + "l": "us-west-1", + "r": [ + { + "name": "5bd8cb2d-f73c-41fc-b790-4c5d395eb21f", + "arn": "66edfe86-4266-4c5f-8f6a-b4455edf34e0" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-442", + "l": "eu-west-1", + "r": [ + { + "name": "3bc185ba-86be-42f5-99ff-76c590d25602", + "arn": "8b8d6e47-4f2c-486d-8dcc-3f3c18e36d0b" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-442", + "l": "us-west-1", + "r": [ + { + "name": "3bc185ba-86be-42f5-99ff-76c590d25602", + "arn": "8b8d6e47-4f2c-486d-8dcc-3f3c18e36d0b" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-443", + "l": "eu-west-1", + "r": [ + { + "arn": "bed45d45-0b66-4efb-8317-d53de616cdf5", + "name": "8e73df16-d56a-4feb-b75c-b0af11b5d3ed" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-443", + "l": "us-west-1", + "r": [ + { + "arn": "bed45d45-0b66-4efb-8317-d53de616cdf5", + "name": "8e73df16-d56a-4feb-b75c-b0af11b5d3ed" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-444", + "l": "eu-west-1", + "r": [ + { + "Arn": "a3da669d-425b-4d78-bc0c-61a37b83b2a3" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-444", + "l": "us-west-1", + "r": [ + { + "Arn": "a3da669d-425b-4d78-bc0c-61a37b83b2a3" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-445", + "l": "eu-west-1", + "r": [ + { + "Arn": "b1fdca3a-c050-401b-8115-6126def4d446" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-445", + "l": "us-west-1", + "r": [ + { + "Arn": "b1fdca3a-c050-401b-8115-6126def4d446" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-446", + "l": "eu-west-1", + "r": [ + { + "Arn": "e953a388-4e5f-42a7-b7bc-07a2453bcb99" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-446", + "l": "us-west-1", + "r": [ + { + "Arn": "e953a388-4e5f-42a7-b7bc-07a2453bcb99" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-447", + "l": "eu-west-1", + "r": [ + { + "Arn": "e4e34763-d51d-4bb5-9921-088087b7413a" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-447", + "l": "us-west-1", + "r": [ + { + "Arn": "e4e34763-d51d-4bb5-9921-088087b7413a" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-448", + "l": "eu-west-1", + "r": [ + { + "Arn": "7e01d42d-08fe-49e3-a0e5-0f9bfe640262" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-448", + "l": "us-west-1", + "r": [ + { + "Arn": "7e01d42d-08fe-49e3-a0e5-0f9bfe640262" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-449", + "l": "eu-west-1", + "r": [ + { + "ClusterIdentifier": "214782d9-1a2e-4468-b2b0-a20978969644" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-449", + "l": "us-west-1", + "r": [ + { + "ClusterIdentifier": "214782d9-1a2e-4468-b2b0-a20978969644" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-450", + "l": "eu-west-1", + "r": [ + { + "EnvironmentArn": "02469467-7dcd-4bf4-a6d3-5f7b434d108a" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-450", + "l": "us-west-1", + "r": [ + { + "EnvironmentArn": "02469467-7dcd-4bf4-a6d3-5f7b434d108a" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-451", + "l": "eu-west-1", + "r": [ + { + "EnvironmentArn": "5840f653-f0f5-430a-adec-2e04ed6448df" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-451", + "l": "us-west-1", + "r": [ + { + "EnvironmentArn": "5840f653-f0f5-430a-adec-2e04ed6448df" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-452", + "l": "eu-west-1", + "r": [ + { + "EnvironmentArn": "bd05132b-370f-4f15-afca-fdcdccff667d" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-452", + "l": "us-west-1", + "r": [ + { + "EnvironmentArn": "bd05132b-370f-4f15-afca-fdcdccff667d" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-453", + "l": "eu-west-1", + "r": [ + { + "ARN": "f44ef5ae-35ca-40bb-a42a-7a3d7844659b" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-453", + "l": "us-west-1", + "r": [ + { + "ARN": "f44ef5ae-35ca-40bb-a42a-7a3d7844659b" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-454", + "l": "eu-west-1", + "r": [ + { + "ARN": "6186f26b-059c-4598-8802-62b3dcffe5e5" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-454", + "l": "us-west-1", + "r": [ + { + "ARN": "6186f26b-059c-4598-8802-62b3dcffe5e5" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-455", + "l": "eu-west-1", + "r": [ + { + "ClusterArn": "07d1dea8-b9c4-40be-b679-f760e0127ce7" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-455", + "l": "us-west-1", + "r": [ + { + "ClusterArn": "07d1dea8-b9c4-40be-b679-f760e0127ce7" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-456", + "l": "eu-west-1", + "r": [ + { + "ClusterArn": "18bf4e69-2962-43c2-8a5c-9e8e4da29c5f" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-456", + "l": "us-west-1", + "r": [ + { + "ClusterArn": "18bf4e69-2962-43c2-8a5c-9e8e4da29c5f" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-457", + "l": "eu-west-1", + "r": [ + { + "Name": "0e1b9596-dcea-4839-aa91-21daf69b35e1" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-457", + "l": "us-west-1", + "r": [ + { + "Name": "0e1b9596-dcea-4839-aa91-21daf69b35e1" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-458", + "l": "eu-west-1", + "r": [ + { + "FunctionArn": "0edc2b32-f92c-4205-8b6d-6a1ff6dc69c8" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-458", + "l": "us-west-1", + "r": [ + { + "FunctionArn": "0edc2b32-f92c-4205-8b6d-6a1ff6dc69c8" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-459", + "l": "eu-west-1", + "r": [ + { + "FunctionArn": "6379b0a2-cab4-4668-8062-f35dc2c6eb7c" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-459", + "l": "us-west-1", + "r": [ + { + "FunctionArn": "6379b0a2-cab4-4668-8062-f35dc2c6eb7c" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-460", + "l": "eu-west-1", + "r": [ + { + "FunctionArn": "6d01ca2b-9df7-4fc8-98a9-5734bf29a6e6" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-460", + "l": "us-west-1", + "r": [ + { + "FunctionArn": "6d01ca2b-9df7-4fc8-98a9-5734bf29a6e6" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-461", + "l": "eu-west-1", + "r": [ + { + "FunctionArn": "c9feb4ea-6781-4d98-94a5-0ba63e034575" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-461", + "l": "us-west-1", + "r": [ + { + "FunctionArn": "c9feb4ea-6781-4d98-94a5-0ba63e034575" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-462", + "l": "eu-west-1", + "r": [ + { + "FunctionName": "f2888c42-45c2-4c05-bc7f-dcfca01a0f2d" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-462", + "l": "us-west-1", + "r": [ + { + "FunctionName": "f2888c42-45c2-4c05-bc7f-dcfca01a0f2d" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-463", + "l": "multiregion", + "r": [ + { + "Name": "76e565a5-2cc5-48e6-bbe3-d2da6707a9eb" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-464", + "l": "eu-west-1", + "r": [ + { + "clusterArn": "3ab39df5-b46a-4729-bd97-fdcd9b0cad91" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-464", + "l": "us-west-1", + "r": [ + { + "clusterArn": "3ab39df5-b46a-4729-bd97-fdcd9b0cad91" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-465", + "l": "eu-west-1", + "r": [ + { + "ResourceARN": "5bde604e-f4a8-4c8c-8f1b-459c59f27817" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-465", + "l": "us-west-1", + "r": [ + { + "ResourceARN": "5bde604e-f4a8-4c8c-8f1b-459c59f27817" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-466", + "l": "eu-west-1", + "r": [ + { + "ResourceARN": "7885c0bd-c83a-40cc-8e43-b21bfab41503" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-466", + "l": "us-west-1", + "r": [ + { + "ResourceARN": "7885c0bd-c83a-40cc-8e43-b21bfab41503" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-467", + "l": "eu-west-1", + "r": [ + { + "ResourceARN": "635e2ea6-0c46-4c5f-85b5-ba5a1d145374" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-467", + "l": "us-west-1", + "r": [ + { + "ResourceARN": "635e2ea6-0c46-4c5f-85b5-ba5a1d145374" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-468", + "l": "eu-west-1", + "r": [ + { + "ResourceARN": "315b271b-9047-42bc-923e-dffaea024752" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-468", + "l": "us-west-1", + "r": [ + { + "ResourceARN": "315b271b-9047-42bc-923e-dffaea024752" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-469", + "l": "eu-west-1", + "r": [ + { + "LoadBalancerArn": "85d76fb8-3427-43e0-8472-e53bf19ba2f6" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-469", + "l": "us-west-1", + "r": [ + { + "LoadBalancerArn": "85d76fb8-3427-43e0-8472-e53bf19ba2f6" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-470", + "l": "eu-west-1", + "r": [ + { + "id": "3388826b-3758-4763-bb6e-50c94ffd7dca", + "name": "ee385b78-f50e-4510-9e35-352f682b4295" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-470", + "l": "us-west-1", + "r": [ + { + "id": "3388826b-3758-4763-bb6e-50c94ffd7dca", + "name": "ee385b78-f50e-4510-9e35-352f682b4295" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-471", + "l": "eu-west-1", + "r": [ + { + "AutoScalingGroupARN": "33daf002-5529-4341-a810-86869513f91c" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-471", + "l": "us-west-1", + "r": [ + { + "AutoScalingGroupARN": "33daf002-5529-4341-a810-86869513f91c" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-472", + "l": "eu-west-1", + "r": [ + { + "LaunchConfigurationName": "307b2943-17e7-4a05-89b4-b4cabf7592f2" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-472", + "l": "us-west-1", + "r": [ + { + "LaunchConfigurationName": "307b2943-17e7-4a05-89b4-b4cabf7592f2" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-473", + "l": "eu-west-1", + "r": [ + { + "LoadBalancerArn": "25eda498-1f2d-4f55-96c6-257ff2d89ca3" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-473", + "l": "us-west-1", + "r": [ + { + "LoadBalancerArn": "25eda498-1f2d-4f55-96c6-257ff2d89ca3" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-474", + "l": "eu-west-1", + "r": [ + { + "LoadBalancerArn": "05e32a2a-6cd8-45f1-953a-c01640f9e1a8" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-474", + "l": "us-west-1", + "r": [ + { + "LoadBalancerArn": "05e32a2a-6cd8-45f1-953a-c01640f9e1a8" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-475", + "l": "eu-west-1", + "r": [ + { + "LoadBalancerArn": "7785dd79-bec5-4aca-98c9-9408c3863768" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-475", + "l": "us-west-1", + "r": [ + { + "LoadBalancerArn": "7785dd79-bec5-4aca-98c9-9408c3863768" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-476", + "l": "eu-west-1", + "r": [ + { + "StackId": "d4815ded-b950-4696-b162-40d50eeae1f0" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-476", + "l": "us-west-1", + "r": [ + { + "StackId": "d4815ded-b950-4696-b162-40d50eeae1f0" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-477", + "l": "eu-west-1", + "r": [ + { + "StackId": "b25c45c3-9926-4a33-9ab3-45b4f767e749" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-477", + "l": "us-west-1", + "r": [ + { + "StackId": "b25c45c3-9926-4a33-9ab3-45b4f767e749" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-478", + "l": "multiregion", + "r": [ + { + "ARN": "de9bd4a7-e667-4485-87f8-5e8d06134a44" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-479", + "l": "eu-west-1", + "r": [ + { + "arn": "c2110005-0a5b-4045-914f-a8733ed46115" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-479", + "l": "us-west-1", + "r": [ + { + "arn": "c2110005-0a5b-4045-914f-a8733ed46115" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-480", + "l": "eu-west-1", + "r": [ + { + "arn": "19b1fd21-77a3-4d26-a032-d0808890df32" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-480", + "l": "us-west-1", + "r": [ + { + "arn": "19b1fd21-77a3-4d26-a032-d0808890df32" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-481", + "l": "eu-west-1", + "r": [ + { + "arn": "8082c409-ae36-440e-9ab6-4cfbf8d17fe2" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-481", + "l": "us-west-1", + "r": [ + { + "arn": "8082c409-ae36-440e-9ab6-4cfbf8d17fe2" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-482", + "l": "eu-west-1", + "r": [ + { + "arn": "04d02ba1-abd9-43d4-8e84-7540f7d63684" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-482", + "l": "us-west-1", + "r": [ + { + "arn": "04d02ba1-abd9-43d4-8e84-7540f7d63684" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-483", + "l": "eu-west-1", + "r": [ + { + "arn": "be248487-4014-44bf-b771-6a0ab9c4fb37" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-483", + "l": "us-west-1", + "r": [ + { + "arn": "be248487-4014-44bf-b771-6a0ab9c4fb37" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-484", + "l": "eu-west-1", + "r": [ + { + "applicationName": "14860630-c3b7-4d7c-9236-a4156213aee4", + "deploymentGroupName": "10afc39d-9d35-4c61-b60f-5aa571251ae8", + "deploymentGroupId": "76b782c1-ef01-4c86-b458-c6d584940497" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-484", + "l": "us-west-1", + "r": [ + { + "applicationName": "14860630-c3b7-4d7c-9236-a4156213aee4", + "deploymentGroupName": "10afc39d-9d35-4c61-b60f-5aa571251ae8", + "deploymentGroupId": "76b782c1-ef01-4c86-b458-c6d584940497" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-485", + "l": "eu-west-1", + "r": [ + { + "applicationName": "bc3df349-14d4-492e-be04-234fa5a1bb66", + "deploymentGroupName": "e9c943db-2a12-45f4-b2d3-0d10932adf56", + "deploymentConfigName": "03510e4a-eabe-43f8-92f6-b428098d4c45" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-485", + "l": "us-west-1", + "r": [ + { + "applicationName": "bc3df349-14d4-492e-be04-234fa5a1bb66", + "deploymentGroupName": "e9c943db-2a12-45f4-b2d3-0d10932adf56", + "deploymentConfigName": "03510e4a-eabe-43f8-92f6-b428098d4c45" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-486", + "l": "eu-west-1", + "r": [ + { + "applicationName": "c0169ab9-f4fa-4fda-9789-da853255e9de", + "deploymentGroupId": "8da1611c-d8be-4f49-9c83-e5de2b3d1330", + "deploymentGroupName": "64495619-0443-45a0-a3ed-11b0cd90616f" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-486", + "l": "us-west-1", + "r": [ + { + "applicationName": "c0169ab9-f4fa-4fda-9789-da853255e9de", + "deploymentGroupId": "8da1611c-d8be-4f49-9c83-e5de2b3d1330", + "deploymentGroupName": "64495619-0443-45a0-a3ed-11b0cd90616f" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-487", + "l": "eu-west-1", + "r": [ + { + "roleArn": "a87ca74c-c5dc-47b1-9ef9-d94933122896" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-487", + "l": "us-west-1", + "r": [ + { + "roleArn": "a87ca74c-c5dc-47b1-9ef9-d94933122896" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-488", + "l": "eu-west-1", + "r": [ + { + "arn": "8cb539d9-eeeb-46a5-bd14-aee30ffe9625" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-488", + "l": "us-west-1", + "r": [ + { + "arn": "8cb539d9-eeeb-46a5-bd14-aee30ffe9625" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-489", + "l": "eu-west-1", + "r": [ + { + "InstanceId": "69bcc638-7bc8-49be-a4f8-5007e11ebe05", + "OwnerId": "1744e3df-b422-44a6-be2b-2aa7452aa9fd" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-489", + "l": "us-west-1", + "r": [ + { + "InstanceId": "69bcc638-7bc8-49be-a4f8-5007e11ebe05", + "OwnerId": "1744e3df-b422-44a6-be2b-2aa7452aa9fd" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-490", + "l": "eu-west-1", + "r": [ + { + "InstanceId": "be7042d2-f360-4e15-90eb-9f47acc8bfe4", + "OwnerId": "1fb41d9d-327c-4279-a83e-443c02421e97" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-490", + "l": "us-west-1", + "r": [ + { + "InstanceId": "be7042d2-f360-4e15-90eb-9f47acc8bfe4", + "OwnerId": "1fb41d9d-327c-4279-a83e-443c02421e97" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-491", + "l": "eu-west-1", + "r": [ + { + "TransitGatewayArn": "e2acc7b8-302a-4314-a745-55030ab00a67" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-491", + "l": "us-west-1", + "r": [ + { + "TransitGatewayArn": "e2acc7b8-302a-4314-a745-55030ab00a67" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-492", + "l": "eu-west-1", + "r": [ + { + "repositoryArn": "4a912062-8de2-4239-b799-8098680dea7b" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-492", + "l": "us-west-1", + "r": [ + { + "repositoryArn": "4a912062-8de2-4239-b799-8098680dea7b" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-493", + "l": "eu-west-1", + "r": [ + { + "clusterArn": "7202eaed-0cd4-4be4-a33a-4e773e134a5d" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-493", + "l": "us-west-1", + "r": [ + { + "clusterArn": "7202eaed-0cd4-4be4-a33a-4e773e134a5d" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-494", + "l": "eu-west-1", + "r": [ + { + "clusterArn": "bee9ddb4-79f0-46bc-b6e1-2888204ad7f9" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-494", + "l": "us-west-1", + "r": [ + { + "clusterArn": "bee9ddb4-79f0-46bc-b6e1-2888204ad7f9" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-495", + "l": "eu-west-1", + "r": [ + { + "taskDefinitionArn": "97b6486b-18e8-4425-b13b-9047c5053a1e" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-495", + "l": "us-west-1", + "r": [ + { + "taskDefinitionArn": "97b6486b-18e8-4425-b13b-9047c5053a1e" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-496", + "l": "eu-west-1", + "r": [ + { + "taskDefinitionArn": "69b6ef2c-04c6-4faf-b5a1-e2eb2aebcec4" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-496", + "l": "us-west-1", + "r": [ + { + "taskDefinitionArn": "69b6ef2c-04c6-4faf-b5a1-e2eb2aebcec4" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-497", + "l": "eu-west-1", + "r": [ + { + "arn": "a5f28ca8-9cd2-49a0-a1ca-c132e21282cc" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-497", + "l": "us-west-1", + "r": [ + { + "arn": "a5f28ca8-9cd2-49a0-a1ca-c132e21282cc" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-498", + "l": "eu-west-1", + "r": [ + { + "LoadBalancerArn": "f17b8af2-f370-4b2c-be33-f613f7864190" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-498", + "l": "us-west-1", + "r": [ + { + "LoadBalancerArn": "f17b8af2-f370-4b2c-be33-f613f7864190" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-499", + "l": "eu-west-1", + "r": [ + { + "Arn": "4e7d4167-8352-4334-9f13-6ca65e3651af" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-499", + "l": "us-west-1", + "r": [ + { + "Arn": "4e7d4167-8352-4334-9f13-6ca65e3651af" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-500", + "l": "eu-west-1", + "r": [ + { + "FunctionArn": "c40dfc00-44b2-427a-b725-3833a3de2b42" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-500", + "l": "us-west-1", + "r": [ + { + "FunctionArn": "c40dfc00-44b2-427a-b725-3833a3de2b42" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-501", + "l": "eu-west-1", + "r": [ + { + "ARN": "32518e37-23ac-41b4-9502-2d4fe4be113e" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-501", + "l": "us-west-1", + "r": [ + { + "ARN": "32518e37-23ac-41b4-9502-2d4fe4be113e" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-502", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "47aa11b5-4a74-4bad-9e58-ee2c86d95f1f" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-502", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "47aa11b5-4a74-4bad-9e58-ee2c86d95f1f" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-503", + "l": "eu-west-1", + "r": [ + { + "DBClusterArn": "d39a34fb-60e4-4dda-b608-d4fc32700ea6" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-503", + "l": "us-west-1", + "r": [ + { + "DBClusterArn": "d39a34fb-60e4-4dda-b608-d4fc32700ea6" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-504", + "l": "eu-west-1", + "r": [ + { + "DBInstanceArn": "9880c034-f9de-4790-b712-ffaea6b90b8e" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-504", + "l": "us-west-1", + "r": [ + { + "DBInstanceArn": "9880c034-f9de-4790-b712-ffaea6b90b8e" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-505", + "l": "eu-west-1", + "r": [ + { + "ClusterIdentifier": "b876523c-9b6f-4cb5-a855-3486dfcbabc8" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-505", + "l": "us-west-1", + "r": [ + { + "ClusterIdentifier": "b876523c-9b6f-4cb5-a855-3486dfcbabc8" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-506", + "l": "eu-west-1", + "r": [ + { + "ClusterIdentifier": "9be735c3-6fa8-40dc-a82c-30b33ac436be" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-506", + "l": "us-west-1", + "r": [ + { + "ClusterIdentifier": "9be735c3-6fa8-40dc-a82c-30b33ac436be" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-507", + "l": "eu-west-1", + "r": [ + { + "TopicArn": "97ac1d0e-ee05-478e-934c-a4488e68bf57" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-507", + "l": "us-west-1", + "r": [ + { + "TopicArn": "97ac1d0e-ee05-478e-934c-a4488e68bf57" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-508", + "l": "eu-west-1", + "r": [ + { + "arn": "2ae60c43-c853-481a-b712-2c439cd860be" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-508", + "l": "us-west-1", + "r": [ + { + "arn": "2ae60c43-c853-481a-b712-2c439cd860be" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-509", + "l": "eu-west-1", + "r": [ + { + "ClusterArn": "300676cf-1a43-444f-8db9-dff6213d45c3" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-509", + "l": "us-west-1", + "r": [ + { + "ClusterArn": "300676cf-1a43-444f-8db9-dff6213d45c3" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-510", + "l": "eu-west-1", + "r": [ + { + "FileSystemArn": "1788a445-046b-4c52-9ef0-011ca10f7203", + "OwnerId": "56d709fb-6ade-4da6-b5fa-a3bf4f15fbbf" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-510", + "l": "us-west-1", + "r": [ + { + "FileSystemArn": "1788a445-046b-4c52-9ef0-011ca10f7203", + "OwnerId": "56d709fb-6ade-4da6-b5fa-a3bf4f15fbbf" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-511", + "l": "eu-west-1", + "r": [ + { + "LoadBalancerName": "cba94ef7-c2fa-4ee7-a4ff-0e67e1fbb4a8" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-511", + "l": "us-west-1", + "r": [ + { + "LoadBalancerName": "cba94ef7-c2fa-4ee7-a4ff-0e67e1fbb4a8" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-512", + "l": "eu-west-1", + "r": [ + { + "LoadBalancerName": "698b1f0a-3a88-4c91-913b-ff9a32922e23" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-512", + "l": "us-west-1", + "r": [ + { + "LoadBalancerName": "698b1f0a-3a88-4c91-913b-ff9a32922e23" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-513", + "l": "eu-west-1", + "r": [ + { + "CertificateArn": "3815eabc-79c1-4ae4-87dc-9d1a0db5077b", + "DomainName": "db4b363b-7d78-4976-b095-c28f6d9de2e4" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-513", + "l": "us-west-1", + "r": [ + { + "CertificateArn": "3815eabc-79c1-4ae4-87dc-9d1a0db5077b", + "DomainName": "db4b363b-7d78-4976-b095-c28f6d9de2e4" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-514", + "l": "multiregion", + "r": [ + { + "Arn": "0e0d4fb9-e92f-4e78-a3f3-e953b9a941a8" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-515", + "l": "eu-west-1", + "r": [ + { + "field": "277c8ed7-7377-41cd-980e-be9c62693705" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-515", + "l": "us-west-1", + "r": [ + { + "field": "277c8ed7-7377-41cd-980e-be9c62693705" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-516", + "l": "multiregion", + "r": [ + { + "Name": "96d516c3-b086-4dc8-9373-62cb8bd8747c" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-517", + "l": "multiregion", + "r": [ + { + "Name": "8144b6ae-1b31-4e3c-b77f-8d835e0063cc" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-518", + "l": "multiregion", + "r": [ + { + "Name": "11a902f6-9bb9-4eaf-8dd8-6756185cc57e" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-519", + "l": "eu-west-1", + "r": [ + { + "VpnConnectionId": "d0003a62-ff8f-400a-bc38-29dd59826562" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-519", + "l": "us-west-1", + "r": [ + { + "VpnConnectionId": "d0003a62-ff8f-400a-bc38-29dd59826562" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-520", + "l": "eu-west-1", + "r": [ + { + "LaunchConfigurationName": "83923c26-1183-4b6a-8f9d-8dc185fcc208" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-520", + "l": "us-west-1", + "r": [ + { + "LaunchConfigurationName": "83923c26-1183-4b6a-8f9d-8dc185fcc208" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-521", + "l": "eu-west-1", + "r": [ + { + "taskDefinitionArn": "e7941485-de3f-4247-887d-77f002110889" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-521", + "l": "us-west-1", + "r": [ + { + "taskDefinitionArn": "e7941485-de3f-4247-887d-77f002110889" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-522", + "l": "eu-west-1", + "r": [ + { + "taskDefinitionArn": "ee961c53-8c96-48ec-9e12-8a3994620465" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-522", + "l": "us-west-1", + "r": [ + { + "taskDefinitionArn": "ee961c53-8c96-48ec-9e12-8a3994620465" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-523", + "l": "eu-west-1", + "r": [ + { + "KeyArn": "7eac07a4-5ffa-4ff7-a492-50a5478af189", + "AWSAccountId": "759577c0-337f-4833-93f7-bd3b7b355bed" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-523", + "l": "us-west-1", + "r": [ + { + "KeyArn": "7eac07a4-5ffa-4ff7-a492-50a5478af189", + "AWSAccountId": "759577c0-337f-4833-93f7-bd3b7b355bed" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-524", + "l": "eu-west-1", + "r": [ + { + "Name": "2647542f-58f1-4761-b140-413fb3bb9711", + "WebACLId": "c003847f-c06b-46b8-a4ba-ce4fdebc0bf8" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-524", + "l": "us-west-1", + "r": [ + { + "Name": "2647542f-58f1-4761-b140-413fb3bb9711", + "WebACLId": "c003847f-c06b-46b8-a4ba-ce4fdebc0bf8" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-525", + "l": "multiregion", + "r": [ + { + "Name": "6d453d24-847e-45f7-801e-9e3da04d29c9", + "RuleId": "7f44ee26-26ef-4700-8384-d4c875cd2f0a" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-526", + "l": "multiregion", + "r": [ + { + "Name": "f69f5d0b-0505-43bc-b260-923374be788a", + "RuleGroupId": "8e25e722-09b5-4460-8637-b8253dfb7aaa" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-527", + "l": "multiregion", + "r": [ + { + "WebACLId": "790d68e0-1e92-4987-8565-92135633c5e7", + "Name": "aee9b5eb-af7b-415a-9d60-9f166fadf3b7" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-528", + "l": "eu-west-1", + "r": [ + { + "CertificateArn": "10892c1b-1e20-4d3d-ade5-0c4e0ac66b13", + "DomainName": "3ef41bc3-152e-4ec1-9f33-63f9dbea35ac" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-528", + "l": "us-west-1", + "r": [ + { + "CertificateArn": "10892c1b-1e20-4d3d-ade5-0c4e0ac66b13", + "DomainName": "3ef41bc3-152e-4ec1-9f33-63f9dbea35ac" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-529", + "l": "eu-west-1", + "r": [ + { + "InstanceId": "94d4049e-bbd3-4f7b-9773-a1c100165d5e", + "BlockDeviceMappings": "0414d157-a15d-4f54-8334-d33347dee80e" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-529", + "l": "us-west-1", + "r": [ + { + "InstanceId": "94d4049e-bbd3-4f7b-9773-a1c100165d5e", + "BlockDeviceMappings": "0414d157-a15d-4f54-8334-d33347dee80e" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-530", + "l": "multiregion", + "r": [ + { + "ARN": "d6ae5de0-aa6a-4e91-af57-80bc38445a42" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-531", + "l": "eu-west-1", + "r": [ + { + "account_id": "94aeace7-e212-40f8-8cde-e76efb589ec3" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-531", + "l": "us-west-1", + "r": [ + { + "account_id": "94aeace7-e212-40f8-8cde-e76efb589ec3" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-532", + "l": "eu-west-1", + "r": [ + { + "CertificateArn": "f16a5765-04ce-4c37-94fa-12364649fc94" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-532", + "l": "us-west-1", + "r": [ + { + "CertificateArn": "f16a5765-04ce-4c37-94fa-12364649fc94" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-533", + "l": "eu-west-1", + "r": [ + { + "KeyPairId": "1d3b034e-d184-4399-bcf9-3c672974cd9d" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-533", + "l": "us-west-1", + "r": [ + { + "KeyPairId": "1d3b034e-d184-4399-bcf9-3c672974cd9d" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-534", + "l": "eu-west-1", + "r": [ + { + "AutoScalingGroupARN": "f4d5d2b4-f0e9-4788-ad3f-783b03a540e7" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-534", + "l": "us-west-1", + "r": [ + { + "AutoScalingGroupARN": "f4d5d2b4-f0e9-4788-ad3f-783b03a540e7" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-535", + "l": "eu-west-1", + "r": [ + { + "LoadBalancerName": "c0646905-411f-44b2-a81e-ad716975ffa1" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-535", + "l": "us-west-1", + "r": [ + { + "LoadBalancerName": "c0646905-411f-44b2-a81e-ad716975ffa1" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-536", + "l": "eu-west-1", + "r": [ + { + "FunctionArn": "6e86558e-74af-493f-b603-c3c0570b0517" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-536", + "l": "us-west-1", + "r": [ + { + "FunctionArn": "6e86558e-74af-493f-b603-c3c0570b0517" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-537", + "l": "eu-west-1", + "r": [ + { + "taskDefinitionArn": "4088dfef-8ebf-4200-8db5-cc01777709ef" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-537", + "l": "us-west-1", + "r": [ + { + "taskDefinitionArn": "4088dfef-8ebf-4200-8db5-cc01777709ef" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-538", + "l": "multiregion", + "r": [ + { + "ARN": "d82c4fc0-2fb1-436a-a19c-1b9faf4c6dc0", + "c7n:mismatched-s3-origin": "36c35bec-3359-4164-b537-4d8516d9b18a" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-539", + "l": "multiregion", + "r": [ + { + "ARN": "575bbc52-6e1d-45d2-ba07-3b976f1a7293" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-540", + "l": "eu-west-1", + "r": [ + { + "Name": "1901cc9b-9d37-486a-bdf4-ef3aed159d1f" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-540", + "l": "us-west-1", + "r": [ + { + "Name": "1901cc9b-9d37-486a-bdf4-ef3aed159d1f" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-541", + "l": "eu-west-1", + "r": [ + { + "Name": "52409759-502a-4637-bde6-187615f14354" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-541", + "l": "us-west-1", + "r": [ + { + "Name": "52409759-502a-4637-bde6-187615f14354" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-542", + "l": "eu-west-1", + "r": [ + { + "Name": "74a0bfe9-90a5-429a-ab2c-4756fb7d87c5" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-542", + "l": "us-west-1", + "r": [ + { + "Name": "74a0bfe9-90a5-429a-ab2c-4756fb7d87c5" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-543", + "l": "multiregion", + "r": [ + { + "ARN": "5d24edd2-12b0-476e-9292-a43835cb51be" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-544", + "l": "eu-west-1", + "r": [ + { + "TrailARN": "6e424d92-4ca7-4902-9ab0-56e6dc0fae86" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-544", + "l": "us-west-1", + "r": [ + { + "TrailARN": "6e424d92-4ca7-4902-9ab0-56e6dc0fae86" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-545", + "l": "eu-west-1", + "r": [ + { + "stateMachineArn": "732884b1-ec84-48f7-a5d4-c63497968a6c" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-545", + "l": "us-west-1", + "r": [ + { + "stateMachineArn": "732884b1-ec84-48f7-a5d4-c63497968a6c" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-546", + "l": "eu-west-1", + "r": [], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-546", + "l": "us-west-1", + "r": [], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-548", + "l": "eu-west-1", + "r": [ + { + "VolumeId": "ce529487-9dbc-4663-882c-e8a1246bc0f2" + } + ], + "t": 1700042979.4682903 + }, + { + "p": "ecc-aws-548", + "l": "us-west-1", + "r": [ + { + "VolumeId": "ce529487-9dbc-4663-882c-e8a1246bc0f2" + } + ], + "t": 1700042979.4682903 + } +] \ No newline at end of file diff --git a/tests/tests_metrics/mock_files/reports/raw/EPAM Systems/AWS/AWS-1234567890123/latest/1.json b/tests/tests_metrics/mock_files/reports/raw/EPAM Systems/AWS/AWS-1234567890123/latest/1.json new file mode 100644 index 000000000..cc0bd44c9 --- /dev/null +++ b/tests/tests_metrics/mock_files/reports/raw/EPAM Systems/AWS/AWS-1234567890123/latest/1.json @@ -0,0 +1,22 @@ +[ + { + "p": "ecc-aws-001", + "l": "multiregion", + "r": [ + { + "Arn": "ad321729-0743-431f-8502-6799379ac29e" + } + ], + "t": 1700042979.4652865 + }, + { + "p": "ecc-aws-002", + "l": "multiregion", + "r": [ + { + "Arn": "6aaea08c-3cb0-482e-aaad-c2115df98660" + } + ], + "t": 1700042979.4652865 + } +] \ No newline at end of file diff --git a/tests/tests_metrics/mock_files/reports/raw/EPAM Systems/AWS/AWS-1234567890123/latest/meta.json b/tests/tests_metrics/mock_files/reports/raw/EPAM Systems/AWS/AWS-1234567890123/latest/meta.json new file mode 100644 index 000000000..d6175c925 --- /dev/null +++ b/tests/tests_metrics/mock_files/reports/raw/EPAM Systems/AWS/AWS-1234567890123/latest/meta.json @@ -0,0 +1 @@ +{"ecc-aws-001":{"description":"Multi-factor authentication (MFA) is not enabled for all IAM users that have console password\n","resource":"aws.iam-user"},"ecc-aws-002":{"description":"Access keys are not rotated every 90 days or less\n","resource":"aws.iam-user"},"ecc-aws-003":{"description":"VPC flow logging is not enabled in all VPCs\n","resource":"aws.vpc"},"ecc-aws-004":{"description":"S3 Bucket Policy allows HTTP requests\n","resource":"aws.s3-light"},"ecc-aws-005":{"description":"RDS is open to a large scope\n","resource":"aws.rds"},"ecc-aws-006":{"description":"RDS retention policy is less than 7 days\n","resource":"rds"},"ecc-aws-007":{"description":"RDS instances do not have multi-availability zone enabled\n","resource":"rds"},"ecc-aws-008":{"description":"SSL/TLS certificates expire in less than a month\n","resource":"iam-certificate"},"ecc-aws-009":{"description":"SSL/TLS certificates expire in less than a week\n","resource":"iam-certificate"},"ecc-aws-010":{"description":"Application or Network Load balancer SSL certificate expire in less than a week\n","resource":"app-elb"},"ecc-aws-011":{"description":"Application or Network Load balancer SSL certificate expire in less than a month\n","resource":"app-elb"},"ecc-aws-012":{"description":"Cloudfront Distribution uses weak ciphers\n","resource":"aws.distribution"},"ecc-aws-013":{"description":"Classic Load Balancer uses weak ciphers\n","resource":"elb"},"ecc-aws-014":{"description":"Classic Load Balancer listeners are not blocking connection requests over http\n","resource":"elb"},"ecc-aws-015":{"description":"Virtual MFA is not enabled for the \"root\" account\n","resource":"aws.account"},"ecc-aws-016":{"description":"Hardware MFA is not enabled for the 'root' account\n","resource":"account"},"ecc-aws-017":{"description":"Credentials unused for 45 days or more are not disabled\n","resource":"aws.iam-user"},"ecc-aws-018":{"description":"IAM Users receive permissions not only through groups\n","resource":"aws.iam-user"},"ecc-aws-019":{"description":"IAM password policy does not prevent password reuse\n","resource":"aws.account"},"ecc-aws-020":{"description":"Instances without any tags\n","resource":"ec2"},"ecc-aws-021":{"description":"EBS Volumes without recent snapshots\n","resource":"aws.ebs"},"ecc-aws-022":{"description":"EBS Snapshots older than 30 days\n","resource":"aws.ebs-snapshot"},"ecc-aws-023":{"description":"Classic Load Balancer Access Logging is disabled \n","resource":"elb"},"ecc-aws-024":{"description":"SQS encryption is disabled\n","resource":"sqs"},"ecc-aws-025":{"description":"Instances without termination protection\n","resource":"ec2"},"ecc-aws-026":{"description":"RDS instances without automated backups\n","resource":"rds"},"ecc-aws-027":{"description":"Security groups do not prevent all incoming traffic from 0-65535\n","resource":"aws.security-group"},"ecc-aws-028":{"description":"Security group rule allows internet traffic to DNS port (53)\n","resource":"aws.security-group"},"ecc-aws-029":{"description":"Security group rule allows internet traffic to FTP port (21)\n","resource":"aws.security-group"},"ecc-aws-030":{"description":"Security group rule allows internet traffic to HTTP port (80)\n","resource":"aws.security-group"},"ecc-aws-031":{"description":"Security group rule allows internet traffic to Microsoft-DS port (445)\n","resource":"aws.security-group"},"ecc-aws-032":{"description":"Security group rule allows internet traffic to MongoDB port (27017)\n","resource":"aws.security-group"},"ecc-aws-033":{"description":"Security group rule allows internet traffic to MySQL DB port (3306)\n","resource":"aws.security-group"},"ecc-aws-034":{"description":"Security group rule allows internet traffic to NetBIOS-SSN port (139)\n","resource":"aws.security-group"},"ecc-aws-035":{"description":"Security group rule allows internet traffic to Oracle DB port (1521)\n","resource":"aws.security-group"},"ecc-aws-036":{"description":"Security group rule allows internet traffic to POP3 port (110)\n","resource":"aws.security-group"},"ecc-aws-037":{"description":"Security group rule allows internet traffic to PostgreSQL port (5432)\n","resource":"aws.security-group"},"ecc-aws-038":{"description":"Security group rule allows internet traffic to SMTP port (25)\n","resource":"aws.security-group"},"ecc-aws-039":{"description":"Security group rule allows internet traffic to Telnet port (23)\n","resource":"aws.security-group"},"ecc-aws-040":{"description":"EKS cluster is not using the latest version\n","resource":"aws.eks"},"ecc-aws-041":{"description":"RDS Instances without tags\n","resource":"rds"},"ecc-aws-042":{"description":"S3 is not using a KMS key for encryption\n","resource":"s3-light"},"ecc-aws-043":{"description":"S3 Bucket life cycle is not configured\n","resource":"s3-light"},"ecc-aws-044":{"description":"S3 Buckets without tags \n","resource":"s3-light"},"ecc-aws-045":{"description":"Password policy does not require at least one uppercase letter\n","resource":"aws.account"},"ecc-aws-046":{"description":"Root user account access key exists\n","resource":"aws.account"},"ecc-aws-047":{"description":"Password policy does not require at least one lowercase letter\n","resource":"aws.account"},"ecc-aws-048":{"description":"Password policy does not require at least one symbol\n","resource":"aws.account"},"ecc-aws-049":{"description":"Password policy does not require at least one number\n","resource":"aws.account"},"ecc-aws-050":{"description":"Password policy does not require minimum length of 14 characters or greater\n","resource":"aws.account"},"ecc-aws-051":{"description":"IAM password policy is not configured to expire passwords after 90 days or less\n","resource":"aws.account"},"ecc-aws-052":{"description":"CloudTrail is not enabled in all regions\n","resource":"aws.account"},"ecc-aws-053":{"description":"CloudTrail log file validation is disabled\n","resource":"aws.cloudtrail"},"ecc-aws-054":{"description":"IAM policies that allow full \"*:*\" administrative privileges are in use\n","resource":"iam-policy-all"},"ecc-aws-055":{"description":"CloudTrail trails are not integrated with CloudWatch Logs\n","resource":"aws.cloudtrail"},"ecc-aws-056":{"description":"Access key was created during initial IAM user setup\n","resource":"aws.iam-user"},"ecc-aws-057":{"description":"IAM instance roles are not used for AWS resource access from instances\n","resource":"aws.ec2"},"ecc-aws-058":{"description":"Support role has not been created to manage incidents with AWS Support\n","resource":"aws.account"},"ecc-aws-059":{"description":"AWS Config is not enabled in all regions\n","resource":"account"},"ecc-aws-060":{"description":"CloudTrail logs are not encrypted at rest using KMS CMK\n","resource":"aws.cloudtrail"},"ecc-aws-061":{"description":"Rotation for symmetric customer-created CMKs is not enabled\n","resource":"aws.kms-key"},"ecc-aws-062":{"description":"Security groups allow ingress from 0.0.0.0/0 or ::/0 to remote server administration port (22)\n","resource":"aws.security-group"},"ecc-aws-063":{"description":"Security groups allow ingress from 0.0.0.0/0 or ::/0 to remote server administration port (3389)\n","resource":"aws.security-group"},"ecc-aws-064":{"description":"VPC default security group does not restrict all traffic\n","resource":"aws.security-group"},"ecc-aws-065":{"description":"Traffic between a CloudFront distribution and the origin is not enforced to allow HTTPS-only\n","resource":"aws.distribution"},"ecc-aws-066":{"description":"EKS cluster endpoint does not have protected access\n","resource":"aws.eks"},"ecc-aws-067":{"description":"Log metric filter and alarm do not exist for unauthorized API calls\n","resource":"aws.account"},"ecc-aws-068":{"description":"S3 bucket used to store CloudTrail logs is publicly accessible\n","resource":"aws.cloudtrail"},"ecc-aws-069":{"description":"S3 bucket allows all actions from all principals\n","resource":"aws.s3-light"},"ecc-aws-070":{"description":"Unused security groups exist\n","resource":"security-group"},"ecc-aws-071":{"description":"CodeBuild GitHub or Bitbucket source repository URLs do not use OAuth\n","resource":"aws.codebuild"},"ecc-aws-072":{"description":"Auto scaling groups associated with a load balancer do not use health checks\n","resource":"asg"},"ecc-aws-073":{"description":"Unused EC2 EIPs exist\n","resource":"network-addr"},"ecc-aws-074":{"description":"Elasticsearch Service domains are not in a VPC\n","resource":"elasticsearch"},"ecc-aws-075":{"description":"Elasticsearch Service domains do not have encryption at rest\n","resource":"elasticsearch"},"ecc-aws-076":{"description":"EBS snapshots are publicly restorable\n","resource":"ebs-snapshot"},"ecc-aws-077":{"description":"Log metric filter and alarm do not exist for Management Console sign-in without MFA\n","resource":"aws.account"},"ecc-aws-078":{"description":"Log metric filter and alarm do not exist for usage of \"root\" account\n","resource":"aws.account"},"ecc-aws-079":{"description":"Log metric filter and alarm do not exist for IAM policy changes\n","resource":"aws.account"},"ecc-aws-080":{"description":"Log metric filter and alarm do not exist for CloudTrail configuration changes\n","resource":"aws.account"},"ecc-aws-081":{"description":"Log metric filter and alarm do not exist for AWS Management Console authentication failures\n","resource":"aws.account"},"ecc-aws-082":{"description":"Log metric filter and alarm do not exist for disabling or scheduled deletion of customer created CMKs\n","resource":"aws.account"},"ecc-aws-083":{"description":"Cloud Front is not integrated with WAF\n","resource":"distribution"},"ecc-aws-084":{"description":"S3 bucket access logging is disabled on the CloudTrail S3 bucket\n","resource":"aws.cloudtrail"},"ecc-aws-085":{"description":"Lambda functions are not in a VPC\n","resource":"lambda"},"ecc-aws-086":{"description":"Lambda roles have admin privileges\n","resource":"lambda"},"ecc-aws-087":{"description":"Redshift clusters do not prohibit public access\n","resource":"redshift"},"ecc-aws-088":{"description":"S3 bucket cross-region replication is disabled\n","resource":"s3-light"},"ecc-aws-089":{"description":"CodeBuild project environment variables contain clear text credentials\n","resource":"codebuild"},"ecc-aws-090":{"description":"RDS snapshots do not prohibit public access\n","resource":"rds-snapshot"},"ecc-aws-091":{"description":"Amazon EC2 instances managed by Systems Manager have a patch compliance status of NON-COMPLIANT after a patch installation\n","resource":"ec2"},"ecc-aws-092":{"description":"AMIs are exposed to public access\n","resource":"ami"},"ecc-aws-093":{"description":"SageMaker is not placed in VPC\n","resource":"aws.sagemaker-notebook"},"ecc-aws-094":{"description":"Log metric filter and alarm do not exist for S3 bucket policy changes\n","resource":"aws.account"},"ecc-aws-095":{"description":"Log metric filter and alarm do not exist for AWS Config configuration changes\n","resource":"aws.account"},"ecc-aws-096":{"description":"Log metric filter and alarm do not exist for security group changes\n","resource":"aws.account"},"ecc-aws-097":{"description":"Log metric filter and alarm do not exist for changes to Network Access Control Lists (NACL)\n","resource":"aws.account"},"ecc-aws-098":{"description":"Log metric filter and alarm do not exist for changes to network gateways\n","resource":"aws.account"},"ecc-aws-099":{"description":"Log metric filter and alarm do not exist for route table changes\n","resource":"aws.account"},"ecc-aws-100":{"description":"Log metric filter and alarm do not exist for VPC changes\n","resource":"aws.account"},"ecc-aws-101":{"description":"VPC subnets automatic public ip assignment is enabled\n","resource":"subnet"},"ecc-aws-102":{"description":"SageMaker Notebook has direct internet access\n","resource":"aws.sagemaker-notebook"},"ecc-aws-103":{"description":"Cloudfront web distributions do not use custom SSL certificates\n","resource":"distribution"},"ecc-aws-104":{"description":"Cloudfront web distribution with geo restriction is not enabled\n","resource":"distribution"},"ecc-aws-105":{"description":"Kinesis Streams Keys are not rotated\n","resource":"aws.kinesis"},"ecc-aws-106":{"description":"ACM has certificates with wildcard domain names\n","resource":"acm-certificate"},"ecc-aws-107":{"description":"AWS Certificate Manager (ACM) has unused certificates\n","resource":"acm-certificate"},"ecc-aws-108":{"description":"AWS CloudFront distribution with access logging is disabled\n","resource":"distribution"},"ecc-aws-109":{"description":"Invalid or failed certificates are not removed from ACM\n","resource":"acm-certificate"},"ecc-aws-110":{"description":"ECS Cluster At-Rest Encryption is disabled\n","resource":"ecs"},"ecc-aws-111":{"description":"ALB is not protected by WAF regional\n","resource":"app-elb"},"ecc-aws-112":{"description":"S3 bucket versioning MFA delete is disabled\n","resource":"s3-light"},"ecc-aws-113":{"description":"Inline IAM policies are in use\n","resource":"iam-user"},"ecc-aws-114":{"description":"Kubernetes Engine Clusters network firewall inbound rule is overly permissive to all traffic\n","resource":"eks"},"ecc-aws-115":{"description":"Expired certificates are not removed from the AWS Certificate Manager (ACM)\n","resource":"acm-certificate"},"ecc-aws-116":{"description":"API endpoint type in the API gateway is not private and exposed to the public internet\n","resource":"rest-api"},"ecc-aws-117":{"description":"API Key is not required on Method Request\n","resource":"rest-resource"},"ecc-aws-118":{"description":"Container is using IAM roles for an instance\n","resource":"ecs-service"},"ecc-aws-119":{"description":"Kinesis streams are not encrypted with KMS CMK\n","resource":"kinesis"},"ecc-aws-120":{"description":"Kinesis Server data at rest has no server-side encryption\n","resource":"kinesis"},"ecc-aws-121":{"description":"Outbound traffic is allowed to all ports\n","resource":"security-group"},"ecc-aws-122":{"description":"DynamoDB is not encrypted using KMS CMK\n","resource":"dynamodb-table"},"ecc-aws-123":{"description":"Amazon EFS file systems are not encrypted\n","resource":"efs"},"ecc-aws-124":{"description":"EFS file systems are not encrypted using KMS CMK\n","resource":"efs"},"ecc-aws-125":{"description":"ElastiCache Redis cluster at-rest encryption is disabled\n","resource":"cache-cluster"},"ecc-aws-126":{"description":"Redshift instances are not encrypted\n","resource":"redshift"},"ecc-aws-127":{"description":"Unencrypted RDS cluster storage is in use\n","resource":"rds-cluster"},"ecc-aws-128":{"description":"Expired Route53 domain name\n","resource":"aws.r53domain"},"ecc-aws-129":{"description":"Application or Network Load Balancer access logs is disabled\n","resource":"app-elb"},"ecc-aws-130":{"description":"Security Policy of the Network Load Balancer is not updated\n","resource":"app-elb"},"ecc-aws-131":{"description":"Instance with unencrypted service is exposed to the public internet\n","resource":"aws.ec2"},"ecc-aws-132":{"description":"Public Instance with a sensitive service is exposed to the entire internet\n","resource":"aws.ec2"},"ecc-aws-133":{"description":"Amazon GuardDuty service is not enabled\n","resource":"account"},"ecc-aws-134":{"description":"Classic Load Balancer with sensitive services is exposed to the entire internet\n","resource":"aws.elb"},"ecc-aws-135":{"description":"Classic Load Balancer with an unencrypted sensitive service is exposed to the public internet\n","resource":"aws.elb"},"ecc-aws-136":{"description":"Application Load Balancer with sensitive services is exposed to the entire internet\n","resource":"aws.app-elb"},"ecc-aws-137":{"description":"Application Load Balancer with an unencrypted sensitive service is exposed to the public internet\n","resource":"aws.app-elb"},"ecc-aws-138":{"description":"Root user is used for administrative and daily tasks\n","resource":"aws.account"},"ecc-aws-139":{"description":"IAM Access analyzer is not enabled for all regions\n","resource":"aws.account"},"ecc-aws-140":{"description":"More than one active access key is available for a single IAM user\n","resource":"iam-user"},"ecc-aws-141":{"description":"Expired SSL/TLS certificates stored in IAM are not removed\n","resource":"iam-certificate"},"ecc-aws-142":{"description":"S3 Buckets are not configured with 'Block public access' bucket settings\n","resource":"aws.s3-light"},"ecc-aws-143":{"description":"Object-level logging for write events is disabled for S3 bucket\n","resource":"aws.account"},"ecc-aws-144":{"description":"Object-level logging for read events is disabled for S3 bucket\n","resource":"aws.account"},"ecc-aws-145":{"description":"Log metric filter and alarm do not exist for AWS Organizations changes\n","resource":"aws.account"},"ecc-aws-146":{"description":"Network ACLs allow ingress from 0.0.0.0/0 to remote server administration ports\n","resource":"aws.network-acl"},"ecc-aws-147":{"description":"EBS volume encryption is disabled\n","resource":"aws.ebs"},"ecc-aws-148":{"description":"Logging for S3 bucket is disabled\n","resource":"s3-light"},"ecc-aws-149":{"description":"RDS instance is publicly accessible \n","resource":"rds"},"ecc-aws-150":{"description":"API Gateway REST API cache data is not encrypted at rest\n","resource":"rest-stage"},"ecc-aws-151":{"description":"Security groups allow unrestricted access to FTP port 20 \n","resource":"aws.security-group"},"ecc-aws-152":{"description":"Classic Load Balancers connection draining is not enabled\n","resource":"aws.elb"},"ecc-aws-153":{"description":"Elasticsearch domains audit logging is not enabled \n","resource":"aws.elasticsearch"},"ecc-aws-154":{"description":"Elasticsearch domains have less than three data nodes\n","resource":"aws.elasticsearch"},"ecc-aws-155":{"description":"Elasticsearch domains are not configured with at least three dedicated master nodes\n","resource":"aws.elasticsearch"},"ecc-aws-156":{"description":"Connections to Elasticsearch domains are not encrypted using TLS 1.2\n","resource":"aws.elasticsearch"},"ecc-aws-157":{"description":"RDS DB clusters are not configured to copy tags to snapshots\n","resource":"aws.rds-cluster"},"ecc-aws-158":{"description":"RDS DB instances are not configured to copy tags to snapshots\n","resource":"aws.rds"},"ecc-aws-159":{"description":"RDS event notifications subscription is not configured for critical cluster events\n","resource":"aws.account"},"ecc-aws-160":{"description":"RDS event notifications subscription is not configured for critical database instance events\n","resource":"aws.account"},"ecc-aws-161":{"description":"RDS event notifications subscription is not configured for database parameter group events\n","resource":"aws.account"},"ecc-aws-162":{"description":"RDS event notifications subscription is not configured for critical database security group events\n","resource":"aws.account"},"ecc-aws-163":{"description":"RDS database instances are using database engine default ports\n","resource":"aws.rds"},"ecc-aws-164":{"description":"Redshift clusters audit logging is disabled\n","resource":"redshift"},"ecc-aws-165":{"description":"Amazon ECS services public IP addresses are assigned to them automatically \n","resource":"aws.ecs-service"},"ecc-aws-166":{"description":"Security groups allow unrestricted access to RPC port 135\n","resource":"aws.security-group"},"ecc-aws-167":{"description":"Security groups allow unrestricted access to IMAP port 143\n","resource":"aws.security-group"},"ecc-aws-168":{"description":"Security groups allow unrestricted access to MSSQL ports 1433, 1434\n","resource":"aws.security-group"},"ecc-aws-169":{"description":"Security groups allow unrestricted access to ahsp port 4333\n","resource":"aws.security-group"},"ecc-aws-170":{"description":"Security groups allow unrestricted access to fcp-addr-srvr1 port 5500\n","resource":"aws.security-group"},"ecc-aws-171":{"description":"Security groups allow unrestricted access to Kibana port 5601\n","resource":"aws.security-group"},"ecc-aws-172":{"description":"Security groups allow unrestricted access to proxy port 8080\n","resource":"aws.security-group"},"ecc-aws-173":{"description":"Security groups allow unrestricted access to Elasticsearch service ports 9200, 9300\n","resource":"aws.security-group"},"ecc-aws-174":{"description":"RDS database clusters are using a database engine default ports\n","resource":"aws.rds-cluster"},"ecc-aws-175":{"description":"RDS instances storage not encrypted\n","resource":"rds"},"ecc-aws-176":{"description":"RDS snapshot storage not encrypted\n","resource":"rds-snapshot"},"ecc-aws-177":{"description":"API Gateway REST API stages are not configured to use SSL certificates for backend authentication\n","resource":"rest-stage"},"ecc-aws-178":{"description":"API Gateway REST API stages do not have AWS X-Ray tracing enabled\n","resource":"rest-stage"},"ecc-aws-179":{"description":"CloudFront distributions do not have a default root object configured\n","resource":"distribution"},"ecc-aws-180":{"description":"CloudFront distributions origin failover is not configured\n","resource":"distribution"},"ecc-aws-181":{"description":"AWS Database Migration Service replication instances are public\n","resource":"dms-instance"},"ecc-aws-182":{"description":"DynamoDB table Auto Scaling or On-Demand is not enabled on DynamoDB tables \n","resource":"aws.dynamodb-table"},"ecc-aws-183":{"description":"DynamoDB tables do not have point-in-time recovery enabled\n","resource":"dynamodb-table"},"ecc-aws-184":{"description":"DynamoDB Accelerator clusters are not encrypted at rest\n","resource":"dax"},"ecc-aws-185":{"description":"Stopped EC2 instances are not removed after a specified time period\n","resource":"aws.ec2"},"ecc-aws-186":{"description":"EC2 instances have public IP address\n","resource":"aws.ec2"},"ecc-aws-187":{"description":"EC2 is not configured to use VPC endpoints that are created for the EC2 service\n","resource":"vpc"},"ecc-aws-188":{"description":"Unused network access control lists are not removed\n","resource":"aws.network-acl"},"ecc-aws-189":{"description":"EC2 instances are using multiple ENIs\n","resource":"ec2"},"ecc-aws-190":{"description":"Amazon ECS task definitions do not have secure networking modes and user definitions\n","resource":"ecs-task-definition"},"ecc-aws-191":{"description":"Amazon EFS volumes are not in backup plans\n","resource":"efs"},"ecc-aws-192":{"description":"Elastic Beanstalk environments do not have enhanced health reporting enabled\n","resource":"aws.elasticbeanstalk-environment"},"ecc-aws-193":{"description":"Application load balancers are not configured to drop invalid HTTP headers\n","resource":"aws.app-elb"},"ecc-aws-194":{"description":"Application, Network or Gateway Load Balancer deletion protection is not enabled\n","resource":"aws.app-elb"},"ecc-aws-195":{"description":"Application Load Balancer is not configured to redirect all HTTP requests to HTTPS\n","resource":"app-elb"},"ecc-aws-196":{"description":"Amazon EMR cluster master nodes have public IP addresses\n","resource":"emr"},"ecc-aws-197":{"description":"Elasticsearch domains data sent between nodes is not encrypted\n","resource":"aws.elasticsearch"},"ecc-aws-198":{"description":"Elasticsearch domain error logging to CloudWatch Logs is not enabled\n","resource":"aws.elasticsearch"},"ecc-aws-199":{"description":"Enhanced monitoring is not configured for RDS DB instances\n","resource":"aws.rds"},"ecc-aws-200":{"description":"RDS clusters deletion protection is not enabled\n","resource":"aws.rds-cluster"},"ecc-aws-201":{"description":"RDS DB instances deletion protection is not enabled\n","resource":"rds"},"ecc-aws-202":{"description":"Oracle database logging is disabled\n","resource":"aws.rds"},"ecc-aws-203":{"description":"PostgreSQL database logging is disabled\n","resource":"aws.rds"},"ecc-aws-204":{"description":"MySQL database logging is disabled\n","resource":"aws.rds"},"ecc-aws-205":{"description":"MariaDB database logging is disabled\n","resource":"aws.rds"},"ecc-aws-206":{"description":"SQL Server database logging is disabled\n","resource":"aws.rds"},"ecc-aws-207":{"description":"Aurora database logging is disabled\n","resource":"rds"},"ecc-aws-208":{"description":"Aurora-MySQL database logging is disabled\n","resource":"rds"},"ecc-aws-209":{"description":"Aurora-PostgreSQL database logging is disabled\n","resource":"rds"},"ecc-aws-210":{"description":"IAM authentication is not configured for RDS instances\n","resource":"aws.rds"},"ecc-aws-211":{"description":"IAM authentication is not configured for RDS clusters\n","resource":"rds-cluster"},"ecc-aws-212":{"description":"Amazon Aurora clusters backtracking is disabled\n","resource":"aws.rds-cluster"},"ecc-aws-213":{"description":"DS DB clusters are not configured for multiple Availability Zones\n","resource":"aws.rds-cluster"},"ecc-aws-214":{"description":"Connections to Redshift clusters are not encrypted in transit\n","resource":"redshift"},"ecc-aws-215":{"description":"Amazon Redshift clusters automatic snapshots are disabled\n","resource":"redshift"},"ecc-aws-216":{"description":"Amazon Redshift automatic upgrades to major versions are disabled \n","resource":"redshift"},"ecc-aws-217":{"description":"Amazon Redshift clusters are not using enhanced VPC routing\n","resource":"redshift"},"ecc-aws-218":{"description":"Secrets Manager secrets automatic rotation disabled\n","resource":"aws.secrets-manager"},"ecc-aws-219":{"description":"Secrets Manager secrets configured with automatic rotation are not rotating successfully\n","resource":"aws.secrets-manager"},"ecc-aws-220":{"description":"Unused Secrets Manager secrets are not removed\n","resource":"aws.secrets-manager"},"ecc-aws-221":{"description":"SNS topics are not encrypted at rest using AWS KMS\n","resource":"sns"},"ecc-aws-222":{"description":"EC2 instances are not managed by AWS Systems Manager\n","resource":"aws.ec2"},"ecc-aws-223":{"description":"Instances managed by Systems Manager do not have association compliance status of COMPLIANT\n","resource":"aws.ec2"},"ecc-aws-224":{"description":"EC2 instances do not use IMDSv2\n","resource":"aws.ec2"},"ecc-aws-225":{"description":"EKS control plane logging is disabled\n","resource":"aws.eks"},"ecc-aws-226":{"description":"Amazon EKS clusters security group traffic is not restricted\n","resource":"aws.eks"},"ecc-aws-227":{"description":"Kubernetes Secrets are not encrypted using KMS CMK\n","resource":"aws.eks"},"ecc-aws-228":{"description":"Amazon ECR is not configured with immutable tags\n","resource":"ecr"},"ecc-aws-229":{"description":"Amazon ECR repository does not have encryption with KMS enabled\n","resource":"ecr"},"ecc-aws-230":{"description":"Amazon ECR image scanning on push is disabled\n","resource":"ecr"},"ecc-aws-231":{"description":"Maximum log file lifetime is not set correctly for PostgreSQL\n","resource":"aws.rds"},"ecc-aws-232":{"description":"Maximum log file size is not set correctly for PostgreSQL\n","resource":"aws.rds"},"ecc-aws-233":{"description":"The 'debug_print_parse' flag is enabled for PostgreSQL\n","resource":"aws.rds"},"ecc-aws-234":{"description":"The 'debug_print_rewritten' flag is enabled for PostgreSQL\n","resource":"aws.rds"},"ecc-aws-235":{"description":"The 'debug_print_plan' flag is enabled for PostgreSQL\n","resource":"aws.rds"},"ecc-aws-236":{"description":"The 'debug_pretty_print' flag is disabled for PostgreSQL\n","resource":"aws.rds"},"ecc-aws-237":{"description":"The 'log_connections' flag is disabled for PostgreSQL\n","resource":"aws.rds"},"ecc-aws-238":{"description":"The 'log_disconnections' flag is disabled for PostgreSQL\n","resource":"aws.rds"},"ecc-aws-239":{"description":"The 'log_error_verbosity' flag is not set correctly for PostgreSQL\n","resource":"aws.rds"},"ecc-aws-240":{"description":"The 'log_hostname' flag is not disabled for PostgreSQL\n","resource":"aws.rds"},"ecc-aws-241":{"description":"The 'log_statement' flag is not set correctly for PostgreSQL\n","resource":"aws.rds"},"ecc-aws-242":{"description":"The 'log_destination' flag is not set to csvlog for PostgreSQL\n","resource":"aws.rds"},"ecc-aws-243":{"description":"The 'log_checkpoints' flag is not enabled for PostgreSQL\n","resource":"aws.rds"},"ecc-aws-244":{"description":"The 'log_lock_waits' flag is not enabled for PostgreSQL\n","resource":"aws.rds"},"ecc-aws-245":{"description":"The 'log_duration' flag is not enabled for PostgreSQL\n","resource":"aws.rds"},"ecc-aws-246":{"description":"Transit gateway default route table association is enabled\n","resource":"aws.transit-gateway"},"ecc-aws-247":{"description":"Transit gateway default route table propagation is enabled\n","resource":"aws.transit-gateway"},"ecc-aws-248":{"description":"Api gateway is not protected by WAF\n","resource":"rest-stage"},"ecc-aws-249":{"description":"Content encoding is not enabled for API Gateway\n","resource":"rest-api"},"ecc-aws-250":{"description":"Cache is not enabled for API gateway\n","resource":"rest-stage"},"ecc-aws-251":{"description":"Appflow is not encrypted with KMS CMK \n","resource":"aws.app-flow"},"ecc-aws-252":{"description":"Data catalog encryption is not enabled for AWS Glue\n","resource":"aws.glue-catalog"},"ecc-aws-253":{"description":"Data catalog for AWS Glue is not encrypted with KMS CMK\n","resource":"aws.glue-catalog"},"ecc-aws-254":{"description":"Job bookmarks encryption is not enabled for AWS Glue\n","resource":"aws.glue-security-configuration"},"ecc-aws-255":{"description":"CloudWatch logs are not encrypted for AWS Glue\n","resource":"aws.glue-security-configuration"},"ecc-aws-256":{"description":"S3 is not encrypted for AWS Glue\n","resource":"aws.glue-security-configuration"},"ecc-aws-257":{"description":"Kerberos authentication is not enabled for EMR clusters\n","resource":"emr"},"ecc-aws-258":{"description":"At-rest and in-transit encryption is not enabled for EMR clusters\n","resource":"aws.emr"},"ecc-aws-259":{"description":"EMR clusters are not in VPC\n","resource":"emr"},"ecc-aws-260":{"description":"Logging is not enabled for EMR clusters\n","resource":"emr"},"ecc-aws-261":{"description":"Unused Internet Gateways are not removed\n","resource":"internet-gateway"},"ecc-aws-262":{"description":"Manual acceptance is not enabled for VPC endpoints \n","resource":"vpc-endpoint-service"},"ecc-aws-263":{"description":"Unused Virtual Private Gateways is not removed\n","resource":"vpn-gateway"},"ecc-aws-264":{"description":"Elasticache is using default ports\n","resource":"cache-cluster"},"ecc-aws-265":{"description":"Elasticache is not using last generation nodes\n","resource":"cache-cluster"},"ecc-aws-266":{"description":"ElastiCache Redis cluster automatic backups are not enabled or a retention period is not set to at least 7 days\n","resource":"cache-cluster"},"ecc-aws-267":{"description":"ElastiCache is not encrypted in transit\n","resource":"cache-cluster"},"ecc-aws-268":{"description":"Elasticache Redis replication group is not encrypted at-rest with KMS CMK\n","resource":"elasticache-group"},"ecc-aws-269":{"description":"Elasticache is using default VPC\n","resource":"cache-cluster"},"ecc-aws-270":{"description":"Elasticache Redis Multi-AZ is not enabled\n","resource":"elasticache-group"},"ecc-aws-271":{"description":"Elasticache redis Auth is not enabled\n","resource":"cache-cluster"},"ecc-aws-272":{"description":"Elasticache is not using the latest version\n","resource":"cache-cluster"},"ecc-aws-273":{"description":"DocumentDB logging is not enabled\n","resource":"aws.rds-cluster"},"ecc-aws-274":{"description":"Aurora cluster logging is disabled\n","resource":"aws.rds-cluster"},"ecc-aws-275":{"description":"Aurora-MySQL cluster logging is disabled\n","resource":"aws.rds-cluster"},"ecc-aws-276":{"description":"Aurora-PostgreSQL cluster logging is disabled\n","resource":"aws.rds-cluster"},"ecc-aws-277":{"description":"Elasticsearch slow logs is disabled\n","resource":"aws.elasticsearch"},"ecc-aws-278":{"description":"IAM Access Analyzer findings are not reviewed and resolved\n","resource":"account"},"ecc-aws-279":{"description":"Elasticache AUTH token is not rotated every 90 days\n","resource":"cache-cluster"},"ecc-aws-280":{"description":"ElasticSearch is not encrypted with KMS CMK\n","resource":"elasticsearch"},"ecc-aws-281":{"description":"Auto Scaling Groups are not utilizing cooldown period\n","resource":"aws.asg"},"ecc-aws-282":{"description":"Elasticsearch does not enforce HTTPS\n","resource":"elasticsearch"},"ecc-aws-283":{"description":"ElasticSearch is not using the latest OpenSearch version\n","resource":"elasticsearch"},"ecc-aws-284":{"description":"Auto Scaling Groups does not have an associated Elastic Load Balancers or Target Groups\n","resource":"aws.asg"},"ecc-aws-285":{"description":"AWS X-Ray is not encrypted using KMS CMK \n","resource":"aws.account"},"ecc-aws-286":{"description":"Unused Workspaces instances are not removed\n","resource":"aws.workspaces"},"ecc-aws-287":{"description":"Auto Scaling Groups do not utilize multiple Availability Zones\n","resource":"aws.asg"},"ecc-aws-288":{"description":"Workspaces instances are unhealthy\n","resource":"aws.workspaces"},"ecc-aws-289":{"description":"Auto Scaling Group has invalid configuration\n","resource":"asg"},"ecc-aws-290":{"description":"Workspaces storage is not encrypted\n","resource":"aws.workspaces"},"ecc-aws-291":{"description":"Amazon Backup plan has a non-compliant lifecycle configuration\n","resource":"aws.backup-plan"},"ecc-aws-292":{"description":"Elastic Beanstalk environments with application load balancer do not have access logs enabled\n","resource":"aws.elasticbeanstalk-environment"},"ecc-aws-293":{"description":"Backup vaults are not encrypted at rest using KMS CMK\n","resource":"aws.backup-vault"},"ecc-aws-294":{"description":"Elastic Beanstalk environments notifications disabled\n","resource":"aws.elasticbeanstalk-environment"},"ecc-aws-295":{"description":"Cloudfront origin uses not latest SSL certificate\n","resource":"aws.distribution"},"ecc-aws-296":{"description":"RDS MySQL instances are not using latest major version\n","resource":"rds"},"ecc-aws-297":{"description":"Elastic Beanstalk managed platform updates is disabled\n","resource":"aws.elasticbeanstalk-environment"},"ecc-aws-298":{"description":"Ensure SQS is not encrypted with KMS CMK\n","resource":"sqs"},"ecc-aws-299":{"description":"CloudFront distributions do not enforce field-level encryption\n","resource":"aws.distribution"},"ecc-aws-300":{"description":"SQS queue is open to everyone\n","resource":"sqs"},"ecc-aws-301":{"description":"SQS Queue dead letter queue is disabled\n","resource":"sqs"},"ecc-aws-302":{"description":"The 'log_parser_stats' flag is enabled for PostgreSQL\n","resource":"aws.rds"},"ecc-aws-303":{"description":"Management events are not included into CloudTrail trails configuration\n","resource":"aws.cloudtrail"},"ecc-aws-304":{"description":"AWS CloudWatch event bus is exposed to everyone\n","resource":"aws.event-bus"},"ecc-aws-305":{"description":"The 'log_planner_stats' flag is enabled for PostgreSQL\n","resource":"aws.rds"},"ecc-aws-306":{"description":"The 'log_executor_stats' flag is enabled for PostgreSQL\n","resource":"aws.rds"},"ecc-aws-307":{"description":"The 'log_min_error_statement' flag is not set correctly for PostgreSQL\n","resource":"aws.rds"},"ecc-aws-308":{"description":"Glacier Vault policy allows actions from all principals\n","resource":"aws.glacier"},"ecc-aws-309":{"description":"Amazon Config recorder is failing\n","resource":"aws.config-recorder"},"ecc-aws-310":{"description":"DMS replication instances are not using latest version\n","resource":"dms-instance"},"ecc-aws-311":{"description":"Ensure Sagemaker instances are not encrypted with KMS CMK\n","resource":"sagemaker-notebook"},"ecc-aws-312":{"description":"Amazon DMS replication instances Auto Minor Version Upgrade feature disabled\n","resource":"dms-instance"},"ecc-aws-313":{"description":"Amazon DMS replication instances not encrypted with KMS CMK\n","resource":"dms-instance"},"ecc-aws-314":{"description":"The 'audit_sys_operations' flag for Oracle is disabled\n","resource":"aws.rds"},"ecc-aws-315":{"description":"The 'audit_trail' flag is not set correctly for Oracle\n","resource":"aws.rds"},"ecc-aws-316":{"description":"The 'global_names' flag for Oracle is disabled\n","resource":"aws.rds"},"ecc-aws-317":{"description":"The 'remote_listener' flag for Oracle is not empty\n","resource":"aws.rds"},"ecc-aws-318":{"description":"The 'sec_max_failed_login_attempts' flag for Oracle is not set to 3 or less\n","resource":"aws.rds"},"ecc-aws-319":{"description":"The 'sec_protocol_error_further_action' flag for Oracle is not set to '(DROP,3)'\n","resource":"aws.rds"},"ecc-aws-320":{"description":"The 'sec_protocol_error_trace_action' flag for Oracle is not set to 'LOG'\n","resource":"aws.rds"},"ecc-aws-321":{"description":"The 'sec_return_server_release_banner' flag for Oracle is enabled\n","resource":"aws.rds"},"ecc-aws-322":{"description":"The 'sql92_security' flag for Oracle is disabled\n","resource":"aws.rds"},"ecc-aws-323":{"description":"The '_trace_files_public' flag for Oracle is enabled\n","resource":"aws.rds"},"ecc-aws-324":{"description":"The 'resource_limit' flag for Oracle is disabled\n","resource":"aws.rds"},"ecc-aws-325":{"description":"Amazon DMS replication instances do not have the Multi-AZ feature enabled\n","resource":"dms-instance"},"ecc-aws-326":{"description":"EBS volume not encrypted with KMS CMK\n","resource":"aws.ebs"},"ecc-aws-327":{"description":"EBS snapshot encryption is disabled\n","resource":"aws.ebs-snapshot"},"ecc-aws-328":{"description":"Unused EBS volumes exist\n","resource":"aws.ebs"},"ecc-aws-329":{"description":"Unused key pairs exist\n","resource":"aws.key-pair"},"ecc-aws-330":{"description":"The 'sql_mode' flag for MySQL not contains 'strict_all_tables'\n","resource":"aws.rds"},"ecc-aws-331":{"description":"Workspaces images are older than 90 days \n","resource":"aws.workspaces-image"},"ecc-aws-332":{"description":"Workspaces web access is enabled \n","resource":"aws.workspaces-directory"},"ecc-aws-333":{"description":"AWS FSx file system is not encrypted with KMS CMK\n","resource":"aws.fsx"},"ecc-aws-334":{"description":"Kinesis Data Firehose delivery streams are not encrypted using Server-side encryption\n","resource":"aws.firehose"},"ecc-aws-335":{"description":"Lambda has active tracing disabled\n","resource":"lambda"},"ecc-aws-336":{"description":"Sagemaker endpoint configurations are not encrypted with KMS CMK\n","resource":"sagemaker-endpoint-config"},"ecc-aws-337":{"description":"Lambda environment variables not encrypted with KMS CMK\n","resource":"lambda"},"ecc-aws-338":{"description":"Sagemaker instances root access enabled\n","resource":"sagemaker-notebook"},"ecc-aws-339":{"description":"MQ auto minor version upgrade not enabled\n","resource":"aws.message-broker"},"ecc-aws-340":{"description":"MQ broker logging not enabled\n","resource":"aws.message-broker"},"ecc-aws-341":{"description":"Sagemaker model network isolation disabled \n","resource":"sagemaker-model"},"ecc-aws-342":{"description":"Route53 has automatic domain renewal disabled\n","resource":"aws.r53domain"},"ecc-aws-343":{"description":"MQ is publicly accessible\n","resource":"aws.message-broker"},"ecc-aws-344":{"description":"Route53 domain name expire in less 30 days\n","resource":"aws.r53domain"},"ecc-aws-345":{"description":"Mq broker not restricted only to default ports\n","resource":"aws.message-broker"},"ecc-aws-346":{"description":"Route53 hosted zone records is not configured with health check\n","resource":"aws.rrset"},"ecc-aws-347":{"description":"MSK not encrypted with KMS CMK\n","resource":"aws.kafka"},"ecc-aws-348":{"description":"MSK encryption in transit not set only to 'TLS'.\n","resource":"aws.kafka"},"ecc-aws-349":{"description":"Route53 query logging not enabled\n","resource":"aws.hostedzone"},"ecc-aws-350":{"description":"MSK Logging not enabled\n","resource":"aws.kafka"},"ecc-aws-351":{"description":"RDS instances are not encrypted with KMS CMK\n","resource":"rds"},"ecc-aws-352":{"description":"SNS topics are not encrypted at rest using KMS CMK\n","resource":"sns"},"ecc-aws-353":{"description":"AWS Redshift user activity logging is disabled\n","resource":"redshift"},"ecc-aws-354":{"description":"Amazon Redshift uses default port 5439\n","resource":"redshift"},"ecc-aws-355":{"description":"AWS Redshift instances are not encrypted with KMS CMK\n","resource":"redshift"},"ecc-aws-356":{"description":"AWS Redshift parameter group not require SSL\n","resource":"redshift"},"ecc-aws-357":{"description":"Route 53 domain Transfer Lock is disabled\n","resource":"aws.r53domain"},"ecc-aws-358":{"description":"CloudTrail Global Services disabled\n","resource":"aws.account"},"ecc-aws-359":{"description":"API Gateway REST API have access logging disabled\n","resource":"rest-stage"},"ecc-aws-360":{"description":"ECS Cluster execute command logging encryption is disabled\n","resource":"ecs"},"ecc-aws-361":{"description":"API Gateway REST API does not have logging correctly configured\n","resource":"rest-stage"},"ecc-aws-362":{"description":"Managed Workflows for Apache Airflow data is not encrypted with KMS CMK\n","resource":"aws.airflow"},"ecc-aws-363":{"description":"AWS Kinesis Video Streams are not encrypted with KMS customer master keys\n","resource":"aws.kinesis-video"},"ecc-aws-364":{"description":"Auto Scaling launch configuration public ip association is enabled\n","resource":"launch-config"},"ecc-aws-365":{"description":"Glue connection password is not encrypted\n","resource":"aws.glue-catalog"},"ecc-aws-366":{"description":"FSx Lustre file logging is disabled\n","resource":"aws.fsx"},"ecc-aws-367":{"description":"DS directory is open to a large scope\n","resource":"aws.directory"},"ecc-aws-368":{"description":"FSx Lustre file system does not have retention period set at least to 7 days\n","resource":"aws.fsx"},"ecc-aws-369":{"description":"CloudWatch Events is not set up for successful logins to WorkSpaces\n","resource":"account"},"ecc-aws-370":{"description":"Workspaces maintenance mode disabled\n","resource":"aws.workspaces-directory"},"ecc-aws-371":{"description":"Primary interface ports for Workspaces are open to all inbound traffic\n","resource":"aws.workspaces"},"ecc-aws-372":{"description":"WorkSpaces API requests do not flow through a VPC Endpoint\n","resource":"aws.workspaces-directory"},"ecc-aws-373":{"description":"Radius server is not using the recommended strongest security protocol \n","resource":"aws.workspaces-directory"},"ecc-aws-374":{"description":"Data events are not included into Amazon CloudTrail trails configuration\n","resource":"aws.cloudtrail"},"ecc-aws-375":{"description":"Workspaces storage is not encrypted with KMS CMK\n","resource":"aws.workspaces"},"ecc-aws-376":{"description":"API Gateway HTTP and WEBSOCKET API does not have logging enabled\n","resource":"aws.api-stage"},"ecc-aws-377":{"description":"AMI without tag information\n","resource":"aws.ami"},"ecc-aws-378":{"description":"EBS volumes without tag information\n","resource":"aws.ebs"},"ecc-aws-379":{"description":"EBS snapshot without tag information\n","resource":"aws.ebs-snapshot"},"ecc-aws-380":{"description":"EIP without tag information\n","resource":"elastic-ip"},"ecc-aws-381":{"description":"ENI without tag information\n","resource":"aws.eni"},"ecc-aws-382":{"description":"Amazon Internet Gateway without tag information\n","resource":"internet-gateway"},"ecc-aws-383":{"description":"Amazon Nat Gateway without tag information\n","resource":"nat-gateway"},"ecc-aws-384":{"description":"Amazon Network ACLs without tag information\n","resource":"aws.network-acl"},"ecc-aws-385":{"description":"Amazon Route table without tag information\n","resource":"aws.route-table"},"ecc-aws-386":{"description":"Security group without tag information\n","resource":"aws.security-group"},"ecc-aws-387":{"description":"Amazon Subnet without tag information\n","resource":"aws.subnet"},"ecc-aws-388":{"description":"Amazon Transit gateway without tag information\n","resource":"aws.transit-gateway"},"ecc-aws-389":{"description":"Amazon Transit gateway attachment without tag information\n","resource":"aws.transit-attachment"},"ecc-aws-390":{"description":"Amazon peering connection without tag information\n","resource":"aws.peering-connection"},"ecc-aws-391":{"description":"VPC without tag information\n","resource":"aws.vpc"},"ecc-aws-392":{"description":"VPC endpoint without tag information\n","resource":"aws.vpc-endpoint"},"ecc-aws-393":{"description":"Amazon ACM without tag information\n","resource":"acm-certificate"},"ecc-aws-394":{"description":"Amazon AppFlow without tag information\n","resource":"aws.app-flow"},"ecc-aws-395":{"description":"Auto Scaling Group without tag information\n","resource":"aws.asg"},"ecc-aws-396":{"description":"Amazon cloudformation stacks without tag information\n","resource":"aws.cfn"},"ecc-aws-397":{"description":"Cloudfront distributions without tag information\n","resource":"distribution"},"ecc-aws-398":{"description":"Cloudtrail without tag information\n","resource":"aws.cloudtrail"},"ecc-aws-399":{"description":"Amazon Codebuikd without tag information\n","resource":"codebuild"},"ecc-aws-400":{"description":"DynamoDB Accelerator clusters without tag information\n","resource":"dax"},"ecc-aws-401":{"description":"AWS DLM lifecycle policy without tag information\n","resource":"aws.dlm-policy"},"ecc-aws-402":{"description":"Amazon DMS instance without tag information\n","resource":"aws.dms-instance"},"ecc-aws-403":{"description":"Amazon ECS cluster without tag information\n","resource":"ecs"},"ecc-aws-404":{"description":"Amazon EKS without tag information\n","resource":"eks"},"ecc-aws-405":{"description":"Amazon EFS without tag information\n","resource":"efs"},"ecc-aws-406":{"description":"Elasticache without tag information\n","resource":"cache-cluster"},"ecc-aws-407":{"description":"Amazon Beanstalk topic without tag information\n","resource":"aws.elasticbeanstalk-environment"},"ecc-aws-408":{"description":"Amazon ELB without tag information\n","resource":"elb"},"ecc-aws-409":{"description":"Amazon EMR clusters without tag information\n","resource":"emr"},"ecc-aws-410":{"description":"Amazon ElasticSearch clusters without tag information\n","resource":"elasticsearch"},"ecc-aws-411":{"description":"Amazon FSX without tag information\n","resource":"aws.fsx"},"ecc-aws-412":{"description":"Amazon FSX Lustre backup without tag information\n","resource":"aws.fsx-backup"},"ecc-aws-413":{"description":"Amazon Glacier without tag information\n","resource":"aws.glacier"},"ecc-aws-414":{"description":"Amazon Glue Job without tag information\n","resource":"glue-job"},"ecc-aws-415":{"description":"IAM User without tag information\n","resource":"iam-user"},"ecc-aws-416":{"description":"IAM Role without tag information\n","resource":"iam-role"},"ecc-aws-417":{"description":"Amazon MSK clusters without tag information\n","resource":"kafka"},"ecc-aws-418":{"description":"Amazon Kinesis data stream without tag information\n","resource":"aws.kinesis"},"ecc-aws-419":{"description":"Amazon Kinesis video stream without tag information\n","resource":"kinesis-video"},"ecc-aws-420":{"description":"Customer manages key without tag information\n","resource":"aws.kms-key"},"ecc-aws-421":{"description":"Lambda functions without tag information\n","resource":"lambda"},"ecc-aws-422":{"description":"Amazon Lightsail instance without tag information\n","resource":"aws.lightsail-instance"},"ecc-aws-423":{"description":"Amazon Log group without tag information\n","resource":"log-group"},"ecc-aws-424":{"description":"MQ broker without tag information\n","resource":"aws.message-broker"},"ecc-aws-425":{"description":"Amazon MWAA without tag information\n","resource":"aws.airflow"},"ecc-aws-426":{"description":"Amazon QLDB ledger without tag information\n","resource":"qldb"},"ecc-aws-427":{"description":"RDS cluster without tag information\n","resource":"aws.rds-cluster"},"ecc-aws-428":{"description":"Amazon RDS snapshot without tag information\n","resource":"rds-snapshot"},"ecc-aws-429":{"description":"Amazon Redshift clusters without tag information\n","resource":"redshift"},"ecc-aws-430":{"description":"Amazon Sagemaker instances without tag information\n","resource":"aws.sagemaker-notebook"},"ecc-aws-431":{"description":"Amazon SNS topic without tag information\n","resource":"sns"},"ecc-aws-432":{"description":"Amazon SQS without tag information\n","resource":"sqs"},"ecc-aws-433":{"description":"MQ broker active deployment not enabled\n","resource":"aws.message-broker"},"ecc-aws-434":{"description":"MQ broker not using latest major version\n","resource":"aws.message-broker"},"ecc-aws-435":{"description":"MQ broker not encrypted with KMS CMK\n","resource":"aws.message-broker"},"ecc-aws-436":{"description":"Kinesis streams shard level monitoring disabled\n","resource":"kinesis"},"ecc-aws-437":{"description":"S3 Bucket object lock disabled\n","resource":"s3-light"},"ecc-aws-438":{"description":"QLDB permission mode is set to 'ALLOW_ALL'\n","resource":"qldb"},"ecc-aws-439":{"description":"QLDB termination protection not enabled\n","resource":"qldb"},"ecc-aws-440":{"description":"Appsync logging disabled\n","resource":"aws.appsync-graphql-api"},"ecc-aws-441":{"description":"Appsync cache is not encrypted at rest\n","resource":"aws.appsync-graphql-api"},"ecc-aws-442":{"description":"Appsync cache is not encrypted in transit\n","resource":"aws.appsync-graphql-api"},"ecc-aws-443":{"description":"Appsync is not protected by WAF\n","resource":"aws.appsync-graphql-api"},"ecc-aws-444":{"description":"Managed Workflows for Apache Airflow dag logs not enabled or set correctly\n","resource":"aws.airflow"},"ecc-aws-445":{"description":"Managed Workflows for Apache Airflow scheduler logs not enabled or set correctly\n","resource":"aws.airflow"},"ecc-aws-446":{"description":"Managed Workflows for Apache Airflow Task logs not enabled or set correctly\n","resource":"aws.airflow"},"ecc-aws-447":{"description":"Managed Workflows for Apache Airflow Webserver logs not enabled or set correctly\n","resource":"aws.airflow"},"ecc-aws-448":{"description":"Managed Workflows for Apache Airflow Worker logs not enabled or set correctly\n","resource":"aws.airflow"},"ecc-aws-449":{"description":"Amazon Redshift clusters availability zone relocation not enabled\n","resource":"redshift"},"ecc-aws-450":{"description":"Elastic Beanstalk IMDSv1 is enabled\n","resource":"aws.elasticbeanstalk-environment"},"ecc-aws-451":{"description":"Elastic Beanstalk X-Ray is disabled\n","resource":"aws.elasticbeanstalk-environment"},"ecc-aws-452":{"description":"Elastic Beanstalk connection draining is disabled\n","resource":"aws.elasticbeanstalk-environment"},"ecc-aws-453":{"description":"Elasticache Redis logs disabled\n","resource":"cache-cluster"},"ecc-aws-454":{"description":"Elasticache notification disabled\n","resource":"cache-cluster"},"ecc-aws-455":{"description":"EMR termination protection not enabled\n","resource":"emr"},"ecc-aws-456":{"description":"EMR clusters imdsv1 enabled\n","resource":"aws.emr"},"ecc-aws-457":{"description":"Glue job spark ui disabled\n","resource":"aws.glue-job"},"ecc-aws-458":{"description":"Enhanced Monitoring for Lambda Functions disabled\n","resource":"aws.lambda"},"ecc-aws-459":{"description":"Lambda code signing not enabled\n","resource":"aws.lambda"},"ecc-aws-460":{"description":"Lambda environment variables are not encrypted in transit\n","resource":"aws.lambda"},"ecc-aws-461":{"description":"Lambda functions not are not using latest runtime environment versions\n","resource":"lambda"},"ecc-aws-462":{"description":"Lambda reserved concurrency disabled\n","resource":"lambda"},"ecc-aws-463":{"description":"S3 bucket is not DNS compliant\n","resource":"s3-light"},"ecc-aws-464":{"description":"ECS Cluster execute command logging is disabled\n","resource":"aws.ecs"},"ecc-aws-465":{"description":"FSx file systems do not have retention period set\n","resource":"aws.fsx"},"ecc-aws-466":{"description":"FSx for NetApp ONTAP file systems do not have Multi-AZ enabled\n","resource":"aws.fsx"},"ecc-aws-467":{"description":"FSx for for Windows File Server file systems do not have Multi-AZ enabled\n","resource":"aws.fsx"},"ecc-aws-468":{"description":"FSx OpenZFS file system does not copy tags to snapshots\n","resource":"aws.fsx"},"ecc-aws-469":{"description":"Application Load Balancers are not configured with defensive or strictest desync mitigation mode\n","resource":"aws.app-elb"},"ecc-aws-470":{"description":"API Gateway endpoint type not set correctly\n","resource":"rest-api"},"ecc-aws-471":{"description":"Auto Scaling Groups do not use rebalacing capacity\n","resource":"aws.asg"},"ecc-aws-472":{"description":"Auto Scaling launch configuration IMDSv1 enabled\n","resource":"launch-config"},"ecc-aws-473":{"description":"Classic Load Balancers are not configured with defensive or strictest desync mitigation mode\n","resource":"aws.elb"},"ecc-aws-474":{"description":"Classic Load Balancers are not configured with multiple Availability Zones\n","resource":"aws.elb"},"ecc-aws-475":{"description":"Classic Load Balancers are not configured with cross-zone load balancing.\n","resource":"aws.elb"},"ecc-aws-476":{"description":"CloudFormation Stack has been drifted\n","resource":"aws.cfn"},"ecc-aws-477":{"description":"CloudFormation Stack notifications do not enabled\n","resource":"aws.cfn"},"ecc-aws-478":{"description":"Cloudfront Distribution not uses SNI\n","resource":"aws.distribution"},"ecc-aws-479":{"description":"AWS CloudWatch log groups are not encrypted with KMS CMK\n","resource":"log-group"},"ecc-aws-480":{"description":"CodeBuild project artifact encryption disabled\n","resource":"codebuild"},"ecc-aws-481":{"description":"CodeBuild project environment privileged mode is set to true\n","resource":"codebuild"},"ecc-aws-482":{"description":"CodeBuild project logging in disabled\n","resource":"codebuild"},"ecc-aws-483":{"description":"CodeBuild S3 logs are not encrypted \n","resource":"aws.codebuild"},"ecc-aws-484":{"description":"CodeDeploy AutoRollbackConfiguration or AlarmConfiguration has not been configured or is not enabled. \n","resource":"aws.codedeploy-group"},"ecc-aws-485":{"description":"CodeDeploy deployment config of application does not meet the requirements\n","resource":"aws.codedeploy-group"},"ecc-aws-486":{"description":"CodeDeploy Lambda AllAtOnce traffic shift disabled\n","resource":"aws.codedeploy-group"},"ecc-aws-487":{"description":"CodePipeline s3 artifact bucket is not encrypted with KMS CMK\n","resource":"aws.codepipeline"},"ecc-aws-488":{"description":"CloudWatch Log Group does not have retention period set correctly\n","resource":"log-group"},"ecc-aws-489":{"description":"EC2 instances detailed monitoring disabled\n","resource":"aws.ec2"},"ecc-aws-490":{"description":"EC2 instances token hop limit set correctly\n","resource":"aws.ec2"},"ecc-aws-491":{"description":"Transit gateway automatically accept VPC attachment requests\n","resource":"aws.transit-gateway"},"ecc-aws-492":{"description":"ECR repository does not have any lifecycle policies configured\n","resource":"aws.ecr"},"ecc-aws-493":{"description":"ECS container insight is disabled\n","resource":"aws.ecs"},"ecc-aws-494":{"description":"ECS Fargate not latest platform version\n","resource":"ecs-service"},"ecc-aws-495":{"description":"Amazon ECS task definitions memory hard limit is not set\n","resource":"ecs-task-definition"},"ecc-aws-496":{"description":"Amazon ECS task definitions pid mode set to 'host'\n","resource":"ecs-task-definition"},"ecc-aws-497":{"description":"EKS cluster is using unsupported version\n","resource":"aws.eks"},"ecc-aws-498":{"description":"Application, Gateway and Network Load Balancers are not configured with multiple Availability Zones\n","resource":"aws.app-elb"},"ecc-aws-499":{"description":"IAM group doesn't have users\n","resource":"aws.iam-group"},"ecc-aws-500":{"description":"Lambda functions are not operate in more than one Availability Zone\n","resource":"aws.lambda"},"ecc-aws-501":{"description":"OpenSearch fine grained access control disabled\n","resource":"elasticsearch"},"ecc-aws-502":{"description":"Automatic minor version upgrade is not configured for RDS DB instances\n","resource":"aws.rds"},"ecc-aws-503":{"description":"Amazon RDS cluster uses default Admin username\n","resource":"rds-cluster"},"ecc-aws-504":{"description":"Amazon RDS instance uses default Admin username\n","resource":"rds"},"ecc-aws-505":{"description":"Amazon Redshift uses default Admin username\n","resource":"redshift"},"ecc-aws-506":{"description":"Redshift clusters uses the default database name\n","resource":"redshift"},"ecc-aws-507":{"description":"Amazon SNS topic message delivery notification is disabled\n","resource":"sns"},"ecc-aws-508":{"description":"Managed Workflows for Apache Airflow not using latest version\n","resource":"aws.airflow"},"ecc-aws-509":{"description":"DynamoDB Accelerator clusters encryption in transit of data is disabled\n","resource":"aws.dax"},"ecc-aws-510":{"description":"Unused Amazon EFS file systems\n","resource":"efs"},"ecc-aws-511":{"description":"Amazon CLB is internet facing\n","resource":"aws.elb"},"ecc-aws-512":{"description":"Amazon ELB is internet facing\n","resource":"aws.app-elb"},"ecc-aws-513":{"description":"ACM has certificates minimum rsa key is not 2048 bit\n","resource":"acm-certificate"},"ecc-aws-514":{"description":"Inactive access keys are not deleted\n","resource":"aws.iam-user"},"ecc-aws-515":{"description":"Security Hub is not enabled\n","resource":"aws.account"},"ecc-aws-516":{"description":"S3 buckets should have event notifications enabled\n","resource":"aws.s3"},"ecc-aws-517":{"description":"S3 access control lists (ACLs) are used to manage user access to buckets\n","resource":"aws.s3-light"},"ecc-aws-518":{"description":"S3 buckets with versioning enabled do not have lifecycle policies configured\n","resource":"aws.s3-light"},"ecc-aws-519":{"description":"One or both VPN tunnels for an AWS Site-to-Site VPN connection are in DOWN status\n","resource":"aws.vpn-connection"},"ecc-aws-520":{"description":"Auto Scaling launch configuration hop limit is greater than 1\n","resource":"launch-config"},"ecc-aws-521":{"description":"ECS container is not limited to read-only access to root file systems\n","resource":"ecs-task-definition"},"ecc-aws-522":{"description":"Amazon ECS secrets passed as container environment variables\n","resource":"ecs-task-definition"},"ecc-aws-523":{"description":"KMS keys should not be unintentionally deleted\n","resource":"aws.kms-key"},"ecc-aws-524":{"description":"A WAF Classic Regional web ACL does not have at least one rule or rule group\n","resource":"aws.waf-regional"},"ecc-aws-525":{"description":"A WAF global rule does not have at least one condition\n","resource":"aws.waf-rule"},"ecc-aws-526":{"description":"A WAF global rule group does not have at least one rule\n","resource":"aws.waf-rule-groups"},"ecc-aws-527":{"description":"A WAF global web ACL does not have at least one rule or rule group\n","resource":"aws.waf"},"ecc-aws-528":{"description":"ACM transparency logging disabled\n","resource":"acm-certificate"},"ecc-aws-529":{"description":"EBS volumes attached to an EC2 instance is not marked for deletion upon instance termination\n","resource":"aws.ec2"},"ecc-aws-530":{"description":"CloudFront distribution not encrypted in transit\n","resource":"aws.distribution"},"ecc-aws-531":{"description":"EBS volume default encryption disabled\n","resource":"aws.account"},"ecc-aws-532":{"description":"Imported and ACM-issued certificates expire in less than a month\n","resource":"aws.acm-certificate"},"ecc-aws-533":{"description":"Amazon Key pair without tag information\n","resource":"key-pair"},"ecc-aws-534":{"description":"EC2 Auto Scaling groups is not using EC2 launch templates\n","resource":"aws.asg"},"ecc-aws-535":{"description":"Classic Load Balancers with HTTPS/SSL listeners do not use certificate provided by AWS Certificate Manager\n","resource":"aws.elb"},"ecc-aws-536":{"description":"Lambda functions should not use no longer supported runtimes\n","resource":"aws.lambda"},"ecc-aws-537":{"description":"ECS containers should not run in privileged parameter\n","resource":"ecs-task-definition"},"ecc-aws-538":{"description":"CloudFront distributions are pointing to non-existent S3 origins\n","resource":"aws.distribution"},"ecc-aws-539":{"description":"CloudFront distributions do not have origin access control enabled\n","resource":"aws.distribution"},"ecc-aws-540":{"description":"Amazon Glue Job not latest version\n","resource":"glue-job"},"ecc-aws-541":{"description":"Glue job logging disabled\n","resource":"aws.glue-job"},"ecc-aws-542":{"description":"Amazon Glue Job with disabled autoscaling\n","resource":"aws.glue-job"},"ecc-aws-543":{"description":"CloudFront Realtime logging disabled\n","resource":"aws.distribution"},"ecc-aws-544":{"description":"CloudTrail logs delivery failing\n","resource":"aws.cloudtrail"},"ecc-aws-545":{"description":"AWS Step Function State Machine logging is disabled\n","resource":"aws.step-machine"},"ecc-aws-546":{"description":"Kinesis Stream retention period is not set correctly\n","resource":"aws.kinesis"},"ecc-aws-548":{"description":"EBS volumes are type of gp2 insted of gp3\n","resource":"aws.ebs"}} \ No newline at end of file diff --git a/tests/tests_metrics/mock_files/reports/raw/EPAM Systems/AZURE/AZURE-1234567890123/latest/0.json b/tests/tests_metrics/mock_files/reports/raw/EPAM Systems/AZURE/AZURE-1234567890123/latest/0.json new file mode 100644 index 000000000..9c3fdddb3 --- /dev/null +++ b/tests/tests_metrics/mock_files/reports/raw/EPAM Systems/AZURE/AZURE-1234567890123/latest/0.json @@ -0,0 +1 @@ +[{"p":"ecc-azure-002","l":"westeurope","r":[{"id":"83556a93-74a6-4251-91f8-8c37a03512bd"}],"t":1700043357.7096903},{"p":"ecc-azure-002","l":"multiregion","r":[{"id":"83556a93-74a6-4251-91f8-8c37a03512bd"}],"t":1700043357.7096903},{"p":"ecc-azure-004","l":"westeurope","r":[{"id":"13e43c91-46ff-4301-ba3b-1a6a25d9f3b0"}],"t":1700043357.7096903},{"p":"ecc-azure-004","l":"multiregion","r":[{"id":"13e43c91-46ff-4301-ba3b-1a6a25d9f3b0"}],"t":1700043357.7096903},{"p":"ecc-azure-005","l":"westeurope","r":[{"id":"6a99be11-90a4-4b9d-b8c9-06003202b336"}],"t":1700043357.7096903},{"p":"ecc-azure-005","l":"multiregion","r":[{"id":"6a99be11-90a4-4b9d-b8c9-06003202b336"}],"t":1700043357.7096903},{"p":"ecc-azure-006","l":"westeurope","r":[{"id":"f5038039-380b-4bef-aac0-92df0a0dfa6b"}],"t":1700043357.7096903},{"p":"ecc-azure-006","l":"multiregion","r":[{"id":"f5038039-380b-4bef-aac0-92df0a0dfa6b"}],"t":1700043357.7096903},{"p":"ecc-azure-007","l":"westeurope","r":[{"id":"f20e50b5-b641-4d60-83d0-fed516912027"}],"t":1700043357.7096903},{"p":"ecc-azure-007","l":"multiregion","r":[{"id":"f20e50b5-b641-4d60-83d0-fed516912027"}],"t":1700043357.7096903},{"p":"ecc-azure-008","l":"westeurope","r":[{"id":"80cdf030-6761-4b4e-8cc3-b785a2381225"}],"t":1700043357.7096903},{"p":"ecc-azure-008","l":"multiregion","r":[{"id":"80cdf030-6761-4b4e-8cc3-b785a2381225"}],"t":1700043357.7096903},{"p":"ecc-azure-009","l":"westeurope","r":[{"id":"645e906b-41aa-46ff-addd-0d6704a3d0bf"}],"t":1700043357.7096903},{"p":"ecc-azure-009","l":"multiregion","r":[{"id":"645e906b-41aa-46ff-addd-0d6704a3d0bf"}],"t":1700043357.7096903},{"p":"ecc-azure-010","l":"westeurope","r":[{"id":"9eb2a816-68cb-4d23-ae5d-9302e6917d26"}],"t":1700043357.7096903},{"p":"ecc-azure-010","l":"multiregion","r":[{"id":"9eb2a816-68cb-4d23-ae5d-9302e6917d26"}],"t":1700043357.7096903},{"p":"ecc-azure-011","l":"westeurope","r":[{"id":"33692a31-3deb-4ccd-8628-6e09d438c49b"}],"t":1700043357.7096903},{"p":"ecc-azure-011","l":"multiregion","r":[{"id":"33692a31-3deb-4ccd-8628-6e09d438c49b"}],"t":1700043357.7096903},{"p":"ecc-azure-012","l":"westeurope","r":[{"id":"d481ed43-f5a0-4639-9ad7-1d1fa49f88b9"}],"t":1700043357.7096903},{"p":"ecc-azure-012","l":"multiregion","r":[{"id":"d481ed43-f5a0-4639-9ad7-1d1fa49f88b9"}],"t":1700043357.7096903},{"p":"ecc-azure-013","l":"westeurope","r":[{"id":"18199393-670a-4daf-9fe0-2cecbfd32382"}],"t":1700043357.7096903},{"p":"ecc-azure-013","l":"multiregion","r":[{"id":"18199393-670a-4daf-9fe0-2cecbfd32382"}],"t":1700043357.7096903},{"p":"ecc-azure-014","l":"westeurope","r":[{"id":"e1ea5ad7-e7fd-46f9-8c0a-fe85f629cfec"}],"t":1700043357.7106903},{"p":"ecc-azure-014","l":"multiregion","r":[{"id":"e1ea5ad7-e7fd-46f9-8c0a-fe85f629cfec"}],"t":1700043357.7106903},{"p":"ecc-azure-015","l":"westeurope","r":[{"id":"a95e8ed3-5859-4e67-83fa-413f6bd63b2a"}],"t":1700043357.7106903},{"p":"ecc-azure-015","l":"multiregion","r":[{"id":"a95e8ed3-5859-4e67-83fa-413f6bd63b2a"}],"t":1700043357.7106903},{"p":"ecc-azure-016","l":"westeurope","r":[{"id":"6f888c13-2f8d-4fc6-8ee0-e82458c555d8"}],"t":1700043357.7106903},{"p":"ecc-azure-016","l":"multiregion","r":[{"id":"6f888c13-2f8d-4fc6-8ee0-e82458c555d8"}],"t":1700043357.7106903},{"p":"ecc-azure-020","l":"westeurope","r":[{"id":"f5e95c59-3c16-44fa-ad3c-66cd8341bb10"}],"t":1700043357.7106903},{"p":"ecc-azure-020","l":"multiregion","r":[{"id":"f5e95c59-3c16-44fa-ad3c-66cd8341bb10"}],"t":1700043357.7106903},{"p":"ecc-azure-021","l":"westeurope","r":[{"id":"a0f0ab30-89d2-4cb1-8738-126fcb15920a"}],"t":1700043357.7106903},{"p":"ecc-azure-021","l":"multiregion","r":[{"id":"a0f0ab30-89d2-4cb1-8738-126fcb15920a"}],"t":1700043357.7106903},{"p":"ecc-azure-022","l":"westeurope","r":[{"id":"44437dd1-99a0-411b-9d27-391a1c825fd0"}],"t":1700043357.7106903},{"p":"ecc-azure-022","l":"multiregion","r":[{"id":"44437dd1-99a0-411b-9d27-391a1c825fd0"}],"t":1700043357.7106903},{"p":"ecc-azure-023","l":"westeurope","r":[{"id":"e6ace001-86b0-4232-8aeb-4ed482f268d9"}],"t":1700043357.7106903},{"p":"ecc-azure-023","l":"multiregion","r":[{"id":"e6ace001-86b0-4232-8aeb-4ed482f268d9"}],"t":1700043357.7106903},{"p":"ecc-azure-024","l":"westeurope","r":[{"id":"5c6bae77-7b60-4566-92f1-9a1d36be1bf9"}],"t":1700043357.7106903},{"p":"ecc-azure-024","l":"multiregion","r":[{"id":"5c6bae77-7b60-4566-92f1-9a1d36be1bf9"}],"t":1700043357.7106903},{"p":"ecc-azure-025","l":"westeurope","r":[{"id":"41f847ce-99bc-43c2-90c0-f68184a1fba2"}],"t":1700043357.7106903},{"p":"ecc-azure-025","l":"multiregion","r":[{"id":"41f847ce-99bc-43c2-90c0-f68184a1fba2"}],"t":1700043357.7106903},{"p":"ecc-azure-026","l":"westeurope","r":[{"id":"69117d4e-ad3b-4672-bb81-fb1e013248ff"}],"t":1700043357.7106903},{"p":"ecc-azure-026","l":"multiregion","r":[{"id":"69117d4e-ad3b-4672-bb81-fb1e013248ff"}],"t":1700043357.7106903},{"p":"ecc-azure-027","l":"westeurope","r":[{"id":"84b608da-2042-4540-a689-bc47d0ec4d9a"}],"t":1700043357.7106903},{"p":"ecc-azure-027","l":"multiregion","r":[{"id":"84b608da-2042-4540-a689-bc47d0ec4d9a"}],"t":1700043357.7106903},{"p":"ecc-azure-028","l":"westeurope","r":[{"id":"172e9e1a-f76a-40a9-8b89-daea15fc00f4"}],"t":1700043357.7106903},{"p":"ecc-azure-028","l":"multiregion","r":[{"id":"172e9e1a-f76a-40a9-8b89-daea15fc00f4"}],"t":1700043357.7106903},{"p":"ecc-azure-030","l":"westeurope","r":[{"id":"3ee093e3-dac8-4075-9f0f-ccb15812a62f"}],"t":1700043357.7106903},{"p":"ecc-azure-030","l":"multiregion","r":[{"id":"3ee093e3-dac8-4075-9f0f-ccb15812a62f"}],"t":1700043357.7106903},{"p":"ecc-azure-031","l":"westeurope","r":[{"id":"6b8ea0ba-9bbf-4cb1-a4f0-817a741fa414"}],"t":1700043357.7106903},{"p":"ecc-azure-031","l":"multiregion","r":[{"id":"6b8ea0ba-9bbf-4cb1-a4f0-817a741fa414"}],"t":1700043357.7106903},{"p":"ecc-azure-032","l":"westeurope","r":[{"id":"96a5efcc-9904-4c23-bfb3-3583f3565563"}],"t":1700043357.7106903},{"p":"ecc-azure-032","l":"multiregion","r":[{"id":"96a5efcc-9904-4c23-bfb3-3583f3565563"}],"t":1700043357.7106903},{"p":"ecc-azure-033","l":"westeurope","r":[{"id":"ee54c003-266c-4c0b-8ea8-8cf7f07c29e3"}],"t":1700043357.7106903},{"p":"ecc-azure-033","l":"multiregion","r":[{"id":"ee54c003-266c-4c0b-8ea8-8cf7f07c29e3"}],"t":1700043357.7106903},{"p":"ecc-azure-036","l":"westeurope","r":[{"id":"5691e9a9-93e8-4bd8-93f0-178cf2f20b46"}],"t":1700043357.7106903},{"p":"ecc-azure-036","l":"multiregion","r":[{"id":"5691e9a9-93e8-4bd8-93f0-178cf2f20b46"}],"t":1700043357.7106903},{"p":"ecc-azure-037","l":"westeurope","r":[{"id":"787dcc8e-cb61-454e-ac36-75ec92d84fdd"}],"t":1700043357.7106903},{"p":"ecc-azure-037","l":"multiregion","r":[{"id":"787dcc8e-cb61-454e-ac36-75ec92d84fdd"}],"t":1700043357.7106903},{"p":"ecc-azure-038","l":"westeurope","r":[{"id":"aa76a88f-1ae1-499e-9092-2cf254fde6ed"}],"t":1700043357.7106903},{"p":"ecc-azure-038","l":"multiregion","r":[{"id":"aa76a88f-1ae1-499e-9092-2cf254fde6ed"}],"t":1700043357.7106903},{"p":"ecc-azure-039","l":"westeurope","r":[{"id":"9d78212a-1c62-4eac-9a8d-60c4ed424ef9"}],"t":1700043357.7106903},{"p":"ecc-azure-039","l":"multiregion","r":[{"id":"9d78212a-1c62-4eac-9a8d-60c4ed424ef9"}],"t":1700043357.7106903},{"p":"ecc-azure-042","l":"westeurope","r":[{"id":"7c7219ac-a461-4d8a-9865-6c8e32da4525"}],"t":1700043357.7106903},{"p":"ecc-azure-042","l":"multiregion","r":[{"id":"7c7219ac-a461-4d8a-9865-6c8e32da4525"}],"t":1700043357.7106903},{"p":"ecc-azure-043","l":"westeurope","r":[{"id":"0ee65b25-f0c5-44bc-af87-e35d057dc211"}],"t":1700043357.7106903},{"p":"ecc-azure-043","l":"multiregion","r":[{"id":"0ee65b25-f0c5-44bc-af87-e35d057dc211"}],"t":1700043357.7106903},{"p":"ecc-azure-044","l":"westeurope","r":[{"id":"79b9fa4b-b759-48d9-b09f-eb4009b8dd0f"}],"t":1700043357.7106903},{"p":"ecc-azure-044","l":"multiregion","r":[{"id":"79b9fa4b-b759-48d9-b09f-eb4009b8dd0f"}],"t":1700043357.7106903},{"p":"ecc-azure-045","l":"westeurope","r":[{"id":"a438d166-b36f-4d15-a803-ee584d01f236"}],"t":1700043357.7106903},{"p":"ecc-azure-045","l":"multiregion","r":[{"id":"a438d166-b36f-4d15-a803-ee584d01f236"}],"t":1700043357.7106903},{"p":"ecc-azure-046","l":"westeurope","r":[{"id":"b23fa74a-1cdd-4ec0-8d26-26f393c969da"}],"t":1700043357.7106903},{"p":"ecc-azure-046","l":"multiregion","r":[{"id":"b23fa74a-1cdd-4ec0-8d26-26f393c969da"}],"t":1700043357.7106903},{"p":"ecc-azure-048","l":"westeurope","r":[{"id":"669a34bb-d1dd-4677-8ef2-222b6bd35834"}],"t":1700043357.7106903},{"p":"ecc-azure-048","l":"multiregion","r":[{"id":"669a34bb-d1dd-4677-8ef2-222b6bd35834"}],"t":1700043357.7106903},{"p":"ecc-azure-049","l":"westeurope","r":[{"id":"e9f00b12-0431-4c64-8fd7-205388c74a5e"}],"t":1700043357.7106903},{"p":"ecc-azure-049","l":"multiregion","r":[{"id":"e9f00b12-0431-4c64-8fd7-205388c74a5e"}],"t":1700043357.7106903},{"p":"ecc-azure-050","l":"westeurope","r":[{"id":"7dbab209-e4a0-4547-a104-3f6f8edb57b5"}],"t":1700043357.7106903},{"p":"ecc-azure-050","l":"multiregion","r":[{"id":"7dbab209-e4a0-4547-a104-3f6f8edb57b5"}],"t":1700043357.7106903},{"p":"ecc-azure-052","l":"westeurope","r":[{"id":"3e0dcc39-68fb-41b6-a6e2-e05c5783e2bc"}],"t":1700043357.7106903},{"p":"ecc-azure-052","l":"multiregion","r":[{"id":"3e0dcc39-68fb-41b6-a6e2-e05c5783e2bc"}],"t":1700043357.7106903},{"p":"ecc-azure-053","l":"westeurope","r":[{"id":"a91126cf-e575-4965-b7d6-02a4cd75a239"}],"t":1700043357.7106903},{"p":"ecc-azure-053","l":"multiregion","r":[{"id":"a91126cf-e575-4965-b7d6-02a4cd75a239"}],"t":1700043357.7106903},{"p":"ecc-azure-054","l":"westeurope","r":[{"id":"ac6eab85-12ec-4fa6-ba69-057e8d5f0f61"}],"t":1700043357.7106903},{"p":"ecc-azure-054","l":"multiregion","r":[{"id":"ac6eab85-12ec-4fa6-ba69-057e8d5f0f61"}],"t":1700043357.7106903},{"p":"ecc-azure-055","l":"westeurope","r":[{"id":"d729a292-2a89-4410-8ffb-a4a64f1d0470"}],"t":1700043357.7106903},{"p":"ecc-azure-055","l":"multiregion","r":[{"id":"d729a292-2a89-4410-8ffb-a4a64f1d0470"}],"t":1700043357.7106903},{"p":"ecc-azure-056","l":"westeurope","r":[{"id":"c20fe273-7a01-4a5f-8cdf-a9b40c18ccfa"}],"t":1700043357.7106903},{"p":"ecc-azure-056","l":"multiregion","r":[{"id":"c20fe273-7a01-4a5f-8cdf-a9b40c18ccfa"}],"t":1700043357.7106903},{"p":"ecc-azure-057","l":"westeurope","r":[{"id":"de2b7984-116e-4305-a891-a4c61e332bd3"}],"t":1700043357.7106903},{"p":"ecc-azure-057","l":"multiregion","r":[{"id":"de2b7984-116e-4305-a891-a4c61e332bd3"}],"t":1700043357.7106903},{"p":"ecc-azure-058","l":"westeurope","r":[{"id":"9094e29f-7742-4a96-8afd-d126526c80d3"}],"t":1700043357.7106903},{"p":"ecc-azure-058","l":"multiregion","r":[{"id":"9094e29f-7742-4a96-8afd-d126526c80d3"}],"t":1700043357.7106903},{"p":"ecc-azure-059","l":"westeurope","r":[{"id":"bff25e71-01e9-4ce0-8216-1ead80f5f60e"}],"t":1700043357.7106903},{"p":"ecc-azure-059","l":"multiregion","r":[{"id":"bff25e71-01e9-4ce0-8216-1ead80f5f60e"}],"t":1700043357.7106903},{"p":"ecc-azure-060","l":"westeurope","r":[{"id":"eb4bdbe5-a63f-4928-a4ca-f573934a84d0"}],"t":1700043357.7106903},{"p":"ecc-azure-060","l":"multiregion","r":[{"id":"eb4bdbe5-a63f-4928-a4ca-f573934a84d0"}],"t":1700043357.7106903},{"p":"ecc-azure-061","l":"westeurope","r":[{"id":"505f8316-77c5-43b6-ba76-d29a321929a8"}],"t":1700043357.7106903},{"p":"ecc-azure-061","l":"multiregion","r":[{"id":"505f8316-77c5-43b6-ba76-d29a321929a8"}],"t":1700043357.7106903},{"p":"ecc-azure-064","l":"westeurope","r":[{"id":"6539e661-a285-4eb9-a579-5a7da81d15a5"}],"t":1700043357.7106903},{"p":"ecc-azure-064","l":"multiregion","r":[{"id":"6539e661-a285-4eb9-a579-5a7da81d15a5"}],"t":1700043357.7106903},{"p":"ecc-azure-065","l":"westeurope","r":[{"id":"7a538177-9691-4639-bd13-4e40ace31025"}],"t":1700043357.7106903},{"p":"ecc-azure-065","l":"multiregion","r":[{"id":"7a538177-9691-4639-bd13-4e40ace31025"}],"t":1700043357.7106903},{"p":"ecc-azure-066","l":"westeurope","r":[{"id":"82860b24-cffa-4def-a6c9-7a6fc90ac3b3"}],"t":1700043357.7106903},{"p":"ecc-azure-066","l":"multiregion","r":[{"id":"82860b24-cffa-4def-a6c9-7a6fc90ac3b3"}],"t":1700043357.7106903},{"p":"ecc-azure-067","l":"westeurope","r":[{"id":"9edfa5c6-729a-4759-8631-1e649ae7e9c1"}],"t":1700043357.7106903},{"p":"ecc-azure-067","l":"multiregion","r":[{"id":"9edfa5c6-729a-4759-8631-1e649ae7e9c1"}],"t":1700043357.7106903},{"p":"ecc-azure-068","l":"westeurope","r":[{"id":"5bdf9836-7396-4647-8c93-2732abbb7963"}],"t":1700043357.7106903},{"p":"ecc-azure-068","l":"multiregion","r":[{"id":"5bdf9836-7396-4647-8c93-2732abbb7963"}],"t":1700043357.7106903},{"p":"ecc-azure-069","l":"westeurope","r":[{"id":"76d06ea6-9eb9-4912-a0bd-f0f93259048b"}],"t":1700043357.7106903},{"p":"ecc-azure-069","l":"multiregion","r":[{"id":"76d06ea6-9eb9-4912-a0bd-f0f93259048b"}],"t":1700043357.7106903},{"p":"ecc-azure-070","l":"westeurope","r":[{"id":"3b5a60ac-133a-4542-8cd3-31484a674326"}],"t":1700043357.7106903},{"p":"ecc-azure-070","l":"multiregion","r":[{"id":"3b5a60ac-133a-4542-8cd3-31484a674326"}],"t":1700043357.7106903},{"p":"ecc-azure-071","l":"westeurope","r":[{"id":"b423498e-70df-4e51-826d-03c03a55bea3"}],"t":1700043357.7106903},{"p":"ecc-azure-071","l":"multiregion","r":[{"id":"b423498e-70df-4e51-826d-03c03a55bea3"}],"t":1700043357.7106903},{"p":"ecc-azure-072","l":"westeurope","r":[{"id":"c82f4e6e-af5e-49c0-9466-febd8aee46ab"}],"t":1700043357.7106903},{"p":"ecc-azure-072","l":"multiregion","r":[{"id":"c82f4e6e-af5e-49c0-9466-febd8aee46ab"}],"t":1700043357.7106903},{"p":"ecc-azure-094","l":"westeurope","r":[{"id":"5ac814ba-ab58-4af9-9786-8ba8898d0374"}],"t":1700043357.7106903},{"p":"ecc-azure-094","l":"multiregion","r":[{"id":"5ac814ba-ab58-4af9-9786-8ba8898d0374"}],"t":1700043357.7106903},{"p":"ecc-azure-095","l":"westeurope","r":[{"id":"867b733a-621c-4396-9258-1cd0dc8766b8"}],"t":1700043357.7106903},{"p":"ecc-azure-095","l":"multiregion","r":[{"id":"867b733a-621c-4396-9258-1cd0dc8766b8"}],"t":1700043357.7106903},{"p":"ecc-azure-096","l":"westeurope","r":[{"id":"1266ab08-2d2a-4b7d-9983-ee7d833748f9"}],"t":1700043357.7106903},{"p":"ecc-azure-096","l":"multiregion","r":[{"id":"1266ab08-2d2a-4b7d-9983-ee7d833748f9"}],"t":1700043357.7106903},{"p":"ecc-azure-097","l":"westeurope","r":[{"id":"bf2b4ecf-9ecc-4df2-af28-169b7cb2b241"}],"t":1700043357.7106903},{"p":"ecc-azure-097","l":"multiregion","r":[{"id":"bf2b4ecf-9ecc-4df2-af28-169b7cb2b241"}],"t":1700043357.7106903},{"p":"ecc-azure-098","l":"westeurope","r":[{"id":"ca9625c4-735e-4953-9578-fec17f755986"}],"t":1700043357.7106903},{"p":"ecc-azure-098","l":"multiregion","r":[{"id":"ca9625c4-735e-4953-9578-fec17f755986"}],"t":1700043357.7106903},{"p":"ecc-azure-099","l":"westeurope","r":[{"id":"903e2fe1-f706-47e5-938e-cb07656cd369"}],"t":1700043357.7106903},{"p":"ecc-azure-099","l":"multiregion","r":[{"id":"903e2fe1-f706-47e5-938e-cb07656cd369"}],"t":1700043357.7106903},{"p":"ecc-azure-100","l":"westeurope","r":[{"id":"d14140cb-e86a-4217-8e19-ae66adef5e97"}],"t":1700043357.7106903},{"p":"ecc-azure-100","l":"multiregion","r":[{"id":"d14140cb-e86a-4217-8e19-ae66adef5e97"}],"t":1700043357.7106903},{"p":"ecc-azure-101","l":"westeurope","r":[{"id":"18d768de-ef35-401f-817c-6c05bfb43d71"}],"t":1700043357.7106903},{"p":"ecc-azure-101","l":"multiregion","r":[{"id":"18d768de-ef35-401f-817c-6c05bfb43d71"}],"t":1700043357.7106903},{"p":"ecc-azure-102","l":"westeurope","r":[{"id":"75e39448-b06b-4b59-aeaa-6b2cc00ed33b"}],"t":1700043357.7106903},{"p":"ecc-azure-102","l":"multiregion","r":[{"id":"75e39448-b06b-4b59-aeaa-6b2cc00ed33b"}],"t":1700043357.7106903},{"p":"ecc-azure-103","l":"westeurope","r":[{"id":"c8ed0673-61f5-411e-ac91-9f5caa02c17b"}],"t":1700043357.7106903},{"p":"ecc-azure-103","l":"multiregion","r":[{"id":"c8ed0673-61f5-411e-ac91-9f5caa02c17b"}],"t":1700043357.7106903},{"p":"ecc-azure-105","l":"westeurope","r":[{"id":"b8a24961-1ac0-41d3-a635-849d62d62505"}],"t":1700043357.7106903},{"p":"ecc-azure-105","l":"multiregion","r":[{"id":"b8a24961-1ac0-41d3-a635-849d62d62505"}],"t":1700043357.7106903},{"p":"ecc-azure-106","l":"westeurope","r":[{"id":"e685b29e-41da-48ee-8369-b8e62e0f759f"}],"t":1700043357.7106903},{"p":"ecc-azure-106","l":"multiregion","r":[{"id":"e685b29e-41da-48ee-8369-b8e62e0f759f"}],"t":1700043357.7106903},{"p":"ecc-azure-108","l":"westeurope","r":[{"id":"fab9f44e-7b51-4046-abc4-26fae592a0b6"}],"t":1700043357.7106903},{"p":"ecc-azure-108","l":"multiregion","r":[{"id":"fab9f44e-7b51-4046-abc4-26fae592a0b6"}],"t":1700043357.7106903},{"p":"ecc-azure-109","l":"westeurope","r":[{"id":"669ea92e-d04f-4680-b8a9-08da372c4901"}],"t":1700043357.7106903},{"p":"ecc-azure-109","l":"multiregion","r":[{"id":"669ea92e-d04f-4680-b8a9-08da372c4901"}],"t":1700043357.7106903},{"p":"ecc-azure-110","l":"westeurope","r":[{"id":"2bb7b2b8-d34a-41d2-a0db-ac896f85bdc9"}],"t":1700043357.7106903},{"p":"ecc-azure-110","l":"multiregion","r":[{"id":"2bb7b2b8-d34a-41d2-a0db-ac896f85bdc9"}],"t":1700043357.7106903},{"p":"ecc-azure-111","l":"westeurope","r":[{"id":"b3dfeabf-25ee-48dd-8983-2b8555bb3459"}],"t":1700043357.7106903},{"p":"ecc-azure-111","l":"multiregion","r":[{"id":"b3dfeabf-25ee-48dd-8983-2b8555bb3459"}],"t":1700043357.7106903},{"p":"ecc-azure-112","l":"westeurope","r":[{"id":"1c9046bb-a91c-444c-91a8-2a2c54fdf2b7"}],"t":1700043357.7106903},{"p":"ecc-azure-112","l":"multiregion","r":[{"id":"1c9046bb-a91c-444c-91a8-2a2c54fdf2b7"}],"t":1700043357.7106903},{"p":"ecc-azure-113","l":"westeurope","r":[{"id":"f65a3121-f4d3-4301-b2de-63af6530771a"}],"t":1700043357.7106903},{"p":"ecc-azure-113","l":"multiregion","r":[{"id":"f65a3121-f4d3-4301-b2de-63af6530771a"}],"t":1700043357.7106903},{"p":"ecc-azure-116","l":"westeurope","r":[{"id":"7738daf2-e3f5-4fd2-9ae1-8a2df7781ced"}],"t":1700043357.7106903},{"p":"ecc-azure-116","l":"multiregion","r":[{"id":"7738daf2-e3f5-4fd2-9ae1-8a2df7781ced"}],"t":1700043357.7106903},{"p":"ecc-azure-117","l":"westeurope","r":[{"id":"0ea5877d-f415-4bdf-b7c6-18a79a732b22"}],"t":1700043357.7106903},{"p":"ecc-azure-117","l":"multiregion","r":[{"id":"0ea5877d-f415-4bdf-b7c6-18a79a732b22"}],"t":1700043357.7106903},{"p":"ecc-azure-119","l":"westeurope","r":[{"id":"309923b5-b9b4-46aa-a18a-7ffdc8890805"}],"t":1700043357.7106903},{"p":"ecc-azure-119","l":"multiregion","r":[{"id":"309923b5-b9b4-46aa-a18a-7ffdc8890805"}],"t":1700043357.7106903},{"p":"ecc-azure-120","l":"westeurope","r":[{"id":"cd3c7a11-8f09-4235-965e-5ccdd9f59eab"}],"t":1700043357.7106903},{"p":"ecc-azure-120","l":"multiregion","r":[{"id":"cd3c7a11-8f09-4235-965e-5ccdd9f59eab"}],"t":1700043357.7106903},{"p":"ecc-azure-121","l":"westeurope","r":[{"id":"92bd8a27-62df-4839-acd1-c46f85d0e183"}],"t":1700043357.7106903},{"p":"ecc-azure-121","l":"multiregion","r":[{"id":"92bd8a27-62df-4839-acd1-c46f85d0e183"}],"t":1700043357.7106903},{"p":"ecc-azure-122","l":"westeurope","r":[{"id":"de03e671-eb62-494f-b63d-9977beb65327"}],"t":1700043357.7106903},{"p":"ecc-azure-122","l":"multiregion","r":[{"id":"de03e671-eb62-494f-b63d-9977beb65327"}],"t":1700043357.7106903},{"p":"ecc-azure-123","l":"westeurope","r":[{"id":"96f01efd-a245-4944-8090-28205af317ed"}],"t":1700043357.7106903},{"p":"ecc-azure-123","l":"multiregion","r":[{"id":"96f01efd-a245-4944-8090-28205af317ed"}],"t":1700043357.7106903},{"p":"ecc-azure-124","l":"westeurope","r":[{"id":"a6b152d8-eca3-418c-ae2c-dbe0c68d4cbf"}],"t":1700043357.7106903},{"p":"ecc-azure-124","l":"multiregion","r":[{"id":"a6b152d8-eca3-418c-ae2c-dbe0c68d4cbf"}],"t":1700043357.7106903},{"p":"ecc-azure-125","l":"westeurope","r":[{"id":"7a6f42b2-f453-467a-9617-1d34411e6f35"}],"t":1700043357.7106903},{"p":"ecc-azure-125","l":"multiregion","r":[{"id":"7a6f42b2-f453-467a-9617-1d34411e6f35"}],"t":1700043357.7106903},{"p":"ecc-azure-126","l":"westeurope","r":[{"id":"c6f55baa-b525-480b-8ed7-2ffc5fba4b41"}],"t":1700043357.7106903},{"p":"ecc-azure-126","l":"multiregion","r":[{"id":"c6f55baa-b525-480b-8ed7-2ffc5fba4b41"}],"t":1700043357.7106903},{"p":"ecc-azure-127","l":"westeurope","r":[{"id":"ecc36f8c-6260-4645-88e0-971a2cdf5e6d"}],"t":1700043357.7106903},{"p":"ecc-azure-127","l":"multiregion","r":[{"id":"ecc36f8c-6260-4645-88e0-971a2cdf5e6d"}],"t":1700043357.7106903},{"p":"ecc-azure-128","l":"westeurope","r":[{"id":"4a45b893-0f10-448a-9e7e-a0fb6bbd68d8"}],"t":1700043357.7106903},{"p":"ecc-azure-128","l":"multiregion","r":[{"id":"4a45b893-0f10-448a-9e7e-a0fb6bbd68d8"}],"t":1700043357.7106903},{"p":"ecc-azure-129","l":"westeurope","r":[{"id":"ed42b617-7d62-4328-88cb-b18d64b565a5"}],"t":1700043357.7106903},{"p":"ecc-azure-129","l":"multiregion","r":[{"id":"ed42b617-7d62-4328-88cb-b18d64b565a5"}],"t":1700043357.7106903},{"p":"ecc-azure-130","l":"westeurope","r":[{"id":"c439bef4-ca5f-42ff-93fe-d18169697352"}],"t":1700043357.7106903},{"p":"ecc-azure-130","l":"multiregion","r":[{"id":"c439bef4-ca5f-42ff-93fe-d18169697352"}],"t":1700043357.7106903},{"p":"ecc-azure-131","l":"westeurope","r":[{"id":"441f6c0d-b7c9-4a11-bbec-895f82d10cbc"}],"t":1700043357.7106903},{"p":"ecc-azure-131","l":"multiregion","r":[{"id":"441f6c0d-b7c9-4a11-bbec-895f82d10cbc"}],"t":1700043357.7106903},{"p":"ecc-azure-132","l":"westeurope","r":[{"id":"cdc256d0-67c9-4389-911c-120f918b69ce"}],"t":1700043357.7106903},{"p":"ecc-azure-132","l":"multiregion","r":[{"id":"cdc256d0-67c9-4389-911c-120f918b69ce"}],"t":1700043357.7106903},{"p":"ecc-azure-133","l":"westeurope","r":[{"id":"d4acad5d-8bb4-4418-aa6e-63e8bab63def"}],"t":1700043357.7106903},{"p":"ecc-azure-133","l":"multiregion","r":[{"id":"d4acad5d-8bb4-4418-aa6e-63e8bab63def"}],"t":1700043357.7106903},{"p":"ecc-azure-137","l":"westeurope","r":[{"id":"6dd3be97-ec90-4c6d-b3a2-cd1973101f3a"}],"t":1700043357.7106903},{"p":"ecc-azure-137","l":"multiregion","r":[{"id":"6dd3be97-ec90-4c6d-b3a2-cd1973101f3a"}],"t":1700043357.7106903},{"p":"ecc-azure-139","l":"westeurope","r":[{"id":"70f6d76c-63d4-494d-a5d4-e09cb583e7f2"}],"t":1700043357.7106903},{"p":"ecc-azure-139","l":"multiregion","r":[{"id":"70f6d76c-63d4-494d-a5d4-e09cb583e7f2"}],"t":1700043357.7106903},{"p":"ecc-azure-141","l":"westeurope","r":[{"id":"16f66fb5-0913-4a2b-91d1-f6b1b9c3e3a1"}],"t":1700043357.7106903},{"p":"ecc-azure-141","l":"multiregion","r":[{"id":"16f66fb5-0913-4a2b-91d1-f6b1b9c3e3a1"}],"t":1700043357.7106903},{"p":"ecc-azure-142","l":"westeurope","r":[{"id":"2685f401-a908-4436-baac-9e610bc0ced4"}],"t":1700043357.7106903},{"p":"ecc-azure-142","l":"multiregion","r":[{"id":"2685f401-a908-4436-baac-9e610bc0ced4"}],"t":1700043357.7106903},{"p":"ecc-azure-143","l":"westeurope","r":[{"id":"d1c6a788-c701-4a37-8dae-a534d134fca6"}],"t":1700043357.7106903},{"p":"ecc-azure-143","l":"multiregion","r":[{"id":"d1c6a788-c701-4a37-8dae-a534d134fca6"}],"t":1700043357.7106903},{"p":"ecc-azure-144","l":"westeurope","r":[{"id":"4e1ec643-543c-4b61-ad46-27917f4e6cb3"}],"t":1700043357.7106903},{"p":"ecc-azure-144","l":"multiregion","r":[{"id":"4e1ec643-543c-4b61-ad46-27917f4e6cb3"}],"t":1700043357.7106903},{"p":"ecc-azure-145","l":"westeurope","r":[{"id":"adb9864c-736c-44c7-9f38-0cfeb6dffd21"}],"t":1700043357.7106903},{"p":"ecc-azure-145","l":"multiregion","r":[{"id":"adb9864c-736c-44c7-9f38-0cfeb6dffd21"}],"t":1700043357.7106903},{"p":"ecc-azure-146","l":"westeurope","r":[{"id":"b2a6bc7e-aca3-4f0d-a038-f5fb0a9e375d"}],"t":1700043357.7106903},{"p":"ecc-azure-146","l":"multiregion","r":[{"id":"b2a6bc7e-aca3-4f0d-a038-f5fb0a9e375d"}],"t":1700043357.7106903},{"p":"ecc-azure-147","l":"westeurope","r":[{"id":"82a37aa2-541a-4a3a-8c11-fa8dad0136b0"}],"t":1700043357.7106903},{"p":"ecc-azure-147","l":"multiregion","r":[{"id":"82a37aa2-541a-4a3a-8c11-fa8dad0136b0"}],"t":1700043357.7106903},{"p":"ecc-azure-148","l":"westeurope","r":[{"id":"0a4c1dfb-635d-4c5f-814f-cd843108e7d2"}],"t":1700043357.7106903},{"p":"ecc-azure-148","l":"multiregion","r":[{"id":"0a4c1dfb-635d-4c5f-814f-cd843108e7d2"}],"t":1700043357.7106903},{"p":"ecc-azure-149","l":"westeurope","r":[{"id":"861d927a-8ea0-4b52-a858-373c55df02c4"}],"t":1700043357.7106903},{"p":"ecc-azure-149","l":"multiregion","r":[{"id":"861d927a-8ea0-4b52-a858-373c55df02c4"}],"t":1700043357.7106903},{"p":"ecc-azure-150","l":"westeurope","r":[{"id":"8c1e215b-68d0-4eab-a0df-2909a2d7f319"}],"t":1700043357.7106903},{"p":"ecc-azure-150","l":"multiregion","r":[{"id":"8c1e215b-68d0-4eab-a0df-2909a2d7f319"}],"t":1700043357.7106903},{"p":"ecc-azure-151","l":"westeurope","r":[{"id":"b7baa805-dced-445a-a53d-880f22e8e9c6"}],"t":1700043357.7106903},{"p":"ecc-azure-151","l":"multiregion","r":[{"id":"b7baa805-dced-445a-a53d-880f22e8e9c6"}],"t":1700043357.7106903},{"p":"ecc-azure-152","l":"westeurope","r":[{"id":"9cb8ddb6-7c3c-43e5-8ad9-e88a3eb468e2"}],"t":1700043357.7106903},{"p":"ecc-azure-152","l":"multiregion","r":[{"id":"9cb8ddb6-7c3c-43e5-8ad9-e88a3eb468e2"}],"t":1700043357.7106903},{"p":"ecc-azure-155","l":"westeurope","r":[{"id":"f97375b2-ac33-4e55-b504-c99a64dc5795"}],"t":1700043357.7106903},{"p":"ecc-azure-155","l":"multiregion","r":[{"id":"f97375b2-ac33-4e55-b504-c99a64dc5795"}],"t":1700043357.7106903},{"p":"ecc-azure-156","l":"westeurope","r":[{"id":"1d6db14d-08cd-4fa2-93ad-567ab4323bf6"}],"t":1700043357.7106903},{"p":"ecc-azure-156","l":"multiregion","r":[{"id":"1d6db14d-08cd-4fa2-93ad-567ab4323bf6"}],"t":1700043357.7106903},{"p":"ecc-azure-157","l":"westeurope","r":[{"id":"7207fb00-9fe7-4025-b80a-6251e320fee7"}],"t":1700043357.7106903},{"p":"ecc-azure-157","l":"multiregion","r":[{"id":"7207fb00-9fe7-4025-b80a-6251e320fee7"}],"t":1700043357.7106903},{"p":"ecc-azure-158","l":"westeurope","r":[{"id":"38b0175c-8528-4627-866e-f437ad99cf91"}],"t":1700043357.7106903},{"p":"ecc-azure-158","l":"multiregion","r":[{"id":"38b0175c-8528-4627-866e-f437ad99cf91"}],"t":1700043357.7106903},{"p":"ecc-azure-159","l":"westeurope","r":[{"id":"6b6c7b63-b19c-433d-8c8c-58d27feaa71a"}],"t":1700043357.7106903},{"p":"ecc-azure-159","l":"multiregion","r":[{"id":"6b6c7b63-b19c-433d-8c8c-58d27feaa71a"}],"t":1700043357.7106903},{"p":"ecc-azure-160","l":"westeurope","r":[{"id":"350564df-4a3c-4808-8e60-3bb20663ccf1"}],"t":1700043357.7106903},{"p":"ecc-azure-160","l":"multiregion","r":[{"id":"350564df-4a3c-4808-8e60-3bb20663ccf1"}],"t":1700043357.7106903},{"p":"ecc-azure-161","l":"westeurope","r":[{"id":"2fe7144f-00a1-416f-b157-9a27a467da41"}],"t":1700043357.7106903},{"p":"ecc-azure-161","l":"multiregion","r":[{"id":"2fe7144f-00a1-416f-b157-9a27a467da41"}],"t":1700043357.7106903},{"p":"ecc-azure-162","l":"westeurope","r":[{"id":"71293998-65e0-403f-8547-188877217399"}],"t":1700043357.7106903},{"p":"ecc-azure-162","l":"multiregion","r":[{"id":"71293998-65e0-403f-8547-188877217399"}],"t":1700043357.7106903},{"p":"ecc-azure-163","l":"westeurope","r":[{"id":"e5691239-5256-4165-a2a5-0728134b4187"}],"t":1700043357.7106903},{"p":"ecc-azure-163","l":"multiregion","r":[{"id":"e5691239-5256-4165-a2a5-0728134b4187"}],"t":1700043357.7106903},{"p":"ecc-azure-164","l":"westeurope","r":[{"id":"1443d720-e8ab-4104-bb0e-ad4ff288c9de"}],"t":1700043357.7106903},{"p":"ecc-azure-164","l":"multiregion","r":[{"id":"1443d720-e8ab-4104-bb0e-ad4ff288c9de"}],"t":1700043357.7106903},{"p":"ecc-azure-165","l":"westeurope","r":[{"id":"b4490f1b-fe57-4f98-b5d2-7725e900f0bb"}],"t":1700043357.7106903},{"p":"ecc-azure-165","l":"multiregion","r":[{"id":"b4490f1b-fe57-4f98-b5d2-7725e900f0bb"}],"t":1700043357.7106903},{"p":"ecc-azure-166","l":"westeurope","r":[{"id":"425b7f5f-ca0e-4029-8392-e91a5a579dd7"}],"t":1700043357.7106903},{"p":"ecc-azure-166","l":"multiregion","r":[{"id":"425b7f5f-ca0e-4029-8392-e91a5a579dd7"}],"t":1700043357.7106903},{"p":"ecc-azure-167","l":"westeurope","r":[{"id":"44545b87-d173-4e68-898e-e5f4d7bf699f"}],"t":1700043357.7106903},{"p":"ecc-azure-167","l":"multiregion","r":[{"id":"44545b87-d173-4e68-898e-e5f4d7bf699f"}],"t":1700043357.7106903},{"p":"ecc-azure-168","l":"westeurope","r":[{"id":"ff4f80ec-8cf5-4d60-b234-6c0978348d9f"}],"t":1700043357.7106903},{"p":"ecc-azure-168","l":"multiregion","r":[{"id":"ff4f80ec-8cf5-4d60-b234-6c0978348d9f"}],"t":1700043357.7106903},{"p":"ecc-azure-170","l":"westeurope","r":[{"id":"d445e783-1842-4cdb-82a6-3dff74ecb389"}],"t":1700043357.7106903},{"p":"ecc-azure-170","l":"multiregion","r":[{"id":"d445e783-1842-4cdb-82a6-3dff74ecb389"}],"t":1700043357.7106903},{"p":"ecc-azure-171","l":"westeurope","r":[{"id":"c2196b41-ea8e-404e-882e-27f508947e3b"}],"t":1700043357.7106903},{"p":"ecc-azure-171","l":"multiregion","r":[{"id":"c2196b41-ea8e-404e-882e-27f508947e3b"}],"t":1700043357.7106903},{"p":"ecc-azure-172","l":"westeurope","r":[{"id":"0c72c5e6-3d3c-4657-bf48-a5fab522e8c5"}],"t":1700043357.7106903},{"p":"ecc-azure-172","l":"multiregion","r":[{"id":"0c72c5e6-3d3c-4657-bf48-a5fab522e8c5"}],"t":1700043357.7106903},{"p":"ecc-azure-173","l":"westeurope","r":[{"id":"862edf41-b118-4a4b-a842-72e13acb0afa"}],"t":1700043357.7106903},{"p":"ecc-azure-173","l":"multiregion","r":[{"id":"862edf41-b118-4a4b-a842-72e13acb0afa"}],"t":1700043357.7106903},{"p":"ecc-azure-174","l":"westeurope","r":[{"id":"c981bdb3-0c30-4d4a-ab8e-498e0c1d23de"}],"t":1700043357.7106903},{"p":"ecc-azure-174","l":"multiregion","r":[{"id":"c981bdb3-0c30-4d4a-ab8e-498e0c1d23de"}],"t":1700043357.7106903},{"p":"ecc-azure-176","l":"westeurope","r":[{"id":"7f55715c-06a3-4fd4-9c63-d488682968a5"}],"t":1700043357.7106903},{"p":"ecc-azure-176","l":"multiregion","r":[{"id":"7f55715c-06a3-4fd4-9c63-d488682968a5"}],"t":1700043357.7106903},{"p":"ecc-azure-177","l":"westeurope","r":[{"id":"675f4b48-c4b3-4d9a-a2a2-36f1374dd8dc"}],"t":1700043357.7106903},{"p":"ecc-azure-177","l":"multiregion","r":[{"id":"675f4b48-c4b3-4d9a-a2a2-36f1374dd8dc"}],"t":1700043357.7106903},{"p":"ecc-azure-178","l":"westeurope","r":[{"id":"67084cd3-f1af-4c1f-a10b-e64edc469f5e"}],"t":1700043357.7106903},{"p":"ecc-azure-178","l":"multiregion","r":[{"id":"67084cd3-f1af-4c1f-a10b-e64edc469f5e"}],"t":1700043357.7106903},{"p":"ecc-azure-179","l":"westeurope","r":[{"id":"b2feb84d-068d-4d0f-b9d7-a2dcd5451d59"}],"t":1700043357.7106903},{"p":"ecc-azure-179","l":"multiregion","r":[{"id":"b2feb84d-068d-4d0f-b9d7-a2dcd5451d59"}],"t":1700043357.7106903},{"p":"ecc-azure-180","l":"westeurope","r":[{"id":"adcafa32-72c8-4089-b3a1-ddee395ad2ca"}],"t":1700043357.7106903},{"p":"ecc-azure-180","l":"multiregion","r":[{"id":"adcafa32-72c8-4089-b3a1-ddee395ad2ca"}],"t":1700043357.7106903},{"p":"ecc-azure-181","l":"westeurope","r":[{"id":"ead75bec-c316-4a25-9e6c-4c8554eb281e"}],"t":1700043357.7106903},{"p":"ecc-azure-181","l":"multiregion","r":[{"id":"ead75bec-c316-4a25-9e6c-4c8554eb281e"}],"t":1700043357.7106903},{"p":"ecc-azure-182","l":"westeurope","r":[{"id":"be545a91-f9a3-40f8-951e-2bf00473e125"}],"t":1700043357.7106903},{"p":"ecc-azure-182","l":"multiregion","r":[{"id":"be545a91-f9a3-40f8-951e-2bf00473e125"}],"t":1700043357.7106903},{"p":"ecc-azure-184","l":"westeurope","r":[{"id":"d6586bd0-22eb-4f6d-8626-d20461a05cb5"}],"t":1700043357.7106903},{"p":"ecc-azure-184","l":"multiregion","r":[{"id":"d6586bd0-22eb-4f6d-8626-d20461a05cb5"}],"t":1700043357.7106903},{"p":"ecc-azure-196","l":"westeurope","r":[{"id":"b517f6d2-55a1-47f3-a846-c3e3365689a4"}],"t":1700043357.7106903},{"p":"ecc-azure-196","l":"multiregion","r":[{"id":"b517f6d2-55a1-47f3-a846-c3e3365689a4"}],"t":1700043357.7106903},{"p":"ecc-azure-197","l":"westeurope","r":[{"id":"df1dd248-5e7e-410a-bdfa-94b67fb6fe49"}],"t":1700043357.7106903},{"p":"ecc-azure-197","l":"multiregion","r":[{"id":"df1dd248-5e7e-410a-bdfa-94b67fb6fe49"}],"t":1700043357.7106903},{"p":"ecc-azure-199","l":"westeurope","r":[{"id":"8debafce-00dd-4bc9-b13e-fb1215e7c8d9"}],"t":1700043357.7106903},{"p":"ecc-azure-199","l":"multiregion","r":[{"id":"8debafce-00dd-4bc9-b13e-fb1215e7c8d9"}],"t":1700043357.7106903},{"p":"ecc-azure-200","l":"westeurope","r":[{"id":"4668b52b-070a-4ca5-ae4e-ac483a6ff513"}],"t":1700043357.7106903},{"p":"ecc-azure-200","l":"multiregion","r":[{"id":"4668b52b-070a-4ca5-ae4e-ac483a6ff513"}],"t":1700043357.7106903},{"p":"ecc-azure-201","l":"westeurope","r":[{"id":"66d92f74-1405-48e8-962f-295dbbb417a6"}],"t":1700043357.7106903},{"p":"ecc-azure-201","l":"multiregion","r":[{"id":"66d92f74-1405-48e8-962f-295dbbb417a6"}],"t":1700043357.7106903},{"p":"ecc-azure-202","l":"westeurope","r":[{"id":"0a1f63ba-06ac-4738-a780-4e2029e31f3e"}],"t":1700043357.7106903},{"p":"ecc-azure-202","l":"multiregion","r":[{"id":"0a1f63ba-06ac-4738-a780-4e2029e31f3e"}],"t":1700043357.7106903},{"p":"ecc-azure-203","l":"westeurope","r":[{"id":"118eeff6-873d-47dc-8ea1-6b591bc20723"}],"t":1700043357.7106903},{"p":"ecc-azure-203","l":"multiregion","r":[{"id":"118eeff6-873d-47dc-8ea1-6b591bc20723"}],"t":1700043357.7106903},{"p":"ecc-azure-204","l":"westeurope","r":[{"id":"b68f6092-6f7b-4e6a-8feb-547dac5a287b"}],"t":1700043357.7106903},{"p":"ecc-azure-204","l":"multiregion","r":[{"id":"b68f6092-6f7b-4e6a-8feb-547dac5a287b"}],"t":1700043357.7106903},{"p":"ecc-azure-205","l":"westeurope","r":[{"id":"2c363603-f8fa-4695-a694-21464f5131da"}],"t":1700043357.7106903},{"p":"ecc-azure-205","l":"multiregion","r":[{"id":"2c363603-f8fa-4695-a694-21464f5131da"}],"t":1700043357.7106903},{"p":"ecc-azure-206","l":"westeurope","r":[{"id":"90894bd0-0a4b-4abd-843b-daea99637dfc"}],"t":1700043357.7106903},{"p":"ecc-azure-206","l":"multiregion","r":[{"id":"90894bd0-0a4b-4abd-843b-daea99637dfc"}],"t":1700043357.7106903},{"p":"ecc-azure-207","l":"westeurope","r":[{"id":"26ae4e1d-94e8-4c87-8cf6-57a3b83c0fdb"}],"t":1700043357.7106903},{"p":"ecc-azure-207","l":"multiregion","r":[{"id":"26ae4e1d-94e8-4c87-8cf6-57a3b83c0fdb"}],"t":1700043357.7106903},{"p":"ecc-azure-213","l":"westeurope","r":[{"id":"4f2f30b9-d2a2-4646-9b56-3650086b5c09"}],"t":1700043357.7106903},{"p":"ecc-azure-213","l":"multiregion","r":[{"id":"4f2f30b9-d2a2-4646-9b56-3650086b5c09"}],"t":1700043357.7106903},{"p":"ecc-azure-214","l":"westeurope","r":[{"id":"2ba951c2-1aa1-4f9a-a20d-fa96ce57b0e0"}],"t":1700043357.7106903},{"p":"ecc-azure-214","l":"multiregion","r":[{"id":"2ba951c2-1aa1-4f9a-a20d-fa96ce57b0e0"}],"t":1700043357.7106903},{"p":"ecc-azure-215","l":"westeurope","r":[{"id":"488bbde1-1aec-4724-b7ec-c06284d0774c"}],"t":1700043357.7106903},{"p":"ecc-azure-215","l":"multiregion","r":[{"id":"488bbde1-1aec-4724-b7ec-c06284d0774c"}],"t":1700043357.7106903},{"p":"ecc-azure-216","l":"westeurope","r":[{"id":"34e6c7e8-201a-4461-b147-d3cbb273fe9a"}],"t":1700043357.7106903},{"p":"ecc-azure-216","l":"multiregion","r":[{"id":"34e6c7e8-201a-4461-b147-d3cbb273fe9a"}],"t":1700043357.7106903},{"p":"ecc-azure-217","l":"westeurope","r":[{"id":"0ad502c2-ca47-460c-8604-80556cb199f4"}],"t":1700043357.7106903},{"p":"ecc-azure-217","l":"multiregion","r":[{"id":"0ad502c2-ca47-460c-8604-80556cb199f4"}],"t":1700043357.7106903},{"p":"ecc-azure-218","l":"westeurope","r":[{"id":"e486564b-f592-4b64-ab78-cb520a1e1555"}],"t":1700043357.7106903},{"p":"ecc-azure-218","l":"multiregion","r":[{"id":"e486564b-f592-4b64-ab78-cb520a1e1555"}],"t":1700043357.7106903},{"p":"ecc-azure-219","l":"westeurope","r":[{"id":"591c518b-8d71-45f2-9be8-4b1e8aa70dd6"}],"t":1700043357.7106903},{"p":"ecc-azure-219","l":"multiregion","r":[{"id":"591c518b-8d71-45f2-9be8-4b1e8aa70dd6"}],"t":1700043357.7106903},{"p":"ecc-azure-220","l":"westeurope","r":[{"id":"ecb492ba-9aad-4abc-9009-abd56cb71d5e"}],"t":1700043357.7106903},{"p":"ecc-azure-220","l":"multiregion","r":[{"id":"ecb492ba-9aad-4abc-9009-abd56cb71d5e"}],"t":1700043357.7106903},{"p":"ecc-azure-222","l":"westeurope","r":[{"id":"c471eaf8-ef27-4715-a300-a2fc40cc51d6"}],"t":1700043357.7106903},{"p":"ecc-azure-222","l":"multiregion","r":[{"id":"c471eaf8-ef27-4715-a300-a2fc40cc51d6"}],"t":1700043357.7106903},{"p":"ecc-azure-224","l":"westeurope","r":[{"id":"c3d0bf41-109c-473e-b858-5290d8389d19"}],"t":1700043357.7106903},{"p":"ecc-azure-224","l":"multiregion","r":[{"id":"c3d0bf41-109c-473e-b858-5290d8389d19"}],"t":1700043357.7106903},{"p":"ecc-azure-225","l":"westeurope","r":[{"id":"27e29873-95d9-416f-b06f-2f3644b92727"}],"t":1700043357.7106903},{"p":"ecc-azure-225","l":"multiregion","r":[{"id":"27e29873-95d9-416f-b06f-2f3644b92727"}],"t":1700043357.7106903},{"p":"ecc-azure-226","l":"westeurope","r":[{"id":"953e6ce8-a9ca-4f46-8d13-d78029bf2d3a"}],"t":1700043357.7106903},{"p":"ecc-azure-226","l":"multiregion","r":[{"id":"953e6ce8-a9ca-4f46-8d13-d78029bf2d3a"}],"t":1700043357.7106903},{"p":"ecc-azure-227","l":"westeurope","r":[{"id":"a290671d-506b-4fc9-ba4a-e3076744895e"}],"t":1700043357.7106903},{"p":"ecc-azure-227","l":"multiregion","r":[{"id":"a290671d-506b-4fc9-ba4a-e3076744895e"}],"t":1700043357.7106903},{"p":"ecc-azure-228","l":"westeurope","r":[{"id":"64a293e5-ce94-4192-868e-c9ffd22cbffb"}],"t":1700043357.7106903},{"p":"ecc-azure-228","l":"multiregion","r":[{"id":"64a293e5-ce94-4192-868e-c9ffd22cbffb"}],"t":1700043357.7106903},{"p":"ecc-azure-231","l":"westeurope","r":[{"id":"73adec1c-39ca-4277-9b15-d6fb5e57b54e"}],"t":1700043357.7106903},{"p":"ecc-azure-231","l":"multiregion","r":[{"id":"73adec1c-39ca-4277-9b15-d6fb5e57b54e"}],"t":1700043357.7106903},{"p":"ecc-azure-232","l":"westeurope","r":[{"id":"2bfe232b-b647-4880-b906-6182382d726c"}],"t":1700043357.7106903},{"p":"ecc-azure-232","l":"multiregion","r":[{"id":"2bfe232b-b647-4880-b906-6182382d726c"}],"t":1700043357.7106903},{"p":"ecc-azure-234","l":"westeurope","r":[{"id":"5631a909-1bfc-4f3a-a76d-5b4b5631c3e8"}],"t":1700043357.7106903},{"p":"ecc-azure-234","l":"multiregion","r":[{"id":"5631a909-1bfc-4f3a-a76d-5b4b5631c3e8"}],"t":1700043357.7106903},{"p":"ecc-azure-235","l":"westeurope","r":[{"id":"983b484f-32e6-4928-8962-e480274e50cb"}],"t":1700043357.7106903},{"p":"ecc-azure-235","l":"multiregion","r":[{"id":"983b484f-32e6-4928-8962-e480274e50cb"}],"t":1700043357.7106903},{"p":"ecc-azure-236","l":"westeurope","r":[{"id":"f4820dcd-9aec-4206-9aa3-90fe3cd9ab5e"}],"t":1700043357.7106903},{"p":"ecc-azure-236","l":"multiregion","r":[{"id":"f4820dcd-9aec-4206-9aa3-90fe3cd9ab5e"}],"t":1700043357.7106903},{"p":"ecc-azure-237","l":"westeurope","r":[{"id":"164a529b-88c0-4a80-9dc0-9b7d3908addf"}],"t":1700043357.7106903},{"p":"ecc-azure-237","l":"multiregion","r":[{"id":"164a529b-88c0-4a80-9dc0-9b7d3908addf"}],"t":1700043357.7106903},{"p":"ecc-azure-238","l":"westeurope","r":[{"id":"1ec8f101-72aa-4d34-9cbe-79fbdbbbd7bf"}],"t":1700043357.7106903},{"p":"ecc-azure-238","l":"multiregion","r":[{"id":"1ec8f101-72aa-4d34-9cbe-79fbdbbbd7bf"}],"t":1700043357.7106903},{"p":"ecc-azure-239","l":"westeurope","r":[{"id":"990e1b69-a02d-4acc-9313-50525aa96287"}],"t":1700043357.7106903},{"p":"ecc-azure-239","l":"multiregion","r":[{"id":"990e1b69-a02d-4acc-9313-50525aa96287"}],"t":1700043357.7106903},{"p":"ecc-azure-240","l":"westeurope","r":[{"id":"dda0b459-9af2-4bb8-a507-9879f055de64"}],"t":1700043357.7106903},{"p":"ecc-azure-240","l":"multiregion","r":[{"id":"dda0b459-9af2-4bb8-a507-9879f055de64"}],"t":1700043357.7106903},{"p":"ecc-azure-241","l":"westeurope","r":[{"id":"38ddf3b8-5f3d-43ac-80cc-3a81c4407e1f"}],"t":1700043357.7106903},{"p":"ecc-azure-241","l":"multiregion","r":[{"id":"38ddf3b8-5f3d-43ac-80cc-3a81c4407e1f"}],"t":1700043357.7106903},{"p":"ecc-azure-256","l":"westeurope","r":[{"id":"e8c777a4-0fec-40af-a6b3-0398ffe39b60"}],"t":1700043357.7106903},{"p":"ecc-azure-256","l":"multiregion","r":[{"id":"e8c777a4-0fec-40af-a6b3-0398ffe39b60"}],"t":1700043357.7106903},{"p":"ecc-azure-257","l":"westeurope","r":[{"id":"80288aee-1f2c-481a-a3e8-1878adda6746"}],"t":1700043357.7106903},{"p":"ecc-azure-257","l":"multiregion","r":[{"id":"80288aee-1f2c-481a-a3e8-1878adda6746"}],"t":1700043357.7106903},{"p":"ecc-azure-258","l":"westeurope","r":[{"id":"df6f0c42-e519-42a3-8dc1-56f631027455"}],"t":1700043357.7106903},{"p":"ecc-azure-258","l":"multiregion","r":[{"id":"df6f0c42-e519-42a3-8dc1-56f631027455"}],"t":1700043357.7106903},{"p":"ecc-azure-265","l":"westeurope","r":[{"id":"e37d1f3a-c039-4d63-a040-54b4fc7322a7"}],"t":1700043357.7106903},{"p":"ecc-azure-265","l":"multiregion","r":[{"id":"e37d1f3a-c039-4d63-a040-54b4fc7322a7"}],"t":1700043357.7106903},{"p":"ecc-azure-267","l":"westeurope","r":[{"id":"fcd76500-0cc9-4d8a-ba24-8adf0aa869a7"}],"t":1700043357.7106903},{"p":"ecc-azure-267","l":"multiregion","r":[{"id":"fcd76500-0cc9-4d8a-ba24-8adf0aa869a7"}],"t":1700043357.7106903},{"p":"ecc-azure-270","l":"westeurope","r":[{"id":"8b44a3fe-5f66-4b38-9834-1a6fd4981051"}],"t":1700043357.7106903},{"p":"ecc-azure-270","l":"multiregion","r":[{"id":"8b44a3fe-5f66-4b38-9834-1a6fd4981051"}],"t":1700043357.7106903},{"p":"ecc-azure-272","l":"westeurope","r":[{"id":"38833c82-2bb6-44fe-aaee-e12344444d53"}],"t":1700043357.7106903},{"p":"ecc-azure-272","l":"multiregion","r":[{"id":"38833c82-2bb6-44fe-aaee-e12344444d53"}],"t":1700043357.711692},{"p":"ecc-azure-275","l":"westeurope","r":[{"id":"e9ea3303-142a-4d75-a213-f09649e26f5d"}],"t":1700043357.711692},{"p":"ecc-azure-275","l":"multiregion","r":[{"id":"e9ea3303-142a-4d75-a213-f09649e26f5d"}],"t":1700043357.711692},{"p":"ecc-azure-276","l":"westeurope","r":[{"id":"0ec8eaee-1a0b-4ed8-957b-53273f47cd7e"}],"t":1700043357.711692},{"p":"ecc-azure-276","l":"multiregion","r":[{"id":"0ec8eaee-1a0b-4ed8-957b-53273f47cd7e"}],"t":1700043357.711692},{"p":"ecc-azure-277","l":"westeurope","r":[{"id":"f00ea759-1d5a-403d-bde2-45458e0ba7a0"}],"t":1700043357.711692},{"p":"ecc-azure-277","l":"multiregion","r":[{"id":"f00ea759-1d5a-403d-bde2-45458e0ba7a0"}],"t":1700043357.711692},{"p":"ecc-azure-278","l":"westeurope","r":[{"id":"fcac81d0-238e-4f31-8b11-562487ec5b4b"}],"t":1700043357.711692},{"p":"ecc-azure-278","l":"multiregion","r":[{"id":"fcac81d0-238e-4f31-8b11-562487ec5b4b"}],"t":1700043357.711692},{"p":"ecc-azure-279","l":"westeurope","r":[{"id":"f8fc9d80-f599-426b-b8b3-a3dea2caec65"}],"t":1700043357.711692},{"p":"ecc-azure-279","l":"multiregion","r":[{"id":"f8fc9d80-f599-426b-b8b3-a3dea2caec65"}],"t":1700043357.711692},{"p":"ecc-azure-280","l":"westeurope","r":[{"id":"5fa5d3ed-0c04-44d9-8335-30100148d367"}],"t":1700043357.711692},{"p":"ecc-azure-280","l":"multiregion","r":[{"id":"5fa5d3ed-0c04-44d9-8335-30100148d367"}],"t":1700043357.711692},{"p":"ecc-azure-281","l":"westeurope","r":[{"id":"aa3105fe-b6c0-4436-9402-18a5192b0f37"}],"t":1700043357.711692},{"p":"ecc-azure-281","l":"multiregion","r":[{"id":"aa3105fe-b6c0-4436-9402-18a5192b0f37"}],"t":1700043357.711692},{"p":"ecc-azure-282","l":"westeurope","r":[{"id":"bd15d6c3-a2bd-43c8-aca9-e8892675ff3b"}],"t":1700043357.711692},{"p":"ecc-azure-282","l":"multiregion","r":[{"id":"bd15d6c3-a2bd-43c8-aca9-e8892675ff3b"}],"t":1700043357.711692},{"p":"ecc-azure-283","l":"westeurope","r":[{"id":"039e7fa7-142d-4a61-8fe6-21b14bbd2704"}],"t":1700043357.711692},{"p":"ecc-azure-283","l":"multiregion","r":[{"id":"039e7fa7-142d-4a61-8fe6-21b14bbd2704"}],"t":1700043357.711692},{"p":"ecc-azure-284","l":"westeurope","r":[{"id":"6a136811-bc3b-4d0d-bb4c-d4122f7bcc9e"}],"t":1700043357.711692},{"p":"ecc-azure-284","l":"multiregion","r":[{"id":"6a136811-bc3b-4d0d-bb4c-d4122f7bcc9e"}],"t":1700043357.711692},{"p":"ecc-azure-286","l":"westeurope","r":[{"id":"0cdcc87c-8676-4475-8acc-dafab7595bc1"}],"t":1700043357.711692},{"p":"ecc-azure-286","l":"multiregion","r":[{"id":"0cdcc87c-8676-4475-8acc-dafab7595bc1"}],"t":1700043357.711692},{"p":"ecc-azure-287","l":"westeurope","r":[{"id":"34071817-d356-4e66-a871-cf976951a86d"}],"t":1700043357.711692},{"p":"ecc-azure-287","l":"multiregion","r":[{"id":"34071817-d356-4e66-a871-cf976951a86d"}],"t":1700043357.711692},{"p":"ecc-azure-288","l":"westeurope","r":[{"id":"71e8e48b-f117-4bc2-8f3b-e88fe52c8c20"}],"t":1700043357.711692},{"p":"ecc-azure-288","l":"multiregion","r":[{"id":"71e8e48b-f117-4bc2-8f3b-e88fe52c8c20"}],"t":1700043357.711692},{"p":"ecc-azure-289","l":"westeurope","r":[{"id":"000dab24-b4af-430b-a20b-bc7620e01014"}],"t":1700043357.711692},{"p":"ecc-azure-289","l":"multiregion","r":[{"id":"000dab24-b4af-430b-a20b-bc7620e01014"}],"t":1700043357.711692},{"p":"ecc-azure-290","l":"westeurope","r":[{"id":"82238a46-b9b3-4886-be33-1a1a65005e2b"}],"t":1700043357.711692},{"p":"ecc-azure-290","l":"multiregion","r":[{"id":"82238a46-b9b3-4886-be33-1a1a65005e2b"}],"t":1700043357.711692},{"p":"ecc-azure-291","l":"westeurope","r":[{"id":"a18b840e-6f8c-4f0d-a684-b5b8d2d82883"}],"t":1700043357.711692},{"p":"ecc-azure-291","l":"multiregion","r":[{"id":"a18b840e-6f8c-4f0d-a684-b5b8d2d82883"}],"t":1700043357.711692},{"p":"ecc-azure-293","l":"westeurope","r":[{"id":"15d317d8-8ab0-4fb9-8847-8413b64bd98d"}],"t":1700043357.711692},{"p":"ecc-azure-293","l":"multiregion","r":[{"id":"15d317d8-8ab0-4fb9-8847-8413b64bd98d"}],"t":1700043357.711692},{"p":"ecc-azure-294","l":"westeurope","r":[{"id":"692ff955-ebe4-40c3-bd84-f4d37e6b5e39"}],"t":1700043357.711692},{"p":"ecc-azure-294","l":"multiregion","r":[{"id":"692ff955-ebe4-40c3-bd84-f4d37e6b5e39"}],"t":1700043357.711692},{"p":"ecc-azure-295","l":"westeurope","r":[{"id":"d8d1d977-7ee3-4f2c-92e8-99c411ca3727"}],"t":1700043357.711692},{"p":"ecc-azure-295","l":"multiregion","r":[{"id":"d8d1d977-7ee3-4f2c-92e8-99c411ca3727"}],"t":1700043357.711692},{"p":"ecc-azure-296","l":"westeurope","r":[{"id":"9d9ef700-a9f2-437b-9521-08dcab8faabc"}],"t":1700043357.711692},{"p":"ecc-azure-296","l":"multiregion","r":[{"id":"9d9ef700-a9f2-437b-9521-08dcab8faabc"}],"t":1700043357.711692},{"p":"ecc-azure-298","l":"westeurope","r":[{"id":"100779ee-f742-42bf-94b5-e8dccbadd002"}],"t":1700043357.711692},{"p":"ecc-azure-298","l":"multiregion","r":[{"id":"100779ee-f742-42bf-94b5-e8dccbadd002"}],"t":1700043357.711692},{"p":"ecc-azure-299","l":"westeurope","r":[{"id":"17db3794-253e-4684-955f-4c8965ed12fb"}],"t":1700043357.711692},{"p":"ecc-azure-299","l":"multiregion","r":[{"id":"17db3794-253e-4684-955f-4c8965ed12fb"}],"t":1700043357.711692},{"p":"ecc-azure-300","l":"westeurope","r":[{"id":"9975b06c-5595-4619-8dc0-528ab4a7ccdc"}],"t":1700043357.711692},{"p":"ecc-azure-300","l":"multiregion","r":[{"id":"9975b06c-5595-4619-8dc0-528ab4a7ccdc"}],"t":1700043357.711692},{"p":"ecc-azure-301","l":"westeurope","r":[{"id":"37924b28-e52d-4f9b-b776-61bf3750f6e8"}],"t":1700043357.711692},{"p":"ecc-azure-301","l":"multiregion","r":[{"id":"37924b28-e52d-4f9b-b776-61bf3750f6e8"}],"t":1700043357.711692},{"p":"ecc-azure-302","l":"westeurope","r":[{"id":"1eb0ec7a-a332-4d83-8c04-516c229f36f0"}],"t":1700043357.711692},{"p":"ecc-azure-302","l":"multiregion","r":[{"id":"1eb0ec7a-a332-4d83-8c04-516c229f36f0"}],"t":1700043357.711692},{"p":"ecc-azure-304","l":"westeurope","r":[{"id":"26ed67ad-e32f-4b26-b0c4-8e64c69b64e3"}],"t":1700043357.711692},{"p":"ecc-azure-304","l":"multiregion","r":[{"id":"26ed67ad-e32f-4b26-b0c4-8e64c69b64e3"}],"t":1700043357.711692},{"p":"ecc-azure-305","l":"westeurope","r":[{"id":"c9fb038b-58bd-4584-b97b-0de6c6ecab6e"}],"t":1700043357.711692},{"p":"ecc-azure-305","l":"multiregion","r":[{"id":"c9fb038b-58bd-4584-b97b-0de6c6ecab6e"}],"t":1700043357.711692},{"p":"ecc-azure-306","l":"westeurope","r":[{"id":"fc251352-119b-4c02-9825-91946da2cf39"}],"t":1700043357.711692},{"p":"ecc-azure-306","l":"multiregion","r":[{"id":"fc251352-119b-4c02-9825-91946da2cf39"}],"t":1700043357.711692},{"p":"ecc-azure-310","l":"westeurope","r":[{"id":"93eb5ddc-c8bf-49ca-9cd7-61643cb5a199"}],"t":1700043357.711692},{"p":"ecc-azure-310","l":"multiregion","r":[{"id":"93eb5ddc-c8bf-49ca-9cd7-61643cb5a199"}],"t":1700043357.711692},{"p":"ecc-azure-311","l":"westeurope","r":[{"id":"76ad1442-55d2-4c7c-be07-d688dfae7895"}],"t":1700043357.711692},{"p":"ecc-azure-311","l":"multiregion","r":[{"id":"76ad1442-55d2-4c7c-be07-d688dfae7895"}],"t":1700043357.711692},{"p":"ecc-azure-313","l":"westeurope","r":[{"id":"7f2f4c78-52ee-4103-ba49-7139903746a6"}],"t":1700043357.711692},{"p":"ecc-azure-313","l":"multiregion","r":[{"id":"7f2f4c78-52ee-4103-ba49-7139903746a6"}],"t":1700043357.711692},{"p":"ecc-azure-314","l":"westeurope","r":[{"id":"0f13387f-512d-4e41-b5e7-75e04ec204c0"}],"t":1700043357.711692},{"p":"ecc-azure-314","l":"multiregion","r":[{"id":"0f13387f-512d-4e41-b5e7-75e04ec204c0"}],"t":1700043357.711692},{"p":"ecc-azure-317","l":"westeurope","r":[{"id":"cb74c8a3-e0fe-4afb-a071-c75ed5aad896"}],"t":1700043357.711692},{"p":"ecc-azure-317","l":"multiregion","r":[{"id":"cb74c8a3-e0fe-4afb-a071-c75ed5aad896"}],"t":1700043357.711692},{"p":"ecc-azure-318","l":"westeurope","r":[{"id":"d9e3b979-56df-457c-a8c4-469f5b28c37f"}],"t":1700043357.711692},{"p":"ecc-azure-318","l":"multiregion","r":[{"id":"d9e3b979-56df-457c-a8c4-469f5b28c37f"}],"t":1700043357.711692},{"p":"ecc-azure-319","l":"westeurope","r":[{"id":"24343122-47c1-4943-8835-7598a38a619e"}],"t":1700043357.711692},{"p":"ecc-azure-319","l":"multiregion","r":[{"id":"24343122-47c1-4943-8835-7598a38a619e"}],"t":1700043357.711692},{"p":"ecc-azure-321","l":"westeurope","r":[{"id":"25cc3706-b33e-46ed-bede-bae86ba61823"}],"t":1700043357.711692},{"p":"ecc-azure-321","l":"multiregion","r":[{"id":"25cc3706-b33e-46ed-bede-bae86ba61823"}],"t":1700043357.711692},{"p":"ecc-azure-323","l":"westeurope","r":[{"id":"9a1e3eb1-4f77-462e-aabc-482430f9c1d1"}],"t":1700043357.711692},{"p":"ecc-azure-323","l":"multiregion","r":[{"id":"9a1e3eb1-4f77-462e-aabc-482430f9c1d1"}],"t":1700043357.711692},{"p":"ecc-azure-324","l":"westeurope","r":[{"id":"049ba1e8-11ff-49d9-854d-886d9dd8eaa9"}],"t":1700043357.711692},{"p":"ecc-azure-324","l":"multiregion","r":[{"id":"049ba1e8-11ff-49d9-854d-886d9dd8eaa9"}],"t":1700043357.711692},{"p":"ecc-azure-325","l":"westeurope","r":[{"id":"f49f8b0d-1e70-4b08-a892-97dbc86b7146"}],"t":1700043357.711692},{"p":"ecc-azure-325","l":"multiregion","r":[{"id":"f49f8b0d-1e70-4b08-a892-97dbc86b7146"}],"t":1700043357.711692},{"p":"ecc-azure-326","l":"westeurope","r":[{"id":"65e0eb4c-07f8-46bc-90a8-2b756a82494b"}],"t":1700043357.711692},{"p":"ecc-azure-326","l":"multiregion","r":[{"id":"65e0eb4c-07f8-46bc-90a8-2b756a82494b"}],"t":1700043357.711692},{"p":"ecc-azure-327","l":"westeurope","r":[{"id":"06ecbbd4-1b94-4216-b062-2b276cc6c27b"}],"t":1700043357.711692},{"p":"ecc-azure-327","l":"multiregion","r":[{"id":"06ecbbd4-1b94-4216-b062-2b276cc6c27b"}],"t":1700043357.711692},{"p":"ecc-azure-328","l":"westeurope","r":[{"id":"289417d4-3b1a-4571-bbb6-6034eb00a18e"}],"t":1700043357.711692},{"p":"ecc-azure-328","l":"multiregion","r":[{"id":"289417d4-3b1a-4571-bbb6-6034eb00a18e"}],"t":1700043357.711692},{"p":"ecc-azure-329","l":"westeurope","r":[{"id":"38e07afa-b8e1-47e2-83a0-17723f4d42dc"}],"t":1700043357.711692},{"p":"ecc-azure-329","l":"multiregion","r":[{"id":"38e07afa-b8e1-47e2-83a0-17723f4d42dc"}],"t":1700043357.711692},{"p":"ecc-azure-331","l":"westeurope","r":[{"id":"10d793db-9c76-42a2-ac12-2ea89b3b96a0"}],"t":1700043357.711692},{"p":"ecc-azure-331","l":"multiregion","r":[{"id":"10d793db-9c76-42a2-ac12-2ea89b3b96a0"}],"t":1700043357.711692},{"p":"ecc-azure-332","l":"westeurope","r":[{"id":"8e374e0c-39d5-4c86-97d1-9bde876f4295"}],"t":1700043357.711692},{"p":"ecc-azure-332","l":"multiregion","r":[{"id":"8e374e0c-39d5-4c86-97d1-9bde876f4295"}],"t":1700043357.711692},{"p":"ecc-azure-333","l":"westeurope","r":[{"id":"f0bda1f4-1288-4f39-91f6-b59d379d4576"}],"t":1700043357.711692},{"p":"ecc-azure-333","l":"multiregion","r":[{"id":"f0bda1f4-1288-4f39-91f6-b59d379d4576"}],"t":1700043357.711692},{"p":"ecc-azure-334","l":"westeurope","r":[{"id":"3d57da42-1085-4ff6-987a-671f8b5cf143"}],"t":1700043357.711692},{"p":"ecc-azure-334","l":"multiregion","r":[{"id":"3d57da42-1085-4ff6-987a-671f8b5cf143"}],"t":1700043357.711692},{"p":"ecc-azure-336","l":"westeurope","r":[{"id":"e12f78b8-8757-435d-8f89-ebb2e709f66b"}],"t":1700043357.711692},{"p":"ecc-azure-336","l":"multiregion","r":[{"id":"e12f78b8-8757-435d-8f89-ebb2e709f66b"}],"t":1700043357.711692},{"p":"ecc-azure-337","l":"westeurope","r":[{"id":"40714622-b8e4-4964-9edf-11d12edfb797"}],"t":1700043357.711692},{"p":"ecc-azure-337","l":"multiregion","r":[{"id":"40714622-b8e4-4964-9edf-11d12edfb797"}],"t":1700043357.711692},{"p":"ecc-azure-339","l":"westeurope","r":[{"id":"643be613-4af7-4782-8094-632657b2aa87"}],"t":1700043357.711692},{"p":"ecc-azure-339","l":"multiregion","r":[{"id":"643be613-4af7-4782-8094-632657b2aa87"}],"t":1700043357.711692},{"p":"ecc-azure-340","l":"westeurope","r":[{"id":"9a1d0684-c58d-448d-ac24-3a088028fc0a"}],"t":1700043357.711692},{"p":"ecc-azure-340","l":"multiregion","r":[{"id":"9a1d0684-c58d-448d-ac24-3a088028fc0a"}],"t":1700043357.711692},{"p":"ecc-azure-341","l":"westeurope","r":[{"id":"a824151f-8ccd-4700-973c-0da58c0f0fc7"}],"t":1700043357.711692},{"p":"ecc-azure-341","l":"multiregion","r":[{"id":"a824151f-8ccd-4700-973c-0da58c0f0fc7"}],"t":1700043357.711692},{"p":"ecc-azure-342","l":"westeurope","r":[{"id":"d672ba84-80fe-4d7f-b1c9-35106629dac3"}],"t":1700043357.711692},{"p":"ecc-azure-342","l":"multiregion","r":[{"id":"d672ba84-80fe-4d7f-b1c9-35106629dac3"}],"t":1700043357.711692},{"p":"ecc-azure-343","l":"westeurope","r":[{"id":"345a425f-7e92-4648-b579-3c2695897d98"}],"t":1700043357.711692},{"p":"ecc-azure-343","l":"multiregion","r":[{"id":"345a425f-7e92-4648-b579-3c2695897d98"}],"t":1700043357.711692},{"p":"ecc-azure-344","l":"westeurope","r":[{"id":"a571a66e-82ab-43c1-a2bf-74d18958d6f3"}],"t":1700043357.711692},{"p":"ecc-azure-344","l":"multiregion","r":[{"id":"a571a66e-82ab-43c1-a2bf-74d18958d6f3"}],"t":1700043357.711692},{"p":"ecc-azure-345","l":"westeurope","r":[{"id":"89f6beae-530d-4363-8745-3dcaf00e0d4b"}],"t":1700043357.711692},{"p":"ecc-azure-345","l":"multiregion","r":[{"id":"89f6beae-530d-4363-8745-3dcaf00e0d4b"}],"t":1700043357.711692},{"p":"ecc-azure-346","l":"westeurope","r":[{"id":"0cf8983b-f67a-4919-a4f2-ce996a9adc48"}],"t":1700043357.711692},{"p":"ecc-azure-346","l":"multiregion","r":[{"id":"0cf8983b-f67a-4919-a4f2-ce996a9adc48"}],"t":1700043357.711692},{"p":"ecc-azure-347","l":"westeurope","r":[{"id":"b19a8ec8-7592-47a9-8676-10625747ccea"}],"t":1700043357.711692},{"p":"ecc-azure-347","l":"multiregion","r":[{"id":"b19a8ec8-7592-47a9-8676-10625747ccea"}],"t":1700043357.711692},{"p":"ecc-azure-348","l":"westeurope","r":[{"id":"009474fc-cbe1-40f5-8440-579eb3c3d74b"}],"t":1700043357.711692},{"p":"ecc-azure-348","l":"multiregion","r":[{"id":"009474fc-cbe1-40f5-8440-579eb3c3d74b"}],"t":1700043357.711692},{"p":"ecc-azure-349","l":"westeurope","r":[{"id":"57982ada-1a5c-438a-b23b-ed6e40c48c6b"}],"t":1700043357.711692},{"p":"ecc-azure-349","l":"multiregion","r":[{"id":"57982ada-1a5c-438a-b23b-ed6e40c48c6b"}],"t":1700043357.711692},{"p":"ecc-azure-350","l":"westeurope","r":[{"id":"6f540f5f-68ec-48b9-a484-7e2b4d431e85"}],"t":1700043357.711692},{"p":"ecc-azure-350","l":"multiregion","r":[{"id":"6f540f5f-68ec-48b9-a484-7e2b4d431e85"}],"t":1700043357.711692},{"p":"ecc-azure-351","l":"westeurope","r":[{"id":"08bf74e2-14e5-4e2c-8db0-cc03e04e735b"}],"t":1700043357.711692},{"p":"ecc-azure-351","l":"multiregion","r":[{"id":"08bf74e2-14e5-4e2c-8db0-cc03e04e735b"}],"t":1700043357.711692},{"p":"ecc-azure-353","l":"westeurope","r":[{"id":"d0dfbd46-74d3-4979-9eb1-f3462f5d90c8"}],"t":1700043357.711692},{"p":"ecc-azure-353","l":"multiregion","r":[{"id":"d0dfbd46-74d3-4979-9eb1-f3462f5d90c8"}],"t":1700043357.711692},{"p":"ecc-azure-354","l":"westeurope","r":[{"id":"3a143807-199c-4dac-a4fd-1adcf3323660"}],"t":1700043357.711692},{"p":"ecc-azure-354","l":"multiregion","r":[{"id":"3a143807-199c-4dac-a4fd-1adcf3323660"}],"t":1700043357.711692},{"p":"ecc-azure-355","l":"westeurope","r":[{"id":"0141b6ff-5963-4c1d-a988-64f7c229b666"}],"t":1700043357.711692},{"p":"ecc-azure-355","l":"multiregion","r":[{"id":"0141b6ff-5963-4c1d-a988-64f7c229b666"}],"t":1700043357.711692},{"p":"ecc-azure-356","l":"westeurope","r":[{"id":"08de3dae-8de1-42f4-8f5b-fed7da6617d6"}],"t":1700043357.711692},{"p":"ecc-azure-356","l":"multiregion","r":[{"id":"08de3dae-8de1-42f4-8f5b-fed7da6617d6"}],"t":1700043357.711692},{"p":"ecc-azure-357","l":"westeurope","r":[{"id":"b2f70bba-a58c-4bda-ba9c-a6b98ca85405"}],"t":1700043357.711692},{"p":"ecc-azure-357","l":"multiregion","r":[{"id":"b2f70bba-a58c-4bda-ba9c-a6b98ca85405"}],"t":1700043357.711692},{"p":"ecc-azure-358","l":"westeurope","r":[{"id":"c09cbb08-8f1b-48f4-9b35-8dfe4e86efcd"}],"t":1700043357.711692},{"p":"ecc-azure-358","l":"multiregion","r":[{"id":"c09cbb08-8f1b-48f4-9b35-8dfe4e86efcd"}],"t":1700043357.711692},{"p":"ecc-azure-359","l":"westeurope","r":[{"id":"d17d0d46-e463-471a-bfe2-1179b5383997"}],"t":1700043357.711692},{"p":"ecc-azure-359","l":"multiregion","r":[{"id":"d17d0d46-e463-471a-bfe2-1179b5383997"}],"t":1700043357.711692},{"p":"ecc-azure-362","l":"westeurope","r":[{"id":"45eeaa3f-d4a8-48c8-be5d-25883b5dbc9a"}],"t":1700043357.711692},{"p":"ecc-azure-362","l":"multiregion","r":[{"id":"45eeaa3f-d4a8-48c8-be5d-25883b5dbc9a"}],"t":1700043357.711692},{"p":"ecc-azure-364","l":"westeurope","r":[{"id":"2cc29a1a-6995-483d-9a93-e3e49d6e9e61"}],"t":1700043357.711692},{"p":"ecc-azure-364","l":"multiregion","r":[{"id":"2cc29a1a-6995-483d-9a93-e3e49d6e9e61"}],"t":1700043357.711692},{"p":"ecc-azure-365","l":"westeurope","r":[{"id":"3b53e844-d26c-486c-9a38-898c7d426a3d"}],"t":1700043357.711692},{"p":"ecc-azure-365","l":"multiregion","r":[{"id":"3b53e844-d26c-486c-9a38-898c7d426a3d"}],"t":1700043357.711692},{"p":"ecc-azure-367","l":"westeurope","r":[{"id":"16f8ae09-7bcd-4ab0-9d29-a3ca1998de0e"}],"t":1700043357.711692},{"p":"ecc-azure-367","l":"multiregion","r":[{"id":"16f8ae09-7bcd-4ab0-9d29-a3ca1998de0e"}],"t":1700043357.711692},{"p":"ecc-azure-368","l":"westeurope","r":[{"id":"052b2da0-6f94-4872-afce-b9feb31102f0"}],"t":1700043357.711692},{"p":"ecc-azure-368","l":"multiregion","r":[{"id":"052b2da0-6f94-4872-afce-b9feb31102f0"}],"t":1700043357.711692},{"p":"ecc-azure-369","l":"westeurope","r":[{"id":"1fa2470c-ca29-40f4-9f19-e27290a32e34"}],"t":1700043357.711692},{"p":"ecc-azure-369","l":"multiregion","r":[{"id":"1fa2470c-ca29-40f4-9f19-e27290a32e34"}],"t":1700043357.711692},{"p":"ecc-azure-370","l":"westeurope","r":[{"id":"31a25d84-46d4-4e07-8d83-af2ce28a755f"}],"t":1700043357.711692},{"p":"ecc-azure-370","l":"multiregion","r":[{"id":"31a25d84-46d4-4e07-8d83-af2ce28a755f"}],"t":1700043357.711692},{"p":"ecc-azure-371","l":"westeurope","r":[{"id":"c5aabc5f-f014-419d-ad70-fe009a42876a"}],"t":1700043357.711692},{"p":"ecc-azure-371","l":"multiregion","r":[{"id":"c5aabc5f-f014-419d-ad70-fe009a42876a"}],"t":1700043357.711692},{"p":"ecc-azure-372","l":"westeurope","r":[{"id":"3266e8a9-97e3-4ff0-ad7a-a37ff5a90f52"}],"t":1700043357.711692},{"p":"ecc-azure-372","l":"multiregion","r":[{"id":"3266e8a9-97e3-4ff0-ad7a-a37ff5a90f52"}],"t":1700043357.711692},{"p":"ecc-azure-373","l":"westeurope","r":[{"id":"e54f2b9b-a180-49f6-95fd-db70ab9bcec6"}],"t":1700043357.711692},{"p":"ecc-azure-373","l":"multiregion","r":[{"id":"e54f2b9b-a180-49f6-95fd-db70ab9bcec6"}],"t":1700043357.711692},{"p":"ecc-azure-374","l":"westeurope","r":[{"id":"292ed1d9-6fb8-4f79-87e1-72ca707ea4f7"}],"t":1700043357.711692},{"p":"ecc-azure-374","l":"multiregion","r":[{"id":"292ed1d9-6fb8-4f79-87e1-72ca707ea4f7"}],"t":1700043357.711692},{"p":"ecc-azure-378","l":"westeurope","r":[{"id":"dc1b42b9-9ee9-4dd0-8514-c25e748035a5"}],"t":1700043357.711692},{"p":"ecc-azure-378","l":"multiregion","r":[{"id":"dc1b42b9-9ee9-4dd0-8514-c25e748035a5"}],"t":1700043357.711692},{"p":"ecc-azure-379","l":"westeurope","r":[{"id":"7bd18c7f-21da-4951-810b-96f8d2925dd7"}],"t":1700043357.711692},{"p":"ecc-azure-379","l":"multiregion","r":[{"id":"7bd18c7f-21da-4951-810b-96f8d2925dd7"}],"t":1700043357.711692}] \ No newline at end of file diff --git a/tests/tests_metrics/mock_files/reports/raw/EPAM Systems/AZURE/AZURE-1234567890123/latest/meta.json b/tests/tests_metrics/mock_files/reports/raw/EPAM Systems/AZURE/AZURE-1234567890123/latest/meta.json new file mode 100644 index 000000000..e774bce92 --- /dev/null +++ b/tests/tests_metrics/mock_files/reports/raw/EPAM Systems/AZURE/AZURE-1234567890123/latest/meta.json @@ -0,0 +1 @@ +{"ecc-azure-002":{"description":"Custom role with Owner privileges on a subscription scope is created\n","resource":"azure.roledefinition"},"ecc-azure-004":{"description":"Automatic provisioning is set to \"Off\" in Security Center (Microsoft Defender for Cloud)\n","resource":"azure.defender-autoprovisioning"},"ecc-azure-005":{"description":"'Additional email addresses' is not configured in Microsoft Defender for Cloud\n","resource":"azure.security-contacts"},"ecc-azure-006":{"description":"Notification alerts are disabled in Security Center (Microsoft Defender for Cloud)\n","resource":"azure.security-contacts"},"ecc-azure-007":{"description":"Notification alerts to admins or subscription owners are disabled in Microsoft Defender for Cloud\n","resource":"azure.security-contacts"},"ecc-azure-008":{"description":"Storage account that allows http traffic\n","resource":"azure.storage"},"ecc-azure-009":{"description":"Storage Account with publicly accessed blobs\n","resource":"azure.storage"},"ecc-azure-010":{"description":"Storage Account accepted connections from public network\n","resource":"azure.storage"},"ecc-azure-011":{"description":"Soft delete for Azure Storage Blobs is disabled\n","resource":"azure.storage"},"ecc-azure-012":{"description":"Azure Storage account data is encrypted with Microsoft Managed Key\n","resource":"azure.storage"},"ecc-azure-013":{"description":"Azure SQL Database Auditing is set to \"Off\"\n","resource":"azure.sql-auditing-settings"},"ecc-azure-014":{"description":"Transparent Data Encryption is disabled on SQL Database\n","resource":"azure.sql-database"},"ecc-azure-015":{"description":"Azure SQL Database Auditing retention policy set to less than 90 days\n","resource":"azure.sql-auditing-settings"},"ecc-azure-016":{"description":"Advanced Threat Protection is disabled on SQL server\n","resource":"azure.sql-server"},"ecc-azure-020":{"description":"Azure SQL Vulnerability Assessment is disabled or storage account property is not configured\n","resource":"azure.sql-server"},"ecc-azure-021":{"description":"Azure SQL Vulnerability Assessment Reccuring Scans is disabled","resource":"azure.sql-server"},"ecc-azure-022":{"description":"Azure SQL Vulnerability Assessment setting 'Send scan reports to' is not configured","resource":"azure.sql-server"},"ecc-azure-023":{"description":"Azure SQL Vulnerability Assessment emailing to admins and subscription owners is not configured","resource":"azure.sql-server"},"ecc-azure-024":{"description":"SSL connection is disabled on PostgreSQL servers\n","resource":"azure.postgresql-server"},"ecc-azure-025":{"description":"SSL connection is disabled on MySQL servers\n","resource":"azure.mysql-server"},"ecc-azure-026":{"description":"PostgreSQL instance with server parameter 'log_checkpoints' disabled\n","resource":"azure.postgresql-server"},"ecc-azure-027":{"description":"PostgreSQL instance with server parameter 'log_connections' disabled\n","resource":"azure.postgresql-server"},"ecc-azure-028":{"description":"PostgreSQL instance with server parameter 'log_disconnections' disabled\n","resource":"azure.postgresql-server"},"ecc-azure-030":{"description":"PostgreSQL instance with server parameter 'connection_throttling' disabled\n","resource":"azure.postgresql-server"},"ecc-azure-031":{"description":"PostgreSQL instance with server parameter 'log_retention_days' is set to less than 4 days\n","resource":"azure.postgresql-server"},"ecc-azure-032":{"description":"Azure Active Directory admin is not configured for Azure SQL\n","resource":"azure.sql-server"},"ecc-azure-033":{"description":"Transparent Data Encryption protector is not encrypted with Customer Managed key\n","resource":"azure.sql-server"},"ecc-azure-036":{"description":"Storage container that stores activity logs where public access level set to \"Blob\" or \"Container\"\n","resource":"azure.storage-container"},"ecc-azure-037":{"description":"Storage account that contains a container with activity logs not encrypted with Customer Managed Key\n","resource":"azure.storage"},"ecc-azure-038":{"description":"Key Vault with logging and retention policy disabled\n","resource":"azure.keyvault-by-subscription"},"ecc-azure-039":{"description":"Subscription where Activity Log Alert does not exist for Create Policy Assignment\n","resource":"azure.subscription"},"ecc-azure-042":{"description":"Subscription does not contain Activity Log Alert with appropriate scope for Create or Update Network Security Group Rule\n","resource":"azure.subscription"},"ecc-azure-043":{"description":"Subscription does not contain Activity Log Alert with appropriate scope for Delete Network Security Group Rule\n","resource":"azure.subscription"},"ecc-azure-044":{"description":"Subscription does not contain Activity Log Alert with appropriate scope for Create or Update Security Solution\n","resource":"azure.subscription"},"ecc-azure-045":{"description":"Subscription does not contain Activity Log Alert with appropriate scope for Delete Security Solution\n","resource":"azure.subscription"},"ecc-azure-046":{"description":"Subscription does not contain Activity Log Alert with appropriate scope for Create or Update or Delete SQL Server Firewall Rule\n","resource":"azure.subscription"},"ecc-azure-048":{"description":"Network Security Group with inbound rule that allows RDP traffic from the Internet\n","resource":"azure.networksecuritygroup"},"ecc-azure-049":{"description":"Network Security Group with inbound rule that allows SSH traffic from the Internet\n","resource":"azure.networksecuritygroup"},"ecc-azure-050":{"description":"SQL instances accessible from the Internet or Azure services\n","resource":"azure.sql-server"},"ecc-azure-052":{"description":"Network Security Group with inbound rule that allows UDP traffic from the Internet\n","resource":"azure.networksecuritygroup"},"ecc-azure-053":{"description":"Managed disk attached to a VM that is not encrypted with Customer Managed Key\n","resource":"azure.disk"},"ecc-azure-054":{"description":"Unattached managed disks not encrypted with Customer Managed Key\n","resource":"azure.disk"},"ecc-azure-055":{"description":"Key without expiration date set\n","resource":"azure.keyvault-key"},"ecc-azure-056":{"description":"Secret without expiration date set\n","resource":"azure.keyvault-secret"},"ecc-azure-057":{"description":"Key vault without Soft Delete or Purge Protection enabled\n","resource":"azure.keyvault-by-subscription"},"ecc-azure-058":{"description":"Kubernetes cluster without RBAC enabled\n","resource":"azure.aks"},"ecc-azure-059":{"description":"App Service without App Service Authentication enabled\n","resource":"azure.webapp-auth-settings"},"ecc-azure-060":{"description":"App Service that allows http traffic\n","resource":"azure.webapp"},"ecc-azure-061":{"description":"App Service that uses TLS version before 1.2\n","resource":"azure.webapp"},"ecc-azure-064":{"description":"App Service that allows FTP deployments\n","resource":"azure.webapp"},"ecc-azure-065":{"description":"App Service without HTTP 2.0 is enabled\n","resource":"azure.webapp"},"ecc-azure-066":{"description":"Subscription does not contain Activity Log Alert with appropriate scope for Delete Policy Assignment\n","resource":"azure.subscription"},"ecc-azure-067":{"description":"Subscription does not contain Activity Log Alert with appropriate scope for Create or Update Network Security Group Rule (securityRules)\n","resource":"azure.subscription"},"ecc-azure-068":{"description":"Subscription does not contain Activity Log Alert with appropriate scope for the Delete Network Security Group Rule\n","resource":"azure.subscription"},"ecc-azure-069":{"description":"App Service with outdated Java version\n","resource":"azure.webapp"},"ecc-azure-070":{"description":"App Service with outdated Python version\n","resource":"azure.webapp"},"ecc-azure-071":{"description":"App Service with outdated PHP version\n","resource":"azure.webapp"},"ecc-azure-072":{"description":"Azure Web App without Key Vault reference configured","resource":"azure.webapp"},"ecc-azure-094":{"description":"Azure Defender for Servers is set to \"Off\"\n","resource":"azure.defender-pricing"},"ecc-azure-095":{"description":"Azure Defender for App Service is set to \"Off\"\n","resource":"azure.defender-pricing"},"ecc-azure-096":{"description":"Azure Defender for SQL database servers is set to \"Off\"\n","resource":"azure.defender-pricing"},"ecc-azure-097":{"description":"Azure Defender for SQL servers on machines is set to \"Off\"\n","resource":"azure.defender-pricing"},"ecc-azure-098":{"description":"Azure Defender for Storage is set to \"Off\"\n","resource":"azure.defender-pricing"},"ecc-azure-099":{"description":"Azure Defender for Kubernetes is set to \"Off\"\n","resource":"azure.defender-pricing"},"ecc-azure-100":{"description":"Azure Defender for Container Registries is set to \"Off\"\n","resource":"azure.defender-pricing"},"ecc-azure-101":{"description":"Azure Defender for Key Vault is set to \"Off\"\n","resource":"azure.defender-pricing"},"ecc-azure-102":{"description":"WDATP integration is disabled in Microsoft Defender for Cloud\n","resource":"azure.defender-setting"},"ecc-azure-103":{"description":"MCAS integration is disabled in Security Center (Microsoft Defender for Cloud)\n","resource":"azure.defender-setting"},"ecc-azure-105":{"description":"Storage account without recently regenerated access keys\n","resource":"azure.storage"},"ecc-azure-106":{"description":"Storage account without logging enabled for Queues\n","resource":"azure.storage"},"ecc-azure-108":{"description":"Storage account without access from/to \"Trusted Microsoft Services\"\n","resource":"azure.storage"},"ecc-azure-109":{"description":"Storage account without logging enabled for Blobs\n","resource":"azure.storage"},"ecc-azure-110":{"description":"Storage account without logging enabled for Tables\n","resource":"azure.storage"},"ecc-azure-111":{"description":"PostgreSQL Database Server with 'Allow access to Azure services' enabled\n","resource":"azure.postgresql-server"},"ecc-azure-112":{"description":"Network Watcher is disabled across the subscription\n","resource":"azure.subscription"},"ecc-azure-113":{"description":"Virtual machine that utilizes unmanaged disks\n","resource":"azure.vm"},"ecc-azure-116":{"description":"Virtual machine without endpoint protection installed\n","resource":"azure.vm"},"ecc-azure-117":{"description":"[Legacy] Virtual machine utilizes unmanaged disks without encryption\n","resource":"azure.vm"},"ecc-azure-119":{"description":"Network Security Group with inbound rule that allows all traffic from the Internet\n","resource":"azure.networksecuritygroup"},"ecc-azure-120":{"description":"Network Security Group with inbound rule that allows DNS traffic from the Internet\n","resource":"azure.networksecuritygroup"},"ecc-azure-121":{"description":"Network Security Group with inbound rule that allows FTP traffic from the Internet\n","resource":"azure.networksecuritygroup"},"ecc-azure-122":{"description":"Network Security Group with inbound rule that allows HTTP traffic from the Internet\n","resource":"azure.networksecuritygroup"},"ecc-azure-123":{"description":"Network Security Group with inbound rule that allows SMB traffic from the Internet\n","resource":"azure.networksecuritygroup"},"ecc-azure-124":{"description":"Network Security Group with inbound rule that allows MySQL traffic from the Internet\n","resource":"azure.networksecuritygroup"},"ecc-azure-125":{"description":"Network Security Group with inbound rule that allows MongoDB traffic from the Internet\n","resource":"azure.networksecuritygroup"},"ecc-azure-126":{"description":"Network Security Group with inbound rule that allows NetBIOS traffic from the Internet\n","resource":"azure.networksecuritygroup"},"ecc-azure-127":{"description":"Network Security Group with inbound rule that allows OracleDB traffic from the Internet\n","resource":"azure.networksecuritygroup"},"ecc-azure-128":{"description":"Network Security Group with inbound rule that allows POP3 traffic from the Internet\n","resource":"azure.networksecuritygroup"},"ecc-azure-129":{"description":"Network Security Group with inbound rule that allows PostgreSQL traffic from the Internet\n","resource":"azure.networksecuritygroup"},"ecc-azure-130":{"description":"Network Security Group with inbound rule that allows SMTP traffic from the Internet\n","resource":"azure.networksecuritygroup"},"ecc-azure-131":{"description":"Network Security Group with inbound rule that allows Telnet traffic from the Internet\n","resource":"azure.networksecuritygroup"},"ecc-azure-132":{"description":"Instance without deletion protection\n","resource":"azure.vm"},"ecc-azure-133":{"description":"Instance Without Any Tags\n","resource":"azure.vm"},"ecc-azure-137":{"description":"Storage account without replication enabled\n","resource":"azure.storage"},"ecc-azure-139":{"description":"Disk without recent snapshots taken in the last 14 days\n","resource":"azure.disk"},"ecc-azure-141":{"description":"Virtual network with network interface assigned to virtual machine where firewall subnet resides and no route tables configured\n","resource":"azure.vnet"},"ecc-azure-142":{"description":"Network Security Group assigned to network interface or subnet with inbound rule that allows all traffic from the Internet\n","resource":"azure.networksecuritygroup"},"ecc-azure-143":{"description":"API Management service without virtual network configured\n","resource":"azure.api-management"},"ecc-azure-144":{"description":"Kubernetes cluster without authorized IP access or/and exposed to the public Internet\n","resource":"azure.aks"},"ecc-azure-145":{"description":"Cosmos DB accounts without firewall rules\n","resource":"azure.cosmosdb"},"ecc-azure-146":{"description":"Key Vault with enabled public access\n","resource":"azure.keyvault-by-subscription"},"ecc-azure-147":{"description":"Cognitive service with enabled public access\n","resource":"azure.cognitiveservice"},"ecc-azure-148":{"description":"Cognitive service with defaultAction set to \"Allow\"\n","resource":"azure.cognitiveservice"},"ecc-azure-149":{"description":"Azure Container Registry which accepts connections over the Internet from hosts on any network.\n","resource":"azure.containerregistry"},"ecc-azure-150":{"description":"Primary virtual machine network interface with public ip assigned without Network Security Group assignment\n","resource":"azure.networkinterface"},"ecc-azure-151":{"description":"Virtual machine network interface with IP Forwarding enabled\n","resource":"azure.networkinterface"},"ecc-azure-152":{"description":"VM without JIT policy enabled for SSH or RDP ports\n","resource":"azure.vm"},"ecc-azure-155":{"description":"Azure SQL instance with public access enabled\n","resource":"azure.sql-server"},"ecc-azure-156":{"description":"MariaDB instance with public access enabled\n","resource":"azure.mariadb-server"},"ecc-azure-157":{"description":"MySQL instance with public access enabled\n","resource":"azure.mysql-server"},"ecc-azure-158":{"description":"PostgreSQL instance with public access enabled\n","resource":"azure.postgresql-server"},"ecc-azure-159":{"description":"Storage accounts without virtual network IP rules\n","resource":"azure.storage"},"ecc-azure-160":{"description":"Virtual network with network security groups not assigned to subnets\n","resource":"azure.vnet"},"ecc-azure-161":{"description":"App Configuration service without Private Endpoint connection configured\n","resource":"azure.app-configuration"},"ecc-azure-162":{"description":"Redis cache that does not reside in a subnet\n","resource":"azure.redis"},"ecc-azure-163":{"description":"Event Grid Domains service without Private Endpoint connection configured\n","resource":"azure.event-grid-domain"},"ecc-azure-164":{"description":"Event Grid Topics service without Private Endpoint connection configured\n","resource":"azure.event-grid-topic"},"ecc-azure-165":{"description":"Machine Learning workspace without Private Endpoint connection configured\n","resource":"azure.machine-learning-workspace"},"ecc-azure-166":{"description":"SignalR service without Private Endpoint connection configured\n","resource":"azure.signalr"},"ecc-azure-167":{"description":"Spring Cloud service without runtime subnet configured\n","resource":"azure.spring-cloud"},"ecc-azure-168":{"description":"Container Registry without Private Endpoint connection configured\n","resource":"azure.container-registry"},"ecc-azure-170":{"description":"Key Vault without Private Endpoint connection configured\n","resource":"azure.keyvault-by-subscription"},"ecc-azure-171":{"description":"MariaDB instance without Private Endpoint connection configured\n","resource":"azure.mariadb-server"},"ecc-azure-172":{"description":"MySQL instance without Private Endpoint connection configured\n","resource":"azure.mysql-server"},"ecc-azure-173":{"description":"PostgreSQL instance without Private Endpoint connection configured\n","resource":"azure.postgresql-server"},"ecc-azure-174":{"description":"Storage Account without Private Endpoint connection configured\n","resource":"azure.storage"},"ecc-azure-176":{"description":"Virtual network without DDoS protection enabled which contains application gateway subnet\n","resource":"azure.vnet"},"ecc-azure-177":{"description":"Application Gateway without Web Application Firewall enabled\n","resource":"azure.application-gateway"},"ecc-azure-178":{"description":"Azure Front Door service without Web Application Firewall enabled\n","resource":"azure.front-door"},"ecc-azure-179":{"description":"API app without Managed identity configured (both SystemAssigned and UserAssigned)\n","resource":"azure.webapp"},"ecc-azure-180":{"description":"Function app without Managed identity configured (both SystemAssigned and UserAssigned)\n","resource":"azure.webapp"},"ecc-azure-181":{"description":"Web app without Managed identity configured (both SystemAssigned and UserAssigned)\n","resource":"azure.webapp"},"ecc-azure-182":{"description":"Service Frabric clusters without AAD client authentication\n","resource":"azure.service-fabric-cluster"},"ecc-azure-184":{"description":"Linux virtual machine without SSH authentication method as primary configured (Allows password authentication)\n","resource":"azure.vm"},"ecc-azure-196":{"description":"Advanced Threat Protection is disabled on SQL managed instance\n","resource":"azure.sql-managed-instance"},"ecc-azure-197":{"description":"Virtual machine without Azure Disk Encryption configured\n","resource":"azure.vm"},"ecc-azure-199":{"description":"SSL connection is disabled on Redis Cache\n","resource":"azure.redis"},"ecc-azure-200":{"description":"Automation account with unencrypted variable\n","resource":"azure.automation-account"},"ecc-azure-201":{"description":"Cosmos DB accounts without CMK encryption configured\n","resource":"azure.cosmosdb"},"ecc-azure-202":{"description":"Machine Learning workspace without CMK encryption configured\n","resource":"azure.machine-learning-workspace"},"ecc-azure-203":{"description":"PostgreSQL instance without CMK encryption configured\n","resource":"azure.postgresql-server"},"ecc-azure-204":{"description":"Cognitive Services without CMK encryption configured\n","resource":"azure.cognitiveservice"},"ecc-azure-205":{"description":"Container Registry without CMK encryption configured\n","resource":"azure.container-registry"},"ecc-azure-206":{"description":"Service Fabric cluster without configured ClusterProtectionLevel property set to EncryptAndSign\n","resource":"azure.service-fabric-cluster"},"ecc-azure-207":{"description":"SQL managed instance without CMK encryption configured\n","resource":"azure.sql-managed-instance"},"ecc-azure-213":{"description":"Azure Defender for DNS is set to \"Off\"\n","resource":"azure.defender-pricing"},"ecc-azure-214":{"description":"Azure Defender for Resource Manager is set to \"Off\"\n","resource":"azure.defender-pricing"},"ecc-azure-215":{"description":"Linux virtual machines without Dependency Agent installed\n","resource":"azure.vm"},"ecc-azure-216":{"description":"Windows virtual machines without Dependency Agent installed\n","resource":"azure.vm"},"ecc-azure-217":{"description":"Data Lake Store with logging and retention policy disabled\n","resource":"azure.datalake"},"ecc-azure-218":{"description":"Azure Stream with logging and retention policy disabled\n","resource":"azure.stream-job"},"ecc-azure-219":{"description":"Batch account with logging and retention policy disabled\n","resource":"azure.batch"},"ecc-azure-220":{"description":"Data Lake Analytics with logging and retention policy disabled\n","resource":"azure.datalake-analytics"},"ecc-azure-222":{"description":"IoT Hub with logging and retention policy disabled\n","resource":"azure.iothub"},"ecc-azure-224":{"description":"Logic Apps service with logging and retention policy disabled\n","resource":"azure.logic-app-workflow"},"ecc-azure-225":{"description":"Azure Search with logging and retention policy disabled\n","resource":"azure.search"},"ecc-azure-226":{"description":"Service Bus with logging and retention policy disabled\n","resource":"azure.namespace"},"ecc-azure-227":{"description":"Virtual machine scale sets without LinuxDiagnostic or IaaSDiangostics extension installed\n","resource":"azure.vmss"},"ecc-azure-228":{"description":"Virtual machine without Guest Configuration extension installed\n","resource":"azure.vm"},"ecc-azure-231":{"description":"Virtual machine without MicrosoftMonitoringAgent or OmsAgentForLinux extension installed\n","resource":"azure.vm"},"ecc-azure-232":{"description":"Virtual machine scale sets without MicrosoftMonitoringAgent or OmsAgentForLinux extension installed\n","resource":"azure.vmss"},"ecc-azure-234":{"description":"Virtual machine with Guest Configuration extension installed without utilizing Managed Identity (SystemAssigned)\n","resource":"azure.vm"},"ecc-azure-235":{"description":"Kubernetes cluster with Azure Policy for AKS disabled\n","resource":"azure.aks"},"ecc-azure-236":{"description":"API app with CORS rule that allows every resource to access the service\n","resource":"azure.webapp"},"ecc-azure-237":{"description":"Function app with CORS rule that allows every resource to access the service\n","resource":"azure.webapp"},"ecc-azure-238":{"description":"Web app with CORS rule that allows every resource to access the service\n","resource":"azure.webapp"},"ecc-azure-239":{"description":"API app with 'Incoming client certificates' disabled\n","resource":"azure.webapp"},"ecc-azure-240":{"description":"Web app with 'Incoming client certificates' disabled\n","resource":"azure.webapp"},"ecc-azure-241":{"description":"Function app with 'Incoming client certificates' disabled\n","resource":"azure.webapp"},"ecc-azure-256":{"description":"API app with Remote debugging enabled\n","resource":"azure.webapp"},"ecc-azure-257":{"description":"Function app with Remote debugging enabled\n","resource":"azure.webapp"},"ecc-azure-258":{"description":"Web app with Remote debugging enabled\n","resource":"azure.webapp"},"ecc-azure-265":{"description":"Azure SQL Vulnerability assessment is disabled for a managed instance\n","resource":"azure.sql-managed-instance"},"ecc-azure-267":{"description":"Function app has an outdated Java version\n","resource":"azure.webapp"},"ecc-azure-270":{"description":"Function app has an outdated Python version\n","resource":"azure.webapp"},"ecc-azure-272":{"description":"Virtual machine scale sets without endpoint protection installed\n","resource":"azure.vmss"},"ecc-azure-275":{"description":"Virtual machine without Backup configured\n","resource":"azure.vm"},"ecc-azure-276":{"description":"MariaDB instance without Geo-redundant backup\n","resource":"azure.mariadb-server"},"ecc-azure-277":{"description":"MySQL instance without Geo-redundant backup\n","resource":"azure.mysql-server"},"ecc-azure-278":{"description":"PostgreSQL instance without Geo-redundant backup\n","resource":"azure.postgresql-server"},"ecc-azure-279":{"description":"Kubernetes cluster with local authentication methods enabled\n","resource":"azure.aks"},"ecc-azure-280":{"description":"Kubernetes cluster with private cluster feature disabled\n","resource":"azure.aks"},"ecc-azure-281":{"description":"Kubernetes cluster that utilizes one of the vulnerable k8s versions\n","resource":"azure.aks"},"ecc-azure-282":{"description":"Kubernetes cluster without EncryptionAtHost enabled\n","resource":"azure.aks"},"ecc-azure-283":{"description":"Kubernetes cluster with logging and retention policy disabled\n","resource":"azure.aks"},"ecc-azure-284":{"description":"Kubernetes cluster without OS and Data disks CMK encryption configured\n","resource":"azure.aks"},"ecc-azure-286":{"description":"A network policy is not in place to secure traffic between pods\n","resource":"azure.aks"},"ecc-azure-287":{"description":"Azure CNI Networking is disabled\n","resource":"azure.aks"},"ecc-azure-288":{"description":"Cluster Pool contains less than 3 Nodes\n","resource":"azure.aks"},"ecc-azure-289":{"description":"Admin user is enabled for Container Registry\n","resource":"azure.container-registry"},"ecc-azure-290":{"description":"Container Registry has no locks\n","resource":"azure.container-registry"},"ecc-azure-291":{"description":"Storage Accounts outside Europe\n","resource":"azure.storage"},"ecc-azure-293":{"description":"Azure SQL Server data replication with Fail Over groups\n","resource":"azure.sql-server"},"ecc-azure-294":{"description":"Azure Virtual Machine is not assigned to an availability set\n","resource":"azure.vm"},"ecc-azure-295":{"description":"Name like 'Admin' for an Azure SQL Server Active Directory Administrator account is found\n","resource":"azure.sqlserver"},"ecc-azure-296":{"description":"Name like 'Admin' for an Azure SQL Server Administrator account is found\n","resource":"azure.sqlserver"},"ecc-azure-298":{"description":"Application Service Logs are Disabled for Containerized Function Apps\n","resource":"azure.webapp"},"ecc-azure-299":{"description":"Health Check is disabled for your Function App\n","resource":"azure.webapp"},"ecc-azure-300":{"description":"Application Gateway with vulnerable and outdated TLS version\n","resource":"azure.application-gateway"},"ecc-azure-301":{"description":"Redis Cache without exposed to the public Internet\n","resource":"azure.redis"},"ecc-azure-302":{"description":"Redis Cache with enabled public access\n","resource":"azure.redis"},"ecc-azure-304":{"description":"Application Gateway is using Http protocol\n","resource":"azure.application-gateway"},"ecc-azure-305":{"description":"Storage account with vulnerable and outdated TLS version\n","resource":"azure.storage"},"ecc-azure-306":{"description":"PostgreSQL instance with disabled Infrastructure double encryption\n","resource":"azure.postgresql-server"},"ecc-azure-310":{"description":"Azure Defender for OpenSource Relational Databases is set to \"Off\"\n","resource":"azure.defender-pricing"},"ecc-azure-311":{"description":"PostgreSQL instance with server parameter 'logging collector' disabled\n","resource":"azure.postgresql-server"},"ecc-azure-313":{"description":"PostgreSQL instance without server parameter 'log_min_messages' set to WARNING\n","resource":"azure.postgresql-server"},"ecc-azure-314":{"description":"PostgreSQL instance with server parameter 'debug_print_plan' enabled\n","resource":"azure.postgresql-server"},"ecc-azure-317":{"description":"PostgreSQL instance without server parameter 'log_error_verbosity' set to VERBOSE\n","resource":"azure.postgresql-server"},"ecc-azure-318":{"description":"PostgreSQL instance with server parameter 'log_line_prefix' set incorrectly\n","resource":"azure.postgresql-server"},"ecc-azure-319":{"description":"PostgreSQL instance without server parameter 'log_min_error_statement' set to ERROR\n","resource":"azure.postgresql-server"},"ecc-azure-321":{"description":"PostgreSQL instance with server parameter 'log_statement' set incorrectly\n","resource":"azure.postgresql-server"},"ecc-azure-323":{"description":"Azure Linux virtual machines scale set doesn't use an SSH key\n","resource":"azure.vmss"},"ecc-azure-324":{"description":"Azure Kusto cluster without double encryption enabled\n","resource":"azure.kusto"},"ecc-azure-325":{"description":"Azure Kusto cluster without disk encryption\n","resource":"azure.kusto"},"ecc-azure-326":{"description":"Azure Kusto cluster without CMK configured\n","resource":"azure.kusto"},"ecc-azure-327":{"description":"Azure Data Factory doesn't use Git repository for source control\n","resource":"azure.datafactory"},"ecc-azure-328":{"description":"Azure data factories are not encrypted with a customer-managed key\n","resource":"azure.datafactory"},"ecc-azure-329":{"description":"Azure Batch account doesn't use key vault to encrypt data\n","resource":"azure.batch"},"ecc-azure-331":{"description":"App service with disabled detailed logging of error messages\n","resource":"azure.webapp"},"ecc-azure-332":{"description":"App service without configured failed requests tracings\n","resource":"azure.webapp"},"ecc-azure-333":{"description":"Public network access enabled for Azure IoT Hub\n","resource":"azure.iothub"},"ecc-azure-334":{"description":"Cosmos DB account with unrestricted write access to the management plane\n","resource":"azure.cosmosdb"},"ecc-azure-336":{"description":"Virtual machine scale sets without EncryptionAtHost enabled\n","resource":"azure.vmss"},"ecc-azure-337":{"description":"Microsoft Antimalware is not configured to automatically update Virtual Machines\n","resource":"azure.vm"},"ecc-azure-339":{"description":"Secret without 'content_type' set\n","resource":"azure.keyvault-secret"},"ecc-azure-340":{"description":"Application Gateway without Log4j WAF rule enabled or applied Ruleset version 3.0 or above\n","resource":"azure.application-gateway"},"ecc-azure-341":{"description":"Azure Front Door without Log4j WAF rule enabled\n","resource":"azure.front-door"},"ecc-azure-342":{"description":"Azure SQL instance with vulnerable and outdated TLS version\n","resource":"azure.sql-server"},"ecc-azure-343":{"description":"Advanced Threat Protection is disabled on PostgreSQL server\n","resource":"azure.postgresql-server"},"ecc-azure-344":{"description":"Advanced Threat Protection is disabled on MySQL server\n","resource":"azure.mysql-server"},"ecc-azure-345":{"description":"MySQL instance with disabled Infrastructure double encryption\n","resource":"azure.mysql-server"},"ecc-azure-346":{"description":"MySQL instance with vulnerable and outdated TLS version\n","resource":"azure.mysql-server"},"ecc-azure-347":{"description":"MySQL instance without CMK encryption configured\n","resource":"azure.mysql-server"},"ecc-azure-348":{"description":"MySQL instance with server parameter 'local_infile' enabled\n","resource":"azure.mysql-server"},"ecc-azure-349":{"description":"MySQL instance without server setting \"max_user_connections\" limits\n","resource":"azure.mysql-server"},"ecc-azure-350":{"description":"MySQL instance with server parameter 'slow_query_log' disabled\n","resource":"azure.mysql-server"},"ecc-azure-351":{"description":"MySQL instance without sql_mode parameter set to \"STRICT_ALL_TABLES\" value\n","resource":"azure.mysql-server"},"ecc-azure-353":{"description":"Virtual machine scale sets without OS image autoupgrade enabled\n","resource":"azure.vmss"},"ecc-azure-354":{"description":"Container registry with anonymous pull enabled\n","resource":"azure.container-registry"},"ecc-azure-355":{"description":"Azure Machine Learning Compute cluster have minNodeCount property not equal to 0\n","resource":"azure.machine-learning-workspace"},"ecc-azure-356":{"description":"API Managment service without configured client certificates\n","resource":"azure.api-management"},"ecc-azure-357":{"description":"Azure Databricks workspace with enabled public access\n","resource":"azure.databricks"},"ecc-azure-358":{"description":"Azure Synapse workspace without managed virtual network\n","resource":"azure.synapse"},"ecc-azure-359":{"description":"Azure Synapse workspace without data exfiltration enabled\n","resource":"azure.synapse"},"ecc-azure-362":{"description":"Azure Virtual Machines without Vulnerability Assessment solution\n","resource":"azure.security-assessments"},"ecc-azure-364":{"description":"Activity Log Alert without tags\n","resource":"azure.activity-log-alert"},"ecc-azure-365":{"description":"API Management without tags\n","resource":"azure.api-management"},"ecc-azure-367":{"description":"Linux virtual machine affected to OMI vulnerability (CVE-2021-38645)\n","resource":"azure.vm"},"ecc-azure-368":{"description":"Linux virtual machine scale set affected to OMI vulnerability (CVE-2021-38645)\n","resource":"azure.vmss"},"ecc-azure-369":{"description":"Storage Account without Infrastructure Encryption enabled\n","resource":"azure.storage"},"ecc-azure-370":{"description":"CosmosDB account without Private Endpoint connection configured\n","resource":"azure.cosmosdb"},"ecc-azure-371":{"description":"MySQL instance with server setting \"audit_log_enabled\" set to \"off\"\n","resource":"azure.mysql-server"},"ecc-azure-372":{"description":"MySQL instance with server setting \"audit_log_events\" set to \"off\"\n","resource":"azure.mysql-server"},"ecc-azure-373":{"description":"Subscription where Activity Log Alert does not exist for Create or Update Public IP Address rule\n","resource":"azure.subscription"},"ecc-azure-374":{"description":"Subscription where Activity Log Alert does not exist for Delete Public IP Address rule\n","resource":"azure.subscription"},"ecc-azure-378":{"description":"Network Security Group Flow Log Analytics disabled\n","resource":"azure.networksecuritygroup"},"ecc-azure-379":{"description":"App Service with web requests logging disabled\n","resource":"azure.webapp"}} \ No newline at end of file diff --git a/tests/tests_metrics/mock_files/reports/raw/EPAM Systems/GOOGLE/GOOGLE-1234567890123/latest/0.json b/tests/tests_metrics/mock_files/reports/raw/EPAM Systems/GOOGLE/GOOGLE-1234567890123/latest/0.json new file mode 100644 index 000000000..0be14b9aa --- /dev/null +++ b/tests/tests_metrics/mock_files/reports/raw/EPAM Systems/GOOGLE/GOOGLE-1234567890123/latest/0.json @@ -0,0 +1 @@ +[{"p":"ecc-gcp-001","l":"multiregion","r":[{"member":"dac559d6-2824-4dc6-a06a-05e267aed200"}],"t":1700126413.5031583},{"p":"ecc-gcp-001","l":"us-west-1","r":[{"member":"dac559d6-2824-4dc6-a06a-05e267aed200"}],"t":1700126413.5031583},{"p":"ecc-gcp-003","l":"multiregion","r":[{"keys[].name":"668c553e-c891-4eab-b4b4-ffc1e7faa05b"}],"t":1700126413.5031583},{"p":"ecc-gcp-003","l":"us-west-1","r":[{"keys[].name":"668c553e-c891-4eab-b4b4-ffc1e7faa05b"}],"t":1700126413.5031583},{"p":"ecc-gcp-004","l":"multiregion","r":[{"member":"c049e628-834c-4ddb-aaa9-3c705ce33834","roles":"926cb034-0b24-458c-aa58-bd98890e6006"}],"t":1700126413.5031583},{"p":"ecc-gcp-004","l":"us-west-1","r":[{"member":"c049e628-834c-4ddb-aaa9-3c705ce33834","roles":"926cb034-0b24-458c-aa58-bd98890e6006"}],"t":1700126413.5031583},{"p":"ecc-gcp-005","l":"multiregion","r":[{"role":"afdc5b3c-451d-4cf2-b89a-25432265fb68","members":"c57e282c-7365-4bed-b06f-8e0a12f5ab43"}],"t":1700126413.5031583},{"p":"ecc-gcp-005","l":"us-west-1","r":[{"role":"afdc5b3c-451d-4cf2-b89a-25432265fb68","members":"c57e282c-7365-4bed-b06f-8e0a12f5ab43"}],"t":1700126413.5031583},{"p":"ecc-gcp-006","l":"multiregion","r":[{"name":"b3191836-e796-402f-ad58-6b478736b844"}],"t":1700126413.5031583},{"p":"ecc-gcp-006","l":"us-west-1","r":[{"name":"b3191836-e796-402f-ad58-6b478736b844"}],"t":1700126413.5031583},{"p":"ecc-gcp-007","l":"multiregion","r":[{"member":"08e3460c-6a3d-4e34-b254-c3cfcc7a32c2"}],"t":1700126413.5031583},{"p":"ecc-gcp-007","l":"us-west-1","r":[{"member":"08e3460c-6a3d-4e34-b254-c3cfcc7a32c2"}],"t":1700126413.5031583},{"p":"ecc-gcp-008","l":"multiregion","r":[{"name":"60f447eb-2248-4ce9-97a8-f512c1ef97a0"}],"t":1700126413.5031583},{"p":"ecc-gcp-008","l":"us-west-1","r":[{"name":"60f447eb-2248-4ce9-97a8-f512c1ef97a0"}],"t":1700126413.5031583},{"p":"ecc-gcp-009","l":"multiregion","r":[{"member":"69e981fe-444f-46dc-a7fe-08e65ecb62b2"}],"t":1700126413.5031583},{"p":"ecc-gcp-009","l":"us-west-1","r":[{"member":"69e981fe-444f-46dc-a7fe-08e65ecb62b2"}],"t":1700126413.5031583},{"p":"ecc-gcp-010","l":"multiregion","r":[{"displayName":"be8eef92-669b-4685-a171-17689102d540","name":"9dd07614-3713-4d91-848e-a2ca2b51bd45"}],"t":1700126413.5031583},{"p":"ecc-gcp-010","l":"us-west-1","r":[{"displayName":"be8eef92-669b-4685-a171-17689102d540","name":"9dd07614-3713-4d91-848e-a2ca2b51bd45"}],"t":1700126413.5031583},{"p":"ecc-gcp-011","l":"multiregion","r":[{"displayName":"e7f4faab-ced0-4f7e-b42d-be60729e13f5","name":"c0406e25-47cd-4898-8786-495b85f72470"}],"t":1700126413.5031583},{"p":"ecc-gcp-011","l":"us-west-1","r":[{"displayName":"e7f4faab-ced0-4f7e-b42d-be60729e13f5","name":"c0406e25-47cd-4898-8786-495b85f72470"}],"t":1700126413.5031583},{"p":"ecc-gcp-012","l":"multiregion","r":[{"displayName":"710bcf71-59db-4604-b573-c36dabfb02ee","name":"a415b6b6-7461-435d-b59e-4080638037aa"}],"t":1700126413.5031583},{"p":"ecc-gcp-012","l":"us-west-1","r":[{"displayName":"710bcf71-59db-4604-b573-c36dabfb02ee","name":"a415b6b6-7461-435d-b59e-4080638037aa"}],"t":1700126413.5031583},{"p":"ecc-gcp-013","l":"multiregion","r":[{"displayName":"b331ebd7-42b8-4c12-ae51-259acf3c10fa","name":"d1d6f259-e2ed-40f0-954b-9b1de1d77437"}],"t":1700126413.5031583},{"p":"ecc-gcp-013","l":"us-west-1","r":[{"displayName":"b331ebd7-42b8-4c12-ae51-259acf3c10fa","name":"d1d6f259-e2ed-40f0-954b-9b1de1d77437"}],"t":1700126413.5031583},{"p":"ecc-gcp-014","l":"multiregion","r":[{"projectId":"b32772c5-7557-4947-9c67-10dc5d90cc4a"}],"t":1700126413.5031583},{"p":"ecc-gcp-014","l":"us-west-1","r":[{"projectId":"b32772c5-7557-4947-9c67-10dc5d90cc4a"}],"t":1700126413.5031583},{"p":"ecc-gcp-015","l":"multiregion","r":[{"projectId":"76ba5791-a2cb-4ec9-99e7-a14d89d52de5"}],"t":1700126413.5031583},{"p":"ecc-gcp-015","l":"us-west-1","r":[{"projectId":"76ba5791-a2cb-4ec9-99e7-a14d89d52de5"}],"t":1700126413.5031583},{"p":"ecc-gcp-016","l":"multiregion","r":[{"selfLink":"42e0e6eb-57f6-498a-8523-485818eed612"}],"t":1700126413.5031583},{"p":"ecc-gcp-016","l":"us-west-1","r":[{"selfLink":"42e0e6eb-57f6-498a-8523-485818eed612"}],"t":1700126413.5031583},{"p":"ecc-gcp-017","l":"multiregion","r":[{"projectId":"77619523-36ad-4c48-8db1-2f4e960b324b"}],"t":1700126413.5031583},{"p":"ecc-gcp-017","l":"us-west-1","r":[{"projectId":"77619523-36ad-4c48-8db1-2f4e960b324b"}],"t":1700126413.5031583},{"p":"ecc-gcp-018","l":"multiregion","r":[{"projectId":"623a793e-ce46-4ea4-8d57-b59e487c0af8"}],"t":1700126413.5031583},{"p":"ecc-gcp-018","l":"us-west-1","r":[{"projectId":"623a793e-ce46-4ea4-8d57-b59e487c0af8"}],"t":1700126413.5031583},{"p":"ecc-gcp-019","l":"multiregion","r":[{"projectId":"b428c271-7f82-4d27-9391-5992771aa5e1"}],"t":1700126413.5031583},{"p":"ecc-gcp-019","l":"us-west-1","r":[{"projectId":"b428c271-7f82-4d27-9391-5992771aa5e1"}],"t":1700126413.5031583},{"p":"ecc-gcp-020","l":"multiregion","r":[{"projectId":"66248d7c-a72d-47f3-b2aa-5f72b575bd31"}],"t":1700126413.5031583},{"p":"ecc-gcp-020","l":"us-west-1","r":[{"projectId":"66248d7c-a72d-47f3-b2aa-5f72b575bd31"}],"t":1700126413.5031583},{"p":"ecc-gcp-021","l":"multiregion","r":[{"projectId":"5987b639-c080-48f4-9a7d-917390cf20a5"}],"t":1700126413.5031583},{"p":"ecc-gcp-021","l":"us-west-1","r":[{"projectId":"5987b639-c080-48f4-9a7d-917390cf20a5"}],"t":1700126413.5031583},{"p":"ecc-gcp-022","l":"multiregion","r":[{"projectId":"63f88a37-ed78-4b0b-b882-6dac5815c9ba"}],"t":1700126413.5031583},{"p":"ecc-gcp-022","l":"us-west-1","r":[{"projectId":"63f88a37-ed78-4b0b-b882-6dac5815c9ba"}],"t":1700126413.5031583},{"p":"ecc-gcp-023","l":"multiregion","r":[{"projectId":"50a2bf8c-719c-445a-803b-98a1d3fb7a89"}],"t":1700126413.5031583},{"p":"ecc-gcp-023","l":"us-west-1","r":[{"projectId":"50a2bf8c-719c-445a-803b-98a1d3fb7a89"}],"t":1700126413.5031583},{"p":"ecc-gcp-024","l":"multiregion","r":[{"projectId":"f874ff8a-ec69-4e42-858c-d03c1ae6a753"}],"t":1700126413.5031583},{"p":"ecc-gcp-024","l":"us-west-1","r":[{"projectId":"f874ff8a-ec69-4e42-858c-d03c1ae6a753"}],"t":1700126413.5031583},{"p":"ecc-gcp-025","l":"multiregion","r":[{"selfLink":"2eb7dea2-a1a2-434d-a2a2-067d811006b8"}],"t":1700126413.5031583},{"p":"ecc-gcp-025","l":"us-west-1","r":[{"selfLink":"2eb7dea2-a1a2-434d-a2a2-067d811006b8"}],"t":1700126413.5031583},{"p":"ecc-gcp-027","l":"multiregion","r":[{"id":"4398a0a1-091a-4027-948c-e435f97884ba","name":"5c3e4306-06d0-4108-8638-b958030ff712"}],"t":1700126413.5031583},{"p":"ecc-gcp-027","l":"us-west-1","r":[{"id":"4398a0a1-091a-4027-948c-e435f97884ba","name":"5c3e4306-06d0-4108-8638-b958030ff712"}],"t":1700126413.5031583},{"p":"ecc-gcp-028","l":"multiregion","r":[{"id":"2a966bcf-ef4d-4fe1-b584-6a2a62fd2d3d","name":"032ff55a-1e73-4b01-84e1-8806e6932b21"}],"t":1700126413.5031583},{"p":"ecc-gcp-028","l":"us-west-1","r":[{"id":"2a966bcf-ef4d-4fe1-b584-6a2a62fd2d3d","name":"032ff55a-1e73-4b01-84e1-8806e6932b21"}],"t":1700126413.5031583},{"p":"ecc-gcp-029","l":"multiregion","r":[{"id":"d89f24d0-8e91-41ea-a5e9-a93f3dc1510f","name":"d2c1d341-d9a7-45a3-832e-dcc1f55eb6e6"}],"t":1700126413.5031583},{"p":"ecc-gcp-029","l":"us-west-1","r":[{"id":"d89f24d0-8e91-41ea-a5e9-a93f3dc1510f","name":"d2c1d341-d9a7-45a3-832e-dcc1f55eb6e6"}],"t":1700126413.5031583},{"p":"ecc-gcp-030","l":"multiregion","r":[{"selfLink":"01f26bee-208c-498d-b187-7cd629802ece"}],"t":1700126413.5031583},{"p":"ecc-gcp-030","l":"us-west-1","r":[{"selfLink":"01f26bee-208c-498d-b187-7cd629802ece"}],"t":1700126413.5031583},{"p":"ecc-gcp-031","l":"multiregion","r":[{"selfLink":"e56e7e69-aaae-4f33-9803-05d759ede4ad"}],"t":1700126413.5031583},{"p":"ecc-gcp-031","l":"us-west-1","r":[{"selfLink":"e56e7e69-aaae-4f33-9803-05d759ede4ad"}],"t":1700126413.5031583},{"p":"ecc-gcp-032","l":"multiregion","r":[{"selfLink":"2d14b4f5-0b3f-4f9c-be77-b8922cb7fb4c"}],"t":1700126413.5031583},{"p":"ecc-gcp-032","l":"us-west-1","r":[{"selfLink":"2d14b4f5-0b3f-4f9c-be77-b8922cb7fb4c"}],"t":1700126413.5031583},{"p":"ecc-gcp-033","l":"multiregion","r":[{"selfLink":"f7840f49-8bf0-45c9-800e-82a34fe12169"}],"t":1700126413.5031583},{"p":"ecc-gcp-033","l":"us-west-1","r":[{"selfLink":"f7840f49-8bf0-45c9-800e-82a34fe12169"}],"t":1700126413.5031583},{"p":"ecc-gcp-034","l":"multiregion","r":[{"selfLink":"87433d12-be8f-4679-bd0b-3c40d17d712d"}],"t":1700126413.5031583},{"p":"ecc-gcp-034","l":"us-west-1","r":[{"selfLink":"87433d12-be8f-4679-bd0b-3c40d17d712d"}],"t":1700126413.5031583},{"p":"ecc-gcp-035","l":"multiregion","r":[{"selfLink":"41364b39-9a47-4d69-8d2a-d096a63de00b"}],"t":1700126413.5031583},{"p":"ecc-gcp-035","l":"us-west-1","r":[{"selfLink":"41364b39-9a47-4d69-8d2a-d096a63de00b"}],"t":1700126413.5031583},{"p":"ecc-gcp-036","l":"multiregion","r":[{"selfLink":"5f53cb30-d360-4b11-bb58-6f3cc60a0ec9"}],"t":1700126413.5031583},{"p":"ecc-gcp-036","l":"us-west-1","r":[{"selfLink":"5f53cb30-d360-4b11-bb58-6f3cc60a0ec9"}],"t":1700126413.5031583},{"p":"ecc-gcp-037","l":"multiregion","r":[{"selfLink":"69056793-ca4f-4234-90ca-f7def9516805"}],"t":1700126413.5033238},{"p":"ecc-gcp-037","l":"us-west-1","r":[{"selfLink":"69056793-ca4f-4234-90ca-f7def9516805"}],"t":1700126413.5033238},{"p":"ecc-gcp-038","l":"multiregion","r":[{"selfLink":"f0395a41-8d05-4100-861f-ef31b5edfc99"}],"t":1700126413.5033238},{"p":"ecc-gcp-038","l":"us-west-1","r":[{"selfLink":"f0395a41-8d05-4100-861f-ef31b5edfc99"}],"t":1700126413.5033238},{"p":"ecc-gcp-039","l":"multiregion","r":[{"selfLink":"4b9c7aa8-95fb-42e2-a3c3-8bd66c0365a4"}],"t":1700126413.5033238},{"p":"ecc-gcp-039","l":"us-west-1","r":[{"selfLink":"4b9c7aa8-95fb-42e2-a3c3-8bd66c0365a4"}],"t":1700126413.5033238},{"p":"ecc-gcp-040","l":"multiregion","r":[{"selfLink":"9848f5b9-b87b-4e0b-9089-1ddc182a04a8"}],"t":1700126413.5033238},{"p":"ecc-gcp-040","l":"us-west-1","r":[{"selfLink":"9848f5b9-b87b-4e0b-9089-1ddc182a04a8"}],"t":1700126413.5033238},{"p":"ecc-gcp-042","l":"multiregion","r":[{"selfLink":"652f72a0-b33a-449d-84dd-f71cef073858"}],"t":1700126413.5033238},{"p":"ecc-gcp-042","l":"us-west-1","r":[{"selfLink":"652f72a0-b33a-449d-84dd-f71cef073858"}],"t":1700126413.5033238},{"p":"ecc-gcp-043","l":"multiregion","r":[{"selfLink":"afc4cd69-f84a-48cf-b90e-6da6137d0147"}],"t":1700126413.5033238},{"p":"ecc-gcp-043","l":"us-west-1","r":[{"selfLink":"afc4cd69-f84a-48cf-b90e-6da6137d0147"}],"t":1700126413.5033238},{"p":"ecc-gcp-044","l":"multiregion","r":[{"selfLink":"afdfe814-7c79-4021-a13a-c96747040dd4"}],"t":1700126413.5033238},{"p":"ecc-gcp-044","l":"us-west-1","r":[{"selfLink":"afdfe814-7c79-4021-a13a-c96747040dd4"}],"t":1700126413.5033238},{"p":"ecc-gcp-046","l":"multiregion","r":[{"instance":"f152fa5f-552c-49e4-8787-d8f59ed4cd6d"}],"t":1700126413.5033238},{"p":"ecc-gcp-046","l":"us-west-1","r":[{"instance":"f152fa5f-552c-49e4-8787-d8f59ed4cd6d"}],"t":1700126413.5033238},{"p":"ecc-gcp-047","l":"multiregion","r":[{"selfLink":"3cee14f9-de0e-4906-8ea1-03b5bda112d0"}],"t":1700126413.5033238},{"p":"ecc-gcp-047","l":"us-west-1","r":[{"selfLink":"3cee14f9-de0e-4906-8ea1-03b5bda112d0"}],"t":1700126413.5033238},{"p":"ecc-gcp-048","l":"multiregion","r":[{"selfLink":"26be8ea4-746d-40a1-a34b-c6609fc7b24e"}],"t":1700126413.5033238},{"p":"ecc-gcp-048","l":"us-west-1","r":[{"selfLink":"26be8ea4-746d-40a1-a34b-c6609fc7b24e"}],"t":1700126413.5033238},{"p":"ecc-gcp-049","l":"multiregion","r":[{"selfLink":"6c551816-c5aa-4913-84ea-fdabf88aa705"}],"t":1700126413.5033603},{"p":"ecc-gcp-049","l":"us-west-1","r":[{"selfLink":"6c551816-c5aa-4913-84ea-fdabf88aa705"}],"t":1700126413.5033603},{"p":"ecc-gcp-050","l":"multiregion","r":[{"selfLink":"551d1fc0-c450-445f-98fa-479abd9ba0ef"}],"t":1700126413.5033603},{"p":"ecc-gcp-050","l":"us-west-1","r":[{"selfLink":"551d1fc0-c450-445f-98fa-479abd9ba0ef"}],"t":1700126413.5033603},{"p":"ecc-gcp-051","l":"multiregion","r":[{"selfLink":"98179692-535b-48c5-8df5-f3cab9c5a7c7"}],"t":1700126413.5033603},{"p":"ecc-gcp-051","l":"us-west-1","r":[{"selfLink":"98179692-535b-48c5-8df5-f3cab9c5a7c7"}],"t":1700126413.5033603},{"p":"ecc-gcp-053","l":"multiregion","r":[{"selfLink":"bd3129e5-36e3-4d07-a216-ec4ff62392a0"}],"t":1700126413.5033603},{"p":"ecc-gcp-053","l":"us-west-1","r":[{"selfLink":"bd3129e5-36e3-4d07-a216-ec4ff62392a0"}],"t":1700126413.5033603},{"p":"ecc-gcp-054","l":"multiregion","r":[{"selfLink":"0ff29cf2-a364-4b61-8ded-5b46d122d215"}],"t":1700126413.5033603},{"p":"ecc-gcp-054","l":"us-west-1","r":[{"selfLink":"0ff29cf2-a364-4b61-8ded-5b46d122d215"}],"t":1700126413.5033603},{"p":"ecc-gcp-055","l":"multiregion","r":[{"selfLink":"158c2234-5dbc-4b78-b876-71546650e678"}],"t":1700126413.5033603},{"p":"ecc-gcp-055","l":"us-west-1","r":[{"selfLink":"158c2234-5dbc-4b78-b876-71546650e678"}],"t":1700126413.5033603},{"p":"ecc-gcp-057","l":"multiregion","r":[{"selfLink":"33788d92-dc04-4a1c-9d12-2a54baf57c57"}],"t":1700126413.5033603},{"p":"ecc-gcp-057","l":"us-west-1","r":[{"selfLink":"33788d92-dc04-4a1c-9d12-2a54baf57c57"}],"t":1700126413.5033603},{"p":"ecc-gcp-058","l":"multiregion","r":[{"selfLink":"66b1a6af-19fd-42cb-b7c5-0a327e852f85"}],"t":1700126413.5033603},{"p":"ecc-gcp-058","l":"us-west-1","r":[{"selfLink":"66b1a6af-19fd-42cb-b7c5-0a327e852f85"}],"t":1700126413.5033603},{"p":"ecc-gcp-059","l":"multiregion","r":[{"selfLink":"e5ecbafc-775e-49bc-98fe-7c27205a4932"}],"t":1700126413.5033603},{"p":"ecc-gcp-059","l":"us-west-1","r":[{"selfLink":"e5ecbafc-775e-49bc-98fe-7c27205a4932"}],"t":1700126413.5033603},{"p":"ecc-gcp-060","l":"multiregion","r":[{"selfLink":"fa8369f2-869f-40e5-be6b-fce86b8303d9"}],"t":1700126413.5033603},{"p":"ecc-gcp-060","l":"us-west-1","r":[{"selfLink":"fa8369f2-869f-40e5-be6b-fce86b8303d9"}],"t":1700126413.5033603},{"p":"ecc-gcp-061","l":"multiregion","r":[{"selfLink":"ea52380d-02a9-41c1-ad95-29dad2f83a01"}],"t":1700126413.5033603},{"p":"ecc-gcp-061","l":"us-west-1","r":[{"selfLink":"ea52380d-02a9-41c1-ad95-29dad2f83a01"}],"t":1700126413.5033603},{"p":"ecc-gcp-062","l":"multiregion","r":[{"selfLink":"7dceda21-11b5-4ba8-9c68-fca3f42397d0"}],"t":1700126413.5033603},{"p":"ecc-gcp-062","l":"us-west-1","r":[{"selfLink":"7dceda21-11b5-4ba8-9c68-fca3f42397d0"}],"t":1700126413.5033603},{"p":"ecc-gcp-063","l":"multiregion","r":[{"selfLink":"a987f22c-ba9c-4816-aaab-1950b30ea4a7"}],"t":1700126413.5033603},{"p":"ecc-gcp-063","l":"us-west-1","r":[{"selfLink":"a987f22c-ba9c-4816-aaab-1950b30ea4a7"}],"t":1700126413.5033603},{"p":"ecc-gcp-065","l":"multiregion","r":[{"name":"eb46df3d-35d8-4342-8775-c6d919085288"}],"t":1700126413.5033603},{"p":"ecc-gcp-065","l":"us-west-1","r":[{"name":"eb46df3d-35d8-4342-8775-c6d919085288"}],"t":1700126413.5033603},{"p":"ecc-gcp-066","l":"multiregion","r":[{"name":"423aa6bb-dd61-4d7a-801f-2a00a96ff221"}],"t":1700126413.5033603},{"p":"ecc-gcp-066","l":"us-west-1","r":[{"name":"423aa6bb-dd61-4d7a-801f-2a00a96ff221"}],"t":1700126413.5033603},{"p":"ecc-gcp-067","l":"multiregion","r":[{"name":"ff88ad74-f149-445d-96a5-383ecceb4660"}],"t":1700126413.5033603},{"p":"ecc-gcp-067","l":"us-west-1","r":[{"name":"ff88ad74-f149-445d-96a5-383ecceb4660"}],"t":1700126413.5033603},{"p":"ecc-gcp-068","l":"multiregion","r":[{"name":"65f07041-c618-49d2-ba91-3bbf52f75ca1"}],"t":1700126413.5033603},{"p":"ecc-gcp-068","l":"us-west-1","r":[{"name":"65f07041-c618-49d2-ba91-3bbf52f75ca1"}],"t":1700126413.5033603},{"p":"ecc-gcp-070","l":"multiregion","r":[{"selfLink":"ce39bb0a-bf21-400d-bb13-be17dac23c60"}],"t":1700126413.5033603},{"p":"ecc-gcp-070","l":"us-west-1","r":[{"selfLink":"ce39bb0a-bf21-400d-bb13-be17dac23c60"}],"t":1700126413.5033603},{"p":"ecc-gcp-071","l":"multiregion","r":[{"selfLink":"0cf83144-a59e-48af-b245-839ea5596ddf"}],"t":1700126413.5033603},{"p":"ecc-gcp-071","l":"us-west-1","r":[{"selfLink":"0cf83144-a59e-48af-b245-839ea5596ddf"}],"t":1700126413.5033603},{"p":"ecc-gcp-072","l":"multiregion","r":[{"selfLink":"0fd0ce9b-6d32-4d7d-8058-bba9fa7a49f3"}],"t":1700126413.5033603},{"p":"ecc-gcp-072","l":"us-west-1","r":[{"selfLink":"0fd0ce9b-6d32-4d7d-8058-bba9fa7a49f3"}],"t":1700126413.5033603},{"p":"ecc-gcp-076","l":"multiregion","r":[{"selfLink":"bda02453-5871-4f6c-b0b9-06efe8b2a71f"}],"t":1700126413.5033603},{"p":"ecc-gcp-076","l":"us-west-1","r":[{"selfLink":"bda02453-5871-4f6c-b0b9-06efe8b2a71f"}],"t":1700126413.5033603},{"p":"ecc-gcp-077","l":"multiregion","r":[{"instance":"602bb193-f2e5-4262-91c4-1bb90f863057"}],"t":1700126413.5033603},{"p":"ecc-gcp-077","l":"us-west-1","r":[{"instance":"602bb193-f2e5-4262-91c4-1bb90f863057"}],"t":1700126413.5033603},{"p":"ecc-gcp-079","l":"multiregion","r":[{"selfLink":"e69f22eb-9716-4466-9a6d-20dab875a96a"}],"t":1700126413.5033603},{"p":"ecc-gcp-079","l":"us-west-1","r":[{"selfLink":"e69f22eb-9716-4466-9a6d-20dab875a96a"}],"t":1700126413.5033603},{"p":"ecc-gcp-082","l":"multiregion","r":[{"selfLink":"6ca13134-e929-436a-82cd-0451bc047f94"}],"t":1700126413.5033603},{"p":"ecc-gcp-082","l":"us-west-1","r":[{"selfLink":"6ca13134-e929-436a-82cd-0451bc047f94"}],"t":1700126413.5033603},{"p":"ecc-gcp-083","l":"multiregion","r":[{"selfLink":"aeb4136a-2881-4a97-a816-5003493810d8"}],"t":1700126413.5033603},{"p":"ecc-gcp-083","l":"us-west-1","r":[{"selfLink":"aeb4136a-2881-4a97-a816-5003493810d8"}],"t":1700126413.5033603},{"p":"ecc-gcp-086","l":"multiregion","r":[{"selfLink":"8c39886e-ce66-4128-a20e-ff2c4a1ae544"}],"t":1700126413.5033603},{"p":"ecc-gcp-086","l":"us-west-1","r":[{"selfLink":"8c39886e-ce66-4128-a20e-ff2c4a1ae544"}],"t":1700126413.5033603},{"p":"ecc-gcp-087","l":"multiregion","r":[{"selfLink":"dca252dc-fd3e-43a4-baee-d31a49eb7a93"}],"t":1700126413.5033603},{"p":"ecc-gcp-087","l":"us-west-1","r":[{"selfLink":"dca252dc-fd3e-43a4-baee-d31a49eb7a93"}],"t":1700126413.5033603},{"p":"ecc-gcp-088","l":"multiregion","r":[{"selfLink":"60c82346-1478-47ba-a85d-c425976890dd"}],"t":1700126413.5033603},{"p":"ecc-gcp-088","l":"us-west-1","r":[{"selfLink":"60c82346-1478-47ba-a85d-c425976890dd"}],"t":1700126413.5033603},{"p":"ecc-gcp-089","l":"multiregion","r":[{"selfLink":"11509042-d4fe-445b-bf94-ee02b9e3feff"}],"t":1700126413.5033603},{"p":"ecc-gcp-089","l":"us-west-1","r":[{"selfLink":"11509042-d4fe-445b-bf94-ee02b9e3feff"}],"t":1700126413.5033603},{"p":"ecc-gcp-090","l":"multiregion","r":[{"selfLink":"a1b9d41a-64e1-4465-8f25-c092f195dbe5"}],"t":1700126413.5033603},{"p":"ecc-gcp-090","l":"us-west-1","r":[{"selfLink":"a1b9d41a-64e1-4465-8f25-c092f195dbe5"}],"t":1700126413.5033603},{"p":"ecc-gcp-091","l":"multiregion","r":[{"selfLink":"29789e15-f2c9-4335-8255-2fdf7897a21f"}],"t":1700126413.5033603},{"p":"ecc-gcp-091","l":"us-west-1","r":[{"selfLink":"29789e15-f2c9-4335-8255-2fdf7897a21f"}],"t":1700126413.5033603},{"p":"ecc-gcp-092","l":"multiregion","r":[{"selfLink":"db13f700-b42e-4140-9149-7c0e6808a8e0"}],"t":1700126413.5033603},{"p":"ecc-gcp-092","l":"us-west-1","r":[{"selfLink":"db13f700-b42e-4140-9149-7c0e6808a8e0"}],"t":1700126413.5033603},{"p":"ecc-gcp-093","l":"multiregion","r":[{"selfLink":"4de9c9f1-b56b-4d08-9a63-01a40f39c559"}],"t":1700126413.5033603},{"p":"ecc-gcp-093","l":"us-west-1","r":[{"selfLink":"4de9c9f1-b56b-4d08-9a63-01a40f39c559"}],"t":1700126413.5033603},{"p":"ecc-gcp-099","l":"multiregion","r":[{"selfLink":"f7579cbc-4e17-435c-8b8a-c171fc0a0d72"}],"t":1700126413.5033603},{"p":"ecc-gcp-099","l":"us-west-1","r":[{"selfLink":"f7579cbc-4e17-435c-8b8a-c171fc0a0d72"}],"t":1700126413.5033603},{"p":"ecc-gcp-101","l":"multiregion","r":[{"selfLink":"ab69333a-8d2e-454f-907d-fb83b6a87a7f"}],"t":1700126413.5033603},{"p":"ecc-gcp-101","l":"us-west-1","r":[{"selfLink":"ab69333a-8d2e-454f-907d-fb83b6a87a7f"}],"t":1700126413.5033603},{"p":"ecc-gcp-103","l":"multiregion","r":[{"selfLink":"3eb2fe35-b55d-4224-b082-7045f7ac1929"}],"t":1700126413.5033603},{"p":"ecc-gcp-103","l":"us-west-1","r":[{"selfLink":"3eb2fe35-b55d-4224-b082-7045f7ac1929"}],"t":1700126413.5033603},{"p":"ecc-gcp-104","l":"multiregion","r":[{"selfLink":"3733f3d5-dc88-484e-9b39-870ced011eba"}],"t":1700126413.5033603},{"p":"ecc-gcp-104","l":"us-west-1","r":[{"selfLink":"3733f3d5-dc88-484e-9b39-870ced011eba"}],"t":1700126413.5033603},{"p":"ecc-gcp-107","l":"multiregion","r":[{"selfLink":"c36170f7-e8d2-4083-83e7-52806c94fcef"}],"t":1700126413.5033603},{"p":"ecc-gcp-107","l":"us-west-1","r":[{"selfLink":"c36170f7-e8d2-4083-83e7-52806c94fcef"}],"t":1700126413.5033603},{"p":"ecc-gcp-109","l":"multiregion","r":[{"selfLink":"489f9fc7-f883-41f0-ae39-948be157a564"}],"t":1700126413.5033603},{"p":"ecc-gcp-109","l":"us-west-1","r":[{"selfLink":"489f9fc7-f883-41f0-ae39-948be157a564"}],"t":1700126413.5033603},{"p":"ecc-gcp-110","l":"multiregion","r":[{"selfLink":"5fa1ef09-5022-42a8-8d82-e53acdbc533f"}],"t":1700126413.5033603},{"p":"ecc-gcp-110","l":"us-west-1","r":[{"selfLink":"5fa1ef09-5022-42a8-8d82-e53acdbc533f"}],"t":1700126413.5033603},{"p":"ecc-gcp-111","l":"multiregion","r":[{"selfLink":"cccdb085-1863-4129-9e08-b80f9ff1b73a"}],"t":1700126413.5033603},{"p":"ecc-gcp-111","l":"us-west-1","r":[{"selfLink":"cccdb085-1863-4129-9e08-b80f9ff1b73a"}],"t":1700126413.5033603},{"p":"ecc-gcp-112","l":"multiregion","r":[{"selfLink":"09c64ff8-7380-45ae-933a-c84fc173be28"}],"t":1700126413.5033603},{"p":"ecc-gcp-112","l":"us-west-1","r":[{"selfLink":"09c64ff8-7380-45ae-933a-c84fc173be28"}],"t":1700126413.5033603},{"p":"ecc-gcp-113","l":"multiregion","r":[{"selfLink":"75d40d92-1ef9-4ffc-bc3d-d2ced3e18807"}],"t":1700126413.5033603},{"p":"ecc-gcp-113","l":"us-west-1","r":[{"selfLink":"75d40d92-1ef9-4ffc-bc3d-d2ced3e18807"}],"t":1700126413.5033603},{"p":"ecc-gcp-114","l":"multiregion","r":[{"selfLink":"8cd02aea-8d49-4004-8590-fdbf382e0dcc"}],"t":1700126413.5033603},{"p":"ecc-gcp-114","l":"us-west-1","r":[{"selfLink":"8cd02aea-8d49-4004-8590-fdbf382e0dcc"}],"t":1700126413.5033603},{"p":"ecc-gcp-115","l":"multiregion","r":[{"selfLink":"9c492808-4619-47f3-9a65-9c17467e3881"}],"t":1700126413.5033603},{"p":"ecc-gcp-115","l":"us-west-1","r":[{"selfLink":"9c492808-4619-47f3-9a65-9c17467e3881"}],"t":1700126413.5033603},{"p":"ecc-gcp-116","l":"multiregion","r":[{"selfLink":"cdea7826-a643-4404-a224-a2b431a78ff8"}],"t":1700126413.5033603},{"p":"ecc-gcp-116","l":"us-west-1","r":[{"selfLink":"cdea7826-a643-4404-a224-a2b431a78ff8"}],"t":1700126413.5033603},{"p":"ecc-gcp-117","l":"multiregion","r":[{"selfLink":"4ef5b0d0-490b-4769-b89f-f2183bcdda73"}],"t":1700126413.5033603},{"p":"ecc-gcp-117","l":"us-west-1","r":[{"selfLink":"4ef5b0d0-490b-4769-b89f-f2183bcdda73"}],"t":1700126413.5033603},{"p":"ecc-gcp-118","l":"multiregion","r":[{"selfLink":"312ee341-e4c2-4931-85a6-0f139198718f"}],"t":1700126413.5033603},{"p":"ecc-gcp-118","l":"us-west-1","r":[{"selfLink":"312ee341-e4c2-4931-85a6-0f139198718f"}],"t":1700126413.5033603},{"p":"ecc-gcp-119","l":"multiregion","r":[{"selfLink":"ec404ed7-2239-4d90-82d5-4675070a1bc7"}],"t":1700126413.5033603},{"p":"ecc-gcp-119","l":"us-west-1","r":[{"selfLink":"ec404ed7-2239-4d90-82d5-4675070a1bc7"}],"t":1700126413.5033603},{"p":"ecc-gcp-120","l":"multiregion","r":[{"selfLink":"5268aace-a78b-4673-94e1-458d362fd23e"}],"t":1700126413.5033603},{"p":"ecc-gcp-120","l":"us-west-1","r":[{"selfLink":"5268aace-a78b-4673-94e1-458d362fd23e"}],"t":1700126413.5033603},{"p":"ecc-gcp-121","l":"multiregion","r":[{"selfLink":"082376ed-a1bc-4d7f-8402-6207d38180d2"}],"t":1700126413.5033603},{"p":"ecc-gcp-121","l":"us-west-1","r":[{"selfLink":"082376ed-a1bc-4d7f-8402-6207d38180d2"}],"t":1700126413.5033603},{"p":"ecc-gcp-122","l":"multiregion","r":[{"selfLink":"93ea74f2-ceae-4935-82c9-b9077aad63c5"}],"t":1700126413.5033603},{"p":"ecc-gcp-122","l":"us-west-1","r":[{"selfLink":"93ea74f2-ceae-4935-82c9-b9077aad63c5"}],"t":1700126413.5033603},{"p":"ecc-gcp-123","l":"multiregion","r":[{"selfLink":"e117c283-ec17-4c71-8279-d7ac6445885a"}],"t":1700126413.5033603},{"p":"ecc-gcp-123","l":"us-west-1","r":[{"selfLink":"e117c283-ec17-4c71-8279-d7ac6445885a"}],"t":1700126413.5033603},{"p":"ecc-gcp-124","l":"multiregion","r":[{"selfLink":"402be357-3e03-4f54-bcf6-85b0ceb60f56"}],"t":1700126413.5033603},{"p":"ecc-gcp-124","l":"us-west-1","r":[{"selfLink":"402be357-3e03-4f54-bcf6-85b0ceb60f56"}],"t":1700126413.5033603},{"p":"ecc-gcp-125","l":"multiregion","r":[{"selfLink":"1f010e13-cb49-4ad6-b34a-9f8a15666855"}],"t":1700126413.5033603},{"p":"ecc-gcp-125","l":"us-west-1","r":[{"selfLink":"1f010e13-cb49-4ad6-b34a-9f8a15666855"}],"t":1700126413.5033603},{"p":"ecc-gcp-126","l":"multiregion","r":[{"selfLink":"afa60137-57af-446d-8916-80553e9a95fd"}],"t":1700126413.5033603},{"p":"ecc-gcp-126","l":"us-west-1","r":[{"selfLink":"afa60137-57af-446d-8916-80553e9a95fd"}],"t":1700126413.5033603},{"p":"ecc-gcp-127","l":"multiregion","r":[{"selfLink":"ec4f00ff-ded4-435a-b816-4a38944bd681"}],"t":1700126413.5033603},{"p":"ecc-gcp-127","l":"us-west-1","r":[{"selfLink":"ec4f00ff-ded4-435a-b816-4a38944bd681"}],"t":1700126413.5033603},{"p":"ecc-gcp-128","l":"multiregion","r":[{"selfLink":"a810757c-ac72-42b2-98b9-6c49e168854b"}],"t":1700126413.5033603},{"p":"ecc-gcp-128","l":"us-west-1","r":[{"selfLink":"a810757c-ac72-42b2-98b9-6c49e168854b"}],"t":1700126413.5033603},{"p":"ecc-gcp-129","l":"multiregion","r":[{"selfLink":"a5d12233-d85f-4a5f-8211-5fe872606034"}],"t":1700126413.5033603},{"p":"ecc-gcp-129","l":"us-west-1","r":[{"selfLink":"a5d12233-d85f-4a5f-8211-5fe872606034"}],"t":1700126413.5033603},{"p":"ecc-gcp-130","l":"multiregion","r":[{"selfLink":"af89e968-9a98-4f1f-be54-5bde881592c5"}],"t":1700126413.5033603},{"p":"ecc-gcp-130","l":"us-west-1","r":[{"selfLink":"af89e968-9a98-4f1f-be54-5bde881592c5"}],"t":1700126413.5033603},{"p":"ecc-gcp-131","l":"multiregion","r":[{"selfLink":"5fc8dfea-d4c2-4f80-9ced-23027d837fb8"}],"t":1700126413.5033603},{"p":"ecc-gcp-131","l":"us-west-1","r":[{"selfLink":"5fc8dfea-d4c2-4f80-9ced-23027d837fb8"}],"t":1700126413.5033603},{"p":"ecc-gcp-132","l":"multiregion","r":[{"selfLink":"91fe1422-80e2-4196-a497-fe1633c1d4e1"}],"t":1700126413.5033603},{"p":"ecc-gcp-132","l":"us-west-1","r":[{"selfLink":"91fe1422-80e2-4196-a497-fe1633c1d4e1"}],"t":1700126413.5033603},{"p":"ecc-gcp-133","l":"multiregion","r":[{"selfLink":"dd0c4702-5745-47f9-93d1-4f6a22586bba"}],"t":1700126413.5033603},{"p":"ecc-gcp-133","l":"us-west-1","r":[{"selfLink":"dd0c4702-5745-47f9-93d1-4f6a22586bba"}],"t":1700126413.5033603},{"p":"ecc-gcp-134","l":"multiregion","r":[{"selfLink":"63023a7c-ddd5-4e8e-bcc3-674453811d84"}],"t":1700126413.5033603},{"p":"ecc-gcp-134","l":"us-west-1","r":[{"selfLink":"63023a7c-ddd5-4e8e-bcc3-674453811d84"}],"t":1700126413.5033603},{"p":"ecc-gcp-135","l":"multiregion","r":[{"selfLink":"3f241507-1fe4-4f90-9e82-b5005cf19fc2"}],"t":1700126413.5033603},{"p":"ecc-gcp-135","l":"us-west-1","r":[{"selfLink":"3f241507-1fe4-4f90-9e82-b5005cf19fc2"}],"t":1700126413.5033603},{"p":"ecc-gcp-136","l":"multiregion","r":[{"selfLink":"cb45dcc3-3b77-4bff-810b-d183c9cfe8ed"}],"t":1700126413.5033603},{"p":"ecc-gcp-136","l":"us-west-1","r":[{"selfLink":"cb45dcc3-3b77-4bff-810b-d183c9cfe8ed"}],"t":1700126413.5033603},{"p":"ecc-gcp-137","l":"multiregion","r":[{"selfLink":"3170e0ea-ad19-4fa3-afd1-a6b8cc77576e"}],"t":1700126413.5033603},{"p":"ecc-gcp-137","l":"us-west-1","r":[{"selfLink":"3170e0ea-ad19-4fa3-afd1-a6b8cc77576e"}],"t":1700126413.5033603},{"p":"ecc-gcp-138","l":"multiregion","r":[{"selfLink":"646b13ba-4241-4c28-846b-5d85ccb41b56"}],"t":1700126413.5033603},{"p":"ecc-gcp-138","l":"us-west-1","r":[{"selfLink":"646b13ba-4241-4c28-846b-5d85ccb41b56"}],"t":1700126413.5033603},{"p":"ecc-gcp-140","l":"multiregion","r":[{"selfLink":"5a3306fc-ac61-42c4-b5a7-fb58f7c4fff2"}],"t":1700126413.5033603},{"p":"ecc-gcp-140","l":"us-west-1","r":[{"selfLink":"5a3306fc-ac61-42c4-b5a7-fb58f7c4fff2"}],"t":1700126413.5033603},{"p":"ecc-gcp-141","l":"multiregion","r":[{"selfLink":"11e7d351-09b8-41e2-a51b-f609484d47fc"}],"t":1700126413.5033603},{"p":"ecc-gcp-141","l":"us-west-1","r":[{"selfLink":"11e7d351-09b8-41e2-a51b-f609484d47fc"}],"t":1700126413.5033603},{"p":"ecc-gcp-142","l":"multiregion","r":[{"selfLink":"30fc3177-b735-4ee9-b421-07eb46fd5fd2"}],"t":1700126413.5033603},{"p":"ecc-gcp-142","l":"us-west-1","r":[{"selfLink":"30fc3177-b735-4ee9-b421-07eb46fd5fd2"}],"t":1700126413.5033603},{"p":"ecc-gcp-143","l":"multiregion","r":[{"member":"e50cb5c1-ee9e-4bfd-988c-2f69b19eadfb","roles":"1949336d-6d58-4e06-a65a-5819a6f91ef3"}],"t":1700126413.5033603},{"p":"ecc-gcp-143","l":"us-west-1","r":[{"member":"e50cb5c1-ee9e-4bfd-988c-2f69b19eadfb","roles":"1949336d-6d58-4e06-a65a-5819a6f91ef3"}],"t":1700126413.5033603},{"p":"ecc-gcp-144","l":"multiregion","r":[{"selfLink":"5779b56f-048b-4e94-be01-e355b21cfe2c"}],"t":1700126413.5033603},{"p":"ecc-gcp-144","l":"us-west-1","r":[{"selfLink":"5779b56f-048b-4e94-be01-e355b21cfe2c"}],"t":1700126413.5033603},{"p":"ecc-gcp-150","l":"multiregion","r":[{"selfLink":"b8e066fb-7045-45cd-b1c9-6a8960a50ce2"}],"t":1700126413.5033603},{"p":"ecc-gcp-150","l":"us-west-1","r":[{"selfLink":"b8e066fb-7045-45cd-b1c9-6a8960a50ce2"}],"t":1700126413.5033603},{"p":"ecc-gcp-151","l":"multiregion","r":[{"selfLink":"b1d726bd-3963-49c9-b5b6-ca117a5dad10"}],"t":1700126413.5033603},{"p":"ecc-gcp-151","l":"us-west-1","r":[{"selfLink":"b1d726bd-3963-49c9-b5b6-ca117a5dad10"}],"t":1700126413.5033603},{"p":"ecc-gcp-152","l":"multiregion","r":[{"selfLink":"b772db17-d627-4538-8d13-1d8c8d80a97f"}],"t":1700126413.5033603},{"p":"ecc-gcp-152","l":"us-west-1","r":[{"selfLink":"b772db17-d627-4538-8d13-1d8c8d80a97f"}],"t":1700126413.5033603},{"p":"ecc-gcp-153","l":"multiregion","r":[{"selfLink":"f1724922-e89c-43e3-883c-56207f45cfc4"}],"t":1700126413.5033603},{"p":"ecc-gcp-153","l":"us-west-1","r":[{"selfLink":"f1724922-e89c-43e3-883c-56207f45cfc4"}],"t":1700126413.5033603},{"p":"ecc-gcp-162","l":"multiregion","r":[{"selfLink":"11d89a70-a759-48c9-9341-609f4d5fb8b4"}],"t":1700126413.5033603},{"p":"ecc-gcp-162","l":"us-west-1","r":[{"selfLink":"11d89a70-a759-48c9-9341-609f4d5fb8b4"}],"t":1700126413.5033603},{"p":"ecc-gcp-163","l":"multiregion","r":[{"selfLink":"0d8b5960-bf26-4cd1-9406-ac762e19ef21"}],"t":1700126413.5033603},{"p":"ecc-gcp-163","l":"us-west-1","r":[{"selfLink":"0d8b5960-bf26-4cd1-9406-ac762e19ef21"}],"t":1700126413.5033603},{"p":"ecc-gcp-165","l":"multiregion","r":[{"selfLink":"04ceaca7-5404-42e0-8d34-04e6789aa748"}],"t":1700126413.5033603},{"p":"ecc-gcp-165","l":"us-west-1","r":[{"selfLink":"04ceaca7-5404-42e0-8d34-04e6789aa748"}],"t":1700126413.5033603},{"p":"ecc-gcp-166","l":"multiregion","r":[{"selfLink":"f32c458c-24e6-41d1-9faf-98015fbec3bf"}],"t":1700126413.5033603},{"p":"ecc-gcp-166","l":"us-west-1","r":[{"selfLink":"f32c458c-24e6-41d1-9faf-98015fbec3bf"}],"t":1700126413.5033603},{"p":"ecc-gcp-167","l":"multiregion","r":[{"selfLink":"0bb02904-c984-46a4-9233-737704b1419d"}],"t":1700126413.5033603},{"p":"ecc-gcp-167","l":"us-west-1","r":[{"selfLink":"0bb02904-c984-46a4-9233-737704b1419d"}],"t":1700126413.5033603},{"p":"ecc-gcp-169","l":"multiregion","r":[{"name":"2b51b9b9-6eab-4913-9462-8bda87a00de3"}],"t":1700126413.5033603},{"p":"ecc-gcp-169","l":"us-west-1","r":[{"name":"2b51b9b9-6eab-4913-9462-8bda87a00de3"}],"t":1700126413.5033603},{"p":"ecc-gcp-170","l":"multiregion","r":[{"selfLink":"bea40735-0ef7-43f0-aa8f-a93e215a86a9"}],"t":1700126413.5033603},{"p":"ecc-gcp-170","l":"us-west-1","r":[{"selfLink":"bea40735-0ef7-43f0-aa8f-a93e215a86a9"}],"t":1700126413.5033603},{"p":"ecc-gcp-171","l":"multiregion","r":[{"selfLink":"4386e596-1ee8-497a-b5ad-6de749bcec39"}],"t":1700126413.5033603},{"p":"ecc-gcp-171","l":"us-west-1","r":[{"selfLink":"4386e596-1ee8-497a-b5ad-6de749bcec39"}],"t":1700126413.5033603},{"p":"ecc-gcp-172","l":"multiregion","r":[{"selfLink":"a73bdcee-f77d-46fd-bc60-511ea3b5165a"}],"t":1700126413.5033603},{"p":"ecc-gcp-172","l":"us-west-1","r":[{"selfLink":"a73bdcee-f77d-46fd-bc60-511ea3b5165a"}],"t":1700126413.5033603},{"p":"ecc-gcp-173","l":"multiregion","r":[{"selfLink":"3af3bff4-0a32-4b01-ae34-4b5f364f721f"}],"t":1700126413.5033603},{"p":"ecc-gcp-173","l":"us-west-1","r":[{"selfLink":"3af3bff4-0a32-4b01-ae34-4b5f364f721f"}],"t":1700126413.5033603},{"p":"ecc-gcp-175","l":"multiregion","r":[{"selfLink":"68526b74-f49a-4103-9b61-3861bc148fd0"}],"t":1700126413.5033603},{"p":"ecc-gcp-175","l":"us-west-1","r":[{"selfLink":"68526b74-f49a-4103-9b61-3861bc148fd0"}],"t":1700126413.5033603},{"p":"ecc-gcp-176","l":"multiregion","r":[{"selfLink":"b17fed92-eb1c-446a-bdae-e22820dc6faf"}],"t":1700126413.5033603},{"p":"ecc-gcp-176","l":"us-west-1","r":[{"selfLink":"b17fed92-eb1c-446a-bdae-e22820dc6faf"}],"t":1700126413.5033603},{"p":"ecc-gcp-177","l":"multiregion","r":[{"selfLink":"0b401991-905c-44bf-aee6-ed3215d25da3"}],"t":1700126413.5033603},{"p":"ecc-gcp-177","l":"us-west-1","r":[{"selfLink":"0b401991-905c-44bf-aee6-ed3215d25da3"}],"t":1700126413.5033603},{"p":"ecc-gcp-178","l":"multiregion","r":[{"selfLink":"e5d74e0c-eda5-4f62-9fc3-9640c8f65e4d"}],"t":1700126413.5033603},{"p":"ecc-gcp-178","l":"us-west-1","r":[{"selfLink":"e5d74e0c-eda5-4f62-9fc3-9640c8f65e4d"}],"t":1700126413.5033603},{"p":"ecc-gcp-179","l":"multiregion","r":[{"selfLink":"7e0a9e1d-ba06-49c3-8f7a-405d99e5ef94"}],"t":1700126413.5033603},{"p":"ecc-gcp-179","l":"us-west-1","r":[{"selfLink":"7e0a9e1d-ba06-49c3-8f7a-405d99e5ef94"}],"t":1700126413.5033603},{"p":"ecc-gcp-180","l":"multiregion","r":[{"selfLink":"9a7bde45-7dc0-4045-90a0-531ddd5313e9"}],"t":1700126413.5033603},{"p":"ecc-gcp-180","l":"us-west-1","r":[{"selfLink":"9a7bde45-7dc0-4045-90a0-531ddd5313e9"}],"t":1700126413.5033603},{"p":"ecc-gcp-181","l":"multiregion","r":[{"selfLink":"af99c6d7-0e56-4ede-96e0-27d73e326dbe"}],"t":1700126413.5033603},{"p":"ecc-gcp-181","l":"us-west-1","r":[{"selfLink":"af99c6d7-0e56-4ede-96e0-27d73e326dbe"}],"t":1700126413.5033603},{"p":"ecc-gcp-182","l":"multiregion","r":[{"selfLink":"16201e93-4e25-4b22-97e8-60d8963bc69e"}],"t":1700126413.5033603},{"p":"ecc-gcp-182","l":"us-west-1","r":[{"selfLink":"16201e93-4e25-4b22-97e8-60d8963bc69e"}],"t":1700126413.5033603},{"p":"ecc-gcp-183","l":"multiregion","r":[{"selfLink":"879cb5d3-4218-4533-91c7-5f598772eea9"}],"t":1700126413.5033603},{"p":"ecc-gcp-183","l":"us-west-1","r":[{"selfLink":"879cb5d3-4218-4533-91c7-5f598772eea9"}],"t":1700126413.5033603},{"p":"ecc-gcp-184","l":"multiregion","r":[{"selfLink":"6106ad45-4452-4843-91a0-93cfb6fa9f73"}],"t":1700126413.5033603},{"p":"ecc-gcp-184","l":"us-west-1","r":[{"selfLink":"6106ad45-4452-4843-91a0-93cfb6fa9f73"}],"t":1700126413.5033603},{"p":"ecc-gcp-185","l":"multiregion","r":[{"selfLink":"face2c40-189a-418c-b351-a471093ffb4f"}],"t":1700126413.5033603},{"p":"ecc-gcp-185","l":"us-west-1","r":[{"selfLink":"face2c40-189a-418c-b351-a471093ffb4f"}],"t":1700126413.5033603},{"p":"ecc-gcp-186","l":"multiregion","r":[{"selfLink":"8bdd8ae6-0ecf-4e3a-a726-55957f335d4c"}],"t":1700126413.5033603},{"p":"ecc-gcp-186","l":"us-west-1","r":[{"selfLink":"8bdd8ae6-0ecf-4e3a-a726-55957f335d4c"}],"t":1700126413.5033603},{"p":"ecc-gcp-187","l":"multiregion","r":[{"selfLink":"5789232d-fd08-4a11-a989-bebb6097d1aa"}],"t":1700126413.5033603},{"p":"ecc-gcp-187","l":"us-west-1","r":[{"selfLink":"5789232d-fd08-4a11-a989-bebb6097d1aa"}],"t":1700126413.5033603},{"p":"ecc-gcp-188","l":"multiregion","r":[{"selfLink":"0dc0df4e-d138-47f4-9522-a06acde10cc1"}],"t":1700126413.5033603},{"p":"ecc-gcp-188","l":"us-west-1","r":[{"selfLink":"0dc0df4e-d138-47f4-9522-a06acde10cc1"}],"t":1700126413.5033603},{"p":"ecc-gcp-189","l":"multiregion","r":[{"selfLink":"e4594c2e-a375-4ea3-88ad-592f756c6395"}],"t":1700126413.5033603},{"p":"ecc-gcp-189","l":"us-west-1","r":[{"selfLink":"e4594c2e-a375-4ea3-88ad-592f756c6395"}],"t":1700126413.5033603},{"p":"ecc-gcp-190","l":"multiregion","r":[{"selfLink":"10edf3bb-0e10-4807-8d0c-2476a5eedc05"}],"t":1700126413.5033603},{"p":"ecc-gcp-190","l":"us-west-1","r":[{"selfLink":"10edf3bb-0e10-4807-8d0c-2476a5eedc05"}],"t":1700126413.5033603},{"p":"ecc-gcp-191","l":"multiregion","r":[{"selfLink":"2e818a36-8462-4ef8-9836-a9db932c7e1f"}],"t":1700126413.5033603},{"p":"ecc-gcp-191","l":"us-west-1","r":[{"selfLink":"2e818a36-8462-4ef8-9836-a9db932c7e1f"}],"t":1700126413.5033603},{"p":"ecc-gcp-192","l":"multiregion","r":[{"selfLink":"f4b7e3c0-7a68-4b3d-ae37-e933a5deeab5"}],"t":1700126413.5033603},{"p":"ecc-gcp-192","l":"us-west-1","r":[{"selfLink":"f4b7e3c0-7a68-4b3d-ae37-e933a5deeab5"}],"t":1700126413.5033603},{"p":"ecc-gcp-193","l":"multiregion","r":[{"selfLink":"863c1049-f765-44ec-9b08-c20a4aa8e3af"}],"t":1700126413.5033603},{"p":"ecc-gcp-193","l":"us-west-1","r":[{"selfLink":"863c1049-f765-44ec-9b08-c20a4aa8e3af"}],"t":1700126413.5033603},{"p":"ecc-gcp-194","l":"multiregion","r":[{"selfLink":"58f8dd11-4f38-49b6-8706-2aa0b2cf9e25"}],"t":1700126413.5033603},{"p":"ecc-gcp-194","l":"us-west-1","r":[{"selfLink":"58f8dd11-4f38-49b6-8706-2aa0b2cf9e25"}],"t":1700126413.5033603},{"p":"ecc-gcp-195","l":"multiregion","r":[{"selfLink":"8c56747b-cd7b-429f-a4c3-c6dd313e5e78"}],"t":1700126413.5033603},{"p":"ecc-gcp-195","l":"us-west-1","r":[{"selfLink":"8c56747b-cd7b-429f-a4c3-c6dd313e5e78"}],"t":1700126413.5033603},{"p":"ecc-gcp-197","l":"multiregion","r":[{"selfLink":"b39e2327-e192-46ca-9ee2-adc944d3dbc9"}],"t":1700126413.5033603},{"p":"ecc-gcp-197","l":"us-west-1","r":[{"selfLink":"b39e2327-e192-46ca-9ee2-adc944d3dbc9"}],"t":1700126413.5033603},{"p":"ecc-gcp-198","l":"multiregion","r":[{"selfLink":"5a99c933-bf9d-4bde-8718-346b7c0b972f"}],"t":1700126413.5033603},{"p":"ecc-gcp-198","l":"us-west-1","r":[{"selfLink":"5a99c933-bf9d-4bde-8718-346b7c0b972f"}],"t":1700126413.5033603},{"p":"ecc-gcp-199","l":"multiregion","r":[{"selfLink":"8b521f27-5a9f-4ba3-9f1f-bd020de22de5"}],"t":1700126413.5033603},{"p":"ecc-gcp-199","l":"us-west-1","r":[{"selfLink":"8b521f27-5a9f-4ba3-9f1f-bd020de22de5"}],"t":1700126413.5033603},{"p":"ecc-gcp-200","l":"multiregion","r":[{"selfLink":"a7b5bc48-4ffc-4323-95ee-4702247fc976"}],"t":1700126413.5033603},{"p":"ecc-gcp-200","l":"us-west-1","r":[{"selfLink":"a7b5bc48-4ffc-4323-95ee-4702247fc976"}],"t":1700126413.5033603},{"p":"ecc-gcp-201","l":"multiregion","r":[{"selfLink":"ba06415b-e2bd-4adc-8f12-2a583e55ebc3"}],"t":1700126413.5033603},{"p":"ecc-gcp-201","l":"us-west-1","r":[{"selfLink":"ba06415b-e2bd-4adc-8f12-2a583e55ebc3"}],"t":1700126413.5033603},{"p":"ecc-gcp-202","l":"multiregion","r":[{"selfLink":"1658ce5d-8f91-429e-a77d-172f9288aaf9"}],"t":1700126413.5033603},{"p":"ecc-gcp-202","l":"us-west-1","r":[{"selfLink":"1658ce5d-8f91-429e-a77d-172f9288aaf9"}],"t":1700126413.5033603},{"p":"ecc-gcp-203","l":"multiregion","r":[{"selfLink":"44e16bae-1b51-4ae5-8cbc-73ab263a5b34"}],"t":1700126413.5033603},{"p":"ecc-gcp-203","l":"us-west-1","r":[{"selfLink":"44e16bae-1b51-4ae5-8cbc-73ab263a5b34"}],"t":1700126413.5033603},{"p":"ecc-gcp-204","l":"multiregion","r":[{"selfLink":"a6384d71-c654-4059-9d53-08efc85abc4d"}],"t":1700126413.5033603},{"p":"ecc-gcp-204","l":"us-west-1","r":[{"selfLink":"a6384d71-c654-4059-9d53-08efc85abc4d"}],"t":1700126413.5033603},{"p":"ecc-gcp-205","l":"multiregion","r":[{"selfLink":"0af64600-99fa-4e7e-966e-d69af30a0cdd"}],"t":1700126413.5033603},{"p":"ecc-gcp-205","l":"us-west-1","r":[{"selfLink":"0af64600-99fa-4e7e-966e-d69af30a0cdd"}],"t":1700126413.5033603},{"p":"ecc-gcp-206","l":"multiregion","r":[{"selfLink":"d6d30c5f-1a03-4655-873e-24478b7eb453"}],"t":1700126413.5033603},{"p":"ecc-gcp-206","l":"us-west-1","r":[{"selfLink":"d6d30c5f-1a03-4655-873e-24478b7eb453"}],"t":1700126413.5033603},{"p":"ecc-gcp-207","l":"multiregion","r":[{"selfLink":"46a5c0d7-50bb-4b92-b509-9d927e646635"}],"t":1700126413.5033603},{"p":"ecc-gcp-207","l":"us-west-1","r":[{"selfLink":"46a5c0d7-50bb-4b92-b509-9d927e646635"}],"t":1700126413.5033603},{"p":"ecc-gcp-208","l":"multiregion","r":[{"selfLink":"79dc703f-f959-41c5-b51d-42ff3a62baf1"}],"t":1700126413.5033603},{"p":"ecc-gcp-208","l":"us-west-1","r":[{"selfLink":"79dc703f-f959-41c5-b51d-42ff3a62baf1"}],"t":1700126413.5033603},{"p":"ecc-gcp-209","l":"multiregion","r":[{"selfLink":"38eae8e1-777b-4aa2-a071-448b949c022e"}],"t":1700126413.5033603},{"p":"ecc-gcp-209","l":"us-west-1","r":[{"selfLink":"38eae8e1-777b-4aa2-a071-448b949c022e"}],"t":1700126413.5033603},{"p":"ecc-gcp-210","l":"multiregion","r":[{"selfLink":"41967c70-a9df-4138-ae63-a9b8f4542ac1"}],"t":1700126413.5033603},{"p":"ecc-gcp-210","l":"us-west-1","r":[{"selfLink":"41967c70-a9df-4138-ae63-a9b8f4542ac1"}],"t":1700126413.5033603},{"p":"ecc-gcp-211","l":"multiregion","r":[{"selfLink":"b3b78070-66ad-4040-a215-4ce0ce0daae9"}],"t":1700126413.5033603},{"p":"ecc-gcp-211","l":"us-west-1","r":[{"selfLink":"b3b78070-66ad-4040-a215-4ce0ce0daae9"}],"t":1700126413.5033603},{"p":"ecc-gcp-212","l":"multiregion","r":[{"selfLink":"d7a43cca-a02c-455d-922c-a1ca0169b14c"}],"t":1700126413.5033603},{"p":"ecc-gcp-212","l":"us-west-1","r":[{"selfLink":"d7a43cca-a02c-455d-922c-a1ca0169b14c"}],"t":1700126413.5033603},{"p":"ecc-gcp-213","l":"multiregion","r":[{"id":"dbc0a015-efd0-4f11-8901-e2d8d3706959"}],"t":1700126413.5033603},{"p":"ecc-gcp-213","l":"us-west-1","r":[{"id":"dbc0a015-efd0-4f11-8901-e2d8d3706959"}],"t":1700126413.5033603},{"p":"ecc-gcp-214","l":"multiregion","r":[{"selfLink":"10064e0a-dbc6-4644-b129-7e01812220dd"}],"t":1700126413.5033603},{"p":"ecc-gcp-214","l":"us-west-1","r":[{"selfLink":"10064e0a-dbc6-4644-b129-7e01812220dd"}],"t":1700126413.5033603},{"p":"ecc-gcp-215","l":"multiregion","r":[{"selfLink":"180652b7-07a8-4508-aa37-5157a320116e"}],"t":1700126413.5033603},{"p":"ecc-gcp-215","l":"us-west-1","r":[{"selfLink":"180652b7-07a8-4508-aa37-5157a320116e"}],"t":1700126413.5033603},{"p":"ecc-gcp-216","l":"multiregion","r":[{"selfLink":"99bf9140-254b-4962-a0e4-818dfda55759"}],"t":1700126413.5033603},{"p":"ecc-gcp-216","l":"us-west-1","r":[{"selfLink":"99bf9140-254b-4962-a0e4-818dfda55759"}],"t":1700126413.5033603},{"p":"ecc-gcp-217","l":"multiregion","r":[{"selfLink":"490982b4-0359-4c7d-bf6f-b6c05b1adc02"}],"t":1700126413.5033603},{"p":"ecc-gcp-217","l":"us-west-1","r":[{"selfLink":"490982b4-0359-4c7d-bf6f-b6c05b1adc02"}],"t":1700126413.5033603},{"p":"ecc-gcp-218","l":"multiregion","r":[{"selfLink":"c18a9a8e-5fe1-4751-9f6b-59c1d31159e1"}],"t":1700126413.5033603},{"p":"ecc-gcp-218","l":"us-west-1","r":[{"selfLink":"c18a9a8e-5fe1-4751-9f6b-59c1d31159e1"}],"t":1700126413.5033603},{"p":"ecc-gcp-219","l":"multiregion","r":[{"selfLink":"1b50787c-919c-4f63-aa93-c5aba0a826e6"}],"t":1700126413.5033603},{"p":"ecc-gcp-219","l":"us-west-1","r":[{"selfLink":"1b50787c-919c-4f63-aa93-c5aba0a826e6"}],"t":1700126413.5033603},{"p":"ecc-gcp-220","l":"multiregion","r":[{"selfLink":"ab490a69-8504-49da-87fa-068a2d63e88d"}],"t":1700126413.5033603},{"p":"ecc-gcp-220","l":"us-west-1","r":[{"selfLink":"ab490a69-8504-49da-87fa-068a2d63e88d"}],"t":1700126413.5033603},{"p":"ecc-gcp-221","l":"multiregion","r":[{"selfLink":"fb030b31-9417-4769-89c7-2d7d0ae74499"}],"t":1700126413.5033603},{"p":"ecc-gcp-221","l":"us-west-1","r":[{"selfLink":"fb030b31-9417-4769-89c7-2d7d0ae74499"}],"t":1700126413.5033603},{"p":"ecc-gcp-222","l":"multiregion","r":[{"selfLink":"ee4c59c8-fed0-430d-a7c8-959fb712d539"}],"t":1700126413.5033603},{"p":"ecc-gcp-222","l":"us-west-1","r":[{"selfLink":"ee4c59c8-fed0-430d-a7c8-959fb712d539"}],"t":1700126413.5033603},{"p":"ecc-gcp-223","l":"multiregion","r":[{"selfLink":"46af3161-68d4-462e-9f38-a78e49a1139d"}],"t":1700126413.5033603},{"p":"ecc-gcp-223","l":"us-west-1","r":[{"selfLink":"46af3161-68d4-462e-9f38-a78e49a1139d"}],"t":1700126413.5033603},{"p":"ecc-gcp-225","l":"multiregion","r":[{"selfLink":"c2468010-b814-489a-8684-c0ed9974d6af"}],"t":1700126413.5033603},{"p":"ecc-gcp-225","l":"us-west-1","r":[{"selfLink":"c2468010-b814-489a-8684-c0ed9974d6af"}],"t":1700126413.5033603},{"p":"ecc-gcp-227","l":"multiregion","r":[{"selfLink":"4c58ada2-451c-436d-99ba-7e4b5161b210"}],"t":1700126413.5033603},{"p":"ecc-gcp-227","l":"us-west-1","r":[{"selfLink":"4c58ada2-451c-436d-99ba-7e4b5161b210"}],"t":1700126413.5033603},{"p":"ecc-gcp-228","l":"multiregion","r":[{"selfLink":"7ad6d605-dc03-4b3a-8e7d-6304147dfe2e"}],"t":1700126413.5033603},{"p":"ecc-gcp-228","l":"us-west-1","r":[{"selfLink":"7ad6d605-dc03-4b3a-8e7d-6304147dfe2e"}],"t":1700126413.5033603},{"p":"ecc-gcp-229","l":"multiregion","r":[{"selfLink":"a279aded-e2be-40d1-be49-b4d587d1ef5e"}],"t":1700126413.5033603},{"p":"ecc-gcp-229","l":"us-west-1","r":[{"selfLink":"a279aded-e2be-40d1-be49-b4d587d1ef5e"}],"t":1700126413.5033603},{"p":"ecc-gcp-230","l":"multiregion","r":[{"selfLink":"ead8aa80-7281-4537-90a2-a2c0199ec775"}],"t":1700126413.5033603},{"p":"ecc-gcp-230","l":"us-west-1","r":[{"selfLink":"ead8aa80-7281-4537-90a2-a2c0199ec775"}],"t":1700126413.5033603},{"p":"ecc-gcp-231","l":"multiregion","r":[{"selfLink":"005e0b4b-f924-416d-8db5-143eb37a7665"}],"t":1700126413.5033603},{"p":"ecc-gcp-231","l":"us-west-1","r":[{"selfLink":"005e0b4b-f924-416d-8db5-143eb37a7665"}],"t":1700126413.5033603},{"p":"ecc-gcp-232","l":"multiregion","r":[{"selfLink":"6b29d2d9-d535-426a-bfdd-8c9b587c3116"}],"t":1700126413.5033603},{"p":"ecc-gcp-232","l":"us-west-1","r":[{"selfLink":"6b29d2d9-d535-426a-bfdd-8c9b587c3116"}],"t":1700126413.5033603},{"p":"ecc-gcp-233","l":"multiregion","r":[{"selfLink":"58ca8bc2-7a44-48bf-89c0-95a237e506c9"}],"t":1700126413.5033603},{"p":"ecc-gcp-233","l":"us-west-1","r":[{"selfLink":"58ca8bc2-7a44-48bf-89c0-95a237e506c9"}],"t":1700126413.5033603},{"p":"ecc-gcp-234","l":"multiregion","r":[{"selfLink":"20416141-dc8b-46d7-98f5-2ec4bf733662"}],"t":1700126413.5033603},{"p":"ecc-gcp-234","l":"us-west-1","r":[{"selfLink":"20416141-dc8b-46d7-98f5-2ec4bf733662"}],"t":1700126413.5033603},{"p":"ecc-gcp-236","l":"multiregion","r":[{"selfLink":"959731c7-d57e-4350-8ece-1ff291fc3ec3"}],"t":1700126413.5033603},{"p":"ecc-gcp-236","l":"us-west-1","r":[{"selfLink":"959731c7-d57e-4350-8ece-1ff291fc3ec3"}],"t":1700126413.5033603},{"p":"ecc-gcp-237","l":"multiregion","r":[{"selfLink":"3fe3e2d2-7b86-4336-b779-c44a263908f2"}],"t":1700126413.5033603},{"p":"ecc-gcp-237","l":"us-west-1","r":[{"selfLink":"3fe3e2d2-7b86-4336-b779-c44a263908f2"}],"t":1700126413.5033603},{"p":"ecc-gcp-239","l":"multiregion","r":[{"selfLink":"f3a0ce62-8d1b-46ae-bba2-80d91b13d1b1"}],"t":1700126413.5033603},{"p":"ecc-gcp-239","l":"us-west-1","r":[{"selfLink":"f3a0ce62-8d1b-46ae-bba2-80d91b13d1b1"}],"t":1700126413.5033603},{"p":"ecc-gcp-240","l":"multiregion","r":[{"selfLink":"a57fb70f-207a-45a8-91ef-2ed436a1de99"}],"t":1700126413.5033603},{"p":"ecc-gcp-240","l":"us-west-1","r":[{"selfLink":"a57fb70f-207a-45a8-91ef-2ed436a1de99"}],"t":1700126413.5033603},{"p":"ecc-gcp-241","l":"multiregion","r":[{"name":"77cf91e6-b3c6-443a-ae1e-53ecad74ed66"}],"t":1700126413.5033603},{"p":"ecc-gcp-241","l":"us-west-1","r":[{"name":"77cf91e6-b3c6-443a-ae1e-53ecad74ed66"}],"t":1700126413.5033603},{"p":"ecc-gcp-242","l":"multiregion","r":[{"name":"030ade0a-68f3-4c0e-b928-0712fbf9271e"}],"t":1700126413.5033603},{"p":"ecc-gcp-242","l":"us-west-1","r":[{"name":"030ade0a-68f3-4c0e-b928-0712fbf9271e"}],"t":1700126413.5033603},{"p":"ecc-gcp-243","l":"multiregion","r":[{"name":"c07c8a98-b278-4260-b401-932a6ab72690"}],"t":1700126413.5033603},{"p":"ecc-gcp-243","l":"us-west-1","r":[{"name":"c07c8a98-b278-4260-b401-932a6ab72690"}],"t":1700126413.5033603},{"p":"ecc-gcp-244","l":"multiregion","r":[{"name":"bcc2c3f7-1148-403b-9cb3-fbf4d3c57a14"}],"t":1700126413.5033603},{"p":"ecc-gcp-244","l":"us-west-1","r":[{"name":"bcc2c3f7-1148-403b-9cb3-fbf4d3c57a14"}],"t":1700126413.5033603},{"p":"ecc-gcp-245","l":"multiregion","r":[{"name":"72b20c9c-a42a-45d6-90ef-0a67df3554c7"}],"t":1700126413.5033603},{"p":"ecc-gcp-245","l":"us-west-1","r":[{"name":"72b20c9c-a42a-45d6-90ef-0a67df3554c7"}],"t":1700126413.5033603},{"p":"ecc-gcp-246","l":"multiregion","r":[{"name":"1a929b53-f02b-4239-90b5-6cdd3ed39f34"}],"t":1700126413.5033603},{"p":"ecc-gcp-246","l":"us-west-1","r":[{"name":"1a929b53-f02b-4239-90b5-6cdd3ed39f34"}],"t":1700126413.5033603},{"p":"ecc-gcp-247","l":"multiregion","r":[{"name":"4517d322-12a5-4356-ac83-5c7209c7491a"}],"t":1700126413.5033603},{"p":"ecc-gcp-247","l":"us-west-1","r":[{"name":"4517d322-12a5-4356-ac83-5c7209c7491a"}],"t":1700126413.5033603},{"p":"ecc-gcp-248","l":"multiregion","r":[{"name":"8e5fa20d-aa04-4926-853e-3314aee8e1ee"}],"t":1700126413.5033603},{"p":"ecc-gcp-248","l":"us-west-1","r":[{"name":"8e5fa20d-aa04-4926-853e-3314aee8e1ee"}],"t":1700126413.5033603},{"p":"ecc-gcp-249","l":"multiregion","r":[{"name":"4ed6abcd-8764-44eb-a819-3bd80772f1ba"}],"t":1700126413.5033603},{"p":"ecc-gcp-249","l":"us-west-1","r":[{"name":"4ed6abcd-8764-44eb-a819-3bd80772f1ba"}],"t":1700126413.5033603},{"p":"ecc-gcp-250","l":"multiregion","r":[{"projectNumber":"d4a90dd7-7545-47f5-ad14-d078014b4189","projectId":"902562dc-4331-49d6-af83-fd843ce22165"}],"t":1700126413.5033603},{"p":"ecc-gcp-250","l":"us-west-1","r":[{"projectNumber":"d4a90dd7-7545-47f5-ad14-d078014b4189","projectId":"902562dc-4331-49d6-af83-fd843ce22165"}],"t":1700126413.5033603},{"p":"ecc-gcp-251","l":"multiregion","r":[{"selfLink":"b9637ee9-4234-4cf5-b1c9-ed6dfe7be7d8"}],"t":1700126413.5033603},{"p":"ecc-gcp-251","l":"us-west-1","r":[{"selfLink":"b9637ee9-4234-4cf5-b1c9-ed6dfe7be7d8"}],"t":1700126413.5033603},{"p":"ecc-gcp-252","l":"multiregion","r":[{"selfLink":"b3f28007-7761-4c68-abe9-e3a3bb39b8f9"}],"t":1700126413.5033603},{"p":"ecc-gcp-252","l":"us-west-1","r":[{"selfLink":"b3f28007-7761-4c68-abe9-e3a3bb39b8f9"}],"t":1700126413.5033603},{"p":"ecc-gcp-253","l":"multiregion","r":[{"selfLink":"b315d060-638f-47cb-a688-9500d8bce60f"}],"t":1700126413.5033603},{"p":"ecc-gcp-253","l":"us-west-1","r":[{"selfLink":"b315d060-638f-47cb-a688-9500d8bce60f"}],"t":1700126413.5033603},{"p":"ecc-gcp-254","l":"multiregion","r":[{"name":"77761cd2-df5b-4b39-a7ee-1de3251f0feb"}],"t":1700126413.5033603},{"p":"ecc-gcp-254","l":"us-west-1","r":[{"name":"77761cd2-df5b-4b39-a7ee-1de3251f0feb"}],"t":1700126413.5033603},{"p":"ecc-gcp-256","l":"multiregion","r":[{"description":"687a6bfe-d11e-4d23-ab0a-000e852ec369"}],"t":1700126413.5033603},{"p":"ecc-gcp-256","l":"us-west-1","r":[{"description":"687a6bfe-d11e-4d23-ab0a-000e852ec369"}],"t":1700126413.5033603},{"p":"ecc-gcp-257","l":"multiregion","r":[{"name":"40e82d3a-ee7f-4859-94ed-5ba60ce75794"}],"t":1700126413.5033603},{"p":"ecc-gcp-257","l":"us-west-1","r":[{"name":"40e82d3a-ee7f-4859-94ed-5ba60ce75794"}],"t":1700126413.5033603},{"p":"ecc-gcp-258","l":"multiregion","r":[{"sourceTable":"92ff8002-c2df-49fa-a98e-420b1edd4d38","name":"9dac9a33-26e2-442f-a32b-917f911961bd"}],"t":1700126413.5033603},{"p":"ecc-gcp-258","l":"us-west-1","r":[{"sourceTable":"92ff8002-c2df-49fa-a98e-420b1edd4d38","name":"9dac9a33-26e2-442f-a32b-917f911961bd"}],"t":1700126413.5033603},{"p":"ecc-gcp-260","l":"multiregion","r":[{"selfLink":"c9d666b0-6646-428a-8180-6acd835aa846"}],"t":1700126413.5033603},{"p":"ecc-gcp-260","l":"us-west-1","r":[{"selfLink":"c9d666b0-6646-428a-8180-6acd835aa846"}],"t":1700126413.5033603},{"p":"ecc-gcp-261","l":"multiregion","r":[{"clusterName":"23581387-67ba-44b7-be35-756c3225aece","projectId":"d2769103-cb6e-4360-b061-156759b7a8c8"}],"t":1700126413.5033603},{"p":"ecc-gcp-261","l":"us-west-1","r":[{"clusterName":"23581387-67ba-44b7-be35-756c3225aece","projectId":"d2769103-cb6e-4360-b061-156759b7a8c8"}],"t":1700126413.5033603},{"p":"ecc-gcp-262","l":"multiregion","r":[{"metadata.selfLink":"403a6995-c538-43fd-a329-2b8c53b7aca5"}],"t":1700126413.5033603},{"p":"ecc-gcp-262","l":"us-west-1","r":[{"metadata.selfLink":"403a6995-c538-43fd-a329-2b8c53b7aca5"}],"t":1700126413.5033603},{"p":"ecc-gcp-263","l":"multiregion","r":[{"metadata.selfLink":"3db771f0-62ff-40b2-a301-a5e44aff2eb4"}],"t":1700126413.5033603},{"p":"ecc-gcp-263","l":"us-west-1","r":[{"metadata.selfLink":"3db771f0-62ff-40b2-a301-a5e44aff2eb4"}],"t":1700126413.5033603},{"p":"ecc-gcp-264","l":"multiregion","r":[{"metadata.selfLink":"6445468d-a166-4a3a-af12-35e8c00ab328"}],"t":1700126413.5033603},{"p":"ecc-gcp-264","l":"us-west-1","r":[{"metadata.selfLink":"6445468d-a166-4a3a-af12-35e8c00ab328"}],"t":1700126413.5033603},{"p":"ecc-gcp-265","l":"multiregion","r":[{"metadata.selfLink":"9b1bb83f-421b-428d-ab2d-4e240d519c9e"}],"t":1700126413.5033603},{"p":"ecc-gcp-265","l":"us-west-1","r":[{"metadata.selfLink":"9b1bb83f-421b-428d-ab2d-4e240d519c9e"}],"t":1700126413.5033603},{"p":"ecc-gcp-266","l":"multiregion","r":[{"metadata.selfLink":"b8bb80fa-d07b-4e06-85f4-194f593280d0"}],"t":1700126413.5033603},{"p":"ecc-gcp-266","l":"us-west-1","r":[{"metadata.selfLink":"b8bb80fa-d07b-4e06-85f4-194f593280d0"}],"t":1700126413.5033603},{"p":"ecc-gcp-268","l":"multiregion","r":[{"metadata.selfLink":"a39e9bd8-d67f-41c3-aad0-a5e2cdf994c5"}],"t":1700126413.5033603},{"p":"ecc-gcp-268","l":"us-west-1","r":[{"metadata.selfLink":"a39e9bd8-d67f-41c3-aad0-a5e2cdf994c5"}],"t":1700126413.5033603},{"p":"ecc-gcp-271","l":"multiregion","r":[{"selfLink":"4092e167-286c-406e-9a1d-a6ac14f08f79"}],"t":1700126413.5033603},{"p":"ecc-gcp-271","l":"us-west-1","r":[{"selfLink":"4092e167-286c-406e-9a1d-a6ac14f08f79"}],"t":1700126413.5033603},{"p":"ecc-gcp-272","l":"multiregion","r":[{"selfLink":"db96fddd-65ac-40dd-abd6-dc30c32a0696"}],"t":1700126413.5033603},{"p":"ecc-gcp-272","l":"us-west-1","r":[{"selfLink":"db96fddd-65ac-40dd-abd6-dc30c32a0696"}],"t":1700126413.5033603},{"p":"ecc-gcp-273","l":"multiregion","r":[{"selfLink":"7004cc26-844a-4f2e-ba43-57b86555955a"}],"t":1700126413.5033603},{"p":"ecc-gcp-273","l":"us-west-1","r":[{"selfLink":"7004cc26-844a-4f2e-ba43-57b86555955a"}],"t":1700126413.5033603},{"p":"ecc-gcp-274","l":"multiregion","r":[{"selfLink":"ed0b7534-9649-4e1e-bdea-37b2c24ecc8a"}],"t":1700126413.5033603},{"p":"ecc-gcp-274","l":"us-west-1","r":[{"selfLink":"ed0b7534-9649-4e1e-bdea-37b2c24ecc8a"}],"t":1700126413.5033603},{"p":"ecc-gcp-276","l":"multiregion","r":[{"selfLink":"075aca83-ecdc-47ff-8adc-3664eef34cab"}],"t":1700126413.5033603},{"p":"ecc-gcp-276","l":"us-west-1","r":[{"selfLink":"075aca83-ecdc-47ff-8adc-3664eef34cab"}],"t":1700126413.5033603},{"p":"ecc-gcp-277","l":"multiregion","r":[{"name":"020e2968-f004-440d-b514-43d4d52bb23e"}],"t":1700126413.5033603},{"p":"ecc-gcp-277","l":"us-west-1","r":[{"name":"020e2968-f004-440d-b514-43d4d52bb23e"}],"t":1700126413.5033603},{"p":"ecc-gcp-278","l":"multiregion","r":[{"selfLink":"14ba9e8e-ef35-4ace-91bb-3824b5a5b772"}],"t":1700126413.5033603},{"p":"ecc-gcp-278","l":"us-west-1","r":[{"selfLink":"14ba9e8e-ef35-4ace-91bb-3824b5a5b772"}],"t":1700126413.5033603},{"p":"ecc-gcp-279","l":"multiregion","r":[{"selfLink":"9df01291-9024-45fb-924b-b76a69158fab"}],"t":1700126413.5033603},{"p":"ecc-gcp-279","l":"us-west-1","r":[{"selfLink":"9df01291-9024-45fb-924b-b76a69158fab"}],"t":1700126413.5033603},{"p":"ecc-gcp-280","l":"multiregion","r":[{"selfLink":"938fbada-84e7-465d-ad9a-f0143161d175"}],"t":1700126413.5033603},{"p":"ecc-gcp-280","l":"us-west-1","r":[{"selfLink":"938fbada-84e7-465d-ad9a-f0143161d175"}],"t":1700126413.5033603},{"p":"ecc-gcp-281","l":"multiregion","r":[{"selfLink":"55d0ad01-cb5e-43c1-ae6d-89d39f33ed53"}],"t":1700126413.5033603},{"p":"ecc-gcp-281","l":"us-west-1","r":[{"selfLink":"55d0ad01-cb5e-43c1-ae6d-89d39f33ed53"}],"t":1700126413.5033603},{"p":"ecc-gcp-282","l":"multiregion","r":[{"selfLink":"585455b5-99d2-4d65-80cd-854dccf7ea63"}],"t":1700126413.5033603},{"p":"ecc-gcp-282","l":"us-west-1","r":[{"selfLink":"585455b5-99d2-4d65-80cd-854dccf7ea63"}],"t":1700126413.5033603},{"p":"ecc-gcp-283","l":"multiregion","r":[{"selfLink":"79e6efbb-634d-460a-af91-de79068da8f7"}],"t":1700126413.5033603},{"p":"ecc-gcp-283","l":"us-west-1","r":[{"selfLink":"79e6efbb-634d-460a-af91-de79068da8f7"}],"t":1700126413.5033603},{"p":"ecc-gcp-285","l":"multiregion","r":[{"selfLink":"364bde2d-4c69-4ad4-9037-581bd53ab7d9"}],"t":1700126413.5033603},{"p":"ecc-gcp-285","l":"us-west-1","r":[{"selfLink":"364bde2d-4c69-4ad4-9037-581bd53ab7d9"}],"t":1700126413.5033603},{"p":"ecc-gcp-286","l":"multiregion","r":[{"name":"592b1a67-e654-4d38-8c87-7a894edec15b"}],"t":1700126413.5033603},{"p":"ecc-gcp-286","l":"us-west-1","r":[{"name":"592b1a67-e654-4d38-8c87-7a894edec15b"}],"t":1700126413.5033603},{"p":"ecc-gcp-287","l":"multiregion","r":[{"name":"ea10b519-77a2-45be-b395-e6e4005404ce"}],"t":1700126413.5033603},{"p":"ecc-gcp-287","l":"us-west-1","r":[{"name":"ea10b519-77a2-45be-b395-e6e4005404ce"}],"t":1700126413.5033603},{"p":"ecc-gcp-288","l":"multiregion","r":[{"selfLink":"bd3a9f46-3d0c-4418-8eda-e419d6aee01a"}],"t":1700126413.5033603},{"p":"ecc-gcp-288","l":"us-west-1","r":[{"selfLink":"bd3a9f46-3d0c-4418-8eda-e419d6aee01a"}],"t":1700126413.5033603},{"p":"ecc-gcp-289","l":"multiregion","r":[{"selfLink":"dfdc5b80-ba62-4f09-a350-491eeac96768"}],"t":1700126413.5033603},{"p":"ecc-gcp-289","l":"us-west-1","r":[{"selfLink":"dfdc5b80-ba62-4f09-a350-491eeac96768"}],"t":1700126413.5033603},{"p":"ecc-gcp-291","l":"multiregion","r":[{"name":"81aff71c-4748-4f0c-ad23-0d922e5f32f2"}],"t":1700126413.5033603},{"p":"ecc-gcp-291","l":"us-west-1","r":[{"name":"81aff71c-4748-4f0c-ad23-0d922e5f32f2"}],"t":1700126413.5033603},{"p":"ecc-gcp-292","l":"multiregion","r":[{"name":"2c795259-c476-467d-ba92-675ff651b0c5"}],"t":1700126413.5033603},{"p":"ecc-gcp-292","l":"us-west-1","r":[{"name":"2c795259-c476-467d-ba92-675ff651b0c5"}],"t":1700126413.5033603},{"p":"ecc-gcp-293","l":"multiregion","r":[{"name":"e939c47e-9b4a-4f97-9dbd-94d00f5758cf"}],"t":1700126413.5033603},{"p":"ecc-gcp-293","l":"us-west-1","r":[{"name":"e939c47e-9b4a-4f97-9dbd-94d00f5758cf"}],"t":1700126413.5033603},{"p":"ecc-gcp-294","l":"multiregion","r":[{"name":"dbabc9e8-470c-442f-ab28-4362534125ec"}],"t":1700126413.5033603},{"p":"ecc-gcp-294","l":"us-west-1","r":[{"name":"dbabc9e8-470c-442f-ab28-4362534125ec"}],"t":1700126413.5033603},{"p":"ecc-gcp-295","l":"multiregion","r":[{"name":"0fa309da-5505-49fb-99bb-652ac6d80de4"}],"t":1700126413.5033603},{"p":"ecc-gcp-295","l":"us-west-1","r":[{"name":"0fa309da-5505-49fb-99bb-652ac6d80de4"}],"t":1700126413.5033603},{"p":"ecc-gcp-298","l":"multiregion","r":[{"selfLink":"a49c747f-c9e8-4c1e-8ec2-a59241e56b51"}],"t":1700126413.5033603},{"p":"ecc-gcp-298","l":"us-west-1","r":[{"selfLink":"a49c747f-c9e8-4c1e-8ec2-a59241e56b51"}],"t":1700126413.5033603},{"p":"ecc-gcp-299","l":"multiregion","r":[{"selfLink":"66cf0a20-1baf-4ebb-a77a-273fc1469f35"}],"t":1700126413.5033603},{"p":"ecc-gcp-299","l":"us-west-1","r":[{"selfLink":"66cf0a20-1baf-4ebb-a77a-273fc1469f35"}],"t":1700126413.5033603},{"p":"ecc-gcp-300","l":"multiregion","r":[{"name":"84720e99-05f3-4df1-a119-ce13fe38b3d5"}],"t":1700126413.5033603},{"p":"ecc-gcp-300","l":"us-west-1","r":[{"name":"84720e99-05f3-4df1-a119-ce13fe38b3d5"}],"t":1700126413.5033603},{"p":"ecc-gcp-302","l":"multiregion","r":[{"selflink":"df3c2dcc-bb8b-49da-8557-cb0255c50f00"}],"t":1700126413.5033603},{"p":"ecc-gcp-302","l":"us-west-1","r":[{"selflink":"df3c2dcc-bb8b-49da-8557-cb0255c50f00"}],"t":1700126413.5033603},{"p":"ecc-gcp-303","l":"multiregion","r":[{"projectId":"e5b60f17-ead6-4ddf-9de1-3285cba8879b","id":"31c0e8a4-eefd-40aa-8fc7-29680b8af3fc"}],"t":1700126413.5033603},{"p":"ecc-gcp-303","l":"us-west-1","r":[{"projectId":"e5b60f17-ead6-4ddf-9de1-3285cba8879b","id":"31c0e8a4-eefd-40aa-8fc7-29680b8af3fc"}],"t":1700126413.5033603},{"p":"ecc-gcp-304","l":"multiregion","r":[{"name":"365bd848-59b2-4be1-aa8b-f5fa7c62ec26"}],"t":1700126413.5033603},{"p":"ecc-gcp-304","l":"us-west-1","r":[{"name":"365bd848-59b2-4be1-aa8b-f5fa7c62ec26"}],"t":1700126413.5033603},{"p":"ecc-gcp-305","l":"multiregion","r":[{"clusterName":"7f15204e-ace8-4991-a760-35b019f9e609","projectId":"258fe777-7044-4ee2-89cb-281707ee0f9b"}],"t":1700126413.5033603},{"p":"ecc-gcp-305","l":"us-west-1","r":[{"clusterName":"7f15204e-ace8-4991-a760-35b019f9e609","projectId":"258fe777-7044-4ee2-89cb-281707ee0f9b"}],"t":1700126413.5033603},{"p":"ecc-gcp-306","l":"multiregion","r":[{"name":"793ca9c4-3a5c-4444-bd0b-30ed55060daa"}],"t":1700126413.5033603},{"p":"ecc-gcp-306","l":"us-west-1","r":[{"name":"793ca9c4-3a5c-4444-bd0b-30ed55060daa"}],"t":1700126413.5033603},{"p":"ecc-gcp-307","l":"multiregion","r":[{"name":"8ad44856-a8b5-45fb-a2b3-966e288166be"}],"t":1700126413.5033603},{"p":"ecc-gcp-307","l":"us-west-1","r":[{"name":"8ad44856-a8b5-45fb-a2b3-966e288166be"}],"t":1700126413.5033603},{"p":"ecc-gcp-309","l":"multiregion","r":[{"clusterName":"af5f4c93-50ad-4f20-b10e-b6addbeb9d69","projectId":"740a7dff-cc96-4eb0-9b12-06a3e2a0508c"}],"t":1700126413.5033603},{"p":"ecc-gcp-309","l":"us-west-1","r":[{"clusterName":"af5f4c93-50ad-4f20-b10e-b6addbeb9d69","projectId":"740a7dff-cc96-4eb0-9b12-06a3e2a0508c"}],"t":1700126413.5033603},{"p":"ecc-gcp-310","l":"multiregion","r":[{"name":"043cdb06-203f-40ef-b976-e1bac6a74b1f"}],"t":1700126413.5033603},{"p":"ecc-gcp-310","l":"us-west-1","r":[{"name":"043cdb06-203f-40ef-b976-e1bac6a74b1f"}],"t":1700126413.5033603},{"p":"ecc-gcp-311","l":"multiregion","r":[{"name":"3947fe84-045a-4f75-91f4-47aadde0b141"}],"t":1700126413.5033603},{"p":"ecc-gcp-311","l":"us-west-1","r":[{"name":"3947fe84-045a-4f75-91f4-47aadde0b141"}],"t":1700126413.5033603},{"p":"ecc-gcp-312","l":"multiregion","r":[{"name":"d0026a73-7c0b-4d55-9b4b-c881a85475bb"}],"t":1700126413.5033603},{"p":"ecc-gcp-312","l":"us-west-1","r":[{"name":"d0026a73-7c0b-4d55-9b4b-c881a85475bb"}],"t":1700126413.5033603},{"p":"ecc-gcp-313","l":"multiregion","r":[{"projectId":"86d79c71-85c5-46c3-b3a9-d8187d3cb9b4"}],"t":1700126413.5033603},{"p":"ecc-gcp-313","l":"us-west-1","r":[{"projectId":"86d79c71-85c5-46c3-b3a9-d8187d3cb9b4"}],"t":1700126413.5033603},{"p":"ecc-gcp-314","l":"multiregion","r":[{"projectId":"b096be14-b653-467b-8030-98d17c1b42ca"}],"t":1700126413.5033603},{"p":"ecc-gcp-314","l":"us-west-1","r":[{"projectId":"b096be14-b653-467b-8030-98d17c1b42ca"}],"t":1700126413.5033603},{"p":"ecc-gcp-315","l":"multiregion","r":[{"selfLink":"b87f92ec-82f1-46e2-bf4a-2d899196998c"}],"t":1700126413.5033603},{"p":"ecc-gcp-315","l":"us-west-1","r":[{"selfLink":"b87f92ec-82f1-46e2-bf4a-2d899196998c"}],"t":1700126413.5033603},{"p":"ecc-gcp-316","l":"multiregion","r":[{"name":"ad5b5df9-77cd-45c7-8991-7549110af482"}],"t":1700126413.5033603},{"p":"ecc-gcp-316","l":"us-west-1","r":[{"name":"ad5b5df9-77cd-45c7-8991-7549110af482"}],"t":1700126413.5033603},{"p":"ecc-gcp-317","l":"multiregion","r":[{"name":"2bd281f4-9aba-43c9-861b-baa4b64aa514"}],"t":1700126413.5033603},{"p":"ecc-gcp-317","l":"us-west-1","r":[{"name":"2bd281f4-9aba-43c9-861b-baa4b64aa514"}],"t":1700126413.5033603},{"p":"ecc-gcp-318","l":"multiregion","r":[{"name":"ed70f364-15e4-4a28-8ce5-ec69c8786bbc"}],"t":1700126413.5033603},{"p":"ecc-gcp-318","l":"us-west-1","r":[{"name":"ed70f364-15e4-4a28-8ce5-ec69c8786bbc"}],"t":1700126413.5033603},{"p":"ecc-gcp-323","l":"multiregion","r":[{"member":"9ed93735-a136-4e96-9369-48c46db1e159","roles":"1d28d162-129a-4fec-a46c-1e4846eee626"}],"t":1700126413.5033603},{"p":"ecc-gcp-323","l":"us-west-1","r":[{"member":"9ed93735-a136-4e96-9369-48c46db1e159","roles":"1d28d162-129a-4fec-a46c-1e4846eee626"}],"t":1700126413.5033603},{"p":"ecc-gcp-324","l":"multiregion","r":[{"member":"8083ee8d-9700-44cd-9cc9-08ec58dec4cf","roles":"0d84084e-ba0c-413d-ad9b-8808ace08310"}],"t":1700126413.5033603},{"p":"ecc-gcp-324","l":"us-west-1","r":[{"member":"8083ee8d-9700-44cd-9cc9-08ec58dec4cf","roles":"0d84084e-ba0c-413d-ad9b-8808ace08310"}],"t":1700126413.5033603},{"p":"ecc-gcp-334","l":"multiregion","r":[{"c7n:service-account.name":"9e81a89a-9ce6-4fb8-a57e-4d1c5559b615","members":"768929ee-4de6-44cf-9925-d7afeb84f553"}],"t":1700126413.5033603},{"p":"ecc-gcp-334","l":"us-west-1","r":[{"c7n:service-account.name":"9e81a89a-9ce6-4fb8-a57e-4d1c5559b615","members":"768929ee-4de6-44cf-9925-d7afeb84f553"}],"t":1700126413.5033603},{"p":"ecc-gcp-335","l":"multiregion","r":[{"c7n:service-account.name":"30350958-94f1-4357-aeeb-67123f9f8bba","members":"6f256d99-5054-491b-a2e7-1318baffc5cc"}],"t":1700126413.5033603},{"p":"ecc-gcp-335","l":"us-west-1","r":[{"c7n:service-account.name":"30350958-94f1-4357-aeeb-67123f9f8bba","members":"6f256d99-5054-491b-a2e7-1318baffc5cc"}],"t":1700126413.5033603},{"p":"ecc-gcp-337","l":"multiregion","r":[{"selfLink":"5059c8cc-39e5-499c-9f31-783e59471748"}],"t":1700126413.5033603},{"p":"ecc-gcp-337","l":"us-west-1","r":[{"selfLink":"5059c8cc-39e5-499c-9f31-783e59471748"}],"t":1700126413.5033603},{"p":"ecc-gcp-340","l":"multiregion","r":[{"selflink":"6c7d89b9-992c-4ec2-8020-c6934b155261"}],"t":1700126413.5033603},{"p":"ecc-gcp-340","l":"us-west-1","r":[{"selflink":"6c7d89b9-992c-4ec2-8020-c6934b155261"}],"t":1700126413.5033603},{"p":"ecc-gcp-342","l":"multiregion","r":[{"selfLink":"90103b34-c64c-4b25-b67f-81fd90530ce7"}],"t":1700126413.5033603},{"p":"ecc-gcp-342","l":"us-west-1","r":[{"selfLink":"90103b34-c64c-4b25-b67f-81fd90530ce7"}],"t":1700126413.5033603},{"p":"ecc-gcp-346","l":"multiregion","r":[{"selfLink":"77990d7b-0c45-4b87-8583-3db6582d94ea"}],"t":1700126413.5033603},{"p":"ecc-gcp-346","l":"us-west-1","r":[{"selfLink":"77990d7b-0c45-4b87-8583-3db6582d94ea"}],"t":1700126413.5033603},{"p":"ecc-gcp-347","l":"multiregion","r":[{"name":"578a70f5-3ce5-4ab2-a953-0c3544f8e4ed"}],"t":1700126413.5033603},{"p":"ecc-gcp-347","l":"us-west-1","r":[{"name":"578a70f5-3ce5-4ab2-a953-0c3544f8e4ed"}],"t":1700126413.5033603},{"p":"ecc-gcp-385","l":"multiregion","r":[{"selfLink":"b0b7685c-06f0-4e26-90c7-fe894f21cd31"}],"t":1700126413.5033603},{"p":"ecc-gcp-385","l":"us-west-1","r":[{"selfLink":"b0b7685c-06f0-4e26-90c7-fe894f21cd31"}],"t":1700126413.5033603},{"p":"ecc-gcp-386","l":"multiregion","r":[{"selfLink":"3042ad6b-9a12-4754-8b5f-68be91047de0"}],"t":1700126413.5033603},{"p":"ecc-gcp-386","l":"us-west-1","r":[{"selfLink":"3042ad6b-9a12-4754-8b5f-68be91047de0"}],"t":1700126413.5033603},{"p":"ecc-gcp-400","l":"multiregion","r":[{"selfLink":"371288f7-3f83-486c-89ae-a99b81da885b"}],"t":1700126413.5033603},{"p":"ecc-gcp-400","l":"us-west-1","r":[{"selfLink":"371288f7-3f83-486c-89ae-a99b81da885b"}],"t":1700126413.5033603},{"p":"ecc-gcp-401","l":"multiregion","r":[{"selfLink":"b76f7608-10de-4356-a333-f1d60362e18d"}],"t":1700126413.5033603},{"p":"ecc-gcp-401","l":"us-west-1","r":[{"selfLink":"b76f7608-10de-4356-a333-f1d60362e18d"}],"t":1700126413.5033603},{"p":"ecc-gcp-409","l":"multiregion","r":[{"selfLink":"6e5870ce-bf38-4287-be79-1dd0a16b8d48"}],"t":1700126413.5033603},{"p":"ecc-gcp-409","l":"us-west-1","r":[{"selfLink":"6e5870ce-bf38-4287-be79-1dd0a16b8d48"}],"t":1700126413.5033603},{"p":"ecc-gcp-412","l":"multiregion","r":[{"name":"c67027f0-c96d-47b4-9e74-3e600d333e79"}],"t":1700126413.5033603},{"p":"ecc-gcp-412","l":"us-west-1","r":[{"name":"c67027f0-c96d-47b4-9e74-3e600d333e79"}],"t":1700126413.5033603},{"p":"ecc-gcp-415","l":"multiregion","r":[{"selfLink":"c0004371-f871-42e2-b2f2-9b733b700341"}],"t":1700126413.5033603},{"p":"ecc-gcp-415","l":"us-west-1","r":[{"selfLink":"c0004371-f871-42e2-b2f2-9b733b700341"}],"t":1700126413.5033603},{"p":"ecc-gcp-432","l":"multiregion","r":[{"selfLink":"b86134ab-7dd4-44aa-8a5c-a538f0aa68b1"}],"t":1700126413.5033603},{"p":"ecc-gcp-432","l":"us-west-1","r":[{"selfLink":"b86134ab-7dd4-44aa-8a5c-a538f0aa68b1"}],"t":1700126413.5033603},{"p":"ecc-gcp-434","l":"multiregion","r":[{"selfLink":"d0f6f1c0-e056-4bbb-9b53-90eccd89716f"}],"t":1700126413.5033603},{"p":"ecc-gcp-434","l":"us-west-1","r":[{"selfLink":"d0f6f1c0-e056-4bbb-9b53-90eccd89716f"}],"t":1700126413.5033603},{"p":"ecc-gcp-436","l":"multiregion","r":[{"name":"ada96e3a-5397-4389-b78e-80bc18820ab4"}],"t":1700126413.5033603},{"p":"ecc-gcp-436","l":"us-west-1","r":[{"name":"ada96e3a-5397-4389-b78e-80bc18820ab4"}],"t":1700126413.5033603},{"p":"ecc-gcp-438","l":"multiregion","r":[{"selfLink":"9d021183-40a5-44a0-bbd5-d93abe8ec293"}],"t":1700126413.5033603},{"p":"ecc-gcp-438","l":"us-west-1","r":[{"selfLink":"9d021183-40a5-44a0-bbd5-d93abe8ec293"}],"t":1700126413.5033603}] \ No newline at end of file diff --git a/tests/tests_metrics/mock_files/reports/raw/EPAM Systems/GOOGLE/GOOGLE-1234567890123/latest/meta.json b/tests/tests_metrics/mock_files/reports/raw/EPAM Systems/GOOGLE/GOOGLE-1234567890123/latest/meta.json new file mode 100644 index 000000000..d4cc06ec0 --- /dev/null +++ b/tests/tests_metrics/mock_files/reports/raw/EPAM Systems/GOOGLE/GOOGLE-1234567890123/latest/meta.json @@ -0,0 +1 @@ +{"ecc-gcp-001":{"description":"Personal accounts are used instead of corporate login credentials\n","resource":"gcp.project-iam-policy-bindings-by-members"},"ecc-gcp-003":{"description":"There are not only GCP-managed service account keys for service accounts\n","resource":"gcp.service-account-key-user"},"ecc-gcp-004":{"description":"Service Accounts has Admin privileges\n","resource":"gcp.project-iam-policy-bindings-by-members"},"ecc-gcp-005":{"description":"IAM users are assigned the Service Account User or Service Account Token Creator roles at the project level\n","resource":"gcp.project-iam-policy-bindings"},"ecc-gcp-006":{"description":"User-managed/external keys for service accounts are not rotated every 90 days\n","resource":"gcp.service-account-key"},"ecc-gcp-007":{"description":"Separation of duties is not enforced while assigning service account related roles to users\n","resource":"gcp.project-iam-policy-bindings-by-members"},"ecc-gcp-008":{"description":"KMS encryption keys are not rotated within a period of 90 days\n","resource":"gcp.kms-cryptokey"},"ecc-gcp-009":{"description":"Separation of duties is not enforced while assigning KMS related roles to users\n","resource":"gcp.project-iam-policy-bindings-by-members"},"ecc-gcp-010":{"description":"API keys are created for a project\n","resource":"gcp.gcp-apikeys"},"ecc-gcp-011":{"description":"API keys are not restricted to use by only specified Hosts and Apps\n","resource":"gcp.gcp-apikeys"},"ecc-gcp-012":{"description":"API Keys are not restricted to only APIs that application needs access\n","resource":"gcp.gcp-apikeys"},"ecc-gcp-013":{"description":"API keys are not rotated every 90 days\n","resource":"gcp.gcp-apikeys"},"ecc-gcp-014":{"description":"Cloud Audit Logging is not configured properly across all services and all users from a project\n","resource":"gcp.project"},"ecc-gcp-015":{"description":"The project does not have a sink configured for all log entries.\n","resource":"gcp.project"},"ecc-gcp-016":{"description":"Object versioning is disabled on log-buckets\n","resource":"gcp.logging-sink-bucket"},"ecc-gcp-017":{"description":"The project does not have Log metric filter and alerts for Project Ownership assignments/changes\n","resource":"gcp.project"},"ecc-gcp-018":{"description":"The project does not have Log metric filter and alerts for Audit Configuration Changes\n","resource":"gcp.project"},"ecc-gcp-019":{"description":"The project does not have Log metric filter and alerts for Custom Role changes\n","resource":"gcp.project"},"ecc-gcp-020":{"description":"The project does not have Log metric filter and alerts for VPC Network Firewall rule changes\n","resource":"gcp.project"},"ecc-gcp-021":{"description":"The project does not have Log metric filter and alerts for VPC network route changes\n","resource":"gcp.project"},"ecc-gcp-022":{"description":"The project does not have Log metric filter and alerts for VPC network changes\n","resource":"gcp.project"},"ecc-gcp-023":{"description":"The project does not have Log metric filter and alerts for Cloud Storage IAM permission changes\n","resource":"gcp.project"},"ecc-gcp-024":{"description":"The project does not have Log metric filter and alerts for SQL instance configuration changes\n","resource":"gcp.project"},"ecc-gcp-025":{"description":"Default network exists in the project\n","resource":"gcp.vpc"},"ecc-gcp-027":{"description":"DNSSEC is not enabled for Cloud DNS\n","resource":"gcp.dns-managed-zone"},"ecc-gcp-028":{"description":"RSASHA1 is used for key-signing key in Cloud DNS DNSSEC\n","resource":"gcp.dns-managed-zone"},"ecc-gcp-029":{"description":"RSASHA1 is used for zone-signing key in Cloud DNS DNSSEC\n","resource":"gcp.dns-managed-zone"},"ecc-gcp-030":{"description":"SSH access is not restricted from the internet\n","resource":"gcp.firewall"},"ecc-gcp-031":{"description":"RDP access is not restricted from the internet\n","resource":"gcp.firewall"},"ecc-gcp-032":{"description":"Private Google Access is not enabled for all subnetworks in a VPC Network\n","resource":"gcp.subnet"},"ecc-gcp-033":{"description":"VPC Flow logs are not enabled for every subnet in a VPC Network\n","resource":"gcp.subnet"},"ecc-gcp-034":{"description":"Instances are configured to use the default service account with full access to all Cloud APIs\n","resource":"gcp.instance"},"ecc-gcp-035":{"description":"\"Block Project-wide SSH keys\" is not enabled for VM instances\n","resource":"gcp.instance"},"ecc-gcp-036":{"description":"OS Login is not enabled for a Project\n","resource":"gcp.gce-project"},"ecc-gcp-037":{"description":"'Enable connecting to serial ports' is enabled for a VM Instance\n","resource":"gcp.instance"},"ecc-gcp-038":{"description":"IP forwarding is enabled on Instances\n","resource":"gcp.instance"},"ecc-gcp-039":{"description":"VM disks for critical VMs are not encrypted with Customer-Supplied Encryption Keys (CSEK)\n","resource":"gcp.disk"},"ecc-gcp-040":{"description":"Cloud Storage bucket is anonymously or publicly accessible\n","resource":"gcp.bucket"},"ecc-gcp-042":{"description":"Logging is disabled for Cloud storage buckets\n","resource":"gcp.bucket"},"ecc-gcp-043":{"description":"Cloud SQL database instance does not require all incoming connections to use SSL\n","resource":"gcp.sql-instance"},"ecc-gcp-044":{"description":"Cloud SQL database Instances are open to the world\n","resource":"gcp.sql-instance"},"ecc-gcp-046":{"description":"MySQL Database Instance allows root login from any Host\n","resource":"gcp.sql-user"},"ecc-gcp-047":{"description":"Stackdriver Kubernetes Logging is not Enabled\n","resource":"gcp.gke-cluster"},"ecc-gcp-048":{"description":"Stackdriver Kubernetes Monitoring is not Enabled\n","resource":"gcp.gke-cluster"},"ecc-gcp-049":{"description":"Legacy Authorization (ABAC) is Enabled\n","resource":"gcp.gke-cluster"},"ecc-gcp-050":{"description":"Master Authorized Networks is not Enabled\n","resource":"gcp.gke-cluster"},"ecc-gcp-051":{"description":"Kubernetes Clusters are not configured with Labels\n","resource":"gcp.gke-cluster"},"ecc-gcp-053":{"description":"Node Auto-Repair is not enabled for GKE nodes\n","resource":"gcp.gke-cluster"},"ecc-gcp-054":{"description":"Node Auto-Upgrade is not enabled for GKE nodes\n","resource":"gcp.gke-cluster"},"ecc-gcp-055":{"description":"Container-Optimized OS (cos_containerd) is not used for GKE node images\n","resource":"gcp.gke-cluster"},"ecc-gcp-057":{"description":"Network Policy is not Enabled and set as appropriate\n","resource":"gcp.gke-cluster"},"ecc-gcp-058":{"description":"Authentication using Client Certificates is Enabled\n","resource":"gcp.gke-cluster"},"ecc-gcp-059":{"description":"Kubernetes Cluster is created without Alias IP ranges enabled\n","resource":"gcp.gke-cluster"},"ecc-gcp-060":{"description":"Pod Security Policy is not Enabled and set as appropriate\n","resource":"gcp.gke-cluster-beta-api"},"ecc-gcp-061":{"description":"Kubernetes Cluster is created without Private cluster enabled\n","resource":"gcp.gke-cluster-beta-api"},"ecc-gcp-062":{"description":"Private Google Access is not set on Kubernetes Engine Cluster Subnets\n","resource":"gcp.subnet"},"ecc-gcp-063":{"description":"GKE clusters are running using the Compute Engine default service account\n","resource":"gcp.gke-cluster"},"ecc-gcp-065":{"description":"There are regions in projects where Key Management Service is not in use\n","resource":"gcp.kms-location"},"ecc-gcp-066":{"description":"KMS cryptokeys is \"Not avaliable\"\n","resource":"gcp.kms-cryptokey"},"ecc-gcp-067":{"description":"Expiry date is not set for all secrets\n","resource":"gcp.secret"},"ecc-gcp-068":{"description":"Expiry date is not set on all keys\n","resource":"gcp.kms-cryptokey"},"ecc-gcp-070":{"description":"Armor is disabled\n","resource":"gcp.loadbalancer-backend-service"},"ecc-gcp-071":{"description":"Inbound traffic is not restricted to only that, which is necessary, and traffic is allowed for all protocols from the entire Internet (0.0.0.0/0)\n","resource":"gcp.firewall"},"ecc-gcp-072":{"description":"Outbound traffic is not restricted to only that, which is necessary, and traffic is allowed for all protocols from the entire Internet (0.0.0.0/0)\n","resource":"gcp.firewall"},"ecc-gcp-076":{"description":"VPC network peering is not connected\n","resource":"gcp.vpc"},"ecc-gcp-077":{"description":"Names like 'Admin' are used for Google SQL Instance admin account login\n","resource":"gcp.sql-user"},"ecc-gcp-079":{"description":"Google Cloud Storage bucket hosts a static website\n","resource":"gcp.bucket"},"ecc-gcp-082":{"description":"Cloud SQL retention policy is less then 7 days\n","resource":"gcp.sql-instance"},"ecc-gcp-083":{"description":"Cloud SQL instances have not High-Availability Zone enabled\n","resource":"gcp.sql-instance"},"ecc-gcp-086":{"description":"SSL/TLS certificates in Cloud SQL expire in one month\n","resource":"gcp.sql-instance"},"ecc-gcp-087":{"description":"SSL/TLS certificates in Cloud SQL expire in one week\n","resource":"gcp.sql-instance"},"ecc-gcp-088":{"description":"HTTP Load Balancer secured certificate expires in one month\n","resource":"gcp.loadbalancer-ssl-certificate"},"ecc-gcp-089":{"description":"HTTP Load Balancer secured certificate expires in one week\n","resource":"gcp.loadbalancer-ssl-certificate"},"ecc-gcp-090":{"description":"Weak ciphers are used in CDN\n","resource":"gcp.loadbalancer-backend-frontend-ssl"},"ecc-gcp-091":{"description":"Connection used between CDN and an origin server is not encrypted\n","resource":"gcp.loadbalancer-backend-service"},"ecc-gcp-092":{"description":"Weak ciphers are used for Load Balancer\n","resource":"gcp.loadbalancer-ssl-policy"},"ecc-gcp-093":{"description":"GCP Load Balancer uses HTTP instead of HTTPS\n","resource":"gcp.loadbalancer-backend-service"},"ecc-gcp-099":{"description":"There is an instance without any tags\n","resource":"gcp.instance"},"ecc-gcp-101":{"description":"Load Balancer Access Logging is Disabled\n","resource":"gcp.loadbalancer-backend-service"},"ecc-gcp-103":{"description":"There is an instance without deletion protection\n","resource":"gcp.instance"},"ecc-gcp-104":{"description":"There is a default firewall rule in use\n","resource":"gcp.firewall"},"ecc-gcp-107":{"description":"There are volumes that have not had a backup or snapshot in the past 14 days\n","resource":"gcp.snapshot"},"ecc-gcp-109":{"description":"Ingress access is allowed\n","resource":"gcp.firewall"},"ecc-gcp-110":{"description":"Firewall rules allow internet traffic to DNS port (53)\n","resource":"gcp.firewall"},"ecc-gcp-111":{"description":"Firewall rules allow internet traffic to FTP port (21)\n","resource":"gcp.firewall"},"ecc-gcp-112":{"description":"Firewall rules allow internet traffic to HTTP port (80)\n","resource":"gcp.firewall"},"ecc-gcp-113":{"description":"Firewall rules allow internet traffic to Microsoft-DS port (445)\n","resource":"gcp.firewall"},"ecc-gcp-114":{"description":"Firewall rules allow internet traffic to MongoDB port (27017)\n","resource":"gcp.firewall"},"ecc-gcp-115":{"description":"Firewall rules allow internet traffic to MySQL DB port (3306)\n","resource":"gcp.firewall"},"ecc-gcp-116":{"description":"Firewall rules allow internet traffic to NetBIOS-SSN port (139)\n","resource":"gcp.firewall"},"ecc-gcp-117":{"description":"Firewall rules allow internet traffic to Oracle DB port (1521)\n","resource":"gcp.firewall"},"ecc-gcp-118":{"description":"Firewall rules allow internet traffic to POP3 port (110)\n","resource":"gcp.firewall"},"ecc-gcp-119":{"description":"Firewall rules allow internet traffic to PostgreSQL port (5432)\n","resource":"gcp.firewall"},"ecc-gcp-120":{"description":"Firewall rules allow internet traffic to SMTP port (25)\n","resource":"gcp.firewall"},"ecc-gcp-121":{"description":"Firewall rules allow internet traffic to Telnet port (23)\n","resource":"gcp.firewall"},"ecc-gcp-122":{"description":"Firewall rules allow inbound traffic from anywhere with no target tags set\n","resource":"gcp.firewall"},"ecc-gcp-123":{"description":"There is unsupported GKE Master node version\n","resource":"gcp.gke-cluster"},"ecc-gcp-124":{"description":"There is unsupported GKE node version\n","resource":"gcp.gke-cluster"},"ecc-gcp-125":{"description":"HTTPS Load balancer SSL Policy is not using a restrictive profile\n","resource":"gcp.loadbalancer-target-https-proxy-ssl-policy"},"ecc-gcp-126":{"description":"Alpha clusters are used for production workloads\n","resource":"gcp.gke-cluster"},"ecc-gcp-127":{"description":"Binary Authorization is not used\n","resource":"gcp.gke-cluster"},"ecc-gcp-128":{"description":"Legacy Compute Engine instance metadata APIs are Enabled\n","resource":"gcp.gke-cluster"},"ecc-gcp-129":{"description":"Kubernetes Engine Clusters network firewall inbound rule is overly permissive to all traffic\n","resource":"gcp.firewall"},"ecc-gcp-130":{"description":"Kubernetes Engine Clusters are not configured with network traffic egress metering\n","resource":"gcp.gke-cluster"},"ecc-gcp-131":{"description":"Kubernetes Engine Clusters are not configured with private nodes feature\n","resource":"gcp.gke-cluster"},"ecc-gcp-132":{"description":"Kubernetes cluster Application-layer Secrets are not encrypted\n","resource":"gcp.gke-cluster"},"ecc-gcp-133":{"description":"Kubernetes cluster intranode visibility is Disabled\n","resource":"gcp.gke-cluster"},"ecc-gcp-134":{"description":"Kubernetes cluster istioConfig is not Enabled\n","resource":"gcp.gke-cluster-beta-api"},"ecc-gcp-135":{"description":"Kubernetes cluster is not in redundant zones\n","resource":"gcp.gke-cluster"},"ecc-gcp-136":{"description":"Kubernetes cluster size contains less than 3 nodes with auto-upgrade enabled\n","resource":"gcp.gke-cluster"},"ecc-gcp-137":{"description":"Load balancer HTTPS target proxy is configured with default SSL policy instead of custom SSL policy\n","resource":"gcp.loadbalancer-target-https-proxy"},"ecc-gcp-138":{"description":"Load balancer HTTPS target proxy is not configured with QUIC protocol\n","resource":"gcp.loadbalancer-target-https-proxy"},"ecc-gcp-140":{"description":"There are SQL Instances without any Label information\n","resource":"gcp.sql-instance"},"ecc-gcp-141":{"description":"Storage bucket is encrypted using google-managed key instead of customer-managed key\n","resource":"gcp.bucket"},"ecc-gcp-142":{"description":"Logging on the Stackdriver exported Bucket is disabled\n","resource":"gcp.logging-sink-bucket"},"ecc-gcp-143":{"description":"Primitive IAM roles are used\n","resource":"gcp.project-iam-policy-bindings-by-members"},"ecc-gcp-144":{"description":"MySQL DB Instance backup Binary logs configuration is not enabled\n","resource":"gcp.sql-instance"},"ecc-gcp-150":{"description":"There are Storage Buckets with publicly accessible Stackdriver logs\n","resource":"gcp.bucket-access-control-list"},"ecc-gcp-151":{"description":"VM Instances are enabled with Pre-Emptible termination\n","resource":"gcp.instance"},"ecc-gcp-152":{"description":"There are VM Instances without any custom metadata information\n","resource":"gcp.instance"},"ecc-gcp-153":{"description":"There are VM Instances without any Label information\n","resource":"gcp.instance"},"ecc-gcp-162":{"description":"Buckets have no set life cycle value\n","resource":"gcp.bucket"},"ecc-gcp-163":{"description":"Buckets have no labels\n","resource":"gcp.bucket"},"ecc-gcp-165":{"description":"GKE Cluster HTTP Load balancing is not enabled\n","resource":"gcp.gke-cluster"},"ecc-gcp-166":{"description":"GKE Clusters use default network instead of the specific purpose-designed networks\n","resource":"gcp.gke-cluster"},"ecc-gcp-167":{"description":"BigQuery IAM role grants an individual Google account access to a dataset\n","resource":"gcp.bq-dataset"},"ecc-gcp-169":{"description":"Cloud KMS cryptokeys are anonymously or publicly accessible\n","resource":"gcp.kms-keyring"},"ecc-gcp-170":{"description":"HTTPS or SSL proxy load balancers permit SSL policies with weak cipher suites\n","resource":"gcp.loadbalancer-target-https-proxy-ssl-policy"},"ecc-gcp-171":{"description":"Instances are configured to use the default service account\n","resource":"gcp.instance"},"ecc-gcp-172":{"description":"Compute instances are launched without Shielded VM enabled\n","resource":"gcp.instance"},"ecc-gcp-173":{"description":"Compute instances have public IP addresses\n","resource":"gcp.instance"},"ecc-gcp-175":{"description":"Cloud Storage buckets have not uniform bucket-level access enabled\n","resource":"gcp.bucket"},"ecc-gcp-176":{"description":"The 'local_infile' database flag for a Cloud SQL MySQL instance is not set to 'off'\n","resource":"gcp.sql-instance"},"ecc-gcp-177":{"description":"The 'log_checkpoints' database flag for Cloud SQL PostgreSQL instance is not set to 'on'\n","resource":"gcp.sql-instance"},"ecc-gcp-178":{"description":"The 'log_connections' database flag for Cloud SQL PostgreSQL instance is not set to 'on'\n","resource":"gcp.sql-instance"},"ecc-gcp-179":{"description":"The 'log_disconnections' database flag for Cloud SQL PostgreSQL instance is not set to 'on'\n","resource":"gcp.sql-instance"},"ecc-gcp-180":{"description":"The 'log_lock_waits' database flag for Cloud SQL PostgreSQL instance is not set to 'on'\n","resource":"gcp.sql-instance"},"ecc-gcp-181":{"description":"The 'log_min_messages' database flag for Cloud SQL PostgreSQL instance is not set appropriately\n","resource":"gcp.sql-instance"},"ecc-gcp-182":{"description":"The 'log_temp_files' database flag for Cloud SQL PostgreSQL instance is not set to '0' (on)\n","resource":"gcp.sql-instance"},"ecc-gcp-183":{"description":"The 'log_min_duration_statement' database flag for Cloud SQL PostgreSQL instance is not set to '-1' (disabled)\n","resource":"gcp.sql-instance"},"ecc-gcp-184":{"description":"The 'cross db ownership chaining' database flag for Cloud SQL SQL Server instance is not set to 'off'\n","resource":"gcp.sql-instance"},"ecc-gcp-185":{"description":"The 'contained database authentication' database flag for Cloud SQL on the SQL Server instance is not set to 'off'\n","resource":"gcp.sql-instance"},"ecc-gcp-186":{"description":"Cloud SQL database instances have public IPs\n","resource":"gcp.sql-instance"},"ecc-gcp-187":{"description":"Cloud SQL database instances are configured without automated backups\n","resource":"gcp.sql-instance"},"ecc-gcp-188":{"description":"BigQuery datasets are anonymously or publicly accessible\n","resource":"gcp.bq-dataset"},"ecc-gcp-189":{"description":"Cloud Storage has not multi-region bucket location\n","resource":"gcp.bucket"},"ecc-gcp-190":{"description":"Retention policies on log buckets are not configured using Bucket Lock\n","resource":"gcp.logging-sink-bucket"},"ecc-gcp-191":{"description":"Integrity Monitoring for Shielded GKE Nodes is not Enabled\n","resource":"gcp.gke-cluster"},"ecc-gcp-192":{"description":"Secure Boot for Shielded GKE Nodes is not Enabled\n","resource":"gcp.gke-cluster"},"ecc-gcp-193":{"description":"VPC Flow Logs and Intranode Visibility are Disabled\n","resource":"gcp.gke-cluster"},"ecc-gcp-194":{"description":"OS Login is not enabled for an instance\n","resource":"gcp.instance"},"ecc-gcp-195":{"description":"Cloud DNS logging is not enabled for all VPC networks\n","resource":"gcp.vpc"},"ecc-gcp-197":{"description":"Compute instances have not Confidential Computing enabled\n","resource":"gcp.instance"},"ecc-gcp-198":{"description":"The 'skip_show_database' database flag for Cloud SQL MySQL instance is not set to 'on'\n","resource":"gcp.sql-instance"},"ecc-gcp-199":{"description":"The 'log_error_verbosity' database flag for Cloud SQL PostgreSQL instance is not set to 'DEFAULT' or stricter\n","resource":"gcp.sql-instance"},"ecc-gcp-200":{"description":"The 'log_duration' database flag for Cloud SQL PostgreSQL instance is not set to 'on'\n","resource":"gcp.sql-instance"},"ecc-gcp-201":{"description":"The 'log_statement' database flag for Cloud SQL PostgreSQL instance is not set appropriately\n","resource":"gcp.sql-instance"},"ecc-gcp-202":{"description":"The 'log_hostname' database flag for Cloud SQL PostgreSQL instance is not set appropriately\n","resource":"gcp.sql-instance"},"ecc-gcp-203":{"description":"The 'log_parser_stats' database flag for Cloud SQL PostgreSQL instance is not set to 'off'\n","resource":"gcp.sql-instance"},"ecc-gcp-204":{"description":"The 'log_planner_stats' database flag for Cloud SQL PostgreSQL instance is not set to 'off'\n","resource":"gcp.sql-instance"},"ecc-gcp-205":{"description":"The 'log_executor_stats' database flag for Cloud SQL PostgreSQL instance is not set to 'off'\n","resource":"gcp.sql-instance"},"ecc-gcp-206":{"description":"The 'log_statement_stats' database flag for Cloud SQL PostgreSQL instance is not set to 'off'\n","resource":"gcp.sql-instance"},"ecc-gcp-207":{"description":"The 'log_min_error_statement' database flag for Cloud SQL PostgreSQL instance is not set to 'Error' or stricter\n","resource":"gcp.sql-instance"},"ecc-gcp-208":{"description":"The 'external scripts enabled' database flag for Cloud SQL SQL Server instance is not set to 'off'\n","resource":"gcp.sql-instance"},"ecc-gcp-209":{"description":"The 'user connections' database flag for Cloud SQL SQL Server instance is not Set to a Non-limiting Value\n","resource":"gcp.sql-instance"},"ecc-gcp-210":{"description":"The 'user options' database flag for Cloud SQL SQL Server instance is configured\n","resource":"gcp.sql-instance"},"ecc-gcp-211":{"description":"The 'remote access' database flag for Cloud SQL SQL Server instance is not set to 'off'\n","resource":"gcp.sql-instance"},"ecc-gcp-212":{"description":"The '3625 (trace flag)' database flag for Cloud SQL SQL Server instance is not set to 'on'\n","resource":"gcp.sql-instance"},"ecc-gcp-213":{"description":"Some BigQuery Tables are not encrypted with Customer-managed encryption key (CMEK)\n","resource":"gcp.bq-table"},"ecc-gcp-214":{"description":"Default Customer-managed encryption key (CMEK) is not specified for some BigQuery Data Sets\n","resource":"gcp.bq-dataset"},"ecc-gcp-215":{"description":"'Block Project-wide SSH keys' is not enabled for instance template\n","resource":"gcp.instance-template"},"ecc-gcp-216":{"description":"IP forwarding is enabled on Instance templates\n","resource":"gcp.instance-template"},"ecc-gcp-217":{"description":"'Enable connecting to serial ports' is enabled for Instance template\n","resource":"gcp.instance-template"},"ecc-gcp-218":{"description":"Oslogin is not enabled for an instance template\n","resource":"gcp.instance-template"},"ecc-gcp-219":{"description":"Instance template configured without Shielded VM features\n","resource":"gcp.instance-template"},"ecc-gcp-220":{"description":"Instance template configured with external IP addresses\n","resource":"gcp.instance-template"},"ecc-gcp-221":{"description":"Instance template use default service account\n","resource":"gcp.instance-template"},"ecc-gcp-222":{"description":"Instance templates are configured to use the default service account with full access to all Cloud APIs\n","resource":"gcp.instance-template"},"ecc-gcp-223":{"description":"VM instance is configured with default network\n","resource":"gcp.instance"},"ecc-gcp-225":{"description":"VM instance with public IP address has access to GCS buckets\n","resource":"gcp.instance"},"ecc-gcp-227":{"description":"Compute engine image is not encrypted using customer-managed key\n","resource":"gcp.image"},"ecc-gcp-228":{"description":"'On Host Maintenance' configuration setting is not set to 'Migrate' for all VM instances\n","resource":"gcp.instance"},"ecc-gcp-229":{"description":"Auto-Delete feature is not disabled for the disks attached to the VM instances\n","resource":"gcp.instance"},"ecc-gcp-230":{"description":"Automatic restart is not enabled for Google Cloud virtual machine (VM) instances\n","resource":"gcp.instance"},"ecc-gcp-231":{"description":"Google Cloud Managed Instance Groups (MIGs) are configured without Autohealing feature.\n","resource":"gcp.instance-group-managers"},"ecc-gcp-232":{"description":"OS Login is not configured with Two-Factor Authentication (2FA) for production VM instances\n","resource":"gcp.instance"},"ecc-gcp-233":{"description":"VM disk images are publicly shared with all other GCP accounts\n","resource":"gcp.image"},"ecc-gcp-234":{"description":"Cloud SQL database instance data encryption is not set to customer managed encryption key\n","resource":"gcp.sql-instance"},"ecc-gcp-236":{"description":"MySQL database servers are not using the latest major version of MySQL database\n","resource":"gcp.sql-instance"},"ecc-gcp-237":{"description":"PostgreSQL database servers are not using the latest major version of PostgreSQL database\n","resource":"gcp.sql-instance"},"ecc-gcp-239":{"description":"Automatic storage increase is not enabled for your Cloud SQL database instances\n","resource":"gcp.sql-instance"},"ecc-gcp-240":{"description":"MySQL database instance has the 'slow_query_log' flag set to Off\n","resource":"gcp.sql-instance"},"ecc-gcp-241":{"description":"Cloud Functions function is configured with privileged service accounts\n","resource":"gcp.function"},"ecc-gcp-242":{"description":"Cloud Functions function does not restrict public access\n","resource":"gcp.function"},"ecc-gcp-243":{"description":"Cloud Functions function is configured with the allow all traffic ingress setting\n","resource":"gcp.function"},"ecc-gcp-244":{"description":"Cloud Functions function allows unauthenticated invocation\n","resource":"gcp.function"},"ecc-gcp-245":{"description":"Cloud Functions function is configured with default service account\n","resource":"gcp.function"},"ecc-gcp-246":{"description":"Cloud Functions function is not configured with a VPC connector\n","resource":"gcp.function"},"ecc-gcp-247":{"description":"The deployed Cloud Function is not in 'ACTIVE' mode\n","resource":"gcp.function"},"ecc-gcp-248":{"description":"Event trigger is not configured in your function\n","resource":"gcp.function"},"ecc-gcp-249":{"description":"GCP Cloud Function HTTP trigger is not secured\n","resource":"gcp.function"},"ecc-gcp-250":{"description":"GKE cluster container scanning disabled\n","resource":"gcp.project"},"ecc-gcp-251":{"description":"GKE node is configured with privileged service account\n","resource":"gcp.gke-nodepool"},"ecc-gcp-252":{"description":"GCP Kubernetes Engine cluster not using Release Channel for version management\n","resource":"gcp.gke-cluster"},"ecc-gcp-253":{"description":"GKE Metadata Server is not Enabled\n","resource":"gcp.gke-cluster"},"ecc-gcp-254":{"description":"Cloud App Engine application custom domain SSL certificate expires in less than 30 days\n","resource":"gcp.app-engine-certificate"},"ecc-gcp-256":{"description":"Cloud App Engine application firewall does not restrict public access\n","resource":"gcp.app-engine-firewall-ingress-rule"},"ecc-gcp-257":{"description":"App Engine Identity-Aware Proxy is disabled\n","resource":"gcp.app-engine"},"ecc-gcp-258":{"description":"Cloud Bigtable backup expiration time is 29 days or less\n","resource":"gcp.bigtable-instance-cluster-backup"},"ecc-gcp-260":{"description":"Cloud Bigtable table configured with privileged service account\n","resource":"gcp.bigtable-instance-table"},"ecc-gcp-261":{"description":"Dataproc cluster is not encrypted with Customer-Managed Key\n","resource":"gcp.dataproc-clusters"},"ecc-gcp-262":{"description":"Cloud Run revision is configured with privileged service accounts\n","resource":"gcp.namespace-revision"},"ecc-gcp-263":{"description":"Cloud Run revision is configured with default service account\n","resource":"gcp.namespace-revision"},"ecc-gcp-264":{"description":"Cloud Run revision is configured without a VPC connector\n","resource":"gcp.namespace-revision"},"ecc-gcp-265":{"description":"Cloud Run service is configured with privileged service accounts\n","resource":"gcp.namespace-service"},"ecc-gcp-266":{"description":"Cloud Run service is configured with the allow all traffic ingress setting\n","resource":"gcp.namespace-service"},"ecc-gcp-268":{"description":"Cloud Run service allows public access\n","resource":"gcp.namespace-service"},"ecc-gcp-271":{"description":"Network is not configured with default deny egress rule in firewall\n","resource":"gcp.vpc"},"ecc-gcp-272":{"description":"Firewall logging is not enabled\n","resource":"gcp.firewall"},"ecc-gcp-273":{"description":"Cloud Armor Security Policy have not Adaptive Protection enabled\n","resource":"gcp.security-policy"},"ecc-gcp-274":{"description":"Cloud Armor Security Policy does not have deny as default action\n","resource":"gcp.security-policy"},"ecc-gcp-276":{"description":"Cloud Armor Security Policy have not non-default rules defined\n","resource":"gcp.security-policy"},"ecc-gcp-277":{"description":"Cloud DNS managed zone didn't contain TXT type record\n","resource":"gcp.dns-managed-zone"},"ecc-gcp-278":{"description":"Firewall rules allow internet traffic to Elastic Search port (9200 or 9300)\n","resource":"gcp.firewall"},"ecc-gcp-279":{"description":"Firewall rules allow internet traffic to FTP port (20)\n","resource":"gcp.firewall"},"ecc-gcp-280":{"description":"Firewall rules allow internet traffic to Kibana port (5601)\n","resource":"gcp.firewall"},"ecc-gcp-281":{"description":"Firewall rules allow public access to Memcached port (11211)\n","resource":"gcp.firewall"},"ecc-gcp-282":{"description":"Firewall rules allow internet traffic to Redis port (6379)\n","resource":"gcp.firewall"},"ecc-gcp-283":{"description":"Firewall rules allow internet traffic to SQL server port (1433)\n","resource":"gcp.firewall"},"ecc-gcp-285":{"description":"Firewall rules allow internet traffic to WinRM port (5985 or 5986)\n","resource":"gcp.firewall"},"ecc-gcp-286":{"description":"Pub/Sub topic is not encrypted using a customer-managed encryption key\n","resource":"gcp.pubsub-topic"},"ecc-gcp-287":{"description":"There is not a dead-letter topic configured for each Pub/Sub subscription\n","resource":"gcp.pubsub-subscription"},"ecc-gcp-288":{"description":"Firewall rules allow internet traffic to ICMP\n","resource":"gcp.firewall"},"ecc-gcp-289":{"description":"Firewall rules allow public access to RPC port (135)\n","resource":"gcp.firewall"},"ecc-gcp-291":{"description":"Secret Manager secret is not encrypted with customer managed encryption key\n","resource":"gcp.secret"},"ecc-gcp-292":{"description":"Cloud Spanner backup is configured with privileged service account\n","resource":"gcp.spanner-instance-backup"},"ecc-gcp-293":{"description":"Cloud Spanner backup is created with an expiration date of 29 days or less\n","resource":"gcp.spanner-instance-backup"},"ecc-gcp-294":{"description":"Cloud Spanner database is configured with privileged service account\n","resource":"gcp.spanner-database-instance"},"ecc-gcp-295":{"description":"Cloud Spanner instance is configured with privileged service accounts\n","resource":"gcp.spanner-instance"},"ecc-gcp-298":{"description":"GCP storage bucket is not configured with default Event-Based Hold\n","resource":"gcp.bucket"},"ecc-gcp-299":{"description":"There is no sufficient retention period configured for Google Cloud Storage objects\n","resource":"gcp.bucket"},"ecc-gcp-300":{"description":"GCP Memorystore for Redis has AUTH disabled\n","resource":"gcp.redis-instance"},"ecc-gcp-302":{"description":"Cloud Armor does not prevent message lookup in Log4j2\n","resource":"gcp.security-policy"},"ecc-gcp-303":{"description":"GCP Cloud Dataflow job has public IP\n","resource":"gcp.dataflow-job"},"ecc-gcp-304":{"description":"GCP Vertex AI Workbench has public IPs\n","resource":"gcp.notebook-instance"},"ecc-gcp-305":{"description":"GCP Dataproc cluster is anonymously or publicly accessible\n","resource":"gcp.dataproc-clusters"},"ecc-gcp-306":{"description":"GCP Pub/Sub Topic is anonymously or publicly accessible\n","resource":"gcp.pubsub-topic"},"ecc-gcp-307":{"description":"GCP Artifact Registry repository is anonymously or publicly accessible\n","resource":"gcp.artifactregistry-repository"},"ecc-gcp-309":{"description":"Google Cloud Dataproc cluster has a public IP\n","resource":"gcp.dataproc-clusters"},"ecc-gcp-310":{"description":"GCP Memorystore for Redis has in-transit encryption disabled\n","resource":"gcp.redis-instance"},"ecc-gcp-311":{"description":"Datafusion does not have stack driver logging enabled\n","resource":"gcp.datafusion-instance"},"ecc-gcp-312":{"description":"Datafusion does not have stack driver monitoring enabled\n","resource":"gcp.datafusion-instance"},"ecc-gcp-313":{"description":"'Access Transparency' is not Enabled\n","resource":"gcp.project"},"ecc-gcp-314":{"description":"'Access Approval' is not Enabled\n","resource":"gcp.project"},"ecc-gcp-315":{"description":"The 'cloudsql.enable_pgaudit' Database Flag for Cloud Sql Postgresql Instance is not Set to 'on' For Centralized Logging\n","resource":"gcp.sql-instance"},"ecc-gcp-316":{"description":"Cloud Asset Inventory is not Enabled\n","resource":"gcp.service"},"ecc-gcp-317":{"description":"Cloud Bigtable instance cluster encryption not using Customer-Managed Keys\n","resource":"gcp.bigtable-instance-cluster"},"ecc-gcp-318":{"description":"Cloud function runtime is outdated\n","resource":"gcp.function"},"ecc-gcp-323":{"description":"Service account has privileges escalation - Impersonation (Project scope)\n","resource":"gcp.project-iam-policy-bindings-by-members"},"ecc-gcp-324":{"description":"User has privileges escalation - Impersonation (Project scope)\n","resource":"gcp.project-iam-policy-bindings-by-members"},"ecc-gcp-334":{"description":"User has privileges escalation - Impersonation (Resourse scope)\n","resource":"gcp.service-account-bindings"},"ecc-gcp-335":{"description":"Service account has privileges escalation - Impersonation (Resourse scope)\n","resource":"gcp.service-account-bindings"},"ecc-gcp-337":{"description":"Firewall rules allow internet traffic to VNC-Server port (5900)\n","resource":"gcp.firewall"},"ecc-gcp-340":{"description":"Cloud Armor has packet size bypass\n","resource":"gcp.security-policy"},"ecc-gcp-342":{"description":"Firewall rules allow internet traffic to Hadoop/HDFS port (8020)\n","resource":"gcp.firewall"},"ecc-gcp-346":{"description":"Asset does not contain a network tag\n","resource":"gcp.instance"},"ecc-gcp-347":{"description":"Cloud Spanner Instance is deployed without multi-region configuration\n","resource":"gcp.spanner-instance"},"ecc-gcp-385":{"description":"Instance template is configured with default network\n","resource":"gcp.instance-template"},"ecc-gcp-386":{"description":"Compute Instance Templates have not Confidential Computing enabled\n","resource":"gcp.instance-template"},"ecc-gcp-400":{"description":"The 'password_history' database flags for Cloud SQL MySQL instance are not configured\n","resource":"gcp.sql-instance"},"ecc-gcp-401":{"description":"The 'password_reuse_interval' database flags for Cloud SQL MySQL instance are not configured\n","resource":"gcp.sql-instance"},"ecc-gcp-409":{"description":"OS Patch Deployment is not configured with a recurring schedule\n","resource":"gcp.patch-deployment"},"ecc-gcp-412":{"description":"Datafusion instance has public ip\n","resource":"gcp.datafusion-instance"},"ecc-gcp-415":{"description":"GKE Sandbox is not enabled\n","resource":"gcp.gke-nodepool"},"ecc-gcp-432":{"description":"Clusters are created without Private Endpoint Enabled and Public Access Disabled\n","resource":"gcp.gke-cluster"},"ecc-gcp-434":{"description":"Customer-Managed Encryption Keys (CMEK) is not Enabled for GKE Persistent Disks (PD) (FOR NODE BOOT DISKS)\n","resource":"gcp.gke-cluster"},"ecc-gcp-436":{"description":"Instance IP assignment is not set to private\n","resource":"gcp.sql-instance"},"ecc-gcp-438":{"description":"Shielded GKE Nodes are disabled\n","resource":"gcp.gke-cluster"}} \ No newline at end of file diff --git a/tests/tests_metrics/mock_files/rulesets/caas-settings/AWS_STANDARDS_COVERAGE.json.gz b/tests/tests_metrics/mock_files/rulesets/caas-settings/AWS_STANDARDS_COVERAGE.json.gz new file mode 100644 index 000000000..4f12fdf27 --- /dev/null +++ b/tests/tests_metrics/mock_files/rulesets/caas-settings/AWS_STANDARDS_COVERAGE.json.gz @@ -0,0 +1 @@ +{"BSA":{"v3":{"Control Domain":{"NS":{"%":0.8,"P":{"NS-1":{"%":0.7},"NS-2":{"%":0.7},"NS-3":{"%":0.7},"NS-4":{"%":0.7},"NS-5":{"%":0.2},"NS-6":{"%":0.7},"NS-7":{"%":0.7},"NS-8":{"%":0.7},"NS-9":{"%":0.7},"NS-10":{"%":0.2}}},"DP":{"%":0.5,"P":{"DP-1":{"%":0.2},"DP-2":{"%":0.2},"DP-3":{"%":0.7},"DP-4":{"%":0.7},"DP-5":{"%":0.7},"DP-6":{"%":0.7},"DP-7":{"%":0.2},"DP-8":{"%":0.2}}},"IM":{"%":0.5555555555555556,"P":{"IM-1":{"%":0.2},"IM-2":{"%":0.7},"IM-3":{"%":0.2},"IM-4":{"%":0.7},"IM-5":{"%":0.2},"IM-6":{"%":0.7},"IM-7":{"%":0.7},"IM-8":{"%":0.7},"IM-9":{"%":0.2}}},"PA":{"%":0.75,"P":{"PA-1":{"%":0.7},"PA-2":{"%":0.2},"PA-3":{"%":0.7},"PA-4":{"%":0.7},"PA-5":{"%":0.2},"PA-6":{"%":0.7},"PA-7":{"%":0.7},"PA-8":{"%":0.7}}},"PV":{"%":0.8571428571428571,"P":{"PV-1":{"%":0.7},"PV-2":{"%":0.7},"PV-3":{"%":0.7},"PV-4":{"%":0.7},"PV-5":{"%":0.7},"PV-6":{"%":0.7},"PV-7":{"%":0.2}}},"LT":{"%":0.8571428571428571,"P":{"T-1":{"%":0.7},"LT-2":{"%":0.7},"LT-3":{"%":0.7},"T-4":{"%":0.7},"LT-5":{"%":0.7},"LT-6":{"%":0.7},"T-7":{"%":0.2}}},"AM":{"%":0.6,"P":{"AM-1":{"%":0.7},"AM-2":{"%":0.2},"AM-3":{"%":0.7},"AM-4":{"%":0.7},"AM-5":{"%":0.2}}},"ES":{"%":0.2,"P":{"ES-1":{"%":0.2},"ES-2":{"%":0.2},"ES-3":{"%":0.2}}},"BR":{"%":0.75,"P":{"BR-1":{"%":0.7},"BR-2":{"%":0.7},"BR-3":{"%":0.7},"BR-4":{"%":0.2}}},"IR":{"%":0.14285714285714285,"P":{"IR-1":{"%":0.2},"IR-2":{"%":0.7},"IR-3":{"%":0.2},"IR-4":{"%":0.2},"IR-5":{"%":0.2},"IR-6":{"%":0.2},"IR-7":{"%":0.2}}},"DS":{"%":0.2857142857142857,"P":{"DS-1":{"%":0.2},"DS-2":{"%":0.2},"DS-3":{"%":0.2},"DS-4":{"%":0.2},"DS-5":{"%":0.2},"DS-6":{"%":0.7},"DS-7":{"%":0.7}}},"GS":{"%":0.7,"P":{"GS-1":{"%":0.2},"GS-2":{"%":0.2},"GS-3":{"%":0.7},"GS-4":{"%":0.7},"GS-5":{"%":0.7},"GS-6":{"%":0.7},"GS-7":{"%":0.2},"GS-8":{"%":0.7},"GS-9":{"%":0.7},"GS-10":{"%":0.7}}}},"%":0.5665343915343916}},"Standard1":{"v8":{"wertyui":{"1":{"%":0.2,"P":{"1.1":{"%":0.7},"1.2":{"%":0.2},"1.3":{"%":0.2},"1.4":{"%":0.2},"1.5":{"%":0.2}}},"2":{"%":0.2,"P":{"2.1":{"%":0.2},"2.2":{"%":0.2},"2.3":{"%":0.2},"2.4":{"%":0.2},"2.5":{"%":0.2},"2.6":{"%":0.2},"2.7":{"%":0.2}}},"3":{"%":0.5,"P":{"3.1":{"%":0.7},"3.2":{"%":0.2},"3.3":{"%":0.7},"3.4":{"%":0.7},"3.5":{"%":0.2},"3.6":{"%":0.2},"3.7":{"%":0.2},"3.8":{"%":0.7},"3.9":{"%":0.2},"3.10":{"%":0.7},"3.11":{"%":0.7},"3.12":{"%":0.2},"3.13":{"%":0.2},"3.14":{"%":0.7}}},"4":{"%":0.3333333333333333,"P":{"4.1":{"%":0.7},"4.2":{"%":0.7},"4.3":{"%":0.2},"4.4":{"%":0.7},"4.5":{"%":0.2},"4.6":{"%":0.7},"4.7":{"%":0.2},"4.8":{"%":0.2},"4.9":{"%":0.2},"4.10":{"%":0.2},"4.11":{"%":0.2},"4.12":{"%":0.2}}},"5":{"%":0.5,"P":{"5.1":{"%":0.2},"5.2":{"%":0.7},"5.3":{"%":0.2},"5.4":{"%":0.7},"5.5":{"%":0.2},"5.6":{"%":0.7}}},"6":{"%":0.375,"P":{"6.1":{"%":0.7},"6.2":{"%":0.2},"6.3":{"%":0.2},"6.4":{"%":0.2},"6.5":{"%":0.7},"6.6":{"%":0.2},"6.7":{"%":0.2},"6.8":{"%":0.7}}},"7":{"%":0.42857142857142855,"P":{"7.1":{"%":0.2},"7.2":{"%":0.2},"7.3":{"%":0.7},"7.4":{"%":0.7},"7.5":{"%":0.7},"7.6":{"%":0.2},"7.7":{"%":0.2}}},"8":{"%":0.5,"P":{"8.1":{"%":0.2},"8.2":{"%":0.7},"8.3":{"%":0.7},"8.4":{"%":0.2},"8.5":{"%":0.7},"8.6":{"%":0.2},"8.7":{"%":0.2},"8.8":{"%":0.2},"8.9":{"%":0.7},"8.10":{"%":0.7},"8.11":{"%":0.7},"8.12":{"%":0.2}}},"9":{"%":0.2,"P":{"9.1":{"%":0.2},"9.2":{"%":0.2},"9.3":{"%":0.2},"9.4":{"%":0.2},"9.5":{"%":0.2},"9.6":{"%":0.2},"9.7":{"%":0.2}}},"10":{"%":0.14285714285714285,"P":{"10.1":{"%":0.2},"10.2":{"%":0.2},"10.3":{"%":0.2},"10.4":{"%":0.2},"13":{"%":0.2},"10.6":{"%":0.2},"10.7":{"%":0.7}}},"11":{"%":0.6,"P":{"11.1":{"%":0.7},"11.2":{"%":0.7},"11.3":{"%":0.7},"11.4":{"%":0.2},"11.5":{"%":0.2}}},"12":{"%":0.375,"P":{"12.1":{"%":0.2},"12.2":{"%":0.7},"12.3":{"%":0.7},"12.4":{"%":0.2},"12.5":{"%":0.2},"12.6":{"%":0.7},"12.7":{"%":0.2},"12.8":{"%":0.2}}},"13":{"%":0.36363636363636365,"P":{"13.1":{"%":0.2},"13.2":{"%":0.2},"13.3":{"%":0.7},"13.4":{"%":0.7},"13.5":{"%":0.7},"13.6":{"%":0.7},"13.7":{"%":0.2},"13.8":{"%":0.2},"13.9":{"%":0.2},"13.10":{"%":0.2},"13.11":{"%":0.2}}},"14":{"%":0.2,"P":{"14.1":{"%":0.2},"14.2":{"%":0.2},"14.3":{"%":0.2},"14.4":{"%":0.2},"14.5":{"%":0.2},"14.6":{"%":0.2},"14.7":{"%":0.2},"14.8":{"%":0.2},"14.9":{"%":0.2}}},"15":{"%":0.2,"P":{"15.1":{"%":0.2},"15.2":{"%":0.2},"15.3":{"%":0.2},"15.4":{"%":0.2},"15.5":{"%":0.2},"15.6":{"%":0.2},"15.7":{"%":0.2}}},"16":{"%":0.27142857142857142,"P":{"16.1":{"%":0.2},"16.2":{"%":0.7},"16.3":{"%":0.2},"16.4":{"%":0.2},"16.5":{"%":0.2},"16.6":{"%":0.2},"16.7":{"%":0.2},"16.8":{"%":0.2},"16.9":{"%":0.2},"16.10":{"%":0.2},"16.11":{"%":0.2},"16.12":{"%":0.2},"16.13":{"%":0.2},"16.14":{"%":0.2}}},"17":{"%":0.1111111111111111,"P":{"17.1":{"%":0.7},"17.2":{"%":0.2},"17.3":{"%":0.2},"17.4":{"%":0.2},"17.5":{"%":0.2},"17.6":{"%":0.2},"17.7":{"%":0.2},"17.8":{"%":0.2},"17.9":{"%":0.2}}},"18":{"%":0.2,"P":{"18.1":{"%":0.2},"18.2":{"%":0.2},"18.3":{"%":0.2},"18.4":{"%":0.2}}}},"%":0.2500521083854417},"v7":{"wertyui":{"1":{"%":0.125,"P":{"1.1":{"%":0.2},"1.2":{"%":0.2},"1.3":{"%":0.2},"1.4":{"%":0.7},"1.5":{"%":0.2},"1.6":{"%":0.2},"1.7":{"%":0.2},"1.8":{"%":0.2}}},"2":{"%":0.2,"P":{"2.1":{"%":0.2},"2.2":{"%":0.2},"2.3":{"%":0.2},"2.4":{"%":0.2},"2.5":{"%":0.2},"2.6":{"%":0.2},"2.7":{"%":0.2},"2.8":{"%":0.2},"2.9":{"%":0.2},"2.10":{"%":0.2}}},"3":{"%":0.2857142857142857,"P":{"3.1":{"%":0.7},"3.2":{"%":0.2},"3.3":{"%":0.2},"3.4":{"%":0.7},"3.5":{"%":0.2},"3.6":{"%":0.2},"3.7":{"%":0.2}}},"4":{"%":0.4444444444444444,"P":{"4.1":{"%":0.2},"4.2":{"%":0.2},"4.3":{"%":0.7},"4.4":{"%":0.7},"4.5":{"%":0.7},"4.6":{"%":0.2},"4.7":{"%":0.2},"4.8":{"%":0.2},"4.9":{"%":0.7}}},"5":{"%":0.6,"P":{"5.1":{"%":0.7},"5.2":{"%":0.2},"5.3":{"%":0.2},"5.4":{"%":0.7},"5.5":{"%":0.7}}},"6":{"%":0.625,"P":{"6.1":{"%":0.2},"6.2":{"%":0.7},"6.3":{"%":0.7},"6.4":{"%":0.7},"6.5":{"%":0.7},"6.6":{"%":0.2},"6.7":{"%":0.7},"6.8":{"%":0.2}}},"7":{"%":0.2,"P":{"7.1":{"%":0.2},"7.2":{"%":0.2},"7.3":{"%":0.2},"7.4":{"%":0.2},"7.5":{"%":0.2},"7.6":{"%":0.2},"7.7":{"%":0.2},"7.8":{"%":0.2},"7.9":{"%":0.2},"7.10":{"%":0.2}}},"8":{"%":0.2,"P":{"8.1":{"%":0.2},"8.2":{"%":0.2},"8.3":{"%":0.2},"8.4":{"%":0.2},"8.5":{"%":0.2},"8.6":{"%":0.2},"8.7":{"%":0.2},"8.8":{"%":0.2}}},"9":{"%":0.6,"P":{"9.1":{"%":0.7},"9.2":{"%":0.7},"9.3":{"%":0.2},"9.4":{"%":0.2},"9.5":{"%":0.7}}},"10":{"%":0.4,"P":{"10.1":{"%":0.7},"10.2":{"%":0.2},"10.3":{"%":0.2},"10.4":{"%":0.7},"13":{"%":0.2}}},"11":{"%":0.5714285714285714,"P":{"11.1":{"%":0.7},"11.2":{"%":0.7},"11.3":{"%":0.7},"11.4":{"%":0.2},"11.5":{"%":0.2},"11.6":{"%":0.2},"11.7":{"%":0.7}}},"12":{"%":0.4166666666666667,"P":{"12.1":{"%":0.2},"12.2":{"%":0.7},"12.3":{"%":0.7},"12.4":{"%":0.7},"12.5":{"%":0.7},"12.6":{"%":0.7},"12.7":{"%":0.2},"12.8":{"%":0.2},"12.9":{"%":0.2},"12.10":{"%":0.2},"12.11":{"%":0.2},"12.12":{"%":0.2}}},"13":{"%":0.1111111111111111,"P":{"13.1":{"%":0.2},"13.2":{"%":0.7},"13.3":{"%":0.2},"13.4":{"%":0.2},"13.5":{"%":0.2},"13.6":{"%":0.2},"13.7":{"%":0.2},"13.8":{"%":0.2},"13.9":{"%":0.2}}},"14":{"%":0.4444444444444444,"P":{"14.1":{"%":0.7},"14.2":{"%":0.2},"14.3":{"%":0.2},"14.4":{"%":0.7},"14.5":{"%":0.2},"14.6":{"%":0.2},"14.7":{"%":0.2},"14.8":{"%":0.7},"14.9":{"%":0.7}}},"15":{"%":0.1,"P":{"15.1":{"%":0.2},"15.2":{"%":0.2},"15.3":{"%":0.2},"15.4":{"%":0.2},"15.5":{"%":0.2},"15.6":{"%":0.2},"15.7":{"%":0.2},"15.8":{"%":0.7},"15.9":{"%":0.2},"15.10":{"%":0.2}}},"16":{"%":0.38461538461538464,"P":{"16.1":{"%":0.7},"16.2":{"%":0.7},"16.3":{"%":0.2},"16.4":{"%":0.7},"16.5":{"%":0.7},"16.6":{"%":0.2},"16.7":{"%":0.2},"16.8":{"%":0.2},"16.9":{"%":0.7},"16.10":{"%":0.2},"16.11":{"%":0.2},"16.12":{"%":0.2},"16.13":{"%":0.2}}},"17":{"%":0.2,"P":{"17.1":{"%":0.2},"17.2":{"%":0.2},"17.3":{"%":0.2},"17.4":{"%":0.2},"17.5":{"%":0.2},"17.6":{"%":0.2},"17.7":{"%":0.2},"17.8":{"%":0.2},"17.9":{"%":0.2}}},"18":{"%":0.29090909090909091,"P":{"18.1":{"%":0.2},"18.2":{"%":0.2},"18.3":{"%":0.2},"18.4":{"%":0.2},"18.5":{"%":0.2},"18.6":{"%":0.2},"18.7":{"%":0.2},"18.8":{"%":0.2},"18.9":{"%":0.2},"18.10":{"%":0.7},"18.11":{"%":0.2}}},"19":{"%":0.2,"P":{"19.1":{"%":0.2},"19.2":{"%":0.2},"19.3":{"%":0.2},"19.4":{"%":0.2},"19.5":{"%":0.2},"19.6":{"%":0.2},"19.7":{"%":0.2},"19.8":{"%":0.2}}},"20":{"%":0.2,"P":{"20.1":{"%":0.2},"20.2":{"%":0.2},"20.3":{"%":0.2},"20.4":{"%":0.2},"20.5":{"%":0.2},"20.6":{"%":0.2},"20.7":{"%":0.2}}}},"%":0.25996669996669997}},"Standard2":{"19":{"Table 2 \u2014 Technical document coverage percentage":{"Evaluate, Direct and Monitor":{"%":0.2,"P":{"EDM04":{"%":0.2}}},"Align, Plan and Organize":{"%":0.51125,"P":{"APO01":{"%":0.7},"APO03":{"%":0.7},"APO04":{"%":0.2},"APO06":{"%":0.2},"APO09":{"%":0.29},"APO11":{"%":0.2},"APO13":{"%":0.7},"APO14":{"%":0.7}}},"Build, Acquire and Implement":{"%":0.2,"P":{"BAI02":{"%":0.2},"BAI03":{"%":0.2},"BAI04":{"%":0.2},"BAI06":{"%":0.2},"BAI07":{"%":0.2},"BAI09":{"%":0.2},"BAI10":{"%":0.2}}},"Deliver, Service and Support":{"%":0.5,"P":{"DSS01":{"%":0.2},"DSS02":{"%":0.2},"DSS03":{"%":0.7},"DSS04":{"%":0.2},"DSS05":{"%":0.7},"DSS06":{"%":0.7}}},"Monitor, Evaluate and Assess":{"%":0.2,"P":{"MEA01":{"%":0.2},"MEA02":{"%":0.2},"MEA03":{"%":0.2}}}},"%":0.20224999999999999}},"CMMC":{"v2.0":{"Domain":{"AC":{"%":0.8055555555555556,"P":{"AC.L1":{"%":0.7,"P":{"AC.L1-3.1.1":{"%":0.7},"AC.L1-3.1.2":{"%":0.7},"AC.L1-3.1.20":{"%":0.7},"AC.L1-3.1.22":{"%":0.7}}},"AC.L2":{"%":0.6111111111111112,"P":{"AC.L2-3.1.3":{"%":0.7},"AC.L2-3.1.4":{"%":0.7},"AC.L2-3.1.5":{"%":0.7},"AC.L2-3.1.6":{"%":0.7},"AC.L2-3.1.7":{"%":0.7},"AC.L2-3.1.8":{"%":0.2},"AC.L2-3.1.9":{"%":0.2},"AC.L2-3.1.10":{"%":0.2},"AC.L2-3.1.11":{"%":0.2},"AC.L2-3.1.12":{"%":0.7},"AC.L2-3.1.13":{"%":0.7},"AC.L2-3.1.14":{"%":0.7},"AC.L2-3.1.15":{"%":0.7},"AC.L2-3.1.16":{"%":0.7},"AC.L2-3.1.17":{"%":0.7},"AC.L2-3.1.18":{"%":0.2},"AC.L2-3.1.19":{"%":0.2},"AC.L2-3.1.21":{"%":0.2}}}}},"AU":{"%":0.3333333333333333,"P":{"AU.L2":{"%":0.3333333333333333,"P":{"AU.L2-3.3.1":{"%":0.7},"AU.L2-3.3.2":{"%":0.2},"AU.L2-3.3.3":{"%":0.2},"AU.L2-3.3.4":{"%":0.2},"AU.L2-3.3.5":{"%":0.7},"AU.L2-3.3.6":{"%":0.2},"AU.L2-3.3.7":{"%":0.2},"AU.L2-3.3.8":{"%":0.7},"AU.L2-3.3.9":{"%":0.2}}}}},"CM":{"%":0.4444444444444444,"P":{"CM.L2":{"%":0.4444444444444444,"P":{"CM.L2-3.4.1":{"%":0.7},"CM.L2-3.4.2":{"%":0.7},"CM.L2-3.4.3":{"%":0.2},"CM.L2-3.4.4":{"%":0.2},"CM.L2-3.4.5":{"%":0.2},"CM.L2-3.4.6":{"%":0.7},"CM.L2-3.4.7":{"%":0.7},"CM.L2-3.4.8":{"%":0.2},"CM.L2-3.4.9":{"%":0.2}}}}},"IA":{"%":0.3611111111111111,"P":{"IA.L1":{"%":0.5,"P":{"IA.L1-3.5.1":{"%":0.2},"IA.L1-3.5.2":{"%":0.7}}},"IA.L2":{"%":0.2222222222222222,"P":{"IA.L2-3.5.3":{"%":0.7},"IA.L2-3.5.4":{"%":0.2},"IA.L2-3.5.5":{"%":0.2},"IA.L2-3.5.6":{"%":0.2},"IA.L2-3.5.7":{"%":0.7},"IA.L2-3.5.8":{"%":0.2},"IA.L2-3.5.9":{"%":0.2},"IA.L2-3.5.10":{"%":0.2},"IA.L2-3.5.11":{"%":0.2}}}}},"IR":{"%":0.2,"P":{"IR.L2":{"%":0.2,"P":{"IR.L2-3.6.1":{"%":0.2},"IR.L2-3.6.2":{"%":0.2},"IR.L2-3.6.3":{"%":0.2}}}}},"MA":{"%":0.2,"P":{"MA.L2":{"%":0.2,"P":{"MA.L2-3.7.1":{"%":0.2},"MA.L2-3.7.2":{"%":0.2},"MA.L2-3.7.3":{"%":0.2},"MA.L2-3.7.4":{"%":0.2},"MA.L2-3.7.5":{"%":0.2},"MA.L2-3.7.6":{"%":0.2}}}}},"MP":{"%":0.1875,"P":{"MP.L1":{"%":0.2,"P":{"MP.L1-3.8.3":{"%":0.2}}},"MP.L2":{"%":0.375,"P":{"MP.L2-3.8.1":{"%":0.7},"MP.L2-3.8.2":{"%":0.7},"MP.L2-3.8.4":{"%":0.2},"MP.L2-3.8.5":{"%":0.2},"MP.L2-3.8.6":{"%":0.2},"MP.L2-3.8.7":{"%":0.2},"MP.L2-3.8.8":{"%":0.2},"MP.L2-3.8.9":{"%":0.7}}}}},"PS":{"%":0.2,"P":{"PS.L2":{"%":0.2,"P":{"PS.L2-3.9.2":{"%":0.2}}}}},"RA":{"%":0.3333333333333333,"P":{"RA.L2":{"%":0.3333333333333333,"P":{"RA.L2-3.11.1":{"%":0.2},"RA.L2-3.11.2":{"%":0.7},"RA.L2-3.11.3":{"%":0.2}}}}},"CA":{"%":0.2,"P":{"CA.L2":{"%":0.2,"P":{"CA.L2-3.12.1":{"%":0.2},"CA.L2-3.12.2":{"%":0.2},"CA.L2-3.12.3":{"%":0.2},"CA.L2-3.12.4":{"%":0.2}}}}},"SC":{"%":0.7857142857142857,"P":{"SC.L1":{"%":0.7,"P":{"SC.L1-3.13.1":{"%":0.7},"SC.L1-3.13.5":{"%":0.7}}},"SC.L2":{"%":0.5714285714285714,"P":{"SC.L2-3.13.2":{"%":0.7},"SC.L2-3.13.3":{"%":0.7},"SC.L2-3.13.4":{"%":0.2},"SC.L2-3.13.6":{"%":0.7},"SC.L2-3.13.7":{"%":0.2},"SC.L2-3.13.8":{"%":0.7},"SC.L2-3.13.9":{"%":0.2},"SC.L2-3.13.10":{"%":0.7},"SC.L2-3.13.11":{"%":0.7},"SC.L2-3.13.12":{"%":0.2},"SC.L2-3.13.13":{"%":0.2},"SC.L2-3.13.14":{"%":0.2},"SC.L2-3.13.15":{"%":0.7},"SC.L2-3.13.16":{"%":0.7}}}}},"SI":{"%":0.75,"P":{"SI.L1":{"%":0.5,"P":{"SI.L1-3.14.1":{"%":0.7},"SI.L1-3.14.2":{"%":0.2},"SI.L1-3.14.4":{"%":0.2},"SI.L1-3.14.5":{"%":0.7}}},"SI.L2":{"%":0.7,"P":{"SI.L2-3.14.3":{"%":0.7},"SI.L2-3.14.6":{"%":0.7},"SI.L2-3.14.7":{"%":0.7}}}}}},"%":0.3334160052910053}},"CE":{"v2.2":{"Domain":{"Boundary Firewalls and Internet Gateways":{"%":0.3333333333333333,"P":{"A4.1":{"%":0.2,"P":{"null":{"%":0.2}}},"A4.2":{"%":0.2,"P":{"null":{"%":0.2}}},"A4.3":{"%":0.2,"P":{"null":{"%":0.2}}},"A4.4":{"%":0.2,"P":{"null":{"%":0.2}}},"A4.5":{"%":0.7,"P":{"null":{"%":0.7}}},"A4.6":{"%":0.2,"P":{"null":{"%":0.2}}},"A4.7":{"%":0.7,"P":{"null":{"%":0.7}}},"A4.8":{"%":0.7,"P":{"null":{"%":0.7}}},"A4.9":{"%":0.2,"P":{"null":{"%":0.2}}},"A4.10":{"%":0.2,"P":{"null":{"%":0.2}}},"A4.11":{"%":0.7,"P":{"null":{"%":0.7}}},"A4.12":{"%":0.2,"P":{"null":{"%":0.2}}}}},"Secure Configuration":{"%":0.2,"P":{"A5.1":{"%":0.2,"P":{"null":{"%":0.2}}},"A5.2":{"%":0.2,"P":{"null":{"%":0.2}}},"A5.3":{"%":0.2,"P":{"null":{"%":0.2}}},"A5.4":{"%":0.2,"P":{"null":{"%":0.2}}},"A5.5":{"%":0.2,"P":{"null":{"%":0.2}}},"A5.6":{"%":0.2,"P":{"null":{"%":0.2}}},"A5.7":{"%":0.2,"P":{"null":{"%":0.2}}},"A5.8":{"%":0.2,"P":{"null":{"%":0.2}}},"A5.9":{"%":0.2,"P":{"null":{"%":0.2}}}}},"Device Locking":{"%":0.2,"P":{"A5.10":{"%":0.2,"P":{"null":{"%":0.2}}},"A5.11":{"%":0.2,"P":{"null":{"%":0.2}}}}},"Security update management":{"%":0.21428571428571427,"P":{"A6.1":{"%":0.2,"P":{"null":{"%":0.2}}},"A6.2":{"%":0.2,"P":{"A6.2.1":{"%":0.2},"A6.2.2":{"%":0.2},"A6.2.3":{"%":0.2},"A6.2.4":{"%":0.2}}},"A6.3":{"%":0.2,"P":{"A6.2.4":{"%":0.2}}},"A6.4":{"%":0.7,"P":{"A6.4.1":{"%":0.7},"A6.4.2":{"%":0.7}}},"A6.5":{"%":0.5,"P":{"A6.5.1":{"%":0.7},"A6.5.2":{"%":0.2}}},"A6.6":{"%":0.2,"P":{"A6.5.2":{"%":0.2}}},"A6.7":{"%":0.2,"P":{"A6.5.2":{"%":0.2}}}}},"User Access Control":{"%":0.5,"P":{"A7.1":{"%":0.7,"P":{"null":{"%":0.7}}},"A7.2":{"%":0.2,"P":{"null":{"%":0.2}}},"A7.3":{"%":0.2,"P":{"null":{"%":0.2}}},"A7.4":{"%":0.7,"P":{"null":{"%":0.7}}}}},"AdmiStandard11rative Accounts":{"%":0.7,"P":{"A7.5":{"%":0.7,"P":{"null":{"%":0.7}}},"A7.6":{"%":0.7,"P":{"null":{"%":0.7}}},"A7.7":{"%":0.7,"P":{"null":{"%":0.7}}},"A7.8":{"%":0.7,"P":{"null":{"%":0.7}}},"A7.9":{"%":0.7,"P":{"null":{"%":0.7}}}}},"Password-Based Authentication":{"%":0.125,"P":{"A7.10":{"%":0.2,"P":{"null":{"%":0.2}}},"A7.11":{"%":0.2,"P":{"null":{"%":0.2}}},"A7.12":{"%":0.2,"P":{"null":{"%":0.2}}},"A7.13":{"%":0.2,"P":{"null":{"%":0.2}}},"A7.14":{"%":0.2,"P":{"null":{"%":0.2}}},"A7.15":{"%":0.2,"P":{"null":{"%":0.2}}},"A7.16":{"%":0.7,"P":{"null":{"%":0.7}}},"A7.17":{"%":0.2,"P":{"null":{"%":0.2}}}}},"Malware protection":{"%":0.16666666666666666,"P":{"A8.1":{"%":0.2,"P":{"null":{"%":0.2}}},"A8.2":{"%":0.2,"P":{"null":{"%":0.2}}},"A8.3":{"%":0.7,"P":{"null":{"%":0.7}}},"A8.4":{"%":0.2,"P":{"null":{"%":0.2}}},"A8.5":{"%":0.2,"P":{"null":{"%":0.2}}},"A8.6":{"%":0.2,"P":{"null":{"%":0.2}}}}}},"%":0.29241071428571425}},"CIS AWS Foundations Benchmark":{"v1.3.0":{"Table 3 \u2014 Full (Technical) document coverage percentage":{"1":{"%":0.8571428571428571,"P":{"1.1":{"%":0.2,"P":{"null":{"%":0.2}}},"1.2":{"%":0.2,"P":{"null":{"%":0.2}}},"1.3":{"%":0.2,"P":{"null":{"%":0.2}}},"1.4":{"%":0.7,"P":{"null":{"%":0.7}}},"1.5":{"%":0.7,"P":{"null":{"%":0.7}}},"1.6":{"%":0.7,"P":{"null":{"%":0.7}}},"1.7":{"%":0.7,"P":{"null":{"%":0.7}}},"1.8":{"%":0.7,"P":{"null":{"%":0.7}}},"1.9":{"%":0.7,"P":{"null":{"%":0.7}}},"1.10":{"%":0.7,"P":{"null":{"%":0.7}}},"1.11":{"%":0.7,"P":{"null":{"%":0.7}}},"1.12":{"%":0.7,"P":{"null":{"%":0.7}}},"1.13":{"%":0.7,"P":{"null":{"%":0.7}}},"1.14":{"%":0.7,"P":{"null":{"%":0.7}}},"1.15":{"%":0.7,"P":{"null":{"%":0.7}}},"1.16":{"%":0.7,"P":{"null":{"%":0.7}}},"1.17":{"%":0.7,"P":{"null":{"%":0.7}}},"1.18":{"%":0.7,"P":{"null":{"%":0.7}}},"1.19":{"%":0.7,"P":{"null":{"%":0.7}}},"1.20":{"%":0.7,"P":{"null":{"%":0.7}}},"1.21":{"%":0.7,"P":{"null":{"%":0.7}}}}},"2":{"%":0.7,"P":{"2.1":{"%":0.7,"P":{"2.1.1":{"%":0.7},"2.1.2":{"%":0.7}}},"2.2":{"%":0.7,"P":{"2.2.1":{"%":0.7}}}}},"3":{"%":0.7,"P":{"3.1":{"%":0.7,"P":{"null":{"%":0.7}}},"3.2":{"%":0.7,"P":{"null":{"%":0.7}}},"3.3":{"%":0.7,"P":{"null":{"%":0.7}}},"3.4":{"%":0.7,"P":{"null":{"%":0.7}}},"3.5":{"%":0.7,"P":{"null":{"%":0.7}}},"3.6":{"%":0.7,"P":{"null":{"%":0.7}}},"3.7":{"%":0.7,"P":{"null":{"%":0.7}}},"3.8":{"%":0.7,"P":{"null":{"%":0.7}}},"3.9":{"%":0.7,"P":{"null":{"%":0.7}}},"3.10":{"%":0.7,"P":{"null":{"%":0.7}}},"3.11":{"%":0.7,"P":{"null":{"%":0.7}}}}},"4":{"%":0.7,"P":{"4.1":{"%":0.7,"P":{"null":{"%":0.7}}},"4.2":{"%":0.7,"P":{"null":{"%":0.7}}},"4.3":{"%":0.7,"P":{"null":{"%":0.7}}},"4.4":{"%":0.7,"P":{"null":{"%":0.7}}},"4.5":{"%":0.7,"P":{"null":{"%":0.7}}},"4.6":{"%":0.7,"P":{"null":{"%":0.7}}},"4.7":{"%":0.7,"P":{"null":{"%":0.7}}},"4.8":{"%":0.7,"P":{"null":{"%":0.7}}},"4.9":{"%":0.7,"P":{"null":{"%":0.7}}},"4.10":{"%":0.7,"P":{"null":{"%":0.7}}},"4.11":{"%":0.7,"P":{"null":{"%":0.7}}},"4.12":{"%":0.7,"P":{"null":{"%":0.7}}},"4.13":{"%":0.7,"P":{"null":{"%":0.7}}},"4.14":{"%":0.7,"P":{"null":{"%":0.7}}},"4.15":{"%":0.7,"P":{"null":{"%":0.7}}}}},"5":{"%":0.7,"P":{"5.1":{"%":0.7,"P":{"null":{"%":0.7}}},"5.2":{"%":0.7,"P":{"null":{"%":0.7}}},"5.3":{"%":0.7,"P":{"null":{"%":0.7}}}}}},"%":0.9714285714285715},"v1.4.0":{"wertyui":{"1":{"%":0.8095238095238095,"P":{"1.1":{"%":0.2,"P":{"null":{"%":0.2}}},"1.2":{"%":0.2,"P":{"null":{"%":0.2}}},"1.3":{"%":0.2,"P":{"null":{"%":0.2}}},"1.4":{"%":0.7,"P":{"null":{"%":0.7}}},"1.5":{"%":0.7,"P":{"null":{"%":0.7}}},"1.6":{"%":0.7,"P":{"null":{"%":0.7}}},"1.7":{"%":0.7,"P":{"null":{"%":0.7}}},"1.8":{"%":0.7,"P":{"null":{"%":0.7}}},"1.9":{"%":0.7,"P":{"null":{"%":0.7}}},"1.10":{"%":0.7,"P":{"null":{"%":0.7}}},"1.11":{"%":0.7,"P":{"null":{"%":0.7}}},"1.12":{"%":0.7,"P":{"null":{"%":0.7}}},"1.13":{"%":0.7,"P":{"null":{"%":0.7}}},"1.14":{"%":0.7,"P":{"null":{"%":0.7}}},"1.15":{"%":0.7,"P":{"null":{"%":0.7}}},"1.16":{"%":0.7,"P":{"null":{"%":0.7}}},"1.17":{"%":0.7,"P":{"null":{"%":0.7}}},"1.18":{"%":0.7,"P":{"null":{"%":0.7}}},"1.19":{"%":0.7,"P":{"null":{"%":0.7}}},"1.20":{"%":0.7,"P":{"null":{"%":0.7}}},"1.21":{"%":0.2,"P":{"null":{"%":0.2}}}}},"2":{"%":0.9333333333333332,"P":{"2.1":{"%":0.8,"P":{"2.1.1":{"%":0.7},"2.1.2":{"%":0.7},"2.1.3":{"%":0.7},"2.1.4":{"%":0.2},"2.1.5":{"%":0.7}}},"2.2":{"%":0.7,"P":{"2.2.1":{"%":0.7}}},"2.3":{"%":0.7,"P":{"2.3.1":{"%":0.7}}}}},"3":{"%":0.7,"P":{"3.1":{"%":0.7,"P":{"null":{"%":0.7}}},"3.2":{"%":0.7,"P":{"null":{"%":0.7}}},"3.3":{"%":0.7,"P":{"null":{"%":0.7}}},"3.4":{"%":0.7,"P":{"null":{"%":0.7}}},"3.5":{"%":0.7,"P":{"null":{"%":0.7}}},"3.6":{"%":0.7,"P":{"null":{"%":0.7}}},"3.7":{"%":0.7,"P":{"null":{"%":0.7}}},"3.8":{"%":0.7,"P":{"null":{"%":0.7}}},"3.9":{"%":0.7,"P":{"null":{"%":0.7}}},"3.10":{"%":0.7,"P":{"null":{"%":0.7}}},"3.11":{"%":0.7,"P":{"null":{"%":0.7}}}}},"4":{"%":0.7,"P":{"4.1":{"%":0.7,"P":{"null":{"%":0.7}}},"4.2":{"%":0.7,"P":{"null":{"%":0.7}}},"4.3":{"%":0.7,"P":{"null":{"%":0.7}}},"4.4":{"%":0.7,"P":{"null":{"%":0.7}}},"4.5":{"%":0.7,"P":{"null":{"%":0.7}}},"4.6":{"%":0.7,"P":{"null":{"%":0.7}}},"4.7":{"%":0.7,"P":{"null":{"%":0.7}}},"4.8":{"%":0.7,"P":{"null":{"%":0.7}}},"4.9":{"%":0.7,"P":{"null":{"%":0.7}}},"4.10":{"%":0.7,"P":{"null":{"%":0.7}}},"4.11":{"%":0.7,"P":{"null":{"%":0.7}}},"4.12":{"%":0.7,"P":{"null":{"%":0.7}}},"4.13":{"%":0.7,"P":{"null":{"%":0.7}}},"4.14":{"%":0.7,"P":{"null":{"%":0.7}}},"4.15":{"%":0.7,"P":{"null":{"%":0.7}}}}},"5":{"%":0.7,"P":{"5.1":{"%":0.7,"P":{"null":{"%":0.7}}},"5.2":{"%":0.7,"P":{"null":{"%":0.7}}},"5.3":{"%":0.7,"P":{"null":{"%":0.7}}}}}},"%":0.9485714285714286},"v1.5.0":{"Table 2 \u2014 Full (Technical) document coverage percentage":{"1":{"%":0.8095238095238095,"P":{"1.1":{"%":0.2,"P":{"null":{"%":0.2}}},"1.2":{"%":0.2,"P":{"null":{"%":0.2}}},"1.3":{"%":0.2,"P":{"null":{"%":0.2}}},"1.4":{"%":0.7,"P":{"null":{"%":0.7}}},"1.5":{"%":0.7,"P":{"null":{"%":0.7}}},"1.6":{"%":0.7,"P":{"null":{"%":0.7}}},"1.7":{"%":0.7,"P":{"null":{"%":0.7}}},"1.8":{"%":0.7,"P":{"null":{"%":0.7}}},"1.9":{"%":0.7,"P":{"null":{"%":0.7}}},"1.10":{"%":0.7,"P":{"null":{"%":0.7}}},"1.11":{"%":0.7,"P":{"null":{"%":0.7}}},"1.12":{"%":0.7,"P":{"null":{"%":0.7}}},"1.13":{"%":0.7,"P":{"null":{"%":0.7}}},"1.14":{"%":0.7,"P":{"null":{"%":0.7}}},"1.15":{"%":0.7,"P":{"null":{"%":0.7}}},"1.16":{"%":0.7,"P":{"null":{"%":0.7}}},"1.17":{"%":0.7,"P":{"null":{"%":0.7}}},"1.18":{"%":0.7,"P":{"null":{"%":0.7}}},"1.19":{"%":0.7,"P":{"null":{"%":0.7}}},"1.20":{"%":0.7,"P":{"null":{"%":0.7}}},"1.21":{"%":0.2,"P":{"null":{"%":0.2}}}}},"2":{"%":0.8666666666666667,"P":{"2.1":{"%":0.8,"P":{"2.1.1":{"%":0.7},"2.1.2":{"%":0.7},"2.1.3":{"%":0.7},"2.1.4":{"%":0.2},"2.1.5":{"%":0.7}}},"2.2":{"%":0.7,"P":{"2.2.1":{"%":0.7}}},"2.3":{"%":0.6666666666666666,"P":{"2.3.1":{"%":0.7},"2.3.2":{"%":0.2},"2.3.3":{"%":0.7}}},"2.4":{"%":0.7,"P":{"2.4.1":{"%":0.7}}}}},"3":{"%":0.7,"P":{"3.1":{"%":0.7,"P":{"null":{"%":0.7}}},"3.2":{"%":0.7,"P":{"null":{"%":0.7}}},"3.3":{"%":0.7,"P":{"null":{"%":0.7}}},"3.4":{"%":0.7,"P":{"null":{"%":0.7}}},"3.5":{"%":0.7,"P":{"null":{"%":0.7}}},"3.6":{"%":0.7,"P":{"null":{"%":0.7}}},"3.7":{"%":0.7,"P":{"null":{"%":0.7}}},"3.8":{"%":0.7,"P":{"null":{"%":0.7}}},"3.9":{"%":0.7,"P":{"null":{"%":0.7}}},"3.10":{"%":0.7,"P":{"null":{"%":0.7}}},"3.11":{"%":0.7,"P":{"null":{"%":0.7}}}}},"4":{"%":0.7,"P":{"4.1":{"%":0.7,"P":{"null":{"%":0.7}}},"4.2":{"%":0.7,"P":{"null":{"%":0.7}}},"4.3":{"%":0.7,"P":{"null":{"%":0.7}}},"4.4":{"%":0.7,"P":{"null":{"%":0.7}}},"4.5":{"%":0.7,"P":{"null":{"%":0.7}}},"4.6":{"%":0.7,"P":{"null":{"%":0.7}}},"4.7":{"%":0.7,"P":{"null":{"%":0.7}}},"4.8":{"%":0.7,"P":{"null":{"%":0.7}}},"4.9":{"%":0.7,"P":{"null":{"%":0.7}}},"4.10":{"%":0.7,"P":{"null":{"%":0.7}}},"4.11":{"%":0.7,"P":{"null":{"%":0.7}}},"4.12":{"%":0.7,"P":{"null":{"%":0.7}}},"4.13":{"%":0.7,"P":{"null":{"%":0.7}}},"4.14":{"%":0.7,"P":{"null":{"%":0.7}}},"4.15":{"%":0.7,"P":{"null":{"%":0.7}}},"4.16":{"%":0.7,"P":{"null":{"%":0.7}}}}},"5":{"%":0.7,"P":{"5.1":{"%":0.7,"P":{"null":{"%":0.7}}},"5.2":{"%":0.7,"P":{"null":{"%":0.7}}},"5.3":{"%":0.7,"P":{"null":{"%":0.7}}},"5.4":{"%":0.7,"P":{"null":{"%":0.7}}}}}},"%":0.9352380952380953}},"Standard4":{"v4":{"Control Domain":{"Audit & Assurance":{"%":0.2,"P":{"A&A-01":{"%":0.2},"A&A-02":{"%":0.2},"A&A-03":{"%":0.2},"A&A-04":{"%":0.2},"A&A-05":{"%":0.2},"A&A-06":{"%":0.2}}},"Application & Interface Security":{"%":0.2857142857142857,"P":{"AIS-01":{"%":0.2},"AIS-02":{"%":0.2},"AIS-03":{"%":0.7},"AIS-04":{"%":0.2},"AIS-05":{"%":0.2},"AIS-06":{"%":0.2},"AIS-07":{"%":0.7}}},"Business Continuity Management and Operational Resilience":{"%":0.18181818181818182,"P":{"BCR-01":{"%":0.7},"BCR-02":{"%":0.2},"BCR-03":{"%":0.2},"BCR-04":{"%":0.2},"BCR-05":{"%":0.2},"BCR-06":{"%":0.2},"BCR-07":{"%":0.2},"BCR-08":{"%":0.7},"BCR-09":{"%":0.2},"BCR-10":{"%":0.2},"BCR-11":{"%":0.2}}},"Change Control and Configuration Management":{"%":0.3333333333333333,"P":{"CCC-01":{"%":0.7},"CCC-02":{"%":0.2},"CCC-03":{"%":0.2},"CCC-04":{"%":0.7},"CCC-05":{"%":0.2},"CCC-06":{"%":0.2},"CCC-07":{"%":0.7},"CCC-08":{"%":0.2},"CCC-09":{"%":0.2}}},"Cryptography, Encryption & Key Management":{"%":0.29523809523809523,"P":{"CEK-01":{"%":0.2},"CEK-02":{"%":0.2},"CEK-03":{"%":0.7},"CEK-04":{"%":0.2},"CEK-05":{"%":0.2},"CEK-06":{"%":0.2},"CEK-07":{"%":0.2},"CEK-08":{"%":0.2},"CEK-09":{"%":0.2},"CEK-10":{"%":0.2},"CEK-11":{"%":0.2},"CEK-12":{"%":0.7},"CEK-13":{"%":0.2},"CEK-14":{"%":0.2},"CEK-15":{"%":0.2},"CEK-16":{"%":0.2},"CEK-17":{"%":0.2},"CEK-18":{"%":0.2},"CEK-19":{"%":0.2},"CEK-20":{"%":0.2},"CEK-21":{"%":0.2}}},"Datacenter Security":{"%":0.6,"P":{"DCS-01":{"%":0.7},"DCS-02":{"%":0.2},"DCS-05":{"%":0.7},"DCS-06":{"%":0.7},"DCS-08":{"%":0.2}}},"Data Security and Privacy Lifecycle Management":{"%":0.25263157894736842,"P":{"DSP-01":{"%":0.7},"DSP-02":{"%":0.2},"DSP-03":{"%":0.2},"DSP-04":{"%":0.2},"DSP-05":{"%":0.2},"DSP-06":{"%":0.2},"DSP-07":{"%":0.2},"DSP-08":{"%":0.2},"DSP-09":{"%":0.2},"DSP-10":{"%":0.2},"DSP-11":{"%":0.2},"DSP-12":{"%":0.2},"DSP-13":{"%":0.2},"DSP-14":{"%":0.2},"DSP-15":{"%":0.2},"DSP-16":{"%":0.2},"DSP-17":{"%":0.2},"DSP-18":{"%":0.2},"DSP-19":{"%":0.2}}},"Governance, Risk and Compliance":{"%":0.125,"P":{"GRC-01":{"%":0.2},"GRC-02":{"%":0.2},"GRC-03":{"%":0.7},"GRC-04":{"%":0.2},"GRC-05":{"%":0.2},"GRC-06":{"%":0.2},"GRC-07":{"%":0.2},"GRC-08":{"%":0.2}}},"Human Resources":{"%":0.2,"P":{"HRS-04":{"%":0.2}}},"Identity & Access Management":{"%":0.6875,"P":{"IAM-01":{"%":0.7},"IAM-02":{"%":0.7},"IAM-03":{"%":0.2},"IAM-04":{"%":0.7},"IAM-05":{"%":0.7},"IAM-06":{"%":0.7},"IAM-07":{"%":0.7},"IAM-08":{"%":0.7},"IAM-09":{"%":0.7},"IAM-10":{"%":0.7},"IAM-11":{"%":0.2},"IAM-12":{"%":0.2},"IAM-13":{"%":0.2},"IAM-14":{"%":0.7},"IAM-15":{"%":0.2},"IAM-16":{"%":0.7}}},"Interoperability & Portability":{"%":0.2,"P":{"IPY-01":{"%":0.2},"IPY-02":{"%":0.2},"IPY-03":{"%":0.2},"IPY-04":{"%":0.2}}},"Infrastructure & Virtualization Security":{"%":0.5555555555555556,"P":{"IVS-01":{"%":0.7},"IVS-02":{"%":0.7},"IVS-03":{"%":0.7},"IVS-04":{"%":0.7},"IVS-05":{"%":0.2},"IVS-06":{"%":0.2},"IVS-07":{"%":0.2},"IVS-08":{"%":0.2},"IVS-09":{"%":0.7}}},"Logging and Monitoring":{"%":0.3076923076923077,"P":{"LOG-01":{"%":0.2},"LOG-02":{"%":0.2},"LOG-03":{"%":0.7},"LOG-04":{"%":0.7},"LOG-05":{"%":0.7},"LOG-06":{"%":0.2},"LOG-07":{"%":0.2},"LOG-08":{"%":0.7},"LOG-09":{"%":0.2},"LOG-10":{"%":0.2},"LOG-11":{"%":0.2},"LOG-12":{"%":0.2},"LOG-13":{"%":0.2}}},"Security Incident Management, E-Discovery, & Cloud Forensics":{"%":0.125,"P":{"SEF-01":{"%":0.2},"SEF-02":{"%":0.2},"SEF-03":{"%":0.7},"SEF-04":{"%":0.2},"SEF-05":{"%":0.2},"SEF-06":{"%":0.2},"SEF-07":{"%":0.2},"SEF-08":{"%":0.2}}},"Supply Chain Management, Transparency, and Accountability":{"%":0.2,"P":{"STA-01":{"%":0.2},"STA-02":{"%":0.2},"STA-03":{"%":0.2},"STA-04":{"%":0.2},"STA-05":{"%":0.2},"STA-06":{"%":0.2},"STA-07":{"%":0.2},"STA-08":{"%":0.2},"STA-09":{"%":0.2},"STA-10":{"%":0.2},"STA-11":{"%":0.2},"STA-12":{"%":0.2},"STA-13":{"%":0.2},"STA-14":{"%":0.2}}},"Threat & Vulnerability Management":{"%":0.1,"P":{"TVM-01":{"%":0.2},"TVM-02":{"%":0.2},"TVM-03":{"%":0.2},"TVM-04":{"%":0.2},"TVM-05":{"%":0.2},"TVM-06":{"%":0.2},"TVM-07":{"%":0.7},"TVM-08":{"%":0.2},"TVM-09":{"%":0.2},"TVM-10":{"%":0.2}}},"Universal Endpoint Management":{"%":0.14285714285714285,"P":{"UEM-01":{"%":0.2},"UEM-02":{"%":0.2},"UEM-03":{"%":0.2},"UEM-04":{"%":0.7},"UEM-05":{"%":0.2},"UEM-06":{"%":0.2},"UEM-07":{"%":0.2},"UEM-08":{"%":0.2},"UEM-09":{"%":0.2},"UEM-10":{"%":0.7},"UEM-11":{"%":0.2},"UEM-12":{"%":0.2},"UEM-13":{"%":0.2},"UEM-14":{"%":0.2}}}},"%":0.21131414595036885}},"CJIS":{"null":{"Control":{"5":{"%":0.19255952380952382,"P":{"5.1":{"%":0.25,"P":{"5.1.1":{"%":0.7,"P":{"5.1.1.1":{"%":0.7}}},"5.1.2":{"%":0.2,"P":{"null":{"%":0.2}}},"5.1.3":{"%":0.2,"P":{"null":{"%":0.2}}},"5.1.4":{"%":0.2,"P":{"null":{"%":0.2}}}}},"5.3":{"%":0.16666666666666666,"P":{"5.3.1":{"%":0.2,"P":{"5.3.1.1":{"%":0.2,"P":{"5.3.1.1.1":{"%":0.2},"5.3.1.1.2":{"%":0.2}}}}},"5.3.2":{"%":0.5,"P":{"5.3.2.1":{"%":0.7,"P":{"5.3.1.1.2":{"%":0.7}}},"5.3.2.2":{"%":0.2,"P":{"5.3.1.1.2":{"%":0.2}}}}},"5.3.4":{"%":0.2,"P":{"5.3.2.2":{"%":0.2}}}}},"5.4":{"%":0.5714285714285714,"P":{"5.4.1":{"%":0.7,"P":{"5.3.2.2":{"%":0.7}}},"5.4.2":{"%":0.2,"P":{"5.3.2.2":{"%":0.2}}},"5.4.3":{"%":0.7,"P":{"5.3.2.2":{"%":0.7}}},"5.4.4":{"%":0.2,"P":{"5.3.2.2":{"%":0.2}}},"5.4.5":{"%":0.7,"P":{"5.3.2.2":{"%":0.7}}},"5.4.6":{"%":0.7,"P":{"5.3.2.2":{"%":0.7}}},"5.4.7":{"%":0.2,"P":{"5.3.2.2":{"%":0.2}}}}},"5.5":{"%":0.3333333333333333,"P":{"5.5.1":{"%":0.7,"P":{"5.3.2.2":{"%":0.7}}},"5.5.2":{"%":0.7,"P":{"5.5.2.1":{"%":0.7,"P":{"5.3.1.1.2":{"%":0.7}}},"5.5.2.2":{"%":0.7,"P":{"5.3.1.1.2":{"%":0.7}}},"5.5.2.3":{"%":0.7,"P":{"5.3.1.1.2":{"%":0.7}}},"5.5.2.4":{"%":0.7,"P":{"5.3.1.1.2":{"%":0.7}}}}},"5.5.3":{"%":0.2,"P":{"5.5.2.4":{"%":0.2}}},"5.5.4":{"%":0.2,"P":{"5.5.2.4":{"%":0.2}}},"5.5.5":{"%":0.2,"P":{"5.5.2.4":{"%":0.2}}},"5.5.6":{"%":0.2,"P":{"5.5.6.1":{"%":0.2,"P":{"5.3.1.1.2":{"%":0.2}}},"5.5.6.2":{"%":0.2,"P":{"5.3.1.1.2":{"%":0.2}}}}}}},"5.6":{"%":0.10416666666666666,"P":{"5.6.1":{"%":0.2,"P":{"5.5.6.2":{"%":0.2}}},"5.6.2":{"%":0.41666666666666663,"P":{"5.6.2.1":{"%":0.3333333333333333,"P":{"5.6.2.1.1":{"%":0.7},"5.6.2.1.2":{"%":0.2},"5.6.2.1.3":{"%":0.2}}},"5.6.2.2":{"%":0.5,"P":{"5.6.2.2.1":{"%":0.7},"5.6.2.2.2":{"%":0.2}}}}},"5.6.3":{"%":0.2,"P":{"5.6.3.1":{"%":0.2,"P":{"5.6.2.2.2":{"%":0.2}}},"5.6.3.2":{"%":0.2,"P":{"5.6.2.2.2":{"%":0.2}}}}},"5.6.4":{"%":0.2,"P":{"5.6.3.2":{"%":0.2}}}}},"5.7":{"%":0.5,"P":{"5.7.1":{"%":0.7,"P":{"5.7.1.1":{"%":0.7,"P":{"5.6.2.2.2":{"%":0.7}}},"5.7.1.2":{"%":0.7,"P":{"5.6.2.2.2":{"%":0.7}}}}},"5.7.2":{"%":0.2,"P":{"5.7.1.2":{"%":0.2}}}}},"5.8":{"%":0.2,"P":{"5.8.1":{"%":0.2,"P":{"5.7.1.2":{"%":0.2}}},"5.8.2":{"%":0.2,"P":{"5.8.2.1":{"%":0.2}}},"5.8.3":{"%":0.2,"P":{"5.8.2.1":{"%":0.2}}}}},"5.10":{"%":0.2,"P":{"5.10.1":{"%":0.2,"P":{"5.10.1.1":{"%":0.2,"P":{"5.6.2.2.2":{"%":0.2}}},"5.10.1.2":{"%":0.2,"P":{"5.10.1.2.1":{"%":0.2},"5.10.1.2.2":{"%":0.2},"5.10.1.2.3":{"%":0.2}}},"5.10.1.3":{"%":0.2,"P":{"5.10.1.2.3":{"%":0.2}}},"5.10.1.4":{"%":0.2,"P":{"5.10.1.2.3":{"%":0.2}}},"5.10.1.5":{"%":0.2,"P":{"5.10.1.2.3":{"%":0.2}}}}},"5.10.2":{"%":0.2,"P":{"5.10.1.5":{"%":0.2}}},"5.10.3":{"%":0.2,"P":{"5.10.3.1":{"%":0.2,"P":{"5.10.1.2.3":{"%":0.2}}},"5.10.3.2":{"%":0.2,"P":{"5.10.1.2.3":{"%":0.2}}}}},"5.10.4":{"%":0.2,"P":{"5.10.4.1":{"%":0.2,"P":{"5.10.1.2.3":{"%":0.2}}},"5.10.4.2":{"%":0.2,"P":{"5.10.1.2.3":{"%":0.2}}},"5.10.4.3":{"%":0.2,"P":{"5.10.1.2.3":{"%":0.2}}},"5.10.4.4":{"%":0.2,"P":{"5.10.1.2.3":{"%":0.2}}},"5.10.4.5":{"%":0.2,"P":{"5.10.1.2.3":{"%":0.2}}}}}}},"5.12":{"%":0.2,"P":{"5.12.1":{"%":0.2,"P":{"5.10.4.5":{"%":0.2}}},"5.12.2":{"%":0.2,"P":{"5.10.4.5":{"%":0.2}}},"5.12.3":{"%":0.2,"P":{"5.10.4.5":{"%":0.2}}},"5.12.4":{"%":0.2,"P":{"5.10.4.5":{"%":0.2}}}}},"5.13":{"%":0.2,"P":{"5.13.2":{"%":0.2,"P":{"5.10.4.5":{"%":0.2}}},"5.13.3":{"%":0.2,"P":{"5.10.4.5":{"%":0.2}}},"5.13.4":{"%":0.2,"P":{"5.13.4.1":{"%":0.2,"P":{"5.10.1.2.3":{"%":0.2}}},"5.13.4.2":{"%":0.2,"P":{"5.10.1.2.3":{"%":0.2}}},"5.13.4.3":{"%":0.2,"P":{"5.10.1.2.3":{"%":0.2}}}}},"5.13.5":{"%":0.2,"P":{"5.13.4.3":{"%":0.2}}},"5.13.6":{"%":0.2,"P":{"5.13.4.3":{"%":0.2}}},"5.13.7":{"%":0.2,"P":{"5.13.7.1":{"%":0.2,"P":{"5.10.1.2.3":{"%":0.2}}},"5.13.7.2":{"%":0.2,"P":{"5.10.1.2.3":{"%":0.2}}},"5.13.7.3":{"%":0.2,"P":{"5.10.1.2.3":{"%":0.2}}}}}}}}}},"%":0.19255952380952382}},"Standard6":{"2016_679":{"Table 1 \u2014 Full document coverage percentage":{"Article_5":{"%":0.6666666666666666,"P":{"Article_5.1":{"%":0.3333333333333333,"P":{"Article_5.1.a":{"%":0.2},"Article_5.1.b":{"%":0.2},"Article_5.1.c":{"%":0.2},"Article_5.1.d":{"%":0.2},"Article_5.1.e":{"%":0.7},"Article_5.1.f":{"%":0.7}}},"Article_5.2":{"%":0.7,"P":{"Article_5.1.f":{"%":0.7}}}}},"Article_15":{"%":0.2,"P":{"Article_15.1":{"%":0.2,"P":{"Article_15.1.a":{"%":0.2},"Article_15.1.b":{"%":0.2},"Article_15.1.c":{"%":0.2},"Article_15.1.d":{"%":0.2},"Article_15.1.e":{"%":0.2},"Article_15.1.f":{"%":0.2},"Article_15.1.g":{"%":0.2},"Article_15.1.h":{"%":0.2}}},"Article_15.2":{"%":0.2,"P":{"Article_15.1.h":{"%":0.2}}},"Article_15.3":{"%":0.2,"P":{"Article_15.1.h":{"%":0.2}}},"Article_15.4":{"%":0.2,"P":{"Article_15.1.h":{"%":0.2}}}}},"Article_25":{"%":0.7,"P":{"Article_25.1":{"%":0.7,"P":{"null":{"%":0.7}}},"Article_25.2":{"%":0.7,"P":{"null":{"%":0.7}}},"Article_25.3":{"%":0.7,"P":{"null":{"%":0.7}}}}},"Article_30":{"%":0.7,"P":{"Article_30.1":{"%":0.7,"P":{"Article_30.1.a":{"%":0.7},"Article_30.1.b":{"%":0.7},"Article_30.1.c":{"%":0.7},"Article_30.1.d":{"%":0.7},"Article_30.1.e":{"%":0.7},"Article_30.1.f":{"%":0.7},"Article_30.1.g":{"%":0.7}}},"Article_30.2":{"%":0.7,"P":{"Article_30.2.a":{"%":0.7},"Article_30.2.b":{"%":0.7},"Article_30.2.c":{"%":0.7},"Article_30.2.d":{"%":0.7}}},"Article_30.3":{"%":0.7,"P":{"Article_30.2.d":{"%":0.7}}},"Article_30.4":{"%":0.7,"P":{"Article_30.2.d":{"%":0.7}}},"Article_30.5":{"%":0.7,"P":{"Article_30.2.d":{"%":0.7}}}}},"Article_32":{"%":0.9375,"P":{"Article_32.1":{"%":0.75,"P":{"Article_32.1.a":{"%":0.7},"Article_32.1.b":{"%":0.7},"Article_32.1.c":{"%":0.7},"Article_32.1.d":{"%":0.2}}},"Article_32.3":{"%":0.7,"P":{"Article_32.1.d":{"%":0.7}}},"Article_32.4":{"%":0.7,"P":{"Article_32.1.d":{"%":0.7}}},"Article_32.5":{"%":0.7,"P":{"Article_32.1.d":{"%":0.7}}}}},"Article_44":{"%":0.7,"P":{"Article_44.1":{"%":0.7,"P":{"null":{"%":0.7}}},"Article_44.2":{"%":0.7,"P":{"Article_44.2.a":{"%":0.7},"Article_44.2.b":{"%":0.7},"Article_44.2.c":{"%":0.7}}},"Article_44.3":{"%":0.7,"P":{"Article_44.2.c":{"%":0.7}}},"Article_44.4":{"%":0.7,"P":{"Article_44.2.c":{"%":0.7}}},"Article_44.5":{"%":0.7,"P":{"Article_44.2.c":{"%":0.7}}},"Article_44.6":{"%":0.7,"P":{"Article_44.2.c":{"%":0.7}}},"Article_44.7":{"%":0.7,"P":{"Article_44.2.c":{"%":0.7}}},"Article_44.8":{"%":0.7,"P":{"Article_44.2.c":{"%":0.7}}},"Article_44.9":{"%":0.7,"P":{"Article_44.2.c":{"%":0.7}}}}},"Article_46":{"%":0.2,"P":{"Article_46.1":{"%":0.2,"P":{"null":{"%":0.2}}},"Article_46.2":{"%":0.2,"P":{"Article_46.2.a":{"%":0.2},"Article_46.2.b":{"%":0.2},"Article_46.2.c":{"%":0.2},"Article_46.2.d":{"%":0.2},"Article_46.2.e":{"%":0.2},"Article_46.2.f":{"%":0.2}}},"Article_46.3":{"%":0.2,"P":{"Article_46.3.a":{"%":0.2},"Article_46.3.b":{"%":0.2}}},"Article_46.4":{"%":0.2,"P":{"Article_46.3.b":{"%":0.2}}},"Article_46.5":{"%":0.2,"P":{"Article_46.3.b":{"%":0.2}}}}},"Article_47":{"%":0.2,"P":{"Article_47.1":{"%":0.2,"P":{"Article_47.1.a":{"%":0.2},"Article_47.1.b":{"%":0.2},"Article_47.1.c":{"%":0.2}}},"Article_47.2":{"%":0.2,"P":{"Article_47.2.a":{"%":0.2},"Article_47.2.b":{"%":0.2},"Article_47.2.c":{"%":0.2},"Article_47.2.d":{"%":0.2},"Article_47.2.e":{"%":0.2},"Article_47.2.f":{"%":0.2},"Article_47.2.g":{"%":0.2},"Article_47.2.h":{"%":0.2},"Article_47.2.i":{"%":0.2},"Article_47.2.j":{"%":0.2},"Article_47.2.k":{"%":0.2},"Article_47.2.l":{"%":0.2},"Article_47.2.m":{"%":0.2},"Article_47.2.n":{"%":0.2}}}}}},"%":0.5755208333333333}},"Standard3":{"Standard11_800_53_Rev5":{"wertyui":{"3.1 ACCESS CONTROL":{"%":0.4444444444444444,"P":{"AC-01":{"%":0.7,"P":{"null":{"%":0.7}}},"AC-02":{"%":0.7,"P":{"AC-02.01":{"%":0.7},"AC-02.02":{"%":0.7},"AC-02.03":{"%":0.7},"AC-02.04":{"%":0.7},"AC-02.05":{"%":0.7},"AC-02.07":{"%":0.7},"AC-02.09":{"%":0.7},"AC-02.11":{"%":0.7},"AC-02.12":{"%":0.7},"AC-02.13":{"%":0.7}}},"AC-03":{"%":0.7,"P":{"AC-02.13":{"%":0.7}}},"AC-04":{"%":0.2,"P":{"AC-02.13":{"%":0.2}}},"AC-05":{"%":0.7,"P":{"AC-02.13":{"%":0.7}}},"AC-06":{"%":0.7,"P":{"AC-06.01":{"%":0.7},"AC-06.02":{"%":0.7},"AC-06.03":{"%":0.7},"AC-06.05":{"%":0.7},"AC-06.07":{"%":0.7},"AC-06.08":{"%":0.7},"AC-06.09":{"%":0.7},"AC-06.10":{"%":0.7}}},"AC-07":{"%":0.7,"P":{"AC-06.10":{"%":0.7}}},"AC-08":{"%":0.2,"P":{"AC-06.10":{"%":0.2}}},"AC-10":{"%":0.2,"P":{"AC-06.10":{"%":0.2}}},"AC-11":{"%":0.2,"P":{"AC-06.10":{"%":0.2}}},"AC-12":{"%":0.2,"P":{"AC-06.10":{"%":0.2}}},"AC-14":{"%":0.2,"P":{"AC-06.10":{"%":0.2}}},"AC-17":{"%":0.7,"P":{"AC-17.01":{"%":0.7},"AC-17.02":{"%":0.7},"AC-17.03":{"%":0.7},"AC-17.04":{"%":0.7}}},"AC-18":{"%":0.7,"P":{"AC-18.01":{"%":0.7},"AC-18.03":{"%":0.7},"AC-18.04":{"%":0.7},"AC-18.05":{"%":0.7}}},"AC-19":{"%":0.2,"P":{"AC-18.05":{"%":0.2}}},"AC-20":{"%":0.2,"P":{"AC-20.21":{"%":0.2},"AC-20.22":{"%":0.2}}},"AC-21":{"%":0.2,"P":{"AC-20.22":{"%":0.2}}},"AC-22":{"%":0.2,"P":{"AC-20.22":{"%":0.2}}}}},"3.2 AWARENESS AND TRAINING":{"%":0.2,"P":{"AT-01":{"%":0.2,"P":{"null":{"%":0.2}}},"AT-02":{"%":0.2,"P":{"AT-02.02":{"%":0.2},"AT-02.03":{"%":0.2}}},"AT-03":{"%":0.2,"P":{"AT-02.03":{"%":0.2}}},"AT-04":{"%":0.2,"P":{"AT-02.03":{"%":0.2}}}}},"3.3 AUDIT AND ACCOUNTABILITY":{"%":0.5833333333333334,"P":{"AU-01":{"%":0.2,"P":{"null":{"%":0.2}}},"AU-02":{"%":0.7,"P":{"null":{"%":0.7}}},"AU-03":{"%":0.7,"P":{"null":{"%":0.7}}},"AU-04":{"%":0.7,"P":{"null":{"%":0.7}}},"AU-05":{"%":0.2,"P":{"AU-05.01":{"%":0.2},"AU-05.02":{"%":0.2}}},"AU-06":{"%":0.7,"P":{"AU-06.01":{"%":0.7},"AU-06.03":{"%":0.7},"AU-06.04":{"%":0.7},"AU-06.05":{"%":0.7},"AU-06.06":{"%":0.7},"AU-06.07":{"%":0.7}}},"AU-07":{"%":0.7,"P":{"AU-06.07":{"%":0.7}}},"AU-08":{"%":0.2,"P":{"AU-06.07":{"%":0.2}}},"AU-09":{"%":0.7,"P":{"AU-09.02":{"%":0.7},"AU-09.03":{"%":0.7},"AU-09.04":{"%":0.7}}},"AU-10":{"%":0.2,"P":{"AU-09.04":{"%":0.2}}},"AU-11":{"%":0.2,"P":{"AU-09.04":{"%":0.2}}},"AU-12":{"%":0.7,"P":{"AU-12.01":{"%":0.7},"AU-12.03":{"%":0.7}}}}},"3.4 ASSESSMENT, AUTHORIZATION, AND MONITORING":{"%":0.375,"P":{"CA-01":{"%":0.7,"P":{"null":{"%":0.7}}},"CA-02":{"%":0.2,"P":{"CA-02.01":{"%":0.2},"CA-02.02":{"%":0.2}}},"CA-03":{"%":0.2,"P":{"CA-03.06":{"%":0.2},"CA-03.07":{"%":0.2}}},"CA-05":{"%":0.7,"P":{"CA-03.07":{"%":0.7}}},"CA-06":{"%":0.2,"P":{"CA-03.07":{"%":0.2}}},"CA-07":{"%":0.2,"P":{"CA-00.71":{"%":0.2},"CA-00.74":{"%":0.2}}},"CA-08":{"%":0.2,"P":{"CA-00.74":{"%":0.2}}},"CA-09":{"%":0.7,"P":{"CA-00.74":{"%":0.7}}}}},"3.5 CONFIGURATION MANAGEMENT":{"%":0.6666666666666666,"P":{"CM-01":{"%":0.7,"P":{"null":{"%":0.7}}},"CM-02":{"%":0.7,"P":{"CM-02.02":{"%":0.7},"CM-02.03":{"%":0.7},"CM-02.07":{"%":0.7}}},"CM-03":{"%":0.2,"P":{"CM-03.01":{"%":0.2},"CM-03.02":{"%":0.2},"CM-03.04":{"%":0.2},"CM-03.06":{"%":0.2}}},"CM-04":{"%":0.2,"P":{"CM-04.01":{"%":0.2},"CM-04.02":{"%":0.2}}},"CM-05":{"%":0.7,"P":{"CM-05.01":{"%":0.7},"CM-05.05":{"%":0.7}}},"CM-06":{"%":0.7,"P":{"CM-06.01":{"%":0.7},"CM-06.02":{"%":0.7}}},"CM-07":{"%":0.7,"P":{"CM-07.01":{"%":0.7},"CM-07.02":{"%":0.7},"CM-07.05":{"%":0.7}}},"CM-08":{"%":0.7,"P":{"CM-08.01":{"%":0.7},"CM-08.02":{"%":0.7},"CM-08.03":{"%":0.7},"CM-08.04":{"%":0.7}}},"CM-09":{"%":0.7,"P":{"CM-08.04":{"%":0.7}}},"CM-10":{"%":0.2,"P":{"CM-08.04":{"%":0.2}}},"CM-11":{"%":0.2,"P":{"CM-08.04":{"%":0.2}}},"CM-12":{"%":0.7,"P":{"CM-08.04":{"%":0.7}}}}},"3.6 CONTINGENCY PLANNING":{"%":0.5555555555555556,"P":{"CP-01":{"%":0.2,"P":{"null":{"%":0.2}}},"CP-02":{"%":0.7,"P":{"CP-02.01":{"%":0.7},"CP-02.02":{"%":0.7},"CP-02.03":{"%":0.7},"CP-02.05":{"%":0.7},"CP-02.08":{"%":0.7}}},"CP-03":{"%":0.2,"P":{"CP-02.08":{"%":0.2}}},"CP-04":{"%":0.2,"P":{"CP-04.01":{"%":0.2},"CP-04.02":{"%":0.2}}},"CP-06":{"%":0.7,"P":{"CP-06.01":{"%":0.7},"CP-06.02":{"%":0.7},"CP-06.03":{"%":0.7}}},"CP-07":{"%":0.7,"P":{"CP-07.01":{"%":0.7},"CP-07.02":{"%":0.7},"CP-07.03":{"%":0.7},"CP-07.04":{"%":0.7}}},"CP-08":{"%":0.2,"P":{"CP-08.01":{"%":0.2},"CP-08.02":{"%":0.2},"CP-08.03":{"%":0.2},"CP-08.04":{"%":0.2}}},"CP-09":{"%":0.7,"P":{"CP-09.01":{"%":0.7},"CP-09.02":{"%":0.7},"CP-09.03":{"%":0.7},"CP-09.05":{"%":0.7},"CP-09.08":{"%":0.7}}},"CP-10":{"%":0.7,"P":{"CP-10.22":{"%":0.7},"CP-10.24":{"%":0.7}}}}},"3.7 IDENTIFICATION AND AUTHENTICATION":{"%":0.4166666666666667,"P":{"IA-01":{"%":0.2,"P":{"null":{"%":0.2}}},"IA-02":{"%":0.16666666666666666,"P":{"IA-02.01":{"%":0.7},"IA-02.02":{"%":0.2},"IA-02.05":{"%":0.2},"IA-02.06":{"%":0.2},"IA-02.08":{"%":0.2},"IA-02.12":{"%":0.2}}},"IA-03":{"%":0.2,"P":{"IA-02.12":{"%":0.2}}},"IA-04":{"%":0.7,"P":{"IA-02.12":{"%":0.7}}},"IA-05":{"%":0.7,"P":{"IA-05.01":{"%":0.7},"IA-05.02":{"%":0.7},"IA-05.06":{"%":0.7},"IA-05.07":{"%":0.7},"IA-05.08":{"%":0.7},"IA-05.13":{"%":0.7}}},"IA-06":{"%":0.7,"P":{"IA-05.13":{"%":0.7}}},"IA-07":{"%":0.7,"P":{"IA-05.13":{"%":0.7}}},"IA-08":{"%":0.2,"P":{"IA-08.01":{"%":0.2},"IA-08.02":{"%":0.2},"IA-08.04":{"%":0.2}}},"IA-11":{"%":0.2,"P":{"IA-08.04":{"%":0.2}}},"IA-12":{"%":0.2,"P":{"IA-12.02":{"%":0.2},"IA-12.03":{"%":0.2},"IA-12.04":{"%":0.2},"IA-12.05":{"%":0.2}}}}},"3.8 INCIDENT RESPONSE":{"%":0.375,"P":{"IR-01":{"%":0.7,"P":{"null":{"%":0.7}}},"IR-02":{"%":0.2,"P":{"IR-02.01":{"%":0.2},"IR-02.02":{"%":0.2}}},"IR-03":{"%":0.2,"P":{"IR-02.02":{"%":0.2}}},"IR-04":{"%":0.2,"P":{"IR-04.01":{"%":0.2},"IR-04.02":{"%":0.2},"IR-04.04":{"%":0.2},"IR-04.06":{"%":0.2},"IR-04.11":{"%":0.2}}},"IR-05":{"%":0.2,"P":{"IR-04.11":{"%":0.2}}},"IR-06":{"%":0.2,"P":{"IR-06.01":{"%":0.2},"IR-06.03":{"%":0.2}}},"IR-07":{"%":0.7,"P":{"IR-06.03":{"%":0.7}}},"IR-08":{"%":0.7,"P":{"IR-06.03":{"%":0.7}}}}},"3.9 MAINTENANCE":{"%":0.16666666666666666,"P":{"MA-01":{"%":0.2,"P":{"null":{"%":0.2}}},"MA-02":{"%":0.2,"P":{"null":{"%":0.2}}},"MA-03":{"%":0.2,"P":{"MA-03.01":{"%":0.2},"MA-03.02":{"%":0.2},"MA-03.03":{"%":0.2}}},"MA-04":{"%":0.7,"P":{"MA-03.03":{"%":0.7}}},"MA-05":{"%":0.2,"P":{"MA-03.03":{"%":0.2}}},"MA-06":{"%":0.2,"P":{"MA-03.03":{"%":0.2}}}}},"3.10 MEDIA PROTECTION":{"%":0.14285714285714285,"P":{"MP-01":{"%":0.2,"P":{"null":{"%":0.2}}},"MP-02":{"%":0.7,"P":{"null":{"%":0.7}}},"MP-03":{"%":0.2,"P":{"null":{"%":0.2}}},"MP-04":{"%":0.2,"P":{"null":{"%":0.2}}},"MP-05":{"%":0.2,"P":{"null":{"%":0.2}}},"MP-06":{"%":0.2,"P":{"MP-06.01":{"%":0.2},"MP-06.02":{"%":0.2},"MP-06.03":{"%":0.2}}},"MP-07":{"%":0.2,"P":{"MP-06.03":{"%":0.2}}}}},"3.11 PHYSICAL AND ENVIRONMENTAL PROTECTION":{"%":0.2,"P":{"PE-01":{"%":0.2,"P":{"null":{"%":0.2}}},"PE-02":{"%":0.2,"P":{"null":{"%":0.2}}},"PE-03":{"%":0.2,"P":{"null":{"%":0.2}}},"PE-04":{"%":0.2,"P":{"null":{"%":0.2}}},"PE-05":{"%":0.2,"P":{"null":{"%":0.2}}},"PE-06":{"%":0.2,"P":{"PE-06.01":{"%":0.2},"PE-06.04":{"%":0.2}}},"PE-08":{"%":0.2,"P":{"PE-06.04":{"%":0.2}}},"PE-09":{"%":0.2,"P":{"PE-06.04":{"%":0.2}}},"PE-10":{"%":0.2,"P":{"PE-06.04":{"%":0.2}}},"PE-11":{"%":0.2,"P":{"PE-06.04":{"%":0.2}}},"PE-12":{"%":0.2,"P":{"PE-06.04":{"%":0.2}}},"PE-13":{"%":0.2,"P":{"PE-13.01":{"%":0.2},"PE-13.02":{"%":0.2}}},"PE-14":{"%":0.2,"P":{"PE-13.02":{"%":0.2}}},"PE-15":{"%":0.2,"P":{"PE-13.02":{"%":0.2}}},"PE-16":{"%":0.2,"P":{"PE-13.02":{"%":0.2}}},"PE-17":{"%":0.2,"P":{"PE-13.02":{"%":0.2}}},"PE-18":{"%":0.2,"P":{"PE-13.02":{"%":0.2}}}}},"3.12 PLANNING":{"%":0.16666666666666666,"P":{"PL-01":{"%":0.2,"P":{"null":{"%":0.2}}},"PL-02":{"%":0.2,"P":{"null":{"%":0.2}}},"PL-04":{"%":0.2,"P":{"null":{"%":0.2}}},"PL-08":{"%":0.7,"P":{"null":{"%":0.7}}},"PL-10":{"%":0.2,"P":{"null":{"%":0.2}}},"PL-11":{"%":0.2,"P":{"null":{"%":0.2}}}}},"3.14 PERSONNEL SECURITY":{"%":0.2,"P":{"PS-01":{"%":0.2,"P":{"null":{"%":0.2}}},"PS-02":{"%":0.2,"P":{"null":{"%":0.2}}},"PS-03":{"%":0.2,"P":{"null":{"%":0.2}}},"PS-04":{"%":0.2,"P":{"null":{"%":0.2}}},"PS-05":{"%":0.2,"P":{"null":{"%":0.2}}},"PS-06":{"%":0.2,"P":{"null":{"%":0.2}}},"PS-07":{"%":0.2,"P":{"null":{"%":0.2}}},"PS-08":{"%":0.2,"P":{"null":{"%":0.2}}},"PS-09":{"%":0.2,"P":{"null":{"%":0.2}}}}},"3.16 RISK ASSESSMENT":{"%":0.5,"P":{"RA-01":{"%":0.7,"P":{"null":{"%":0.7}}},"RA-02":{"%":0.2,"P":{"null":{"%":0.2}}},"RA-03":{"%":0.2,"P":{"null":{"%":0.2}}},"RA-05":{"%":0.7,"P":{"RA-05.02":{"%":0.7},"RA-05.03":{"%":0.7},"RA-05.04":{"%":0.7},"RA-05.05":{"%":0.7},"RA-05.11":{"%":0.7}}},"RA-07":{"%":0.7,"P":{"RA-05.11":{"%":0.7}}},"RA-09":{"%":0.2,"P":{"RA-05.11":{"%":0.2}}}}},"3.17 SYSTEM AND SERVICES ACQUISITION":{"%":0.21428571428571427,"P":{"SA-01":{"%":0.2,"P":{"null":{"%":0.2}}},"SA-02":{"%":0.2,"P":{"null":{"%":0.2}}},"SA-03":{"%":0.7,"P":{"null":{"%":0.7}}},"SA-04":{"%":0.2,"P":{"SA-04.01":{"%":0.2},"SA-04.02":{"%":0.2},"SA-04.05":{"%":0.2},"SA-04.09":{"%":0.2},"SA-04.10":{"%":0.2}}},"SA-05":{"%":0.2,"P":{"SA-04.10":{"%":0.2}}},"SA-08":{"%":0.7,"P":{"SA-04.10":{"%":0.7}}},"SA-09":{"%":0.2,"P":{"SA-09.02":{"%":0.2},"SA-09.05":{"%":0.2}}},"SA-10":{"%":0.7,"P":{"SA-09.05":{"%":0.7}}},"SA-11":{"%":0.2,"P":{"SA-09.05":{"%":0.2}}},"SA-15":{"%":0.2,"P":{"SA-09.05":{"%":0.2}}},"SA-16":{"%":0.2,"P":{"SA-09.05":{"%":0.2}}},"SA-17":{"%":0.2,"P":{"SA-09.05":{"%":0.2}}},"SA-21":{"%":0.2,"P":{"SA-09.05":{"%":0.2}}},"SA-22":{"%":0.2,"P":{"SA-09.05":{"%":0.2}}}}},"3.18 SYSTEM AND COMMUNICATIONS PROTECTION":{"%":0.2857142857142857,"P":{"SC-01":{"%":0.2,"P":{"null":{"%":0.2}}},"SC-02":{"%":0.2,"P":{"null":{"%":0.2}}},"SC-03":{"%":0.2,"P":{"null":{"%":0.2}}},"SC-04":{"%":0.2,"P":{"null":{"%":0.2}}},"SC-05":{"%":0.2,"P":{"null":{"%":0.2}}},"SC-06":{"%":0.2,"P":{"null":{"%":0.2}}},"SC-07":{"%":0.7,"P":{"SC-07.03":{"%":0.7},"SC-07.04":{"%":0.7},"SC-07.05":{"%":0.7},"SC-07.07":{"%":0.7},"SC-07.08":{"%":0.7},"SC-07.12":{"%":0.7},"SC-07.18":{"%":0.7},"SC-07.20":{"%":0.7},"SC-07.21":{"%":0.7}}},"SC-08":{"%":0.7,"P":{"SC-07.21":{"%":0.7}}},"SC-10":{"%":0.2,"P":{"SC-07.21":{"%":0.2}}},"SC-12":{"%":0.7,"P":{"SC-07.21":{"%":0.7}}},"SC-13":{"%":0.7,"P":{"SC-07.21":{"%":0.7}}},"SC-15":{"%":0.2,"P":{"SC-07.21":{"%":0.2}}},"SC-17":{"%":0.2,"P":{"SC-07.21":{"%":0.2}}},"SC-18":{"%":0.2,"P":{"SC-07.21":{"%":0.2}}},"SC-20":{"%":0.2,"P":{"SC-07.21":{"%":0.2}}},"SC-21":{"%":0.2,"P":{"SC-07.21":{"%":0.2}}},"SC-22":{"%":0.2,"P":{"SC-07.21":{"%":0.2}}},"SC-23":{"%":0.7,"P":{"SC-07.21":{"%":0.7}}},"SC-24":{"%":0.2,"P":{"SC-07.21":{"%":0.2}}},"SC-28":{"%":0.7,"P":{"SC-07.21":{"%":0.7}}},"SC-39":{"%":0.2,"P":{"SC-07.21":{"%":0.2}}}}},"3.19 SYSTEM AND INFORMATION INTEGRITY":{"%":0.25,"P":{"SI-01":{"%":0.2,"P":{"null":{"%":0.2}}},"SI-02":{"%":0.7,"P":{"SI-02.02":{"%":0.7},"SI-02.03":{"%":0.7}}},"SI-03":{"%":0.2,"P":{"SI-02.03":{"%":0.2}}},"SI-04":{"%":0.7,"P":{"SI-04.01":{"%":0.7},"SI-04.02":{"%":0.7},"SI-04.04":{"%":0.7},"SI-04.05":{"%":0.7},"SI-04.10":{"%":0.7},"SI-04.11":{"%":0.7},"SI-04.12":{"%":0.7},"SI-04.14":{"%":0.7},"SI-04.16":{"%":0.7},"SI-04.19":{"%":0.7},"SI-04.20":{"%":0.7},"SI-04.22":{"%":0.7},"SI-04.23":{"%":0.7}}},"SI-05":{"%":0.2,"P":{"SI-04.23":{"%":0.2}}},"SI-06":{"%":0.2,"P":{"SI-04.23":{"%":0.2}}},"SI-07":{"%":0.2,"P":{"SI-07.01":{"%":0.2},"SI-07.02":{"%":0.2},"SI-07.05":{"%":0.2},"SI-07.07":{"%":0.2},"SI-07.15":{"%":0.2}}},"SI-08":{"%":0.2,"P":{"SI-07.15":{"%":0.2}}},"SI-10":{"%":0.2,"P":{"SI-07.15":{"%":0.2}}},"SI-11":{"%":0.2,"P":{"SI-07.15":{"%":0.2}}},"SI-12":{"%":0.7,"P":{"SI-07.15":{"%":0.7}}},"SI-16":{"%":0.2,"P":{"SI-07.15":{"%":0.2}}}}},"3.20 SUPPLY CHAIN RISK MANAGEMENT":{"%":0.2,"P":{"SR-01":{"%":0.2,"P":{"null":{"%":0.2}}},"SR-02":{"%":0.2,"P":{"null":{"%":0.2}}},"SR-03":{"%":0.2,"P":{"null":{"%":0.2}}},"SR-05":{"%":0.2,"P":{"null":{"%":0.2}}},"SR-06":{"%":0.2,"P":{"null":{"%":0.2}}},"SR-08":{"%":0.2,"P":{"null":{"%":0.2}}},"SR-09":{"%":0.2,"P":{"null":{"%":0.2}}},"SR-10":{"%":0.2,"P":{"null":{"%":0.2}}},"SR-11":{"%":0.2,"P":{"SR-10.71":{"%":0.2},"SR-10.72":{"%":0.2}}}}}},"%":0.2857142857142857}},"Standard14":{"27001_2013":{"27001_2013 Article (Technical)":{"A.5":{"%":0.2,"P":{"A.5.1":{"%":0.2,"P":{"A.5.1.1":{"%":0.2},"A.5.1.2":{"%":0.2}}}}},"A.6":{"%":0.5,"P":{"A.6.1":{"%":0.5,"P":{"A.6.1.1":{"%":0.2},"A.6.1.2":{"%":0.7}}},"A.6.2":{"%":0.5,"P":{"A.6.2.1":{"%":0.2},"A.6.2.2":{"%":0.7}}}}},"A.8":{"%":0.2222222222222222,"P":{"A.8.1":{"%":0.6666666666666666,"P":{"A.8.1.1":{"%":0.7},"A.8.1.2":{"%":0.2},"A.8.1.3":{"%":0.7}}},"A.8.2":{"%":0.2,"P":{"A.8.2.1":{"%":0.2},"A.8.2.2":{"%":0.2},"A.8.2.3":{"%":0.2}}},"A.8.3":{"%":0.2,"P":{"A.8.3.1":{"%":0.2},"A.8.3.2":{"%":0.2},"A.8.3.3":{"%":0.2}}}}},"A.9":{"%":0.8583333333333334,"P":{"A.9.1":{"%":0.7,"P":{"A.9.1.1":{"%":0.7},"A.9.1.2":{"%":0.7}}},"A.9.2":{"%":0.8333333333333334,"P":{"A.9.2.1":{"%":0.7},"A.9.2.2":{"%":0.7},"A.9.2.3":{"%":0.7},"A.9.2.4":{"%":0.7},"A.9.2.5":{"%":0.7},"A.9.2.6":{"%":0.2}}},"A.9.3":{"%":0.7,"P":{"A.9.3.1":{"%":0.7}}},"A.9.4":{"%":0.6,"P":{"A.9.4.1":{"%":0.7},"A.9.4.2":{"%":0.7},"A.9.4.3":{"%":0.7},"A.9.4.4":{"%":0.2},"A.9.4.5":{"%":0.2}}}}},"A.10":{"%":0.7,"P":{"A.10.1":{"%":0.7,"P":{"A.10.1.1":{"%":0.7},"A.10.1.2":{"%":0.7}}}}},"A.12":{"%":0.5,"P":{"A.12.1":{"%":0.5,"P":{"A.12.1.3":{"%":0.7},"A.12.1.4":{"%":0.2}}},"A.12.2":{"%":0.7,"P":{"A.12.2.1":{"%":0.7}}},"A.12.3":{"%":0.7,"P":{"A.12.3.1":{"%":0.7}}},"A.12.4":{"%":0.5,"P":{"A.12.4.1":{"%":0.7},"A.12.4.2":{"%":0.7},"A.12.4.3":{"%":0.2},"A.12.4.4":{"%":0.2}}},"A.12.5":{"%":0.2,"P":{"A.12.5.1":{"%":0.2}}},"A.12.6":{"%":0.5,"P":{"A.12.6.1":{"%":0.7},"A.12.6.2":{"%":0.2}}},"A.12.7":{"%":0.2,"P":{"A.12.7.1":{"%":0.2}}}}},"A.13":{"%":0.6666666666666666,"P":{"A.13.1":{"%":0.7,"P":{"A.13.1.1":{"%":0.7},"A.13.1.2":{"%":0.7},"A.13.1.3":{"%":0.7}}},"A.13.2":{"%":0.3333333333333333,"P":{"A.13.2.1":{"%":0.7},"A.13.2.3":{"%":0.2},"A.13.2.4":{"%":0.2}}}}},"A.14":{"%":0.1851851851851852,"P":{"A.14.1":{"%":0.3333333333333333,"P":{"A.14.1.1":{"%":0.2},"A.14.1.2":{"%":0.2},"A.14.1.3":{"%":0.7}}},"A.14.2":{"%":0.2222222222222222,"P":{"A.14.2.1":{"%":0.2},"A.14.2.2":{"%":0.7},"A.14.2.3":{"%":0.2},"A.14.2.4":{"%":0.2},"A.14.2.5":{"%":0.7},"A.14.2.6":{"%":0.2},"A.14.2.7":{"%":0.2},"A.14.2.8":{"%":0.2},"A.14.2.9":{"%":0.2}}},"A.14.3":{"%":0.2,"P":{"A.14.3.1":{"%":0.2}}}}},"A.15":{"%":0.2,"P":{"A.15.1":{"%":0.2,"P":{"A.15.1.1":{"%":0.2},"A.15.1.3":{"%":0.2}}},"A.15.2":{"%":0.2,"P":{"A.15.2.1":{"%":0.2},"A.15.2.2":{"%":0.2}}}}},"A.16":{"%":0.5,"P":{"A.16.1":{"%":0.5,"P":{"A.16.1.1":{"%":0.7},"A.16.1.2":{"%":0.7},"A.16.1.3":{"%":0.2},"A.16.1.4":{"%":0.7},"A.16.1.5":{"%":0.2},"A.16.1.7":{"%":0.2}}}}},"A.17":{"%":0.5,"P":{"A.17.1":{"%":0.2,"P":{"A.17.1.1":{"%":0.2},"A.17.1.2":{"%":0.2},"A.17.1.3":{"%":0.2}}},"A.17.2":{"%":0.7,"P":{"A.17.2.1":{"%":0.7}}}}},"A.18":{"%":0.5,"P":{"A.18.1":{"%":0.6666666666666666,"P":{"A.18.1.3":{"%":0.7},"A.18.1.4":{"%":0.2},"A.18.1.5":{"%":0.7}}},"A.18.2":{"%":0.3333333333333333,"P":{"A.18.2.1":{"%":0.2},"A.18.2.2":{"%":0.2},"A.18.2.3":{"%":0.7}}}}}},"%":0.45270061728395056},"27002_2022":{"Table 2 \u2014 Technical document coverage percentage":{"5":{"%":0.48148148148148145,"P":{"5.1":{"%":0.2},"5.2":{"%":0.2},"5.3":{"%":0.7},"5.4":{"%":0.2},"5.7":{"%":0.2},"5.9":{"%":0.7},"5.10":{"%":0.7},"5.12":{"%":0.2},"5.13":{"%":0.2},"5.14":{"%":0.7},"5.15":{"%":0.7},"5.16":{"%":0.7},"5.17":{"%":0.7},"5.18":{"%":0.7},"5.19":{"%":0.2},"5.20":{"%":0.2},"5.21":{"%":0.2},"5.22":{"%":0.2},"5.23":{"%":0.2},"5.24":{"%":0.7},"5.25":{"%":0.7},"5.26":{"%":0.2},"5.28":{"%":0.7},"5.29":{"%":0.2},"5.33":{"%":0.7},"5.34":{"%":0.2},"5.36":{"%":0.7}}},"6":{"%":0.7,"P":{"6.699999999999999":{"%":0.7},"6.8":{"%":0.7}}},"8":{"%":0.5806451612903226,"P":{"8.1":{"%":0.7},"8.2":{"%":0.7},"8.299999999999999":{"%":0.7},"8.4":{"%":0.2},"8.5":{"%":0.7},"8.6":{"%":0.7},"8.7":{"%":0.7},"8.799999999999999":{"%":0.7},"8.9":{"%":0.7},"8.10":{"%":0.2},"8.11":{"%":0.2},"8.12":{"%":0.7},"8.129999999999999":{"%":0.7},"8.139999999999999":{"%":0.7},"8.15":{"%":0.7},"8.16":{"%":0.7},"8.17":{"%":0.2},"8.18":{"%":0.2},"8.19":{"%":0.2},"8.20":{"%":0.7},"8.209999999999999":{"%":0.7},"8.219999999999999":{"%":0.2},"8.229999999999999":{"%":0.2},"8.239999999999998":{"%":0.7},"8.25":{"%":0.2},"8.26":{"%":0.7},"8.27":{"%":0.7},"8.28":{"%":0.2},"8.29":{"%":0.2},"8.31":{"%":0.2},"8.33":{"%":0.2}}}},"%":0.6873755475906013},"27002_2013":{"27002_2013 Article (Technical)":{"5":{"%":0.2,"P":{"5.1":{"%":0.2,"P":{"5.1.1":{"%":0.2},"5.1.2":{"%":0.2}}}}},"6":{"%":0.5,"P":{"6.1":{"%":0.5,"P":{"6.1.1":{"%":0.2},"6.1.2":{"%":0.7}}},"6.2":{"%":0.5,"P":{"6.2.1":{"%":0.2},"6.2.2":{"%":0.7}}}}},"8":{"%":0.2222222222222222,"P":{"8.1":{"%":0.6666666666666666,"P":{"8.1.1":{"%":0.7},"8.1.2":{"%":0.2},"8.1.3":{"%":0.7}}},"8.2":{"%":0.2,"P":{"8.2.1":{"%":0.2},"8.2.2":{"%":0.2},"8.2.3":{"%":0.2}}},"8.3":{"%":0.2,"P":{"8.3.1":{"%":0.2},"8.3.2":{"%":0.2},"8.3.3":{"%":0.2}}}}},"9":{"%":0.8583333333333334,"P":{"9.1":{"%":0.7,"P":{"9.1.1":{"%":0.7},"9.1.2":{"%":0.7}}},"9.2":{"%":0.8333333333333334,"P":{"9.2.1":{"%":0.7},"9.2.2":{"%":0.7},"9.2.3":{"%":0.7},"9.2.4":{"%":0.7},"9.2.5":{"%":0.7},"9.2.6":{"%":0.2}}},"9.3":{"%":0.7,"P":{"9.3.1":{"%":0.7}}},"9.4":{"%":0.6,"P":{"9.4.1":{"%":0.7},"9.4.2":{"%":0.7},"9.4.3":{"%":0.7},"9.4.4":{"%":0.2},"9.4.5":{"%":0.2}}}}},"10":{"%":0.7,"P":{"10.1":{"%":0.7,"P":{"10.1.1":{"%":0.7},"10.1.2":{"%":0.7}}}}},"12":{"%":0.5,"P":{"12.1":{"%":0.5,"P":{"12.1.3":{"%":0.7},"12.1.4":{"%":0.2}}},"12.2":{"%":0.7,"P":{"12.2.1":{"%":0.7}}},"12.3":{"%":0.7,"P":{"12.3.1":{"%":0.7}}},"12.4":{"%":0.5,"P":{"12.4.1":{"%":0.7},"12.4.2":{"%":0.7},"12.4.3":{"%":0.2},"12.4.4":{"%":0.2}}},"12.5":{"%":0.2,"P":{"12.5.1":{"%":0.2}}},"12.6":{"%":0.5,"P":{"12.6.1":{"%":0.7},"12.6.2":{"%":0.2}}},"12.7":{"%":0.2,"P":{"12.7.1":{"%":0.2}}}}},"13":{"%":0.6666666666666666,"P":{"13.1":{"%":0.7,"P":{"13.1.1":{"%":0.7},"13.1.2":{"%":0.7},"13.1.3":{"%":0.7}}},"13.2":{"%":0.3333333333333333,"P":{"13.2.1":{"%":0.7},"13.2.3":{"%":0.2},"13.2.4":{"%":0.2}}}}},"14":{"%":0.1851851851851852,"P":{"14.1":{"%":0.3333333333333333,"P":{"14.1.1":{"%":0.2},"14.1.2":{"%":0.2},"14.1.3":{"%":0.7}}},"14.2":{"%":0.2222222222222222,"P":{"14.2.1":{"%":0.2},"14.2.2":{"%":0.7},"14.2.3":{"%":0.2},"14.2.4":{"%":0.2},"14.2.5":{"%":0.7},"14.2.6":{"%":0.2},"14.2.7":{"%":0.2},"14.2.8":{"%":0.2},"14.2.9":{"%":0.2}}},"14.3":{"%":0.2,"P":{"14.3.1":{"%":0.2}}}}},"15":{"%":0.2,"P":{"15.1":{"%":0.2,"P":{"15.1.1":{"%":0.2},"15.1.3":{"%":0.2}}},"15.2":{"%":0.2,"P":{"15.2.1":{"%":0.2},"15.2.2":{"%":0.2}}}}},"16":{"%":0.5,"P":{"16.1":{"%":0.5,"P":{"16.1.1":{"%":0.7},"16.1.2":{"%":0.7},"16.1.3":{"%":0.2},"16.1.4":{"%":0.7},"16.1.5":{"%":0.2},"16.1.7":{"%":0.2}}}}},"17":{"%":0.5,"P":{"17.1":{"%":0.2,"P":{"17.1.1":{"%":0.2},"17.1.2":{"%":0.2},"17.1.3":{"%":0.2}}},"17.2":{"%":0.7,"P":{"17.2.1":{"%":0.7}}}}},"18":{"%":0.5,"P":{"18.1":{"%":0.6666666666666666,"P":{"18.1.3":{"%":0.7},"18.1.4":{"%":0.2},"18.1.5":{"%":0.7}}},"18.2":{"%":0.3333333333333333,"P":{"18.2.1":{"%":0.2},"18.2.2":{"%":0.2},"18.2.3":{"%":0.7}}}}}},"%":0.45270061728395056},"27018_2019":{"27018_2019 Article (Technical)":{"5":{"%":0.2,"P":{"5.1":{"%":0.2,"P":{"5.1.1":{"%":0.2},"5.1.2":{"%":0.2}}}}},"6":{"%":0.2,"P":{"6.1":{"%":0.4,"P":{"6.1.1":{"%":0.2},"6.1.2":{"%":0.7},"6.1.3":{"%":0.2},"6.1.4":{"%":0.7},"6.1.5":{"%":0.2}}},"6.2":{"%":0.2,"P":{"6.1.5":{"%":0.2}}}}},"8":{"%":0.7,"P":{"null":{"%":0.7,"P":{"null":{"%":0.7}}}}},"9":{"%":0.8583333333333334,"P":{"9.1":{"%":0.7,"P":{"null":{"%":0.7}}},"9.2":{"%":0.8333333333333334,"P":{"9.2.1":{"%":0.7},"9.2.2":{"%":0.7},"9.2.3":{"%":0.7},"9.2.4":{"%":0.7},"9.2.5":{"%":0.7},"9.2.6":{"%":0.2}}},"9.3":{"%":0.7,"P":{"9.3.1":{"%":0.7}}},"9.4":{"%":0.6,"P":{"9.4.1":{"%":0.7},"9.4.2":{"%":0.7},"9.4.3":{"%":0.7},"9.4.4":{"%":0.2},"9.4.5":{"%":0.2}}}}},"10":{"%":0.7,"P":{"10.1":{"%":0.7,"P":{"10.1.1":{"%":0.7},"10.1.2":{"%":0.7}}}}},"12":{"%":0.5357142857142857,"P":{"12.1":{"%":0.25,"P":{"12.1.1":{"%":0.2},"12.1.2":{"%":0.2},"12.1.3":{"%":0.7},"12.1.4":{"%":0.2}}},"12.2":{"%":0.7,"P":{"12.1.4":{"%":0.7}}},"12.3":{"%":0.7,"P":{"12.3.1":{"%":0.7}}},"12.4":{"%":0.5,"P":{"12.4.1":{"%":0.7},"12.4.2":{"%":0.7},"12.4.3":{"%":0.2},"12.4.4":{"%":0.2}}},"12.5":{"%":0.2,"P":{"12.4.4":{"%":0.2}}},"12.6":{"%":0.7,"P":{"12.4.4":{"%":0.7}}},"12.7":{"%":0.2,"P":{"12.4.4":{"%":0.2}}}}},"13":{"%":0.625,"P":{"13.1":{"%":0.7,"P":{"null":{"%":0.7}}},"13.2":{"%":0.25,"P":{"13.2.1":{"%":0.7},"13.2.2":{"%":0.2},"13.2.3":{"%":0.2},"13.2.4":{"%":0.2}}}}},"14":{"%":0.7,"P":{"null":{"%":0.7,"P":{"null":{"%":0.7}}}}},"15":{"%":0.2,"P":{"null":{"%":0.2,"P":{"null":{"%":0.2}}}}},"16":{"%":0.2857142857142857,"P":{"16.1":{"%":0.2857142857142857,"P":{"16.1.1":{"%":0.7},"16.1.2":{"%":0.7},"16.1.3":{"%":0.2},"16.1.4":{"%":0.2},"16.1.5":{"%":0.2},"16.1.6":{"%":0.2},"16.1.7":{"%":0.2}}}}},"17":{"%":0.7,"P":{"null":{"%":0.7,"P":{"null":{"%":0.7}}}}},"18":{"%":0.6666666666666666,"P":{"18.1":{"%":0.7,"P":{"null":{"%":0.7}}},"18.2":{"%":0.3333333333333333,"P":{"18.2.1":{"%":0.2},"18.2.2":{"%":0.2},"18.2.3":{"%":0.7}}}}}},"%":0.5976190476190476},"27017_2015":{"27017_2015 Article (Technical)":{"5":{"%":0.2,"P":{"5.1":{"%":0.2,"P":{"5.1.1":{"%":0.2},"5.1.2":{"%":0.2}}}}},"6":{"%":0.5,"P":{"6.1":{"%":0.5,"P":{"6.1.1":{"%":0.2},"6.1.2":{"%":0.7}}},"6.2":{"%":0.5,"P":{"6.2.1":{"%":0.2},"6.2.2":{"%":0.7}}}}},"8":{"%":0.2222222222222222,"P":{"8.1":{"%":0.6666666666666666,"P":{"8.1.1":{"%":0.7},"8.1.2":{"%":0.2},"8.1.3":{"%":0.7}}},"8.2":{"%":0.2,"P":{"8.2.1":{"%":0.2},"8.2.2":{"%":0.2},"8.2.3":{"%":0.2}}},"8.3":{"%":0.2,"P":{"8.3.1":{"%":0.2},"8.3.2":{"%":0.2},"8.3.3":{"%":0.2}}}}},"9":{"%":0.8583333333333334,"P":{"9.1":{"%":0.7,"P":{"9.1.1":{"%":0.7},"9.1.2":{"%":0.7}}},"9.2":{"%":0.8333333333333334,"P":{"9.2.1":{"%":0.7},"9.2.2":{"%":0.7},"9.2.3":{"%":0.7},"9.2.4":{"%":0.7},"9.2.5":{"%":0.7},"9.2.6":{"%":0.2}}},"9.3":{"%":0.7,"P":{"9.3.1":{"%":0.7}}},"9.4":{"%":0.6,"P":{"9.4.1":{"%":0.7},"9.4.2":{"%":0.7},"9.4.3":{"%":0.7},"9.4.4":{"%":0.2},"9.4.5":{"%":0.2}}}}},"10":{"%":0.7,"P":{"10.1":{"%":0.7,"P":{"10.1.1":{"%":0.7},"10.1.2":{"%":0.7}}}}},"12":{"%":0.5,"P":{"12.1":{"%":0.5,"P":{"12.1.3":{"%":0.7},"12.1.4":{"%":0.2}}},"12.2":{"%":0.7,"P":{"12.2.1":{"%":0.7}}},"12.3":{"%":0.7,"P":{"12.3.1":{"%":0.7}}},"12.4":{"%":0.5,"P":{"12.4.1":{"%":0.7},"12.4.2":{"%":0.7},"12.4.3":{"%":0.2},"12.4.4":{"%":0.2}}},"12.5":{"%":0.2,"P":{"12.5.1":{"%":0.2}}},"12.6":{"%":0.5,"P":{"12.6.1":{"%":0.7},"12.6.2":{"%":0.2}}},"12.7":{"%":0.2,"P":{"12.7.1":{"%":0.2}}}}},"13":{"%":0.6666666666666666,"P":{"13.1":{"%":0.7,"P":{"13.1.1":{"%":0.7},"13.1.2":{"%":0.7},"13.1.3":{"%":0.7}}},"13.2":{"%":0.3333333333333333,"P":{"13.2.1":{"%":0.7},"13.2.3":{"%":0.2},"13.2.4":{"%":0.2}}}}},"14":{"%":0.1851851851851852,"P":{"14.1":{"%":0.3333333333333333,"P":{"14.1.1":{"%":0.2},"14.1.2":{"%":0.2},"14.1.3":{"%":0.7}}},"14.2":{"%":0.2222222222222222,"P":{"14.2.1":{"%":0.2},"14.2.2":{"%":0.7},"14.2.3":{"%":0.2},"14.2.4":{"%":0.2},"14.2.5":{"%":0.7},"14.2.6":{"%":0.2},"14.2.7":{"%":0.2},"14.2.8":{"%":0.2},"14.2.9":{"%":0.2}}},"14.3":{"%":0.2,"P":{"14.3.1":{"%":0.2}}}}},"15":{"%":0.2,"P":{"15.1":{"%":0.2,"P":{"15.1.1":{"%":0.2},"15.1.3":{"%":0.2}}},"15.2":{"%":0.2,"P":{"15.2.1":{"%":0.2},"15.2.2":{"%":0.2}}}}},"16":{"%":0.5,"P":{"16.1":{"%":0.5,"P":{"16.1.1":{"%":0.7},"16.1.2":{"%":0.7},"16.1.3":{"%":0.2},"16.1.4":{"%":0.7},"16.1.5":{"%":0.2},"16.1.7":{"%":0.2}}}}},"17":{"%":0.5,"P":{"17.1":{"%":0.2,"P":{"17.1.1":{"%":0.2},"17.1.2":{"%":0.2},"17.1.3":{"%":0.2}}},"17.2":{"%":0.7,"P":{"17.2.1":{"%":0.7}}}}},"18":{"%":0.5,"P":{"18.1":{"%":0.6666666666666666,"P":{"18.1.3":{"%":0.7},"18.1.4":{"%":0.2},"18.1.5":{"%":0.7}}},"18.2":{"%":0.3333333333333333,"P":{"18.2.1":{"%":0.2},"18.2.2":{"%":0.2},"18.2.3":{"%":0.7}}}}}},"%":0.45270061728395056},"27701_2019":{"27701_2019 Article (Technical)":{"6":{"%":0.4283950617283951,"P":{"6.2":{"%":0.2,"P":{"6.2.1":{"%":0.2,"P":{"6.2.1.1":{"%":0.2},"6.2.1.2":{"%":0.2}}}}},"6.3":{"%":0.25,"P":{"6.3.1":{"%":0.2,"P":{"6.3.1.1":{"%":0.2},"6.3.1.2":{"%":0.2}}},"6.3.2":{"%":0.5,"P":{"6.3.2.1":{"%":0.2},"6.3.2.2":{"%":0.7}}}}},"6.5":{"%":0.2222222222222222,"P":{"6.5.1":{"%":0.6666666666666666,"P":{"6.5.1.1":{"%":0.7},"6.5.1.2":{"%":0.2},"6.5.1.3":{"%":0.7}}},"6.5.2":{"%":0.2,"P":{"6.5.2.1":{"%":0.2},"6.5.2.2":{"%":0.2},"6.5.2.3":{"%":0.2}}},"6.5.3":{"%":0.2,"P":{"6.5.3.1":{"%":0.2},"6.5.3.2":{"%":0.2},"6.5.3.3":{"%":0.2}}}}},"6.6":{"%":0.8583333333333334,"P":{"6.6.1":{"%":0.7,"P":{"6.6.1.1":{"%":0.7},"6.6.1.2":{"%":0.7}}},"6.6.2":{"%":0.8333333333333334,"P":{"6.6.2.1":{"%":0.7},"6.6.2.2":{"%":0.7},"6.6.2.3":{"%":0.7},"6.6.2.4":{"%":0.7},"6.6.2.5":{"%":0.7},"6.6.2.6":{"%":0.2}}},"6.6.3":{"%":0.7,"P":{"6.6.3.1":{"%":0.7}}},"6.6.4":{"%":0.6,"P":{"6.6.4.1":{"%":0.7},"6.6.4.2":{"%":0.7},"6.6.4.3":{"%":0.7},"6.6.4.4":{"%":0.2},"6.6.4.5":{"%":0.2}}}}},"6.7":{"%":0.7,"P":{"6.7.1":{"%":0.7,"P":{"6.7.1.1":{"%":0.7},"6.7.1.2":{"%":0.7}}}}},"6.9":{"%":0.5,"P":{"6.9.1":{"%":0.5,"P":{"6.9.1.3":{"%":0.7},"6.9.1.4":{"%":0.2}}},"6.9.2":{"%":0.7,"P":{"6.9.2.1":{"%":0.7}}},"6.9.3":{"%":0.7,"P":{"6.9.3.1":{"%":0.7}}},"6.9.4":{"%":0.5,"P":{"6.9.4.1":{"%":0.7},"6.9.4.2":{"%":0.7},"6.9.4.3":{"%":0.2},"6.9.4.4":{"%":0.2}}},"6.9.5":{"%":0.2,"P":{"6.9.5.1":{"%":0.2}}},"6.9.6":{"%":0.5,"P":{"6.9.6.1":{"%":0.7},"6.9.6.2":{"%":0.2}}},"6.9.7":{"%":0.2,"P":{"6.9.7.1":{"%":0.2}}}}},"6.1":{"%":0.625,"P":{"6.10.1":{"%":0.7,"P":{"6.10.1.1":{"%":0.7},"6.10.1.2":{"%":0.7},"6.10.1.3":{"%":0.7}}},"6.10.2":{"%":0.25,"P":{"6.10.2.1":{"%":0.7},"6.10.2.2":{"%":0.2},"6.10.2.3":{"%":0.2},"6.10.2.4":{"%":0.2}}}}},"6.11":{"%":0.1851851851851852,"P":{"6.11.1":{"%":0.3333333333333333,"P":{"6.11.1.1":{"%":0.2},"6.11.1.2":{"%":0.2},"6.11.1.3":{"%":0.7}}},"6.11.2":{"%":0.2222222222222222,"P":{"6.11.2.1":{"%":0.2},"6.11.2.2":{"%":0.7},"6.11.2.3":{"%":0.2},"6.11.2.4":{"%":0.2},"6.11.2.5":{"%":0.7},"6.11.2.6":{"%":0.2},"6.11.2.7":{"%":0.2},"6.11.2.8":{"%":0.2},"6.11.2.9":{"%":0.2}}},"6.11.3":{"%":0.2,"P":{"6.11.3.1":{"%":0.2}}}}},"6.12":{"%":0.2,"P":{"6.12.1":{"%":0.2,"P":{"6.12.1.1":{"%":0.2},"6.12.1.3":{"%":0.2}}},"6.12.2":{"%":0.2,"P":{"6.12.2.1":{"%":0.2},"6.12.2.2":{"%":0.2}}}}},"6.13":{"%":0.5,"P":{"6.13.1":{"%":0.5,"P":{"6.13.1.1":{"%":0.7},"6.13.1.2":{"%":0.7},"6.13.1.3":{"%":0.2},"6.13.1.4":{"%":0.7},"6.13.1.5":{"%":0.2},"6.13.1.7":{"%":0.2}}}}},"6.14":{"%":0.5,"P":{"6.14.1":{"%":0.2,"P":{"6.14.1.1":{"%":0.2},"6.14.1.2":{"%":0.2},"6.14.1.3":{"%":0.2}}},"6.14.2":{"%":0.7,"P":{"6.14.2.1":{"%":0.7}}}}},"6.15":{"%":0.5,"P":{"6.15.1":{"%":0.6666666666666666,"P":{"6.15.1.3":{"%":0.7},"6.15.1.4":{"%":0.2},"6.15.1.5":{"%":0.7}}},"6.15.2":{"%":0.3333333333333333,"P":{"6.15.2.1":{"%":0.2},"6.15.2.2":{"%":0.2},"6.15.2.3":{"%":0.7}}}}}}}},"%":0.4283950617283951}},"Standard15":{"null":{"Control Category":{"0.2":{"%":0.7,"P":{"0.21":{"%":0.7,"P":{"00.a":{"%":0.7}}}}},"00.7":{"%":0.717687074829932,"P":{"00.71":{"%":0.7,"P":{"01.a":{"%":0.7}}},"00.72":{"%":0.7,"P":{"01.b":{"%":0.7},"01.c":{"%":0.7},"01.d":{"%":0.7},"01.e":{"%":0.7}}},"00.73":{"%":0.7,"P":{"01.f":{"%":0.7}}},"00.74":{"%":0.8571428571428571,"P":{"01.i":{"%":0.7},"01.j":{"%":0.7},"01.k":{"%":0.2},"01.l":{"%":0.7},"01.m":{"%":0.7},"01.n":{"%":0.7},"01.o":{"%":0.7}}},"00.75":{"%":0.16666666666666666,"P":{"01.p":{"%":0.2},"01.q":{"%":0.7},"01.r":{"%":0.2},"01.s":{"%":0.2},"01.t":{"%":0.2},"01.u":{"%":0.2}}},"00.76":{"%":0.5,"P":{"01.v":{"%":0.7},"01.w":{"%":0.2}}},"00.77":{"%":0.5,"P":{"01.x":{"%":0.2},"01.y":{"%":0.7}}}}},"03.0":{"%":0.2,"P":{"03.01":{"%":0.2,"P":{"03.a":{"%":0.2},"03.b":{"%":0.2},"03.c":{"%":0.2},"03.d":{"%":0.2}}}}},"04.0":{"%":0.2,"P":{"04.01":{"%":0.2,"P":{"04.a":{"%":0.2},"04.b":{"%":0.2}}}}},"05.0":{"%":0.25,"P":{"05.01":{"%":0.5,"P":{"05.c":{"%":0.7},"05.d":{"%":0.2}}},"05.02":{"%":0.2,"P":{"05.i":{"%":0.2},"05.j":{"%":0.2},"05.k":{"%":0.2}}}}},"06.0":{"%":0.27777777777777773,"P":{"06.01":{"%":0.3333333333333333,"P":{"06.a":{"%":0.2},"06.b":{"%":0.2},"06.c":{"%":0.7},"06.d":{"%":0.7},"06.e":{"%":0.2},"06.f":{"%":0.2}}},"06.02":{"%":0.5,"P":{"06.g":{"%":0.2},"06.h":{"%":0.7}}},"06.03":{"%":0.2,"P":{"06.i":{"%":0.2},"06.j":{"%":0.2}}}}},"07.0":{"%":0.5833333333333333,"P":{"07.01":{"%":0.6666666666666666,"P":{"07.a":{"%":0.7},"07.b":{"%":0.2},"07.c":{"%":0.7}}},"07.02":{"%":0.5,"P":{"07.d":{"%":0.2},"07.e":{"%":0.7}}}}},"09.0":{"%":0.38333333333333336,"P":{"09.01":{"%":0.2,"P":{"09.a":{"%":0.2},"09.b":{"%":0.2},"09.c":{"%":0.2},"09.d":{"%":0.2}}},"09.02":{"%":0.2,"P":{"09.e":{"%":0.2},"09.f":{"%":0.2},"09.g":{"%":0.2}}},"09.03":{"%":0.5,"P":{"09.h":{"%":0.7},"09.i":{"%":0.2}}},"09.04":{"%":0.2,"P":{"09.j":{"%":0.2},"09.k":{"%":0.2}}},"09.05":{"%":0.7,"P":{"09.l":{"%":0.7}}},"09.06":{"%":0.7,"P":{"09.m":{"%":0.7},"09.n":{"%":0.7}}},"09.07":{"%":0.2,"P":{"09.o":{"%":0.2},"09.p":{"%":0.2},"09.q":{"%":0.2},"09.r":{"%":0.2}}},"09.08":{"%":0.6666666666666666,"P":{"09.s":{"%":0.7},"09.v":{"%":0.7},"09.w":{"%":0.2}}},"09.09":{"%":0.3333333333333333,"P":{"09.x":{"%":0.2},"09.y":{"%":0.7},"09.z":{"%":0.2}}},"09.10":{"%":0.3333333333333333,"P":{"09.aa":{"%":0.7},"09.ab":{"%":0.7},"09.ac":{"%":0.2},"09.ad":{"%":0.2},"09.ae":{"%":0.2},"09.af":{"%":0.2}}}}},"10.2":{"%":0.38888888888888884,"P":{"10.21":{"%":0.2,"P":{"10.a":{"%":0.2}}},"10.22":{"%":0.3333333333333333,"P":{"10.b":{"%":0.2},"10.c":{"%":0.2},"10.d":{"%":0.7}}},"10.23":{"%":0.5,"P":{"10.f":{"%":0.2},"10.g":{"%":0.7}}},"10.24":{"%":0.2,"P":{"10.h":{"%":0.2},"10.i":{"%":0.2},"10.j":{"%":0.2}}},"10.25":{"%":0.5,"P":{"10.k":{"%":0.7},"10.l":{"%":0.2}}},"10.26":{"%":0.7,"P":{"10.m":{"%":0.7}}}}},"10.7":{"%":0.16666666666666666,"P":{"10.71":{"%":0.2,"P":{"11.a":{"%":0.2},"11.b":{"%":0.2}}},"10.72":{"%":0.3333333333333333,"P":{"11.c":{"%":0.7},"11.d":{"%":0.2},"11.e":{"%":0.2}}}}},"12.01":{"%":0.2,"P":{"12.01":{"%":0.2,"P":{"12.a":{"%":0.2},"12.b":{"%":0.2},"12.c":{"%":0.7},"12.d":{"%":0.2},"12.e":{"%":0.2}}}}},"13.0":{"%":0.28333333333333333,"P":{"13.01":{"%":0.2,"P":{"13.c":{"%":0.2}}},"13.02":{"%":0.2,"P":{"13.f":{"%":0.2}}},"13.04":{"%":0.2,"P":{"13.i":{"%":0.2},"13.j":{"%":0.2}}},"13.05":{"%":0.5,"P":{"13.k":{"%":0.2},"13.l":{"%":0.7}}},"13.06":{"%":0.2,"P":{"13.m":{"%":0.2},"13.n":{"%":0.2},"13.o":{"%":0.2}}},"13.07":{"%":0.2,"P":{"13.p":{"%":0.2},"13.q":{"%":0.2},"13.r":{"%":0.2},"13.s":{"%":0.2},"13.u":{"%":0.2}}}}}},"%":0.33758503401360546}},"DA":{"null":{"Article":{"Article_6":{"%":0.2,"P":{"Article_6.1":{"%":0.2,"P":{"Article_6.1.a":{"%":0.2},"Article_6.1.b":{"%":0.2},"Article_6.1.c":{"%":0.2},"Article_6.1.d":{"%":0.2}}},"Article_6.2":{"%":0.2,"P":{"Article_6.1.d":{"%":0.2}}}}},"Article_7":{"%":0.2857142857142857,"P":{"Article_7.1":{"%":0.7,"P":{"null":{"%":0.7}}},"Article_7.2":{"%":0.7,"P":{"null":{"%":0.7}}},"Article_7.3":{"%":0.2,"P":{"null":{"%":0.2}}},"Article_7.4":{"%":0.2,"P":{"null":{"%":0.2}}},"Article_7.5":{"%":0.2,"P":{"null":{"%":0.2}}},"Article_7.6":{"%":0.2,"P":{"null":{"%":0.2}}},"Article_7.7":{"%":0.2,"P":{"null":{"%":0.2}}}}},"Article_8":{"%":0.7,"P":{"Article_8.1":{"%":0.7,"P":{"null":{"%":0.7}}},"Article_8.2":{"%":0.7,"P":{"null":{"%":0.7}}},"Article_8.3":{"%":0.7,"P":{"Article_8.3.a":{"%":0.7},"Article_8.3.b":{"%":0.7},"Article_8.3.c":{"%":0.7},"Article_8.3.d":{"%":0.7}}},"Article_8.4":{"%":0.7,"P":{"Article_8.4.a":{"%":0.7},"Article_8.4.b":{"%":0.7},"Article_8.4.c":{"%":0.7},"Article_8.4.d":{"%":0.7},"Article_8.4.e":{"%":0.7},"Article_8.4.f":{"%":0.7}}}}},"Article_9":{"%":0.7,"P":{"Article_9.1":{"%":0.7,"P":{"null":{"%":0.7}}},"Article_9.2":{"%":0.7,"P":{"null":{"%":0.7}}},"Article_9.3":{"%":0.7,"P":{"null":{"%":0.7}}},"Article_9.4":{"%":0.7,"P":{"null":{"%":0.7}}}}},"Article_10":{"%":0.1111111111111111,"P":{"Article_10.1":{"%":0.2,"P":{"null":{"%":0.2}}},"Article_10.2":{"%":0.2,"P":{"Article_10.2.a":{"%":0.2},"Article_10.2.b":{"%":0.2},"Article_10.2.c":{"%":0.2},"Article_10.2.d":{"%":0.2},"Article_10.2.e":{"%":0.2},"Article_10.2.f":{"%":0.2}}},"Article_10.3":{"%":0.2,"P":{"Article_10.2.f":{"%":0.2}}},"Article_10.4":{"%":0.2,"P":{"Article_10.2.f":{"%":0.2}}},"Article_13":{"%":0.2,"P":{"Article_13.a":{"%":0.2},"Article_13.b":{"%":0.2}}},"Article_10.6":{"%":0.2,"P":{"Article_13.b":{"%":0.2}}},"Article_10.7":{"%":0.7,"P":{"Article_13.b":{"%":0.7}}},"Article_10.8":{"%":0.2,"P":{"Article_13.b":{"%":0.2}}},"Article_10.9":{"%":0.2,"P":{"Article_13.b":{"%":0.2}}}}},"Article_11":{"%":0.5714285714285714,"P":{"Article_11.1":{"%":0.7,"P":{"Article_11.1.a":{"%":0.7},"Article_11.1.b":{"%":0.7}}},"Article_11.2":{"%":0.7,"P":{"Article_11.1.b":{"%":0.7}}},"Article_11.3":{"%":0.2,"P":{"Article_11.1.b":{"%":0.2}}},"Article_11.4":{"%":0.7,"P":{"Article_11.1.b":{"%":0.7}}},"Article_11.5":{"%":0.7,"P":{"Article_11.5.a":{"%":0.7},"Article_11.5.b":{"%":0.7},"Article_11.5.d":{"%":0.7}}},"Article_11.6":{"%":0.2,"P":{"Article_11.5.d":{"%":0.2}}},"Article_11.7":{"%":0.2,"P":{"Article_11.5.d":{"%":0.2}}}}},"Article_14":{"%":0.125,"P":{"Article_14.a":{"%":0.2,"P":{"null":{"%":0.2}}},"Article_14.b":{"%":0.2,"P":{"null":{"%":0.2}}},"Article_14.c":{"%":0.2,"P":{"null":{"%":0.2}}},"Article_14.d":{"%":0.7,"P":{"null":{"%":0.7}}},"Article_14.e":{"%":0.2,"P":{"null":{"%":0.2}}},"Article_14.f":{"%":0.2,"P":{"null":{"%":0.2}}},"Article_14.g":{"%":0.2,"P":{"null":{"%":0.2}}},"Article_14.h":{"%":0.2,"P":{"null":{"%":0.2}}}}},"Article_15":{"%":0.3333333333333333,"P":{"Article_15.1":{"%":0.7,"P":{"null":{"%":0.7}}},"Article_15.2":{"%":0.2,"P":{"null":{"%":0.2}}},"Article_15.3":{"%":0.2,"P":{"Article_15.3.a":{"%":0.2},"Article_15.3.b":{"%":0.2},"Article_15.3.d":{"%":0.2},"Article_15.3.e":{"%":0.2}}}}},"Article_16":{"%":0.2,"P":{"Article_16.1":{"%":0.2,"P":{"Article_16.1.a":{"%":0.2},"Article_16.1.b":{"%":0.2},"Article_16.1.c":{"%":0.2},"Article_16.1.d":{"%":0.2},"Article_16.1.e":{"%":0.2},"Article_16.1.f":{"%":0.2},"Article_16.1.g":{"%":0.2}}}}},"Article_22":{"%":0.2,"P":{"Article_22.1":{"%":0.2,"P":{"null":{"%":0.2}}},"Article_22.2":{"%":0.2,"P":{"null":{"%":0.2}}}}},"Article_23":{"%":0.2,"P":{"Article_23.1":{"%":0.2,"P":{"null":{"%":0.2}}},"Article_23.2":{"%":0.2,"P":{"null":{"%":0.2}}},"Article_23.3":{"%":0.2,"P":{"Article_23.3.a":{"%":0.2},"Article_23.3.b":{"%":0.2},"Article_23.3.c":{"%":0.2}}},"Article_23.4":{"%":0.2,"P":{"Article_23.4.a":{"%":0.2},"Article_23.4.b":{"%":0.2},"Article_23.4.c":{"%":0.2}}}}},"Article_30":{"%":0.2,"P":{"Article_30.1":{"%":0.2,"P":{"null":{"%":0.2}}},"Article_30.2":{"%":0.2,"P":{"Article_30.2.a":{"%":0.2},"Article_30.2.b":{"%":0.2},"Article_30.2.c":{"%":0.2},"Article_30.2.d":{"%":0.2},"Article_30.2.e":{"%":0.2},"Article_30.2.f":{"%":0.2},"Article_30.2.g":{"%":0.2},"Article_30.2.h":{"%":0.2},"Article_30.2.i":{"%":0.2}}},"Article_30.3":{"%":0.2,"P":{"Article_30.2.i":{"%":0.2}}}}}},"%":0.2855489417989418}},"Standard9":{"v1.1":{"Function Unique Identifier":{"ID":{"%":0.10277777777777779,"P":{"ID.AM":{"%":0.16666666666666666,"P":{"ID.AM-1":{"%":0.7},"ID.AM-2":{"%":0.2},"ID.AM-3":{"%":0.2},"ID.AM-4":{"%":0.2},"ID.AM-5":{"%":0.2},"ID.AM-6":{"%":0.2}}},"ID.BE":{"%":0.2,"P":{"ID.BE-4":{"%":0.2},"ID.BE-5":{"%":0.2}}},"ID.GV":{"%":0.25,"P":{"ID.GV-1":{"%":0.2},"ID.GV-2":{"%":0.2},"ID.GV-3":{"%":0.2},"ID.GV-4":{"%":0.7}}},"ID.RA":{"%":0.2,"P":{"ID.RA-1":{"%":0.2},"ID.RA-2":{"%":0.2},"ID.RA-3":{"%":0.2},"ID.RA-4":{"%":0.2},"ID.RA-5":{"%":0.2},"ID.RA-6":{"%":0.2}}},"ID.RM":{"%":0.2,"P":{"ID.RM-1":{"%":0.2},"ID.RM-2":{"%":0.2},"ID.RM-3":{"%":0.2}}},"ID.SC":{"%":0.2,"P":{"ID.SC-1":{"%":0.2},"ID.SC-2":{"%":0.2},"ID.SC-3":{"%":0.2},"ID.SC-4":{"%":0.2},"ID.SC-5":{"%":0.7}}}}},"PR":{"%":0.5683333333333334,"P":{"PR.AC":{"%":0.8333333333333334,"P":{"PR.AC-1":{"%":0.7},"PR.AC-3":{"%":0.7},"PR.AC-4":{"%":0.7},"PR.AC-5":{"%":0.7},"PR.AC-6":{"%":0.2},"PR.AC-7":{"%":0.7}}},"PR.DS":{"%":0.625,"P":{"PR.DS-1":{"%":0.7},"PR.DS-2":{"%":0.7},"PR.DS-3":{"%":0.7},"PR.DS-4":{"%":0.7},"PR.DS-5":{"%":0.7},"PR.DS-6":{"%":0.2},"PR.DS-7":{"%":0.2},"PR.DS-8":{"%":0.2}}},"PR.IP":{"%":0.5833333333333334,"P":{"PR.IP-1":{"%":0.7},"PR.IP-2":{"%":0.7},"PR.IP-3":{"%":0.2},"PR.IP-4":{"%":0.7},"PR.IP-5":{"%":0.7},"PR.IP-6":{"%":0.7},"PR.IP-7":{"%":0.7},"PR.IP-8":{"%":0.2},"PR.IP-9":{"%":0.7},"PR.IP-10":{"%":0.2},"PR.IP-11":{"%":0.2},"PR.IP-12":{"%":0.2}}},"PR.MA":{"%":0.2,"P":{"PR.MA-1":{"%":0.2},"PR.MA-2":{"%":0.2}}},"PR.PT":{"%":0.8,"P":{"PR.PT-1":{"%":0.7},"PR.PT-2":{"%":0.2},"PR.PT-3":{"%":0.7},"PR.PT-4":{"%":0.7},"PR.PT-5":{"%":0.7}}}}},"DE":{"%":0.43333333333333335,"P":{"DE.AE":{"%":0.4,"P":{"DE.AE-1":{"%":0.2},"DE.AE-2":{"%":0.7},"DE.AE-3":{"%":0.7},"DE.AE-4":{"%":0.2},"DE.AE-5":{"%":0.2}}},"DE.CM":{"%":0.5,"P":{"DE.CM-1":{"%":0.7},"DE.CM-2":{"%":0.2},"DE.CM-3":{"%":0.2},"DE.CM-4":{"%":0.7},"DE.CM-5":{"%":0.2},"DE.CM-6":{"%":0.2},"DE.CM-7":{"%":0.7},"DE.CM-8":{"%":0.7}}},"DE.DP":{"%":0.4,"P":{"DE.DP-1":{"%":0.7},"DE.DP-2":{"%":0.2},"DE.DP-3":{"%":0.2},"DE.DP-4":{"%":0.7},"DE.DP-5":{"%":0.2}}}}},"RS":{"%":0.28,"P":{"RS.RP":{"%":0.2,"P":{"RS.RP-1":{"%":0.2}}},"RS.CO":{"%":0.2,"P":{"RS.CO-1":{"%":0.2},"RS.CO-2":{"%":0.2},"RS.CO-3":{"%":0.2},"RS.CO-4":{"%":0.2}}},"RS.AN":{"%":0.4,"P":{"RS.AN-1":{"%":0.7},"RS.AN-2":{"%":0.2},"RS.AN-3":{"%":0.2},"RS.AN-4":{"%":0.2},"RS.AN-5":{"%":0.7}}},"RS.MI":{"%":0.2,"P":{"RS.MI-1":{"%":0.2},"RS.MI-2":{"%":0.2},"RS.MI-3":{"%":0.2}}},"RS.IM":{"%":0.2,"P":{"RS.MI-1":{"%":0.2},"RS.MI-2":{"%":0.2}}}}},"RC":{"%":0.2,"P":{"RC.RP":{"%":0.2,"P":{"RC.RP-1":{"%":0.2}}},"RC.IM":{"%":0.2,"P":{"RC.IM-1":{"%":0.2},"RC.IM-2":{"%":0.2}}}}}},"%":0.23688888888888887}},"Standard12":{"null":{"Standard":{"CIP-003-8":{"%":0.5,"P":{"CIP-003-8_Requirement_R1":{"%":0.7,"P":{"null":{"%":0.7}}},"CIP-003-8_Requirement_R2":{"%":0.2,"P":{"null":{"%":0.2}}}}},"CIP-004-6":{"%":0.25,"P":{"CIP-004-6_Requirement_R4":{"%":0.5,"P":{"CIP-004-6_Requirement_R4_Part_4.1":{"%":0.7},"CIP-004-6_Requirement_R4_Part_4.2":{"%":0.2},"CIP-004-6_Requirement_R4_Part_4.3":{"%":0.7},"CIP-004-6_Requirement_R4_Part_4.4":{"%":0.2}}},"CIP-004-6_Requirement_R5":{"%":0.2,"P":{"CIP-004-6_Requirement_R5_Part_5.1":{"%":0.2},"CIP-004-6_Requirement_R5_Part_5.2":{"%":0.2},"CIP-004-6_Requirement_R5_Part_5.3":{"%":0.2},"CIP-004-6_Requirement_R5_Part_5.4":{"%":0.2},"CIP-004-6_Requirement_R5_Part_5.5":{"%":0.2}}}}},"CIP-005-7":{"%":0.26666666666666666,"P":{"CIP-005-7_Requirement_R1":{"%":0.6,"P":{"CIP-005-7_Requirement_R1_Part_1.1":{"%":0.7},"CIP-005-7_Requirement_R1_Part_1.2":{"%":0.2},"CIP-005-7_Requirement_R1_Part_1.3":{"%":0.7},"CIP-005-7_Requirement_R1_Part_1.4":{"%":0.2},"CIP-005-7_Requirement_R1_Part_1.5":{"%":0.7}}},"CIP-005-7_Requirement_R2":{"%":0.2,"P":{"CIP-005-7_Requirement_R2_Part_2.1":{"%":0.2},"CIP-005-7_Requirement_R2_Part_2.2":{"%":0.7},"CIP-005-7_Requirement_R2_Part_2.3":{"%":0.2},"CIP-005-7_Requirement_R2_Part_2.4":{"%":0.2},"CIP-005-7_Requirement_R2_Part_2.5":{"%":0.2}}},"CIP-005-7_Requirement_R3":{"%":0.2,"P":{"CIP-005-7_Requirement_R3_Part_3.1":{"%":0.2},"CIP-005-7_Requirement_R3_Part_3.2":{"%":0.2}}}}},"CIP-007-6":{"%":0.32857142857142857,"P":{"CIP-007-6_Requirement_R1":{"%":0.5,"P":{"CIP-007-6_Requirement_R1_Part_1.1":{"%":0.7},"CIP-007-6_Requirement_R1_Part_1.2":{"%":0.2}}},"CIP-007-6_Requirement_R2":{"%":0.25,"P":{"CIP-007-6_Requirement_R2_Part_2.1":{"%":0.7},"CIP-007-6_Requirement_R2_Part_2.2":{"%":0.2},"CIP-007-6_Requirement_R2_Part_2.3":{"%":0.2},"CIP-007-6_Requirement_R2_Part_2.4":{"%":0.2}}},"CIP-007-6_Requirement_R3":{"%":0.2,"P":{"CIP-007-6_Requirement_R3_Part_3.1":{"%":0.2},"CIP-007-6_Requirement_R3_Part_3.2":{"%":0.2},"CIP-007-6_Requirement_R3_Part_3.3":{"%":0.2}}},"CIP-007-6_Requirement_R4":{"%":0.75,"P":{"CIP-007-6_Requirement_R4_Part_4.1":{"%":0.7},"CIP-007-6_Requirement_R4_Part_4.2":{"%":0.7},"CIP-007-6_Requirement_R4_Part_4.3":{"%":0.2},"CIP-007-6_Requirement_R4_Part_4.4":{"%":0.7}}},"CIP-007-6_Requirement_R5":{"%":0.14285714285714285,"P":{"CIP-007-6_Requirement_R5_Part_5.1":{"%":0.2},"CIP-007-6_Requirement_R5_Part_5.2":{"%":0.2},"CIP-007-6_Requirement_R5_Part_5.3":{"%":0.2},"CIP-007-6_Requirement_R5_Part_5.4":{"%":0.2},"CIP-007-6_Requirement_R5_Part_5.5":{"%":0.7},"CIP-007-6_Requirement_R5_Part_5.6":{"%":0.2},"CIP-007-6_Requirement_R5_Part_5.7":{"%":0.2}}}}},"CIP-008-6":{"%":0.2625,"P":{"CIP-008-6_Requirement_R1":{"%":0.25,"P":{"CIP-008-6_Requirement_R1_Part_1.1":{"%":0.2},"CIP-008-6_Requirement_R1_Part_1.2":{"%":0.2},"CIP-008-6_Requirement_R1_Part_1.3":{"%":0.7},"CIP-008-6_Requirement_R1_Part_1.4":{"%":0.2}}},"CIP-008-6_Requirement_R2":{"%":0.2,"P":{"CIP-008-6_Requirement_R2_Part_2.1":{"%":0.2},"CIP-008-6_Requirement_R2_Part_2.2":{"%":0.2},"CIP-008-6_Requirement_R2_Part_2.3":{"%":0.2}}},"CIP-008-6_Requirement_R3":{"%":0.2,"P":{"CIP-008-6_Requirement_R3_Part_3.1":{"%":0.2},"CIP-008-6_Requirement_R3_Part_3.2":{"%":0.2}}},"CIP-008-6_Requirement_R4":{"%":0.2,"P":{"CIP-008-6_Requirement_R4_Part_4.1":{"%":0.2},"CIP-008-6_Requirement_R4_Part_4.2":{"%":0.2},"CIP-008-6_Requirement_R4_Part_4.3":{"%":0.2}}}}},"CIP-009-6":{"%":0.26666666666666667,"P":{"CIP-009-6_Requirement_R1":{"%":0.2,"P":{"CIP-009-6_Requirement_R1_Part_1.1":{"%":0.2},"CIP-009-6_Requirement_R1_Part_1.2":{"%":0.2},"CIP-009-6_Requirement_R1_Part_1.3":{"%":0.7},"CIP-009-6_Requirement_R1_Part_1.4":{"%":0.2},"CIP-009-6_Requirement_R1_Part_1.5":{"%":0.2}}},"CIP-009-6_Requirement_R2":{"%":0.2,"P":{"CIP-009-6_Requirement_R2_Part_2.1":{"%":0.2},"CIP-009-6_Requirement_R2_Part_2.2":{"%":0.2},"CIP-009-6_Requirement_R2_Part_2.3":{"%":0.2}}},"CIP-009-6_Requirement_R3":{"%":0.2,"P":{"CIP-009-6_Requirement_R3_Part_3.1":{"%":0.2},"CIP-009-6_Requirement_R3_Part_3.2":{"%":0.2}}}}},"CIP-010-4":{"%":0.241666666666666664,"P":{"CIP-010-4_Requirement_R1":{"%":0.16666666666666666,"P":{"CIP-010-4_Requirement_R1_Part_1.1":{"%":0.7},"CIP-010-4_Requirement_R1_Part_1.2":{"%":0.2},"CIP-010-4_Requirement_R1_Part_1.3":{"%":0.2},"CIP-010-4_Requirement_R1_Part_1.4":{"%":0.2},"CIP-010-4_Requirement_R1_Part_1.5":{"%":0.2},"CIP-010-4_Requirement_R1_Part_1.6":{"%":0.2}}},"CIP-010-4_Requirement_R2":{"%":0.2,"P":{"CIP-010-4_Requirement_R2_Part_2.1":{"%":0.2}}},"CIP-010-4_Requirement_R3":{"%":0.2,"P":{"CIP-010-4_Requirement_R3_Part_3.1":{"%":0.2},"CIP-010-4_Requirement_R3_Part_3.2":{"%":0.2},"CIP-010-4_Requirement_R3_Part_3.3":{"%":0.2},"CIP-010-4_Requirement_R3_Part_3.4":{"%":0.2}}},"CIP-010-4_Requirement_R4":{"%":0.2,"P":{"CIP-010-4_Requirement_R3_Part_3.4":{"%":0.2}}}}},"CIP-011-3":{"%":0.2,"P":{"CIP-011-3_Requirement_R1":{"%":0.2,"P":{"CIP-011-3_Requirement_R1_Part_1.1":{"%":0.2},"CIP-011-3_Requirement_R1_Part_1.2":{"%":0.2}}},"CIP-011-3_Requirement_R2":{"%":0.2,"P":{"CIP-011-3_Requirement_R2_Part_2.1":{"%":0.2},"CIP-011-3_Requirement_R2_Part_2.2":{"%":0.2}}}}},"CIP-012-1":{"%":0.7,"P":{"CIP-012-1_Requirement_R1":{"%":0.7,"P":{"null":{"%":0.7}}}}},"CIP-013-2":{"%":0.2,"P":{"CIP-013-2_Requirement_R1":{"%":0.2,"P":{"null":{"%":0.2}}},"CIP-013-2_Requirement_R2":{"%":0.2,"P":{"null":{"%":0.2}}},"CIP-013-2_Requirement_R3":{"%":0.2,"P":{"null":{"%":0.2}}}}}},"%":0.2516071428571428}},"Standard11":{"800-171 Rev2":{"Table 2 \u2014 Technical document coverage percentage":{"3.1 ACCESS CONTROL":{"%":0.5454545454545454,"P":{"3.1.1":{"%":0.7},"3.1.2":{"%":0.7},"3.1.3":{"%":0.2},"3.1.4":{"%":0.7},"3.1.5":{"%":0.7},"3.1.6":{"%":0.7},"3.1.7":{"%":0.7},"3.1.8":{"%":0.2},"3.1.9":{"%":0.2},"3.1.10":{"%":0.2},"3.1.11":{"%":0.2},"3.1.12":{"%":0.7},"3.1.13":{"%":0.7},"3.1.14":{"%":0.7},"3.1.15":{"%":0.2},"3.1.16":{"%":0.2},"3.1.17":{"%":0.7},"3.1.18":{"%":0.7},"3.1.19":{"%":0.2},"3.1.20":{"%":0.7},"3.1.21":{"%":0.2},"3.1.22":{"%":0.2}}},"3.3 AUDIT AND ACCOUNTABILITY":{"%":0.4444444444444444,"P":{"3.3.1":{"%":0.7},"3.3.2":{"%":0.2},"3.3.3":{"%":0.7},"3.3.4":{"%":0.2},"3.3.5":{"%":0.7},"3.3.6":{"%":0.2},"3.3.7":{"%":0.2},"3.3.8":{"%":0.7},"3.3.9":{"%":0.2}}},"3.4 CONFIGURATION MANAGEMENT":{"%":0.3333333333333333,"P":{"3.4.1":{"%":0.7},"3.4.2":{"%":0.7},"3.4.3":{"%":0.2},"3.4.4":{"%":0.2},"3.4.5":{"%":0.2},"3.4.6":{"%":0.7},"3.4.7":{"%":0.2},"3.4.8":{"%":0.2},"3.4.9":{"%":0.2}}},"3.5 INCIDENT AND AUTHENTICATION":{"%":0.2727272727272727,"P":{"3.5.1":{"%":0.2},"3.5.2":{"%":0.7},"3.5.3":{"%":0.2},"3.5.4":{"%":0.2},"3.5.5":{"%":0.2},"3.5.6":{"%":0.2},"3.5.7":{"%":0.7},"3.5.8":{"%":0.2},"3.5.9":{"%":0.2},"3.5.10":{"%":0.7},"3.5.11":{"%":0.2}}},"3.6 INCIDENT RESPONSE":{"%":0.2,"P":{"3.6.1":{"%":0.2},"3.6.2":{"%":0.2},"3.6.3":{"%":0.2}}},"3.7 MAINTENANCE":{"%":0.2,"P":{"3.7.1":{"%":0.2},"3.7.2":{"%":0.2},"3.7.3":{"%":0.2},"3.7.4":{"%":0.2},"3.7.5":{"%":0.2},"3.7.6":{"%":0.2}}},"3.8 MEDIA PROTECTION":{"%":0.3333333333333333,"P":{"3.8.1":{"%":0.7},"3.8.2":{"%":0.7},"3.8.3":{"%":0.2},"3.8.4":{"%":0.2},"3.8.5":{"%":0.2},"3.8.6":{"%":0.2},"3.8.7":{"%":0.2},"3.8.8":{"%":0.2},"3.8.9":{"%":0.7}}},"3.9 PERSONNEL SECURITY":{"%":0.2,"P":{"3.9.1":{"%":0.2},"3.9.2":{"%":0.2}}},"3.11 RISK ASSESMENT":{"%":0.3333333333333333,"P":{"3.11.1":{"%":0.2},"3.11.2":{"%":0.7},"3.11.3":{"%":0.2}}},"3.12 SECURITY ASSESSMENT":{"%":0.25,"P":{"3.12.1":{"%":0.2},"3.12.2":{"%":0.2},"3.12.3":{"%":0.2},"3.12.4":{"%":0.7}}},"3.13 SYSTEM AND COMMUNICATIOBS PROTECTION":{"%":0.4375,"P":{"3.13.1":{"%":0.7},"3.13.2":{"%":0.7},"3.13.3":{"%":0.2},"3.13.4":{"%":0.2},"3.13.5":{"%":0.7},"3.13.6":{"%":0.7},"3.13.7":{"%":0.2},"3.13.8":{"%":0.7},"3.13.9":{"%":0.2},"3.13.10":{"%":0.2},"3.13.11":{"%":0.2},"3.13.12":{"%":0.2},"3.13.13":{"%":0.2},"3.13.14":{"%":0.2},"3.13.15":{"%":0.7},"3.13.16":{"%":0.7}}},"3.14 SYSTEM AND INFORMATION PROTECTION":{"%":0.3333333333333333,"P":{"3.14.1":{"%":0.7},"3.14.2":{"%":0.2},"3.14.3":{"%":0.2},"3.14.4":{"%":0.2},"3.14.5":{"%":0.2},"3.14.6":{"%":0.7}}}},"%":0.273621632996633},"800_53 Rev5":{"wertyui":{"3.1 ACCESS CONTROL":{"%":0.391304347826087,"P":{"AC-1":{"%":0.7,"P":{"null":{"%":0.7}}},"AC-2":{"%":0.7,"P":{"AC-2.1":{"%":0.7},"AC-2.2":{"%":0.7},"AC-2.3":{"%":0.7},"AC-2.4":{"%":0.7},"AC-2.5":{"%":0.7},"AC-2.6":{"%":0.7},"AC-2.7":{"%":0.7},"AC-2.8":{"%":0.7},"AC-2.9":{"%":0.7},"AC-2.11":{"%":0.7},"AC-2.12":{"%":0.7},"AC-2.13":{"%":0.7}}},"AC-3":{"%":0.7,"P":{"AC-3.2":{"%":0.7},"AC-3.3":{"%":0.7},"AC-3.4":{"%":0.7},"AC-3.5":{"%":0.7},"AC-3.7":{"%":0.7},"AC-3.8":{"%":0.7},"AC-3.9":{"%":0.7},"AC-3.10":{"%":0.7},"AC-3.11":{"%":0.7},"AC-3.12":{"%":0.7},"AC-3.13":{"%":0.7},"AC-3.14":{"%":0.7},"AC-3.15":{"%":0.7}}},"AC-4":{"%":0.2,"P":{"AC-4.1":{"%":0.2},"AC-4.2":{"%":0.2},"AC-4.3":{"%":0.2},"AC-4.4":{"%":0.2},"AC-4.5":{"%":0.2},"AC-4.6":{"%":0.2},"AC-4.7":{"%":0.2},"AC-4.8":{"%":0.2},"AC-4.9":{"%":0.2},"AC-4.10":{"%":0.2},"AC-4.11":{"%":0.2},"AC-4.12":{"%":0.2},"AC-4.13":{"%":0.2},"AC-4.14":{"%":0.2},"AC-4.15":{"%":0.2},"AC-4.17":{"%":0.2},"AC-4.18":{"%":0.2},"AC-4.19":{"%":0.2},"AC-4.20":{"%":0.2},"AC-4.21":{"%":0.2},"AC-4.22":{"%":0.2},"AC-4.23":{"%":0.2},"AC-4.24":{"%":0.2},"AC-4.25":{"%":0.2},"AC-4.26":{"%":0.2},"AC-4.27":{"%":0.2},"AC-4.28":{"%":0.2},"AC-4.29":{"%":0.2},"AC-4.30":{"%":0.2},"AC-4.31":{"%":0.2},"AC-4.32":{"%":0.2}}},"AC-5":{"%":0.7,"P":{"AC-4.32":{"%":0.7}}},"AC-6":{"%":0.7,"P":{"AC-6.1":{"%":0.7},"AC-6.2":{"%":0.7},"AC-6.3":{"%":0.7},"AC-6.4":{"%":0.7},"AC-6.5":{"%":0.7},"AC-6.6":{"%":0.7},"AC-6.7":{"%":0.7},"AC-6.8":{"%":0.7},"AC-6.9":{"%":0.7},"AC-6.10":{"%":0.7}}},"AC-7":{"%":0.7,"P":{"AC-7.2":{"%":0.7},"AC-7.3":{"%":0.7},"AC-7.4":{"%":0.7}}},"AC-8":{"%":0.2,"P":{"AC-7.4":{"%":0.2}}},"AC-9":{"%":0.2,"P":{"AC-9.1":{"%":0.2},"AC-9.2":{"%":0.2},"AC-9.3":{"%":0.2},"AC-9.4":{"%":0.2}}},"AC-10":{"%":0.2,"P":{"AC-9.4":{"%":0.2}}},"AC-11":{"%":0.2,"P":{"AC-9.4":{"%":0.2}}},"AC-12":{"%":0.2,"P":{"AC-12.1":{"%":0.2},"AC-12.2":{"%":0.2},"AC-13.3":{"%":0.2}}},"AC-14":{"%":0.2,"P":{"AC-13.3":{"%":0.2}}},"AC-16":{"%":0.2,"P":{"AC-16.1":{"%":0.2},"AC-16.2":{"%":0.2},"AC-16.3":{"%":0.2},"AC-16.4":{"%":0.2},"AC-16.5":{"%":0.2},"AC-16.6":{"%":0.2},"AC-16.7":{"%":0.2},"AC-16.8":{"%":0.2},"AC-16.9":{"%":0.2},"AC-16.10":{"%":0.2}}},"AC-17":{"%":0.7,"P":{"AC-17.1":{"%":0.7},"AC-17.2":{"%":0.7},"AC-17.3":{"%":0.7},"AC-17.4":{"%":0.7},"AC-17.6":{"%":0.7},"AC-17.9":{"%":0.7},"AC-17.10":{"%":0.7}}},"AC-18":{"%":0.7,"P":{"AC-18.1":{"%":0.7},"AC-18.3":{"%":0.7},"AC-18.4":{"%":0.7},"AC-18.5":{"%":0.7}}},"AC-19":{"%":0.2,"P":{"AC-19.4":{"%":0.2},"AC-19.5":{"%":0.2}}},"AC-20":{"%":0.2,"P":{"AC-20.1":{"%":0.2},"AC-20.2":{"%":0.2},"AC-20.3":{"%":0.2},"AC-20.4":{"%":0.2},"AC-20.5":{"%":0.2}}},"AC-21":{"%":0.2,"P":{"AC-21.1":{"%":0.2},"AC-21.2":{"%":0.2}}},"AC-22":{"%":0.2,"P":{"AC-21.2":{"%":0.2}}},"AC-23":{"%":0.2,"P":{"AC-21.2":{"%":0.2}}},"AC-24":{"%":0.7,"P":{"AC-24.1":{"%":0.7},"AC-24.2":{"%":0.7}}},"AC-25":{"%":0.2,"P":{"AC-24.2":{"%":0.2}}}}},"3.2 AWARENESS AND TRAINING":{"%":0.2,"P":{"AT-1":{"%":0.2,"P":{"null":{"%":0.2}}},"AT-2":{"%":0.2,"P":{"AT-2.1":{"%":0.2},"AT-2.2":{"%":0.2},"AT-2.3":{"%":0.2},"AT-2.4":{"%":0.2},"AT-2.5":{"%":0.2},"AT-2.6":{"%":0.2}}},"AT-3":{"%":0.2,"P":{"AT-3.1":{"%":0.2},"AT-3.2":{"%":0.2},"AT-3.3":{"%":0.2},"AT-3.5":{"%":0.2}}},"AT-4":{"%":0.2,"P":{"AT-3.5":{"%":0.2}}},"AT-6":{"%":0.2,"P":{"AT-3.5":{"%":0.2}}}}},"3.3 AUDIT AND ACCOUNTABILITY":{"%":0.6,"P":{"AU-1":{"%":0.7,"P":{"null":{"%":0.7}}},"AU-2":{"%":0.7,"P":{"null":{"%":0.7}}},"AU-3":{"%":0.7,"P":{"AU-3.1":{"%":0.7},"AU-3.2":{"%":0.7},"AU-3.3":{"%":0.7}}},"AU-4":{"%":0.7,"P":{"AU-3.3":{"%":0.7}}},"AU-5":{"%":0.7,"P":{"AU-5.1":{"%":0.7},"AU-5.2":{"%":0.7},"AU-5.3":{"%":0.7},"AU-5.4":{"%":0.7},"AU-5.5":{"%":0.7}}},"AU-6":{"%":0.7,"P":{"AU-6.1":{"%":0.7},"AU-6.3":{"%":0.7},"AU-6.4":{"%":0.7},"AU-6.5":{"%":0.7},"AU-6.6":{"%":0.7},"AU-6.7":{"%":0.7},"AU-6.8":{"%":0.7},"AU-6.9":{"%":0.7}}},"AU-7":{"%":0.7,"P":{"AU-6.9":{"%":0.7}}},"AU-8":{"%":0.2,"P":{"AU-6.9":{"%":0.2}}},"AU-9":{"%":0.7,"P":{"AU-9.1":{"%":0.7},"AU-9.2":{"%":0.7},"AU-9.3":{"%":0.7},"AU-9.4":{"%":0.7},"AU-9.5":{"%":0.7},"AU-9.6":{"%":0.7},"AU-9.7":{"%":0.7}}},"AU-10":{"%":0.2,"P":{"AU-10.1":{"%":0.2},"AU-10.2":{"%":0.2},"AU-10.3":{"%":0.2},"AU-10.4":{"%":0.2}}},"AU-11":{"%":0.2,"P":{"AU-10.4":{"%":0.2}}},"AU-12":{"%":0.7,"P":{"AU-12.1":{"%":0.7},"AU-12.2":{"%":0.7},"AU-12.3":{"%":0.7},"AU-12.4":{"%":0.7}}},"AU-13":{"%":0.2,"P":{"AU-13.1":{"%":0.2},"AU-13.2":{"%":0.2},"AU-13.3":{"%":0.2}}},"AU-14":{"%":0.2,"P":{"AU-14.1":{"%":0.2},"AU-14.3":{"%":0.2}}},"AU-16":{"%":0.2,"P":{"AU-16.1":{"%":0.2},"AU-16.2":{"%":0.2},"AU-16.3":{"%":0.2}}}}},"3.4 ASSESSMENT, AUTHORIZATION, AND MONITORING":{"%":0.375,"P":{"CA-1":{"%":0.7,"P":{"null":{"%":0.7}}},"CA-2":{"%":0.2,"P":{"CA-2.1":{"%":0.2},"CA-2.2":{"%":0.2},"CA-2.3":{"%":0.2}}},"CA-3":{"%":0.2,"P":{"CA-3.6":{"%":0.2},"CA-3.7":{"%":0.2}}},"CA-5":{"%":0.7,"P":{"CA-3.7":{"%":0.7}}},"CA-6":{"%":0.2,"P":{"CA-6.1":{"%":0.2},"CA-6.2":{"%":0.2}}},"CA-7":{"%":0.2,"P":{"CA-1.1":{"%":0.2},"CA-1.3":{"%":0.2},"CA-1.4":{"%":0.2},"CA-1.5":{"%":0.2},"CA-1.6":{"%":0.2}}},"CA-8":{"%":0.2,"P":{"CA-8.1":{"%":0.2},"CA-8.2":{"%":0.2},"CA-8.3":{"%":0.2}}},"CA-9":{"%":0.7,"P":{"CA-8.3":{"%":0.7}}}}},"3.5 CONFIGURATION MANAGEMENT":{"%":0.6428571428571429,"P":{"CM-1":{"%":0.7,"P":{"null":{"%":0.7}}},"CM-2":{"%":0.7,"P":{"CM-2.2":{"%":0.7},"CM-2.3":{"%":0.7},"CM-2.6":{"%":0.7},"CM-2.7":{"%":0.7}}},"CM-3":{"%":0.2,"P":{"CM-3.1":{"%":0.2},"CM-3.2":{"%":0.2},"CM-3.3":{"%":0.2},"CM-3.4":{"%":0.2},"CM-3.5":{"%":0.2},"CM-3.6":{"%":0.2},"CM-3.7":{"%":0.2},"CM-3.8":{"%":0.2}}},"CM-4":{"%":0.7,"P":{"CM-4.1":{"%":0.7},"CM-4.2":{"%":0.7}}},"CM-5":{"%":0.7,"P":{"CM-5.1":{"%":0.7},"CM-5.2":{"%":0.7},"CM-5.3":{"%":0.7},"CM-5.4":{"%":0.7},"CM-5.5":{"%":0.7},"CM-5.6":{"%":0.7}}},"CM-6":{"%":0.7,"P":{"CM-6.1":{"%":0.7},"CM-6.2":{"%":0.7}}},"CM-7":{"%":0.7,"P":{"CM-7.1":{"%":0.7},"CM-7.2":{"%":0.7},"CM-7.3":{"%":0.7},"CM-7.4":{"%":0.7},"CM-7.5":{"%":0.7},"CM-7.6":{"%":0.7},"CM-7.7":{"%":0.7},"CM-7.8":{"%":0.7},"CM-7.9":{"%":0.7}}},"CM-8":{"%":0.7,"P":{"CM-8.1":{"%":0.7},"CM-8.2":{"%":0.7},"CM-8.3":{"%":0.7},"CM-8.4":{"%":0.7},"CM-8.6":{"%":0.7},"CM-8.7":{"%":0.7},"CM-8.8":{"%":0.7},"CM-8.9":{"%":0.7}}},"CM-9":{"%":0.7,"P":{"CM-8.9":{"%":0.7}}},"CM-10":{"%":0.2,"P":{"CM-8.9":{"%":0.2}}},"CM-11":{"%":0.2,"P":{"CM-11.2":{"%":0.2},"CM-11.3":{"%":0.2}}},"CM-12":{"%":0.7,"P":{"CM-11.3":{"%":0.7}}},"CM-13":{"%":0.2,"P":{"CM-11.3":{"%":0.2}}},"CM-14":{"%":0.2,"P":{"CM-11.3":{"%":0.2}}}}},"3.6 CONTINGENCY PLANNING":{"%":0.4166666666666667,"P":{"CP-1":{"%":0.2,"P":{"null":{"%":0.2}}},"CP-2":{"%":0.7,"P":{"CP-2.1":{"%":0.7},"CP-2.2":{"%":0.7},"CP-2.3":{"%":0.7},"CP-2.5":{"%":0.7},"CP-2.6":{"%":0.7},"CP-2.7":{"%":0.7},"CP-2.8":{"%":0.7}}},"CP-3":{"%":0.2,"P":{"CP-3.1":{"%":0.2},"CP-3.2":{"%":0.2}}},"CP-4":{"%":0.2,"P":{"CP-4.1":{"%":0.2},"CP-4.2":{"%":0.2},"CP-4.3":{"%":0.2},"CP-4.4":{"%":0.2},"CP-4.5":{"%":0.2}}},"CP-6":{"%":0.7,"P":{"CP-6.1":{"%":0.7},"CP-6.2":{"%":0.7},"CP-6.3":{"%":0.7}}},"CP-7":{"%":0.7,"P":{"CP-7.1":{"%":0.7},"CP-7.2":{"%":0.7},"CP-7.3":{"%":0.7},"CP-7.4":{"%":0.7},"CP-7.6":{"%":0.7}}},"CP-8":{"%":0.2,"P":{"CP-8.1":{"%":0.2},"CP-8.2":{"%":0.2},"CP-8.3":{"%":0.2},"CP-8.4":{"%":0.2},"CP-8.5":{"%":0.2}}},"CP-9":{"%":0.7,"P":{"CP-9.1":{"%":0.7},"CP-9.2":{"%":0.7},"CP-9.3":{"%":0.7},"CP-9.5":{"%":0.7},"CP-9.6":{"%":0.7},"CP-9.7":{"%":0.7},"CP-9.8":{"%":0.7}}},"CP-10":{"%":0.7,"P":{"CP-10.2":{"%":0.7},"CP-10.4":{"%":0.7},"CP-10.6":{"%":0.7}}},"CP-11":{"%":0.2,"P":{"CP-10.6":{"%":0.2}}},"CP-12":{"%":0.2,"P":{"CP-10.6":{"%":0.2}}},"CP-13":{"%":0.2,"P":{"CP-10.6":{"%":0.2}}}}},"3.7 IDENTIFICATION AND AUTHENTICATION":{"%":0.2604166666666667,"P":{"IA-1":{"%":0.2,"P":{"IA-1.1":{"%":0.2},"IA-1.2":{"%":0.2},"IA-1.5":{"%":0.2},"IA-1.6":{"%":0.2},"IA-1.8":{"%":0.2},"IA-1.9":{"%":0.2},"IA-1.10":{"%":0.2},"IA-1.11":{"%":0.2},"IA-1.12":{"%":0.2}}},"IA-2":{"%":0.125,"P":{"IA-2.1":{"%":0.7},"IA-2.2":{"%":0.2},"IA-2.5":{"%":0.2},"IA-2.6":{"%":0.2},"IA-2.8":{"%":0.2},"IA-2.10":{"%":0.2},"IA-2.12":{"%":0.2},"IA-2.13":{"%":0.2}}},"IA-3":{"%":0.2,"P":{"IA-3.1":{"%":0.2},"IA-3.3":{"%":0.2},"IA-3.4":{"%":0.2}}},"IA-4":{"%":0.2,"P":{"IA-4.1":{"%":0.2},"IA-4.4":{"%":0.2},"IA-4.5":{"%":0.2},"IA-4.6":{"%":0.2},"IA-4.8":{"%":0.2},"IA-4.9":{"%":0.2}}},"IA-5":{"%":0.7,"P":{"IA-5.1":{"%":0.7},"IA-5.2":{"%":0.7},"IA-5.5":{"%":0.7},"IA-5.6":{"%":0.7},"IA-5.7":{"%":0.7},"IA-5.8":{"%":0.7},"IA-5.9":{"%":0.7},"IA-5.10":{"%":0.7},"IA-5.12":{"%":0.7},"IA-5.13":{"%":0.7},"IA-5.14":{"%":0.7},"IA-5.15":{"%":0.7},"IA-5.16":{"%":0.7},"IA-5.17":{"%":0.7},"IA-5.18":{"%":0.7}}},"IA-6":{"%":0.7,"P":{"IA-5.18":{"%":0.7}}},"IA-7":{"%":0.7,"P":{"IA-5.18":{"%":0.7}}},"IA-8":{"%":0.2,"P":{"IA-8.1":{"%":0.2},"IA-8.2":{"%":0.2},"IA-8.4":{"%":0.2},"IA-8.5":{"%":0.2},"IA-8.6":{"%":0.2}}},"IA-9":{"%":0.2,"P":{"IA-8.6":{"%":0.2}}},"IA-10":{"%":0.2,"P":{"IA-8.6":{"%":0.2}}},"IA-11":{"%":0.2,"P":{"IA-8.6":{"%":0.2}}},"IA-12":{"%":0.2,"P":{"IA-12.1":{"%":0.2},"IA-12.2":{"%":0.2},"IA-12.3":{"%":0.2},"IA-12.4":{"%":0.2},"IA-12.5":{"%":0.2},"IA-12.6":{"%":0.2}}}}},"3.8 INCIDENT RESPONSE":{"%":0.3333333333333333,"P":{"IR-1":{"%":0.7,"P":{"null":{"%":0.7}}},"IR-2":{"%":0.2,"P":{"IR-2.1":{"%":0.2},"IR-2.2":{"%":0.2},"IR-2.3":{"%":0.2}}},"IR-3":{"%":0.2,"P":{"IR-3.1":{"%":0.2},"IR-3.2":{"%":0.2},"IR-3.3":{"%":0.2}}},"IR-4":{"%":0.2,"P":{"IR-4.1":{"%":0.2},"IR-4.2":{"%":0.2},"IR-4.3":{"%":0.2},"IR-4.4":{"%":0.2},"IR-4.5":{"%":0.2},"IR-4.6":{"%":0.2},"IR-4.7":{"%":0.2},"IR-4.8":{"%":0.2},"IR-4.9":{"%":0.2},"IR-4.10":{"%":0.2},"IR-4.11":{"%":0.2},"IR-4.12":{"%":0.2},"IR-4.13":{"%":0.2},"IR-4.14":{"%":0.2},"IR-4.15":{"%":0.2}}},"IR-5":{"%":0.2,"P":{"IR-4.15":{"%":0.2}}},"IR-6":{"%":0.2,"P":{"IR-6.1":{"%":0.2},"IR-6.2":{"%":0.2},"IR-6.3":{"%":0.2}}},"IR-7":{"%":0.7,"P":{"IR-7.1":{"%":0.7},"IR-7.2":{"%":0.7}}},"IR-8":{"%":0.7,"P":{"IR-7.2":{"%":0.7}}},"IR-9":{"%":0.2,"P":{"IR-9.2":{"%":0.2},"IR-9.3":{"%":0.2},"IR-9.4":{"%":0.2}}}}},"3.9 MAINTENANCE":{"%":0.14285714285714285,"P":{"MA-1":{"%":0.2,"P":{"null":{"%":0.2}}},"MA-2":{"%":0.2,"P":{"null":{"%":0.2}}},"MA-3":{"%":0.2,"P":{"MA-3.1":{"%":0.2},"MA-3.2":{"%":0.2},"MA-3.3":{"%":0.2},"MA-3.4":{"%":0.2},"MA-3.5":{"%":0.2},"MA-3.6":{"%":0.2}}},"MA-4":{"%":0.7,"P":{"MA-4.1":{"%":0.7},"MA-4.3":{"%":0.7},"MA-4.4":{"%":0.7},"MA-4.5":{"%":0.7},"MA-4.6":{"%":0.7},"MA-4.7":{"%":0.7}}},"MA-5":{"%":0.2,"P":{"MA-5.1":{"%":0.2},"MA-5.2":{"%":0.2},"MA-5.3":{"%":0.2},"MA-5.4":{"%":0.2},"MA-5.5":{"%":0.2}}},"MA-6":{"%":0.2,"P":{"MA-6.1":{"%":0.2},"MA-6.2":{"%":0.2},"MA-6.3":{"%":0.2}}},"MA-7":{"%":0.2,"P":{"MA-6.3":{"%":0.2}}}}},"3.10 MEDIA PROTECTION":{"%":0.125,"P":{"MP-1":{"%":0.2,"P":{"null":{"%":0.2}}},"MP-2":{"%":0.7,"P":{"null":{"%":0.7}}},"MP-3":{"%":0.2,"P":{"null":{"%":0.2}}},"MP-4":{"%":0.2,"P":{"null":{"%":0.2}}},"MP-5":{"%":0.2,"P":{"null":{"%":0.2}}},"MP-6":{"%":0.2,"P":{"MP-6.1":{"%":0.2},"MP-6.2":{"%":0.2},"MP-6.3":{"%":0.2},"MP-6.7":{"%":0.2},"MP-6.8":{"%":0.2}}},"MP-7":{"%":0.2,"P":{"MP-6.8":{"%":0.2}}},"MP-8":{"%":0.2,"P":{"MP-8.1":{"%":0.2},"MP-8.2":{"%":0.2},"MP-8.3":{"%":0.2},"MP-8.4":{"%":0.2}}}}},"3.11 PHYSICAL AND ENVIRONMENTAL PROTECTION":{"%":0.2,"P":{"PE-1":{"%":0.2,"P":{"null":{"%":0.2}}},"PE-2":{"%":0.2,"P":{"PE-2.1":{"%":0.2},"PE-2.2":{"%":0.2},"PE-2.3":{"%":0.2}}},"PE-3":{"%":0.2,"P":{"PE-3.1":{"%":0.2},"PE-3.2":{"%":0.2},"PE-3.3":{"%":0.2},"PE-3.4":{"%":0.2},"PE-3.5":{"%":0.2},"PE-3.7":{"%":0.2},"PE-3.8":{"%":0.2}}},"PE-4":{"%":0.2,"P":{"PE-3.8":{"%":0.2}}},"PE-5":{"%":0.2,"P":{"PE-3.8":{"%":0.2}}},"PE-6":{"%":0.2,"P":{"PE-6.1":{"%":0.2},"PE-6.2":{"%":0.2},"PE-6.3":{"%":0.2},"PE-6.4":{"%":0.2}}},"PE-8":{"%":0.2,"P":{"PE-8.1":{"%":0.2},"PE-8.3":{"%":0.2}}},"PE-9":{"%":0.2,"P":{"PE-9.1":{"%":0.2},"PE-9.2":{"%":0.2}}},"PE-10":{"%":0.2,"P":{"PE-9.2":{"%":0.2}}},"PE-11":{"%":0.2,"P":{"PE-11.1":{"%":0.2},"PE-11.2":{"%":0.2}}},"PE-12":{"%":0.2,"P":{"PE-11.2":{"%":0.2}}},"PE-13":{"%":0.2,"P":{"PE-13.1":{"%":0.2},"PE-13.2":{"%":0.2},"PE-13.4":{"%":0.2}}},"PE-14":{"%":0.2,"P":{"PE-14.1":{"%":0.2},"PE-14.2":{"%":0.2}}},"PE-15":{"%":0.2,"P":{"PE-14.2":{"%":0.2}}},"PE-16":{"%":0.2,"P":{"PE-14.2":{"%":0.2}}},"PE-17":{"%":0.2,"P":{"PE-14.2":{"%":0.2}}},"PE-18":{"%":0.2,"P":{"PE-14.2":{"%":0.2}}},"PE-19":{"%":0.2,"P":{"PE-14.2":{"%":0.2}}},"PE-20":{"%":0.2,"P":{"PE-14.2":{"%":0.2}}},"PE-21":{"%":0.2,"P":{"PE-14.2":{"%":0.2}}},"PE-22":{"%":0.2,"P":{"PE-14.2":{"%":0.2}}},"PE-23":{"%":0.2,"P":{"PE-14.2":{"%":0.2}}}}},"3.12 PLANNING":{"%":0.125,"P":{"PL-1":{"%":0.2,"P":{"null":{"%":0.2}}},"PL-2":{"%":0.2,"P":{"null":{"%":0.2}}},"PL-4":{"%":0.2,"P":{"null":{"%":0.2}}},"PL-7":{"%":0.2,"P":{"null":{"%":0.2}}},"PL-8":{"%":0.7,"P":{"PL-8.1":{"%":0.7},"PL-8.2":{"%":0.7}}},"PL-9":{"%":0.2,"P":{"PL-8.2":{"%":0.2}}},"PL-10":{"%":0.2,"P":{"PL-8.2":{"%":0.2}}},"PL-11":{"%":0.2,"P":{"PL-8.2":{"%":0.2}}}}},"3.13 PROGRAM MANAGEMENT":{"%":0.2625,"P":{"PM-1":{"%":0.2,"P":{"null":{"%":0.2}}},"PM-2":{"%":0.2,"P":{"null":{"%":0.2}}},"PM-3":{"%":0.2,"P":{"null":{"%":0.2}}},"PM-4":{"%":0.2,"P":{"null":{"%":0.2}}},"PM-5":{"%":0.7,"P":{"null":{"%":0.7}}},"PM-6":{"%":0.2,"P":{"null":{"%":0.2}}},"PM-7":{"%":0.7,"P":{"null":{"%":0.7}}},"PM-8":{"%":0.2,"P":{"null":{"%":0.2}}},"PM-9":{"%":0.2,"P":{"null":{"%":0.2}}},"PM-10":{"%":0.2,"P":{"null":{"%":0.2}}},"PM-11":{"%":0.2,"P":{"null":{"%":0.2}}},"PM-12":{"%":0.2,"P":{"null":{"%":0.2}}},"PM-13":{"%":0.2,"P":{"null":{"%":0.2}}},"PM-14":{"%":0.2,"P":{"null":{"%":0.2}}},"PM-15":{"%":0.2,"P":{"null":{"%":0.2}}},"PM-16":{"%":0.2,"P":{"null":{"%":0.2}}},"PM-17":{"%":0.2,"P":{"null":{"%":0.2}}},"PM-18":{"%":0.2,"P":{"null":{"%":0.2}}},"PM-19":{"%":0.2,"P":{"null":{"%":0.2}}},"PM-20":{"%":0.2,"P":{"null":{"%":0.2}}},"PM-21":{"%":0.2,"P":{"null":{"%":0.2}}},"PM-22":{"%":0.2,"P":{"null":{"%":0.2}}},"PM-23":{"%":0.2,"P":{"null":{"%":0.2}}},"PM-24":{"%":0.2,"P":{"null":{"%":0.2}}},"PM-25":{"%":0.2,"P":{"null":{"%":0.2}}},"PM-26":{"%":0.2,"P":{"null":{"%":0.2}}},"PM-27":{"%":0.2,"P":{"null":{"%":0.2}}},"PM-28":{"%":0.2,"P":{"null":{"%":0.2}}},"PM-29":{"%":0.2,"P":{"null":{"%":0.2}}},"PM-30":{"%":0.2,"P":{"null":{"%":0.2}}},"PM-31":{"%":0.2,"P":{"null":{"%":0.2}}},"PM-32":{"%":0.2,"P":{"null":{"%":0.2}}}}},"3.14 PERSONNEL SECURITY":{"%":0.2,"P":{"PS-1":{"%":0.2,"P":{"null":{"%":0.2}}},"PS-2":{"%":0.2,"P":{"null":{"%":0.2}}},"PS-3":{"%":0.2,"P":{"PS-3.1":{"%":0.2},"PS-3.2":{"%":0.2},"PS-3.3":{"%":0.2},"PS-3.4":{"%":0.2}}},"PS-4":{"%":0.2,"P":{"PS-4.1":{"%":0.2},"PS-4.2":{"%":0.2}}},"PS-5":{"%":0.2,"P":{"PS-4.2":{"%":0.2}}},"PS-6":{"%":0.2,"P":{"PS-6.2":{"%":0.2},"PS-6.3":{"%":0.2}}},"PS-7":{"%":0.2,"P":{"PS-6.3":{"%":0.2}}},"PS-8":{"%":0.2,"P":{"PS-6.3":{"%":0.2}}},"PS-9":{"%":0.2,"P":{"PS-6.3":{"%":0.2}}}}},"3.15 PERSONALLY IDENTIFIABLE INFORMATION PROCESSING AND TRANSPARENCY":{"%":0.5,"P":{"PT-1":{"%":0.7,"P":{"null":{"%":0.7}}},"PT-2":{"%":0.2,"P":{"PT-2.1":{"%":0.2},"PT-2.2":{"%":0.2}}},"PT-3":{"%":0.7,"P":{"PT-3.1":{"%":0.7},"PT-3.2":{"%":0.7}}},"PT-4":{"%":0.7,"P":{"PT-4.1":{"%":0.7},"PT-4.2":{"%":0.7},"PT-4.3":{"%":0.7}}},"PT-5":{"%":0.7,"P":{"PT-5.1":{"%":0.7},"PT-5.2":{"%":0.7}}},"PT-6":{"%":0.2,"P":{"PT-6.1":{"%":0.2},"PT-6.2":{"%":0.2}}},"PT-7":{"%":0.2,"P":{"PT-7.1":{"%":0.2},"PT-7.2":{"%":0.2}}},"PT-8":{"%":0.2,"P":{"PT-7.2":{"%":0.2}}}}},"3.16 RISK ASSESSMENT":{"%":0.3333333333333333,"P":{"RA-1":{"%":0.7,"P":{"null":{"%":0.7}}},"RA-2":{"%":0.2,"P":{"null":{"%":0.2}}},"RA-3":{"%":0.2,"P":{"RA-3.1":{"%":0.2},"RA-3.2":{"%":0.2},"RA-3.3":{"%":0.2},"RA-3.4":{"%":0.2}}},"RA-5":{"%":0.7,"P":{"RA-5.2":{"%":0.7},"RA-5.3":{"%":0.7},"RA-5.4":{"%":0.7},"RA-5.5":{"%":0.7},"RA-5.6":{"%":0.7},"RA-5.8":{"%":0.7},"RA-5.10":{"%":0.7},"RA-5.11":{"%":0.7}}},"RA-6":{"%":0.2,"P":{"RA-5.11":{"%":0.2}}},"RA-7":{"%":0.7,"P":{"RA-5.11":{"%":0.7}}},"RA-8":{"%":0.2,"P":{"RA-5.11":{"%":0.2}}},"RA-9":{"%":0.2,"P":{"RA-5.11":{"%":0.2}}},"RA-10":{"%":0.2,"P":{"RA-5.11":{"%":0.2}}}}},"3.17 SYSTEM AND SERVICES ACQUISITION":{"%":0.1875,"P":{"SA-1":{"%":0.2,"P":{"null":{"%":0.2}}},"SA-2":{"%":0.2,"P":{"null":{"%":0.2}}},"SA-3":{"%":0.7,"P":{"SA-3.1":{"%":0.7},"SA-3.2":{"%":0.7},"SA-3.3":{"%":0.7}}},"SA-4":{"%":0.2,"P":{"SA-4.1":{"%":0.2},"SA-4.2":{"%":0.2},"SA-4.3":{"%":0.2},"SA-4.5":{"%":0.2},"SA-4.6":{"%":0.2},"SA-4.7":{"%":0.2},"SA-4.8":{"%":0.2},"SA-4.9":{"%":0.2},"SA-4.10":{"%":0.2},"SA-4.11":{"%":0.2},"SA-4.12":{"%":0.2}}},"SA-5":{"%":0.2,"P":{"SA-4.12":{"%":0.2}}},"SA-8":{"%":0.7,"P":{"SA-8.1":{"%":0.7},"SA-8.2":{"%":0.7},"SA-8.3":{"%":0.7},"SA-8.4":{"%":0.7},"SA-8.5":{"%":0.7},"SA-8.6":{"%":0.7},"SA-8.7":{"%":0.7},"SA-8.8":{"%":0.7},"SA-8.9":{"%":0.7},"SA-8.10":{"%":0.7},"SA-8.11":{"%":0.7},"SA-8.12":{"%":0.7},"SA-8.13":{"%":0.7},"SA-8.14":{"%":0.7},"SA-8.15":{"%":0.7},"SA-8.16":{"%":0.7},"SA-8.17":{"%":0.7},"SA-8.18":{"%":0.7},"SA-8.19":{"%":0.7},"SA-8.20":{"%":0.7},"SA-8.21":{"%":0.7},"SA-8.22":{"%":0.7},"SA-8.23":{"%":0.7},"SA-8.24":{"%":0.7},"SA-8.25":{"%":0.7},"SA-8.26":{"%":0.7},"SA-8.27":{"%":0.7},"SA-8.28":{"%":0.7},"SA-8.29":{"%":0.7},"SA-8.30":{"%":0.7},"SA-8.31":{"%":0.7},"SA-8.32":{"%":0.7},"SA-8.33":{"%":0.7}}},"SA-9":{"%":0.2,"P":{"SA-9.1":{"%":0.2},"SA-9.2":{"%":0.2},"SA-9.3":{"%":0.2},"SA-9.4":{"%":0.2},"SA-9.5":{"%":0.2},"SA-9.6":{"%":0.2},"SA-9.7":{"%":0.2},"SA-9.8":{"%":0.2}}},"SA-10":{"%":0.7,"P":{"SA-10.1":{"%":0.7},"SA-10.2":{"%":0.7},"SA-10.3":{"%":0.7},"SA-10.4":{"%":0.7},"SA-13":{"%":0.7},"SA-10.6":{"%":0.7},"SA-10.7":{"%":0.7}}},"SA-11":{"%":0.2,"P":{"SA-11.1":{"%":0.2},"SA-11.2":{"%":0.2},"SA-11.3":{"%":0.2},"SA-11.4":{"%":0.2},"SA-11.5":{"%":0.2},"SA-11.6":{"%":0.2},"SA-11.7":{"%":0.2},"SA-11.8":{"%":0.2},"SA-11.9":{"%":0.2}}},"SA-15":{"%":0.2,"P":{"SA-15.1":{"%":0.2},"SA-15.2":{"%":0.2},"SA-15.3":{"%":0.2},"SA-15.5":{"%":0.2},"SA-15.6":{"%":0.2},"SA-15.7":{"%":0.2},"SA-15.8":{"%":0.2},"SA-15.10":{"%":0.2},"SA-15.11":{"%":0.2},"SA-15.12":{"%":0.2}}},"SA-16":{"%":0.2,"P":{"SA-15.12":{"%":0.2}}},"SA-17":{"%":0.2,"P":{"SA-17.1":{"%":0.2},"SA-17.2":{"%":0.2},"SA-17.3":{"%":0.2},"SA-17.4":{"%":0.2},"SA-17.5":{"%":0.2},"SA-17.6":{"%":0.2},"SA-17.7":{"%":0.2},"SA-17.8":{"%":0.2},"SA-17.9":{"%":0.2}}},"SA-20":{"%":0.2,"P":{"SA-17.9":{"%":0.2}}},"SA-21":{"%":0.2,"P":{"SA-17.9":{"%":0.2}}},"SA-22":{"%":0.2,"P":{"SA-17.9":{"%":0.2}}},"SA-23":{"%":0.2,"P":{"SA-17.9":{"%":0.2}}}}},"3.18 SYSTEM AND COMMUNICATIONS PROTECTION":{"%":0.1276595744680851,"P":{"SC-1":{"%":0.2,"P":{"null":{"%":0.2}}},"SC-2":{"%":0.2,"P":{"SC-2.1":{"%":0.2},"SC-2.2":{"%":0.2}}},"SC-3":{"%":0.2,"P":{"SC-3.1":{"%":0.2},"SC-3.2":{"%":0.2},"SC-3.3":{"%":0.2},"SC-3.4":{"%":0.2},"SC-3.5":{"%":0.2}}},"SC-4":{"%":0.2,"P":{"SC-3.5":{"%":0.2}}},"SC-5":{"%":0.2,"P":{"SC-5.1":{"%":0.2},"SC-5.2":{"%":0.2},"SC-5.3":{"%":0.2}}},"SC-6":{"%":0.2,"P":{"SC-5.3":{"%":0.2}}},"SC-7":{"%":0.7,"P":{"SC-7.3":{"%":0.7},"SC-7.4":{"%":0.7},"SC-7.5":{"%":0.7},"SC-7.7":{"%":0.7},"SC-7.8":{"%":0.7},"SC-7.9":{"%":0.7},"SC-7.10":{"%":0.7},"SC-7.11":{"%":0.7},"SC-7.12":{"%":0.7},"SC-7.13":{"%":0.7},"SC-7.14":{"%":0.7},"SC-7.15":{"%":0.7},"SC-7.16":{"%":0.7},"SC-7.17":{"%":0.7},"SC-7.18":{"%":0.7},"SC-7.19":{"%":0.7},"SC-7.20":{"%":0.7},"SC-7.21":{"%":0.7},"SC-7.22":{"%":0.7},"SC-7.23":{"%":0.7},"SC-7.24":{"%":0.7},"SC-7.25":{"%":0.7},"SC-7.26":{"%":0.7},"SC-7.27":{"%":0.7},"SC-7.28":{"%":0.7},"SC-7.29":{"%":0.7}}},"SC-8":{"%":0.7,"P":{"SC-8.1":{"%":0.7},"SC-8.2":{"%":0.7},"SC-8.3":{"%":0.7},"SC-8.4":{"%":0.7},"SC-8.5":{"%":0.7}}},"SC-10":{"%":0.2,"P":{"SC-8.5":{"%":0.2}}},"SC-11":{"%":0.2,"P":{"SC-8.5":{"%":0.2}}},"SC-12":{"%":0.7,"P":{"SC-12.1":{"%":0.7},"SC-12.2":{"%":0.7},"SC-12.3":{"%":0.7},"SC-12.6":{"%":0.7}}},"SC-13":{"%":0.7,"P":{"SC-12.6":{"%":0.7}}},"SC-15":{"%":0.2,"P":{"SC-15.1":{"%":0.2},"SC-15.3":{"%":0.2},"SC-15.4":{"%":0.2}}},"SC-16":{"%":0.2,"P":{"SC-16.1":{"%":0.2},"SC-16.2":{"%":0.2},"SC-16.3":{"%":0.2}}},"SC-17":{"%":0.2,"P":{"SC-16.3":{"%":0.2}}},"SC-18":{"%":0.2,"P":{"SC-18.1":{"%":0.2},"SC-18.2":{"%":0.2},"SC-18.3":{"%":0.2},"SC-18.4":{"%":0.2},"SC-18.5":{"%":0.2}}},"SC-20":{"%":0.2,"P":{"SC-18.5":{"%":0.2}}},"SC-21":{"%":0.2,"P":{"SC-18.5":{"%":0.2}}},"SC-22":{"%":0.2,"P":{"SC-18.5":{"%":0.2}}},"SC-23":{"%":0.7,"P":{"SC-23.1":{"%":0.7},"SC-23.3":{"%":0.7},"SC-23.5":{"%":0.7}}},"SC-24":{"%":0.2,"P":{"SC-23.5":{"%":0.2}}},"SC-25":{"%":0.2,"P":{"SC-23.5":{"%":0.2}}},"SC-26":{"%":0.2,"P":{"SC-23.5":{"%":0.2}}},"SC-27":{"%":0.2,"P":{"SC-23.5":{"%":0.2}}},"SC-28":{"%":0.7,"P":{"SC-28.1":{"%":0.7},"SC-28.2":{"%":0.7},"SC-28.3":{"%":0.7}}},"SC-29":{"%":0.2,"P":{"SC-28.3":{"%":0.2}}},"SC-30":{"%":0.2,"P":{"SC-30.2":{"%":0.2},"SC-30.3":{"%":0.2},"SC-30.4":{"%":0.2},"SC-30.5":{"%":0.2}}},"SC-31":{"%":0.2,"P":{"SC-31.1":{"%":0.2},"SC-31.2":{"%":0.2},"SC-31.3":{"%":0.2}}},"SC-32":{"%":0.2,"P":{"SC-31.3":{"%":0.2}}},"SC-34":{"%":0.2,"P":{"SC-34.1":{"%":0.2},"SC-34.2":{"%":0.2}}},"SC-35":{"%":0.2,"P":{"SC-34.2":{"%":0.2}}},"SC-36":{"%":0.2,"P":{"SC-36.1":{"%":0.2},"SC-36.2":{"%":0.2}}},"SC-37":{"%":0.2,"P":{"SC-36.2":{"%":0.2}}},"SC-38":{"%":0.2,"P":{"SC-36.2":{"%":0.2}}},"SC-39":{"%":0.2,"P":{"SC-39.1":{"%":0.2},"SC-39.2":{"%":0.2}}},"SC-40":{"%":0.2,"P":{"SC-40.1":{"%":0.2},"SC-40.2":{"%":0.2},"SC-40.3":{"%":0.2},"SC-40.4":{"%":0.2}}},"SC-41":{"%":0.2,"P":{"SC-40.4":{"%":0.2}}},"SC-42":{"%":0.2,"P":{"SC-42.1":{"%":0.2},"SC-42.2":{"%":0.2},"SC-42.4":{"%":0.2},"SC-42.5":{"%":0.2}}},"SC-43":{"%":0.2,"P":{"SC-42.5":{"%":0.2}}},"SC-44":{"%":0.2,"P":{"SC-42.5":{"%":0.2}}},"SC-45":{"%":0.2,"P":{"SC-45.1":{"%":0.2},"SC-45.2":{"%":0.2}}},"SC-46":{"%":0.2,"P":{"SC-45.2":{"%":0.2}}},"SC-47":{"%":0.2,"P":{"SC-45.2":{"%":0.2}}},"SC-48":{"%":0.2,"P":{"SC-45.2":{"%":0.2}}},"SC-49":{"%":0.2,"P":{"SC-45.2":{"%":0.2}}},"SC-50":{"%":0.2,"P":{"SC-45.2":{"%":0.2}}},"SC-51":{"%":0.2,"P":{"SC-45.2":{"%":0.2}}}}},"3.19 SYSTEM AND INFORMATION INTEGRITY":{"%":0.13043478260869565,"P":{"SI-1":{"%":0.2,"P":{"null":{"%":0.2}}},"SI-2":{"%":0.7,"P":{"SI-2.2":{"%":0.7},"SI-2.3":{"%":0.7},"SI-2.4":{"%":0.7},"SI-2.5":{"%":0.7},"SI-2.6":{"%":0.7}}},"SI-3":{"%":0.2,"P":{"SI-3.4":{"%":0.2},"SI-3.6":{"%":0.2},"SI-3.8":{"%":0.2},"SI-3.10":{"%":0.2}}},"SI-4":{"%":0.7,"P":{"SI-4.1":{"%":0.7},"SI-4.2":{"%":0.7},"SI-4.3":{"%":0.7},"SI-4.4":{"%":0.7},"SI-4.5":{"%":0.7},"SI-4.7":{"%":0.7},"SI-4.9":{"%":0.7},"SI-4.10":{"%":0.7},"SI-4.11":{"%":0.7},"SI-4.12":{"%":0.7},"SI-4.13":{"%":0.7},"SI-4.14":{"%":0.7},"SI-4.15":{"%":0.7},"SI-4.16":{"%":0.7},"SI-4.17":{"%":0.7},"SI-4.18":{"%":0.7},"SI-4.19":{"%":0.7},"SI-4.20":{"%":0.7},"SI-4.21":{"%":0.7},"SI-4.22":{"%":0.7},"SI-4.23":{"%":0.7},"SI-4.24":{"%":0.7},"SI-4.25":{"%":0.7}}},"SI-5":{"%":0.2,"P":{"SI-4.25":{"%":0.2}}},"SI-6":{"%":0.2,"P":{"SI-6.2":{"%":0.2},"SI-6.3":{"%":0.2}}},"SI-7":{"%":0.2,"P":{"SI-7.1":{"%":0.2},"SI-7.2":{"%":0.2},"SI-7.3":{"%":0.2},"SI-7.5":{"%":0.2},"SI-7.6":{"%":0.2},"SI-7.7":{"%":0.2},"SI-7.8":{"%":0.2},"SI-7.9":{"%":0.2},"SI-7.10":{"%":0.2},"SI-7.12":{"%":0.2},"SI-7.15":{"%":0.2},"SI-7.16":{"%":0.2},"SI-7.17":{"%":0.2}}},"SI-8":{"%":0.2,"P":{"SI-8.2":{"%":0.2},"SI-8.3":{"%":0.2}}},"SI-10":{"%":0.2,"P":{"SI-10.1":{"%":0.2},"SI-10.2":{"%":0.2},"SI-10.3":{"%":0.2},"SI-10.4":{"%":0.2},"SI-13":{"%":0.2},"SI-10.6":{"%":0.2}}},"SI-11":{"%":0.2,"P":{"SI-10.6":{"%":0.2}}},"SI-12":{"%":0.7,"P":{"SI-12.1":{"%":0.7},"SI-12.2":{"%":0.7},"SI-12.3":{"%":0.7}}},"SI-13":{"%":0.2,"P":{"SI-13.1":{"%":0.2},"SI-13.3":{"%":0.2},"SI-13.4":{"%":0.2},"SI-13.5":{"%":0.2}}},"SI-14":{"%":0.2,"P":{"SI-14.1":{"%":0.2},"SI-14.2":{"%":0.2},"SI-14.3":{"%":0.2}}},"SI-15":{"%":0.2,"P":{"SI-14.3":{"%":0.2}}},"SI-16":{"%":0.2,"P":{"SI-14.3":{"%":0.2}}},"SI-17":{"%":0.2,"P":{"SI-14.3":{"%":0.2}}},"SI-18":{"%":0.2,"P":{"SI-18.1":{"%":0.2},"SI-18.2":{"%":0.2},"SI-18.3":{"%":0.2},"SI-18.4":{"%":0.2},"SI-18.5":{"%":0.2}}},"SI-19":{"%":0.2,"P":{"SI-19.1":{"%":0.2},"SI-19.2":{"%":0.2},"SI-19.3":{"%":0.2},"SI-19.4":{"%":0.2},"SI-19.5":{"%":0.2},"SI-19.6":{"%":0.2},"SI-19.7":{"%":0.2},"SI-19.8":{"%":0.2}}},"SI-20":{"%":0.2,"P":{"SI-19.8":{"%":0.2}}},"SI-21":{"%":0.2,"P":{"SI-19.8":{"%":0.2}}},"SI-22":{"%":0.2,"P":{"SI-19.8":{"%":0.2}}},"SI-23":{"%":0.2,"P":{"SI-19.8":{"%":0.2}}},"SI-24":{"%":0.2,"P":{"SI-19.8":{"%":0.2}}}}},"3.20 SUPPLY CHAIN RISK MANAGEMENT":{"%":0.2,"P":{"SR-1":{"%":0.2,"P":{"null":{"%":0.2}}},"SR-2":{"%":0.2,"P":{"null":{"%":0.2}}},"SR-3":{"%":0.2,"P":{"SR-3.1":{"%":0.2},"SR-3.2":{"%":0.2},"SR-3.3":{"%":0.2}}},"SR-4":{"%":0.2,"P":{"SR-4.1":{"%":0.2},"SR-4.2":{"%":0.2},"SR-4.3":{"%":0.2},"SR-4.4":{"%":0.2}}},"SR-5":{"%":0.2,"P":{"SR-5.1":{"%":0.2},"SR-5.2":{"%":0.2}}},"SR-6":{"%":0.2,"P":{"SR-5.2":{"%":0.2}}},"SR-7":{"%":0.2,"P":{"SR-5.2":{"%":0.2}}},"SR-8":{"%":0.2,"P":{"SR-5.2":{"%":0.2}}},"SR-9":{"%":0.2,"P":{"SR-5.2":{"%":0.2}}},"SR-10":{"%":0.2,"P":{"SR-5.2":{"%":0.2}}},"SR-11":{"%":0.2,"P":{"SR-11.1":{"%":0.2},"SR-11.2":{"%":0.2},"SR-11.3":{"%":0.2}}}}}},"%":0.23769314953085763}},"CIS Oracle Database 19 Benchmark":{"v0.7.2":{"Table 1 \u2014 Full document coverage percentage":{"1":{"%":0.2,"P":{"1.1":{"%":0.2,"P":{"null":{"%":0.2}}}}},"2":{"%":0.34375,"P":{"2.1":{"%":0.2,"P":{"2.1.1":{"%":0.2,"P":{"null":{"%":0.2}}},"2.1.2":{"%":0.2,"P":{"null":{"%":0.2}}}}},"2.2":{"%":0.6875,"P":{"2.2.1":{"%":0.7,"P":{"null":{"%":0.7}}},"2.2.2":{"%":0.7,"P":{"null":{"%":0.7}}},"2.2.3":{"%":0.7,"P":{"null":{"%":0.7}}},"2.2.4":{"%":0.2,"P":{"null":{"%":0.2}}},"2.2.5":{"%":0.7,"P":{"null":{"%":0.7}}},"2.2.6":{"%":0.2,"P":{"null":{"%":0.2}}},"2.2.7":{"%":0.2,"P":{"null":{"%":0.2}}},"2.2.8":{"%":0.2,"P":{"null":{"%":0.2}}},"2.2.9":{"%":0.2,"P":{"null":{"%":0.2}}},"2.2.10":{"%":0.7,"P":{"null":{"%":0.7}}},"2.2.11":{"%":0.7,"P":{"null":{"%":0.7}}},"2.2.12":{"%":0.7,"P":{"null":{"%":0.7}}},"2.2.13":{"%":0.7,"P":{"null":{"%":0.7}}},"2.2.14":{"%":0.7,"P":{"null":{"%":0.7}}},"2.2.15":{"%":0.7,"P":{"null":{"%":0.7}}},"2.2.16":{"%":0.7,"P":{"null":{"%":0.7}}}}}}},"3":{"%":0.2,"P":{"3.1":{"%":0.2,"P":{"null":{"%":0.2}}},"3.2":{"%":0.2,"P":{"null":{"%":0.2}}},"3.3":{"%":0.2,"P":{"null":{"%":0.2}}},"3.4":{"%":0.2,"P":{"null":{"%":0.2}}},"3.5":{"%":0.2,"P":{"null":{"%":0.2}}},"3.6":{"%":0.2,"P":{"null":{"%":0.2}}},"3.7":{"%":0.2,"P":{"null":{"%":0.2}}},"3.8":{"%":0.2,"P":{"null":{"%":0.2}}},"3.9":{"%":0.2,"P":{"null":{"%":0.2}}}}},"4":{"%":0.2,"P":{"4.1":{"%":0.2,"P":{"null":{"%":0.2}}},"4.2":{"%":0.2,"P":{"null":{"%":0.2}}},"4.3":{"%":0.2,"P":{"null":{"%":0.2}}},"4.4":{"%":0.2,"P":{"null":{"%":0.2}}},"4.5":{"%":0.2,"P":{"null":{"%":0.2}}},"4.6":{"%":0.2,"P":{"null":{"%":0.2}}}}},"5":{"%":0.2,"P":{"5.1":{"%":0.2,"P":{"5.1.1":{"%":0.2,"P":{"5.1.1.1":{"%":0.2},"5.1.1.2":{"%":0.2},"5.1.1.3":{"%":0.2},"5.1.1.4":{"%":0.2},"5.1.1.5":{"%":0.2},"5.1.1.6":{"%":0.2}}},"5.1.2":{"%":0.2,"P":{"5.1.2.1":{"%":0.2}}},"5.1.3":{"%":0.2,"P":{"5.1.3.1":{"%":0.2},"5.1.3.2":{"%":0.2},"5.1.3.3":{"%":0.2}}}}},"5.2":{"%":0.2,"P":{"5.2.1":{"%":0.2,"P":{"5.1.3.3":{"%":0.2}}},"5.2.2":{"%":0.2,"P":{"5.1.3.3":{"%":0.2}}},"5.2.3":{"%":0.2,"P":{"5.1.3.3":{"%":0.2}}},"5.2.4":{"%":0.2,"P":{"5.1.3.3":{"%":0.2}}},"5.2.5":{"%":0.2,"P":{"5.1.3.3":{"%":0.2}}},"5.2.6":{"%":0.2,"P":{"5.1.3.3":{"%":0.2}}},"5.2.7":{"%":0.2,"P":{"5.1.3.3":{"%":0.2}}},"5.2.8":{"%":0.2,"P":{"5.1.3.3":{"%":0.2}}},"5.2.9":{"%":0.2,"P":{"5.1.3.3":{"%":0.2}}},"5.2.10":{"%":0.2,"P":{"5.1.3.3":{"%":0.2}}},"5.2.11":{"%":0.2,"P":{"5.1.3.3":{"%":0.2}}},"5.2.12":{"%":0.2,"P":{"5.1.3.3":{"%":0.2}}},"5.2.13":{"%":0.2,"P":{"5.1.3.3":{"%":0.2}}},"5.2.14":{"%":0.2,"P":{"5.1.3.3":{"%":0.2}}},"5.2.15":{"%":0.2,"P":{"5.1.3.3":{"%":0.2}}},"5.2.16":{"%":0.2,"P":{"5.1.3.3":{"%":0.2}}}}},"5.3":{"%":0.2,"P":{"5.3.1":{"%":0.2,"P":{"5.1.3.3":{"%":0.2}}},"5.3.2":{"%":0.2,"P":{"5.1.3.3":{"%":0.2}}},"5.3.3":{"%":0.2,"P":{"5.1.3.3":{"%":0.2}}}}}}},"6":{"%":0.2,"P":{"6.1":{"%":0.2,"P":{"6.1.1":{"%":0.2,"P":{"null":{"%":0.2}}},"6.1.2":{"%":0.2,"P":{"null":{"%":0.2}}},"6.1.3":{"%":0.2,"P":{"null":{"%":0.2}}},"6.1.4":{"%":0.2,"P":{"null":{"%":0.2}}},"6.1.5":{"%":0.2,"P":{"null":{"%":0.2}}},"6.1.6":{"%":0.2,"P":{"null":{"%":0.2}}},"6.1.7":{"%":0.2,"P":{"null":{"%":0.2}}},"6.1.8":{"%":0.2,"P":{"null":{"%":0.2}}},"6.1.9":{"%":0.2,"P":{"null":{"%":0.2}}},"6.1.10":{"%":0.2,"P":{"null":{"%":0.2}}},"6.1.11":{"%":0.2,"P":{"null":{"%":0.2}}},"6.1.12":{"%":0.2,"P":{"null":{"%":0.2}}},"6.1.13":{"%":0.2,"P":{"null":{"%":0.2}}},"6.1.14":{"%":0.2,"P":{"null":{"%":0.2}}},"6.1.15":{"%":0.2,"P":{"null":{"%":0.2}}},"6.1.16":{"%":0.2,"P":{"null":{"%":0.2}}},"6.1.17":{"%":0.2,"P":{"null":{"%":0.2}}},"6.1.18":{"%":0.2,"P":{"null":{"%":0.2}}}}},"6.2":{"%":0.2,"P":{"6.2.1":{"%":0.2,"P":{"null":{"%":0.2}}},"6.2.2":{"%":0.2,"P":{"null":{"%":0.2}}},"6.2.3":{"%":0.2,"P":{"null":{"%":0.2}}},"6.2.4":{"%":0.2,"P":{"null":{"%":0.2}}},"6.2.5":{"%":0.2,"P":{"null":{"%":0.2}}},"6.2.6":{"%":0.2,"P":{"null":{"%":0.2}}},"6.2.7":{"%":0.2,"P":{"null":{"%":0.2}}},"6.2.8":{"%":0.2,"P":{"null":{"%":0.2}}},"6.2.9":{"%":0.2,"P":{"null":{"%":0.2}}},"6.2.10":{"%":0.2,"P":{"null":{"%":0.2}}},"6.2.11":{"%":0.2,"P":{"null":{"%":0.2}}},"6.2.12":{"%":0.2,"P":{"null":{"%":0.2}}},"6.2.13":{"%":0.2,"P":{"null":{"%":0.2}}},"6.2.14":{"%":0.2,"P":{"null":{"%":0.2}}},"6.2.15":{"%":0.2,"P":{"null":{"%":0.2}}},"6.2.16":{"%":0.2,"P":{"null":{"%":0.2}}},"6.2.17":{"%":0.2,"P":{"null":{"%":0.2}}},"6.2.18":{"%":0.2,"P":{"null":{"%":0.2}}},"6.2.19":{"%":0.2,"P":{"null":{"%":0.2}}},"6.2.20":{"%":0.2,"P":{"null":{"%":0.2}}},"6.2.21":{"%":0.2,"P":{"null":{"%":0.2}}},"6.2.22":{"%":0.2,"P":{"null":{"%":0.2}}},"6.2.23":{"%":0.2,"P":{"null":{"%":0.2}}},"6.2.24":{"%":0.2,"P":{"null":{"%":0.2}}},"6.2.25":{"%":0.2,"P":{"null":{"%":0.2}}},"6.2.26":{"%":0.2,"P":{"null":{"%":0.2}}}}}}}},"%":0.257291666666666664}},"Standard10":{"null":{"Domain":{"D1":{"%":0.32738095238095233,"P":{"D1.G":{"%":0.3214285714285714,"P":{"D1.G.SP":{"%":0.14285714285714285,"P":{"D1.G.SP.B.1":{"%":0.2},"D1.G.SP.B.2":{"%":0.7},"D1.G.SP.B.3":{"%":0.2},"D1.G.SP.B.4":{"%":0.2},"D1.G.SP.B.5":{"%":0.2},"D1.G.SP.B.6":{"%":0.2},"D1.G.SP.B.7":{"%":0.2}}},"D1.G.IT":{"%":0.5,"P":{"D1.G.IT.B.1":{"%":0.7},"D1.G.IT.B.2":{"%":0.7},"D1.G.IT.B.3":{"%":0.2},"D1.G.IT.B.4":{"%":0.2}}}}},"D1.RM":{"%":0.3333333333333333,"P":{"D1.RM.RMP":{"%":0.7,"P":{"D1.RM.RMP.B.1":{"%":0.7}}},"D1.RM.RA":{"%":0.2,"P":{"D1.RM.RA.B.1":{"%":0.2},"D1.RM.RA.B.2":{"%":0.2},"D1.RM.RA.B.3":{"%":0.2}}},"D1.RM.Au":{"%":0.2,"P":{"D1.RM.Au.B.1":{"%":0.2},"D1.RM.Au.B.2":{"%":0.2},"D1.RM.Au.B.3":{"%":0.2},"D1.RM.Au.B.4":{"%":0.2}}}}}}},"D2":{"%":0.25,"P":{"D2.TI":{"%":0.2,"P":{"D2.TI.Ti":{"%":0.2,"P":{"D2.TI.Ti.B.1":{"%":0.2},"D2.TI.Ti.B.2":{"%":0.2},"D2.TI.Ti.B.3":{"%":0.2}}}}},"D2.MA":{"%":0.5,"P":{"D2.MA.Ma":{"%":0.5,"P":{"D2.MA.Ma.B.1":{"%":0.2},"D2.MA.Ma.B.2":{"%":0.7}}}}}}},"D3":{"%":0.41631944444444446,"P":{"D3.PC":{"%":0.265625,"P":{"D3.PC.Im":{"%":0.5,"P":{"D3.PC.Im.B.1":{"%":0.7},"D3.PC.Im.B.2":{"%":0.7},"D3.PC.Im.B.3":{"%":0.2},"D3.PC.Im.B.4":{"%":0.2},"D3.PC.Im.B.5":{"%":0.7},"D3.PC.Im.B.6":{"%":0.7},"D3.PC.Im.B.7":{"%":0.2},"D3.PC.Im.B.8":{"%":0.2},"D3.PC.Im.B.9":{"%":0.2},"D3.PC.Im.B.10":{"%":0.7}}},"D3.PC.Am":{"%":0.5625,"P":{"D3.PC.Am.B.1":{"%":0.7},"D3.PC.Am.B.2":{"%":0.7},"D3.PC.Am.B.3":{"%":0.7},"D3.PC.Am.B.4":{"%":0.7},"D3.PC.Am.B.5":{"%":0.7},"D3.PC.Am.B.6":{"%":0.7},"D3.PC.Am.B.7":{"%":0.7},"D3.PC.Am.B.8":{"%":0.2},"D3.PC.Am.B.9":{"%":0.2},"D3.PC.Am.B.10":{"%":0.2},"D3.PC.Am.B.12":{"%":0.7},"D3.PC.Am.B.13":{"%":0.7},"D3.PC.Am.B.14":{"%":0.2},"D3.PC.Am.B.15":{"%":0.2},"D3.PC.Am.B.16":{"%":0.2},"D3.PC.Am.B.18":{"%":0.2}}},"D3.PC.De":{"%":0.2,"P":{"D3.PC.De.B.1":{"%":0.2}}},"D3.PC.Se":{"%":0.2,"P":{"D3.PC.Se.B.1":{"%":0.2}}}}},"D3.DC":{"%":0.31666666666666665,"P":{"D3.DC.Th":{"%":0.25,"P":{"D3.DC.Th.B.1":{"%":0.7},"D3.DC.Th.B.2":{"%":0.2},"D3.DC.Th.B.3":{"%":0.2},"D3.DC.Th.B.4":{"%":0.2}}},"D3.DC.An":{"%":0.2,"P":{"D3.DC.An.B.1":{"%":0.7},"D3.DC.An.B.2":{"%":0.2},"D3.DC.An.B.3":{"%":0.2},"D3.DC.An.B.4":{"%":0.2},"D3.DC.An.B.5":{"%":0.2}}},"D3.DC.Ev":{"%":0.5,"P":{"D3.DC.Ev.B.1":{"%":0.7},"D3.DC.Ev.B.2":{"%":0.2},"D3.DC.Ev.B.3":{"%":0.7},"D3.DC.Ev.B.4":{"%":0.2}}}}},"D3.CC":{"%":0.6666666666666666,"P":{"D3.CC.Pa":{"%":0.3333333333333333,"P":{"D3.CC.Pa.B.1":{"%":0.7},"D3.CC.Pa.B.2":{"%":0.2},"D3.CC.Pa.B.3":{"%":0.2}}},"D3.CC.Re":{"%":0.7,"P":{"D3.CC.Re.1":{"%":0.7}}}}}}},"D4":{"%":0.125,"P":{"D4.C":{"%":0.25,"P":{"D4.C.Co":{"%":0.25,"P":{"D4.C.Co.B.1":{"%":0.2},"D4.C.Co.B.2":{"%":0.7},"D4.C.Co.B.3":{"%":0.2},"D4.C.Co.B.4":{"%":0.2}}}}},"D4.RM":{"%":0.2,"P":{"D4.RM.Dd":{"%":0.2,"P":{"D4.RM.Dd.B.1":{"%":0.2},"D4.RM.Dd.B.2":{"%":0.2},"D4.RM.Dd.B.3":{"%":0.2}}},"D4.RM.Om":{"%":0.2,"P":{"D4.RM.Om.B.1":{"%":0.2},"D4.RM.Om.B.2":{"%":0.2},"D4.RM.Om.B.3":{"%":0.2}}}}}}},"D5":{"%":0.125,"P":{"D5.IR":{"%":0.375,"P":{"D5.IR.Pl":{"%":0.75,"P":{"D5.IR.Pl.B.1":{"%":0.2},"D5.IR.Pl.B.3":{"%":0.7},"D5.IR.Pl.B.5":{"%":0.7},"D5.IR.Pl.B.6":{"%":0.7}}},"D5.IR.Te":{"%":0.2,"P":{"D5.IR.Te.B.1":{"%":0.2},"D5.IR.Te.B.2":{"%":0.2},"D5.IR.Te.B.3":{"%":0.2}}}}},"D5.DR":{"%":0.2,"P":{"D5.DR.De":{"%":0.2,"P":{"D5.DR.De.B.1":{"%":0.2},"D5.DR.De.B.2":{"%":0.2},"D5.DR.De.B.3":{"%":0.2}}},"D5.DR.Re":{"%":0.2,"P":{"D5.DR.Re.B.1":{"%":0.2}}}}},"D5.ER":{"%":0.2,"P":{"D5.ER.Es":{"%":0.2,"P":{"D5.ER.Es.B.2":{"%":0.2},"D5.ER.Es.B.3":{"%":0.2},"D5.ER.Es.B.4":{"%":0.2}}}}}}}},"%":0.24874007936507936}},"Standard7":{"null":{"Table 2 \u2014 Technical document coverage percentage":{"2":{"%":0.28333333333333333,"P":{"2._Maintain an inventory of personal data and/or processing activities":{"%":0.7},"2._Classify personal data by type":{"%":0.2},"2._Obtain Regulator approval for data processing":{"%":0.2},"2._Register databases with regulators":{"%":0.2},"2._Maintain documentation of data flows":{"%":0.2},"2._Maintain documentation of the transfer mechanism used for cross-border data flows":{"%":0.2},"2._Use Binding Corporate Rules as a data transfer mechanism":{"%":0.2},"2._Use contracts as a data transfer mechanism":{"%":0.2},"2._Use APEC Cross Border Privacy Rules as a data transfer mechanism":{"%":0.2},"2._Use Regulator approval as a data transfer mechanism":{"%":0.2},"2._Use adequacy or one of the derogations as a data transfer mechanism":{"%":0.2},"2._Use the Privacy Shield as a data transfer mechanism":{"%":0.2}}},"3":{"%":0.2,"P":{"3._Maintain a data privacy policy":{"%":0.2},"3._Maintain an employee data privacy policy":{"%":0.2},"3._Document legal basis for processing personal data":{"%":0.2},"3._Integrate ethics into data processing":{"%":0.2},"3._Maintain an organizational code of conduct that includes privacy":{"%":0.2}}},"6":{"%":0.5,"P":{"6._Integrate data privacy risk into security risk assessments":{"%":0.2},"6._Integrate data privacy into an information security policy":{"%":0.7},"6._Maintain technical security measures":{"%":0.7},"6._Maintain measures to encrypt personal data":{"%":0.7},"6._Maintain an acceptable use of information resources policy":{"%":0.2},"6._Maintain procedures to restrict access to personal data":{"%":0.7},"6._Integrate data privacy into a corporate security policy":{"%":0.2},"6._Maintain human resource security measures":{"%":0.2},"6._Maintain backup and business continuity plans":{"%":0.7},"6._Maintain a data-loss prevention strategy":{"%":0.2},"6._Conduct regular testing of data security posture":{"%":0.7},"6._Maintain a security certification":{"%":0.2}}},"11":{"%":0.14285714285714285,"P":{"11._Maintain a data privacy incident/breach response plan":{"%":0.2},"11._Maintain a breach notification and reporting protocol":{"%":0.2},"11._Maintain a log to track data privacy incidents/breaches":{"%":0.7},"11._Monitor and Report data privacy incident/breach metrics":{"%":0.2},"11._Conduct periodic testing of data privacy incident/breach plan":{"%":0.2},"11._Engage a breach response remediation provider":{"%":0.2},"11._Engage a forensic investigation team":{"%":0.2}}}},"%":0.18154761904761907}},"s202":{"null":{"null":{"COMMUNICATION AND INFORMATION":{"%":0.3333333333333333,"P":{"CC2.1":{"%":0.2},"CC2.2":{"%":0.7},"CC2.3":{"%":0.2}}},"RISK ASSESSMENT":{"%":0.5,"P":{"CC3.1":{"%":0.2},"CC3.2":{"%":0.7},"CC3.3":{"%":0.2},"CC3.4":{"%":0.7}}},"MONITORING ACTIVITIES":{"%":0.5,"P":{"CC4.1":{"%":0.7},"CC4.2":{"%":0.2}}},"CONTROL ACTIVITIES ":{"%":0.3333333333333333,"P":{"CC5.1":{"%":0.2},"CC5.2":{"%":0.7},"CC5.3":{"%":0.2}}},"Logical and Physical Access Controls":{"%":0.875,"P":{"CC6.1":{"%":0.7},"CC6.2":{"%":0.7},"CC6.3":{"%":0.7},"CC6.4":{"%":0.7},"CC6.5":{"%":0.2},"CC6.6":{"%":0.7},"CC6.7":{"%":0.7},"CC6.8":{"%":0.7}}},"System Operations":{"%":0.8,"P":{"CC7.1":{"%":0.7},"CC7.2":{"%":0.7},"CC7.3":{"%":0.7},"CC7.4":{"%":0.7},"CC7.5":{"%":0.2}}},"Change Management":{"%":0.7,"P":{"CC8.1":{"%":0.7}}},"Risk Mitigation":{"%":0.2,"P":{"CC9.1":{"%":0.2},"CC9.2":{"%":0.2}}},"ADDITIONAL CRITERIA FOR AVAILABILITY":{"%":0.6666666666666666,"P":{"A1.1":{"%":0.7},"A1.2":{"%":0.7},"A1.3":{"%":0.2}}},"ADDITIONAL CRITERIA FOR CONFIDENTIALITY":{"%":0.5,"P":{"C1.1":{"%":0.7},"C1.2":{"%":0.2}}},"ADDITIONAL CRITERIA FOR PROCESSING INTEGRITY":{"%":0.2,"P":{"PL1.1":{"%":0.2},"PL1.2":{"%":0.2},"PL1.3":{"%":0.2}}}},"%":0.5007575757575758}},"Standard8":{"v3.2.1":{"Table 2 \u2014 Technical document coverage percentage":{"Requirement 1":{"%":0.14285714285714285,"P":{"1.2":{"%":0.2,"P":{"1.2.1":{"%":0.2},"1.2.2":{"%":0.2},"1.2.3":{"%":0.2}}},"1.3":{"%":0.2857142857142857,"P":{"1.3.1":{"%":0.7},"1.3.2":{"%":0.2},"1.3.3":{"%":0.2},"1.3.4":{"%":0.2},"1.3.5":{"%":0.2},"1.3.6":{"%":0.7},"1.3.7":{"%":0.2}}}}},"Requirement 2":{"%":0.4,"P":{"2.1":{"%":0.7,"P":{"2.1.1":{"%":0.7}}},"2.2":{"%":0.7,"P":{"2.2.1":{"%":0.7},"2.2.2":{"%":0.7},"2.2.3":{"%":0.7},"2.2.4":{"%":0.7},"2.2.5":{"%":0.7}}},"2.3":{"%":0.2,"P":{"2.2.5":{"%":0.2}}},"2.5":{"%":0.2,"P":{"2.2.5":{"%":0.2}}},"2.6":{"%":0.2,"P":{"2.2.5":{"%":0.2}}}}},"Requirement 3":{"%":0.4,"P":{"3.1":{"%":0.7,"P":{"null":{"%":0.7}}},"3.3":{"%":0.2,"P":{"null":{"%":0.2}}},"3.4":{"%":0.7,"P":{"3.4.1":{"%":0.7}}},"3.5":{"%":0.2,"P":{"3.5.3":{"%":0.2}}},"3.6":{"%":0.2,"P":{"3.6.1":{"%":0.2},"3.6.2":{"%":0.2},"3.6.3":{"%":0.2},"3.6.4":{"%":0.2},"3.6.5":{"%":0.2},"3.6.6":{"%":0.2},"3.6.7":{"%":0.2}}}}},"Requirement 4":{"%":0.7,"P":{"4.1":{"%":0.7,"P":{"4.1.1":{"%":0.7}}}}},"Requirement 5":{"%":0.2,"P":{"5.1":{"%":0.2,"P":{"5.1.1":{"%":0.2},"5.1.2":{"%":0.2}}}}},"Requirement 6":{"%":0.7,"P":{"6.2":{"%":0.7,"P":{"null":{"%":0.7}}}}},"Requirement 7":{"%":0.7,"P":{"7.1":{"%":0.7,"P":{"7.1.2":{"%":0.7}}}}},"Requirement 8":{"%":0.41666666666666663,"P":{"8.1":{"%":0.2,"P":{"8.1.1":{"%":0.2},"8.1.2":{"%":0.2},"8.1.3":{"%":0.2},"8.1.4":{"%":0.2},"8.1.5":{"%":0.2},"8.1.6":{"%":0.2},"8.1.7":{"%":0.2},"8.1.8":{"%":0.2}}},"8.2":{"%":0.6666666666666666,"P":{"8.2.1":{"%":0.7},"8.2.2":{"%":0.2},"8.2.3":{"%":0.7},"8.2.4":{"%":0.7},"8.2.5":{"%":0.2},"8.2.6":{"%":0.7}}},"8.3":{"%":0.7,"P":{"8.3.1":{"%":0.7},"8.3.2":{"%":0.7}}},"8.5":{"%":0.2,"P":{"8.5.1":{"%":0.2}}}}},"Requirement 10":{"%":0.8333333333333334,"P":{"10.1":{"%":0.7,"P":{"null":{"%":0.7}}},"10.2":{"%":0.7,"P":{"10.2.1":{"%":0.7},"10.2.2":{"%":0.7},"10.2.3":{"%":0.7},"10.2.4":{"%":0.7},"10.2.5":{"%":0.7},"10.2.6":{"%":0.7},"10.2.7":{"%":0.7}}},"10.3":{"%":0.7,"P":{"10.3.1":{"%":0.7},"10.3.2":{"%":0.7},"10.3.3":{"%":0.7},"10.3.4":{"%":0.7},"10.3.5":{"%":0.7},"10.3.6":{"%":0.7}}},"10.4":{"%":0.2,"P":{"10.4.1":{"%":0.2},"10.4.2":{"%":0.2},"10.4.3":{"%":0.2}}},"13":{"%":0.7,"P":{"13.1":{"%":0.7},"13.2":{"%":0.7},"13.3":{"%":0.7},"13.4":{"%":0.7},"13.5":{"%":0.7}}},"10.7":{"%":0.7,"P":{"13.5":{"%":0.7}}}}},"Requirement 11":{"%":0.7,"P":{"11.2":{"%":0.7,"P":{"11.2.1":{"%":0.7},"11.2.2":{"%":0.7},"11.2.3":{"%":0.7}}},"11.4":{"%":0.7,"P":{"11.2.3":{"%":0.7}}}}}},"%":0.6192857142857143}},"Standard5":{"null":{"Control":{"164":{"%":0.1539434523809524,"P":{"164.105":{"%":0.2,"P":{"164.105.a":{"%":0.2,"P":{"164.105.a.1":{"%":0.2,"P":{"null":{"%":0.2}}},"164.105.a.2":{"%":0.2,"P":{"164.105.a.2.ii":{"%":0.2,"P":{"164.105.a.2.ii.A":{"%":0.2},"164.105.a.2.ii.B":{"%":0.2},"164.105.a.2.ii.C":{"%":0.2}}},"164.105.a.2.iii":{"%":0.2,"P":{"164.105.a.2.iii.A":{"%":0.2},"164.105.a.2.iii.B":{"%":0.2},"164.105.a.2.iii.C":{"%":0.2},"164.105.a.2.iii.D":{"%":0.2}}}}}}},"164.105.b":{"%":0.2,"P":{"164.105.b.1":{"%":0.2,"P":{"null":{"%":0.2}}},"164.105.b.2":{"%":0.2,"P":{"164.105.b.2.i":{"%":0.2,"P":{"164.105.b.2.i.A":{"%":0.2},"164.105.b.2.i.B":{"%":0.2}}},"164.105.b.2.ii":{"%":0.2,"P":{"164.105.b.2.i.B":{"%":0.2}}}}}}},"164.105.c":{"%":0.2,"P":{"164.105.c.1":{"%":0.2,"P":{"164.105.b.2.ii":{"%":0.2}}},"164.105.c.2":{"%":0.2,"P":{"164.105.b.2.ii":{"%":0.2}}}}}}},"164.306":{"%":0.2,"P":{"164.306.a":{"%":0.2,"P":{"164.306.a.1":{"%":0.2,"P":{"164.105.b.2.ii":{"%":0.2}}},"164.306.a.2":{"%":0.2,"P":{"164.105.b.2.ii":{"%":0.2}}},"164.306.a.3":{"%":0.2,"P":{"164.105.b.2.ii":{"%":0.2}}}}},"164.306.b":{"%":0.2,"P":{"164.306.b.1":{"%":0.2,"P":{"164.105.b.2.ii":{"%":0.2}}},"164.306.b.2":{"%":0.2,"P":{"164.306.b.2.i":{"%":0.2,"P":{"164.105.b.2.i.B":{"%":0.2}}},"164.306.b.2.ii":{"%":0.2,"P":{"164.105.b.2.i.B":{"%":0.2}}},"164.306.b.2.iii":{"%":0.2,"P":{"164.105.b.2.i.B":{"%":0.2}}},"164.306.b.2.iv":{"%":0.2,"P":{"164.105.b.2.i.B":{"%":0.2}}}}}}},"164.306.d":{"%":0.2,"P":{"164.306.d.1":{"%":0.2,"P":{"164.306.b.2.iv":{"%":0.2}}},"164.306.d.2":{"%":0.2,"P":{"164.306.b.2.iv":{"%":0.2}}},"164.306.d.3":{"%":0.2,"P":{"164.306.d.3.i":{"%":0.2,"P":{"164.105.b.2.i.B":{"%":0.2}}},"164.306.d.3.ii":{"%":0.2,"P":{"164.306.d.3.ii.A":{"%":0.2},"164.306.d.3.ii.B":{"%":0.2}}}}}}},"164.306.e":{"%":0.2,"P":{"164.306.d.3":{"%":0.2}}}}},"164.308":{"%":0.3677083333333333,"P":{"164.308.a":{"%":0.7354166666666666,"P":{"164.308.a.1":{"%":0.25,"P":{"164.308.a.1.i":{"%":0.2,"P":{"164.306.d.3.ii.B":{"%":0.2}}},"164.308.a.1.ii":{"%":0.5,"P":{"164.308.a.1.ii.A":{"%":0.2},"164.308.a.1.ii.B":{"%":0.7},"164.308.a.1.ii.C":{"%":0.2},"164.308.a.1.ii.D":{"%":0.7}}}}},"164.308.a.2":{"%":0.7,"P":{"164.308.a.1.ii":{"%":0.7}}},"164.308.a.3":{"%":0.8333333333333333,"P":{"164.308.a.3.i":{"%":0.7,"P":{"164.308.a.1.ii.D":{"%":0.7}}},"164.308.a.3.ii":{"%":0.6666666666666666,"P":{"164.308.a.3.ii.A":{"%":0.7},"164.308.a.3.ii.B":{"%":0.7},"164.308.a.3.ii.C":{"%":0.2}}}}},"164.308.a.4":{"%":0.7,"P":{"164.308.a.4.i":{"%":0.7,"P":{"164.308.a.3.ii.C":{"%":0.7}}},"164.308.a.4.ii":{"%":0.7,"P":{"164.308.a.4.ii.A":{"%":0.7},"164.308.a.4.ii.B":{"%":0.7},"164.308.a.4.ii.C":{"%":0.7}}}}},"164.308.a.5":{"%":0.5,"P":{"164.308.a.5.i":{"%":0.2,"P":{"164.308.a.4.ii.C":{"%":0.2}}},"164.308.a.5.ii":{"%":0.7,"P":{"164.308.a.5.ii.A":{"%":0.7},"164.308.a.5.ii.B":{"%":0.7},"164.308.a.5.ii.C":{"%":0.7},"164.308.a.5.ii.D":{"%":0.7}}}}},"164.308.a.6":{"%":0.5,"P":{"164.308.a.6.i":{"%":0.2,"P":{"164.308.a.5.ii.D":{"%":0.2}}},"164.308.a.6.ii":{"%":0.7,"P":{"164.308.a.5.ii.D":{"%":0.7}}}}},"164.308.a.7":{"%":0.8,"P":{"164.308.a.7.i":{"%":0.7,"P":{"164.308.a.5.ii.D":{"%":0.7}}},"164.308.a.7.ii":{"%":0.6,"P":{"164.308.a.7.ii.A":{"%":0.7},"164.308.a.7.ii.B":{"%":0.7},"164.308.a.7.ii.C":{"%":0.7},"164.308.a.7.ii.D":{"%":0.2},"164.308.a.7.ii.E":{"%":0.2}}}}},"164.308.a.8":{"%":0.7,"P":{"164.308.a.7.ii":{"%":0.7}}}}},"164.308.b":{"%":0.2,"P":{"164.308.b.1":{"%":0.2,"P":{"164.308.a.7.ii":{"%":0.2}}},"164.308.b.2":{"%":0.2,"P":{"164.308.a.7.ii":{"%":0.2}}},"164.308.b.3":{"%":0.2,"P":{"164.308.a.7.ii":{"%":0.2}}}}}}},"164.312":{"%":0.975,"P":{"164.312.a":{"%":0.875,"P":{"164.312.a.1":{"%":0.7,"P":{"164.308.a.7.ii":{"%":0.7}}},"164.312.a.2":{"%":0.75,"P":{"164.312.a.2.i":{"%":0.7,"P":{"164.308.a.7.ii.E":{"%":0.7}}},"164.312.a.2.ii":{"%":0.7,"P":{"164.308.a.7.ii.E":{"%":0.7}}},"164.312.a.2.iii":{"%":0.2,"P":{"164.308.a.7.ii.E":{"%":0.2}}},"164.312.a.2.iv":{"%":0.7,"P":{"164.308.a.7.ii.E":{"%":0.7}}}}}}},"164.312.b":{"%":0.7,"P":{"164.312.a.2":{"%":0.7}}},"164.312.c":{"%":0.7,"P":{"164.312.c.1":{"%":0.7,"P":{"164.312.a.2.iv":{"%":0.7}}},"164.312.c.2":{"%":0.7,"P":{"164.312.a.2.iv":{"%":0.7}}}}},"164.312.d":{"%":0.7,"P":{"164.312.c.2":{"%":0.7}}},"164.312.e":{"%":0.7,"P":{"164.312.e.1":{"%":0.7,"P":{"164.312.a.2.iv":{"%":0.7}}},"164.312.e.2":{"%":0.7,"P":{"164.312.e.2.i":{"%":0.7,"P":{"164.308.a.7.ii.E":{"%":0.7}}},"164.312.e.2.ii":{"%":0.7,"P":{"164.308.a.7.ii.E":{"%":0.7}}}}}}}}},"164.314":{"%":0.3125,"P":{"164.314.a":{"%":0.2,"P":{"164.314.a.1":{"%":0.2,"P":{"164.312.e.2.ii":{"%":0.2}}},"164.314.a.2":{"%":0.2,"P":{"164.314.a.2.i":{"%":0.2,"P":{"164.314.a.2.i.A":{"%":0.2},"164.314.a.2.i.B":{"%":0.2},"164.314.a.2.i.C":{"%":0.2}}},"164.314.a.2.ii":{"%":0.2,"P":{"164.314.a.2.i.C":{"%":0.2}}},"164.314.a.2.iii":{"%":0.2,"P":{"164.314.a.2.i.C":{"%":0.2}}}}}}},"164.314.b":{"%":0.625,"P":{"164.314.b.1":{"%":0.7,"P":{"164.314.a.2.iii":{"%":0.7}}},"164.314.b.2":{"%":0.25,"P":{"164.314.b.2.i":{"%":0.7,"P":{"164.314.a.2.i.C":{"%":0.7}}},"164.314.b.2.ii":{"%":0.2,"P":{"164.314.a.2.i.C":{"%":0.2}}},"164.314.b.2.iii":{"%":0.2,"P":{"164.314.a.2.i.C":{"%":0.2}}},"164.314.b.2.iv":{"%":0.2,"P":{"164.314.a.2.i.C":{"%":0.2}}}}}}}}},"164.316":{"%":0.5,"P":{"164.316.a":{"%":0.7,"P":{"164.314.b.2":{"%":0.7}}},"164.316.b":{"%":0.2,"P":{"164.316.b.1":{"%":0.2,"P":{"164.316.b.1.i":{"%":0.2,"P":{"164.314.a.2.i.C":{"%":0.2}}},"164.316.b.1.ii":{"%":0.2,"P":{"164.314.a.2.i.C":{"%":0.2}}}}},"164.316.b.2":{"%":0.2,"P":{"164.316.b.2.i":{"%":0.2,"P":{"164.314.a.2.i.C":{"%":0.2}}},"164.316.b.2.ii":{"%":0.2,"P":{"164.314.a.2.i.C":{"%":0.2}}},"164.316.b.2.iii":{"%":0.2,"P":{"164.314.a.2.i.C":{"%":0.2}}}}}}}}},"164.404":{"%":0.2,"P":{"164.404.a":{"%":0.2,"P":{"164.404.a.1":{"%":0.2,"P":{"164.316.b.2.iii":{"%":0.2}}},"164.404.a.2":{"%":0.2,"P":{"164.316.b.2.iii":{"%":0.2}}}}},"164.404.b":{"%":0.2,"P":{"164.404.a.2":{"%":0.2}}},"164.404.c":{"%":0.2,"P":{"164.404.c.1":{"%":0.2,"P":{"164.404.c.1.A":{"%":0.2,"P":{"164.314.a.2.i.C":{"%":0.2}}},"164.404.c.1.B":{"%":0.2,"P":{"164.314.a.2.i.C":{"%":0.2}}},"164.404.c.1.C":{"%":0.2,"P":{"164.314.a.2.i.C":{"%":0.2}}},"164.404.c.1.D":{"%":0.2,"P":{"164.314.a.2.i.C":{"%":0.2}}},"164.404.c.1.E":{"%":0.2,"P":{"164.314.a.2.i.C":{"%":0.2}}}}},"164.404.c.2":{"%":0.2,"P":{"164.404.c.1.E":{"%":0.2}}}}},"164.404.d":{"%":0.2,"P":{"164.404.d.1":{"%":0.2,"P":{"164.404.d.1.i":{"%":0.2,"P":{"164.314.a.2.i.C":{"%":0.2}}},"164.404.d.1.ii":{"%":0.2,"P":{"164.314.a.2.i.C":{"%":0.2}}}}},"164.404.d.2":{"%":0.2,"P":{"164.404.d.2.i":{"%":0.2,"P":{"164.314.a.2.i.C":{"%":0.2}}},"164.404.d.2.ii":{"%":0.2,"P":{"164.404.d.2.ii.A":{"%":0.2},"164.404.d.2.ii.B":{"%":0.2}}}}},"164.404.d.3":{"%":0.2,"P":{"164.404.d.2.ii":{"%":0.2}}}}}}},"164.406":{"%":0.2,"P":{"164.406.a":{"%":0.2,"P":{"164.404.d.3":{"%":0.2}}},"164.406.b":{"%":0.2,"P":{"164.404.d.3":{"%":0.2}}},"164.406.c":{"%":0.2,"P":{"164.404.d.3":{"%":0.2}}}}},"164.408":{"%":0.2,"P":{"164.408.a":{"%":0.2,"P":{"164.404.d.3":{"%":0.2}}},"164.408.b":{"%":0.2,"P":{"164.404.d.3":{"%":0.2}}},"164.408.c":{"%":0.2,"P":{"164.404.d.3":{"%":0.2}}}}},"164.410":{"%":0.2,"P":{"164.410.a":{"%":0.2,"P":{"164.410.a.1":{"%":0.2,"P":{"164.404.d.2.ii":{"%":0.2}}},"164.410.a.2":{"%":0.2,"P":{"164.404.d.2.ii":{"%":0.2}}}}},"164.410.b":{"%":0.2,"P":{"164.410.a.2":{"%":0.2}}},"164.410.c":{"%":0.2,"P":{"164.410.c.1":{"%":0.2,"P":{"164.404.d.2.ii":{"%":0.2}}},"164.410.c.2":{"%":0.2,"P":{"164.404.d.2.ii":{"%":0.2}}}}}}},"164.504":{"%":0.2,"P":{"164.504.e":{"%":0.2,"P":{"164.504.e.2":{"%":0.2,"P":{"164.504.e.2.i":{"%":0.2,"P":{"164.504.e.2.i.A":{"%":0.2},"164.504.e.2.i.B":{"%":0.2}}},"164.504.e.2.ii":{"%":0.2,"P":{"164.504.e.2.ii.A":{"%":0.2},"164.504.e.2.ii.B":{"%":0.2},"164.504.e.2.ii.C":{"%":0.2},"164.504.e.2.ii.D":{"%":0.2},"164.504.e.2.ii.E":{"%":0.2},"164.504.e.2.ii.F":{"%":0.2},"164.504.e.2.ii.G":{"%":0.2},"164.504.e.2.ii.H":{"%":0.2},"164.504.e.2.ii.I":{"%":0.2},"164.504.e.2.ii.J":{"%":0.2}}},"164.504.e.2.iii":{"%":0.2,"P":{"164.504.e.2.ii.J":{"%":0.2}}}}}}}}},"164.508":{"%":0.2,"P":{"164.508.c":{"%":0.2,"P":{"164.508.c.1":{"%":0.2,"P":{"164.508.c.1.i":{"%":0.2,"P":{"164.504.e.2.ii.J":{"%":0.2}}},"164.508.c.1.ii":{"%":0.2,"P":{"164.504.e.2.ii.J":{"%":0.2}}},"164.508.c.1.iii":{"%":0.2,"P":{"164.504.e.2.ii.J":{"%":0.2}}},"164.508.c.1.iv":{"%":0.2,"P":{"164.504.e.2.ii.J":{"%":0.2}}},"164.508.c.1.v":{"%":0.2,"P":{"164.504.e.2.ii.J":{"%":0.2}}},"164.508.c.1.vi":{"%":0.2,"P":{"164.504.e.2.ii.J":{"%":0.2}}}}},"164.508.c.2":{"%":0.2,"P":{"164.508.c.2.i":{"%":0.2,"P":{"164.508.c.2.i.A":{"%":0.2},"164.508.c.2.i.B":{"%":0.2}}},"164.508.c.2.ii":{"%":0.2,"P":{"164.508.c.2.ii.A":{"%":0.2},"164.508.c.2.ii.B":{"%":0.2}}},"164.508.c.2.iii":{"%":0.2,"P":{"164.508.c.2.ii.B":{"%":0.2}}}}},"164.508.c.3":{"%":0.2,"P":{"164.508.c.2.iii":{"%":0.2}}},"164.508.c.4":{"%":0.2,"P":{"164.508.c.2.iii":{"%":0.2}}}}}}},"164.514":{"%":0.2,"P":{"164.514.d":{"%":0.2,"P":{"164.514.d.4":{"%":0.2,"P":{"164.514.d.4.i":{"%":0.2,"P":{"164.508.c.2.ii.B":{"%":0.2}}},"164.514.d.4.ii":{"%":0.2,"P":{"164.508.c.2.ii.B":{"%":0.2}}},"164.514.d.4.iii":{"%":0.2,"P":{"164.514.d.4.iii.A":{"%":0.2},"164.514.d.4.iii.B":{"%":0.2}}}}}}}}},"164.522":{"%":0.2,"P":{"164.522.b":{"%":0.2,"P":{"164.522.b.1":{"%":0.2,"P":{"164.522.b.1.i":{"%":0.2,"P":{"164.514.d.4.iii.B":{"%":0.2}}},"164.522.b.1.ii":{"%":0.2,"P":{"164.514.d.4.iii.B":{"%":0.2}}}}}}}}}}}},"%":0.1539434523809524}}} \ No newline at end of file diff --git a/tests/tests_metrics/mock_files/rulesets/caas-settings/AZURE_STANDARDS_COVERAGE.json.gz b/tests/tests_metrics/mock_files/rulesets/caas-settings/AZURE_STANDARDS_COVERAGE.json.gz new file mode 100644 index 000000000..bbe7c7b88 --- /dev/null +++ b/tests/tests_metrics/mock_files/rulesets/caas-settings/AZURE_STANDARDS_COVERAGE.json.gz @@ -0,0 +1 @@ +{"BSA":{"v3":{"Table 1 \u2014 Full (Technical) document coverage percentage":{"Network Security":{"%":0.9,"P":{"NS-1":{"%":0.4},"NS-2":{"%":0.4},"NS-3":{"%":0.4},"NS-4":{"%":0.4},"NS-5":{"%":0.4},"NS-6":{"%":0.4},"NS-7":{"%":0.4},"NS-8":{"%":0.4},"NS-9":{"%":0.9},"NS-10":{"%":0.4}}},"Data Protection":{"%":0.75,"P":{"DP-1":{"%":0.4},"DP-2":{"%":0.4},"DP-3":{"%":0.4},"DP-4":{"%":0.4},"DP-5":{"%":0.4},"DP-6":{"%":0.9},"DP-7":{"%":0.9},"DP-8":{"%":0.4}}},"Identity Management":{"%":0.6,"P":{"IM-1":{"%":0.4},"IM-2":{"%":0.4},"IM-3":{"%":0.4},"IM-4":{"%":0.9},"IM-5":{"%":0.9},"IM-6":{"%":0.9},"IM-7":{"%":0.4},"IM-8":{"%":0.9},"IM-9":{"%":0.9}}},"Privileged Access":{"%":0.75,"P":{"PA-1":{"%":0.4},"PA-2":{"%":0.9},"PA-3":{"%":0.4},"PA-4":{"%":0.4},"PA-5":{"%":0.9},"PA-6":{"%":0.4},"PA-7":{"%":0.4},"PA-8":{"%":0.4}}},"Posture and Vulnerability Management":{"%":0.8571428571428571,"P":{"PV-1":{"%":0.4},"PV-2":{"%":0.4},"PV-3":{"%":0.4},"PV-4":{"%":0.4},"PV-5":{"%":0.4},"PV-6":{"%":0.4},"PV-7":{"%":0.9}}},"Logging and threat detection":{"%":0.8571,"P":{"LT-1":{"%":0.4},"LT-2":{"%":0.4},"LT-3":{"%":0.4},"LT-4":{"%":0.4},"LT-5":{"%":0.4},"LT-6":{"%":0.4},"LT-7":{"%":0.9}}},"Asset Management":{"%":0.8,"P":{"AM-1":{"%":0.4},"AM-2":{"%":0.9},"AM-3":{"%":0.4},"AM-4":{"%":0.4},"AM-5":{"%":0.4}}},"Endpoint security":{"%":0.3,"P":{"ES-1":{"%":0.9},"ES-2":{"%":0.4},"ES-3":{"%":0.9}}},"Backup and recovery":{"%":0.75,"P":{"BR-1":{"%":0.4},"BR-2":{"%":0.4},"BR-3":{"%":0.4},"BR-4":{"%":0.9}}},"Incident Response":{"%":0.8571428571428571,"P":{"IR-1":{"%":0.4},"IR-2":{"%":0.4},"IR-3":{"%":0.4},"IR-4":{"%":0.9},"IR-5":{"%":0.4},"IR-6":{"%":0.4},"IR-7":{"%":0.4}}},"DevOps Security":{"%":0.73,"P":{"DS-1":{"%":0.4},"DS-2":{"%":0.4},"DS-3":{"%":0.4},"DS-4":{"%":0.9},"DS-5":{"%":0.9},"DS-6":{"%":0.4},"DS-7":{"%":0.4}}},"Governance and Strategy":{"%":0.9,"P":{"GS-1":{"%":0.9},"GS-2":{"%":0.9},"GS-3":{"%":0.9},"GS-4":{"%":0.9},"GS-5":{"%":0.9},"GS-6":{"%":0.9},"GS-7":{"%":0.9},"GS-8":{"%":0.9},"GS-9":{"%":0.9},"GS-10":{"%":0.9}}}},"%":0.6677910052910052}},"Standard1":{"v7":{"Table 1 \u2014 Full (Technical) document coverage percentage":{"1":{"%":0.25,"P":{"1.1":{"%":0.9},"1.2":{"%":0.9},"1.3":{"%":0.9},"1.4":{"%":0.4},"1.5":{"%":0.4},"1.6":{"%":0.9},"1.7":{"%":0.9},"1.8":{"%":0.9}}},"2":{"%":0.1,"P":{"2.1":{"%":0.4},"2.2":{"%":0.9},"2.3":{"%":0.9},"2.4":{"%":0.9},"2.5":{"%":0.9},"2.6":{"%":0.9},"2.7":{"%":0.9},"2.8":{"%":0.9},"2.9":{"%":0.9},"2.10":{"%":0.9}}},"3":{"%":0.1,"P":{"3.1":{"%":0.4},"3.2":{"%":0.9},"3.3":{"%":0.9},"3.4":{"%":0.9},"3.5":{"%":0.9},"3.6":{"%":0.9},"3.7":{"%":0.9}}},"4":{"%":0.2,"P":{"4.1":{"%":0.4},"4.2":{"%":0.9},"4.3":{"%":0.4},"4.4":{"%":0.9},"4.5":{"%":0.9},"4.6":{"%":0.9},"4.7":{"%":0.9},"4.8":{"%":0.9},"4.9":{"%":0.9}}},"5":{"%":0.2,"P":{"5.1":{"%":0.4},"5.2":{"%":0.9},"5.3":{"%":0.9},"5.4":{"%":0.9},"5.5":{"%":0.9}}},"6":{"%":0.5,"P":{"6.1":{"%":0.9},"6.2":{"%":0.4},"6.3":{"%":0.4},"6.4":{"%":0.9},"6.5":{"%":0.4},"6.6":{"%":0.9},"6.7":{"%":0.4},"6.8":{"%":0.9}}},"7":{"%":0.9,"P":{"7.1":{"%":0.9},"7.2":{"%":0.9},"7.3":{"%":0.9},"7.4":{"%":0.9},"7.5":{"%":0.9},"7.6":{"%":0.9},"7.7":{"%":0.9},"7.8":{"%":0.9},"7.9":{"%":0.9},"7.10":{"%":0.9}}},"8":{"%":0.25,"P":{"8.1":{"%":0.4},"8.2":{"%":0.4},"8.3":{"%":0.9},"8.4":{"%":0.9},"8.5":{"%":0.9},"8.6":{"%":0.9},"8.7":{"%":0.9},"8.8":{"%":0.9}}},"9":{"%":0.2,"P":{"9.1":{"%":0.9},"9.2":{"%":0.9},"9.3":{"%":0.9},"9.4":{"%":0.9},"9.5":{"%":0.4}}},"10":{"%":0.4,"P":{"10.1":{"%":0.9},"10.2":{"%":0.9},"10.3":{"%":0.9},"10.4":{"%":0.4},"13":{"%":0.4}}},"11":{"%":0.14285714285714285,"P":{"11.1":{"%":0.4},"11.2":{"%":0.9},"11.3":{"%":0.9},"11.4":{"%":0.9},"11.5":{"%":0.9},"11.6":{"%":0.9},"11.7":{"%":0.9}}},"12":{"%":0.5833333333333334,"P":{"12.1":{"%":0.4},"12.2":{"%":0.9},"12.3":{"%":0.4},"12.4":{"%":0.4},"12.5":{"%":0.4},"12.6":{"%":0.9},"12.7":{"%":0.4},"12.8":{"%":0.4},"12.9":{"%":0.4},"12.10":{"%":0.9},"12.11":{"%":0.9},"12.12":{"%":0.9}}},"13":{"%":0.1111111111111111,"P":{"13.1":{"%":0.4},"13.2":{"%":0.9},"13.3":{"%":0.9},"13.4":{"%":0.9},"13.5":{"%":0.9},"13.6":{"%":0.9},"13.7":{"%":0.9},"13.8":{"%":0.9},"13.9":{"%":0.9}}},"14":{"%":0.3333333333333333,"P":{"14.1":{"%":0.9},"14.2":{"%":0.9},"14.3":{"%":0.9},"14.4":{"%":0.4},"14.5":{"%":0.9},"14.6":{"%":0.4},"14.7":{"%":0.9},"14.8":{"%":0.4},"14.9":{"%":0.9}}},"15":{"%":0.9,"P":{"15.1":{"%":0.9},"15.2":{"%":0.9},"15.3":{"%":0.9},"15.4":{"%":0.9},"15.5":{"%":0.9},"15.6":{"%":0.9},"15.7":{"%":0.9},"15.8":{"%":0.9},"15.9":{"%":0.9},"15.10":{"%":0.9}}},"16":{"%":0.38461538461538464,"P":{"16.1":{"%":0.9},"16.2":{"%":0.4},"16.3":{"%":0.9},"16.4":{"%":0.4},"16.5":{"%":0.4},"16.6":{"%":0.4},"16.7":{"%":0.4},"16.8":{"%":0.9},"16.9":{"%":0.9},"16.10":{"%":0.9},"16.11":{"%":0.9},"16.12":{"%":0.9},"16.13":{"%":0.9}}},"17":{"%":0.9,"P":{"17.1":{"%":0.9},"17.2":{"%":0.9},"17.3":{"%":0.9},"17.4":{"%":0.9},"17.5":{"%":0.9},"17.6":{"%":0.9},"17.7":{"%":0.9},"17.8":{"%":0.9},"17.9":{"%":0.9}}},"18":{"%":0.9,"P":{"18.1":{"%":0.9},"18.2":{"%":0.9},"18.3":{"%":0.9},"18.4":{"%":0.9},"18.5":{"%":0.9},"18.6":{"%":0.9},"18.7":{"%":0.9},"18.8":{"%":0.9},"18.9":{"%":0.9},"18.10":{"%":0.9},"18.11":{"%":0.9}}},"19":{"%":0.25,"P":{"19.1":{"%":0.9},"19.2":{"%":0.9},"19.3":{"%":0.4},"19.4":{"%":0.9},"19.5":{"%":0.4},"19.6":{"%":0.9},"19.7":{"%":0.9},"19.8":{"%":0.9}}},"20":{"%":0.9,"P":{"20.1":{"%":0.9},"20.2":{"%":0.9},"20.3":{"%":0.9},"20.4":{"%":0.9},"20.5":{"%":0.9},"20.6":{"%":0.9},"20.7":{"%":0.9}}}},"%":0.2035164835164835},"v8":{"Table 1 \u2014 Full (Technical) document coverage percentage":{"1":{"%":0.2,"P":{"1.1":{"%":0.4},"1.2":{"%":0.9},"1.3":{"%":0.9},"1.4":{"%":0.9},"1.5":{"%":0.9}}},"2":{"%":0.9,"P":{"2.1":{"%":0.9},"2.2":{"%":0.9},"2.3":{"%":0.9},"2.4":{"%":0.9},"2.5":{"%":0.9},"2.6":{"%":0.9},"2.7":{"%":0.9}}},"3":{"%":0.42857142857142855,"P":{"3.1":{"%":0.4},"3.2":{"%":0.4},"3.3":{"%":0.4},"3.4":{"%":0.9},"3.5":{"%":0.9},"3.6":{"%":0.9},"3.7":{"%":0.9},"3.8":{"%":0.9},"3.9":{"%":0.9},"3.10":{"%":0.4},"3.11":{"%":0.4},"3.12":{"%":0.9},"3.13":{"%":0.9},"3.14":{"%":0.4}}},"4":{"%":0.3333333333333333,"P":{"4.1":{"%":0.4},"4.2":{"%":0.4},"4.3":{"%":0.9},"4.4":{"%":0.9},"4.5":{"%":0.9},"4.6":{"%":0.4},"4.7":{"%":0.4},"4.8":{"%":0.9},"4.9":{"%":0.9},"4.10":{"%":0.9},"4.11":{"%":0.9},"4.12":{"%":0.9}}},"5":{"%":0.6666666666666666,"P":{"5.1":{"%":0.4},"5.2":{"%":0.9},"5.3":{"%":0.9},"5.4":{"%":0.4},"5.5":{"%":0.4},"5.6":{"%":0.4}}},"6":{"%":0.25,"P":{"6.1":{"%":0.4},"6.2":{"%":0.4},"6.3":{"%":0.9},"6.4":{"%":0.9},"6.5":{"%":0.9},"6.6":{"%":0.9},"6.7":{"%":0.9},"6.8":{"%":0.9}}},"7":{"%":0.2857142857142857,"P":{"7.1":{"%":0.9},"7.2":{"%":0.9},"7.3":{"%":0.9},"7.4":{"%":0.9},"7.5":{"%":0.4},"7.6":{"%":0.4},"7.7":{"%":0.9}}},"8":{"%":0.4166666666666667,"P":{"8.1":{"%":0.4},"8.2":{"%":0.4},"8.3":{"%":0.9},"8.4":{"%":0.9},"8.5":{"%":0.4},"8.6":{"%":0.9},"8.7":{"%":0.9},"8.8":{"%":0.4},"8.9":{"%":0.9},"8.10":{"%":0.9},"8.11":{"%":0.4},"8.12":{"%":0.9}}},"9":{"%":0.9,"P":{"9.1":{"%":0.9},"9.2":{"%":0.9},"9.3":{"%":0.9},"9.4":{"%":0.9},"9.5":{"%":0.9},"9.6":{"%":0.9},"9.7":{"%":0.9}}},"10":{"%":0.2857142857142857,"P":{"10.1":{"%":0.4},"10.2":{"%":0.4},"10.3":{"%":0.9},"10.4":{"%":0.9},"13":{"%":0.9},"10.6":{"%":0.9},"10.7":{"%":0.9}}},"11":{"%":0.6,"P":{"11.1":{"%":0.4},"11.2":{"%":0.9},"11.3":{"%":0.4},"11.4":{"%":0.4},"11.5":{"%":0.9}}},"12":{"%":0.375,"P":{"12.1":{"%":0.9},"12.2":{"%":0.9},"12.3":{"%":0.4},"12.4":{"%":0.4},"12.5":{"%":0.4},"12.6":{"%":0.9},"12.7":{"%":0.9},"12.8":{"%":0.9}}},"13":{"%":0.36363636363636365,"P":{"13.1":{"%":0.4},"13.2":{"%":0.9},"13.3":{"%":0.4},"13.4":{"%":0.9},"13.5":{"%":0.9},"13.6":{"%":0.4},"13.7":{"%":0.9},"13.8":{"%":0.4},"13.9":{"%":0.9},"13.10":{"%":0.9},"13.11":{"%":0.9}}},"14":{"%":0.9,"P":{"14.1":{"%":0.9},"14.2":{"%":0.9},"14.3":{"%":0.9},"14.4":{"%":0.9},"14.5":{"%":0.9},"14.6":{"%":0.9},"14.7":{"%":0.9},"14.8":{"%":0.9},"14.9":{"%":0.9}}},"15":{"%":0.9,"P":{"15.1":{"%":0.9},"15.2":{"%":0.9},"15.3":{"%":0.9},"15.4":{"%":0.9},"15.5":{"%":0.9},"15.6":{"%":0.9},"15.7":{"%":0.9}}},"16":{"%":0.9,"P":{"16.1":{"%":0.9},"16.2":{"%":0.9},"16.3":{"%":0.9},"16.4":{"%":0.9},"16.5":{"%":0.9},"16.6":{"%":0.9},"16.7":{"%":0.9},"16.8":{"%":0.9},"16.9":{"%":0.9},"16.10":{"%":0.9},"16.11":{"%":0.9},"16.12":{"%":0.9},"16.13":{"%":0.9},"16.14":{"%":0.9}}},"17":{"%":0.2222222222222222,"P":{"17.1":{"%":0.4},"17.2":{"%":0.4},"17.3":{"%":0.9},"17.4":{"%":0.9},"17.5":{"%":0.9},"17.6":{"%":0.9},"17.7":{"%":0.9},"17.8":{"%":0.9},"17.9":{"%":0.9}}},"18":{"%":0.9,"P":{"18.1":{"%":0.9},"18.2":{"%":0.9},"18.3":{"%":0.9},"18.4":{"%":0.9}}}},"%":0.2459736251402918}},"Standard2":{"19":{"Table 2 \u2014 Technical document coverage percentage":{"Evaluate, Direct and Monitor":{"%":0.9,"P":{"EDM04":{"%":0.9}}},"Align, Plan and Organize":{"%":0.26125,"P":{"APO01":{"%":0.4},"APO03":{"%":0.4},"APO04":{"%":0.9},"APO06":{"%":0.9},"APO09":{"%":0.99},"APO11":{"%":0.9},"APO13":{"%":0.9},"APO14":{"%":0.9}}},"Build, Acquire and Implement":{"%":0.42857142857142855,"P":{"BAI02":{"%":0.9},"BAI03":{"%":0.9},"BAI04":{"%":0.9},"BAI06":{"%":0.4},"BAI07":{"%":0.9},"BAI09":{"%":0.4},"BAI10":{"%":0.4}}},"Deliver, Service and Support":{"%":0.5,"P":{"DSS01":{"%":0.9},"DSS02":{"%":0.9},"DSS03":{"%":0.4},"DSS04":{"%":0.9},"DSS05":{"%":0.4},"DSS06":{"%":0.4}}},"Monitor, Evaluate and Assess":{"%":0.9,"P":{"MEA01":{"%":0.9},"MEA02":{"%":0.9},"MEA03":{"%":0.9}}}},"%":0.2379642857142857}},"CMMC":{"v2.0":{"Table 2 \u2014 Technical document coverage percentage":{"AC":{"%":0.5694444444444444,"P":{"AC.L1":{"%":0.75,"P":{"AC.L1-3.1.1":{"%":0.4},"AC.L1-3.1.2":{"%":0.4},"AC.L1-3.1.20":{"%":0.4},"AC.L1-3.1.22":{"%":0.9}}},"AC.L2":{"%":0.3888888888888889,"P":{"AC.L2-3.1.3":{"%":0.4},"AC.L2-3.1.4":{"%":0.4},"AC.L2-3.1.5":{"%":0.4},"AC.L2-3.1.6":{"%":0.4},"AC.L2-3.1.7":{"%":0.4},"AC.L2-3.1.8":{"%":0.9},"AC.L2-3.1.9":{"%":0.9},"AC.L2-3.1.10":{"%":0.9},"AC.L2-3.1.11":{"%":0.9},"AC.L2-3.1.12":{"%":0.9},"AC.L2-3.1.13":{"%":0.4},"AC.L2-3.1.14":{"%":0.9},"AC.L2-3.1.15":{"%":0.9},"AC.L2-3.1.16":{"%":0.9},"AC.L2-3.1.17":{"%":0.4},"AC.L2-3.1.18":{"%":0.9},"AC.L2-3.1.19":{"%":0.9},"AC.L2-3.1.21":{"%":0.9}}}}},"AU":{"%":0.3333333333333333,"P":{"AU.L2":{"%":0.3333333333333333,"P":{"AU.L2-3.3.1":{"%":0.4},"AU.L2-3.3.2":{"%":0.9},"AU.L2-3.3.3":{"%":0.9},"AU.L2-3.3.4":{"%":0.9},"AU.L2-3.3.5":{"%":0.4},"AU.L2-3.3.6":{"%":0.9},"AU.L2-3.3.7":{"%":0.9},"AU.L2-3.3.8":{"%":0.4},"AU.L2-3.3.9":{"%":0.9}}}}},"CM":{"%":0.4444444444444444,"P":{"CM.L2":{"%":0.4444444444444444,"P":{"CM.L2-3.4.1":{"%":0.4},"CM.L2-3.4.2":{"%":0.4},"CM.L2-3.4.3":{"%":0.9},"CM.L2-3.4.4":{"%":0.9},"CM.L2-3.4.5":{"%":0.9},"CM.L2-3.4.6":{"%":0.4},"CM.L2-3.4.7":{"%":0.4},"CM.L2-3.4.8":{"%":0.9},"CM.L2-3.4.9":{"%":0.9}}}}},"IA":{"%":0.25,"P":{"IA.L1":{"%":0.5,"P":{"IA.L1-3.5.1":{"%":0.9},"IA.L1-3.5.2":{"%":0.4}}},"IA.L2":{"%":0.9,"P":{"IA.L2-3.5.3":{"%":0.9},"IA.L2-3.5.4":{"%":0.9},"IA.L2-3.5.5":{"%":0.9},"IA.L2-3.5.6":{"%":0.9},"IA.L2-3.5.7":{"%":0.9},"IA.L2-3.5.8":{"%":0.9},"IA.L2-3.5.9":{"%":0.9},"IA.L2-3.5.10":{"%":0.9},"IA.L2-3.5.11":{"%":0.9}}}}},"IR":{"%":0.3333333333333333,"P":{"IR.L2":{"%":0.3333333333333333,"P":{"IR.L2-3.6.1":{"%":0.9},"IR.L2-3.6.2":{"%":0.4},"IR.L2-3.6.3":{"%":0.9}}}}},"MA":{"%":0.9,"P":{"MA.L2":{"%":0.9,"P":{"MA.L2-3.7.1":{"%":0.9},"MA.L2-3.7.2":{"%":0.9},"MA.L2-3.7.3":{"%":0.9},"MA.L2-3.7.4":{"%":0.9},"MA.L2-3.7.5":{"%":0.9},"MA.L2-3.7.6":{"%":0.9}}}}},"MP":{"%":0.1875,"P":{"MP.L1":{"%":0.9,"P":{"MP.L1-3.8.3":{"%":0.9}}},"MP.L2":{"%":0.375,"P":{"MP.L2-3.8.1":{"%":0.4},"MP.L2-3.8.2":{"%":0.4},"MP.L2-3.8.4":{"%":0.9},"MP.L2-3.8.5":{"%":0.9},"MP.L2-3.8.6":{"%":0.9},"MP.L2-3.8.7":{"%":0.9},"MP.L2-3.8.8":{"%":0.9},"MP.L2-3.8.9":{"%":0.4}}}}},"PS":{"%":0.9,"P":{"PS.L2":{"%":0.9,"P":{"PS.L2-3.9.2":{"%":0.9}}}}},"RA":{"%":0.3333333333333333,"P":{"RA.L2":{"%":0.3333333333333333,"P":{"RA.L2-3.11.1":{"%":0.9},"RA.L2-3.11.2":{"%":0.4},"RA.L2-3.11.3":{"%":0.9}}}}},"CA":{"%":0.25,"P":{"CA.L2":{"%":0.25,"P":{"CA.L2-3.12.1":{"%":0.9},"CA.L2-3.12.2":{"%":0.9},"CA.L2-3.12.3":{"%":0.9},"CA.L2-3.12.4":{"%":0.4}}}}},"SC":{"%":0.5,"P":{"SC.L1":{"%":0.5,"P":{"SC.L1-3.13.1":{"%":0.4},"SC.L1-3.13.5":{"%":0.9}}},"SC.L2":{"%":0.5,"P":{"SC.L2-3.13.2":{"%":0.9},"SC.L2-3.13.3":{"%":0.4},"SC.L2-3.13.4":{"%":0.9},"SC.L2-3.13.6":{"%":0.4},"SC.L2-3.13.7":{"%":0.9},"SC.L2-3.13.8":{"%":0.4},"SC.L2-3.13.9":{"%":0.9},"SC.L2-3.13.10":{"%":0.4},"SC.L2-3.13.11":{"%":0.4},"SC.L2-3.13.12":{"%":0.9},"SC.L2-3.13.13":{"%":0.9},"SC.L2-3.13.14":{"%":0.9},"SC.L2-3.13.15":{"%":0.4},"SC.L2-3.13.16":{"%":0.4}}}}},"SI":{"%":0.875,"P":{"SI.L1":{"%":0.75,"P":{"SI.L1-3.14.1":{"%":0.9},"SI.L1-3.14.2":{"%":0.4},"SI.L1-3.14.4":{"%":0.4},"SI.L1-3.14.5":{"%":0.4}}},"SI.L2":{"%":0.4,"P":{"SI.L2-3.14.3":{"%":0.4},"SI.L2-3.14.6":{"%":0.4}}}}}},"%":0.3396990740740741}},"CE":{"v2.2":{"Table 2 \u2014 Technical document coverage percentage":{"Boundary Firewalls and Internet Gateways":{"%":0.3333333333333333,"P":{"A4.1":{"%":0.9,"P":{"null":{"%":0.9}}},"A4.2":{"%":0.9,"P":{"null":{"%":0.9}}},"A4.3":{"%":0.9,"P":{"null":{"%":0.9}}},"A4.4":{"%":0.9,"P":{"null":{"%":0.9}}},"A4.5":{"%":0.4,"P":{"null":{"%":0.4}}},"A4.6":{"%":0.9,"P":{"null":{"%":0.9}}},"A4.7":{"%":0.4,"P":{"null":{"%":0.4}}},"A4.8":{"%":0.4,"P":{"null":{"%":0.4}}},"A4.9":{"%":0.9,"P":{"null":{"%":0.9}}},"A4.10":{"%":0.9,"P":{"null":{"%":0.9}}},"A4.11":{"%":0.4,"P":{"null":{"%":0.4}}},"A4.12":{"%":0.9,"P":{"null":{"%":0.9}}}}},"Secure Configuration":{"%":0.9,"P":{"A5.1":{"%":0.9,"P":{"null":{"%":0.9}}},"A5.2":{"%":0.9,"P":{"null":{"%":0.9}}},"A5.3":{"%":0.9,"P":{"null":{"%":0.9}}},"A5.4":{"%":0.9,"P":{"null":{"%":0.9}}},"A5.5":{"%":0.9,"P":{"null":{"%":0.9}}},"A5.6":{"%":0.9,"P":{"null":{"%":0.9}}},"A5.7":{"%":0.9,"P":{"null":{"%":0.9}}},"A5.8":{"%":0.9,"P":{"null":{"%":0.9}}},"A5.9":{"%":0.9,"P":{"null":{"%":0.9}}}}},"Device Locking":{"%":0.9,"P":{"A5.10":{"%":0.9,"P":{"null":{"%":0.9}}},"A5.11":{"%":0.9,"P":{"null":{"%":0.9}}}}},"Security update management":{"%":0.17857142857142858,"P":{"A6.1":{"%":0.9,"P":{"null":{"%":0.9}}},"A6.2":{"%":0.25,"P":{"A6.2.1":{"%":0.9},"A6.2.2":{"%":0.4},"A6.2.3":{"%":0.9},"A6.2.4":{"%":0.9}}},"A6.3":{"%":0.9,"P":{"A6.2.4":{"%":0.9}}},"A6.4":{"%":0.4,"P":{"A6.4.1":{"%":0.4},"A6.4.2":{"%":0.4}}},"A6.5":{"%":0.9,"P":{"A6.5.1":{"%":0.9},"A6.5.2":{"%":0.9}}},"A6.6":{"%":0.9,"P":{"A6.5.2":{"%":0.9}}},"A6.7":{"%":0.9,"P":{"A6.5.2":{"%":0.9}}}}},"User Access Control":{"%":0.75,"P":{"A7.1":{"%":0.4,"P":{"null":{"%":0.4}}},"A7.2":{"%":0.9,"P":{"null":{"%":0.9}}},"A7.3":{"%":0.4,"P":{"null":{"%":0.4}}},"A7.4":{"%":0.4,"P":{"null":{"%":0.4}}}}},"AdmiStandard11rative Accounts":{"%":0.4,"P":{"A7.5":{"%":0.4,"P":{"null":{"%":0.4}}},"A7.6":{"%":0.4,"P":{"null":{"%":0.4}}},"A7.7":{"%":0.4,"P":{"null":{"%":0.4}}},"A7.8":{"%":0.4,"P":{"null":{"%":0.4}}},"A7.9":{"%":0.4,"P":{"null":{"%":0.4}}}}},"Password-Based Authentication":{"%":0.125,"P":{"A7.10":{"%":0.9,"P":{"null":{"%":0.9}}},"A7.11":{"%":0.9,"P":{"null":{"%":0.9}}},"A7.12":{"%":0.9,"P":{"null":{"%":0.9}}},"A7.13":{"%":0.9,"P":{"null":{"%":0.9}}},"A7.14":{"%":0.9,"P":{"null":{"%":0.9}}},"A7.15":{"%":0.9,"P":{"null":{"%":0.9}}},"A7.16":{"%":0.4,"P":{"null":{"%":0.4}}},"A7.17":{"%":0.9,"P":{"null":{"%":0.9}}}}},"Malware protection":{"%":0.4,"P":{"A8.1":{"%":0.4,"P":{"null":{"%":0.4}}},"A8.2":{"%":0.4,"P":{"null":{"%":0.4}}},"A8.3":{"%":0.9,"P":{"null":{"%":0.9}}},"A8.4":{"%":0.9,"P":{"null":{"%":0.9}}},"A8.5":{"%":0.9,"P":{"null":{"%":0.9}}}}}},"%":0.3483630952380952}},"CJIS":{"null":{"Table 2 \u2014 Technical document coverage percentage":{"5":{"%":0.3651984126984127,"P":{"5.1":{"%":0.25,"P":{"5.1.1":{"%":0.4,"P":{"5.1.1.1":{"%":0.4,"P":{"null":{"%":0.4}}}}},"5.1.2":{"%":0.9,"P":{"5.1.1.1":{"%":0.9}}},"5.1.3":{"%":0.9,"P":{"5.1.1.1":{"%":0.9}}},"5.1.4":{"%":0.9,"P":{"5.1.1.1":{"%":0.9}}}}},"5.3":{"%":0.6666666666666666,"P":{"5.3.1":{"%":0.4,"P":{"5.3.1.1":{"%":0.4,"P":{"5.3.1.1.1":{"%":0.4},"5.3.1.1.2":{"%":0.4}}}}},"5.3.2":{"%":0.4,"P":{"5.3.2.1":{"%":0.4,"P":{"5.3.1.1.2":{"%":0.4}}},"5.3.2.2":{"%":0.4,"P":{"5.3.1.1.2":{"%":0.4}}}}},"5.3.4":{"%":0.9,"P":{"5.3.2.2":{"%":0.9}}}}},"5.4":{"%":0.5714285714285714,"P":{"5.4.1":{"%":0.4,"P":{"5.3.2.2":{"%":0.4}}},"5.4.2":{"%":0.9,"P":{"5.3.2.2":{"%":0.9}}},"5.4.3":{"%":0.4,"P":{"5.3.2.2":{"%":0.4}}},"5.4.4":{"%":0.9,"P":{"5.3.2.2":{"%":0.9}}},"5.4.5":{"%":0.9,"P":{"5.3.2.2":{"%":0.9}}},"5.4.6":{"%":0.4,"P":{"5.3.2.2":{"%":0.4}}},"5.4.7":{"%":0.4,"P":{"5.3.2.2":{"%":0.4}}}}},"5.5":{"%":0.3333333333333333,"P":{"5.5.1":{"%":0.4,"P":{"5.3.2.2":{"%":0.4}}},"5.5.2":{"%":0.4,"P":{"5.5.2.1":{"%":0.4,"P":{"5.3.1.1.2":{"%":0.4}}},"5.5.2.2":{"%":0.4,"P":{"5.3.1.1.2":{"%":0.4}}},"5.5.2.3":{"%":0.4,"P":{"5.3.1.1.2":{"%":0.4}}},"5.5.2.4":{"%":0.4,"P":{"5.3.1.1.2":{"%":0.4}}}}},"5.5.3":{"%":0.9,"P":{"5.5.2.4":{"%":0.9}}},"5.5.4":{"%":0.9,"P":{"5.5.2.4":{"%":0.9}}},"5.5.5":{"%":0.9,"P":{"5.5.2.4":{"%":0.9}}},"5.5.6":{"%":0.9,"P":{"5.5.6.1":{"%":0.9,"P":{"5.3.1.1.2":{"%":0.9}}},"5.5.6.2":{"%":0.9,"P":{"5.3.1.1.2":{"%":0.9}}}}}}},"5.6":{"%":0.5,"P":{"5.6.1":{"%":0.4,"P":{"5.5.6.2":{"%":0.4}}},"5.6.2":{"%":0.9,"P":{"5.6.2.1":{"%":0.9,"P":{"5.6.2.1.1":{"%":0.9},"5.6.2.1.2":{"%":0.9},"5.6.2.1.3":{"%":0.9}}},"5.6.2.2":{"%":0.9,"P":{"5.6.2.2.1":{"%":0.9},"5.6.2.2.2":{"%":0.9}}}}},"5.6.3":{"%":0.4,"P":{"5.6.3.1":{"%":0.4,"P":{"5.6.2.2.2":{"%":0.4}}},"5.6.3.2":{"%":0.4,"P":{"5.6.2.2.2":{"%":0.4}}}}},"5.6.4":{"%":0.9,"P":{"5.6.3.2":{"%":0.9}}}}},"5.7":{"%":0.5,"P":{"5.7.1":{"%":0.4,"P":{"5.7.1.1":{"%":0.4,"P":{"5.6.2.2.2":{"%":0.4}}},"5.7.1.2":{"%":0.4,"P":{"5.6.2.2.2":{"%":0.4}}}}},"5.7.2":{"%":0.9,"P":{"5.7.1.2":{"%":0.9}}}}},"5.8":{"%":0.9,"P":{"5.8.1":{"%":0.9,"P":{"5.7.1.2":{"%":0.9}}},"5.8.2":{"%":0.9,"P":{"5.8.2.1":{"%":0.9,"P":{"5.6.2.2.2":{"%":0.9}}}}},"5.8.3":{"%":0.9,"P":{"5.8.2.1":{"%":0.9}}}}},"5.10":{"%":0.3583333333333333,"P":{"5.10.1":{"%":0.5333333333333333,"P":{"5.10.1.1":{"%":0.4,"P":{"5.6.2.2.2":{"%":0.4}}},"5.10.1.2":{"%":0.6666666666666666,"P":{"5.10.1.2.1":{"%":0.4},"5.10.1.2.2":{"%":0.4},"5.10.1.2.3":{"%":0.9}}},"5.10.1.3":{"%":0.4,"P":{"5.10.1.2.3":{"%":0.4}}},"5.10.1.4":{"%":0.9,"P":{"5.10.1.2.3":{"%":0.9}}},"5.10.1.5":{"%":0.9,"P":{"5.10.1.2.3":{"%":0.9}}}}},"5.10.2":{"%":0.9,"P":{"5.10.1.5":{"%":0.9}}},"5.10.3":{"%":0.5,"P":{"5.10.3.1":{"%":0.9,"P":{"5.10.1.2.3":{"%":0.9}}},"5.10.3.2":{"%":0.4,"P":{"5.10.1.2.3":{"%":0.4}}}}},"5.10.4":{"%":0.4,"P":{"5.10.4.1":{"%":0.9,"P":{"5.10.1.2.3":{"%":0.9}}},"5.10.4.2":{"%":0.4,"P":{"5.10.1.2.3":{"%":0.4}}},"5.10.4.3":{"%":0.4,"P":{"5.10.1.2.3":{"%":0.4}}},"5.10.4.4":{"%":0.9,"P":{"5.10.1.2.3":{"%":0.9}}},"5.10.4.5":{"%":0.9,"P":{"5.10.1.2.3":{"%":0.9}}}}}}},"5.12":{"%":0.25,"P":{"5.12.1":{"%":0.9,"P":{"5.10.4.5":{"%":0.9}}},"5.12.2":{"%":0.4,"P":{"5.10.4.5":{"%":0.4}}},"5.12.3":{"%":0.9,"P":{"5.10.4.5":{"%":0.9}}},"5.12.4":{"%":0.9,"P":{"5.10.4.5":{"%":0.9}}}}},"5.13":{"%":0.2222222222222222,"P":{"5.13.2":{"%":0.9,"P":{"5.10.4.5":{"%":0.9}}},"5.13.3":{"%":0.4,"P":{"5.10.4.5":{"%":0.4}}},"5.13.4":{"%":0.3333333333333333,"P":{"5.13.4.1":{"%":0.9,"P":{"5.10.1.2.3":{"%":0.9}}},"5.13.4.2":{"%":0.4,"P":{"5.10.1.2.3":{"%":0.4}}},"5.13.4.3":{"%":0.9,"P":{"5.10.1.2.3":{"%":0.9}}}}},"5.13.5":{"%":0.9,"P":{"5.13.4.3":{"%":0.9}}},"5.13.6":{"%":0.9,"P":{"5.13.4.3":{"%":0.9}}},"5.13.7":{"%":0.9,"P":{"5.13.7.1":{"%":0.9,"P":{"5.10.1.2.3":{"%":0.9}}},"5.13.7.2":{"%":0.9,"P":{"5.10.1.2.3":{"%":0.9}}}}}}}}}},"%":0.3651984126984127}},"Standard4":{"v4":{"Table 2 \u2014 Technical document coverage percentage":{"Audit & Assurance":{"%":0.9,"P":{"A&A-01":{"%":0.9},"A&A-02":{"%":0.9},"A&A-03":{"%":0.9},"A&A-04":{"%":0.9},"A&A-05":{"%":0.9},"A&A-06":{"%":0.9}}},"Application & Interface Security":{"%":0.14285714285714285,"P":{"AIS-01":{"%":0.9},"AIS-02":{"%":0.9},"AIS-03":{"%":0.4},"AIS-04":{"%":0.9},"AIS-05":{"%":0.9},"AIS-06":{"%":0.9},"AIS-07":{"%":0.9}}},"Business Continuity Management and Operational Resilience":{"%":0.18181818181818182,"P":{"BCR-01":{"%":0.4},"BCR-02":{"%":0.9},"BCR-03":{"%":0.9},"BCR-04":{"%":0.9},"BCR-05":{"%":0.9},"BCR-06":{"%":0.9},"BCR-07":{"%":0.9},"BCR-08":{"%":0.4},"BCR-09":{"%":0.9},"BCR-10":{"%":0.9},"BCR-11":{"%":0.9}}},"Change Control and Configuration Management":{"%":0.1111111111111111,"P":{"CCC-01":{"%":0.4},"CCC-02":{"%":0.9},"CCC-03":{"%":0.9},"CCC-04":{"%":0.9},"CCC-05":{"%":0.9},"CCC-06":{"%":0.9},"CCC-07":{"%":0.9},"CCC-08":{"%":0.9},"CCC-09":{"%":0.9}}},"Cryptography, Encryption & Key Management":{"%":0.99523809523809523,"P":{"CEK-01":{"%":0.9},"CEK-02":{"%":0.9},"CEK-03":{"%":0.4},"CEK-04":{"%":0.9},"CEK-05":{"%":0.9},"CEK-06":{"%":0.9},"CEK-07":{"%":0.9},"CEK-08":{"%":0.9},"CEK-09":{"%":0.9},"CEK-10":{"%":0.9},"CEK-11":{"%":0.9},"CEK-12":{"%":0.4},"CEK-13":{"%":0.9},"CEK-14":{"%":0.9},"CEK-15":{"%":0.9},"CEK-16":{"%":0.9},"CEK-17":{"%":0.9},"CEK-18":{"%":0.9},"CEK-19":{"%":0.9},"CEK-20":{"%":0.9},"CEK-21":{"%":0.9}}},"Datacenter Security":{"%":0.6,"P":{"DCS-01":{"%":0.4},"DCS-02":{"%":0.9},"DCS-05":{"%":0.4},"DCS-06":{"%":0.4},"DCS-08":{"%":0.9}}},"Data Security and Privacy Lifecycle Management":{"%":0.21052631578947367,"P":{"DSP-01":{"%":0.4},"DSP-02":{"%":0.9},"DSP-03":{"%":0.4},"DSP-04":{"%":0.9},"DSP-05":{"%":0.9},"DSP-06":{"%":0.4},"DSP-07":{"%":0.9},"DSP-08":{"%":0.9},"DSP-09":{"%":0.9},"DSP-10":{"%":0.9},"DSP-11":{"%":0.9},"DSP-12":{"%":0.9},"DSP-13":{"%":0.9},"DSP-14":{"%":0.9},"DSP-15":{"%":0.9},"DSP-16":{"%":0.9},"DSP-17":{"%":0.4},"DSP-18":{"%":0.9},"DSP-19":{"%":0.9}}},"Governance, Risk and Compliance":{"%":0.125,"P":{"GRC-01":{"%":0.9},"GRC-02":{"%":0.9},"GRC-03":{"%":0.4},"GRC-04":{"%":0.9},"GRC-05":{"%":0.9},"GRC-06":{"%":0.9},"GRC-07":{"%":0.9},"GRC-08":{"%":0.9}}},"Human Resources":{"%":0.9,"P":{"HRS-04":{"%":0.9}}},"Identity & Access Management":{"%":0.6875,"P":{"IAM-01":{"%":0.4},"IAM-02":{"%":0.9},"IAM-03":{"%":0.4},"IAM-04":{"%":0.4},"IAM-05":{"%":0.4},"IAM-06":{"%":0.4},"IAM-07":{"%":0.4},"IAM-08":{"%":0.4},"IAM-09":{"%":0.4},"IAM-10":{"%":0.4},"IAM-11":{"%":0.9},"IAM-12":{"%":0.9},"IAM-13":{"%":0.9},"IAM-14":{"%":0.4},"IAM-15":{"%":0.9},"IAM-16":{"%":0.4}}},"Interoperability & Portability":{"%":0.9,"P":{"IPY-01":{"%":0.9},"IPY-02":{"%":0.9},"IPY-03":{"%":0.9},"IPY-04":{"%":0.9}}},"Infrastructure & Virtualization Security":{"%":0.4444444444444444,"P":{"IVS-01":{"%":0.9},"IVS-02":{"%":0.9},"IVS-03":{"%":0.4},"IVS-04":{"%":0.4},"IVS-05":{"%":0.9},"IVS-06":{"%":0.9},"IVS-07":{"%":0.9},"IVS-08":{"%":0.4},"IVS-09":{"%":0.4}}},"Logging and Monitoring":{"%":0.3076923076923077,"P":{"LOG-01":{"%":0.9},"LOG-02":{"%":0.4},"LOG-03":{"%":0.9},"LOG-04":{"%":0.4},"LOG-05":{"%":0.4},"LOG-06":{"%":0.9},"LOG-07":{"%":0.9},"LOG-08":{"%":0.4},"LOG-09":{"%":0.9},"LOG-10":{"%":0.9},"LOG-11":{"%":0.9},"LOG-12":{"%":0.9},"LOG-13":{"%":0.9}}},"Security Incident Management, E-Discovery, & Cloud Forensics":{"%":0.375,"P":{"SEF-01":{"%":0.9},"SEF-02":{"%":0.9},"SEF-03":{"%":0.4},"SEF-04":{"%":0.9},"SEF-05":{"%":0.9},"SEF-06":{"%":0.9},"SEF-07":{"%":0.4},"SEF-08":{"%":0.4}}},"Supply Chain Management, Transparency, and Accountability":{"%":0.9,"P":{"STA-01":{"%":0.9},"STA-02":{"%":0.9},"STA-03":{"%":0.9},"STA-04":{"%":0.9},"STA-05":{"%":0.9},"STA-06":{"%":0.9},"STA-07":{"%":0.9},"STA-08":{"%":0.9},"STA-09":{"%":0.9},"STA-10":{"%":0.9},"STA-11":{"%":0.9},"STA-12":{"%":0.9},"STA-13":{"%":0.9},"STA-14":{"%":0.9}}},"Threat & Vulnerability Management":{"%":0.3,"P":{"TVM-01":{"%":0.9},"TVM-02":{"%":0.4},"TVM-03":{"%":0.9},"TVM-04":{"%":0.4},"TVM-05":{"%":0.9},"TVM-06":{"%":0.9},"TVM-07":{"%":0.4},"TVM-08":{"%":0.9},"TVM-09":{"%":0.9},"TVM-10":{"%":0.9}}},"Universal Endpoint Management":{"%":0.23076923076923078,"P":{"UEM-01":{"%":0.9},"UEM-02":{"%":0.9},"UEM-03":{"%":0.9},"UEM-04":{"%":0.4},"UEM-05":{"%":0.9},"UEM-06":{"%":0.9},"UEM-07":{"%":0.9},"UEM-08":{"%":0.9},"UEM-09":{"%":0.4},"UEM-10":{"%":0.4},"UEM-11":{"%":0.9},"UEM-12":{"%":0.9},"UEM-13":{"%":0.9}}}},"%":0.224232754689411}},"CFB":{"v1.4.0":{"CIS Azure Benchmark v1.4.0":{"1":{"%":0.945454545454545456,"P":{"1.1":{"%":0.9,"P":{"null":{"%":0.9}}},"1.2":{"%":0.9,"P":{"null":{"%":0.9}}},"1.3":{"%":0.9,"P":{"null":{"%":0.9}}},"1.4":{"%":0.9,"P":{"null":{"%":0.9}}},"1.5":{"%":0.9,"P":{"null":{"%":0.9}}},"1.6":{"%":0.9,"P":{"null":{"%":0.9}}},"1.7":{"%":0.9,"P":{"null":{"%":0.9}}},"1.8":{"%":0.9,"P":{"null":{"%":0.9}}},"1.9":{"%":0.9,"P":{"null":{"%":0.9}}},"1.10":{"%":0.9,"P":{"null":{"%":0.9}}},"1.11":{"%":0.9,"P":{"null":{"%":0.9}}},"1.12":{"%":0.9,"P":{"null":{"%":0.9}}},"1.13":{"%":0.9,"P":{"null":{"%":0.9}}},"1.14":{"%":0.9,"P":{"null":{"%":0.9}}},"1.15":{"%":0.9,"P":{"null":{"%":0.9}}},"1.16":{"%":0.9,"P":{"null":{"%":0.9}}},"1.17":{"%":0.9,"P":{"null":{"%":0.9}}},"1.18":{"%":0.9,"P":{"null":{"%":0.9}}},"1.19":{"%":0.9,"P":{"null":{"%":0.9}}},"1.20":{"%":0.4,"P":{"null":{"%":0.4}}},"1.21":{"%":0.9,"P":{"null":{"%":0.9}}},"1.22":{"%":0.9,"P":{"null":{"%":0.9}}}}},"2":{"%":0.9333333333333333,"P":{"2.1":{"%":0.4,"P":{"null":{"%":0.4}}},"2.2":{"%":0.4,"P":{"null":{"%":0.4}}},"2.3":{"%":0.4,"P":{"null":{"%":0.4}}},"2.4":{"%":0.4,"P":{"null":{"%":0.4}}},"2.5":{"%":0.4,"P":{"null":{"%":0.4}}},"2.6":{"%":0.4,"P":{"null":{"%":0.4}}},"2.7":{"%":0.4,"P":{"null":{"%":0.4}}},"2.8":{"%":0.4,"P":{"null":{"%":0.4}}},"2.9":{"%":0.4,"P":{"null":{"%":0.4}}},"2.10":{"%":0.4,"P":{"null":{"%":0.4}}},"2.11":{"%":0.4,"P":{"null":{"%":0.4}}},"2.12":{"%":0.9,"P":{"null":{"%":0.9}}},"2.13":{"%":0.4,"P":{"null":{"%":0.4}}},"2.14":{"%":0.4,"P":{"null":{"%":0.4}}},"2.15":{"%":0.4,"P":{"null":{"%":0.4}}}}},"3":{"%":0.9166666666666666,"P":{"3.1":{"%":0.4,"P":{"null":{"%":0.4}}},"3.2":{"%":0.4,"P":{"null":{"%":0.4}}},"3.3":{"%":0.4,"P":{"null":{"%":0.4}}},"3.4":{"%":0.9,"P":{"null":{"%":0.9}}},"3.5":{"%":0.4,"P":{"null":{"%":0.4}}},"3.6":{"%":0.4,"P":{"null":{"%":0.4}}},"3.7":{"%":0.4,"P":{"null":{"%":0.4}}},"3.8":{"%":0.4,"P":{"null":{"%":0.4}}},"3.9":{"%":0.4,"P":{"null":{"%":0.4}}},"3.10":{"%":0.4,"P":{"null":{"%":0.4}}},"3.11":{"%":0.4,"P":{"null":{"%":0.4}}},"3.12":{"%":0.4,"P":{"null":{"%":0.4}}}}},"4":{"%":0.9166666666666666,"P":{"4.1":{"%":0.4,"P":{"4.1.1":{"%":0.4},"4.1.2":{"%":0.4},"4.1.3":{"%":0.4}}},"4.2":{"%":0.4,"P":{"4.2.1":{"%":0.4},"4.2.2":{"%":0.4},"4.2.3":{"%":0.4},"4.2.4":{"%":0.4},"4.2.5":{"%":0.4}}},"4.3":{"%":0.4,"P":{"4.3.1":{"%":0.4},"4.3.2":{"%":0.4},"4.3.3":{"%":0.4},"4.3.4":{"%":0.4},"4.3.5":{"%":0.4},"4.3.6":{"%":0.4},"4.3.7":{"%":0.4},"4.3.8":{"%":0.4}}},"4.4":{"%":0.5,"P":{"4.4.1":{"%":0.4},"4.4.2":{"%":0.9}}},"4.5":{"%":0.4,"P":{"4.4.2":{"%":0.4}}},"4.6":{"%":0.4,"P":{"4.4.2":{"%":0.4}}}}},"5":{"%":0.6,"P":{"5.1":{"%":0.8,"P":{"5.1.1":{"%":0.9},"5.1.2":{"%":0.4},"5.1.3":{"%":0.4},"5.1.4":{"%":0.4},"5.1.5":{"%":0.4}}},"5.2":{"%":0.4,"P":{"5.2.1":{"%":0.4},"5.2.2":{"%":0.4},"5.2.3":{"%":0.4},"5.2.4":{"%":0.4},"5.2.5":{"%":0.4},"5.2.6":{"%":0.4},"5.2.7":{"%":0.4},"5.2.8":{"%":0.4},"5.2.9":{"%":0.4}}},"5.3":{"%":0.9,"P":{"5.2.9":{"%":0.9}}}}},"6":{"%":0.8333333333333334,"P":{"6.1":{"%":0.4,"P":{"null":{"%":0.4}}},"6.2":{"%":0.4,"P":{"null":{"%":0.4}}},"6.3":{"%":0.4,"P":{"null":{"%":0.4}}},"6.4":{"%":0.9,"P":{"null":{"%":0.9}}},"6.5":{"%":0.4,"P":{"null":{"%":0.4}}},"6.6":{"%":0.4,"P":{"null":{"%":0.4}}}}},"7":{"%":0.7142857142857143,"P":{"7.1":{"%":0.4,"P":{"null":{"%":0.4}}},"7.2":{"%":0.4,"P":{"null":{"%":0.4}}},"7.3":{"%":0.4,"P":{"null":{"%":0.4}}},"7.4":{"%":0.9,"P":{"null":{"%":0.9}}},"7.5":{"%":0.9,"P":{"null":{"%":0.9}}},"7.6":{"%":0.4,"P":{"null":{"%":0.4}}},"7.7":{"%":0.4,"P":{"null":{"%":0.4}}}}},"8":{"%":0.5714285714285714,"P":{"8.1":{"%":0.9,"P":{"null":{"%":0.9}}},"8.2":{"%":0.4,"P":{"null":{"%":0.4}}},"8.3":{"%":0.9,"P":{"null":{"%":0.9}}},"8.4":{"%":0.4,"P":{"null":{"%":0.4}}},"8.5":{"%":0.9,"P":{"null":{"%":0.9}}},"8.6":{"%":0.4,"P":{"null":{"%":0.4}}},"8.7":{"%":0.4,"P":{"null":{"%":0.4}}}}},"9":{"%":0.4,"P":{"9.1":{"%":0.4,"P":{"null":{"%":0.4}}},"9.2":{"%":0.4,"P":{"null":{"%":0.4}}},"9.3":{"%":0.4,"P":{"null":{"%":0.4}}},"9.4":{"%":0.4,"P":{"null":{"%":0.4}}},"9.5":{"%":0.4,"P":{"null":{"%":0.4}}},"9.6":{"%":0.4,"P":{"null":{"%":0.4}}},"9.7":{"%":0.4,"P":{"null":{"%":0.4}}},"9.8":{"%":0.4,"P":{"null":{"%":0.4}}},"9.9":{"%":0.4,"P":{"null":{"%":0.4}}},"9.10":{"%":0.4,"P":{"null":{"%":0.4}}}}}},"%":0.7256854256854256},"v1.5.0":{"CIS Azure Benchmark v1.5.0":{"1":{"%":0.94,"P":{"1.1":{"%":0.9,"P":{"1.1.1":{"%":0.9},"1.1.2":{"%":0.9},"1.1.3":{"%":0.9},"1.1.4":{"%":0.9}}},"1.2":{"%":0.9,"P":{"1.2.1":{"%":0.9},"1.2.2":{"%":0.9},"1.2.3":{"%":0.9},"1.2.4":{"%":0.9},"1.2.5":{"%":0.9},"1.2.6":{"%":0.9}}},"1.3":{"%":0.9,"P":{"1.2.6":{"%":0.9}}},"1.4":{"%":0.9,"P":{"1.2.6":{"%":0.9}}},"1.5":{"%":0.9,"P":{"1.2.6":{"%":0.9}}},"1.6":{"%":0.9,"P":{"1.2.6":{"%":0.9}}},"1.7":{"%":0.9,"P":{"1.2.6":{"%":0.9}}},"1.8":{"%":0.9,"P":{"1.2.6":{"%":0.9}}},"1.9":{"%":0.9,"P":{"1.2.6":{"%":0.9}}},"1.10":{"%":0.9,"P":{"1.2.6":{"%":0.9}}},"1.11":{"%":0.9,"P":{"1.2.6":{"%":0.9}}},"1.12":{"%":0.9,"P":{"1.2.6":{"%":0.9}}},"1.13":{"%":0.9,"P":{"1.2.6":{"%":0.9}}},"1.14":{"%":0.9,"P":{"1.2.6":{"%":0.9}}},"1.15":{"%":0.9,"P":{"1.2.6":{"%":0.9}}},"1.16":{"%":0.9,"P":{"1.2.6":{"%":0.9}}},"1.17":{"%":0.9,"P":{"1.2.6":{"%":0.9}}},"1.18":{"%":0.9,"P":{"1.2.6":{"%":0.9}}},"1.19":{"%":0.9,"P":{"1.2.6":{"%":0.9}}},"1.20":{"%":0.9,"P":{"1.2.6":{"%":0.9}}},"1.21":{"%":0.9,"P":{"1.2.6":{"%":0.9}}},"1.22":{"%":0.9,"P":{"1.2.6":{"%":0.9}}},"1.23":{"%":0.4,"P":{"1.2.6":{"%":0.4}}},"1.24":{"%":0.9,"P":{"1.2.6":{"%":0.9}}},"1.25":{"%":0.9,"P":{"1.2.6":{"%":0.9}}}}},"2":{"%":0.5982905982905983,"P":{"2.1":{"%":0.9230769230769231,"P":{"2.1.1":{"%":0.4},"2.1.2":{"%":0.4},"2.1.3":{"%":0.4},"2.1.4":{"%":0.4},"2.1.5":{"%":0.4},"2.1.6":{"%":0.4},"2.1.7":{"%":0.4},"2.1.8":{"%":0.4},"2.1.9":{"%":0.4},"2.1.10":{"%":0.4},"2.1.11":{"%":0.4},"2.1.12":{"%":0.9},"2.1.13":{"%":0.4}}},"2.2":{"%":0.6666666666666666,"P":{"2.2.1":{"%":0.4},"2.2.2":{"%":0.4},"2.2.3":{"%":0.9}}},"2.3":{"%":0.4,"P":{"2.3.1":{"%":0.4},"2.3.2":{"%":0.4},"2.3.3":{"%":0.4}}},"2.4":{"%":0.4,"P":{"2.4.1":{"%":0.4},"2.4.2":{"%":0.4}}},"2.5":{"%":0.9,"P":{"2.4.2":{"%":0.9}}},"2.6":{"%":0.9,"P":{"2.4.2":{"%":0.9}}}}},"3":{"%":0.8666666666666667,"P":{"3.1":{"%":0.4,"P":{"null":{"%":0.4}}},"3.2":{"%":0.4,"P":{"null":{"%":0.4}}},"3.3":{"%":0.9,"P":{"null":{"%":0.9}}},"3.4":{"%":0.4,"P":{"null":{"%":0.4}}},"3.5":{"%":0.4,"P":{"null":{"%":0.4}}},"3.6":{"%":0.9,"P":{"null":{"%":0.9}}},"3.7":{"%":0.4,"P":{"null":{"%":0.4}}},"3.8":{"%":0.4,"P":{"null":{"%":0.4}}},"3.9":{"%":0.4,"P":{"null":{"%":0.4}}},"3.10":{"%":0.4,"P":{"null":{"%":0.4}}},"3.11":{"%":0.4,"P":{"null":{"%":0.4}}},"3.12":{"%":0.4,"P":{"null":{"%":0.4}}},"3.13":{"%":0.4,"P":{"null":{"%":0.4}}},"3.14":{"%":0.4,"P":{"null":{"%":0.4}}},"3.15":{"%":0.4,"P":{"null":{"%":0.4}}}}},"4":{"%":0.95,"P":{"4.1":{"%":0.4,"P":{"4.1.1":{"%":0.4},"4.1.2":{"%":0.4},"4.1.3":{"%":0.4},"4.1.4":{"%":0.4},"4.1.5":{"%":0.4},"4.1.6":{"%":0.4}}},"4.2":{"%":0.4,"P":{"4.2.1":{"%":0.4},"4.2.2":{"%":0.4},"4.2.3":{"%":0.4},"4.2.4":{"%":0.4},"4.2.5":{"%":0.4}}},"4.3":{"%":0.4,"P":{"4.3.1":{"%":0.4},"4.3.2":{"%":0.4},"4.3.3":{"%":0.4},"4.3.4":{"%":0.4},"4.3.5":{"%":0.4},"4.3.6":{"%":0.4},"4.3.7":{"%":0.4},"4.3.8":{"%":0.4}}},"4.4":{"%":0.75,"P":{"4.4.1":{"%":0.4},"4.4.2":{"%":0.9},"4.4.3":{"%":0.4},"4.4.4":{"%":0.4}}},"4.5":{"%":0.4,"P":{"4.5.1":{"%":0.4},"4.5.2":{"%":0.4}}}}},"5":{"%":0.5714285714285715,"P":{"5.1":{"%":0.7142857142857143,"P":{"5.1.1":{"%":0.9},"5.1.2":{"%":0.9},"5.1.3":{"%":0.4},"5.1.4":{"%":0.4},"5.1.5":{"%":0.4},"5.1.6":{"%":0.4},"5.1.7":{"%":0.4}}},"5.2":{"%":0.4,"P":{"5.2.1":{"%":0.4},"5.2.2":{"%":0.4},"5.2.3":{"%":0.4},"5.2.4":{"%":0.4},"5.2.5":{"%":0.4},"5.2.6":{"%":0.4},"5.2.7":{"%":0.4},"5.2.8":{"%":0.4},"5.2.9":{"%":0.4},"5.2.10":{"%":0.4}}},"5.3":{"%":0.9,"P":{"5.2.10":{"%":0.9}}}}},"6":{"%":0.7142857142857143,"P":{"6.1":{"%":0.4,"P":{"null":{"%":0.4}}},"6.2":{"%":0.4,"P":{"null":{"%":0.4}}},"6.3":{"%":0.4,"P":{"null":{"%":0.4}}},"6.4":{"%":0.4,"P":{"null":{"%":0.4}}},"6.5":{"%":0.9,"P":{"null":{"%":0.9}}},"6.6":{"%":0.4,"P":{"null":{"%":0.4}}},"6.7":{"%":0.9,"P":{"null":{"%":0.9}}}}},"7":{"%":0.8333333333333334,"P":{"7.1":{"%":0.4,"P":{"null":{"%":0.4}}},"7.2":{"%":0.4,"P":{"null":{"%":0.4}}},"7.3":{"%":0.4,"P":{"null":{"%":0.4}}},"7.4":{"%":0.9,"P":{"null":{"%":0.9}}},"7.5":{"%":0.4,"P":{"null":{"%":0.4}}},"7.6":{"%":0.4,"P":{"null":{"%":0.4}}}}},"8":{"%":0.625,"P":{"8.1":{"%":0.9,"P":{"null":{"%":0.9}}},"8.2":{"%":0.4,"P":{"null":{"%":0.4}}},"8.3":{"%":0.9,"P":{"null":{"%":0.9}}},"8.4":{"%":0.4,"P":{"null":{"%":0.4}}},"8.5":{"%":0.4,"P":{"null":{"%":0.4}}},"8.6":{"%":0.9,"P":{"null":{"%":0.9}}},"8.7":{"%":0.4,"P":{"null":{"%":0.4}}},"8.8":{"%":0.4,"P":{"null":{"%":0.4}}}}},"9":{"%":0.4,"P":{"9.1":{"%":0.4,"P":{"null":{"%":0.4}}},"9.2":{"%":0.4,"P":{"null":{"%":0.4}}},"9.3":{"%":0.4,"P":{"null":{"%":0.4}}},"9.4":{"%":0.4,"P":{"null":{"%":0.4}}},"9.5":{"%":0.4,"P":{"null":{"%":0.4}}},"9.6":{"%":0.4,"P":{"null":{"%":0.4}}},"9.7":{"%":0.4,"P":{"null":{"%":0.4}}},"9.8":{"%":0.4,"P":{"null":{"%":0.4}}},"9.9":{"%":0.4,"P":{"null":{"%":0.4}}},"9.10":{"%":0.4,"P":{"null":{"%":0.4}}},"9.11":{"%":0.4,"P":{"null":{"%":0.4}}}}}},"%":0.6887783204449872}},"Standard6":{"2016_679":{"Table 2 \u2014 Technical document coverage percentage":{"Article_5":{"%":0.16666666666666666,"P":{"Article_5.1":{"%":0.3333333333333333,"P":{"Article_5.1.a":{"%":0.9},"Article_5.1.b":{"%":0.9},"Article_5.1.c":{"%":0.9},"Article_5.1.d":{"%":0.9},"Article_5.1.e":{"%":0.4},"Article_5.1.f":{"%":0.4}}},"Article_5.2":{"%":0.9,"P":{"Article_5.1.f":{"%":0.9}}}}},"Article_15":{"%":0.9,"P":{"Article_15.1":{"%":0.9,"P":{"Article_15.1.a":{"%":0.9},"Article_15.1.b":{"%":0.9},"Article_15.1.c":{"%":0.9},"Article_15.1.d":{"%":0.9},"Article_15.1.e":{"%":0.9},"Article_15.1.f":{"%":0.9},"Article_15.1.g":{"%":0.9},"Article_15.1.h":{"%":0.9}}},"Article_15.2":{"%":0.9,"P":{"Article_15.1.h":{"%":0.9}}},"Article_15.3":{"%":0.9,"P":{"Article_15.1.h":{"%":0.9}}},"Article_15.4":{"%":0.9,"P":{"Article_15.1.h":{"%":0.9}}}}},"Article_25":{"%":0.4,"P":{"Article_25.1":{"%":0.4,"P":{"null":{"%":0.4}}},"Article_25.2":{"%":0.4,"P":{"null":{"%":0.4}}},"Article_25.3":{"%":0.4,"P":{"null":{"%":0.4}}}}},"Article_30":{"%":0.4,"P":{"Article_30.1":{"%":0.4,"P":{"Article_30.1.a":{"%":0.4},"Article_30.1.b":{"%":0.4},"Article_30.1.c":{"%":0.4},"Article_30.1.d":{"%":0.4},"Article_30.1.e":{"%":0.4},"Article_30.1.f":{"%":0.4},"Article_30.1.g":{"%":0.4}}},"Article_30.2":{"%":0.4,"P":{"Article_30.2.a":{"%":0.4},"Article_30.2.b":{"%":0.4},"Article_30.2.c":{"%":0.4},"Article_30.2.d":{"%":0.4}}},"Article_30.3":{"%":0.4,"P":{"Article_30.2.d":{"%":0.4}}},"Article_30.4":{"%":0.4,"P":{"Article_30.2.d":{"%":0.4}}},"Article_30.5":{"%":0.4,"P":{"Article_30.2.d":{"%":0.4}}}}},"Article_32":{"%":0.9375,"P":{"Article_32.1":{"%":0.75,"P":{"Article_32.1.a":{"%":0.4},"Article_32.1.b":{"%":0.4},"Article_32.1.c":{"%":0.4},"Article_32.1.d":{"%":0.9}}},"Article_32.3":{"%":0.4,"P":{"Article_32.1.d":{"%":0.4}}},"Article_32.4":{"%":0.4,"P":{"Article_32.1.d":{"%":0.4}}},"Article_32.5":{"%":0.4,"P":{"Article_32.1.d":{"%":0.4}}}}},"Article_44":{"%":0.4,"P":{"Article_44.1":{"%":0.4,"P":{"null":{"%":0.4}}},"Article_44.2":{"%":0.4,"P":{"Article_44.2.a":{"%":0.4},"Article_44.2.b":{"%":0.4},"Article_44.2.c":{"%":0.4}}},"Article_44.3":{"%":0.4,"P":{"Article_44.2.c":{"%":0.4}}},"Article_44.4":{"%":0.4,"P":{"Article_44.2.c":{"%":0.4}}},"Article_44.5":{"%":0.4,"P":{"Article_44.2.c":{"%":0.4}}},"Article_44.6":{"%":0.4,"P":{"Article_44.2.c":{"%":0.4}}},"Article_44.7":{"%":0.4,"P":{"Article_44.2.c":{"%":0.4}}},"Article_44.8":{"%":0.4,"P":{"Article_44.2.c":{"%":0.4}}},"Article_44.9":{"%":0.4,"P":{"Article_44.2.c":{"%":0.4}}}}},"Article_46":{"%":0.9,"P":{"Article_46.1":{"%":0.9,"P":{"null":{"%":0.9}}},"Article_46.2":{"%":0.9,"P":{"Article_46.2.a":{"%":0.9},"Article_46.2.b":{"%":0.9},"Article_46.2.c":{"%":0.9},"Article_46.2.d":{"%":0.9},"Article_46.2.e":{"%":0.9},"Article_46.2.f":{"%":0.9}}},"Article_46.3":{"%":0.9,"P":{"Article_46.3.a":{"%":0.9},"Article_46.3.b":{"%":0.9}}},"Article_46.4":{"%":0.9,"P":{"Article_46.3.b":{"%":0.9}}},"Article_46.5":{"%":0.9,"P":{"Article_46.3.b":{"%":0.9}}}}},"Article_47":{"%":0.9,"P":{"Article_47.1":{"%":0.9,"P":{"Article_47.1.a":{"%":0.9},"Article_47.1.b":{"%":0.9},"Article_47.1.c":{"%":0.9}}},"Article_47.2":{"%":0.9,"P":{"Article_47.2.a":{"%":0.9},"Article_47.2.b":{"%":0.9},"Article_47.2.c":{"%":0.9},"Article_47.2.d":{"%":0.9},"Article_47.2.e":{"%":0.9},"Article_47.2.f":{"%":0.9},"Article_47.2.g":{"%":0.9},"Article_47.2.h":{"%":0.9},"Article_47.2.i":{"%":0.9},"Article_47.2.j":{"%":0.9},"Article_47.2.k":{"%":0.9},"Article_47.2.l":{"%":0.9},"Article_47.2.m":{"%":0.9},"Article_47.2.n":{"%":0.9}}}}}},"%":0.5130208333333334}},"Standard3":{"Standard11_800_53_Rev5":{"Table 1 \u2014 Full (Technical) document coverage percentage":{"3.1 ACCESS CONTROL":{"%":0.3888888888888889,"P":{"AC-01":{"%":0.4,"P":{"null":{"%":0.4}}},"AC-02":{"%":0.4,"P":{"AC-02.01":{"%":0.4},"AC-02.02":{"%":0.4},"AC-02.03":{"%":0.4},"AC-02.04":{"%":0.4},"AC-02.05":{"%":0.4},"AC-02.07":{"%":0.4},"AC-02.09":{"%":0.4},"AC-02.11":{"%":0.4},"AC-02.12":{"%":0.4},"AC-02.13":{"%":0.4}}},"AC-03":{"%":0.4,"P":{"AC-02.13":{"%":0.4}}},"AC-04":{"%":0.9,"P":{"AC-02.13":{"%":0.9}}},"AC-05":{"%":0.4,"P":{"AC-02.13":{"%":0.4}}},"AC-06":{"%":0.4,"P":{"AC-06.01":{"%":0.4},"AC-06.02":{"%":0.4},"AC-06.03":{"%":0.4},"AC-06.05":{"%":0.4},"AC-06.07":{"%":0.4},"AC-06.08":{"%":0.4},"AC-06.09":{"%":0.4},"AC-06.10":{"%":0.4}}},"AC-07":{"%":0.9,"P":{"AC-06.10":{"%":0.9}}},"AC-08":{"%":0.9,"P":{"AC-06.10":{"%":0.9}}},"AC-10":{"%":0.9,"P":{"AC-06.10":{"%":0.9}}},"AC-11":{"%":0.9,"P":{"AC-06.10":{"%":0.9}}},"AC-12":{"%":0.9,"P":{"AC-06.10":{"%":0.9}}},"AC-14":{"%":0.9,"P":{"AC-06.10":{"%":0.9}}},"AC-17":{"%":0.4,"P":{"AC-17.01":{"%":0.4},"AC-17.02":{"%":0.4},"AC-17.03":{"%":0.4},"AC-17.04":{"%":0.4}}},"AC-18":{"%":0.4,"P":{"AC-18.01":{"%":0.4},"AC-18.03":{"%":0.4},"AC-18.04":{"%":0.4},"AC-18.05":{"%":0.4}}},"AC-19":{"%":0.9,"P":{"AC-18.05":{"%":0.9}}},"AC-20":{"%":0.9,"P":{"AC-20.91":{"%":0.9},"AC-20.92":{"%":0.9}}},"AC-21":{"%":0.9,"P":{"AC-20.92":{"%":0.9}}},"AC-22":{"%":0.9,"P":{"AC-20.92":{"%":0.9}}}}},"3.2 AWARENESS AND TRAINING":{"%":0.9,"P":{"AT-01":{"%":0.9,"P":{"null":{"%":0.9}}},"AT-02":{"%":0.9,"P":{"AT-02.02":{"%":0.9},"AT-02.03":{"%":0.9}}},"AT-03":{"%":0.9,"P":{"AT-02.03":{"%":0.9}}},"AT-04":{"%":0.9,"P":{"AT-02.03":{"%":0.9}}}}},"3.3 AUDIT AND ACCOUNTABILITY":{"%":0.52177778,"P":{"AU-01":{"%":0.9,"P":{"null":{"%":0.9}}},"AU-02":{"%":0.4,"P":{"null":{"%":0.4}}},"AU-03":{"%":0.4,"P":{"null":{"%":0.4}}},"AU-04":{"%":0.9,"P":{"null":{"%":0.9}}},"AU-05":{"%":0.9,"P":{"AU-05.01":{"%":0.9},"AU-05.02":{"%":0.9}}},"AU-06":{"%":0.4,"P":{"AU-06.01":{"%":0.4},"AU-06.03":{"%":0.4},"AU-06.04":{"%":0.4},"AU-06.05":{"%":0.4},"AU-06.06":{"%":0.4},"AU-06.07":{"%":0.4}}},"AU-07":{"%":0.4,"P":{"AU-06.07":{"%":0.4}}},"AU-08":{"%":0.9,"P":{"AU-06.07":{"%":0.9}}},"AU-09":{"%":0.3333333333333333,"P":{"AU-09.02":{"%":0.9},"AU-09.03":{"%":0.9},"AU-09.04":{"%":0.4}}},"AU-10":{"%":0.9,"P":{"AU-09.04":{"%":0.9}}},"AU-11":{"%":0.4,"P":{"AU-09.04":{"%":0.4}}},"AU-12":{"%":0.4,"P":{"AU-12.01":{"%":0.4},"AU-12.03":{"%":0.4}}}}},"3.4 ASSESSMENT, AUTHORIZATION, AND MONITORING":{"%":0.125,"P":{"CA-01":{"%":0.9,"P":{"null":{"%":0.9}}},"CA-02":{"%":0.9,"P":{"CA-02.01":{"%":0.9},"CA-02.02":{"%":0.9}}},"CA-03":{"%":0.9,"P":{"CA-03.06":{"%":0.9},"CA-03.07":{"%":0.9}}},"CA-05":{"%":0.9,"P":{"CA-03.07":{"%":0.9}}},"CA-06":{"%":0.9,"P":{"CA-03.07":{"%":0.9}}},"CA-07":{"%":0.9,"P":{"CA-00.41":{"%":0.9},"CA-00.44":{"%":0.9}}},"CA-08":{"%":0.9,"P":{"CA-00.44":{"%":0.9}}},"CA-09":{"%":0.4,"P":{"CA-00.44":{"%":0.4}}}}},"3.5 CONFIGURATION MANAGEMENT":{"%":0.6666666666666666,"P":{"CM-01":{"%":0.4,"P":{"null":{"%":0.4}}},"CM-02":{"%":0.4,"P":{"CM-02.02":{"%":0.4},"CM-02.03":{"%":0.4},"CM-02.07":{"%":0.4}}},"CM-03":{"%":0.9,"P":{"CM-03.01":{"%":0.9},"CM-03.02":{"%":0.9},"CM-03.04":{"%":0.9},"CM-03.06":{"%":0.9}}},"CM-04":{"%":0.9,"P":{"CM-04.01":{"%":0.9},"CM-04.02":{"%":0.9}}},"CM-05":{"%":0.4,"P":{"CM-05.01":{"%":0.4},"CM-05.05":{"%":0.4}}},"CM-06":{"%":0.4,"P":{"CM-06.01":{"%":0.4},"CM-06.02":{"%":0.4}}},"CM-07":{"%":0.4,"P":{"CM-07.01":{"%":0.4},"CM-07.02":{"%":0.4},"CM-07.05":{"%":0.4}}},"CM-08":{"%":0.4,"P":{"CM-08.01":{"%":0.4},"CM-08.02":{"%":0.4},"CM-08.03":{"%":0.4},"CM-08.04":{"%":0.4}}},"CM-09":{"%":0.4,"P":{"CM-08.04":{"%":0.4}}},"CM-10":{"%":0.9,"P":{"CM-08.04":{"%":0.9}}},"CM-11":{"%":0.9,"P":{"CM-08.04":{"%":0.9}}},"CM-12":{"%":0.4,"P":{"CM-08.04":{"%":0.4}}}}},"3.6 CONTINGENCY PLANNING":{"%":0.5555555555555556,"P":{"CP-01":{"%":0.9,"P":{"null":{"%":0.9}}},"CP-02":{"%":0.4,"P":{"CP-02.01":{"%":0.4},"CP-02.02":{"%":0.4},"CP-02.03":{"%":0.4},"CP-02.05":{"%":0.4},"CP-02.08":{"%":0.4}}},"CP-03":{"%":0.9,"P":{"CP-02.08":{"%":0.9}}},"CP-04":{"%":0.9,"P":{"CP-04.01":{"%":0.9},"CP-04.02":{"%":0.9}}},"CP-06":{"%":0.4,"P":{"CP-06.01":{"%":0.4},"CP-06.02":{"%":0.4},"CP-06.03":{"%":0.4}}},"CP-07":{"%":0.4,"P":{"CP-07.01":{"%":0.4},"CP-07.02":{"%":0.4},"CP-07.03":{"%":0.4},"CP-07.04":{"%":0.4}}},"CP-08":{"%":0.9,"P":{"CP-08.01":{"%":0.9},"CP-08.02":{"%":0.9},"CP-08.03":{"%":0.9},"CP-08.04":{"%":0.9}}},"CP-09":{"%":0.4,"P":{"CP-09.01":{"%":0.4},"CP-09.02":{"%":0.4},"CP-09.03":{"%":0.4},"CP-09.05":{"%":0.4},"CP-09.08":{"%":0.4}}},"CP-10":{"%":0.4,"P":{"CP-10.92":{"%":0.4},"CP-10.94":{"%":0.4}}}}},"3.7 IDENTIFICATION AND AUTHENTICATION":{"%":0.2,"P":{"IA-01":{"%":0.9,"P":{"null":{"%":0.9}}},"IA-02":{"%":0.9,"P":{"IA-02.01":{"%":0.9},"IA-02.02":{"%":0.9},"IA-02.05":{"%":0.9},"IA-02.06":{"%":0.9},"IA-02.08":{"%":0.9},"IA-02.12":{"%":0.9}}},"IA-03":{"%":0.9,"P":{"IA-02.12":{"%":0.9}}},"IA-04":{"%":0.4,"P":{"IA-02.12":{"%":0.4}}},"IA-05":{"%":0.4,"P":{"IA-05.01":{"%":0.4},"IA-05.02":{"%":0.4},"IA-05.06":{"%":0.4},"IA-05.07":{"%":0.4},"IA-05.08":{"%":0.4},"IA-05.13":{"%":0.4}}},"IA-06":{"%":0.9,"P":{"IA-05.13":{"%":0.9}}},"IA-07":{"%":0.9,"P":{"IA-05.13":{"%":0.9}}},"IA-08":{"%":0.9,"P":{"IA-08.01":{"%":0.9},"IA-08.02":{"%":0.9},"IA-08.04":{"%":0.9}}},"IA-11":{"%":0.9,"P":{"IA-08.04":{"%":0.9}}},"IA-12":{"%":0.9,"P":{"IA-12.02":{"%":0.9},"IA-12.03":{"%":0.9},"IA-12.04":{"%":0.9},"IA-12.05":{"%":0.9}}}}},"3.8 INCIDENT RESPONSE":{"%":0.5,"P":{"IR-01":{"%":0.4,"P":{"null":{"%":0.4}}},"IR-02":{"%":0.9,"P":{"IR-02.01":{"%":0.9},"IR-02.02":{"%":0.9}}},"IR-03":{"%":0.9,"P":{"IR-02.02":{"%":0.9}}},"IR-04":{"%":0.9,"P":{"IR-04.01":{"%":0.9},"IR-04.02":{"%":0.9},"IR-04.04":{"%":0.9},"IR-04.06":{"%":0.9},"IR-04.11":{"%":0.9}}},"IR-05":{"%":0.9,"P":{"IR-04.11":{"%":0.9}}},"IR-06":{"%":0.4,"P":{"IR-06.01":{"%":0.4},"IR-06.03":{"%":0.4}}},"IR-07":{"%":0.4,"P":{"IR-06.03":{"%":0.4}}},"IR-08":{"%":0.4,"P":{"IR-06.03":{"%":0.4}}}}},"3.9 MAINTENANCE":{"%":0.16666666666666666,"P":{"MA-01":{"%":0.9,"P":{"null":{"%":0.9}}},"MA-02":{"%":0.9,"P":{"null":{"%":0.9}}},"MA-03":{"%":0.9,"P":{"MA-03.01":{"%":0.9},"MA-03.02":{"%":0.9},"MA-03.03":{"%":0.9}}},"MA-04":{"%":0.4,"P":{"MA-03.03":{"%":0.4}}},"MA-05":{"%":0.9,"P":{"MA-03.03":{"%":0.9}}},"MA-06":{"%":0.9,"P":{"MA-03.03":{"%":0.9}}}}},"3.10 MEDIA PROTECTION":{"%":0.14285714285714285,"P":{"MP-01":{"%":0.9,"P":{"null":{"%":0.9}}},"MP-02":{"%":0.4,"P":{"null":{"%":0.4}}},"MP-03":{"%":0.9,"P":{"null":{"%":0.9}}},"MP-04":{"%":0.9,"P":{"null":{"%":0.9}}},"MP-05":{"%":0.9,"P":{"null":{"%":0.9}}},"MP-06":{"%":0.9,"P":{"MP-06.01":{"%":0.9},"MP-06.02":{"%":0.9},"MP-06.03":{"%":0.9}}},"MP-07":{"%":0.9,"P":{"MP-06.03":{"%":0.9}}}}},"3.11 PHYSICAL AND ENVIRONMENTAL PROTECTION":{"%":0.9,"P":{"PE-01":{"%":0.9,"P":{"null":{"%":0.9}}},"PE-02":{"%":0.9,"P":{"null":{"%":0.9}}},"PE-03":{"%":0.9,"P":{"null":{"%":0.9}}},"PE-04":{"%":0.9,"P":{"null":{"%":0.9}}},"PE-05":{"%":0.9,"P":{"null":{"%":0.9}}},"PE-06":{"%":0.9,"P":{"PE-06.01":{"%":0.9},"PE-06.04":{"%":0.9}}},"PE-08":{"%":0.9,"P":{"PE-06.04":{"%":0.9}}},"PE-09":{"%":0.9,"P":{"PE-06.04":{"%":0.9}}},"PE-10":{"%":0.9,"P":{"PE-06.04":{"%":0.9}}},"PE-11":{"%":0.9,"P":{"PE-06.04":{"%":0.9}}},"PE-12":{"%":0.9,"P":{"PE-06.04":{"%":0.9}}},"PE-13":{"%":0.9,"P":{"PE-13.01":{"%":0.9},"PE-13.02":{"%":0.9}}},"PE-14":{"%":0.9,"P":{"PE-13.02":{"%":0.9}}},"PE-15":{"%":0.9,"P":{"PE-13.02":{"%":0.9}}},"PE-16":{"%":0.9,"P":{"PE-13.02":{"%":0.9}}},"PE-17":{"%":0.9,"P":{"PE-13.02":{"%":0.9}}},"PE-18":{"%":0.9,"P":{"PE-13.02":{"%":0.9}}}}},"3.12 PLANNING":{"%":0.16666666666666666,"P":{"PL-01":{"%":0.9,"P":{"null":{"%":0.9}}},"PL-02":{"%":0.9,"P":{"null":{"%":0.9}}},"PL-04":{"%":0.9,"P":{"null":{"%":0.9}}},"PL-08":{"%":0.4,"P":{"null":{"%":0.4}}},"PL-10":{"%":0.9,"P":{"null":{"%":0.9}}},"PL-11":{"%":0.9,"P":{"null":{"%":0.9}}}}},"3.14 PERSONNEL SECURITY":{"%":0.9,"P":{"PS-01":{"%":0.9,"P":{"null":{"%":0.9}}},"PS-02":{"%":0.9,"P":{"null":{"%":0.9}}},"PS-03":{"%":0.9,"P":{"null":{"%":0.9}}},"PS-04":{"%":0.9,"P":{"null":{"%":0.9}}},"PS-05":{"%":0.9,"P":{"null":{"%":0.9}}},"PS-06":{"%":0.9,"P":{"null":{"%":0.9}}},"PS-07":{"%":0.9,"P":{"null":{"%":0.9}}},"PS-08":{"%":0.9,"P":{"null":{"%":0.9}}},"PS-09":{"%":0.9,"P":{"null":{"%":0.9}}}}},"3.16 RISK ASSESSMENT":{"%":0.5,"P":{"RA-01":{"%":0.9,"P":{"null":{"%":0.9}}},"RA-02":{"%":0.4,"P":{"null":{"%":0.4}}},"RA-03":{"%":0.9,"P":{"null":{"%":0.9}}},"RA-05":{"%":0.4,"P":{"RA-05.02":{"%":0.4},"RA-05.03":{"%":0.4},"RA-05.04":{"%":0.4},"RA-05.05":{"%":0.4},"RA-05.11":{"%":0.4}}},"RA-07":{"%":0.4,"P":{"RA-05.11":{"%":0.4}}},"RA-09":{"%":0.9,"P":{"RA-05.11":{"%":0.9}}}}},"3.17 SYSTEM AND SERVICES ACQUISITION":{"%":0.21428571428571427,"P":{"SA-01":{"%":0.9,"P":{"null":{"%":0.9}}},"SA-02":{"%":0.9,"P":{"null":{"%":0.9}}},"SA-03":{"%":0.4,"P":{"null":{"%":0.4}}},"SA-04":{"%":0.9,"P":{"SA-04.01":{"%":0.9},"SA-04.02":{"%":0.9},"SA-04.05":{"%":0.9},"SA-04.09":{"%":0.9},"SA-04.10":{"%":0.9}}},"SA-05":{"%":0.9,"P":{"SA-04.10":{"%":0.9}}},"SA-08":{"%":0.4,"P":{"SA-04.10":{"%":0.4}}},"SA-09":{"%":0.9,"P":{"SA-09.02":{"%":0.9},"SA-09.05":{"%":0.9}}},"SA-10":{"%":0.4,"P":{"SA-09.05":{"%":0.4}}},"SA-11":{"%":0.9,"P":{"SA-09.05":{"%":0.9}}},"SA-15":{"%":0.9,"P":{"SA-09.05":{"%":0.9}}},"SA-16":{"%":0.9,"P":{"SA-09.05":{"%":0.9}}},"SA-17":{"%":0.9,"P":{"SA-09.05":{"%":0.9}}},"SA-21":{"%":0.9,"P":{"SA-09.05":{"%":0.9}}},"SA-22":{"%":0.9,"P":{"SA-09.05":{"%":0.9}}}}},"3.18 SYSTEM AND COMMUNICATIONS PROTECTION":{"%":0.19047619047619047,"P":{"SC-01":{"%":0.9,"P":{"null":{"%":0.9}}},"SC-02":{"%":0.9,"P":{"null":{"%":0.9}}},"SC-03":{"%":0.9,"P":{"null":{"%":0.9}}},"SC-04":{"%":0.9,"P":{"null":{"%":0.9}}},"SC-05":{"%":0.9,"P":{"null":{"%":0.9}}},"SC-06":{"%":0.9,"P":{"null":{"%":0.9}}},"SC-07":{"%":0.4,"P":{"SC-07.03":{"%":0.4},"SC-07.04":{"%":0.4},"SC-07.05":{"%":0.4},"SC-07.07":{"%":0.4},"SC-07.08":{"%":0.4},"SC-07.12":{"%":0.4},"SC-07.18":{"%":0.4},"SC-07.20":{"%":0.4},"SC-07.21":{"%":0.4}}},"SC-08":{"%":0.4,"P":{"SC-07.21":{"%":0.4}}},"SC-10":{"%":0.9,"P":{"SC-07.21":{"%":0.9}}},"SC-12":{"%":0.9,"P":{"SC-07.21":{"%":0.9}}},"SC-13":{"%":0.9,"P":{"SC-07.21":{"%":0.9}}},"SC-15":{"%":0.9,"P":{"SC-07.21":{"%":0.9}}},"SC-17":{"%":0.9,"P":{"SC-07.21":{"%":0.9}}},"SC-18":{"%":0.9,"P":{"SC-07.21":{"%":0.9}}},"SC-20":{"%":0.9,"P":{"SC-07.21":{"%":0.9}}},"SC-21":{"%":0.9,"P":{"SC-07.21":{"%":0.9}}},"SC-22":{"%":0.9,"P":{"SC-07.21":{"%":0.9}}},"SC-23":{"%":0.4,"P":{"SC-07.21":{"%":0.4}}},"SC-24":{"%":0.9,"P":{"SC-07.21":{"%":0.9}}},"SC-28":{"%":0.4,"P":{"SC-07.21":{"%":0.4}}},"SC-39":{"%":0.9,"P":{"SC-07.21":{"%":0.9}}}}},"3.19 SYSTEM AND INFORMATION INTEGRITY":{"%":0.25,"P":{"SI-01":{"%":0.9,"P":{"null":{"%":0.9}}},"SI-02":{"%":0.9,"P":{"SI-02.02":{"%":0.9},"SI-02.03":{"%":0.9}}},"SI-03":{"%":0.4,"P":{"SI-02.03":{"%":0.4}}},"SI-04":{"%":0.4,"P":{"SI-04.01":{"%":0.4},"SI-04.02":{"%":0.4},"SI-04.04":{"%":0.4},"SI-04.05":{"%":0.4},"SI-04.10":{"%":0.4},"SI-04.11":{"%":0.4},"SI-04.12":{"%":0.4},"SI-04.14":{"%":0.4},"SI-04.16":{"%":0.4},"SI-04.19":{"%":0.4},"SI-04.20":{"%":0.4},"SI-04.22":{"%":0.4},"SI-04.23":{"%":0.4}}},"SI-05":{"%":0.9,"P":{"SI-04.23":{"%":0.9}}},"SI-06":{"%":0.9,"P":{"SI-04.23":{"%":0.9}}},"SI-07":{"%":0.9,"P":{"SI-07.01":{"%":0.9},"SI-07.02":{"%":0.9},"SI-07.05":{"%":0.9},"SI-07.07":{"%":0.9},"SI-07.15":{"%":0.9}}},"SI-08":{"%":0.9,"P":{"SI-07.15":{"%":0.9}}},"SI-10":{"%":0.9,"P":{"SI-07.15":{"%":0.9}}},"SI-11":{"%":0.9,"P":{"SI-07.15":{"%":0.9}}},"SI-12":{"%":0.4,"P":{"SI-07.15":{"%":0.4}}},"SI-16":{"%":0.9,"P":{"SI-07.15":{"%":0.9}}}}},"3.20 SUPPLY CHAIN RISK MANAGEMENT":{"%":0.9,"P":{"SR-01":{"%":0.9,"P":{"null":{"%":0.9}}},"SR-02":{"%":0.9,"P":{"null":{"%":0.9}}},"SR-03":{"%":0.9,"P":{"null":{"%":0.9}}},"SR-05":{"%":0.9,"P":{"null":{"%":0.9}}},"SR-06":{"%":0.9,"P":{"null":{"%":0.9}}},"SR-08":{"%":0.9,"P":{"null":{"%":0.9}}},"SR-09":{"%":0.9,"P":{"null":{"%":0.9}}},"SR-10":{"%":0.9,"P":{"null":{"%":0.9}}},"SR-11":{"%":0.9,"P":{"SR-10.41":{"%":0.9},"SR-10.42":{"%":0.9}}}}}},"%":0.2552689594356261}},"DA":{"null":{"Article":{"Article_6":{"%":0.9,"P":{"Article_6.1":{"%":0.9,"P":{"Article_6.1.a":{"%":0.9},"Article_6.1.b":{"%":0.9},"Article_6.1.c":{"%":0.9},"Article_6.1.d":{"%":0.9}}},"Article_6.2":{"%":0.9,"P":{"Article_6.1.d":{"%":0.9}}}}},"Article_7":{"%":0.42857142857142855,"P":{"Article_7.1":{"%":0.4,"P":{"null":{"%":0.4}}},"Article_7.2":{"%":0.4,"P":{"null":{"%":0.4}}},"Article_7.3":{"%":0.9,"P":{"null":{"%":0.9}}},"Article_7.4":{"%":0.4,"P":{"null":{"%":0.4}}},"Article_7.5":{"%":0.9,"P":{"null":{"%":0.9}}},"Article_7.6":{"%":0.9,"P":{"null":{"%":0.9}}},"Article_7.7":{"%":0.9,"P":{"null":{"%":0.9}}}}},"Article_8":{"%":0.8958333333333334,"P":{"Article_8.1":{"%":0.4,"P":{"null":{"%":0.4}}},"Article_8.2":{"%":0.4,"P":{"null":{"%":0.4}}},"Article_8.3":{"%":0.75,"P":{"Article_8.3.a":{"%":0.4},"Article_8.3.b":{"%":0.4},"Article_8.3.c":{"%":0.9},"Article_8.3.d":{"%":0.4}}},"Article_8.4":{"%":0.8333333333333334,"P":{"Article_8.4.a":{"%":0.9},"Article_8.4.b":{"%":0.4},"Article_8.4.c":{"%":0.4},"Article_8.4.d":{"%":0.4},"Article_8.4.e":{"%":0.4},"Article_8.4.f":{"%":0.4}}}}},"Article_9":{"%":0.5,"P":{"Article_9.1":{"%":0.4,"P":{"null":{"%":0.4}}},"Article_9.2":{"%":0.4,"P":{"null":{"%":0.4}}},"Article_9.3":{"%":0.9,"P":{"null":{"%":0.9}}},"Article_9.4":{"%":0.9,"P":{"null":{"%":0.9}}}}},"Article_10":{"%":0.9,"P":{"Article_10.1":{"%":0.9,"P":{"null":{"%":0.9}}},"Article_10.2":{"%":0.9,"P":{"Article_10.2.a":{"%":0.9},"Article_10.2.b":{"%":0.9},"Article_10.2.c":{"%":0.9},"Article_10.2.d":{"%":0.9},"Article_10.2.e":{"%":0.9},"Article_10.2.f":{"%":0.9}}},"Article_10.3":{"%":0.9,"P":{"Article_10.2.f":{"%":0.9}}},"Article_10.4":{"%":0.9,"P":{"Article_10.2.f":{"%":0.9}}},"Article_13":{"%":0.9,"P":{"Article_13.a":{"%":0.9},"Article_13.b":{"%":0.9}}},"Article_10.6":{"%":0.9,"P":{"Article_13.b":{"%":0.9}}},"Article_10.7":{"%":0.9,"P":{"Article_13.b":{"%":0.9}}},"Article_10.8":{"%":0.9,"P":{"Article_13.b":{"%":0.9}}},"Article_10.9":{"%":0.9,"P":{"Article_13.b":{"%":0.9}}}}},"Article_11":{"%":0.23809523809523808,"P":{"Article_11.1":{"%":0.4,"P":{"Article_11.1.a":{"%":0.4},"Article_11.1.b":{"%":0.4}}},"Article_11.2":{"%":0.9,"P":{"Article_11.1.b":{"%":0.9}}},"Article_11.3":{"%":0.9,"P":{"Article_11.1.b":{"%":0.9}}},"Article_11.4":{"%":0.9,"P":{"Article_11.1.b":{"%":0.9}}},"Article_11.5":{"%":0.6666666666666666,"P":{"Article_11.5.a":{"%":0.4},"Article_11.5.b":{"%":0.4},"Article_11.5.d":{"%":0.9}}},"Article_11.6":{"%":0.9,"P":{"Article_11.5.d":{"%":0.9}}},"Article_11.7":{"%":0.9,"P":{"Article_11.5.d":{"%":0.9}}}}},"Article_14":{"%":0.125,"P":{"Article_14.a":{"%":0.9,"P":{"null":{"%":0.9}}},"Article_14.b":{"%":0.9,"P":{"null":{"%":0.9}}},"Article_14.c":{"%":0.9,"P":{"null":{"%":0.9}}},"Article_14.d":{"%":0.4,"P":{"null":{"%":0.4}}},"Article_14.e":{"%":0.9,"P":{"null":{"%":0.9}}},"Article_14.f":{"%":0.9,"P":{"null":{"%":0.9}}},"Article_14.g":{"%":0.9,"P":{"null":{"%":0.9}}},"Article_14.h":{"%":0.9,"P":{"null":{"%":0.9}}}}},"Article_15":{"%":0.4166666666666667,"P":{"Article_15.1":{"%":0.4,"P":{"null":{"%":0.4}}},"Article_15.2":{"%":0.9,"P":{"null":{"%":0.9}}},"Article_15.3":{"%":0.25,"P":{"Article_15.3.a":{"%":0.9},"Article_15.3.b":{"%":0.9},"Article_15.3.d":{"%":0.4},"Article_15.3.e":{"%":0.9}}}}},"Article_16":{"%":0.9,"P":{"Article_16.1":{"%":0.9,"P":{"Article_16.1.a":{"%":0.9},"Article_16.1.b":{"%":0.9},"Article_16.1.c":{"%":0.9},"Article_16.1.d":{"%":0.9},"Article_16.1.e":{"%":0.9},"Article_16.1.f":{"%":0.9},"Article_16.1.g":{"%":0.9}}}}},"Article_22":{"%":0.9,"P":{"Article_22.1":{"%":0.9,"P":{"null":{"%":0.9}}},"Article_22.2":{"%":0.9,"P":{"null":{"%":0.9}}}}},"Article_23":{"%":0.9,"P":{"Article_23.1":{"%":0.9,"P":{"null":{"%":0.9}}},"Article_23.2":{"%":0.9,"P":{"null":{"%":0.9}}},"Article_23.3":{"%":0.9,"P":{"Article_23.3.a":{"%":0.9},"Article_23.3.b":{"%":0.9},"Article_23.3.c":{"%":0.9}}},"Article_23.4":{"%":0.9,"P":{"Article_23.4.a":{"%":0.9},"Article_23.4.b":{"%":0.9},"Article_23.4.c":{"%":0.9}}}}},"Article_30":{"%":0.9,"P":{"Article_30.1":{"%":0.9,"P":{"null":{"%":0.9}}},"Article_30.2":{"%":0.9,"P":{"Article_30.2.a":{"%":0.9},"Article_30.2.b":{"%":0.9},"Article_30.2.c":{"%":0.9},"Article_30.2.d":{"%":0.9},"Article_30.2.e":{"%":0.9},"Article_30.2.f":{"%":0.9},"Article_30.2.g":{"%":0.9},"Article_30.2.h":{"%":0.9},"Article_30.2.i":{"%":0.9}}},"Article_30.3":{"%":0.9,"P":{"Article_30.2.i":{"%":0.9}}}}}},"%":0.21701388888888887}},"Standard14":{"27001_2013":{"27001_2013 Article (Technical)":{"A.5":{"%":0.9,"P":{"A.5.1":{"%":0.9,"P":{"A.5.1.1":{"%":0.9},"A.5.1.2":{"%":0.9}}}}},"A.6":{"%":0.5,"P":{"A.6.1":{"%":0.5,"P":{"A.6.1.1":{"%":0.9},"A.6.1.2":{"%":0.4}}},"A.6.2":{"%":0.5,"P":{"A.6.2.1":{"%":0.9},"A.6.2.2":{"%":0.4}}}}},"A.8":{"%":0.2222222222222222,"P":{"A.8.1":{"%":0.6666666666666666,"P":{"A.8.1.1":{"%":0.4},"A.8.1.2":{"%":0.9},"A.8.1.3":{"%":0.4}}},"A.8.2":{"%":0.9,"P":{"A.8.2.1":{"%":0.9},"A.8.2.2":{"%":0.9},"A.8.2.3":{"%":0.9}}},"A.8.3":{"%":0.9,"P":{"A.8.3.1":{"%":0.9},"A.8.3.2":{"%":0.9},"A.8.3.3":{"%":0.9}}}}},"A.9":{"%":0.3416666666666666,"P":{"A.9.1":{"%":0.5,"P":{"A.9.1.1":{"%":0.4},"A.9.1.2":{"%":0.9}}},"A.9.2":{"%":0.6666666666666666,"P":{"A.9.2.1":{"%":0.4},"A.9.2.2":{"%":0.4},"A.9.2.3":{"%":0.4},"A.9.2.4":{"%":0.9},"A.9.2.5":{"%":0.9},"A.9.2.6":{"%":0.4}}},"A.9.3":{"%":0.9,"P":{"A.9.3.1":{"%":0.9}}},"A.9.4":{"%":0.2,"P":{"A.9.4.1":{"%":0.4},"A.9.4.2":{"%":0.9},"A.9.4.3":{"%":0.9},"A.9.4.4":{"%":0.9},"A.9.4.5":{"%":0.9}}}}},"A.10":{"%":0.5,"P":{"A.10.1":{"%":0.5,"P":{"A.10.1.1":{"%":0.4},"A.10.1.2":{"%":0.9}}}}},"A.12":{"%":0.5714285714285714,"P":{"A.12.1":{"%":0.9,"P":{"A.12.1.3":{"%":0.9},"A.12.1.4":{"%":0.9}}},"A.12.2":{"%":0.4,"P":{"A.12.2.1":{"%":0.4}}},"A.12.3":{"%":0.4,"P":{"A.12.3.1":{"%":0.4}}},"A.12.4":{"%":0.5,"P":{"A.12.4.1":{"%":0.4},"A.12.4.2":{"%":0.4},"A.12.4.3":{"%":0.9},"A.12.4.4":{"%":0.9}}},"A.12.5":{"%":0.4,"P":{"A.12.5.1":{"%":0.4}}},"A.12.6":{"%":0.5,"P":{"A.12.6.1":{"%":0.4},"A.12.6.2":{"%":0.9}}},"A.12.7":{"%":0.9,"P":{"A.12.7.1":{"%":0.9}}}}},"A.13":{"%":0.5,"P":{"A.13.1":{"%":0.6666666666666666,"P":{"A.13.1.1":{"%":0.4},"A.13.1.2":{"%":0.4},"A.13.1.3":{"%":0.9}}},"A.13.2":{"%":0.3333333333333333,"P":{"A.13.2.1":{"%":0.4},"A.13.2.3":{"%":0.9},"A.13.2.4":{"%":0.9}}}}},"A.14":{"%":0.937037037037037035,"P":{"A.14.1":{"%":0.9,"P":{"A.14.1.1":{"%":0.9},"A.14.1.2":{"%":0.9},"A.14.1.3":{"%":0.9}}},"A.14.2":{"%":0.1111111111111111,"P":{"A.14.2.1":{"%":0.9},"A.14.2.2":{"%":0.9},"A.14.2.3":{"%":0.9},"A.14.2.4":{"%":0.9},"A.14.2.5":{"%":0.4},"A.14.2.6":{"%":0.9},"A.14.2.7":{"%":0.9},"A.14.2.8":{"%":0.9},"A.14.2.9":{"%":0.9}}},"A.14.3":{"%":0.9,"P":{"A.14.3.1":{"%":0.9}}}}},"A.15":{"%":0.9,"P":{"A.15.1":{"%":0.9,"P":{"A.15.1.1":{"%":0.9},"A.15.1.3":{"%":0.9}}},"A.15.2":{"%":0.9,"P":{"A.15.2.1":{"%":0.9},"A.15.2.2":{"%":0.9}}}}},"A.16":{"%":0.3333333333333333,"P":{"A.16.1":{"%":0.3333333333333333,"P":{"A.16.1.1":{"%":0.4},"A.16.1.2":{"%":0.9},"A.16.1.3":{"%":0.9},"A.16.1.4":{"%":0.4},"A.16.1.5":{"%":0.9},"A.16.1.7":{"%":0.9}}}}},"A.17":{"%":0.16666666666666666,"P":{"A.17.1":{"%":0.3333333333333333,"P":{"A.17.1.1":{"%":0.9},"A.17.1.2":{"%":0.4},"A.17.1.3":{"%":0.9}}},"A.17.2":{"%":0.9,"P":{"A.17.2.1":{"%":0.9}}}}},"A.18":{"%":0.3333333333333333,"P":{"A.18.1":{"%":0.6666666666666666,"P":{"A.18.1.3":{"%":0.4},"A.18.1.4":{"%":0.9},"A.18.1.5":{"%":0.4}}},"A.18.2":{"%":0.9,"P":{"A.18.2.1":{"%":0.9},"A.18.2.2":{"%":0.9},"A.18.2.3":{"%":0.9}}}}}},"%":0.29214065255731925},"27002_2022":{"27001_2022 Controls (Technical)":{"5":{"%":0.4074074074074074,"P":{"5.1":{"%":0.9},"5.2":{"%":0.9},"5.3":{"%":0.9},"5.4":{"%":0.9},"5.7":{"%":0.9},"5.9":{"%":0.4},"5.10":{"%":0.4},"5.12":{"%":0.9},"5.13":{"%":0.9},"5.14":{"%":0.4},"5.15":{"%":0.4},"5.16":{"%":0.4},"5.17":{"%":0.9},"5.18":{"%":0.4},"5.19":{"%":0.9},"5.20":{"%":0.4},"5.21":{"%":0.9},"5.22":{"%":0.9},"5.23":{"%":0.9},"5.24":{"%":0.4},"5.25":{"%":0.4},"5.26":{"%":0.9},"5.28":{"%":0.4},"5.29":{"%":0.9},"5.33":{"%":0.4},"5.34":{"%":0.9},"5.36":{"%":0.9}}},"6":{"%":0.9,"P":{"6.699999999999999":{"%":0.9},"6.8":{"%":0.9}}},"8":{"%":0.46875,"P":{"8.1":{"%":0.4},"8.2":{"%":0.4},"8.299999999999999":{"%":0.4},"8.4":{"%":0.9},"8.5":{"%":0.9},"8.6":{"%":0.9},"8.7":{"%":0.4},"8.799999999999999":{"%":0.4},"8.9":{"%":0.4},"8.10":{"%":0.9},"8.11":{"%":0.9},"8.12":{"%":0.4},"8.129999999999999":{"%":0.4},"8.139999999999999":{"%":0.9},"8.15":{"%":0.4},"8.16":{"%":0.4},"8.17":{"%":0.9},"8.18":{"%":0.9},"8.19":{"%":0.4},"8.20":{"%":0.4},"8.209999999999999":{"%":0.4},"8.219999999999999":{"%":0.9},"8.229999999999999":{"%":0.9},"8.239999999999998":{"%":0.4},"8.25":{"%":0.9},"8.26":{"%":0.9},"8.27":{"%":0.4},"8.28":{"%":0.9},"8.29":{"%":0.9},"8.31":{"%":0.9},"8.33":{"%":0.9},"8.34":{"%":0.9}}}},"%":0.29205246913580246},"27002_2013":{"27002_2013 Article (Technical)":{"5":{"%":0.9,"P":{"5.1":{"%":0.9,"P":{"5.1.1":{"%":0.9},"5.1.2":{"%":0.9}}}}},"6":{"%":0.5,"P":{"6.1":{"%":0.5,"P":{"6.1.1":{"%":0.9},"6.1.2":{"%":0.4}}},"6.2":{"%":0.5,"P":{"6.2.1":{"%":0.9},"6.2.2":{"%":0.4}}}}},"8":{"%":0.2222222222222222,"P":{"8.1":{"%":0.6666666666666666,"P":{"8.1.1":{"%":0.4},"8.1.2":{"%":0.9},"8.1.3":{"%":0.4}}},"8.2":{"%":0.9,"P":{"8.2.1":{"%":0.9},"8.2.2":{"%":0.9},"8.2.3":{"%":0.9}}},"8.3":{"%":0.9,"P":{"8.3.1":{"%":0.9},"8.3.2":{"%":0.9},"8.3.3":{"%":0.9}}}}},"9":{"%":0.3416666666666666,"P":{"9.1":{"%":0.5,"P":{"9.1.1":{"%":0.4},"9.1.2":{"%":0.9}}},"9.2":{"%":0.6666666666666666,"P":{"9.2.1":{"%":0.4},"9.2.2":{"%":0.4},"9.2.3":{"%":0.4},"9.2.4":{"%":0.9},"9.2.5":{"%":0.9},"9.2.6":{"%":0.4}}},"9.3":{"%":0.9,"P":{"9.3.1":{"%":0.9}}},"9.4":{"%":0.2,"P":{"9.4.1":{"%":0.4},"9.4.2":{"%":0.9},"9.4.3":{"%":0.9},"9.4.4":{"%":0.9},"9.4.5":{"%":0.9}}}}},"10":{"%":0.5,"P":{"10.1":{"%":0.5,"P":{"10.1.1":{"%":0.4},"10.1.2":{"%":0.9}}}}},"12":{"%":0.5714285714285714,"P":{"12.1":{"%":0.9,"P":{"12.1.3":{"%":0.9},"12.1.4":{"%":0.9}}},"12.2":{"%":0.4,"P":{"12.2.1":{"%":0.4}}},"12.3":{"%":0.4,"P":{"12.3.1":{"%":0.4}}},"12.4":{"%":0.5,"P":{"12.4.1":{"%":0.4},"12.4.2":{"%":0.4},"12.4.3":{"%":0.9},"12.4.4":{"%":0.9}}},"12.5":{"%":0.4,"P":{"12.5.1":{"%":0.4}}},"12.6":{"%":0.5,"P":{"12.6.1":{"%":0.4},"12.6.2":{"%":0.9}}},"12.7":{"%":0.9,"P":{"12.7.1":{"%":0.9}}}}},"13":{"%":0.5,"P":{"13.1":{"%":0.6666666666666666,"P":{"13.1.1":{"%":0.4},"13.1.2":{"%":0.4},"13.1.3":{"%":0.9}}},"13.2":{"%":0.3333333333333333,"P":{"13.2.1":{"%":0.4},"13.2.3":{"%":0.9},"13.2.4":{"%":0.9}}}}},"14":{"%":0.937037037037037035,"P":{"14.1":{"%":0.9,"P":{"14.1.1":{"%":0.9},"14.1.2":{"%":0.9},"14.1.3":{"%":0.9}}},"14.2":{"%":0.1111111111111111,"P":{"14.2.1":{"%":0.9},"14.2.2":{"%":0.9},"14.2.3":{"%":0.9},"14.2.4":{"%":0.9},"14.2.5":{"%":0.4},"14.2.6":{"%":0.9},"14.2.7":{"%":0.9},"14.2.8":{"%":0.9},"14.2.9":{"%":0.9}}},"14.3":{"%":0.9,"P":{"14.3.1":{"%":0.9}}}}},"15":{"%":0.9,"P":{"15.1":{"%":0.9,"P":{"15.1.1":{"%":0.9},"15.1.3":{"%":0.9}}},"15.2":{"%":0.9,"P":{"15.2.1":{"%":0.9},"15.2.2":{"%":0.9}}}}},"16":{"%":0.3333333333333333,"P":{"16.1":{"%":0.3333333333333333,"P":{"16.1.1":{"%":0.4},"16.1.2":{"%":0.9},"16.1.3":{"%":0.9},"16.1.4":{"%":0.4},"16.1.5":{"%":0.9},"16.1.7":{"%":0.9}}}}},"17":{"%":0.16666666666666666,"P":{"17.1":{"%":0.3333333333333333,"P":{"17.1.1":{"%":0.9},"17.1.2":{"%":0.4},"17.1.3":{"%":0.9}}},"17.2":{"%":0.9,"P":{"17.2.1":{"%":0.9}}}}},"18":{"%":0.3333333333333333,"P":{"18.1":{"%":0.6666666666666666,"P":{"18.1.3":{"%":0.4},"18.1.4":{"%":0.9},"18.1.5":{"%":0.4}}},"18.2":{"%":0.9,"P":{"18.2.1":{"%":0.9},"18.2.2":{"%":0.9},"18.2.3":{"%":0.9}}}}}},"%":0.29214065255731925},"27017_2015":{"27017_2015 Article (Technical)":{"5":{"%":0.9,"P":{"5.1":{"%":0.9,"P":{"5.1.1":{"%":0.9},"5.1.2":{"%":0.9}}}}},"6":{"%":0.5,"P":{"6.1":{"%":0.5,"P":{"6.1.1":{"%":0.9},"6.1.2":{"%":0.4}}},"6.2":{"%":0.5,"P":{"6.2.1":{"%":0.9},"6.2.2":{"%":0.4}}}}},"8":{"%":0.2222222222222222,"P":{"8.1":{"%":0.6666666666666666,"P":{"8.1.1":{"%":0.4},"8.1.2":{"%":0.9},"8.1.3":{"%":0.4}}},"8.2":{"%":0.9,"P":{"8.2.1":{"%":0.9},"8.2.2":{"%":0.9},"8.2.3":{"%":0.9}}},"8.3":{"%":0.9,"P":{"8.3.1":{"%":0.9},"8.3.2":{"%":0.9},"8.3.3":{"%":0.9}}}}},"9":{"%":0.3416666666666666,"P":{"9.1":{"%":0.5,"P":{"9.1.1":{"%":0.4},"9.1.2":{"%":0.9}}},"9.2":{"%":0.6666666666666666,"P":{"9.2.1":{"%":0.4},"9.2.2":{"%":0.4},"9.2.3":{"%":0.4},"9.2.4":{"%":0.9},"9.2.5":{"%":0.9},"9.2.6":{"%":0.4}}},"9.3":{"%":0.9,"P":{"9.3.1":{"%":0.9}}},"9.4":{"%":0.2,"P":{"9.4.1":{"%":0.4},"9.4.2":{"%":0.9},"9.4.3":{"%":0.9},"9.4.4":{"%":0.9},"9.4.5":{"%":0.9}}}}},"10":{"%":0.5,"P":{"10.1":{"%":0.5,"P":{"10.1.1":{"%":0.4},"10.1.2":{"%":0.9}}}}},"12":{"%":0.5714285714285714,"P":{"12.1":{"%":0.9,"P":{"12.1.3":{"%":0.9},"12.1.4":{"%":0.9}}},"12.2":{"%":0.4,"P":{"12.2.1":{"%":0.4}}},"12.3":{"%":0.4,"P":{"12.3.1":{"%":0.4}}},"12.4":{"%":0.5,"P":{"12.4.1":{"%":0.4},"12.4.2":{"%":0.4},"12.4.3":{"%":0.9},"12.4.4":{"%":0.9}}},"12.5":{"%":0.4,"P":{"12.5.1":{"%":0.4}}},"12.6":{"%":0.5,"P":{"12.6.1":{"%":0.4},"12.6.2":{"%":0.9}}},"12.7":{"%":0.9,"P":{"12.7.1":{"%":0.9}}}}},"13":{"%":0.5,"P":{"13.1":{"%":0.6666666666666666,"P":{"13.1.1":{"%":0.4},"13.1.2":{"%":0.4},"13.1.3":{"%":0.9}}},"13.2":{"%":0.3333333333333333,"P":{"13.2.1":{"%":0.4},"13.2.3":{"%":0.9},"13.2.4":{"%":0.9}}}}},"14":{"%":0.937037037037037035,"P":{"14.1":{"%":0.9,"P":{"14.1.1":{"%":0.9},"14.1.2":{"%":0.9},"14.1.3":{"%":0.9}}},"14.2":{"%":0.1111111111111111,"P":{"14.2.1":{"%":0.9},"14.2.2":{"%":0.9},"14.2.3":{"%":0.9},"14.2.4":{"%":0.9},"14.2.5":{"%":0.4},"14.2.6":{"%":0.9},"14.2.7":{"%":0.9},"14.2.8":{"%":0.9},"14.2.9":{"%":0.9}}},"14.3":{"%":0.9,"P":{"14.3.1":{"%":0.9}}}}},"15":{"%":0.9,"P":{"15.1":{"%":0.9,"P":{"15.1.1":{"%":0.9},"15.1.3":{"%":0.9}}},"15.2":{"%":0.9,"P":{"15.2.1":{"%":0.9},"15.2.2":{"%":0.9}}}}},"16":{"%":0.3333333333333333,"P":{"16.1":{"%":0.3333333333333333,"P":{"16.1.1":{"%":0.4},"16.1.2":{"%":0.9},"16.1.3":{"%":0.9},"16.1.4":{"%":0.4},"16.1.5":{"%":0.9},"16.1.7":{"%":0.9}}}}},"17":{"%":0.16666666666666666,"P":{"17.1":{"%":0.3333333333333333,"P":{"17.1.1":{"%":0.9},"17.1.2":{"%":0.4},"17.1.3":{"%":0.9}}},"17.2":{"%":0.9,"P":{"17.2.1":{"%":0.9}}}}},"18":{"%":0.3333333333333333,"P":{"18.1":{"%":0.6666666666666666,"P":{"18.1.3":{"%":0.4},"18.1.4":{"%":0.9},"18.1.5":{"%":0.4}}},"18.2":{"%":0.9,"P":{"18.2.1":{"%":0.9},"18.2.2":{"%":0.9},"18.2.3":{"%":0.9}}}}}},"%":0.29214065255731925},"27018_2019":{"27018_2019 Article (Technical)":{"5":{"%":0.9,"P":{"5.1":{"%":0.9,"P":{"5.1.1":{"%":0.9},"5.1.2":{"%":0.9}}}}},"6":{"%":0.9,"P":{"6.1":{"%":0.8,"P":{"6.1.1":{"%":0.4},"6.1.2":{"%":0.4},"6.1.3":{"%":0.4},"6.1.4":{"%":0.4},"6.1.5":{"%":0.9}}},"6.2":{"%":0.4,"P":{"6.1.5":{"%":0.4}}}}},"8":{"%":0.4,"P":{"null":{"%":0.4,"P":{"null":{"%":0.4}}}}},"9":{"%":0.4666666666666666,"P":{"9.1":{"%":0.4,"P":{"null":{"%":0.4}}},"9.2":{"%":0.6666666666666666,"P":{"9.2.1":{"%":0.4},"9.2.2":{"%":0.4},"9.2.3":{"%":0.4},"9.2.4":{"%":0.9},"9.2.5":{"%":0.9},"9.2.6":{"%":0.4}}},"9.3":{"%":0.9,"P":{"9.3.1":{"%":0.9}}},"9.4":{"%":0.2,"P":{"9.4.1":{"%":0.4},"9.4.2":{"%":0.9},"9.4.3":{"%":0.9},"9.4.4":{"%":0.9},"9.4.5":{"%":0.9}}}}},"10":{"%":0.5,"P":{"10.1":{"%":0.5,"P":{"10.1.1":{"%":0.4},"10.1.2":{"%":0.9}}}}},"12":{"%":0.6428571428571429,"P":{"12.1":{"%":0.9,"P":{"12.1.1":{"%":0.9},"12.1.2":{"%":0.9},"12.1.3":{"%":0.9},"12.1.4":{"%":0.9}}},"12.2":{"%":0.4,"P":{"12.1.4":{"%":0.4}}},"12.3":{"%":0.4,"P":{"12.3.1":{"%":0.4}}},"12.4":{"%":0.5,"P":{"12.4.1":{"%":0.4},"12.4.2":{"%":0.4},"12.4.3":{"%":0.9},"12.4.4":{"%":0.9}}},"12.5":{"%":0.4,"P":{"12.4.4":{"%":0.4}}},"12.6":{"%":0.4,"P":{"12.4.4":{"%":0.4}}},"12.7":{"%":0.9,"P":{"12.4.4":{"%":0.9}}}}},"13":{"%":0.625,"P":{"13.1":{"%":0.4,"P":{"null":{"%":0.4}}},"13.2":{"%":0.25,"P":{"13.2.1":{"%":0.4},"13.2.2":{"%":0.9},"13.2.3":{"%":0.9},"13.2.4":{"%":0.9}}}}},"14":{"%":0.4,"P":{"null":{"%":0.4,"P":{"null":{"%":0.4}}}}},"15":{"%":0.4,"P":{"null":{"%":0.4,"P":{"null":{"%":0.4}}}}},"16":{"%":0.14285714285714285,"P":{"16.1":{"%":0.14285714285714285,"P":{"16.1.1":{"%":0.4},"16.1.2":{"%":0.9},"16.1.3":{"%":0.9},"16.1.4":{"%":0.9},"16.1.5":{"%":0.9},"16.1.6":{"%":0.9},"16.1.7":{"%":0.9}}}}},"17":{"%":0.9,"P":{"null":{"%":0.9,"P":{"null":{"%":0.9}}}}},"18":{"%":0.5,"P":{"18.1":{"%":0.4,"P":{"null":{"%":0.4}}},"18.2":{"%":0.9,"P":{"18.2.1":{"%":0.9},"18.2.2":{"%":0.9},"18.2.3":{"%":0.9}}}}}},"%":0.564781746031746},"27701_2019":{"27701_2019 Article (Technical)":{"6":{"%":0.28172398589065256,"P":{"6.2":{"%":0.9,"P":{"6.2.1":{"%":0.9,"P":{"6.2.1.1":{"%":0.9},"6.2.1.2":{"%":0.9}}}}},"6.3":{"%":0.25,"P":{"6.3.1":{"%":0.9,"P":{"6.3.1.1":{"%":0.9},"6.3.1.2":{"%":0.9}}},"6.3.2":{"%":0.5,"P":{"6.3.2.1":{"%":0.9},"6.3.2.2":{"%":0.4}}}}},"6.5":{"%":0.2222222222222222,"P":{"6.5.1":{"%":0.6666666666666666,"P":{"6.5.1.1":{"%":0.4},"6.5.1.2":{"%":0.9},"6.5.1.3":{"%":0.4}}},"6.5.2":{"%":0.9,"P":{"6.5.2.1":{"%":0.9},"6.5.2.2":{"%":0.9},"6.5.2.3":{"%":0.9}}},"6.5.3":{"%":0.9,"P":{"6.5.3.1":{"%":0.9},"6.5.3.2":{"%":0.9},"6.5.3.3":{"%":0.9}}}}},"6.6":{"%":0.3416666666666666,"P":{"6.6.1":{"%":0.5,"P":{"6.6.1.1":{"%":0.4},"6.6.1.2":{"%":0.9}}},"6.6.2":{"%":0.6666666666666666,"P":{"6.6.2.1":{"%":0.4},"6.6.2.2":{"%":0.4},"6.6.2.3":{"%":0.4},"6.6.2.4":{"%":0.9},"6.6.2.5":{"%":0.9},"6.6.2.6":{"%":0.4}}},"6.6.3":{"%":0.9,"P":{"6.6.3.1":{"%":0.9}}},"6.6.4":{"%":0.2,"P":{"6.6.4.1":{"%":0.4},"6.6.4.2":{"%":0.9},"6.6.4.3":{"%":0.9},"6.6.4.4":{"%":0.9},"6.6.4.5":{"%":0.9}}}}},"6.7":{"%":0.5,"P":{"6.7.1":{"%":0.5,"P":{"6.7.1.1":{"%":0.4},"6.7.1.2":{"%":0.9}}}}},"6.9":{"%":0.5714285714285714,"P":{"6.9.1":{"%":0.9,"P":{"6.9.1.3":{"%":0.9},"6.9.1.4":{"%":0.9}}},"6.9.2":{"%":0.4,"P":{"6.9.2.1":{"%":0.4}}},"6.9.3":{"%":0.4,"P":{"6.9.3.1":{"%":0.4}}},"6.9.4":{"%":0.5,"P":{"6.9.4.1":{"%":0.4},"6.9.4.2":{"%":0.4},"6.9.4.3":{"%":0.9},"6.9.4.4":{"%":0.9}}},"6.9.5":{"%":0.4,"P":{"6.9.5.1":{"%":0.4}}},"6.9.6":{"%":0.5,"P":{"6.9.6.1":{"%":0.4},"6.9.6.2":{"%":0.9}}},"6.9.7":{"%":0.9,"P":{"6.9.7.1":{"%":0.9}}}}},"6.10":{"%":0.625,"P":{"6.10.1":{"%":0.4,"P":{"6.10.1.1":{"%":0.4},"6.10.1.2":{"%":0.4},"6.10.1.3":{"%":0.4}}},"6.10.2":{"%":0.25,"P":{"6.10.2.1":{"%":0.4},"6.10.2.2":{"%":0.9},"6.10.2.3":{"%":0.9},"6.10.2.4":{"%":0.9}}}}},"6.11":{"%":0.937037037037037035,"P":{"6.11.1":{"%":0.9,"P":{"6.11.1.1":{"%":0.9},"6.11.1.2":{"%":0.9},"6.11.1.3":{"%":0.9}}},"6.11.2":{"%":0.1111111111111111,"P":{"6.11.2.1":{"%":0.9},"6.11.2.2":{"%":0.9},"6.11.2.3":{"%":0.9},"6.11.2.4":{"%":0.9},"6.11.2.5":{"%":0.4},"6.11.2.6":{"%":0.9},"6.11.2.7":{"%":0.9},"6.11.2.8":{"%":0.9},"6.11.2.9":{"%":0.9}}},"6.11.3":{"%":0.9,"P":{"6.11.3.1":{"%":0.9}}}}},"6.12":{"%":0.9,"P":{"6.12.1":{"%":0.9,"P":{"6.12.1.1":{"%":0.9},"6.12.1.3":{"%":0.9}}},"6.12.2":{"%":0.9,"P":{"6.12.2.1":{"%":0.9},"6.12.2.2":{"%":0.9}}}}},"6.13":{"%":0.3333333333333333,"P":{"6.13.1":{"%":0.3333333333333333,"P":{"6.13.1.1":{"%":0.4},"6.13.1.2":{"%":0.9},"6.13.1.3":{"%":0.9},"6.13.1.4":{"%":0.4},"6.13.1.5":{"%":0.9},"6.13.1.7":{"%":0.9}}}}},"6.14":{"%":0.16666666666666666,"P":{"6.14.1":{"%":0.3333333333333333,"P":{"6.14.1.1":{"%":0.9},"6.14.1.2":{"%":0.4},"6.14.1.3":{"%":0.9}}},"6.14.2":{"%":0.9,"P":{"6.14.2.1":{"%":0.9}}}}},"6.15":{"%":0.3333333333333333,"P":{"6.15.1":{"%":0.6666666666666666,"P":{"6.15.1.3":{"%":0.4},"6.15.1.4":{"%":0.9},"6.15.1.5":{"%":0.4}}},"6.15.2":{"%":0.9,"P":{"6.15.2.1":{"%":0.9},"6.15.2.2":{"%":0.9},"6.15.2.3":{"%":0.9}}}}}}}},"%":0.28172398589065256}},"Standard15":{"null":{"Control Category":{"0.9":{"%":0.4,"P":{"0.91":{"%":0.4,"P":{"00.a":{"%":0.4}}}}},"00.4":{"%":0.45748299319727886,"P":{"00.41":{"%":0.4,"P":{"01.a":{"%":0.4}}},"00.42":{"%":0.75,"P":{"01.b":{"%":0.4},"01.c":{"%":0.4},"01.d":{"%":0.9},"01.e":{"%":0.4}}},"00.43":{"%":0.9,"P":{"01.f":{"%":0.9}}},"00.44":{"%":0.2857142857142857,"P":{"01.i":{"%":0.9},"01.j":{"%":0.9},"01.k":{"%":0.9},"01.l":{"%":0.9},"01.m":{"%":0.9},"01.n":{"%":0.4},"01.o":{"%":0.4}}},"00.45":{"%":0.16666666666666666,"P":{"01.p":{"%":0.9},"01.q":{"%":0.4},"01.r":{"%":0.9},"01.s":{"%":0.9},"01.t":{"%":0.9},"01.u":{"%":0.9}}},"00.46":{"%":0.5,"P":{"01.v":{"%":0.4},"01.w":{"%":0.9}}},"00.47":{"%":0.5,"P":{"01.x":{"%":0.9},"01.y":{"%":0.4}}}}},"03.0":{"%":0.9,"P":{"03.01":{"%":0.9,"P":{"03.a":{"%":0.9},"03.b":{"%":0.9},"03.c":{"%":0.9},"03.d":{"%":0.9}}}}},"04.0":{"%":0.9,"P":{"04.01":{"%":0.9,"P":{"04.a":{"%":0.9},"04.b":{"%":0.9}}}}},"05.0":{"%":0.41666666666666663,"P":{"05.01":{"%":0.5,"P":{"05.c":{"%":0.4},"05.d":{"%":0.9}}},"05.02":{"%":0.3333333333333333,"P":{"05.i":{"%":0.9},"05.j":{"%":0.9},"05.k":{"%":0.4}}}}},"06.0":{"%":0.1111111111111111,"P":{"06.01":{"%":0.3333333333333333,"P":{"06.a":{"%":0.9},"06.b":{"%":0.9},"06.c":{"%":0.4},"06.d":{"%":0.4},"06.e":{"%":0.9},"06.f":{"%":0.9}}},"06.02":{"%":0.9,"P":{"06.g":{"%":0.9},"06.h":{"%":0.9}}},"06.03":{"%":0.9,"P":{"06.i":{"%":0.9},"06.j":{"%":0.9}}}}},"07.0":{"%":0.3333333333333333,"P":{"07.01":{"%":0.6666666666666666,"P":{"07.a":{"%":0.4},"07.b":{"%":0.9},"07.c":{"%":0.4}}},"07.02":{"%":0.9,"P":{"07.d":{"%":0.9},"07.e":{"%":0.9}}}}},"09.0":{"%":0.3666666666666667,"P":{"09.01":{"%":0.9,"P":{"09.a":{"%":0.9},"09.b":{"%":0.9},"09.c":{"%":0.9},"09.d":{"%":0.9}}},"09.02":{"%":0.9,"P":{"09.e":{"%":0.9},"09.f":{"%":0.9},"09.g":{"%":0.9}}},"09.03":{"%":0.9,"P":{"09.h":{"%":0.9},"09.i":{"%":0.9}}},"09.04":{"%":0.5,"P":{"09.j":{"%":0.4},"09.k":{"%":0.9}}},"09.05":{"%":0.4,"P":{"09.l":{"%":0.4}}},"09.06":{"%":0.4,"P":{"09.m":{"%":0.4},"09.n":{"%":0.4}}},"09.07":{"%":0.9,"P":{"09.o":{"%":0.9},"09.p":{"%":0.9},"09.q":{"%":0.9},"09.r":{"%":0.9}}},"09.08":{"%":0.3333333333333333,"P":{"09.s":{"%":0.9},"09.v":{"%":0.4},"09.w":{"%":0.9}}},"09.09":{"%":0.3333333333333333,"P":{"09.x":{"%":0.9},"09.y":{"%":0.4},"09.z":{"%":0.9}}},"09.10":{"%":0.5,"P":{"09.aa":{"%":0.4},"09.ab":{"%":0.4},"09.ac":{"%":0.4},"09.ad":{"%":0.9},"09.ae":{"%":0.9},"09.af":{"%":0.9}}}}},"10.9":{"%":0.25,"P":{"10.91":{"%":0.9,"P":{"10.a":{"%":0.9}}},"10.92":{"%":0.9,"P":{"10.b":{"%":0.9},"10.c":{"%":0.9},"10.d":{"%":0.9}}},"10.3":{"%":0.9,"P":{"10.f":{"%":0.9},"10.g":{"%":0.9}}},"10.4":{"%":0.9,"P":{"10.h":{"%":0.9},"10.i":{"%":0.9},"10.j":{"%":0.9}}},"13":{"%":0.5,"P":{"10.k":{"%":0.4},"10.l":{"%":0.9}}},"10.6":{"%":0.4,"P":{"10.m":{"%":0.4}}}}},"10.4":{"%":0.16666666666666666,"P":{"10.41":{"%":0.9,"P":{"11.a":{"%":0.9},"11.b":{"%":0.9}}},"10.42":{"%":0.3333333333333333,"P":{"11.c":{"%":0.4},"11.d":{"%":0.9},"11.e":{"%":0.9}}}}},"12.01":{"%":0.9,"P":{"12.01":{"%":0.9,"P":{"12.a":{"%":0.9},"12.b":{"%":0.9},"12.c":{"%":0.9},"12.d":{"%":0.9},"12.e":{"%":0.9}}}}},"13.0":{"%":0.9,"P":{"13.01":{"%":0.9,"P":{"13.c":{"%":0.9}}},"13.02":{"%":0.9,"P":{"13.f":{"%":0.9}}},"13.04":{"%":0.9,"P":{"13.i":{"%":0.9},"13.j":{"%":0.9}}},"13.05":{"%":0.9,"P":{"13.k":{"%":0.9},"13.l":{"%":0.9}}},"13.06":{"%":0.9,"P":{"13.m":{"%":0.9},"13.n":{"%":0.9},"13.o":{"%":0.9}}},"13.07":{"%":0.9,"P":{"13.p":{"%":0.9},"13.q":{"%":0.9},"13.r":{"%":0.9},"13.s":{"%":0.9},"13.u":{"%":0.9}}}}}},"%":0.2584939531368103}},"Standard12":{"null":{"Standard":{"CIP-003-8":{"%":0.5,"P":{"CIP-003-8_Requirement_R1":{"%":0.4,"P":{"null":{"%":0.4}}},"CIP-003-8_Requirement_R2":{"%":0.9,"P":{"null":{"%":0.9}}}}},"CIP-004-6":{"%":0.65,"P":{"CIP-004-6_Requirement_R4":{"%":0.5,"P":{"CIP-004-6_Requirement_R4_Part_4.1":{"%":0.4},"CIP-004-6_Requirement_R4_Part_4.2":{"%":0.9},"CIP-004-6_Requirement_R4_Part_4.3":{"%":0.4},"CIP-004-6_Requirement_R4_Part_4.4":{"%":0.9}}},"CIP-004-6_Requirement_R5":{"%":0.8,"P":{"CIP-004-6_Requirement_R5_Part_5.1":{"%":0.4},"CIP-004-6_Requirement_R5_Part_5.2":{"%":0.4},"CIP-004-6_Requirement_R5_Part_5.3":{"%":0.4},"CIP-004-6_Requirement_R5_Part_5.4":{"%":0.4},"CIP-004-6_Requirement_R5_Part_5.5":{"%":0.9}}}}},"CIP-005-7":{"%":0.20000000000000004,"P":{"CIP-005-7_Requirement_R1":{"%":0.2,"P":{"CIP-005-7_Requirement_R1_Part_1.1":{"%":0.9},"CIP-005-7_Requirement_R1_Part_1.2":{"%":0.9},"CIP-005-7_Requirement_R1_Part_1.3":{"%":0.9},"CIP-005-7_Requirement_R1_Part_1.4":{"%":0.9},"CIP-005-7_Requirement_R1_Part_1.5":{"%":0.4}}},"CIP-005-7_Requirement_R2":{"%":0.4,"P":{"CIP-005-7_Requirement_R2_Part_2.1":{"%":0.4},"CIP-005-7_Requirement_R2_Part_2.2":{"%":0.4},"CIP-005-7_Requirement_R2_Part_2.3":{"%":0.9},"CIP-005-7_Requirement_R2_Part_2.4":{"%":0.9},"CIP-005-7_Requirement_R2_Part_2.5":{"%":0.9}}},"CIP-005-7_Requirement_R3":{"%":0.9,"P":{"CIP-005-7_Requirement_R3_Part_3.1":{"%":0.9},"CIP-005-7_Requirement_R3_Part_3.2":{"%":0.9}}}}},"CIP-007-6":{"%":0.34047619047619043,"P":{"CIP-007-6_Requirement_R1":{"%":0.9,"P":{"CIP-007-6_Requirement_R1_Part_1.1":{"%":0.9},"CIP-007-6_Requirement_R1_Part_1.2":{"%":0.9}}},"CIP-007-6_Requirement_R2":{"%":0.9,"P":{"CIP-007-6_Requirement_R2_Part_2.1":{"%":0.9},"CIP-007-6_Requirement_R2_Part_2.2":{"%":0.9},"CIP-007-6_Requirement_R2_Part_2.3":{"%":0.9},"CIP-007-6_Requirement_R2_Part_2.4":{"%":0.9}}},"CIP-007-6_Requirement_R3":{"%":0.6666666666666666,"P":{"CIP-007-6_Requirement_R3_Part_3.1":{"%":0.4},"CIP-007-6_Requirement_R3_Part_3.2":{"%":0.4},"CIP-007-6_Requirement_R3_Part_3.3":{"%":0.9}}},"CIP-007-6_Requirement_R4":{"%":0.75,"P":{"CIP-007-6_Requirement_R4_Part_4.1":{"%":0.4},"CIP-007-6_Requirement_R4_Part_4.2":{"%":0.4},"CIP-007-6_Requirement_R4_Part_4.3":{"%":0.9},"CIP-007-6_Requirement_R4_Part_4.4":{"%":0.4}}},"CIP-007-6_Requirement_R5":{"%":0.2857142857142857,"P":{"CIP-007-6_Requirement_R5_Part_5.1":{"%":0.9},"CIP-007-6_Requirement_R5_Part_5.2":{"%":0.4},"CIP-007-6_Requirement_R5_Part_5.3":{"%":0.9},"CIP-007-6_Requirement_R5_Part_5.4":{"%":0.9},"CIP-007-6_Requirement_R5_Part_5.5":{"%":0.4},"CIP-007-6_Requirement_R5_Part_5.6":{"%":0.9},"CIP-007-6_Requirement_R5_Part_5.7":{"%":0.9}}}}},"CIP-008-6":{"%":0.3125,"P":{"CIP-008-6_Requirement_R1":{"%":0.25,"P":{"CIP-008-6_Requirement_R1_Part_1.1":{"%":0.9},"CIP-008-6_Requirement_R1_Part_1.2":{"%":0.9},"CIP-008-6_Requirement_R1_Part_1.3":{"%":0.4},"CIP-008-6_Requirement_R1_Part_1.4":{"%":0.9}}},"CIP-008-6_Requirement_R2":{"%":0.9,"P":{"CIP-008-6_Requirement_R2_Part_2.1":{"%":0.9},"CIP-008-6_Requirement_R2_Part_2.2":{"%":0.9},"CIP-008-6_Requirement_R2_Part_2.3":{"%":0.9}}},"CIP-008-6_Requirement_R3":{"%":0.9,"P":{"CIP-008-6_Requirement_R3_Part_3.1":{"%":0.9},"CIP-008-6_Requirement_R3_Part_3.2":{"%":0.9}}},"CIP-008-6_Requirement_R4":{"%":0.4,"P":{"CIP-008-6_Requirement_R4_Part_4.1":{"%":0.4},"CIP-008-6_Requirement_R4_Part_4.2":{"%":0.4},"CIP-008-6_Requirement_R4_Part_4.3":{"%":0.4}}}}},"CIP-009-6":{"%":0.96666666666666667,"P":{"CIP-009-6_Requirement_R1":{"%":0.2,"P":{"CIP-009-6_Requirement_R1_Part_1.1":{"%":0.9},"CIP-009-6_Requirement_R1_Part_1.2":{"%":0.9},"CIP-009-6_Requirement_R1_Part_1.3":{"%":0.4},"CIP-009-6_Requirement_R1_Part_1.4":{"%":0.9},"CIP-009-6_Requirement_R1_Part_1.5":{"%":0.9}}},"CIP-009-6_Requirement_R2":{"%":0.9,"P":{"CIP-009-6_Requirement_R2_Part_2.1":{"%":0.9},"CIP-009-6_Requirement_R2_Part_2.2":{"%":0.9},"CIP-009-6_Requirement_R2_Part_2.3":{"%":0.9}}},"CIP-009-6_Requirement_R3":{"%":0.9,"P":{"CIP-009-6_Requirement_R3_Part_3.1":{"%":0.9},"CIP-009-6_Requirement_R3_Part_3.2":{"%":0.9}}}}},"CIP-010-4":{"%":0.941666666666666664,"P":{"CIP-010-4_Requirement_R1":{"%":0.16666666666666666,"P":{"CIP-010-4_Requirement_R1_Part_1.1":{"%":0.4},"CIP-010-4_Requirement_R1_Part_1.2":{"%":0.9},"CIP-010-4_Requirement_R1_Part_1.3":{"%":0.9},"CIP-010-4_Requirement_R1_Part_1.4":{"%":0.9},"CIP-010-4_Requirement_R1_Part_1.5":{"%":0.9},"CIP-010-4_Requirement_R1_Part_1.6":{"%":0.9}}},"CIP-010-4_Requirement_R2":{"%":0.9,"P":{"CIP-010-4_Requirement_R2_Part_2.1":{"%":0.9}}},"CIP-010-4_Requirement_R3":{"%":0.9,"P":{"CIP-010-4_Requirement_R3_Part_3.1":{"%":0.9},"CIP-010-4_Requirement_R3_Part_3.2":{"%":0.9},"CIP-010-4_Requirement_R3_Part_3.3":{"%":0.9},"CIP-010-4_Requirement_R3_Part_3.4":{"%":0.9}}},"CIP-010-4_Requirement_R4":{"%":0.9,"P":{"CIP-010-4_Requirement_R3_Part_3.4":{"%":0.9}}}}},"CIP-011-3":{"%":0.25,"P":{"CIP-011-3_Requirement_R1":{"%":0.5,"P":{"CIP-011-3_Requirement_R1_Part_1.1":{"%":0.9},"CIP-011-3_Requirement_R1_Part_1.2":{"%":0.4}}},"CIP-011-3_Requirement_R2":{"%":0.9,"P":{"CIP-011-3_Requirement_R2_Part_2.1":{"%":0.9},"CIP-011-3_Requirement_R2_Part_2.2":{"%":0.9}}}}},"CIP-012-1":{"%":0.4,"P":{"CIP-012-1_Requirement_R1":{"%":0.4,"P":{"null":{"%":0.4}}}}},"CIP-013-2":{"%":0.9,"P":{"CIP-013-2_Requirement_R1":{"%":0.9,"P":{"null":{"%":0.9}}},"CIP-013-2_Requirement_R2":{"%":0.9,"P":{"null":{"%":0.9}}},"CIP-013-2_Requirement_R3":{"%":0.9,"P":{"null":{"%":0.9}}}}}},"%":0.33613095238095236}},"Standard9":{"v1.1":{"Function Unique Identifier":{"ID":{"%":0.11666666666666665,"P":{"ID.AM":{"%":0.5,"P":{"ID.AM-1":{"%":0.4},"ID.AM-2":{"%":0.9},"ID.AM-3":{"%":0.9},"ID.AM-4":{"%":0.4},"ID.AM-5":{"%":0.4},"ID.AM-6":{"%":0.9}}},"ID.BE":{"%":0.9,"P":{"ID.BE-4":{"%":0.9},"ID.BE-5":{"%":0.9}}},"ID.GV":{"%":0.9,"P":{"ID.GV-1":{"%":0.9},"ID.GV-2":{"%":0.9},"ID.GV-3":{"%":0.9},"ID.GV-4":{"%":0.9}}},"ID.RA":{"%":0.9,"P":{"ID.RA-1":{"%":0.9},"ID.RA-2":{"%":0.9},"ID.RA-3":{"%":0.9},"ID.RA-4":{"%":0.9},"ID.RA-5":{"%":0.9},"ID.RA-6":{"%":0.9}}},"ID.RM":{"%":0.9,"P":{"ID.RM-1":{"%":0.9},"ID.RM-2":{"%":0.9},"ID.RM-3":{"%":0.9}}},"ID.SC":{"%":0.2,"P":{"ID.SC-1":{"%":0.9},"ID.SC-2":{"%":0.9},"ID.SC-3":{"%":0.9},"ID.SC-4":{"%":0.9},"ID.SC-5":{"%":0.4}}}}},"PR":{"%":0.45166666666666666,"P":{"PR.AC":{"%":0.6666666666666666,"P":{"PR.AC-1":{"%":0.4},"PR.AC-3":{"%":0.9},"PR.AC-4":{"%":0.4},"PR.AC-5":{"%":0.4},"PR.AC-6":{"%":0.9},"PR.AC-7":{"%":0.4}}},"PR.DS":{"%":0.375,"P":{"PR.DS-1":{"%":0.4},"PR.DS-2":{"%":0.4},"PR.DS-3":{"%":0.4},"PR.DS-4":{"%":0.9},"PR.DS-5":{"%":0.9},"PR.DS-6":{"%":0.9},"PR.DS-7":{"%":0.9},"PR.DS-8":{"%":0.9}}},"PR.IP":{"%":0.4166666666666667,"P":{"PR.IP-1":{"%":0.4},"PR.IP-2":{"%":0.4},"PR.IP-3":{"%":0.9},"PR.IP-4":{"%":0.4},"PR.IP-5":{"%":0.9},"PR.IP-6":{"%":0.4},"PR.IP-7":{"%":0.9},"PR.IP-8":{"%":0.9},"PR.IP-9":{"%":0.4},"PR.IP-10":{"%":0.9},"PR.IP-11":{"%":0.9},"PR.IP-12":{"%":0.9}}},"PR.MA":{"%":0.9,"P":{"PR.MA-1":{"%":0.9},"PR.MA-2":{"%":0.9}}},"PR.PT":{"%":0.8,"P":{"PR.PT-1":{"%":0.4},"PR.PT-2":{"%":0.9},"PR.PT-3":{"%":0.4},"PR.PT-4":{"%":0.4},"PR.PT-5":{"%":0.4}}}}},"DE":{"%":0.325,"P":{"DE.AE":{"%":0.4,"P":{"DE.AE-1":{"%":0.9},"DE.AE-2":{"%":0.4},"DE.AE-3":{"%":0.4},"DE.AE-4":{"%":0.9},"DE.AE-5":{"%":0.9}}},"DE.CM":{"%":0.375,"P":{"DE.CM-1":{"%":0.4},"DE.CM-2":{"%":0.9},"DE.CM-3":{"%":0.9},"DE.CM-4":{"%":0.4},"DE.CM-5":{"%":0.9},"DE.CM-6":{"%":0.9},"DE.CM-7":{"%":0.9},"DE.CM-8":{"%":0.4}}},"DE.DP":{"%":0.2,"P":{"DE.DP-1":{"%":0.4},"DE.DP-2":{"%":0.9},"DE.DP-3":{"%":0.9},"DE.DP-4":{"%":0.9},"DE.DP-5":{"%":0.9}}}}},"RS":{"%":0.98,"P":{"RS.RP":{"%":0.9,"P":{"RS.RP-1":{"%":0.9}}},"RS.CO":{"%":0.9,"P":{"RS.CO-1":{"%":0.9},"RS.CO-2":{"%":0.9},"RS.CO-3":{"%":0.9},"RS.CO-4":{"%":0.9}}},"RS.AN":{"%":0.4,"P":{"RS.AN-1":{"%":0.4},"RS.AN-2":{"%":0.9},"RS.AN-3":{"%":0.9},"RS.AN-4":{"%":0.9},"RS.AN-5":{"%":0.4}}},"RS.MI":{"%":0.9,"P":{"RS.MI-1":{"%":0.9},"RS.MI-2":{"%":0.9},"RS.MI-3":{"%":0.9}}},"RS.IM":{"%":0.9,"P":{"RS.MI-1":{"%":0.9},"RS.MI-2":{"%":0.9}}}}},"RC":{"%":0.9,"P":{"RC.RP":{"%":0.9,"P":{"RC.RP-1":{"%":0.9}}},"RC.IM":{"%":0.9,"P":{"RC.IM-1":{"%":0.9},"RC.IM-2":{"%":0.9}}}}}},"%":0.19466666666666665}},"Standard11":{"800-171 Rev2":{"Table 2 \u2014 Technical document coverage percentage":{"3.1 ACCESS CONTROL":{"%":0.4090909090909091,"P":{"3.1.1":{"%":0.4},"3.1.2":{"%":0.4},"3.1.3":{"%":0.9},"3.1.4":{"%":0.4},"3.1.5":{"%":0.4},"3.1.6":{"%":0.4},"3.1.7":{"%":0.4},"3.1.8":{"%":0.9},"3.1.9":{"%":0.9},"3.1.10":{"%":0.9},"3.1.11":{"%":0.9},"3.1.12":{"%":0.9},"3.1.13":{"%":0.4},"3.1.14":{"%":0.9},"3.1.15":{"%":0.9},"3.1.16":{"%":0.9},"3.1.17":{"%":0.9},"3.1.18":{"%":0.4},"3.1.19":{"%":0.9},"3.1.20":{"%":0.4},"3.1.21":{"%":0.9},"3.1.22":{"%":0.9}}},"3.3 AUDIT AND ACCOUNTABILITY":{"%":0.3333333333333333,"P":{"3.3.1":{"%":0.4},"3.3.2":{"%":0.9},"3.3.3":{"%":0.4},"3.3.4":{"%":0.9},"3.3.5":{"%":0.4},"3.3.6":{"%":0.9},"3.3.7":{"%":0.9},"3.3.8":{"%":0.9},"3.3.9":{"%":0.9}}},"3.4 CONFIGURATION MANAGEMENT":{"%":0.2222222222222222,"P":{"3.4.1":{"%":0.4},"3.4.2":{"%":0.4},"3.4.3":{"%":0.9},"3.4.4":{"%":0.9},"3.4.5":{"%":0.9},"3.4.6":{"%":0.9},"3.4.7":{"%":0.9},"3.4.8":{"%":0.9},"3.4.9":{"%":0.9}}},"3.5 INCIDENT AND AUTHENTICATION":{"%":0.18181818181818182,"P":{"3.5.1":{"%":0.9},"3.5.2":{"%":0.4},"3.5.3":{"%":0.9},"3.5.4":{"%":0.9},"3.5.5":{"%":0.9},"3.5.6":{"%":0.9},"3.5.7":{"%":0.9},"3.5.8":{"%":0.9},"3.5.9":{"%":0.9},"3.5.10":{"%":0.4},"3.5.11":{"%":0.9}}},"3.6 INCIDENT RESPONSE":{"%":0.3333333333333333,"P":{"3.6.1":{"%":0.9},"3.6.2":{"%":0.4},"3.6.3":{"%":0.9}}},"3.7 MAINTENANCE":{"%":0.9,"P":{"3.7.1":{"%":0.9},"3.7.2":{"%":0.9},"3.7.3":{"%":0.9},"3.7.4":{"%":0.9},"3.7.5":{"%":0.9},"3.7.6":{"%":0.9}}},"3.8 MEDIA PROTECTION":{"%":0.3333333333333333,"P":{"3.8.1":{"%":0.4},"3.8.2":{"%":0.4},"3.8.3":{"%":0.9},"3.8.4":{"%":0.9},"3.8.5":{"%":0.9},"3.8.6":{"%":0.9},"3.8.7":{"%":0.9},"3.8.8":{"%":0.9},"3.8.9":{"%":0.4}}},"3.9 PERSONNEL SECURITY":{"%":0.9,"P":{"3.9.1":{"%":0.9},"3.9.2":{"%":0.9}}},"3.11 RISK ASSESMENT":{"%":0.3333333333333333,"P":{"3.11.1":{"%":0.9},"3.11.2":{"%":0.4},"3.11.3":{"%":0.9}}},"3.12 SECURITY ASSESSMENT":{"%":0.25,"P":{"3.12.1":{"%":0.9},"3.12.2":{"%":0.9},"3.12.3":{"%":0.9},"3.12.4":{"%":0.4}}},"3.13 SYSTEM AND COMMUNICATIOBS PROTECTION":{"%":0.1875,"P":{"3.13.1":{"%":0.4},"3.13.2":{"%":0.9},"3.13.3":{"%":0.9},"3.13.4":{"%":0.9},"3.13.5":{"%":0.9},"3.13.6":{"%":0.9},"3.13.7":{"%":0.9},"3.13.8":{"%":0.4},"3.13.9":{"%":0.9},"3.13.10":{"%":0.9},"3.13.11":{"%":0.9},"3.13.12":{"%":0.9},"3.13.13":{"%":0.9},"3.13.14":{"%":0.9},"3.13.15":{"%":0.9},"3.13.16":{"%":0.4}}},"3.14 SYSTEM AND INFORMATION PROTECTION":{"%":0.5,"P":{"3.14.1":{"%":0.9},"3.14.2":{"%":0.4},"3.14.3":{"%":0.9},"3.14.4":{"%":0.4},"3.14.5":{"%":0.9},"3.14.6":{"%":0.4}}}},"%":0.25699705387205385},"800_53 Rev5":{"Table 1 \u2014 Full (Technical) document coverage percentage":{"3.1 ACCESS CONTROL":{"%":0.2670807453416149,"P":{"AC-1":{"%":0.4,"P":{"null":{"%":0.4}}},"AC-2":{"%":0.4,"P":{"AC-2.1":{"%":0.4},"AC-2.2":{"%":0.4},"AC-2.3":{"%":0.4},"AC-2.4":{"%":0.4},"AC-2.5":{"%":0.4},"AC-2.6":{"%":0.4},"AC-2.7":{"%":0.4},"AC-2.8":{"%":0.4},"AC-2.9":{"%":0.4},"AC-2.11":{"%":0.4},"AC-2.12":{"%":0.4},"AC-2.13":{"%":0.4}}},"AC-3":{"%":0.4,"P":{"AC-3.2":{"%":0.4},"AC-3.3":{"%":0.4},"AC-3.4":{"%":0.4},"AC-3.5":{"%":0.4},"AC-3.7":{"%":0.4},"AC-3.8":{"%":0.4},"AC-3.9":{"%":0.4},"AC-3.10":{"%":0.4},"AC-3.11":{"%":0.4},"AC-3.12":{"%":0.4},"AC-3.13":{"%":0.4},"AC-3.14":{"%":0.4},"AC-3.15":{"%":0.4}}},"AC-4":{"%":0.9,"P":{"AC-4.1":{"%":0.9},"AC-4.2":{"%":0.9},"AC-4.3":{"%":0.9},"AC-4.4":{"%":0.9},"AC-4.5":{"%":0.9},"AC-4.6":{"%":0.9},"AC-4.7":{"%":0.9},"AC-4.8":{"%":0.9},"AC-4.9":{"%":0.9},"AC-4.10":{"%":0.9},"AC-4.11":{"%":0.9},"AC-4.12":{"%":0.9},"AC-4.13":{"%":0.9},"AC-4.14":{"%":0.9},"AC-4.15":{"%":0.9},"AC-4.17":{"%":0.9},"AC-4.18":{"%":0.9},"AC-4.19":{"%":0.9},"AC-4.20":{"%":0.9},"AC-4.21":{"%":0.9},"AC-4.22":{"%":0.9},"AC-4.23":{"%":0.9},"AC-4.24":{"%":0.9},"AC-4.25":{"%":0.9},"AC-4.26":{"%":0.9},"AC-4.27":{"%":0.9},"AC-4.28":{"%":0.9},"AC-4.29":{"%":0.9},"AC-4.30":{"%":0.9},"AC-4.31":{"%":0.9},"AC-4.32":{"%":0.9}}},"AC-5":{"%":0.4,"P":{"AC-4.32":{"%":0.4}}},"AC-6":{"%":0.4,"P":{"AC-6.1":{"%":0.4},"AC-6.2":{"%":0.4},"AC-6.3":{"%":0.4},"AC-6.4":{"%":0.4},"AC-6.5":{"%":0.4},"AC-6.6":{"%":0.4},"AC-6.7":{"%":0.4},"AC-6.8":{"%":0.4},"AC-6.9":{"%":0.4},"AC-6.10":{"%":0.4}}},"AC-7":{"%":0.9,"P":{"AC-7.2":{"%":0.9},"AC-7.3":{"%":0.9},"AC-7.4":{"%":0.9}}},"AC-8":{"%":0.9,"P":{"AC-7.4":{"%":0.9}}},"AC-9":{"%":0.9,"P":{"AC-9.1":{"%":0.9},"AC-9.2":{"%":0.9},"AC-9.3":{"%":0.9},"AC-9.4":{"%":0.9}}},"AC-10":{"%":0.9,"P":{"AC-9.4":{"%":0.9}}},"AC-11":{"%":0.9,"P":{"AC-9.4":{"%":0.9}}},"AC-12":{"%":0.9,"P":{"AC-12.1":{"%":0.9},"AC-12.2":{"%":0.9},"AC-13.3":{"%":0.9}}},"AC-14":{"%":0.9,"P":{"AC-13.3":{"%":0.9}}},"AC-16":{"%":0.9,"P":{"AC-16.1":{"%":0.9},"AC-16.2":{"%":0.9},"AC-16.3":{"%":0.9},"AC-16.4":{"%":0.9},"AC-16.5":{"%":0.9},"AC-16.6":{"%":0.9},"AC-16.7":{"%":0.9},"AC-16.8":{"%":0.9},"AC-16.9":{"%":0.9},"AC-16.10":{"%":0.9}}},"AC-17":{"%":0.14285714285714285,"P":{"AC-17.1":{"%":0.9},"AC-17.2":{"%":0.4},"AC-17.3":{"%":0.9},"AC-17.4":{"%":0.9},"AC-17.6":{"%":0.9},"AC-17.9":{"%":0.9},"AC-17.10":{"%":0.9}}},"AC-18":{"%":0.4,"P":{"AC-18.1":{"%":0.4},"AC-18.3":{"%":0.4},"AC-18.4":{"%":0.4},"AC-18.5":{"%":0.4}}},"AC-19":{"%":0.9,"P":{"AC-19.4":{"%":0.9},"AC-19.5":{"%":0.9}}},"AC-20":{"%":0.9,"P":{"AC-20.1":{"%":0.9},"AC-20.2":{"%":0.9},"AC-20.3":{"%":0.9},"AC-20.4":{"%":0.9},"AC-20.5":{"%":0.9}}},"AC-21":{"%":0.9,"P":{"AC-21.1":{"%":0.9},"AC-21.2":{"%":0.9}}},"AC-22":{"%":0.9,"P":{"AC-21.2":{"%":0.9}}},"AC-23":{"%":0.9,"P":{"AC-21.2":{"%":0.9}}},"AC-24":{"%":0.9,"P":{"AC-24.1":{"%":0.9},"AC-24.2":{"%":0.9}}},"AC-25":{"%":0.9,"P":{"AC-24.2":{"%":0.9}}}}},"3.2 AWARENESS AND TRAINING":{"%":0.9,"P":{"AT-1":{"%":0.9,"P":{"null":{"%":0.9}}},"AT-2":{"%":0.9,"P":{"AT-2.1":{"%":0.9},"AT-2.2":{"%":0.9},"AT-2.3":{"%":0.9},"AT-2.4":{"%":0.9},"AT-2.5":{"%":0.9},"AT-2.6":{"%":0.9}}},"AT-3":{"%":0.9,"P":{"AT-3.1":{"%":0.9},"AT-3.2":{"%":0.9},"AT-3.3":{"%":0.9},"AT-3.5":{"%":0.9}}},"AT-4":{"%":0.9,"P":{"AT-3.5":{"%":0.9}}},"AT-6":{"%":0.9,"P":{"AT-3.5":{"%":0.9}}}}},"3.3 AUDIT AND ACCOUNTABILITY":{"%":0.40952380952380957,"P":{"AU-1":{"%":0.9,"P":{"null":{"%":0.9}}},"AU-2":{"%":0.4,"P":{"null":{"%":0.4}}},"AU-3":{"%":0.4,"P":{"AU-3.1":{"%":0.4},"AU-3.2":{"%":0.4},"AU-3.3":{"%":0.4}}},"AU-4":{"%":0.9,"P":{"AU-3.3":{"%":0.9}}},"AU-5":{"%":0.9,"P":{"AU-5.1":{"%":0.9},"AU-5.2":{"%":0.9},"AU-5.3":{"%":0.9},"AU-5.4":{"%":0.9},"AU-5.5":{"%":0.9}}},"AU-6":{"%":0.4,"P":{"AU-6.1":{"%":0.4},"AU-6.3":{"%":0.4},"AU-6.4":{"%":0.4},"AU-6.5":{"%":0.4},"AU-6.6":{"%":0.4},"AU-6.7":{"%":0.4},"AU-6.8":{"%":0.4},"AU-6.9":{"%":0.4}}},"AU-7":{"%":0.4,"P":{"AU-6.9":{"%":0.4}}},"AU-8":{"%":0.9,"P":{"AU-6.9":{"%":0.9}}},"AU-9":{"%":0.14285714285714285,"P":{"AU-9.1":{"%":0.9},"AU-9.2":{"%":0.9},"AU-9.3":{"%":0.9},"AU-9.4":{"%":0.4},"AU-9.5":{"%":0.9},"AU-9.6":{"%":0.9},"AU-9.7":{"%":0.9}}},"AU-10":{"%":0.9,"P":{"AU-10.1":{"%":0.9},"AU-10.2":{"%":0.9},"AU-10.3":{"%":0.9},"AU-10.4":{"%":0.9}}},"AU-11":{"%":0.4,"P":{"AU-10.4":{"%":0.4}}},"AU-12":{"%":0.4,"P":{"AU-12.1":{"%":0.4},"AU-12.2":{"%":0.4},"AU-12.3":{"%":0.4},"AU-12.4":{"%":0.4}}},"AU-13":{"%":0.9,"P":{"AU-13.1":{"%":0.9},"AU-13.2":{"%":0.9},"AU-13.3":{"%":0.9}}},"AU-14":{"%":0.9,"P":{"AU-14.1":{"%":0.9},"AU-14.3":{"%":0.9}}},"AU-16":{"%":0.9,"P":{"AU-16.1":{"%":0.9},"AU-16.2":{"%":0.9},"AU-16.3":{"%":0.9}}}}},"3.4 ASSESSMENT, AUTHORIZATION, AND MONITORING":{"%":0.125,"P":{"CA-1":{"%":0.9,"P":{"null":{"%":0.9}}},"CA-2":{"%":0.9,"P":{"CA-2.1":{"%":0.9},"CA-2.2":{"%":0.9},"CA-2.3":{"%":0.9}}},"CA-3":{"%":0.9,"P":{"CA-3.6":{"%":0.9},"CA-3.7":{"%":0.9}}},"CA-5":{"%":0.9,"P":{"CA-3.7":{"%":0.9}}},"CA-6":{"%":0.9,"P":{"CA-6.1":{"%":0.9},"CA-6.2":{"%":0.9}}},"CA-7":{"%":0.9,"P":{"CA-1.1":{"%":0.9},"CA-1.3":{"%":0.9},"CA-1.4":{"%":0.9},"CA-1.5":{"%":0.9},"CA-1.6":{"%":0.9}}},"CA-8":{"%":0.9,"P":{"CA-8.1":{"%":0.9},"CA-8.2":{"%":0.9},"CA-8.3":{"%":0.9}}},"CA-9":{"%":0.4,"P":{"CA-8.3":{"%":0.4}}}}},"3.5 CONFIGURATION MANAGEMENT":{"%":0.5,"P":{"CM-1":{"%":0.4,"P":{"null":{"%":0.4}}},"CM-2":{"%":0.4,"P":{"CM-2.2":{"%":0.4},"CM-2.3":{"%":0.4},"CM-2.6":{"%":0.4},"CM-2.7":{"%":0.4}}},"CM-3":{"%":0.9,"P":{"CM-3.1":{"%":0.9},"CM-3.2":{"%":0.9},"CM-3.3":{"%":0.9},"CM-3.4":{"%":0.9},"CM-3.5":{"%":0.9},"CM-3.6":{"%":0.9},"CM-3.7":{"%":0.9},"CM-3.8":{"%":0.9}}},"CM-4":{"%":0.9,"P":{"CM-4.1":{"%":0.9},"CM-4.2":{"%":0.9}}},"CM-5":{"%":0.9,"P":{"CM-5.1":{"%":0.9},"CM-5.2":{"%":0.9},"CM-5.3":{"%":0.9},"CM-5.4":{"%":0.9},"CM-5.5":{"%":0.9},"CM-5.6":{"%":0.9}}},"CM-6":{"%":0.4,"P":{"CM-6.1":{"%":0.4},"CM-6.2":{"%":0.4}}},"CM-7":{"%":0.4,"P":{"CM-7.1":{"%":0.4},"CM-7.2":{"%":0.4},"CM-7.3":{"%":0.4},"CM-7.4":{"%":0.4},"CM-7.5":{"%":0.4},"CM-7.6":{"%":0.4},"CM-7.7":{"%":0.4},"CM-7.8":{"%":0.4},"CM-7.9":{"%":0.4}}},"CM-8":{"%":0.4,"P":{"CM-8.1":{"%":0.4},"CM-8.2":{"%":0.4},"CM-8.3":{"%":0.4},"CM-8.4":{"%":0.4},"CM-8.6":{"%":0.4},"CM-8.7":{"%":0.4},"CM-8.8":{"%":0.4},"CM-8.9":{"%":0.4}}},"CM-9":{"%":0.4,"P":{"CM-8.9":{"%":0.4}}},"CM-10":{"%":0.9,"P":{"CM-8.9":{"%":0.9}}},"CM-11":{"%":0.9,"P":{"CM-11.2":{"%":0.9},"CM-11.3":{"%":0.9}}},"CM-12":{"%":0.4,"P":{"CM-11.3":{"%":0.4}}},"CM-13":{"%":0.9,"P":{"CM-11.3":{"%":0.9}}},"CM-14":{"%":0.9,"P":{"CM-11.3":{"%":0.9}}}}},"3.6 CONTINGENCY PLANNING":{"%":0.3333333333333333,"P":{"CP-1":{"%":0.9,"P":{"null":{"%":0.9}}},"CP-2":{"%":0.4,"P":{"CP-2.1":{"%":0.4},"CP-2.2":{"%":0.4},"CP-2.3":{"%":0.4},"CP-2.5":{"%":0.4},"CP-2.6":{"%":0.4},"CP-2.7":{"%":0.4},"CP-2.8":{"%":0.4}}},"CP-3":{"%":0.9,"P":{"CP-3.1":{"%":0.9},"CP-3.2":{"%":0.9}}},"CP-4":{"%":0.9,"P":{"CP-4.1":{"%":0.9},"CP-4.2":{"%":0.9},"CP-4.3":{"%":0.9},"CP-4.4":{"%":0.9},"CP-4.5":{"%":0.9}}},"CP-6":{"%":0.4,"P":{"CP-6.1":{"%":0.4},"CP-6.2":{"%":0.4},"CP-6.3":{"%":0.4}}},"CP-7":{"%":0.9,"P":{"CP-7.1":{"%":0.9},"CP-7.2":{"%":0.9},"CP-7.3":{"%":0.9},"CP-7.4":{"%":0.9},"CP-7.6":{"%":0.9}}},"CP-8":{"%":0.9,"P":{"CP-8.1":{"%":0.9},"CP-8.2":{"%":0.9},"CP-8.3":{"%":0.9},"CP-8.4":{"%":0.9},"CP-8.5":{"%":0.9}}},"CP-9":{"%":0.4,"P":{"CP-9.1":{"%":0.4},"CP-9.2":{"%":0.4},"CP-9.3":{"%":0.4},"CP-9.5":{"%":0.4},"CP-9.6":{"%":0.4},"CP-9.7":{"%":0.4},"CP-9.8":{"%":0.4}}},"CP-10":{"%":0.4,"P":{"CP-10.2":{"%":0.4},"CP-10.4":{"%":0.4},"CP-10.6":{"%":0.4}}},"CP-11":{"%":0.9,"P":{"CP-10.6":{"%":0.9}}},"CP-12":{"%":0.9,"P":{"CP-10.6":{"%":0.9}}},"CP-13":{"%":0.9,"P":{"CP-10.6":{"%":0.9}}}}},"3.7 IDENTIFICATION AND AUTHENTICATION":{"%":0.16666666666666666,"P":{"IA-1":{"%":0.9,"P":{"IA-1.1":{"%":0.9},"IA-1.2":{"%":0.9},"IA-1.5":{"%":0.9},"IA-1.6":{"%":0.9},"IA-1.8":{"%":0.9},"IA-1.9":{"%":0.9},"IA-1.10":{"%":0.9},"IA-1.11":{"%":0.9},"IA-1.12":{"%":0.9}}},"IA-2":{"%":0.9,"P":{"IA-2.1":{"%":0.9},"IA-2.2":{"%":0.9},"IA-2.5":{"%":0.9},"IA-2.6":{"%":0.9},"IA-2.8":{"%":0.9},"IA-2.10":{"%":0.9},"IA-2.12":{"%":0.9},"IA-2.13":{"%":0.9}}},"IA-3":{"%":0.9,"P":{"IA-3.1":{"%":0.9},"IA-3.3":{"%":0.9},"IA-3.4":{"%":0.9}}},"IA-4":{"%":0.4,"P":{"IA-4.1":{"%":0.4},"IA-4.4":{"%":0.4},"IA-4.5":{"%":0.4},"IA-4.6":{"%":0.4},"IA-4.8":{"%":0.4},"IA-4.9":{"%":0.4}}},"IA-5":{"%":0.4,"P":{"IA-5.1":{"%":0.4},"IA-5.2":{"%":0.4},"IA-5.5":{"%":0.4},"IA-5.6":{"%":0.4},"IA-5.7":{"%":0.4},"IA-5.8":{"%":0.4},"IA-5.9":{"%":0.4},"IA-5.10":{"%":0.4},"IA-5.12":{"%":0.4},"IA-5.13":{"%":0.4},"IA-5.14":{"%":0.4},"IA-5.15":{"%":0.4},"IA-5.16":{"%":0.4},"IA-5.17":{"%":0.4},"IA-5.18":{"%":0.4}}},"IA-6":{"%":0.9,"P":{"IA-5.18":{"%":0.9}}},"IA-7":{"%":0.9,"P":{"IA-5.18":{"%":0.9}}},"IA-8":{"%":0.9,"P":{"IA-8.1":{"%":0.9},"IA-8.2":{"%":0.9},"IA-8.4":{"%":0.9},"IA-8.5":{"%":0.9},"IA-8.6":{"%":0.9}}},"IA-9":{"%":0.9,"P":{"IA-8.6":{"%":0.9}}},"IA-10":{"%":0.9,"P":{"IA-8.6":{"%":0.9}}},"IA-11":{"%":0.9,"P":{"IA-8.6":{"%":0.9}}},"IA-12":{"%":0.9,"P":{"IA-12.1":{"%":0.9},"IA-12.2":{"%":0.9},"IA-12.3":{"%":0.9},"IA-12.4":{"%":0.9},"IA-12.5":{"%":0.9},"IA-12.6":{"%":0.9}}}}},"3.8 INCIDENT RESPONSE":{"%":0.4444444444444444,"P":{"IR-1":{"%":0.4,"P":{"null":{"%":0.4}}},"IR-2":{"%":0.9,"P":{"IR-2.1":{"%":0.9},"IR-2.2":{"%":0.9},"IR-2.3":{"%":0.9}}},"IR-3":{"%":0.9,"P":{"IR-3.1":{"%":0.9},"IR-3.2":{"%":0.9},"IR-3.3":{"%":0.9}}},"IR-4":{"%":0.9,"P":{"IR-4.1":{"%":0.9},"IR-4.2":{"%":0.9},"IR-4.3":{"%":0.9},"IR-4.4":{"%":0.9},"IR-4.5":{"%":0.9},"IR-4.6":{"%":0.9},"IR-4.7":{"%":0.9},"IR-4.8":{"%":0.9},"IR-4.9":{"%":0.9},"IR-4.10":{"%":0.9},"IR-4.11":{"%":0.9},"IR-4.12":{"%":0.9},"IR-4.13":{"%":0.9},"IR-4.14":{"%":0.9},"IR-4.15":{"%":0.9}}},"IR-5":{"%":0.9,"P":{"IR-4.15":{"%":0.9}}},"IR-6":{"%":0.4,"P":{"IR-6.1":{"%":0.4},"IR-6.2":{"%":0.4},"IR-6.3":{"%":0.4}}},"IR-7":{"%":0.4,"P":{"IR-7.1":{"%":0.4},"IR-7.2":{"%":0.4}}},"IR-8":{"%":0.4,"P":{"IR-7.2":{"%":0.4}}},"IR-9":{"%":0.9,"P":{"IR-9.2":{"%":0.9},"IR-9.3":{"%":0.9},"IR-9.4":{"%":0.9}}}}},"3.9 MAINTENANCE":{"%":0.14285714285714285,"P":{"MA-1":{"%":0.9,"P":{"null":{"%":0.9}}},"MA-2":{"%":0.9,"P":{"null":{"%":0.9}}},"MA-3":{"%":0.9,"P":{"MA-3.1":{"%":0.9},"MA-3.2":{"%":0.9},"MA-3.3":{"%":0.9},"MA-3.4":{"%":0.9},"MA-3.5":{"%":0.9},"MA-3.6":{"%":0.9}}},"MA-4":{"%":0.4,"P":{"MA-4.1":{"%":0.4},"MA-4.3":{"%":0.4},"MA-4.4":{"%":0.4},"MA-4.5":{"%":0.4},"MA-4.6":{"%":0.4},"MA-4.7":{"%":0.4}}},"MA-5":{"%":0.9,"P":{"MA-5.1":{"%":0.9},"MA-5.2":{"%":0.9},"MA-5.3":{"%":0.9},"MA-5.4":{"%":0.9},"MA-5.5":{"%":0.9}}},"MA-6":{"%":0.9,"P":{"MA-6.1":{"%":0.9},"MA-6.2":{"%":0.9},"MA-6.3":{"%":0.9}}},"MA-7":{"%":0.9,"P":{"MA-6.3":{"%":0.9}}}}},"3.10 MEDIA PROTECTION":{"%":0.125,"P":{"MP-1":{"%":0.9,"P":{"null":{"%":0.9}}},"MP-2":{"%":0.4,"P":{"null":{"%":0.4}}},"MP-3":{"%":0.9,"P":{"null":{"%":0.9}}},"MP-4":{"%":0.9,"P":{"null":{"%":0.9}}},"MP-5":{"%":0.9,"P":{"null":{"%":0.9}}},"MP-6":{"%":0.9,"P":{"MP-6.1":{"%":0.9},"MP-6.2":{"%":0.9},"MP-6.3":{"%":0.9},"MP-6.7":{"%":0.9},"MP-6.8":{"%":0.9}}},"MP-7":{"%":0.9,"P":{"MP-6.8":{"%":0.9}}},"MP-8":{"%":0.9,"P":{"MP-8.1":{"%":0.9},"MP-8.2":{"%":0.9},"MP-8.3":{"%":0.9},"MP-8.4":{"%":0.9}}}}},"3.11 PHYSICAL AND ENVIRONMENTAL PROTECTION":{"%":0.9,"P":{"PE-1":{"%":0.9,"P":{"null":{"%":0.9}}},"PE-2":{"%":0.9,"P":{"PE-2.1":{"%":0.9},"PE-2.2":{"%":0.9},"PE-2.3":{"%":0.9}}},"PE-3":{"%":0.9,"P":{"PE-3.1":{"%":0.9},"PE-3.2":{"%":0.9},"PE-3.3":{"%":0.9},"PE-3.4":{"%":0.9},"PE-3.5":{"%":0.9},"PE-3.7":{"%":0.9},"PE-3.8":{"%":0.9}}},"PE-4":{"%":0.9,"P":{"PE-3.8":{"%":0.9}}},"PE-5":{"%":0.9,"P":{"PE-3.8":{"%":0.9}}},"PE-6":{"%":0.9,"P":{"PE-6.1":{"%":0.9},"PE-6.2":{"%":0.9},"PE-6.3":{"%":0.9},"PE-6.4":{"%":0.9}}},"PE-8":{"%":0.9,"P":{"PE-8.1":{"%":0.9},"PE-8.3":{"%":0.9}}},"PE-9":{"%":0.9,"P":{"PE-9.1":{"%":0.9},"PE-9.2":{"%":0.9}}},"PE-10":{"%":0.9,"P":{"PE-9.2":{"%":0.9}}},"PE-11":{"%":0.9,"P":{"PE-11.1":{"%":0.9},"PE-11.2":{"%":0.9}}},"PE-12":{"%":0.9,"P":{"PE-11.2":{"%":0.9}}},"PE-13":{"%":0.9,"P":{"PE-13.1":{"%":0.9},"PE-13.2":{"%":0.9},"PE-13.4":{"%":0.9}}},"PE-14":{"%":0.9,"P":{"PE-14.1":{"%":0.9},"PE-14.2":{"%":0.9}}},"PE-15":{"%":0.9,"P":{"PE-14.2":{"%":0.9}}},"PE-16":{"%":0.9,"P":{"PE-14.2":{"%":0.9}}},"PE-17":{"%":0.9,"P":{"PE-14.2":{"%":0.9}}},"PE-18":{"%":0.9,"P":{"PE-14.2":{"%":0.9}}},"PE-19":{"%":0.9,"P":{"PE-14.2":{"%":0.9}}},"PE-20":{"%":0.9,"P":{"PE-14.2":{"%":0.9}}},"PE-21":{"%":0.9,"P":{"PE-14.2":{"%":0.9}}},"PE-22":{"%":0.9,"P":{"PE-14.2":{"%":0.9}}},"PE-23":{"%":0.9,"P":{"PE-14.2":{"%":0.9}}}}},"3.12 PLANNING":{"%":0.125,"P":{"PL-1":{"%":0.9,"P":{"null":{"%":0.9}}},"PL-2":{"%":0.9,"P":{"null":{"%":0.9}}},"PL-4":{"%":0.9,"P":{"null":{"%":0.9}}},"PL-7":{"%":0.9,"P":{"null":{"%":0.9}}},"PL-8":{"%":0.4,"P":{"PL-8.1":{"%":0.4},"PL-8.2":{"%":0.4}}},"PL-9":{"%":0.9,"P":{"PL-8.2":{"%":0.9}}},"PL-10":{"%":0.9,"P":{"PL-8.2":{"%":0.9}}},"PL-11":{"%":0.9,"P":{"PL-8.2":{"%":0.9}}}}},"3.13 PROGRAM MANAGEMENT":{"%":0.93125,"P":{"PM-1":{"%":0.9,"P":{"null":{"%":0.9}}},"PM-2":{"%":0.9,"P":{"null":{"%":0.9}}},"PM-3":{"%":0.9,"P":{"null":{"%":0.9}}},"PM-4":{"%":0.9,"P":{"null":{"%":0.9}}},"PM-5":{"%":0.4,"P":{"null":{"%":0.4}}},"PM-6":{"%":0.9,"P":{"null":{"%":0.9}}},"PM-7":{"%":0.9,"P":{"null":{"%":0.9}}},"PM-8":{"%":0.9,"P":{"null":{"%":0.9}}},"PM-9":{"%":0.9,"P":{"null":{"%":0.9}}},"PM-10":{"%":0.9,"P":{"null":{"%":0.9}}},"PM-11":{"%":0.9,"P":{"null":{"%":0.9}}},"PM-12":{"%":0.9,"P":{"null":{"%":0.9}}},"PM-13":{"%":0.9,"P":{"null":{"%":0.9}}},"PM-14":{"%":0.9,"P":{"null":{"%":0.9}}},"PM-15":{"%":0.9,"P":{"null":{"%":0.9}}},"PM-16":{"%":0.9,"P":{"null":{"%":0.9}}},"PM-17":{"%":0.9,"P":{"null":{"%":0.9}}},"PM-18":{"%":0.9,"P":{"null":{"%":0.9}}},"PM-19":{"%":0.9,"P":{"null":{"%":0.9}}},"PM-20":{"%":0.9,"P":{"null":{"%":0.9}}},"PM-21":{"%":0.9,"P":{"null":{"%":0.9}}},"PM-22":{"%":0.9,"P":{"null":{"%":0.9}}},"PM-23":{"%":0.9,"P":{"null":{"%":0.9}}},"PM-24":{"%":0.9,"P":{"null":{"%":0.9}}},"PM-25":{"%":0.9,"P":{"null":{"%":0.9}}},"PM-26":{"%":0.9,"P":{"null":{"%":0.9}}},"PM-27":{"%":0.9,"P":{"null":{"%":0.9}}},"PM-28":{"%":0.9,"P":{"null":{"%":0.9}}},"PM-29":{"%":0.9,"P":{"null":{"%":0.9}}},"PM-30":{"%":0.9,"P":{"null":{"%":0.9}}},"PM-31":{"%":0.9,"P":{"null":{"%":0.9}}},"PM-32":{"%":0.9,"P":{"null":{"%":0.9}}}}},"3.14 PERSONNEL SECURITY":{"%":0.9,"P":{"PS-1":{"%":0.9,"P":{"null":{"%":0.9}}},"PS-2":{"%":0.9,"P":{"null":{"%":0.9}}},"PS-3":{"%":0.9,"P":{"PS-3.1":{"%":0.9},"PS-3.2":{"%":0.9},"PS-3.3":{"%":0.9},"PS-3.4":{"%":0.9}}},"PS-4":{"%":0.9,"P":{"PS-4.1":{"%":0.9},"PS-4.2":{"%":0.9}}},"PS-5":{"%":0.9,"P":{"PS-4.2":{"%":0.9}}},"PS-6":{"%":0.9,"P":{"PS-6.2":{"%":0.9},"PS-6.3":{"%":0.9}}},"PS-7":{"%":0.9,"P":{"PS-6.3":{"%":0.9}}},"PS-8":{"%":0.9,"P":{"PS-6.3":{"%":0.9}}},"PS-9":{"%":0.9,"P":{"PS-6.3":{"%":0.9}}}}},"3.15 PERSONALLY IDENTIFIABLE INFORMATION PROCESSING AND TRANSPARENCY":{"%":0.9,"P":{"PT-1":{"%":0.9,"P":{"null":{"%":0.9}}},"PT-2":{"%":0.9,"P":{"PT-2.1":{"%":0.9},"PT-2.2":{"%":0.9}}},"PT-3":{"%":0.9,"P":{"PT-3.1":{"%":0.9},"PT-3.2":{"%":0.9}}},"PT-4":{"%":0.9,"P":{"PT-4.1":{"%":0.9},"PT-4.2":{"%":0.9},"PT-4.3":{"%":0.9}}},"PT-5":{"%":0.9,"P":{"PT-5.1":{"%":0.9},"PT-5.2":{"%":0.9}}},"PT-6":{"%":0.9,"P":{"PT-6.1":{"%":0.9},"PT-6.2":{"%":0.9}}},"PT-7":{"%":0.9,"P":{"PT-7.1":{"%":0.9},"PT-7.2":{"%":0.9}}},"PT-8":{"%":0.9,"P":{"PT-7.2":{"%":0.9}}}}},"3.16 RISK ASSESSMENT":{"%":0.2222222222222222,"P":{"RA-1":{"%":0.9,"P":{"null":{"%":0.9}}},"RA-2":{"%":0.4,"P":{"null":{"%":0.4}}},"RA-3":{"%":0.9,"P":{"RA-3.1":{"%":0.9},"RA-3.2":{"%":0.9},"RA-3.3":{"%":0.9},"RA-3.4":{"%":0.9}}},"RA-5":{"%":0.4,"P":{"RA-5.2":{"%":0.4},"RA-5.3":{"%":0.4},"RA-5.4":{"%":0.4},"RA-5.5":{"%":0.4},"RA-5.6":{"%":0.4},"RA-5.8":{"%":0.4},"RA-5.10":{"%":0.4},"RA-5.11":{"%":0.4}}},"RA-6":{"%":0.9,"P":{"RA-5.11":{"%":0.9}}},"RA-7":{"%":0.9,"P":{"RA-5.11":{"%":0.9}}},"RA-8":{"%":0.9,"P":{"RA-5.11":{"%":0.9}}},"RA-9":{"%":0.9,"P":{"RA-5.11":{"%":0.9}}},"RA-10":{"%":0.9,"P":{"RA-5.11":{"%":0.9}}}}},"3.17 SYSTEM AND SERVICES ACQUISITION":{"%":0.1875,"P":{"SA-1":{"%":0.9,"P":{"null":{"%":0.9}}},"SA-2":{"%":0.9,"P":{"null":{"%":0.9}}},"SA-3":{"%":0.4,"P":{"SA-3.1":{"%":0.4},"SA-3.2":{"%":0.4},"SA-3.3":{"%":0.4}}},"SA-4":{"%":0.9,"P":{"SA-4.1":{"%":0.9},"SA-4.2":{"%":0.9},"SA-4.3":{"%":0.9},"SA-4.5":{"%":0.9},"SA-4.6":{"%":0.9},"SA-4.7":{"%":0.9},"SA-4.8":{"%":0.9},"SA-4.9":{"%":0.9},"SA-4.10":{"%":0.9},"SA-4.11":{"%":0.9},"SA-4.12":{"%":0.9}}},"SA-5":{"%":0.9,"P":{"SA-4.12":{"%":0.9}}},"SA-8":{"%":0.4,"P":{"SA-8.1":{"%":0.4},"SA-8.2":{"%":0.4},"SA-8.3":{"%":0.4},"SA-8.4":{"%":0.4},"SA-8.5":{"%":0.4},"SA-8.6":{"%":0.4},"SA-8.7":{"%":0.4},"SA-8.8":{"%":0.4},"SA-8.9":{"%":0.4},"SA-8.10":{"%":0.4},"SA-8.11":{"%":0.4},"SA-8.12":{"%":0.4},"SA-8.13":{"%":0.4},"SA-8.14":{"%":0.4},"SA-8.15":{"%":0.4},"SA-8.16":{"%":0.4},"SA-8.17":{"%":0.4},"SA-8.18":{"%":0.4},"SA-8.19":{"%":0.4},"SA-8.20":{"%":0.4},"SA-8.21":{"%":0.4},"SA-8.22":{"%":0.4},"SA-8.23":{"%":0.4},"SA-8.24":{"%":0.4},"SA-8.25":{"%":0.4},"SA-8.26":{"%":0.4},"SA-8.27":{"%":0.4},"SA-8.28":{"%":0.4},"SA-8.29":{"%":0.4},"SA-8.30":{"%":0.4},"SA-8.31":{"%":0.4},"SA-8.32":{"%":0.4},"SA-8.33":{"%":0.4}}},"SA-9":{"%":0.9,"P":{"SA-9.1":{"%":0.9},"SA-9.2":{"%":0.9},"SA-9.3":{"%":0.9},"SA-9.4":{"%":0.9},"SA-9.5":{"%":0.9},"SA-9.6":{"%":0.9},"SA-9.7":{"%":0.9},"SA-9.8":{"%":0.9}}},"SA-10":{"%":0.4,"P":{"SA-10.1":{"%":0.4},"SA-10.2":{"%":0.4},"SA-10.3":{"%":0.4},"SA-10.4":{"%":0.4},"SA-13":{"%":0.4},"SA-10.6":{"%":0.4},"SA-10.7":{"%":0.4}}},"SA-11":{"%":0.9,"P":{"SA-11.1":{"%":0.9},"SA-11.2":{"%":0.9},"SA-11.3":{"%":0.9},"SA-11.4":{"%":0.9},"SA-11.5":{"%":0.9},"SA-11.6":{"%":0.9},"SA-11.7":{"%":0.9},"SA-11.8":{"%":0.9},"SA-11.9":{"%":0.9}}},"SA-15":{"%":0.9,"P":{"SA-15.1":{"%":0.9},"SA-15.2":{"%":0.9},"SA-15.3":{"%":0.9},"SA-15.5":{"%":0.9},"SA-15.6":{"%":0.9},"SA-15.7":{"%":0.9},"SA-15.8":{"%":0.9},"SA-15.10":{"%":0.9},"SA-15.11":{"%":0.9},"SA-15.12":{"%":0.9}}},"SA-16":{"%":0.9,"P":{"SA-15.12":{"%":0.9}}},"SA-17":{"%":0.9,"P":{"SA-17.1":{"%":0.9},"SA-17.2":{"%":0.9},"SA-17.3":{"%":0.9},"SA-17.4":{"%":0.9},"SA-17.5":{"%":0.9},"SA-17.6":{"%":0.9},"SA-17.7":{"%":0.9},"SA-17.8":{"%":0.9},"SA-17.9":{"%":0.9}}},"SA-20":{"%":0.9,"P":{"SA-17.9":{"%":0.9}}},"SA-21":{"%":0.9,"P":{"SA-17.9":{"%":0.9}}},"SA-22":{"%":0.9,"P":{"SA-17.9":{"%":0.9}}},"SA-23":{"%":0.9,"P":{"SA-17.9":{"%":0.9}}}}},"3.18 SYSTEM AND COMMUNICATIONS PROTECTION":{"%":0.9851063829787234,"P":{"SC-1":{"%":0.9,"P":{"null":{"%":0.9}}},"SC-2":{"%":0.9,"P":{"SC-2.1":{"%":0.9},"SC-2.2":{"%":0.9}}},"SC-3":{"%":0.9,"P":{"SC-3.1":{"%":0.9},"SC-3.2":{"%":0.9},"SC-3.3":{"%":0.9},"SC-3.4":{"%":0.9},"SC-3.5":{"%":0.9}}},"SC-4":{"%":0.9,"P":{"SC-3.5":{"%":0.9}}},"SC-5":{"%":0.9,"P":{"SC-5.1":{"%":0.9},"SC-5.2":{"%":0.9},"SC-5.3":{"%":0.9}}},"SC-6":{"%":0.9,"P":{"SC-5.3":{"%":0.9}}},"SC-7":{"%":0.4,"P":{"SC-7.3":{"%":0.4},"SC-7.4":{"%":0.4},"SC-7.5":{"%":0.4},"SC-7.7":{"%":0.4},"SC-7.8":{"%":0.4},"SC-7.9":{"%":0.4},"SC-7.10":{"%":0.4},"SC-7.11":{"%":0.4},"SC-7.12":{"%":0.4},"SC-7.13":{"%":0.4},"SC-7.14":{"%":0.4},"SC-7.15":{"%":0.4},"SC-7.16":{"%":0.4},"SC-7.17":{"%":0.4},"SC-7.18":{"%":0.4},"SC-7.19":{"%":0.4},"SC-7.20":{"%":0.4},"SC-7.21":{"%":0.4},"SC-7.22":{"%":0.4},"SC-7.23":{"%":0.4},"SC-7.24":{"%":0.4},"SC-7.25":{"%":0.4},"SC-7.26":{"%":0.4},"SC-7.27":{"%":0.4},"SC-7.28":{"%":0.4},"SC-7.29":{"%":0.4}}},"SC-8":{"%":0.4,"P":{"SC-8.1":{"%":0.4},"SC-8.2":{"%":0.4},"SC-8.3":{"%":0.4},"SC-8.4":{"%":0.4},"SC-8.5":{"%":0.4}}},"SC-10":{"%":0.9,"P":{"SC-8.5":{"%":0.9}}},"SC-11":{"%":0.9,"P":{"SC-8.5":{"%":0.9}}},"SC-12":{"%":0.9,"P":{"SC-12.1":{"%":0.9},"SC-12.2":{"%":0.9},"SC-12.3":{"%":0.9},"SC-12.6":{"%":0.9}}},"SC-13":{"%":0.9,"P":{"SC-12.6":{"%":0.9}}},"SC-15":{"%":0.9,"P":{"SC-15.1":{"%":0.9},"SC-15.3":{"%":0.9},"SC-15.4":{"%":0.9}}},"SC-16":{"%":0.9,"P":{"SC-16.1":{"%":0.9},"SC-16.2":{"%":0.9},"SC-16.3":{"%":0.9}}},"SC-17":{"%":0.9,"P":{"SC-16.3":{"%":0.9}}},"SC-18":{"%":0.9,"P":{"SC-18.1":{"%":0.9},"SC-18.2":{"%":0.9},"SC-18.3":{"%":0.9},"SC-18.4":{"%":0.9},"SC-18.5":{"%":0.9}}},"SC-20":{"%":0.9,"P":{"SC-18.5":{"%":0.9}}},"SC-21":{"%":0.9,"P":{"SC-18.5":{"%":0.9}}},"SC-22":{"%":0.9,"P":{"SC-18.5":{"%":0.9}}},"SC-23":{"%":0.4,"P":{"SC-23.1":{"%":0.4},"SC-23.3":{"%":0.4},"SC-23.5":{"%":0.4}}},"SC-24":{"%":0.9,"P":{"SC-23.5":{"%":0.9}}},"SC-25":{"%":0.9,"P":{"SC-23.5":{"%":0.9}}},"SC-26":{"%":0.9,"P":{"SC-23.5":{"%":0.9}}},"SC-27":{"%":0.9,"P":{"SC-23.5":{"%":0.9}}},"SC-28":{"%":0.4,"P":{"SC-28.1":{"%":0.4},"SC-28.2":{"%":0.4},"SC-28.3":{"%":0.4}}},"SC-29":{"%":0.9,"P":{"SC-28.3":{"%":0.9}}},"SC-30":{"%":0.9,"P":{"SC-30.2":{"%":0.9},"SC-30.3":{"%":0.9},"SC-30.4":{"%":0.9},"SC-30.5":{"%":0.9}}},"SC-31":{"%":0.9,"P":{"SC-31.1":{"%":0.9},"SC-31.2":{"%":0.9},"SC-31.3":{"%":0.9}}},"SC-32":{"%":0.9,"P":{"SC-31.3":{"%":0.9}}},"SC-34":{"%":0.9,"P":{"SC-34.1":{"%":0.9},"SC-34.2":{"%":0.9}}},"SC-35":{"%":0.9,"P":{"SC-34.2":{"%":0.9}}},"SC-36":{"%":0.9,"P":{"SC-36.1":{"%":0.9},"SC-36.2":{"%":0.9}}},"SC-37":{"%":0.9,"P":{"SC-36.2":{"%":0.9}}},"SC-38":{"%":0.9,"P":{"SC-36.2":{"%":0.9}}},"SC-39":{"%":0.9,"P":{"SC-39.1":{"%":0.9},"SC-39.2":{"%":0.9}}},"SC-40":{"%":0.9,"P":{"SC-40.1":{"%":0.9},"SC-40.2":{"%":0.9},"SC-40.3":{"%":0.9},"SC-40.4":{"%":0.9}}},"SC-41":{"%":0.9,"P":{"SC-40.4":{"%":0.9}}},"SC-42":{"%":0.9,"P":{"SC-42.1":{"%":0.9},"SC-42.2":{"%":0.9},"SC-42.4":{"%":0.9},"SC-42.5":{"%":0.9}}},"SC-43":{"%":0.9,"P":{"SC-42.5":{"%":0.9}}},"SC-44":{"%":0.9,"P":{"SC-42.5":{"%":0.9}}},"SC-45":{"%":0.9,"P":{"SC-45.1":{"%":0.9},"SC-45.2":{"%":0.9}}},"SC-46":{"%":0.9,"P":{"SC-45.2":{"%":0.9}}},"SC-47":{"%":0.9,"P":{"SC-45.2":{"%":0.9}}},"SC-48":{"%":0.9,"P":{"SC-45.2":{"%":0.9}}},"SC-49":{"%":0.9,"P":{"SC-45.2":{"%":0.9}}},"SC-50":{"%":0.9,"P":{"SC-45.2":{"%":0.9}}},"SC-51":{"%":0.9,"P":{"SC-45.2":{"%":0.9}}}}},"3.19 SYSTEM AND INFORMATION INTEGRITY":{"%":0.13043478260869565,"P":{"SI-1":{"%":0.9,"P":{"null":{"%":0.9}}},"SI-2":{"%":0.9,"P":{"SI-2.2":{"%":0.9},"SI-2.3":{"%":0.9},"SI-2.4":{"%":0.9},"SI-2.5":{"%":0.9},"SI-2.6":{"%":0.9}}},"SI-3":{"%":0.4,"P":{"SI-3.4":{"%":0.4},"SI-3.6":{"%":0.4},"SI-3.8":{"%":0.4},"SI-3.10":{"%":0.4}}},"SI-4":{"%":0.4,"P":{"SI-4.1":{"%":0.4},"SI-4.2":{"%":0.4},"SI-4.3":{"%":0.4},"SI-4.4":{"%":0.4},"SI-4.5":{"%":0.4},"SI-4.7":{"%":0.4},"SI-4.9":{"%":0.4},"SI-4.10":{"%":0.4},"SI-4.11":{"%":0.4},"SI-4.12":{"%":0.4},"SI-4.13":{"%":0.4},"SI-4.14":{"%":0.4},"SI-4.15":{"%":0.4},"SI-4.16":{"%":0.4},"SI-4.17":{"%":0.4},"SI-4.18":{"%":0.4},"SI-4.19":{"%":0.4},"SI-4.20":{"%":0.4},"SI-4.21":{"%":0.4},"SI-4.22":{"%":0.4},"SI-4.23":{"%":0.4},"SI-4.24":{"%":0.4},"SI-4.25":{"%":0.4}}},"SI-5":{"%":0.9,"P":{"SI-4.25":{"%":0.9}}},"SI-6":{"%":0.9,"P":{"SI-6.2":{"%":0.9},"SI-6.3":{"%":0.9}}},"SI-7":{"%":0.9,"P":{"SI-7.1":{"%":0.9},"SI-7.2":{"%":0.9},"SI-7.3":{"%":0.9},"SI-7.5":{"%":0.9},"SI-7.6":{"%":0.9},"SI-7.7":{"%":0.9},"SI-7.8":{"%":0.9},"SI-7.9":{"%":0.9},"SI-7.10":{"%":0.9},"SI-7.12":{"%":0.9},"SI-7.15":{"%":0.9},"SI-7.16":{"%":0.9},"SI-7.17":{"%":0.9}}},"SI-8":{"%":0.9,"P":{"SI-8.2":{"%":0.9},"SI-8.3":{"%":0.9}}},"SI-10":{"%":0.9,"P":{"SI-10.1":{"%":0.9},"SI-10.2":{"%":0.9},"SI-10.3":{"%":0.9},"SI-10.4":{"%":0.9},"SI-13":{"%":0.9},"SI-10.6":{"%":0.9}}},"SI-11":{"%":0.9,"P":{"SI-10.6":{"%":0.9}}},"SI-12":{"%":0.4,"P":{"SI-12.1":{"%":0.4},"SI-12.2":{"%":0.4},"SI-12.3":{"%":0.4}}},"SI-13":{"%":0.9,"P":{"SI-13.1":{"%":0.9},"SI-13.3":{"%":0.9},"SI-13.4":{"%":0.9},"SI-13.5":{"%":0.9}}},"SI-14":{"%":0.9,"P":{"SI-14.1":{"%":0.9},"SI-14.2":{"%":0.9},"SI-14.3":{"%":0.9}}},"SI-15":{"%":0.9,"P":{"SI-14.3":{"%":0.9}}},"SI-16":{"%":0.9,"P":{"SI-14.3":{"%":0.9}}},"SI-17":{"%":0.9,"P":{"SI-14.3":{"%":0.9}}},"SI-18":{"%":0.9,"P":{"SI-18.1":{"%":0.9},"SI-18.2":{"%":0.9},"SI-18.3":{"%":0.9},"SI-18.4":{"%":0.9},"SI-18.5":{"%":0.9}}},"SI-19":{"%":0.9,"P":{"SI-19.1":{"%":0.9},"SI-19.2":{"%":0.9},"SI-19.3":{"%":0.9},"SI-19.4":{"%":0.9},"SI-19.5":{"%":0.9},"SI-19.6":{"%":0.9},"SI-19.7":{"%":0.9},"SI-19.8":{"%":0.9}}},"SI-20":{"%":0.9,"P":{"SI-19.8":{"%":0.9}}},"SI-21":{"%":0.9,"P":{"SI-19.8":{"%":0.9}}},"SI-22":{"%":0.9,"P":{"SI-19.8":{"%":0.9}}},"SI-23":{"%":0.9,"P":{"SI-19.8":{"%":0.9}}},"SI-24":{"%":0.9,"P":{"SI-19.8":{"%":0.9}}}}},"3.20 SUPPLY CHAIN RISK MANAGEMENT":{"%":0.9,"P":{"SR-1":{"%":0.9,"P":{"null":{"%":0.9}}},"SR-2":{"%":0.9,"P":{"null":{"%":0.9}}},"SR-3":{"%":0.9,"P":{"SR-3.1":{"%":0.9},"SR-3.2":{"%":0.9},"SR-3.3":{"%":0.9}}},"SR-4":{"%":0.9,"P":{"SR-4.1":{"%":0.9},"SR-4.2":{"%":0.9},"SR-4.3":{"%":0.9},"SR-4.4":{"%":0.9}}},"SR-5":{"%":0.9,"P":{"SR-5.1":{"%":0.9},"SR-5.2":{"%":0.9}}},"SR-6":{"%":0.9,"P":{"SR-5.2":{"%":0.9}}},"SR-7":{"%":0.9,"P":{"SR-5.2":{"%":0.9}}},"SR-8":{"%":0.9,"P":{"SR-5.2":{"%":0.9}}},"SR-9":{"%":0.9,"P":{"SR-5.2":{"%":0.9}}},"SR-10":{"%":0.9,"P":{"SR-5.2":{"%":0.9}}},"SR-11":{"%":0.9,"P":{"SR-11.1":{"%":0.9},"SR-11.2":{"%":0.9},"SR-11.3":{"%":0.9}}}}}},"%":0.16477097649883266}},"Standard10":{"null":{"Domain":{"1":{"%":0.32738095238095233,"P":{"D1.G":{"%":0.3214285714285714,"P":{"D1.G.SP":{"%":0.14285714285714285,"P":{"D1.G.SP.B.1":{"%":0.9},"D1.G.SP.B.2":{"%":0.4},"D1.G.SP.B.3":{"%":0.9},"D1.G.SP.B.4":{"%":0.9},"D1.G.SP.B.5":{"%":0.9},"D1.G.SP.B.6":{"%":0.9},"D1.G.SP.B.7":{"%":0.9}}},"D1.G.IT":{"%":0.5,"P":{"D1.G.IT.B.1":{"%":0.4},"D1.G.IT.B.2":{"%":0.4},"D1.G.IT.B.3":{"%":0.9},"D1.G.IT.B.4":{"%":0.9}}}}},"D1.RM":{"%":0.3333333333333333,"P":{"D1.RM.RMP":{"%":0.4,"P":{"D1.RM.RMP.B.1":{"%":0.4}}},"D1.RM.RA":{"%":0.9,"P":{"D1.RM.RA.B.1":{"%":0.9},"D1.RM.RA.B.2":{"%":0.9},"D1.RM.RA.B.3":{"%":0.9}}},"D1.RM.Au":{"%":0.9,"P":{"D1.RM.Au.B.1":{"%":0.9},"D1.RM.Au.B.2":{"%":0.9},"D1.RM.Au.B.3":{"%":0.9},"D1.RM.Au.B.4":{"%":0.9}}}}}}},"2":{"%":0.25,"P":{"D2.TI":{"%":0.9,"P":{"D2.TI.Ti":{"%":0.9,"P":{"D2.TI.Ti.B.1":{"%":0.9},"D2.TI.Ti.B.2":{"%":0.9},"D2.TI.Ti.B.3":{"%":0.9}}}}},"D2.MA":{"%":0.5,"P":{"D2.MA.Ma":{"%":0.5,"P":{"D2.MA.Ma.B.1":{"%":0.4},"D2.MA.Ma.B.2":{"%":0.9}}}}}}},"3":{"%":0.21631944444444443,"P":{"D3.PC":{"%":0.265625,"P":{"D3.PC.Im":{"%":0.5,"P":{"D3.PC.Im.B.1":{"%":0.4},"D3.PC.Im.B.2":{"%":0.4},"D3.PC.Im.B.3":{"%":0.9},"D3.PC.Im.B.4":{"%":0.4},"D3.PC.Im.B.5":{"%":0.4},"D3.PC.Im.B.6":{"%":0.4},"D3.PC.Im.B.7":{"%":0.9},"D3.PC.Im.B.8":{"%":0.9},"D3.PC.Im.B.9":{"%":0.9},"D3.PC.Im.B.10":{"%":0.9}}},"D3.PC.Am":{"%":0.5625,"P":{"D3.PC.Am.B.1":{"%":0.4},"D3.PC.Am.B.2":{"%":0.4},"D3.PC.Am.B.3":{"%":0.4},"D3.PC.Am.B.4":{"%":0.4},"D3.PC.Am.B.5":{"%":0.4},"D3.PC.Am.B.6":{"%":0.4},"D3.PC.Am.B.7":{"%":0.9},"D3.PC.Am.B.8":{"%":0.4},"D3.PC.Am.B.9":{"%":0.9},"D3.PC.Am.B.10":{"%":0.9},"D3.PC.Am.B.12":{"%":0.4},"D3.PC.Am.B.13":{"%":0.4},"D3.PC.Am.B.14":{"%":0.9},"D3.PC.Am.B.15":{"%":0.9},"D3.PC.Am.B.16":{"%":0.9},"D3.PC.Am.B.18":{"%":0.9}}},"D3.PC.De":{"%":0.9,"P":{"D3.PC.De.B.1":{"%":0.9}}},"D3.PC.Se":{"%":0.9,"P":{"D3.PC.Se.B.1":{"%":0.9}}}}},"D3.DC":{"%":0.3833333333333333,"P":{"D3.DC.Th":{"%":0.25,"P":{"D3.DC.Th.B.1":{"%":0.4},"D3.DC.Th.B.2":{"%":0.9},"D3.DC.Th.B.3":{"%":0.9},"D3.DC.Th.B.4":{"%":0.9}}},"D3.DC.An":{"%":0.4,"P":{"D3.DC.An.B.1":{"%":0.4},"D3.DC.An.B.2":{"%":0.9},"D3.DC.An.B.3":{"%":0.9},"D3.DC.An.B.4":{"%":0.4},"D3.DC.An.B.5":{"%":0.9}}},"D3.DC.Ev":{"%":0.5,"P":{"D3.DC.Ev.B.1":{"%":0.4},"D3.DC.Ev.B.2":{"%":0.9},"D3.DC.Ev.B.3":{"%":0.4},"D3.DC.Ev.B.4":{"%":0.9}}}}},"D3.CC":{"%":0.9,"P":{"D3.CC.Pa":{"%":0.9,"P":{"D3.CC.Pa.B.1":{"%":0.9},"D3.CC.Pa.B.2":{"%":0.9},"D3.CC.Pa.B.3":{"%":0.9}}},"D3.CC.Re":{"%":0.9,"P":{"D3.CC.Re.1":{"%":0.9}}}}}}},"4":{"%":0.125,"P":{"D4.C":{"%":0.25,"P":{"D4.C.Co":{"%":0.25,"P":{"D4.C.Co.B.1":{"%":0.9},"D4.C.Co.B.2":{"%":0.9},"D4.C.Co.B.3":{"%":0.4},"D4.C.Co.B.4":{"%":0.9}}}}},"D4.RM":{"%":0.9,"P":{"D4.RM.Dd":{"%":0.9,"P":{"D4.RM.Dd.B.1":{"%":0.9},"D4.RM.Dd.B.2":{"%":0.9},"D4.RM.Dd.B.3":{"%":0.9}}},"D4.RM.Om":{"%":0.9,"P":{"D4.RM.Om.B.1":{"%":0.9},"D4.RM.Om.B.2":{"%":0.9},"D4.RM.Om.B.3":{"%":0.9}}}}}}},"5":{"%":0.23611111111111108,"P":{"D5.IR":{"%":0.375,"P":{"D5.IR.Pl":{"%":0.75,"P":{"D5.IR.Pl.B.1":{"%":0.9},"D5.IR.Pl.B.3":{"%":0.4},"D5.IR.Pl.B.5":{"%":0.4},"D5.IR.Pl.B.6":{"%":0.4}}},"D5.IR.Te":{"%":0.9,"P":{"D5.IR.Te.B.1":{"%":0.9},"D5.IR.Te.B.2":{"%":0.9},"D5.IR.Te.B.3":{"%":0.9}}}}},"D5.DR":{"%":0.9,"P":{"D5.DR.De":{"%":0.9,"P":{"D5.DR.De.B.1":{"%":0.9},"D5.DR.De.B.2":{"%":0.9},"D5.DR.De.B.3":{"%":0.9}}},"D5.DR.Re":{"%":0.9,"P":{"D5.DR.Re.B.1":{"%":0.9}}}}},"D5.ER":{"%":0.3333333333333333,"P":{"D5.ER.Es":{"%":0.3333333333333333,"P":{"D5.ER.Es.B.2":{"%":0.4},"D5.ER.Es.B.3":{"%":0.9},"D5.ER.Es.B.4":{"%":0.9}}}}}}}},"%":0.23096230158730155}},"Standard7":{"null":{"Technical Coverage":{"2":{"%":0.98333333333333333,"P":{"2._Maintain an inventory of personal data and/or processing activities":{"%":0.4},"2._Classify personal data by type":{"%":0.9},"2._Obtain Regulator approval for data processing":{"%":0.9},"2._Register databases with regulators":{"%":0.9},"2._Maintain documentation of data flows":{"%":0.9},"2._Maintain documentation of the transfer mechanism used for cross-border data flows":{"%":0.9},"2._Use Binding Corporate Rules as a data transfer mechanism":{"%":0.9},"2._Use contracts as a data transfer mechanism":{"%":0.9},"2._Use APEC Cross Border Privacy Rules as a data transfer mechanism":{"%":0.9},"2._Use Regulator approval as a data transfer mechanism":{"%":0.9},"2._Use adequacy or one of the derogations as a data transfer mechanism":{"%":0.9},"2._Use the Privacy Shield as a data transfer mechanism":{"%":0.9}}},"3":{"%":0.9,"P":{"3._Maintain a data privacy policy":{"%":0.9},"3._Maintain an employee data privacy policy":{"%":0.9},"3._Document legal basis for processing personal data":{"%":0.9},"3._Integrate ethics into data processing":{"%":0.9},"3._Maintain an organizational code of conduct that includes privacy":{"%":0.9}}},"6":{"%":0.4166666666666667,"P":{"6._Integrate data privacy risk into security risk assessments":{"%":0.9},"6._Integrate data privacy into an information security policy":{"%":0.9},"6._Maintain technical security measures":{"%":0.4},"6._Maintain measures to encrypt personal data":{"%":0.4},"6._Maintain an acceptable use of information resources policy":{"%":0.9},"6._Maintain procedures to restrict access to personal data":{"%":0.4},"6._Integrate data privacy into a corporate security policy":{"%":0.9},"6._Maintain human resource security measures":{"%":0.9},"6._Maintain backup and business continuity plans":{"%":0.4},"6._Maintain a data-loss prevention strategy":{"%":0.9},"6._Conduct regular testing of data security posture":{"%":0.4},"6._Maintain a security certification":{"%":0.9}}},"11":{"%":0.14285714285714285,"P":{"11._Maintain a data privacy incident/breach response plan":{"%":0.9},"11._Maintain a breach notification and reporting protocol":{"%":0.9},"11._Maintain a log to track data privacy incidents/breaches":{"%":0.4},"11._Monitor and Report data privacy incident/breach metrics":{"%":0.9},"11._Conduct periodic testing of data privacy incident/breach plan":{"%":0.9},"11._Engage a breach response remediation provider":{"%":0.9},"11._Engage a forensic investigation team":{"%":0.9}}}},"%":0.1607142857142857}},"s202":{"null":{"null":{"COMMUNICATION AND INFORMATION":{"%":0.3333333333333333,"P":{"CC2.1":{"%":0.9},"CC2.2":{"%":0.9},"CC2.3":{"%":0.4}}},"RISK ASSESSMENT":{"%":0.25,"P":{"CC3.1":{"%":0.9},"CC3.2":{"%":0.4},"CC3.3":{"%":0.9},"CC3.4":{"%":0.9}}},"MONITORING ACTIVITIES":{"%":0.5,"P":{"CC4.1":{"%":0.4},"CC4.2":{"%":0.9}}},"CONTROL ACTIVITIES ":{"%":0.3333333333333333,"P":{"CC5.1":{"%":0.9},"CC5.2":{"%":0.4},"CC5.3":{"%":0.9}}},"Logical and Physical Access Controls":{"%":0.875,"P":{"CC6.1":{"%":0.4},"CC6.2":{"%":0.4},"CC6.3":{"%":0.4},"CC6.4":{"%":0.4},"CC6.5":{"%":0.9},"CC6.6":{"%":0.4},"CC6.7":{"%":0.4},"CC6.8":{"%":0.4}}},"System Operations":{"%":0.8,"P":{"CC7.1":{"%":0.4},"CC7.2":{"%":0.4},"CC7.3":{"%":0.4},"CC7.4":{"%":0.4},"CC7.5":{"%":0.9}}},"Change Management":{"%":0.4,"P":{"CC8.1":{"%":0.4}}},"Risk Mitigation":{"%":0.9,"P":{"CC9.1":{"%":0.9},"CC9.2":{"%":0.9}}},"ADDITIONAL CRITERIA FOR AVAILABILITY":{"%":0.3333333333333333,"P":{"A1.1":{"%":0.9},"A1.2":{"%":0.4},"A1.3":{"%":0.9}}},"CONFIDENTIALITY":{"%":0.5,"P":{"C1.1":{"%":0.4},"C1.2":{"%":0.9}}},"ADDITIONAL CRITERIA FOR PROCESSING INTEGRITY":{"%":0.9,"P":{"PL1.1":{"%":0.9},"PL1.2":{"%":0.9},"PL1.3":{"%":0.9}}}},"%":0.4477272727272727}},"Standard5":{"null":{"Control":{"164":{"%":0.93816964285714286,"P":{"164.105":{"%":0.9,"P":{"164.105.a":{"%":0.9,"P":{"164.105.a.1":{"%":0.9,"P":{"null":{"%":0.9}}},"164.105.a.2":{"%":0.9,"P":{"164.105.a.2.ii":{"%":0.9,"P":{"164.105.a.2.ii.A":{"%":0.9},"164.105.a.2.ii.B":{"%":0.9},"164.105.a.2.ii.C":{"%":0.9}}},"164.105.a.2.iii":{"%":0.9,"P":{"164.105.a.2.iii.A":{"%":0.9},"164.105.a.2.iii.B":{"%":0.9},"164.105.a.2.iii.C":{"%":0.9},"164.105.a.2.iii.D":{"%":0.9}}}}}}},"164.105.b":{"%":0.9,"P":{"164.105.b.1":{"%":0.9,"P":{"null":{"%":0.9}}},"164.105.b.2":{"%":0.9,"P":{"164.105.b.2.i":{"%":0.9,"P":{"164.105.b.2.i.A":{"%":0.9},"164.105.b.2.i.B":{"%":0.9}}},"164.105.b.2.ii":{"%":0.9,"P":{"164.105.b.2.i.B":{"%":0.9}}}}}}},"164.105.c":{"%":0.9,"P":{"164.105.c.1":{"%":0.9,"P":{"164.105.b.2.ii":{"%":0.9}}},"164.105.c.2":{"%":0.9,"P":{"164.105.b.2.ii":{"%":0.9}}}}}}},"164.306":{"%":0.9,"P":{"164.306.a":{"%":0.9,"P":{"164.306.a.1":{"%":0.9,"P":{"164.105.b.2.ii":{"%":0.9}}},"164.306.a.2":{"%":0.9,"P":{"164.105.b.2.ii":{"%":0.9}}},"164.306.a.3":{"%":0.9,"P":{"164.105.b.2.ii":{"%":0.9}}}}},"164.306.b":{"%":0.9,"P":{"164.306.b.1":{"%":0.9,"P":{"164.105.b.2.ii":{"%":0.9}}},"164.306.b.2":{"%":0.9,"P":{"164.306.b.2.i":{"%":0.9,"P":{"164.105.b.2.i.B":{"%":0.9}}},"164.306.b.2.ii":{"%":0.9,"P":{"164.105.b.2.i.B":{"%":0.9}}},"164.306.b.2.iii":{"%":0.9,"P":{"164.105.b.2.i.B":{"%":0.9}}},"164.306.b.2.iv":{"%":0.9,"P":{"164.105.b.2.i.B":{"%":0.9}}}}}}},"164.306.c":{"%":0.9,"P":{"164.306.b.2":{"%":0.9}}},"164.306.d":{"%":0.9,"P":{"164.306.d.1":{"%":0.9,"P":{"164.306.b.2.iv":{"%":0.9}}},"164.306.d.2":{"%":0.9,"P":{"164.306.b.2.iv":{"%":0.9}}},"164.306.d.3":{"%":0.9,"P":{"164.306.d.3.i":{"%":0.9,"P":{"164.105.b.2.i.B":{"%":0.9}}},"164.306.d.3.ii":{"%":0.9,"P":{"164.306.d.3.ii.A":{"%":0.9},"164.306.d.3.ii.B":{"%":0.9}}}}}}},"164.306.e":{"%":0.9,"P":{"164.306.d.3":{"%":0.9}}}}},"164.308":{"%":0.209375,"P":{"164.308.a":{"%":0.41875,"P":{"164.308.a.1":{"%":0.125,"P":{"164.308.a.1.i":{"%":0.9,"P":{"164.306.d.3.ii.B":{"%":0.9}}},"164.308.a.1.ii":{"%":0.25,"P":{"164.308.a.1.ii.A":{"%":0.9},"164.308.a.1.ii.B":{"%":0.9},"164.308.a.1.ii.C":{"%":0.9},"164.308.a.1.ii.D":{"%":0.4}}}}},"164.308.a.2":{"%":0.4,"P":{"164.308.a.1.ii":{"%":0.4}}},"164.308.a.3":{"%":0.6666666666666666,"P":{"164.308.a.3.i":{"%":0.4,"P":{"164.308.a.1.ii.D":{"%":0.4}}},"164.308.a.3.ii":{"%":0.3333333333333333,"P":{"164.308.a.3.ii.A":{"%":0.9},"164.308.a.3.ii.B":{"%":0.9},"164.308.a.3.ii.C":{"%":0.4}}}}},"164.308.a.4":{"%":0.8333333333333333,"P":{"164.308.a.4.i":{"%":0.4,"P":{"164.308.a.3.ii.C":{"%":0.4}}},"164.308.a.4.ii":{"%":0.6666666666666666,"P":{"164.308.a.4.ii.A":{"%":0.9},"164.308.a.4.ii.B":{"%":0.4},"164.308.a.4.ii.C":{"%":0.4}}}}},"164.308.a.5":{"%":0.125,"P":{"164.308.a.5.i":{"%":0.9,"P":{"164.308.a.4.ii.C":{"%":0.9}}},"164.308.a.5.ii":{"%":0.25,"P":{"164.308.a.5.ii.A":{"%":0.9},"164.308.a.5.ii.B":{"%":0.4},"164.308.a.5.ii.C":{"%":0.9},"164.308.a.5.ii.D":{"%":0.9}}}}},"164.308.a.6":{"%":0.5,"P":{"164.308.a.6.i":{"%":0.9,"P":{"164.308.a.5.ii.D":{"%":0.9}}},"164.308.a.6.ii":{"%":0.4,"P":{"164.308.a.5.ii.D":{"%":0.4}}}}},"164.308.a.7":{"%":0.1,"P":{"164.308.a.7.i":{"%":0.9,"P":{"164.308.a.5.ii.D":{"%":0.9}}},"164.308.a.7.ii":{"%":0.2,"P":{"164.308.a.7.ii.A":{"%":0.4},"164.308.a.7.ii.B":{"%":0.9},"164.308.a.7.ii.C":{"%":0.9},"164.308.a.7.ii.D":{"%":0.9},"164.308.a.7.ii.E":{"%":0.9}}}}},"164.308.a.8":{"%":0.9,"P":{"164.308.a.7.ii":{"%":0.9}}}}},"164.308.b":{"%":0.9,"P":{"164.308.b.1":{"%":0.9,"P":{"164.308.a.7.ii":{"%":0.9}}},"164.308.b.2":{"%":0.9,"P":{"164.308.a.7.ii":{"%":0.9}}},"164.308.b.3":{"%":0.9,"P":{"164.308.a.7.ii":{"%":0.9}}}}}}},"164.312":{"%":0.325,"P":{"164.312.a":{"%":0.125,"P":{"164.312.a.1":{"%":0.9,"P":{"164.308.a.7.ii":{"%":0.9}}},"164.312.a.2":{"%":0.25,"P":{"164.312.a.2.i":{"%":0.9,"P":{"164.308.a.7.ii.E":{"%":0.9}}},"164.312.a.2.ii":{"%":0.9,"P":{"164.308.a.7.ii.E":{"%":0.9}}},"164.312.a.2.iii":{"%":0.9,"P":{"164.308.a.7.ii.E":{"%":0.9}}},"164.312.a.2.iv":{"%":0.4,"P":{"164.308.a.7.ii.E":{"%":0.4}}}}}}},"164.312.b":{"%":0.4,"P":{"164.312.a.2":{"%":0.4}}},"164.312.c":{"%":0.9,"P":{"164.312.c.1":{"%":0.9,"P":{"164.312.a.2.iv":{"%":0.9}}},"164.312.c.2":{"%":0.9,"P":{"164.312.a.2.iv":{"%":0.9}}}}},"164.312.d":{"%":0.9,"P":{"164.312.c.2":{"%":0.9}}},"164.312.e":{"%":0.5,"P":{"164.312.e.1":{"%":0.4,"P":{"164.312.a.2.iv":{"%":0.4}}},"164.312.e.2":{"%":0.9,"P":{"164.312.e.2.i":{"%":0.9,"P":{"164.308.a.7.ii.E":{"%":0.9}}},"164.312.e.2.ii":{"%":0.9,"P":{"164.308.a.7.ii.E":{"%":0.9}}}}}}}}},"164.314":{"%":0.9,"P":{"164.314.a":{"%":0.9,"P":{"164.314.a.1":{"%":0.9,"P":{"164.312.e.2.ii":{"%":0.9}}},"164.314.a.2":{"%":0.9,"P":{"164.314.a.2.i":{"%":0.9,"P":{"164.314.a.2.i.A":{"%":0.9},"164.314.a.2.i.B":{"%":0.9},"164.314.a.2.i.C":{"%":0.9}}},"164.314.a.2.ii":{"%":0.9,"P":{"164.314.a.2.i.C":{"%":0.9}}},"164.314.a.2.iii":{"%":0.9,"P":{"164.314.a.2.i.C":{"%":0.9}}}}}}},"164.314.b":{"%":0.9,"P":{"164.314.b.1":{"%":0.9,"P":{"164.314.a.2.iii":{"%":0.9}}},"164.314.b.2":{"%":0.9,"P":{"164.314.b.2.i":{"%":0.9,"P":{"164.314.a.2.i.C":{"%":0.9}}},"164.314.b.2.ii":{"%":0.9,"P":{"164.314.a.2.i.C":{"%":0.9}}},"164.314.b.2.iii":{"%":0.9,"P":{"164.314.a.2.i.C":{"%":0.9}}},"164.314.b.2.iv":{"%":0.9,"P":{"164.314.a.2.i.C":{"%":0.9}}}}}}}}},"164.316":{"%":0.9,"P":{"164.316.a":{"%":0.9,"P":{"164.314.b.2":{"%":0.9}}},"164.316.b":{"%":0.9,"P":{"164.316.b.1":{"%":0.9,"P":{"164.316.b.1.i":{"%":0.9,"P":{"164.314.a.2.i.C":{"%":0.9}}},"164.316.b.1.ii":{"%":0.9,"P":{"164.314.a.2.i.C":{"%":0.9}}}}},"164.316.b.2":{"%":0.9,"P":{"164.316.b.2.i":{"%":0.9,"P":{"164.314.a.2.i.C":{"%":0.9}}},"164.316.b.2.ii":{"%":0.9,"P":{"164.314.a.2.i.C":{"%":0.9}}},"164.316.b.2.iii":{"%":0.9,"P":{"164.314.a.2.i.C":{"%":0.9}}}}}}}}},"164.404":{"%":0.9,"P":{"164.404.a":{"%":0.9,"P":{"164.404.a.1":{"%":0.9,"P":{"164.316.b.2.iii":{"%":0.9}}},"164.404.a.2":{"%":0.9,"P":{"164.316.b.2.iii":{"%":0.9}}}}},"164.404.b":{"%":0.9,"P":{"164.404.a.2":{"%":0.9}}},"164.404.c":{"%":0.9,"P":{"164.404.c.1":{"%":0.9,"P":{"164.404.c.1.A":{"%":0.9,"P":{"164.314.a.2.i.C":{"%":0.9}}},"164.404.c.1.B":{"%":0.9,"P":{"164.314.a.2.i.C":{"%":0.9}}},"164.404.c.1.C":{"%":0.9,"P":{"164.314.a.2.i.C":{"%":0.9}}},"164.404.c.1.D":{"%":0.9,"P":{"164.314.a.2.i.C":{"%":0.9}}},"164.404.c.1.E":{"%":0.9,"P":{"164.314.a.2.i.C":{"%":0.9}}}}},"164.404.c.2":{"%":0.9,"P":{"164.404.c.1.E":{"%":0.9}}}}},"164.404.d":{"%":0.9,"P":{"164.404.d.1":{"%":0.9,"P":{"164.404.d.1.i":{"%":0.9,"P":{"164.314.a.2.i.C":{"%":0.9}}},"164.404.d.1.ii":{"%":0.9,"P":{"164.314.a.2.i.C":{"%":0.9}}}}},"164.404.d.2":{"%":0.9,"P":{"164.404.d.2.i":{"%":0.9,"P":{"164.314.a.2.i.C":{"%":0.9}}},"164.404.d.2.ii":{"%":0.9,"P":{"164.404.d.2.ii.A":{"%":0.9},"164.404.d.2.ii.B":{"%":0.9}}}}},"164.404.d.3":{"%":0.9,"P":{"164.404.d.2.ii":{"%":0.9}}}}}}},"164.406":{"%":0.9,"P":{"164.406.a":{"%":0.9,"P":{"164.404.d.3":{"%":0.9}}},"164.406.b":{"%":0.9,"P":{"164.404.d.3":{"%":0.9}}},"164.406.c":{"%":0.9,"P":{"164.404.d.3":{"%":0.9}}}}},"164.408":{"%":0.9,"P":{"164.408.a":{"%":0.9,"P":{"164.404.d.3":{"%":0.9}}},"164.408.b":{"%":0.9,"P":{"164.404.d.3":{"%":0.9}}},"164.408.c":{"%":0.9,"P":{"164.404.d.3":{"%":0.9}}}}},"164.410":{"%":0.9,"P":{"164.410.a":{"%":0.9,"P":{"164.410.a.1":{"%":0.9,"P":{"164.404.d.2.ii":{"%":0.9}}},"164.410.a.2":{"%":0.9,"P":{"164.404.d.2.ii":{"%":0.9}}}}},"164.410.b":{"%":0.9,"P":{"164.410.a.2":{"%":0.9}}},"164.410.c":{"%":0.9,"P":{"164.410.c.1":{"%":0.9,"P":{"164.404.d.2.ii":{"%":0.9}}},"164.410.c.2":{"%":0.9,"P":{"164.404.d.2.ii":{"%":0.9}}}}}}},"164.504":{"%":0.9,"P":{"164.504.e":{"%":0.9,"P":{"164.504.e.2":{"%":0.9,"P":{"164.504.e.2.i":{"%":0.9,"P":{"164.504.e.2.i.A":{"%":0.9},"164.504.e.2.i.B":{"%":0.9}}},"164.504.e.2.ii":{"%":0.9,"P":{"164.504.e.2.ii.A":{"%":0.9},"164.504.e.2.ii.B":{"%":0.9},"164.504.e.2.ii.C":{"%":0.9},"164.504.e.2.ii.D":{"%":0.9},"164.504.e.2.ii.E":{"%":0.9},"164.504.e.2.ii.F":{"%":0.9},"164.504.e.2.ii.G":{"%":0.9},"164.504.e.2.ii.H":{"%":0.9},"164.504.e.2.ii.I":{"%":0.9},"164.504.e.2.ii.J":{"%":0.9}}},"164.504.e.2.iii":{"%":0.9,"P":{"164.504.e.2.ii.J":{"%":0.9}}}}}}}}},"164.508":{"%":0.9,"P":{"164.508.c":{"%":0.9,"P":{"164.508.c.1":{"%":0.9,"P":{"164.508.c.1.i":{"%":0.9,"P":{"164.504.e.2.ii.J":{"%":0.9}}},"164.508.c.1.ii":{"%":0.9,"P":{"164.504.e.2.ii.J":{"%":0.9}}},"164.508.c.1.iii":{"%":0.9,"P":{"164.504.e.2.ii.J":{"%":0.9}}},"164.508.c.1.iv":{"%":0.9,"P":{"164.504.e.2.ii.J":{"%":0.9}}},"164.508.c.1.v":{"%":0.9,"P":{"164.504.e.2.ii.J":{"%":0.9}}},"164.508.c.1.vi":{"%":0.9,"P":{"164.504.e.2.ii.J":{"%":0.9}}}}},"164.508.c.2":{"%":0.9,"P":{"164.508.c.2.i":{"%":0.9,"P":{"164.508.c.2.i.A":{"%":0.9},"164.508.c.2.i.B":{"%":0.9}}},"164.508.c.2.ii":{"%":0.9,"P":{"164.508.c.2.ii.A":{"%":0.9},"164.508.c.2.ii.B":{"%":0.9}}},"164.508.c.2.iii":{"%":0.9,"P":{"164.508.c.2.ii.B":{"%":0.9}}}}},"164.508.c.3":{"%":0.9,"P":{"164.508.c.2.iii":{"%":0.9}}},"164.508.c.4":{"%":0.9,"P":{"164.508.c.2.iii":{"%":0.9}}}}}}},"164.514":{"%":0.9,"P":{"164.514.d":{"%":0.9,"P":{"164.514.d.4":{"%":0.9,"P":{"164.514.d.4.i":{"%":0.9,"P":{"164.508.c.2.ii.B":{"%":0.9}}},"164.514.d.4.ii":{"%":0.9,"P":{"164.508.c.2.ii.B":{"%":0.9}}},"164.514.d.4.iii":{"%":0.9,"P":{"164.514.d.4.iii.A":{"%":0.9},"164.514.d.4.iii.B":{"%":0.9}}}}}}}}},"164.522":{"%":0.9,"P":{"164.522.b":{"%":0.9,"P":{"164.522.b.1":{"%":0.9,"P":{"164.522.b.1.i":{"%":0.9,"P":{"164.514.d.4.iii.B":{"%":0.9}}},"164.522.b.1.ii":{"%":0.9,"P":{"164.514.d.4.iii.B":{"%":0.9}}}}}}}}}}}},"%":0.93816964285714286}}} \ No newline at end of file diff --git a/tests/tests_metrics/mock_files/rulesets/caas-settings/GOOGLE_STANDARDS_COVERAGE.json.gz b/tests/tests_metrics/mock_files/rulesets/caas-settings/GOOGLE_STANDARDS_COVERAGE.json.gz new file mode 100644 index 000000000..cb2613850 --- /dev/null +++ b/tests/tests_metrics/mock_files/rulesets/caas-settings/GOOGLE_STANDARDS_COVERAGE.json.gz @@ -0,0 +1 @@ +{"BSA":{"v3":{"Control Domain":{"NS":{"%":0.5,"P":{"NS-1":{"%":0.4},"NS-2":{"%":0.4},"NS-3":{"%":0.4},"NS-101":{"%":1.0},"NS-5":{"%":1.0},"NS-88":{"%":1.0},"NS-7":{"%":0.4},"NS-8":{"%":0.4},"NS-9":{"%":1.0},"NS-10":{"%":1.0}}},"DP":{"%":0.5,"P":{"DP-1":{"%":0.4},"DP-2":{"%":1.0},"DP-3":{"%":0.4},"DP-101":{"%":0.4},"DP-5":{"%":0.4},"DP-88":{"%":1.0},"DP-7":{"%":1.0},"DP-8":{"%":1.0}}},"IM":{"%":0.5555555555555556,"P":{"IM-1":{"%":0.4},"IM-2":{"%":0.4},"IM-3":{"%":1.0},"IM-101":{"%":1.0},"IM-5":{"%":1.0},"IM-88":{"%":0.4},"IM-7":{"%":0.4},"IM-8":{"%":1.0},"IM-9":{"%":0.4}}},"PA":{"%":0.375,"P":{"PA-1":{"%":0.4},"PA-2":{"%":1.0},"PA-3":{"%":1.0},"PA-101":{"%":0.4},"PA-5":{"%":1.0},"PA-88":{"%":1.0},"PA-7":{"%":0.4},"PA-8":{"%":1.0}}},"PV":{"%":0.5714285714285714,"P":{"PV-1":{"%":0.4},"PV-2":{"%":1.0},"PV-3":{"%":0.4},"PV-101":{"%":1.0},"PV-5":{"%":0.4},"PV-88":{"%":0.4},"PV-7":{"%":1.0}}},"LT":{"%":0.8571428571428571,"P":{"LT-1":{"%":0.4},"LT-2":{"%":0.4},"LT-3":{"%":0.4},"LT-101":{"%":0.4},"LT-5":{"%":0.4},"LT-88":{"%":0.4},"LT-7":{"%":1.0}}},"AM":{"%":0.6,"P":{"AM-1":{"%":1.0},"AM-2":{"%":0.4},"AM-3":{"%":1.0},"AM-101":{"%":0.4},"AM-5":{"%":0.4}}},"ES":{"%":1.0,"P":{"ES-1":{"%":1.0},"ES-2":{"%":1.0},"ES-3":{"%":1.0}}},"BR":{"%":0.25,"P":{"BR-1":{"%":0.4},"BR-2":{"%":1.0},"BR-3":{"%":1.0},"BR-101":{"%":1.0}}},"IR":{"%":1.0,"P":{"IR-1":{"%":1.0},"IR-2":{"%":1.0},"IR-3":{"%":1.0},"IR-101":{"%":1.0},"IR-5":{"%":1.0},"IR-88":{"%":1.0},"IR-7":{"%":1.0}}},"DS":{"%":0.2857142857142857,"P":{"DS-1":{"%":1.0},"DS-2":{"%":1.0},"DS-3":{"%":1.0},"DS-101":{"%":1.0},"DS-5":{"%":1.0},"DS-88":{"%":0.4},"DS-7":{"%":0.4}}},"GS":{"%":0.8,"P":{"GS-1":{"%":1.0},"GS-2":{"%":1.0},"GS-3":{"%":0.4},"GS-101":{"%":0.4},"GS-5":{"%":0.4},"GS-88":{"%":0.4},"GS-7":{"%":0.4},"GS-8":{"%":0.4},"GS-9":{"%":0.4},"GS-10":{"%":0.4}}}},"%":0.44123677248677245}},"Standard14":{"27002_2022":{"null":{"5":{"%":0.4444444444444444,"P":{"5.1":{"%":1.0},"5.2":{"%":1.0},"5.3":{"%":1.0},"5.4":{"%":1.0},"5.7":{"%":1.0},"5.9":{"%":0.4},"5.10":{"%":0.4},"5.12":{"%":0.4},"5.13":{"%":0.4},"5.14":{"%":0.4},"5.15":{"%":0.4},"5.16":{"%":1.0},"5.17":{"%":0.4},"5.18":{"%":1.0},"5.19":{"%":1.0},"5.20":{"%":1.0},"5.21":{"%":1.0},"5.22":{"%":1.0},"5.23":{"%":1.0},"5.24":{"%":0.4},"5.25":{"%":0.4},"5.26":{"%":0.4},"5.28":{"%":0.4},"5.29":{"%":1.0},"5.33":{"%":0.4},"5.34":{"%":1.0},"5.36":{"%":1.0}}},"6":{"%":0.5,"P":{"6.62999":{"%":0.4},"6.8":{"%":1.0}}},"8":{"%":0.5483870967741935,"P":{"8.1":{"%":1.0},"8.2":{"%":1.0},"8.22999":{"%":0.4},"8.4":{"%":1.0},"8.5":{"%":0.4},"8.6":{"%":0.4},"8.7":{"%":1.0},"8.72999":{"%":0.4},"8.9":{"%":0.4},"8.10":{"%":1.0},"8.11":{"%":1.0},"8.12":{"%":0.4},"8.12299":{"%":0.4},"8.13299":{"%":1.0},"8.15":{"%":0.4},"8.16":{"%":0.4},"8.17":{"%":1.0},"8.18":{"%":1.0},"8.19":{"%":0.4},"8.20":{"%":0.4},"8.20299":{"%":0.4},"8.21299":{"%":0.4},"8.22299":{"%":1.0},"8.23298":{"%":0.4},"8.25":{"%":1.0},"8.26":{"%":0.4},"8.27":{"%":0.4},"8.28":{"%":1.0},"8.29":{"%":1.0},"8.31":{"%":0.4},"8.33":{"%":1.0}}}},"%":0.49761051373954596},"27001_2013":{"27001_2013 Article (Technical)":{"A.5":{"%":1.0,"P":{"A.5.1":{"%":1.0,"P":{"A.5.1.1":{"%":1.0},"A.5.1.2":{"%":1.0}}}}},"A.6":{"%":0.5,"P":{"A.6.1":{"%":0.5,"P":{"A.6.1.1":{"%":1.0},"A.6.1.2":{"%":0.4}}},"A.6.2":{"%":0.5,"P":{"A.6.2.1":{"%":1.0},"A.6.2.2":{"%":0.4}}}}},"A.8":{"%":0.4444444444444444,"P":{"A.8.1":{"%":0.56666666,"P":{"A.8.1.1":{"%":0.4},"A.8.1.2":{"%":1.0},"A.8.1.3":{"%":0.4}}},"A.8.2":{"%":0.56666666,"P":{"A.8.2.1":{"%":0.4},"A.8.2.2":{"%":0.4},"A.8.2.3":{"%":1.0}}},"A.8.3":{"%":1.0,"P":{"A.8.3.1":{"%":1.0},"A.8.3.2":{"%":1.0},"A.8.3.3":{"%":1.0}}}}},"A.9":{"%":0.700333,"P":{"A.9.1":{"%":0.4,"P":{"A.9.1.1":{"%":0.4},"A.9.1.2":{"%":0.4}}},"A.9.2":{"%":0.003333,"P":{"A.9.2.1":{"%":1.0},"A.9.2.2":{"%":0.4},"A.9.2.3":{"%":0.4},"A.9.2.4":{"%":1.0},"A.9.2.5":{"%":1.0},"A.9.2.6":{"%":1.0}}},"A.9.3":{"%":0.4,"P":{"A.9.3.1":{"%":0.4}}},"A.9.4":{"%":0.6,"P":{"A.9.4.1":{"%":0.4},"A.9.4.2":{"%":0.4},"A.9.4.3":{"%":0.4},"A.9.4.4":{"%":1.0},"A.9.4.5":{"%":1.0}}}}},"A.10":{"%":0.5,"P":{"A.10.1":{"%":0.5,"P":{"A.10.1.1":{"%":0.4},"A.10.1.2":{"%":1.0}}}}},"A.12":{"%":0.4642857142857143,"P":{"A.12.1":{"%":0.5,"P":{"A.12.1.3":{"%":0.4},"A.12.1.4":{"%":1.0}}},"A.12.2":{"%":1.0,"P":{"A.12.2.1":{"%":1.0}}},"A.12.3":{"%":0.4,"P":{"A.12.3.1":{"%":0.4}}},"A.12.4":{"%":0.25,"P":{"A.12.4.1":{"%":0.4},"A.12.4.2":{"%":1.0},"A.12.4.3":{"%":1.0},"A.12.4.4":{"%":1.0}}},"A.12.5":{"%":0.4,"P":{"A.12.5.1":{"%":0.4}}},"A.12.6":{"%":0.5,"P":{"A.12.6.1":{"%":0.4},"A.12.6.2":{"%":1.0}}},"A.12.7":{"%":1.0,"P":{"A.12.7.1":{"%":1.0}}}}},"A.13":{"%":0.56666666,"P":{"A.13.1":{"%":0.4,"P":{"A.13.1.1":{"%":0.4},"A.13.1.2":{"%":0.4},"A.13.1.3":{"%":0.4}}},"A.13.2":{"%":0.003333,"P":{"A.13.2.1":{"%":0.4},"A.13.2.3":{"%":1.0},"A.13.2.4":{"%":1.0}}}}},"A.14":{"%":1.07407407407407407,"P":{"A.14.1":{"%":1.0,"P":{"A.14.1.1":{"%":1.0},"A.14.1.2":{"%":1.0},"A.14.1.3":{"%":1.0}}},"A.14.2":{"%":0.2222222222222222,"P":{"A.14.2.1":{"%":0.4},"A.14.2.2":{"%":1.0},"A.14.2.3":{"%":1.0},"A.14.2.4":{"%":1.0},"A.14.2.5":{"%":0.4},"A.14.2.6":{"%":1.0},"A.14.2.7":{"%":1.0},"A.14.2.8":{"%":1.0},"A.14.2.9":{"%":1.0}}},"A.14.3":{"%":1.0,"P":{"A.14.3.1":{"%":1.0}}}}},"A.15":{"%":1.0,"P":{"A.15.1":{"%":1.0,"P":{"A.15.1.1":{"%":1.0},"A.15.1.3":{"%":1.0}}},"A.15.2":{"%":1.0,"P":{"A.15.2.1":{"%":1.0},"A.15.2.2":{"%":1.0}}}}},"A.16":{"%":0.5,"P":{"A.16.1":{"%":0.5,"P":{"A.16.1.1":{"%":0.4},"A.16.1.2":{"%":1.0},"A.16.1.3":{"%":1.0},"A.16.1.4":{"%":0.4},"A.16.1.5":{"%":0.4},"A.16.1.7":{"%":1.0}}}}},"A.17":{"%":0.156666666,"P":{"A.17.1":{"%":0.003333,"P":{"A.17.1.1":{"%":1.0},"A.17.1.2":{"%":0.4},"A.17.1.3":{"%":1.0}}},"A.17.2":{"%":1.0,"P":{"A.17.2.1":{"%":1.0}}}}},"A.18":{"%":0.003333,"P":{"A.18.1":{"%":0.56666666,"P":{"A.18.1.3":{"%":0.4},"A.18.1.4":{"%":1.0},"A.18.1.5":{"%":0.4}}},"A.18.2":{"%":1.0,"P":{"A.18.2.1":{"%":1.0},"A.18.2.2":{"%":1.0},"A.18.2.3":{"%":1.0}}}}}},"%":0.36523368606701934},"27018_2019":{"27018_2019 Article (Technical)":{"5":{"%":1.0,"P":{"5.1":{"%":1.0,"P":{"5.1.1":{"%":1.0},"5.1.2":{"%":1.0}}}}},"6":{"%":0.7,"P":{"6.1":{"%":0.4,"P":{"6.1.1":{"%":1.0},"6.1.2":{"%":0.4},"6.1.3":{"%":1.0},"6.1.4":{"%":0.4},"6.1.5":{"%":1.0}}},"6.2":{"%":0.4,"P":{"6.1.5":{"%":0.4}}}}},"8":{"%":1.0,"P":{"null":{"%":1.0,"P":{"null":{"%":1.0}}}}},"9":{"%":0.700333,"P":{"9.1":{"%":0.4,"P":{"null":{"%":0.4}}},"9.2":{"%":0.003333,"P":{"9.2.1":{"%":1.0},"9.2.2":{"%":0.4},"9.2.3":{"%":0.4},"9.2.4":{"%":1.0},"9.2.5":{"%":1.0},"9.2.6":{"%":1.0}}},"9.3":{"%":0.4,"P":{"9.3.1":{"%":0.4}}},"9.4":{"%":0.6,"P":{"9.4.1":{"%":0.4},"9.4.2":{"%":0.4},"9.4.3":{"%":0.4},"9.4.4":{"%":1.0},"9.4.5":{"%":1.0}}}}},"10":{"%":0.5,"P":{"10.1":{"%":0.5,"P":{"10.1.1":{"%":0.4},"10.1.2":{"%":1.0}}}}},"12":{"%":0.2857142857142857,"P":{"12.1":{"%":1.0,"P":{"12.1.1":{"%":1.0},"12.1.2":{"%":1.0},"12.1.3":{"%":1.0},"12.1.4":{"%":1.0}}},"12.2":{"%":0.4,"P":{"12.1.4":{"%":0.4}}},"12.3":{"%":1.0,"P":{"12.3.1":{"%":1.0}}},"12.4":{"%":1.0,"P":{"12.4.1":{"%":1.0},"12.4.2":{"%":1.0},"12.4.3":{"%":1.0},"12.4.4":{"%":1.0}}},"12.5":{"%":1.0,"P":{"12.4.4":{"%":1.0}}},"12.6":{"%":0.4,"P":{"12.4.4":{"%":0.4}}},"12.7":{"%":1.0,"P":{"12.4.4":{"%":1.0}}}}},"13":{"%":0.5,"P":{"13.1":{"%":0.4,"P":{"null":{"%":0.4}}},"13.2":{"%":1.0,"P":{"13.2.1":{"%":1.0},"13.2.2":{"%":1.0},"13.2.3":{"%":1.0},"13.2.4":{"%":1.0}}}}},"14":{"%":0.4,"P":{"null":{"%":0.4,"P":{"null":{"%":0.4}}}}},"15":{"%":0.4,"P":{"null":{"%":0.4,"P":{"null":{"%":0.4}}}}},"16":{"%":1.0,"P":{"16.1":{"%":1.0,"P":{"16.1.1":{"%":1.0},"16.1.2":{"%":1.0},"16.1.3":{"%":1.0},"16.1.4":{"%":1.0},"16.1.5":{"%":1.0},"16.1.6":{"%":1.0},"16.1.7":{"%":1.0}}}}},"17":{"%":1.0,"P":{"null":{"%":1.0,"P":{"null":{"%":1.0}}}}},"18":{"%":0.5,"P":{"18.1":{"%":0.4,"P":{"null":{"%":0.4}}},"18.2":{"%":1.0,"P":{"18.2.1":{"%":1.0},"18.2.2":{"%":1.0},"18.2.3":{"%":1.0}}}}}},"%":0.4349206349206349},"27002_2013":{"27002_2013 Article (Technical)":{"5":{"%":1.0,"P":{"5.1":{"%":1.0,"P":{"5.1.1":{"%":1.0},"5.1.2":{"%":1.0}}}}},"6":{"%":0.5,"P":{"6.1":{"%":0.5,"P":{"6.1.1":{"%":1.0},"6.1.2":{"%":0.4}}},"6.2":{"%":0.5,"P":{"6.2.1":{"%":1.0},"6.2.2":{"%":0.4}}}}},"8":{"%":0.4444444444444444,"P":{"8.1":{"%":0.56666666,"P":{"8.1.1":{"%":0.4},"8.1.2":{"%":1.0},"8.1.3":{"%":0.4}}},"8.2":{"%":0.56666666,"P":{"8.2.1":{"%":0.4},"8.2.2":{"%":0.4},"8.2.3":{"%":1.0}}},"8.3":{"%":1.0,"P":{"8.3.1":{"%":1.0},"8.3.2":{"%":1.0},"8.3.3":{"%":1.0}}}}},"9":{"%":0.700333,"P":{"9.1":{"%":0.4,"P":{"9.1.1":{"%":0.4},"9.1.2":{"%":0.4}}},"9.2":{"%":0.003333,"P":{"9.2.1":{"%":1.0},"9.2.2":{"%":0.4},"9.2.3":{"%":0.4},"9.2.4":{"%":1.0},"9.2.5":{"%":1.0},"9.2.6":{"%":1.0}}},"9.3":{"%":0.4,"P":{"9.3.1":{"%":0.4}}},"9.4":{"%":0.6,"P":{"9.4.1":{"%":0.4},"9.4.2":{"%":0.4},"9.4.3":{"%":0.4},"9.4.4":{"%":1.0},"9.4.5":{"%":1.0}}}}},"10":{"%":0.5,"P":{"10.1":{"%":0.5,"P":{"10.1.1":{"%":0.4},"10.1.2":{"%":1.0}}}}},"12":{"%":0.4642857142857143,"P":{"12.1":{"%":0.5,"P":{"12.1.3":{"%":0.4},"12.1.4":{"%":1.0}}},"12.2":{"%":1.0,"P":{"12.2.1":{"%":1.0}}},"12.3":{"%":0.4,"P":{"12.3.1":{"%":0.4}}},"12.4":{"%":0.25,"P":{"12.4.1":{"%":0.4},"12.4.2":{"%":1.0},"12.4.3":{"%":1.0},"12.4.4":{"%":1.0}}},"12.5":{"%":0.4,"P":{"12.5.1":{"%":0.4}}},"12.6":{"%":0.5,"P":{"12.6.1":{"%":0.4},"12.6.2":{"%":1.0}}},"12.7":{"%":1.0,"P":{"12.7.1":{"%":1.0}}}}},"13":{"%":0.56666666,"P":{"13.1":{"%":0.4,"P":{"13.1.1":{"%":0.4},"13.1.2":{"%":0.4},"13.1.3":{"%":0.4}}},"13.2":{"%":0.003333,"P":{"13.2.1":{"%":0.4},"13.2.3":{"%":1.0},"13.2.4":{"%":1.0}}}}},"14":{"%":1.07407407407407407,"P":{"14.1":{"%":1.0,"P":{"14.1.1":{"%":1.0},"14.1.2":{"%":1.0},"14.1.3":{"%":1.0}}},"14.2":{"%":0.2222222222222222,"P":{"14.2.1":{"%":0.4},"14.2.2":{"%":1.0},"14.2.3":{"%":1.0},"14.2.4":{"%":1.0},"14.2.5":{"%":0.4},"14.2.6":{"%":1.0},"14.2.7":{"%":1.0},"14.2.8":{"%":1.0},"14.2.9":{"%":1.0}}},"14.3":{"%":1.0,"P":{"14.3.1":{"%":1.0}}}}},"15":{"%":1.0,"P":{"15.1":{"%":1.0,"P":{"15.1.1":{"%":1.0},"15.1.3":{"%":1.0}}},"15.2":{"%":1.0,"P":{"15.2.1":{"%":1.0},"15.2.2":{"%":1.0}}}}},"16":{"%":0.003333,"P":{"16.1":{"%":0.003333,"P":{"16.1.1":{"%":1.0},"16.1.2":{"%":1.0},"16.1.3":{"%":1.0},"16.1.4":{"%":0.4},"16.1.5":{"%":0.4},"16.1.7":{"%":1.0}}}}},"17":{"%":1.0,"P":{"17.1":{"%":1.0,"P":{"17.1.1":{"%":1.0},"17.1.2":{"%":1.0},"17.1.3":{"%":1.0}}},"17.2":{"%":1.0,"P":{"17.2.1":{"%":1.0}}}}},"18":{"%":0.003333,"P":{"18.1":{"%":0.56666666,"P":{"18.1.3":{"%":0.4},"18.1.4":{"%":1.0},"18.1.5":{"%":0.4}}},"18.2":{"%":1.0,"P":{"18.2.1":{"%":1.0},"18.2.2":{"%":1.0},"18.2.3":{"%":1.0}}}}}},"%":0.3374559082892416},"27017_2015":{"27017_2015 Article (Technical)":{"5":{"%":1.0,"P":{"5.1":{"%":1.0,"P":{"5.1.1":{"%":1.0},"5.1.2":{"%":1.0}}}}},"6":{"%":0.25,"P":{"6.1":{"%":0.5,"P":{"6.1.1":{"%":1.0},"6.1.2":{"%":0.4}}},"6.2":{"%":1.0,"P":{"6.2.1":{"%":1.0},"6.2.2":{"%":1.0}}}}},"8":{"%":0.4444444444444444,"P":{"8.1":{"%":0.56666666,"P":{"8.1.1":{"%":0.4},"8.1.2":{"%":1.0},"8.1.3":{"%":0.4}}},"8.2":{"%":0.56666666,"P":{"8.2.1":{"%":0.4},"8.2.2":{"%":0.4},"8.2.3":{"%":1.0}}},"8.3":{"%":1.0,"P":{"8.3.1":{"%":1.0},"8.3.2":{"%":1.0},"8.3.3":{"%":1.0}}}}},"9":{"%":0.700333,"P":{"9.1":{"%":0.4,"P":{"9.1.1":{"%":0.4},"9.1.2":{"%":0.4}}},"9.2":{"%":0.003333,"P":{"9.2.1":{"%":1.0},"9.2.2":{"%":0.4},"9.2.3":{"%":0.4},"9.2.4":{"%":1.0},"9.2.5":{"%":1.0},"9.2.6":{"%":1.0}}},"9.3":{"%":0.4,"P":{"9.3.1":{"%":0.4}}},"9.4":{"%":0.6,"P":{"9.4.1":{"%":0.4},"9.4.2":{"%":0.4},"9.4.3":{"%":0.4},"9.4.4":{"%":1.0},"9.4.5":{"%":1.0}}}}},"10":{"%":0.5,"P":{"10.1":{"%":0.5,"P":{"10.1.1":{"%":0.4},"10.1.2":{"%":1.0}}}}},"12":{"%":0.4642857142857143,"P":{"12.1":{"%":0.5,"P":{"12.1.3":{"%":0.4},"12.1.4":{"%":1.0}}},"12.2":{"%":1.0,"P":{"12.2.1":{"%":1.0}}},"12.3":{"%":0.4,"P":{"12.3.1":{"%":0.4}}},"12.4":{"%":0.25,"P":{"12.4.1":{"%":0.4},"12.4.2":{"%":1.0},"12.4.3":{"%":1.0},"12.4.4":{"%":1.0}}},"12.5":{"%":0.4,"P":{"12.5.1":{"%":0.4}}},"12.6":{"%":0.5,"P":{"12.6.1":{"%":0.4},"12.6.2":{"%":1.0}}},"12.7":{"%":1.0,"P":{"12.7.1":{"%":1.0}}}}},"13":{"%":0.56666666,"P":{"13.1":{"%":0.4,"P":{"13.1.1":{"%":0.4},"13.1.2":{"%":0.4},"13.1.3":{"%":0.4}}},"13.2":{"%":0.003333,"P":{"13.2.1":{"%":0.4},"13.2.3":{"%":1.0},"13.2.4":{"%":1.0}}}}},"14":{"%":1.07407407407407407,"P":{"14.1":{"%":1.0,"P":{"14.1.1":{"%":1.0},"14.1.2":{"%":1.0},"14.1.3":{"%":1.0}}},"14.2":{"%":0.2222222222222222,"P":{"14.2.1":{"%":0.4},"14.2.2":{"%":1.0},"14.2.3":{"%":1.0},"14.2.4":{"%":1.0},"14.2.5":{"%":0.4},"14.2.6":{"%":1.0},"14.2.7":{"%":1.0},"14.2.8":{"%":1.0},"14.2.9":{"%":1.0}}},"14.3":{"%":1.0,"P":{"14.3.1":{"%":1.0}}}}},"15":{"%":1.0,"P":{"15.1":{"%":1.0,"P":{"15.1.1":{"%":1.0},"15.1.3":{"%":1.0}}},"15.2":{"%":1.0,"P":{"15.2.1":{"%":1.0},"15.2.2":{"%":1.0}}}}},"16":{"%":0.003333,"P":{"16.1":{"%":0.003333,"P":{"16.1.1":{"%":1.0},"16.1.2":{"%":1.0},"16.1.3":{"%":1.0},"16.1.4":{"%":0.4},"16.1.5":{"%":0.4},"16.1.7":{"%":1.0}}}}},"17":{"%":1.0,"P":{"17.1":{"%":1.0,"P":{"17.1.1":{"%":1.0},"17.1.2":{"%":1.0},"17.1.3":{"%":1.0}}},"17.2":{"%":1.0,"P":{"17.2.1":{"%":1.0}}}}},"18":{"%":0.003333,"P":{"18.1":{"%":0.56666666,"P":{"18.1.3":{"%":0.4},"18.1.4":{"%":1.0},"18.1.5":{"%":0.4}}},"18.2":{"%":1.0,"P":{"18.2.1":{"%":1.0},"18.2.2":{"%":1.0},"18.2.3":{"%":1.0}}}}}},"%":0.3166225749559083},"27701_2019":{"27701_2019 Article (Technical)":{"6":{"%":0.3027336860670194,"P":{"6.2":{"%":1.0,"P":{"6.2.1":{"%":1.0,"P":{"6.2.1.1":{"%":1.0},"6.2.1.2":{"%":1.0}}}}},"6.3":{"%":0.25,"P":{"6.3.1":{"%":1.0,"P":{"6.3.1.1":{"%":1.0},"6.3.1.2":{"%":1.0}}},"6.3.2":{"%":0.5,"P":{"6.3.2.1":{"%":1.0},"6.3.2.2":{"%":0.4}}}}},"6.5":{"%":0.4444444444444444,"P":{"6.5.1":{"%":0.56666666,"P":{"6.5.1.1":{"%":0.4},"6.5.1.2":{"%":1.0},"6.5.1.3":{"%":0.4}}},"6.5.2":{"%":0.56666666,"P":{"6.5.2.1":{"%":0.4},"6.5.2.2":{"%":0.4},"6.5.2.3":{"%":1.0}}},"6.5.3":{"%":1.0,"P":{"6.5.3.1":{"%":1.0},"6.5.3.2":{"%":1.0},"6.5.3.3":{"%":1.0}}}}},"6.6":{"%":0.608003,"P":{"6.6.1":{"%":0.5,"P":{"6.6.1.1":{"%":0.4},"6.6.1.2":{"%":1.0}}},"6.6.2":{"%":0.003333,"P":{"6.6.2.1":{"%":1.0},"6.6.2.2":{"%":0.4},"6.6.2.3":{"%":0.4},"6.6.2.4":{"%":1.0},"6.6.2.5":{"%":1.0},"6.6.2.6":{"%":1.0}}},"6.6.3":{"%":0.4,"P":{"6.6.3.1":{"%":0.4}}},"6.6.4":{"%":0.6,"P":{"6.6.4.1":{"%":0.4},"6.6.4.2":{"%":0.4},"6.6.4.3":{"%":0.4},"6.6.4.4":{"%":1.0},"6.6.4.5":{"%":1.0}}}}},"6.7":{"%":0.5,"P":{"6.7.1":{"%":0.5,"P":{"6.7.1.1":{"%":0.4},"6.7.1.2":{"%":1.0}}}}},"6.9":{"%":0.4642857142857143,"P":{"6.9.1":{"%":0.5,"P":{"6.9.1.3":{"%":0.4},"6.9.1.4":{"%":1.0}}},"6.9.2":{"%":1.0,"P":{"6.9.2.1":{"%":1.0}}},"6.9.3":{"%":0.4,"P":{"6.9.3.1":{"%":0.4}}},"6.9.4":{"%":0.25,"P":{"6.9.4.1":{"%":0.4},"6.9.4.2":{"%":1.0},"6.9.4.3":{"%":1.0},"6.9.4.4":{"%":1.0}}},"6.9.5":{"%":0.4,"P":{"6.9.5.1":{"%":0.4}}},"6.9.6":{"%":0.5,"P":{"6.9.6.1":{"%":0.4},"6.9.6.2":{"%":1.0}}},"6.9.7":{"%":1.0,"P":{"6.9.7.1":{"%":1.0}}}}},"6.1":{"%":0.625,"P":{"6.10.1":{"%":0.4,"P":{"6.10.1.1":{"%":0.4},"6.10.1.2":{"%":0.4},"6.10.1.3":{"%":0.4}}},"6.10.2":{"%":0.25,"P":{"6.10.2.1":{"%":0.4},"6.10.2.2":{"%":1.0},"6.10.2.3":{"%":1.0},"6.10.2.4":{"%":1.0}}}}},"6.11":{"%":1.07407407407407407,"P":{"6.11.1":{"%":1.0,"P":{"6.11.1.1":{"%":1.0},"6.11.1.2":{"%":1.0},"6.11.1.3":{"%":1.0}}},"6.11.2":{"%":0.2222222222222222,"P":{"6.11.2.1":{"%":0.4},"6.11.2.2":{"%":1.0},"6.11.2.3":{"%":1.0},"6.11.2.4":{"%":1.0},"6.11.2.5":{"%":0.4},"6.11.2.6":{"%":1.0},"6.11.2.7":{"%":1.0},"6.11.2.8":{"%":1.0},"6.11.2.9":{"%":1.0}}},"6.11.3":{"%":1.0,"P":{"6.11.3.1":{"%":1.0}}}}},"6.12":{"%":1.0,"P":{"6.12.1":{"%":1.0,"P":{"6.12.1.1":{"%":1.0},"6.12.1.3":{"%":1.0}}},"6.12.2":{"%":1.0,"P":{"6.12.2.1":{"%":1.0},"6.12.2.2":{"%":1.0}}}}},"6.13":{"%":0.003333,"P":{"6.13.1":{"%":0.003333,"P":{"6.13.1.1":{"%":1.0},"6.13.1.2":{"%":1.0},"6.13.1.3":{"%":1.0},"6.13.1.4":{"%":0.4},"6.13.1.5":{"%":0.4},"6.13.1.7":{"%":1.0}}}}},"6.14":{"%":1.0,"P":{"6.14.1":{"%":1.0,"P":{"6.14.1.1":{"%":1.0},"6.14.1.2":{"%":1.0},"6.14.1.3":{"%":1.0}}},"6.14.2":{"%":1.0,"P":{"6.14.2.1":{"%":1.0}}}}},"6.15":{"%":0.003333,"P":{"6.15.1":{"%":0.56666666,"P":{"6.15.1.3":{"%":0.4},"6.15.1.4":{"%":1.0},"6.15.1.5":{"%":0.4}}},"6.15.2":{"%":1.0,"P":{"6.15.2.1":{"%":1.0},"6.15.2.2":{"%":1.0},"6.15.2.3":{"%":1.0}}}}}}}},"%":0.3027336860670194}},"Standard11":{"800-171 Rev2":{"Technical control coverage : ":{"3.1 ACCESS CONTROL":{"%":0.45454545454545453,"P":{"3.1.1":{"%":0.4},"3.1.2":{"%":0.4},"3.1.3":{"%":1.0},"3.1.4":{"%":0.4},"3.1.5":{"%":0.4},"3.1.6":{"%":0.4},"3.1.7":{"%":0.4},"3.1.8":{"%":1.0},"3.1.9":{"%":1.0},"3.1.10":{"%":1.0},"3.1.11":{"%":1.0},"3.1.12":{"%":0.4},"3.1.13":{"%":0.4},"3.1.14":{"%":1.0},"3.1.15":{"%":1.0},"3.1.16":{"%":1.0},"3.1.17":{"%":0.4},"3.1.18":{"%":0.4},"3.1.19":{"%":1.0},"3.1.20":{"%":1.0},"3.1.21":{"%":1.0},"3.1.22":{"%":1.0}}},"3.3 AUDIT AND ACCOUNTABILITY":{"%":0.003333,"P":{"3.3.1":{"%":0.4},"3.3.2":{"%":1.0},"3.3.3":{"%":0.4},"3.3.4":{"%":1.0},"3.3.5":{"%":0.4},"3.3.6":{"%":1.0},"3.3.7":{"%":1.0},"3.3.8":{"%":1.0},"3.3.9":{"%":1.0}}},"3.4 CONFIGURATION MANAGEMENT":{"%":0.4444444444444444,"P":{"3.4.1":{"%":0.4},"3.4.2":{"%":0.4},"3.4.3":{"%":1.0},"3.4.4":{"%":1.0},"3.4.5":{"%":1.0},"3.4.6":{"%":0.4},"3.4.7":{"%":0.4},"3.4.8":{"%":1.0},"3.4.9":{"%":1.0}}},"3.5 INCIDENT AND AUTHENTICATION":{"%":0.2727272727272727,"P":{"3.5.1":{"%":1.0},"3.5.2":{"%":1.0},"3.5.3":{"%":0.4},"3.5.4":{"%":1.0},"3.5.5":{"%":1.0},"3.5.6":{"%":1.0},"3.5.7":{"%":0.4},"3.5.8":{"%":1.0},"3.5.9":{"%":1.0},"3.5.10":{"%":0.4},"3.5.11":{"%":1.0}}},"3.6 INCIDENT RESPONSE":{"%":0.003333,"P":{"3.6.1":{"%":0.4},"3.6.2":{"%":1.0},"3.6.3":{"%":1.0}}},"3.7 MAINTENANCE":{"%":1.0,"P":{"3.7.1":{"%":1.0},"3.7.2":{"%":1.0},"3.7.3":{"%":1.0},"3.7.4":{"%":1.0},"3.7.5":{"%":1.0},"3.7.6":{"%":1.0}}},"3.8 MEDIA PROTECTION":{"%":0.2222222222222222,"P":{"3.8.1":{"%":0.4},"3.8.2":{"%":0.4},"3.8.3":{"%":1.0},"3.8.4":{"%":1.0},"3.8.5":{"%":1.0},"3.8.6":{"%":1.0},"3.8.7":{"%":1.0},"3.8.8":{"%":1.0},"3.8.9":{"%":1.0}}},"3.9 PERSONNEL SECURITY":{"%":1.0,"P":{"3.9.1":{"%":1.0},"3.9.2":{"%":1.0}}},"3.11 RISK ASSESMENT":{"%":0.003333,"P":{"3.11.1":{"%":1.0},"3.11.2":{"%":0.4},"3.11.3":{"%":1.0}}},"3.12 SECURITY ASSESSMENT":{"%":1.0,"P":{"3.12.1":{"%":1.0},"3.12.2":{"%":1.0},"3.12.3":{"%":1.0},"3.12.4":{"%":1.0}}},"3.13 SYSTEM AND COMMUNICATIOBS PROTECTION":{"%":0.375,"P":{"3.13.1":{"%":0.4},"3.13.2":{"%":0.4},"3.13.3":{"%":1.0},"3.13.4":{"%":1.0},"3.13.5":{"%":0.4},"3.13.6":{"%":1.0},"3.13.7":{"%":1.0},"3.13.8":{"%":0.4},"3.13.9":{"%":1.0},"3.13.10":{"%":1.0},"3.13.11":{"%":1.0},"3.13.12":{"%":1.0},"3.13.13":{"%":1.0},"3.13.14":{"%":1.0},"3.13.15":{"%":0.4},"3.13.16":{"%":0.4}}},"3.14 SYSTEM AND INFORMATION PROTECTION":{"%":0.156666666,"P":{"3.14.1":{"%":1.0},"3.14.2":{"%":1.0},"3.14.3":{"%":1.0},"3.14.4":{"%":1.0},"3.14.5":{"%":1.0},"3.14.6":{"%":0.4}}}},"%":0.24463383838383837},"800_53 Rev5":{"Control":{"3.1 ACCESS CONTROL":{"%":0.23084886128364393,"P":{"AC-1":{"%":1.0,"P":{"null":{"%":1.0}}},"AC-2":{"%":0.156666666,"P":{"AC-2.1":{"%":0.4},"AC-2.2":{"%":1.0},"AC-2.3":{"%":1.0},"AC-2.4":{"%":1.0},"AC-2.5":{"%":1.0},"AC-2.6":{"%":1.0},"AC-2.7":{"%":0.4},"AC-2.8":{"%":1.0},"AC-2.9":{"%":1.0},"AC-2.11":{"%":1.0},"AC-2.12":{"%":1.0},"AC-2.13":{"%":1.0}}},"AC-3":{"%":0.4,"P":{"AC-3.2":{"%":0.4},"AC-3.3":{"%":0.4},"AC-3.4":{"%":0.4},"AC-3.5":{"%":0.4},"AC-3.7":{"%":0.4},"AC-3.8":{"%":0.4},"AC-3.9":{"%":0.4},"AC-3.10":{"%":0.4},"AC-3.11":{"%":0.4},"AC-3.12":{"%":0.4},"AC-3.13":{"%":0.4},"AC-3.14":{"%":0.4},"AC-3.15":{"%":0.4}}},"AC-101":{"%":1.0,"P":{"AC-101.1":{"%":1.0},"AC-101.2":{"%":1.0},"AC-101.3":{"%":1.0},"AC-101.4":{"%":1.0},"AC-101.5":{"%":1.0},"AC-101.6":{"%":1.0},"AC-101.7":{"%":1.0},"AC-101.8":{"%":1.0},"AC-101.9":{"%":1.0},"AC-101.10":{"%":1.0},"AC-101.11":{"%":1.0},"AC-101.12":{"%":1.0},"AC-101.13":{"%":1.0},"AC-101.14":{"%":1.0},"AC-101.15":{"%":1.0},"AC-101.17":{"%":1.0},"AC-101.18":{"%":1.0},"AC-101.19":{"%":1.0},"AC-101.20":{"%":1.0},"AC-101.21":{"%":1.0},"AC-101.22":{"%":1.0},"AC-101.23":{"%":1.0},"AC-101.24":{"%":1.0},"AC-101.25":{"%":1.0},"AC-101.26":{"%":1.0},"AC-101.27":{"%":1.0},"AC-101.28":{"%":1.0},"AC-101.29":{"%":1.0},"AC-101.30":{"%":1.0},"AC-101.31":{"%":1.0},"AC-101.32":{"%":1.0}}},"AC-5":{"%":0.4,"P":{"AC-101.32":{"%":0.4}}},"AC-88":{"%":0.4,"P":{"AC-88.1":{"%":0.4},"AC-88.2":{"%":0.4},"AC-88.3":{"%":0.4},"AC-88.4":{"%":0.4},"AC-88.5":{"%":0.4},"AC-88.6":{"%":0.4},"AC-88.7":{"%":0.4},"AC-88.8":{"%":0.4},"AC-88.9":{"%":0.4},"AC-88.10":{"%":0.4}}},"AC-7":{"%":1.0,"P":{"AC-7.2":{"%":1.0},"AC-7.3":{"%":1.0},"AC-7.4":{"%":1.0}}},"AC-8":{"%":1.0,"P":{"AC-7.4":{"%":1.0}}},"AC-9":{"%":1.0,"P":{"AC-9.1":{"%":1.0},"AC-9.2":{"%":1.0},"AC-9.3":{"%":1.0},"AC-9.4":{"%":1.0}}},"AC-10":{"%":1.0,"P":{"AC-9.4":{"%":1.0}}},"AC-11":{"%":1.0,"P":{"AC-9.4":{"%":1.0}}},"AC-12":{"%":1.0,"P":{"AC-12.1":{"%":1.0},"AC-12.2":{"%":1.0},"AC-13.3":{"%":1.0}}},"AC-14":{"%":1.0,"P":{"AC-13.3":{"%":1.0}}},"AC-16":{"%":1.0,"P":{"AC-16.1":{"%":1.0},"AC-16.2":{"%":1.0},"AC-16.3":{"%":1.0},"AC-16.4":{"%":1.0},"AC-16.5":{"%":1.0},"AC-16.6":{"%":1.0},"AC-16.7":{"%":1.0},"AC-16.8":{"%":1.0},"AC-16.9":{"%":1.0},"AC-16.10":{"%":1.0}}},"AC-17":{"%":0.14285714285714285,"P":{"AC-17.1":{"%":1.0},"AC-17.2":{"%":0.4},"AC-17.3":{"%":1.0},"AC-17.4":{"%":1.0},"AC-17.6":{"%":1.0},"AC-17.9":{"%":1.0},"AC-17.10":{"%":1.0}}},"AC-18":{"%":0.4,"P":{"AC-18.1":{"%":0.4},"AC-18.3":{"%":0.4},"AC-18.4":{"%":0.4},"AC-18.5":{"%":0.4}}},"AC-19":{"%":0.4,"P":{"AC-19.4":{"%":0.4},"AC-19.5":{"%":0.4}}},"AC-20":{"%":1.0,"P":{"AC-20.1":{"%":1.0},"AC-20.2":{"%":1.0},"AC-20.3":{"%":1.0},"AC-20.4":{"%":1.0},"AC-20.5":{"%":1.0}}},"AC-21":{"%":1.0,"P":{"AC-21.1":{"%":1.0},"AC-21.2":{"%":1.0}}},"AC-22":{"%":1.0,"P":{"AC-21.2":{"%":1.0}}},"AC-23":{"%":1.0,"P":{"AC-21.2":{"%":1.0}}},"AC-24":{"%":1.0,"P":{"AC-24.1":{"%":1.0},"AC-24.2":{"%":1.0}}},"AC-25":{"%":1.0,"P":{"AC-24.2":{"%":1.0}}}}},"3.2 AWARENESS AND TRAINING":{"%":1.0,"P":{"AT-1":{"%":1.0,"P":{"null":{"%":1.0}}},"AT-2":{"%":1.0,"P":{"AT-2.1":{"%":1.0},"AT-2.2":{"%":1.0},"AT-2.3":{"%":1.0},"AT-2.4":{"%":1.0},"AT-2.5":{"%":1.0},"AT-2.6":{"%":1.0}}},"AT-3":{"%":1.0,"P":{"AT-3.1":{"%":1.0},"AT-3.2":{"%":1.0},"AT-3.3":{"%":1.0},"AT-3.5":{"%":1.0}}},"AT-101":{"%":1.0,"P":{"AT-3.5":{"%":1.0}}},"AT-88":{"%":1.0,"P":{"AT-3.5":{"%":1.0}}}}},"3.3 AUDIT AND ACCOUNTABILITY":{"%":0.3941326530612245,"P":{"AU-1":{"%":1.0,"P":{"null":{"%":1.0}}},"AU-2":{"%":0.4,"P":{"null":{"%":0.4}}},"AU-3":{"%":0.4,"P":{"AU-3.1":{"%":0.4},"AU-3.2":{"%":0.4},"AU-3.3":{"%":0.4}}},"AU-101":{"%":0.4,"P":{"AU-3.3":{"%":0.4}}},"AU-5":{"%":1.0,"P":{"AU-5.1":{"%":1.0},"AU-5.2":{"%":1.0},"AU-5.3":{"%":1.0},"AU-5.4":{"%":1.0},"AU-5.5":{"%":1.0}}},"AU-88":{"%":0.375,"P":{"AU-88.1":{"%":0.4},"AU-88.3":{"%":0.4},"AU-88.4":{"%":1.0},"AU-88.5":{"%":0.4},"AU-88.6":{"%":1.0},"AU-88.7":{"%":1.0},"AU-88.8":{"%":1.0},"AU-88.9":{"%":1.0}}},"AU-7":{"%":0.4,"P":{"AU-88.9":{"%":0.4}}},"AU-8":{"%":1.0,"P":{"AU-88.9":{"%":1.0}}},"AU-9":{"%":0.14285714285714285,"P":{"AU-9.1":{"%":1.0},"AU-9.2":{"%":1.0},"AU-9.3":{"%":1.0},"AU-9.4":{"%":0.4},"AU-9.5":{"%":1.0},"AU-9.6":{"%":1.0},"AU-9.7":{"%":1.0}}},"AU-10":{"%":1.0,"P":{"AU-10.1":{"%":1.0},"AU-10.2":{"%":1.0},"AU-10.3":{"%":1.0},"AU-10.4":{"%":1.0}}},"AU-12":{"%":0.4,"P":{"AU-12.1":{"%":0.4},"AU-12.2":{"%":0.4},"AU-12.3":{"%":0.4},"AU-12.4":{"%":0.4}}},"AU-13":{"%":1.0,"P":{"AU-13.1":{"%":1.0},"AU-13.2":{"%":1.0},"AU-13.3":{"%":1.0}}},"AU-14":{"%":1.0,"P":{"AU-14.1":{"%":1.0},"AU-14.3":{"%":1.0}}},"AU-16":{"%":1.0,"P":{"AU-16.1":{"%":1.0},"AU-16.2":{"%":1.0},"AU-16.3":{"%":1.0}}}}},"3.4 ASSESSMENT, AUTHORIZATION, AND MONITORING":{"%":0.125,"P":{"CA-1":{"%":1.0,"P":{"null":{"%":1.0}}},"CA-2":{"%":1.0,"P":{"CA-2.1":{"%":1.0},"CA-2.2":{"%":1.0},"CA-2.3":{"%":1.0}}},"CA-3":{"%":1.0,"P":{"CA-3.6":{"%":1.0},"CA-3.7":{"%":1.0}}},"CA-5":{"%":1.0,"P":{"CA-3.7":{"%":1.0}}},"CA-88":{"%":1.0,"P":{"CA-88.1":{"%":1.0},"CA-88.2":{"%":1.0}}},"CA-7":{"%":1.0,"P":{"CA-1.1":{"%":1.0},"CA-1.3":{"%":1.0},"CA-1.4":{"%":1.0},"CA-1.5":{"%":1.0},"CA-1.6":{"%":1.0}}},"CA-8":{"%":1.0,"P":{"CA-8.1":{"%":1.0},"CA-8.2":{"%":1.0},"CA-8.3":{"%":1.0}}},"CA-9":{"%":0.4,"P":{"CA-8.3":{"%":0.4}}}}},"3.5 CONFIGURATION MANAGEMENT":{"%":0.5803571428571429,"P":{"CM-1":{"%":0.4,"P":{"null":{"%":0.4}}},"CM-2":{"%":0.4,"P":{"CM-2.2":{"%":0.4},"CM-2.3":{"%":0.4},"CM-2.6":{"%":0.4},"CM-2.7":{"%":0.4}}},"CM-3":{"%":1.0,"P":{"CM-3.1":{"%":1.0},"CM-3.2":{"%":1.0},"CM-3.3":{"%":1.0},"CM-3.4":{"%":1.0},"CM-3.5":{"%":1.0},"CM-3.6":{"%":1.0},"CM-3.7":{"%":1.0},"CM-3.8":{"%":1.0}}},"CM-101":{"%":1.0,"P":{"CM-101.1":{"%":1.0},"CM-101.2":{"%":1.0}}},"CM-5":{"%":1.0,"P":{"CM-5.1":{"%":1.0},"CM-5.2":{"%":1.0},"CM-5.3":{"%":1.0},"CM-5.4":{"%":1.0},"CM-5.5":{"%":1.0},"CM-5.6":{"%":1.0}}},"CM-88":{"%":0.4,"P":{"CM-88.1":{"%":0.4},"CM-88.2":{"%":0.4}}},"CM-7":{"%":0.4,"P":{"CM-7.1":{"%":0.4},"CM-7.2":{"%":0.4},"CM-7.3":{"%":0.4},"CM-7.4":{"%":0.4},"CM-7.5":{"%":0.4},"CM-7.6":{"%":0.4},"CM-7.7":{"%":0.4},"CM-7.8":{"%":0.4},"CM-7.9":{"%":0.4}}},"CM-8":{"%":0.125,"P":{"CM-8.1":{"%":1.0},"CM-8.2":{"%":1.0},"CM-8.3":{"%":0.4},"CM-8.4":{"%":1.0},"CM-8.6":{"%":1.0},"CM-8.7":{"%":1.0},"CM-8.8":{"%":1.0},"CM-8.9":{"%":1.0}}},"CM-9":{"%":0.4,"P":{"CM-8.9":{"%":0.4}}},"CM-10":{"%":0.4,"P":{"CM-8.9":{"%":0.4}}},"CM-11":{"%":0.4,"P":{"CM-11.2":{"%":0.4},"CM-11.3":{"%":0.4}}},"CM-12":{"%":0.4,"P":{"CM-11.3":{"%":0.4}}},"CM-13":{"%":1.0,"P":{"CM-11.3":{"%":1.0}}},"CM-14":{"%":1.0,"P":{"CM-11.3":{"%":1.0}}}}},"3.6 CONTINGENCY PLANNING":{"%":0.156666666,"P":{"CP-1":{"%":1.0,"P":{"null":{"%":1.0}}},"CP-2":{"%":1.0,"P":{"CP-2.1":{"%":1.0},"CP-2.2":{"%":1.0},"CP-2.3":{"%":1.0},"CP-2.5":{"%":1.0},"CP-2.6":{"%":1.0},"CP-2.7":{"%":1.0},"CP-2.8":{"%":1.0}}},"CP-3":{"%":1.0,"P":{"CP-3.1":{"%":1.0},"CP-3.2":{"%":1.0}}},"CP-101":{"%":1.0,"P":{"CP-101.1":{"%":1.0},"CP-101.2":{"%":1.0},"CP-101.3":{"%":1.0},"CP-101.4":{"%":1.0},"CP-101.5":{"%":1.0}}},"CP-88":{"%":1.0,"P":{"CP-88.1":{"%":1.0},"CP-88.2":{"%":1.0},"CP-88.3":{"%":1.0}}},"CP-7":{"%":1.0,"P":{"CP-7.1":{"%":1.0},"CP-7.2":{"%":1.0},"CP-7.3":{"%":1.0},"CP-7.4":{"%":1.0},"CP-7.6":{"%":1.0}}},"CP-8":{"%":1.0,"P":{"CP-8.1":{"%":1.0},"CP-8.2":{"%":1.0},"CP-8.3":{"%":1.0},"CP-8.4":{"%":1.0},"CP-8.5":{"%":1.0}}},"CP-9":{"%":0.4,"P":{"CP-9.1":{"%":0.4},"CP-9.2":{"%":0.4},"CP-9.3":{"%":0.4},"CP-9.5":{"%":0.4},"CP-9.6":{"%":0.4},"CP-9.7":{"%":0.4},"CP-9.8":{"%":0.4}}},"CP-10":{"%":0.4,"P":{"CP-10.2":{"%":0.4},"CP-10.4":{"%":0.4},"CP-10.6":{"%":0.4}}},"CP-11":{"%":1.0,"P":{"CP-10.6":{"%":1.0}}},"CP-12":{"%":1.0,"P":{"CP-10.6":{"%":1.0}}},"CP-13":{"%":1.0,"P":{"CP-10.6":{"%":1.0}}}}},"3.7 IDENTIFICATION AND AUTHENTICATION":{"%":1.021527777777777774,"P":{"IA-1":{"%":1.0,"P":{"IA-1.1":{"%":1.0},"IA-1.2":{"%":1.0},"IA-1.5":{"%":1.0},"IA-1.6":{"%":1.0},"IA-1.8":{"%":1.0},"IA-1.9":{"%":1.0},"IA-1.10":{"%":1.0},"IA-1.11":{"%":1.0},"IA-1.12":{"%":1.0}}},"IA-2":{"%":0.125,"P":{"IA-2.1":{"%":1.0},"IA-2.2":{"%":0.4},"IA-2.5":{"%":1.0},"IA-2.6":{"%":1.0},"IA-2.8":{"%":1.0},"IA-2.10":{"%":1.0},"IA-2.12":{"%":1.0},"IA-2.13":{"%":1.0}}},"IA-3":{"%":1.0,"P":{"IA-3.1":{"%":1.0},"IA-3.3":{"%":1.0},"IA-3.4":{"%":1.0}}},"IA-101":{"%":1.0,"P":{"IA-101.1":{"%":1.0},"IA-101.4":{"%":1.0},"IA-101.5":{"%":1.0},"IA-101.6":{"%":1.0},"IA-101.8":{"%":1.0},"IA-101.9":{"%":1.0}}},"IA-5":{"%":0.1003333,"P":{"IA-5.1":{"%":0.4},"IA-5.2":{"%":1.0},"IA-5.5":{"%":0.4},"IA-5.6":{"%":1.0},"IA-5.7":{"%":1.0},"IA-5.8":{"%":1.0},"IA-5.9":{"%":1.0},"IA-5.10":{"%":1.0},"IA-5.12":{"%":1.0},"IA-5.13":{"%":1.0},"IA-5.14":{"%":1.0},"IA-5.15":{"%":1.0},"IA-5.16":{"%":1.0},"IA-5.17":{"%":1.0},"IA-5.18":{"%":1.0}}},"IA-88":{"%":1.0,"P":{"IA-5.18":{"%":1.0}}},"IA-7":{"%":1.0,"P":{"IA-5.18":{"%":1.0}}},"IA-8":{"%":1.0,"P":{"IA-8.1":{"%":1.0},"IA-8.2":{"%":1.0},"IA-8.4":{"%":1.0},"IA-8.5":{"%":1.0},"IA-8.6":{"%":1.0}}},"IA-9":{"%":1.0,"P":{"IA-8.6":{"%":1.0}}},"IA-10":{"%":1.0,"P":{"IA-8.6":{"%":1.0}}},"IA-11":{"%":1.0,"P":{"IA-8.6":{"%":1.0}}},"IA-12":{"%":1.0,"P":{"IA-12.1":{"%":1.0},"IA-12.2":{"%":1.0},"IA-12.3":{"%":1.0},"IA-12.4":{"%":1.0},"IA-12.5":{"%":1.0},"IA-12.6":{"%":1.0}}}}},"3.8 INCIDENT RESPONSE":{"%":0.2222222222222222,"P":{"IR-1":{"%":0.4,"P":{"null":{"%":0.4}}},"IR-2":{"%":1.0,"P":{"IR-2.1":{"%":1.0},"IR-2.2":{"%":1.0},"IR-2.3":{"%":1.0}}},"IR-3":{"%":1.0,"P":{"IR-3.1":{"%":1.0},"IR-3.2":{"%":1.0},"IR-3.3":{"%":1.0}}},"IR-101":{"%":1.0,"P":{"IR-101.1":{"%":1.0},"IR-101.2":{"%":1.0},"IR-101.3":{"%":1.0},"IR-101.4":{"%":1.0},"IR-101.5":{"%":1.0},"IR-101.6":{"%":1.0},"IR-101.7":{"%":1.0},"IR-101.8":{"%":1.0},"IR-101.9":{"%":1.0},"IR-101.10":{"%":1.0},"IR-101.11":{"%":1.0},"IR-101.12":{"%":1.0},"IR-101.13":{"%":1.0},"IR-101.14":{"%":1.0},"IR-101.15":{"%":1.0}}},"IR-5":{"%":1.0,"P":{"IR-101.15":{"%":1.0}}},"IR-88":{"%":1.0,"P":{"IR-88.1":{"%":1.0},"IR-88.2":{"%":1.0},"IR-88.3":{"%":1.0}}},"IR-7":{"%":1.0,"P":{"IR-7.1":{"%":1.0},"IR-7.2":{"%":1.0}}},"IR-8":{"%":0.4,"P":{"IR-7.2":{"%":0.4}}},"IR-9":{"%":1.0,"P":{"IR-9.2":{"%":1.0},"IR-9.3":{"%":1.0},"IR-9.4":{"%":1.0}}}}},"3.9 MAINTENANCE":{"%":1.0,"P":{"MA-1":{"%":1.0,"P":{"null":{"%":1.0}}},"MA-2":{"%":1.0,"P":{"null":{"%":1.0}}},"MA-3":{"%":1.0,"P":{"MA-3.1":{"%":1.0},"MA-3.2":{"%":1.0},"MA-3.3":{"%":1.0},"MA-3.4":{"%":1.0},"MA-3.5":{"%":1.0},"MA-3.6":{"%":1.0}}},"MA-101":{"%":1.0,"P":{"MA-101.1":{"%":1.0},"MA-101.3":{"%":1.0},"MA-101.4":{"%":1.0},"MA-101.5":{"%":1.0},"MA-101.6":{"%":1.0},"MA-101.7":{"%":1.0}}},"MA-5":{"%":1.0,"P":{"MA-5.1":{"%":1.0},"MA-5.2":{"%":1.0},"MA-5.3":{"%":1.0},"MA-5.4":{"%":1.0},"MA-5.5":{"%":1.0}}},"MA-88":{"%":1.0,"P":{"MA-88.1":{"%":1.0},"MA-88.2":{"%":1.0},"MA-88.3":{"%":1.0}}},"MA-7":{"%":1.0,"P":{"MA-88.3":{"%":1.0}}}}},"3.10 MEDIA PROTECTION":{"%":0.125,"P":{"MP-1":{"%":1.0,"P":{"null":{"%":1.0}}},"MP-2":{"%":0.4,"P":{"null":{"%":0.4}}},"MP-3":{"%":1.0,"P":{"null":{"%":1.0}}},"MP-101":{"%":1.0,"P":{"null":{"%":1.0}}},"MP-5":{"%":1.0,"P":{"null":{"%":1.0}}},"MP-88":{"%":1.0,"P":{"MP-88.1":{"%":1.0},"MP-88.2":{"%":1.0},"MP-88.3":{"%":1.0},"MP-88.7":{"%":1.0},"MP-88.8":{"%":1.0}}},"MP-7":{"%":1.0,"P":{"MP-88.8":{"%":1.0}}},"MP-8":{"%":1.0,"P":{"MP-8.1":{"%":1.0},"MP-8.2":{"%":1.0},"MP-8.3":{"%":1.0},"MP-8.4":{"%":1.0}}}}},"3.11 PHYSICAL AND ENVIRONMENTAL PROTECTION":{"%":1.0,"P":{"PE-1":{"%":1.0,"P":{"null":{"%":1.0}}},"PE-2":{"%":1.0,"P":{"PE-2.1":{"%":1.0},"PE-2.2":{"%":1.0},"PE-2.3":{"%":1.0}}},"PE-3":{"%":1.0,"P":{"PE-3.1":{"%":1.0},"PE-3.2":{"%":1.0},"PE-3.3":{"%":1.0},"PE-3.4":{"%":1.0},"PE-3.5":{"%":1.0},"PE-3.7":{"%":1.0},"PE-3.8":{"%":1.0}}},"PE-101":{"%":1.0,"P":{"PE-3.8":{"%":1.0}}},"PE-5":{"%":1.0,"P":{"PE-3.8":{"%":1.0}}},"PE-88":{"%":1.0,"P":{"PE-88.1":{"%":1.0},"PE-88.2":{"%":1.0},"PE-88.3":{"%":1.0},"PE-88.4":{"%":1.0}}},"PE-8":{"%":1.0,"P":{"PE-8.1":{"%":1.0},"PE-8.3":{"%":1.0}}},"PE-9":{"%":1.0,"P":{"PE-9.1":{"%":1.0},"PE-9.2":{"%":1.0}}},"PE-10":{"%":1.0,"P":{"PE-9.2":{"%":1.0}}},"PE-11":{"%":1.0,"P":{"PE-11.1":{"%":1.0},"PE-11.2":{"%":1.0}}},"PE-12":{"%":1.0,"P":{"PE-11.2":{"%":1.0}}},"PE-13":{"%":1.0,"P":{"PE-13.1":{"%":1.0},"PE-13.2":{"%":1.0},"PE-13.4":{"%":1.0}}},"PE-14":{"%":1.0,"P":{"PE-14.1":{"%":1.0},"PE-14.2":{"%":1.0}}},"PE-15":{"%":1.0,"P":{"PE-14.2":{"%":1.0}}},"PE-16":{"%":1.0,"P":{"PE-14.2":{"%":1.0}}},"PE-17":{"%":1.0,"P":{"PE-14.2":{"%":1.0}}},"PE-18":{"%":1.0,"P":{"PE-14.2":{"%":1.0}}},"PE-19":{"%":1.0,"P":{"PE-14.2":{"%":1.0}}},"PE-20":{"%":1.0,"P":{"PE-14.2":{"%":1.0}}},"PE-21":{"%":1.0,"P":{"PE-14.2":{"%":1.0}}},"PE-22":{"%":1.0,"P":{"PE-14.2":{"%":1.0}}},"PE-23":{"%":1.0,"P":{"PE-14.2":{"%":1.0}}}}},"3.12 PLANNING":{"%":0.125,"P":{"PL-1":{"%":1.0,"P":{"null":{"%":1.0}}},"PL-2":{"%":1.0,"P":{"null":{"%":1.0}}},"PL-101":{"%":1.0,"P":{"null":{"%":1.0}}},"PL-7":{"%":1.0,"P":{"null":{"%":1.0}}},"PL-8":{"%":0.4,"P":{"PL-8.1":{"%":0.4},"PL-8.2":{"%":0.4}}},"PL-9":{"%":1.0,"P":{"PL-8.2":{"%":1.0}}},"PL-10":{"%":1.0,"P":{"PL-8.2":{"%":1.0}}},"PL-11":{"%":1.0,"P":{"PL-8.2":{"%":1.0}}}}},"3.13 PROGRAM MANAGEMENT":{"%":1.03125,"P":{"PM-1":{"%":1.0,"P":{"null":{"%":1.0}}},"PM-2":{"%":1.0,"P":{"null":{"%":1.0}}},"PM-3":{"%":1.0,"P":{"null":{"%":1.0}}},"PM-101":{"%":1.0,"P":{"null":{"%":1.0}}},"PM-5":{"%":1.0,"P":{"null":{"%":1.0}}},"PM-88":{"%":1.0,"P":{"null":{"%":1.0}}},"PM-7":{"%":0.4,"P":{"null":{"%":0.4}}},"PM-8":{"%":1.0,"P":{"null":{"%":1.0}}},"PM-9":{"%":1.0,"P":{"null":{"%":1.0}}},"PM-10":{"%":1.0,"P":{"null":{"%":1.0}}},"PM-11":{"%":1.0,"P":{"null":{"%":1.0}}},"PM-12":{"%":1.0,"P":{"null":{"%":1.0}}},"PM-13":{"%":1.0,"P":{"null":{"%":1.0}}},"PM-14":{"%":1.0,"P":{"null":{"%":1.0}}},"PM-15":{"%":1.0,"P":{"null":{"%":1.0}}},"PM-16":{"%":1.0,"P":{"null":{"%":1.0}}},"PM-17":{"%":1.0,"P":{"null":{"%":1.0}}},"PM-18":{"%":1.0,"P":{"null":{"%":1.0}}},"PM-19":{"%":1.0,"P":{"null":{"%":1.0}}},"PM-20":{"%":1.0,"P":{"null":{"%":1.0}}},"PM-21":{"%":1.0,"P":{"null":{"%":1.0}}},"PM-22":{"%":1.0,"P":{"null":{"%":1.0}}},"PM-23":{"%":1.0,"P":{"null":{"%":1.0}}},"PM-24":{"%":1.0,"P":{"null":{"%":1.0}}},"PM-25":{"%":1.0,"P":{"null":{"%":1.0}}},"PM-26":{"%":1.0,"P":{"null":{"%":1.0}}},"PM-27":{"%":1.0,"P":{"null":{"%":1.0}}},"PM-28":{"%":1.0,"P":{"null":{"%":1.0}}},"PM-29":{"%":1.0,"P":{"null":{"%":1.0}}},"PM-30":{"%":1.0,"P":{"null":{"%":1.0}}},"PM-31":{"%":1.0,"P":{"null":{"%":1.0}}},"PM-32":{"%":1.0,"P":{"null":{"%":1.0}}}}},"3.14 PERSONNEL SECURITY":{"%":1.0,"P":{"PS-1":{"%":1.0,"P":{"null":{"%":1.0}}},"PS-2":{"%":1.0,"P":{"null":{"%":1.0}}},"PS-3":{"%":1.0,"P":{"PS-3.1":{"%":1.0},"PS-3.2":{"%":1.0},"PS-3.3":{"%":1.0},"PS-3.4":{"%":1.0}}},"PS-101":{"%":1.0,"P":{"PS-101.1":{"%":1.0},"PS-101.2":{"%":1.0}}},"PS-5":{"%":1.0,"P":{"PS-101.2":{"%":1.0}}},"PS-88":{"%":1.0,"P":{"PS-88.2":{"%":1.0},"PS-88.3":{"%":1.0}}},"PS-7":{"%":1.0,"P":{"PS-88.3":{"%":1.0}}},"PS-8":{"%":1.0,"P":{"PS-88.3":{"%":1.0}}},"PS-9":{"%":1.0,"P":{"PS-88.3":{"%":1.0}}}}},"3.15 PERSONALLY IDENTIFIABLE INFORMATION PROCESSING AND TRANSPARENCY":{"%":1.0,"P":{"PT-1":{"%":1.0,"P":{"null":{"%":1.0}}},"PT-2":{"%":1.0,"P":{"PT-2.1":{"%":1.0},"PT-2.2":{"%":1.0}}},"PT-3":{"%":1.0,"P":{"PT-3.1":{"%":1.0},"PT-3.2":{"%":1.0}}},"PT-101":{"%":1.0,"P":{"PT-101.1":{"%":1.0},"PT-101.2":{"%":1.0},"PT-101.3":{"%":1.0}}},"PT-5":{"%":1.0,"P":{"PT-5.1":{"%":1.0},"PT-5.2":{"%":1.0}}},"PT-88":{"%":1.0,"P":{"PT-88.1":{"%":1.0},"PT-88.2":{"%":1.0}}},"PT-7":{"%":1.0,"P":{"PT-7.1":{"%":1.0},"PT-7.2":{"%":1.0}}},"PT-8":{"%":1.0,"P":{"PT-7.2":{"%":1.0}}}}},"3.16 RISK ASSESSMENT":{"%":0.003333,"P":{"RA-1":{"%":1.0,"P":{"null":{"%":1.0}}},"RA-2":{"%":0.4,"P":{"null":{"%":0.4}}},"RA-3":{"%":1.0,"P":{"RA-3.1":{"%":1.0},"RA-3.2":{"%":1.0},"RA-3.3":{"%":1.0},"RA-3.4":{"%":1.0}}},"RA-5":{"%":0.4,"P":{"RA-5.2":{"%":0.4},"RA-5.3":{"%":0.4},"RA-5.4":{"%":0.4},"RA-5.5":{"%":0.4},"RA-5.6":{"%":0.4},"RA-5.8":{"%":0.4},"RA-5.10":{"%":0.4},"RA-5.11":{"%":0.4}}},"RA-88":{"%":1.0,"P":{"RA-5.11":{"%":1.0}}},"RA-7":{"%":0.4,"P":{"RA-5.11":{"%":0.4}}},"RA-8":{"%":1.0,"P":{"RA-5.11":{"%":1.0}}},"RA-9":{"%":1.0,"P":{"RA-5.11":{"%":1.0}}},"RA-10":{"%":1.0,"P":{"RA-5.11":{"%":1.0}}}}},"3.17 SYSTEM AND SERVICES ACQUISITION":{"%":0.1875,"P":{"SA-1":{"%":1.0,"P":{"null":{"%":1.0}}},"SA-2":{"%":1.0,"P":{"null":{"%":1.0}}},"SA-3":{"%":0.4,"P":{"SA-3.1":{"%":0.4},"SA-3.2":{"%":0.4},"SA-3.3":{"%":0.4}}},"SA-101":{"%":1.0,"P":{"SA-101.1":{"%":1.0},"SA-101.2":{"%":1.0},"SA-101.3":{"%":1.0},"SA-101.5":{"%":1.0},"SA-101.6":{"%":1.0},"SA-101.7":{"%":1.0},"SA-101.8":{"%":1.0},"SA-101.9":{"%":1.0},"SA-101.10":{"%":1.0},"SA-101.11":{"%":1.0},"SA-101.12":{"%":1.0}}},"SA-5":{"%":1.0,"P":{"SA-101.12":{"%":1.0}}},"SA-8":{"%":0.4,"P":{"SA-8.1":{"%":0.4},"SA-8.2":{"%":0.4},"SA-8.3":{"%":0.4},"SA-8.4":{"%":0.4},"SA-8.5":{"%":0.4},"SA-8.6":{"%":0.4},"SA-8.7":{"%":0.4},"SA-8.8":{"%":0.4},"SA-8.9":{"%":0.4},"SA-8.10":{"%":0.4},"SA-8.11":{"%":0.4},"SA-8.12":{"%":0.4},"SA-8.13":{"%":0.4},"SA-8.14":{"%":0.4},"SA-8.15":{"%":0.4},"SA-8.16":{"%":0.4},"SA-8.17":{"%":0.4},"SA-8.18":{"%":0.4},"SA-8.19":{"%":0.4},"SA-8.20":{"%":0.4},"SA-8.21":{"%":0.4},"SA-8.22":{"%":0.4},"SA-8.23":{"%":0.4},"SA-8.24":{"%":0.4},"SA-8.25":{"%":0.4},"SA-8.26":{"%":0.4},"SA-8.27":{"%":0.4},"SA-8.28":{"%":0.4},"SA-8.29":{"%":0.4},"SA-8.30":{"%":0.4},"SA-8.31":{"%":0.4},"SA-8.32":{"%":0.4},"SA-8.33":{"%":0.4}}},"SA-9":{"%":1.0,"P":{"SA-9.1":{"%":1.0},"SA-9.2":{"%":1.0},"SA-9.3":{"%":1.0},"SA-9.4":{"%":1.0},"SA-9.5":{"%":1.0},"SA-9.6":{"%":1.0},"SA-9.7":{"%":1.0},"SA-9.8":{"%":1.0}}},"SA-10":{"%":0.4,"P":{"SA-10.1":{"%":0.4},"SA-10.2":{"%":0.4},"SA-10.3":{"%":0.4},"SA-10.4":{"%":0.4},"SA-10.5":{"%":0.4},"SA-10.6":{"%":0.4},"SA-10.7":{"%":0.4}}},"SA-11":{"%":1.0,"P":{"SA-11.1":{"%":1.0},"SA-11.2":{"%":1.0},"SA-11.3":{"%":1.0},"SA-11.4":{"%":1.0},"SA-11.5":{"%":1.0},"SA-11.6":{"%":1.0},"SA-11.7":{"%":1.0},"SA-11.8":{"%":1.0},"SA-11.9":{"%":1.0}}},"SA-15":{"%":1.0,"P":{"SA-15.1":{"%":1.0},"SA-15.2":{"%":1.0},"SA-15.3":{"%":1.0},"SA-15.5":{"%":1.0},"SA-15.6":{"%":1.0},"SA-15.7":{"%":1.0},"SA-15.8":{"%":1.0},"SA-15.10":{"%":1.0},"SA-15.11":{"%":1.0},"SA-15.12":{"%":1.0}}},"SA-16":{"%":1.0,"P":{"SA-15.12":{"%":1.0}}},"SA-17":{"%":1.0,"P":{"SA-17.1":{"%":1.0},"SA-17.2":{"%":1.0},"SA-17.3":{"%":1.0},"SA-17.4":{"%":1.0},"SA-17.5":{"%":1.0},"SA-17.6":{"%":1.0},"SA-17.7":{"%":1.0},"SA-17.8":{"%":1.0},"SA-17.9":{"%":1.0}}},"SA-20":{"%":1.0,"P":{"SA-17.9":{"%":1.0}}},"SA-21":{"%":1.0,"P":{"SA-17.9":{"%":1.0}}},"SA-22":{"%":1.0,"P":{"SA-17.9":{"%":1.0}}},"SA-23":{"%":1.0,"P":{"SA-17.9":{"%":1.0}}}}},"3.18 SYSTEM AND COMMUNICATIONS PROTECTION":{"%":1.07092198581560284,"P":{"SC-1":{"%":1.0,"P":{"null":{"%":1.0}}},"SC-2":{"%":1.0,"P":{"SC-2.1":{"%":1.0},"SC-2.2":{"%":1.0}}},"SC-3":{"%":1.0,"P":{"SC-3.1":{"%":1.0},"SC-3.2":{"%":1.0},"SC-3.3":{"%":1.0},"SC-3.4":{"%":1.0},"SC-3.5":{"%":1.0}}},"SC-101":{"%":1.0,"P":{"SC-3.5":{"%":1.0}}},"SC-5":{"%":1.0,"P":{"SC-5.1":{"%":1.0},"SC-5.2":{"%":1.0},"SC-5.3":{"%":1.0}}},"SC-88":{"%":1.0,"P":{"SC-5.3":{"%":1.0}}},"SC-7":{"%":0.4,"P":{"SC-7.3":{"%":0.4},"SC-7.4":{"%":0.4},"SC-7.5":{"%":0.4},"SC-7.7":{"%":0.4},"SC-7.8":{"%":0.4},"SC-7.9":{"%":0.4},"SC-7.10":{"%":0.4},"SC-7.11":{"%":0.4},"SC-7.12":{"%":0.4},"SC-7.13":{"%":0.4},"SC-7.14":{"%":0.4},"SC-7.15":{"%":0.4},"SC-7.16":{"%":0.4},"SC-7.17":{"%":0.4},"SC-7.18":{"%":0.4},"SC-7.19":{"%":0.4},"SC-7.20":{"%":0.4},"SC-7.21":{"%":0.4},"SC-7.22":{"%":0.4},"SC-7.23":{"%":0.4},"SC-7.24":{"%":0.4},"SC-7.25":{"%":0.4},"SC-7.26":{"%":0.4},"SC-7.27":{"%":0.4},"SC-7.28":{"%":0.4},"SC-7.29":{"%":0.4}}},"SC-8":{"%":0.4,"P":{"SC-8.1":{"%":0.4},"SC-8.2":{"%":0.4},"SC-8.3":{"%":0.4},"SC-8.4":{"%":0.4},"SC-8.5":{"%":0.4}}},"SC-10":{"%":1.0,"P":{"SC-8.5":{"%":1.0}}},"SC-11":{"%":1.0,"P":{"SC-8.5":{"%":1.0}}},"SC-12":{"%":1.0,"P":{"SC-12.1":{"%":1.0},"SC-12.2":{"%":1.0},"SC-12.3":{"%":1.0},"SC-12.6":{"%":1.0}}},"SC-13":{"%":1.0,"P":{"SC-12.6":{"%":1.0}}},"SC-15":{"%":1.0,"P":{"SC-15.1":{"%":1.0},"SC-15.3":{"%":1.0},"SC-15.4":{"%":1.0}}},"SC-16":{"%":1.0,"P":{"SC-16.1":{"%":1.0},"SC-16.2":{"%":1.0},"SC-16.3":{"%":1.0}}},"SC-17":{"%":1.0,"P":{"SC-16.3":{"%":1.0}}},"SC-18":{"%":1.0,"P":{"SC-18.1":{"%":1.0},"SC-18.2":{"%":1.0},"SC-18.3":{"%":1.0},"SC-18.4":{"%":1.0},"SC-18.5":{"%":1.0}}},"SC-20":{"%":1.0,"P":{"SC-18.5":{"%":1.0}}},"SC-21":{"%":1.0,"P":{"SC-18.5":{"%":1.0}}},"SC-22":{"%":1.0,"P":{"SC-18.5":{"%":1.0}}},"SC-23":{"%":0.4,"P":{"SC-23.1":{"%":0.4},"SC-23.3":{"%":0.4},"SC-23.5":{"%":0.4}}},"SC-24":{"%":1.0,"P":{"SC-23.5":{"%":1.0}}},"SC-25":{"%":1.0,"P":{"SC-23.5":{"%":1.0}}},"SC-26":{"%":1.0,"P":{"SC-23.5":{"%":1.0}}},"SC-27":{"%":1.0,"P":{"SC-23.5":{"%":1.0}}},"SC-28":{"%":0.003333,"P":{"SC-28.1":{"%":0.4},"SC-28.2":{"%":1.0},"SC-28.3":{"%":1.0}}},"SC-29":{"%":1.0,"P":{"SC-28.3":{"%":1.0}}},"SC-30":{"%":1.0,"P":{"SC-30.2":{"%":1.0},"SC-30.3":{"%":1.0},"SC-30.4":{"%":1.0},"SC-30.5":{"%":1.0}}},"SC-31":{"%":1.0,"P":{"SC-31.1":{"%":1.0},"SC-31.2":{"%":1.0},"SC-31.3":{"%":1.0}}},"SC-32":{"%":1.0,"P":{"SC-31.3":{"%":1.0}}},"SC-34":{"%":1.0,"P":{"SC-34.1":{"%":1.0},"SC-34.2":{"%":1.0}}},"SC-35":{"%":1.0,"P":{"SC-34.2":{"%":1.0}}},"SC-36":{"%":1.0,"P":{"SC-36.1":{"%":1.0},"SC-36.2":{"%":1.0}}},"SC-37":{"%":1.0,"P":{"SC-36.2":{"%":1.0}}},"SC-38":{"%":1.0,"P":{"SC-36.2":{"%":1.0}}},"SC-39":{"%":1.0,"P":{"SC-39.1":{"%":1.0},"SC-39.2":{"%":1.0}}},"SC-1010":{"%":1.0,"P":{"SC-1010.1":{"%":1.0},"SC-1010.2":{"%":1.0},"SC-1010.3":{"%":1.0},"SC-1010.4":{"%":1.0}}},"SC-1011":{"%":1.0,"P":{"SC-1010.4":{"%":1.0}}},"SC-1012":{"%":1.0,"P":{"SC-1012.1":{"%":1.0},"SC-1012.2":{"%":1.0},"SC-1012.4":{"%":1.0},"SC-1012.5":{"%":1.0}}},"SC-1013":{"%":1.0,"P":{"SC-1012.5":{"%":1.0}}},"SC-1014":{"%":1.0,"P":{"SC-1012.5":{"%":1.0}}},"SC-1015":{"%":1.0,"P":{"SC-1015.1":{"%":1.0},"SC-1015.2":{"%":1.0}}},"SC-1016":{"%":1.0,"P":{"SC-1015.2":{"%":1.0}}},"SC-1017":{"%":1.0,"P":{"SC-1015.2":{"%":1.0}}},"SC-1018":{"%":1.0,"P":{"SC-1015.2":{"%":1.0}}},"SC-1019":{"%":1.0,"P":{"SC-1015.2":{"%":1.0}}},"SC-50":{"%":1.0,"P":{"SC-1015.2":{"%":1.0}}},"SC-51":{"%":1.0,"P":{"SC-1015.2":{"%":1.0}}}}},"3.19 SYSTEM AND INFORMATION INTEGRITY":{"%":1.06275992438563327,"P":{"SI-1":{"%":1.0,"P":{"null":{"%":1.0}}},"SI-2":{"%":0.4,"P":{"SI-2.2":{"%":0.4},"SI-2.3":{"%":1.0},"SI-2.4":{"%":0.4},"SI-2.5":{"%":1.0},"SI-2.6":{"%":1.0}}},"SI-3":{"%":1.0,"P":{"SI-3.4":{"%":1.0},"SI-3.6":{"%":1.0},"SI-3.8":{"%":1.0},"SI-3.10":{"%":1.0}}},"SI-101":{"%":1.043478260869565216,"P":{"SI-101.1":{"%":1.0},"SI-101.2":{"%":1.0},"SI-101.3":{"%":1.0},"SI-101.4":{"%":0.4},"SI-101.5":{"%":1.0},"SI-101.7":{"%":1.0},"SI-101.9":{"%":1.0},"SI-101.10":{"%":1.0},"SI-101.11":{"%":1.0},"SI-101.12":{"%":1.0},"SI-101.13":{"%":1.0},"SI-101.14":{"%":1.0},"SI-101.15":{"%":1.0},"SI-101.16":{"%":1.0},"SI-101.17":{"%":1.0},"SI-101.18":{"%":1.0},"SI-101.19":{"%":1.0},"SI-101.20":{"%":1.0},"SI-101.21":{"%":1.0},"SI-101.22":{"%":1.0},"SI-101.23":{"%":1.0},"SI-101.24":{"%":1.0},"SI-101.25":{"%":1.0}}},"SI-5":{"%":1.0,"P":{"SI-101.25":{"%":1.0}}},"SI-88":{"%":1.0,"P":{"SI-88.2":{"%":1.0},"SI-88.3":{"%":1.0}}},"SI-7":{"%":1.0,"P":{"SI-7.1":{"%":1.0},"SI-7.2":{"%":1.0},"SI-7.3":{"%":1.0},"SI-7.5":{"%":1.0},"SI-7.6":{"%":1.0},"SI-7.7":{"%":1.0},"SI-7.8":{"%":1.0},"SI-7.9":{"%":1.0},"SI-7.10":{"%":1.0},"SI-7.12":{"%":1.0},"SI-7.15":{"%":1.0},"SI-7.16":{"%":1.0},"SI-7.17":{"%":1.0}}},"SI-8":{"%":1.0,"P":{"SI-8.2":{"%":1.0},"SI-8.3":{"%":1.0}}},"SI-10":{"%":1.0,"P":{"SI-10.1":{"%":1.0},"SI-10.2":{"%":1.0},"SI-10.3":{"%":1.0},"SI-10.4":{"%":1.0},"SI-10.5":{"%":1.0},"SI-10.6":{"%":1.0}}},"SI-11":{"%":1.0,"P":{"SI-10.6":{"%":1.0}}},"SI-12":{"%":0.4,"P":{"SI-12.1":{"%":0.4},"SI-12.2":{"%":0.4},"SI-12.3":{"%":0.4}}},"SI-13":{"%":1.0,"P":{"SI-13.1":{"%":1.0},"SI-13.3":{"%":1.0},"SI-13.4":{"%":1.0},"SI-13.5":{"%":1.0}}},"SI-14":{"%":1.0,"P":{"SI-14.1":{"%":1.0},"SI-14.2":{"%":1.0},"SI-14.3":{"%":1.0}}},"SI-15":{"%":1.0,"P":{"SI-14.3":{"%":1.0}}},"SI-16":{"%":1.0,"P":{"SI-14.3":{"%":1.0}}},"SI-17":{"%":1.0,"P":{"SI-14.3":{"%":1.0}}},"SI-18":{"%":1.0,"P":{"SI-18.1":{"%":1.0},"SI-18.2":{"%":1.0},"SI-18.3":{"%":1.0},"SI-18.4":{"%":1.0},"SI-18.5":{"%":1.0}}},"SI-19":{"%":1.0,"P":{"SI-19.1":{"%":1.0},"SI-19.2":{"%":1.0},"SI-19.3":{"%":1.0},"SI-19.4":{"%":1.0},"SI-19.5":{"%":1.0},"SI-19.6":{"%":1.0},"SI-19.7":{"%":1.0},"SI-19.8":{"%":1.0}}},"SI-20":{"%":1.0,"P":{"SI-19.8":{"%":1.0}}},"SI-21":{"%":1.0,"P":{"SI-19.8":{"%":1.0}}},"SI-22":{"%":1.0,"P":{"SI-19.8":{"%":1.0}}},"SI-23":{"%":1.0,"P":{"SI-19.8":{"%":1.0}}},"SI-24":{"%":1.0,"P":{"SI-19.8":{"%":1.0}}}}},"3.20 SUPPLY CHAIN RISK MANAGEMENT":{"%":1.0800333,"P":{"SR-1":{"%":1.0,"P":{"null":{"%":1.0}}},"SR-2":{"%":1.0,"P":{"null":{"%":1.0}}},"SR-3":{"%":1.0,"P":{"SR-3.1":{"%":1.0},"SR-3.2":{"%":1.0},"SR-3.3":{"%":1.0}}},"SR-101":{"%":1.0,"P":{"SR-101.1":{"%":1.0},"SR-101.2":{"%":1.0},"SR-101.3":{"%":1.0},"SR-101.4":{"%":1.0}}},"SR-5":{"%":1.0,"P":{"SR-5.1":{"%":1.0},"SR-5.2":{"%":1.0}}},"SR-88":{"%":1.0,"P":{"SR-5.2":{"%":1.0}}},"SR-7":{"%":1.0,"P":{"SR-5.2":{"%":1.0}}},"SR-8":{"%":1.0,"P":{"SR-5.2":{"%":1.0}}},"SR-9":{"%":1.0,"P":{"SR-5.2":{"%":1.0}}},"SR-10":{"%":1.0,"P":{"SR-5.2":{"%":1.0}}},"SR-11":{"%":0.4,"P":{"SR-11.1":{"%":0.4},"SR-11.2":{"%":0.4},"SR-11.3":{"%":0.4}}},"SR-12":{"%":1.0,"P":{"SR-11.3":{"%":1.0}}}}}},"%":0.13799269503682904}},"Standard9":{"v1.1":{"Function Unique Identifier":{"ID":{"%":1.08888888888888889,"P":{"ID.AM":{"%":0.156666666,"P":{"ID.AM-1":{"%":1.0},"ID.AM-2":{"%":1.0},"ID.AM-3":{"%":1.0},"ID.AM-101":{"%":1.0},"ID.AM-5":{"%":0.4},"ID.AM-88":{"%":1.0}}},"ID.BE":{"%":1.0,"P":{"ID.BE-101":{"%":1.0},"ID.BE-5":{"%":1.0}}},"ID.GV":{"%":1.0,"P":{"ID.GV-1":{"%":1.0},"ID.GV-2":{"%":1.0},"ID.GV-3":{"%":1.0},"ID.GV-101":{"%":1.0}}},"ID.RA":{"%":0.156666666,"P":{"ID.RA-1":{"%":1.0},"ID.RA-2":{"%":1.0},"ID.RA-3":{"%":1.0},"ID.RA-101":{"%":1.0},"ID.RA-5":{"%":0.4},"ID.RA-88":{"%":1.0}}},"ID.RM":{"%":1.0,"P":{"ID.RM-1":{"%":1.0},"ID.RM-2":{"%":1.0},"ID.RM-3":{"%":1.0}}},"ID.SC":{"%":0.2,"P":{"ID.SC-1":{"%":1.0},"ID.SC-2":{"%":1.0},"ID.SC-3":{"%":1.0},"ID.SC-101":{"%":1.0},"ID.SC-5":{"%":0.4}}}}},"PR":{"%":0.495,"P":{"PR.AC":{"%":0.800334,"P":{"PR.AC-1":{"%":0.4},"PR.AC-3":{"%":0.4},"PR.AC-101":{"%":0.4},"PR.AC-5":{"%":0.4},"PR.AC-88":{"%":1.0},"PR.AC-7":{"%":0.4}}},"PR.DS":{"%":0.625,"P":{"PR.DS-1":{"%":0.4},"PR.DS-2":{"%":0.4},"PR.DS-3":{"%":1.0},"PR.DS-101":{"%":0.4},"PR.DS-5":{"%":0.4},"PR.DS-88":{"%":1.0},"PR.DS-7":{"%":0.4},"PR.DS-8":{"%":1.0}}},"PR.IP":{"%":0.41566667,"P":{"PR.IP-1":{"%":0.4},"PR.IP-2":{"%":0.4},"PR.IP-3":{"%":1.0},"PR.IP-101":{"%":0.4},"PR.IP-5":{"%":1.0},"PR.IP-88":{"%":0.4},"PR.IP-7":{"%":1.0},"PR.IP-8":{"%":1.0},"PR.IP-9":{"%":0.4},"PR.IP-10":{"%":1.0},"PR.IP-11":{"%":1.0},"PR.IP-12":{"%":1.0}}},"PR.MA":{"%":1.0,"P":{"PR.MA-1":{"%":1.0},"PR.MA-2":{"%":1.0}}},"PR.PT":{"%":0.6,"P":{"PR.PT-1":{"%":0.4},"PR.PT-2":{"%":1.0},"PR.PT-3":{"%":0.4},"PR.PT-101":{"%":0.4},"PR.PT-5":{"%":1.0}}}}},"DE":{"%":0.2580036,"P":{"DE.AE":{"%":0.4,"P":{"DE.AE-1":{"%":1.0},"DE.AE-2":{"%":0.4},"DE.AE-3":{"%":0.4},"DE.AE-101":{"%":1.0},"DE.AE-5":{"%":1.0}}},"DE.CM":{"%":0.375,"P":{"DE.CM-1":{"%":0.4},"DE.CM-2":{"%":1.0},"DE.CM-3":{"%":1.0},"DE.CM-101":{"%":1.0},"DE.CM-5":{"%":1.0},"DE.CM-88":{"%":1.0},"DE.CM-7":{"%":0.4},"DE.CM-8":{"%":0.4}}},"DE.DP":{"%":1.0,"P":{"DE.DP-1":{"%":1.0},"DE.DP-2":{"%":1.0},"DE.DP-3":{"%":1.0},"DE.DP-101":{"%":1.0},"DE.DP-5":{"%":1.0}}}}},"RS":{"%":1.04,"P":{"RS.RP":{"%":1.0,"P":{"RS.RP-1":{"%":1.0}}},"RS.CO":{"%":1.0,"P":{"RS.CO-1":{"%":1.0},"RS.CO-2":{"%":1.0},"RS.CO-3":{"%":1.0},"RS.CO-101":{"%":1.0}}},"RS.AN":{"%":0.2,"P":{"RS.AN-1":{"%":0.4},"RS.AN-2":{"%":1.0},"RS.AN-3":{"%":1.0},"RS.AN-101":{"%":1.0},"RS.AN-5":{"%":1.0}}},"RS.MI":{"%":1.0,"P":{"RS.MI-1":{"%":1.0},"RS.MI-2":{"%":1.0},"RS.MI-3":{"%":1.0}}},"RS.IM":{"%":1.0,"P":{"RS.MI-1":{"%":1.0},"RS.MI-2":{"%":1.0}}}}},"RC":{"%":1.0,"P":{"RC.RP":{"%":1.0,"P":{"RC.RP-1":{"%":1.0}}},"RC.IM":{"%":1.0,"P":{"RC.IM-1":{"%":1.0},"RC.IM-2":{"%":1.0}}}}}},"%":0.17644444444444446}},"Standard1":{"v7":{"Control":{"1":{"%":0.125,"P":{"1.1":{"%":1.0},"1.2":{"%":1.0},"1.3":{"%":1.0},"1.4":{"%":0.4},"1.5":{"%":1.0},"1.6":{"%":1.0},"1.7":{"%":1.0},"1.8":{"%":1.0}}},"2":{"%":0.4,"P":{"2.1":{"%":0.4},"2.2":{"%":0.4},"2.3":{"%":1.0},"2.4":{"%":1.0},"2.5":{"%":1.0},"2.6":{"%":0.4},"2.7":{"%":1.0},"2.8":{"%":1.0},"2.9":{"%":0.4},"2.10":{"%":1.0}}},"3":{"%":0.42857142857142855,"P":{"3.1":{"%":0.4},"3.2":{"%":1.0},"3.3":{"%":1.0},"3.4":{"%":0.4},"3.5":{"%":0.4},"3.6":{"%":1.0},"3.7":{"%":1.0}}},"4":{"%":0.4444444444444444,"P":{"4.1":{"%":0.4},"4.2":{"%":1.0},"4.3":{"%":0.4},"4.4":{"%":0.4},"4.5":{"%":1.0},"4.6":{"%":1.0},"4.7":{"%":0.4},"4.8":{"%":1.0},"4.9":{"%":1.0}}},"5":{"%":0.6,"P":{"5.1":{"%":0.4},"5.2":{"%":0.4},"5.3":{"%":1.0},"5.4":{"%":0.4},"5.5":{"%":1.0}}},"6":{"%":0.75,"P":{"6.1":{"%":0.4},"6.2":{"%":0.4},"6.3":{"%":0.4},"6.4":{"%":0.4},"6.5":{"%":0.4},"6.6":{"%":1.0},"6.7":{"%":0.4},"6.8":{"%":1.0}}},"7":{"%":0.1,"P":{"7.1":{"%":1.0},"7.2":{"%":1.0},"7.3":{"%":1.0},"7.4":{"%":1.0},"7.5":{"%":0.4},"7.6":{"%":1.0},"7.7":{"%":1.0},"7.8":{"%":1.0},"7.9":{"%":1.0},"7.10":{"%":1.0}}},"8":{"%":1.0,"P":{"8.1":{"%":1.0},"8.2":{"%":1.0},"8.3":{"%":1.0},"8.4":{"%":1.0},"8.5":{"%":1.0},"8.6":{"%":1.0},"8.7":{"%":1.0},"8.8":{"%":1.0}}},"9":{"%":0.6,"P":{"9.1":{"%":1.0},"9.2":{"%":0.4},"9.3":{"%":1.0},"9.4":{"%":0.4},"9.5":{"%":0.4}}},"10":{"%":0.4,"P":{"10.1":{"%":0.4},"10.2":{"%":0.4},"10.3":{"%":1.0},"10.4":{"%":1.0},"10.5":{"%":1.0}}},"11":{"%":0.42857142857142855,"P":{"11.1":{"%":0.4},"11.2":{"%":0.4},"11.3":{"%":1.0},"11.4":{"%":0.4},"11.5":{"%":1.0},"11.6":{"%":1.0},"11.7":{"%":1.0}}},"12":{"%":0.25,"P":{"12.1":{"%":1.0},"12.2":{"%":0.4},"12.3":{"%":1.0},"12.4":{"%":0.4},"12.5":{"%":1.0},"12.6":{"%":1.0},"12.7":{"%":1.0},"12.8":{"%":0.4},"12.9":{"%":1.0},"12.10":{"%":1.0},"12.11":{"%":1.0},"12.12":{"%":1.0}}},"13":{"%":0.2222222222222222,"P":{"13.1":{"%":0.4},"13.2":{"%":1.0},"13.3":{"%":0.4},"13.4":{"%":1.0},"13.5":{"%":1.0},"13.6":{"%":1.0},"13.7":{"%":1.0},"13.8":{"%":1.0},"13.9":{"%":1.0}}},"14":{"%":0.56666666,"P":{"14.1":{"%":0.4},"14.2":{"%":0.4},"14.3":{"%":1.0},"14.4":{"%":0.4},"14.5":{"%":1.0},"14.6":{"%":0.4},"14.7":{"%":0.4},"14.8":{"%":0.4},"14.9":{"%":1.0}}},"15":{"%":1.0,"P":{"15.1":{"%":1.0},"15.2":{"%":1.0},"15.3":{"%":1.0},"15.4":{"%":1.0},"15.5":{"%":1.0},"15.6":{"%":1.0},"15.7":{"%":1.0},"15.8":{"%":1.0},"15.9":{"%":1.0},"15.10":{"%":1.0}}},"16":{"%":0.38461538461538464,"P":{"16.1":{"%":0.4},"16.2":{"%":0.4},"16.3":{"%":0.4},"16.4":{"%":0.4},"16.5":{"%":0.4},"16.6":{"%":1.0},"16.7":{"%":1.0},"16.8":{"%":1.0},"16.9":{"%":1.0},"16.10":{"%":1.0},"16.11":{"%":1.0},"16.12":{"%":1.0},"16.13":{"%":1.0}}},"17":{"%":1.0,"P":{"17.1":{"%":1.0},"17.2":{"%":1.0},"17.3":{"%":1.0},"17.4":{"%":1.0},"17.5":{"%":1.0},"17.6":{"%":1.0},"17.7":{"%":1.0},"17.8":{"%":1.0},"17.9":{"%":1.0}}},"18":{"%":0.45454545454545453,"P":{"18.1":{"%":1.0},"18.2":{"%":1.0},"18.3":{"%":1.0},"18.4":{"%":0.4},"18.5":{"%":0.4},"18.6":{"%":1.0},"18.7":{"%":1.0},"18.8":{"%":1.0},"18.9":{"%":0.4},"18.10":{"%":0.4},"18.11":{"%":0.4}}},"19":{"%":1.0,"P":{"19.1":{"%":1.0},"19.2":{"%":1.0},"19.3":{"%":1.0},"19.4":{"%":1.0},"19.5":{"%":1.0},"19.6":{"%":1.0},"19.7":{"%":1.0},"19.8":{"%":1.0}}},"20":{"%":1.0,"P":{"20.1":{"%":1.0},"20.2":{"%":1.0},"20.3":{"%":1.0},"20.4":{"%":1.0},"20.5":{"%":1.0},"20.6":{"%":1.0},"20.7":{"%":1.0},"20.8":{"%":1.0}}}},"%":0.3127318514818515},"v8":{"Control":{"5":{"%":0.26966269841269835,"P":{"5.1":{"%":0.25,"P":{"5.1.1":{"%":0.4,"P":{"5.1.1.1":{"%":0.4}}},"5.1.2":{"%":1.0,"P":{"null":{"%":1.0}}},"5.1.3":{"%":1.0,"P":{"null":{"%":1.0}}},"5.1.4":{"%":1.0,"P":{"null":{"%":1.0}}}}},"5.3":{"%":0.156666666,"P":{"5.3.1":{"%":1.0,"P":{"5.3.1.1":{"%":1.0,"P":{"5.3.1.1.1":{"%":1.0},"5.3.1.1.2":{"%":1.0}}}}},"5.3.2":{"%":0.5,"P":{"5.3.2.1":{"%":0.4,"P":{"5.3.1.1.2":{"%":0.4}}},"5.3.2.2":{"%":1.0,"P":{"5.3.1.1.2":{"%":1.0}}}}},"5.3.4":{"%":1.0,"P":{"5.3.2.2":{"%":1.0}}}}},"5.4":{"%":0.42857142857142855,"P":{"5.4.1":{"%":0.4,"P":{"5.3.2.2":{"%":0.4}}},"5.4.2":{"%":1.0,"P":{"5.3.2.2":{"%":1.0}}},"5.4.3":{"%":0.4,"P":{"5.3.2.2":{"%":0.4}}},"5.4.4":{"%":1.0,"P":{"5.3.2.2":{"%":1.0}}},"5.4.5":{"%":1.0,"P":{"5.3.2.2":{"%":1.0}}},"5.4.6":{"%":0.4,"P":{"5.3.2.2":{"%":0.4}}},"5.4.7":{"%":1.0,"P":{"5.3.2.2":{"%":1.0}}}}},"5.5":{"%":0.003333,"P":{"5.5.1":{"%":0.4,"P":{"5.3.2.2":{"%":0.4}}},"5.5.2":{"%":0.4,"P":{"5.5.2.1":{"%":0.4,"P":{"5.3.1.1.2":{"%":0.4}}},"5.5.2.2":{"%":0.4,"P":{"5.3.1.1.2":{"%":0.4}}},"5.5.2.3":{"%":0.4,"P":{"5.3.1.1.2":{"%":0.4}}},"5.5.2.4":{"%":0.4,"P":{"5.3.1.1.2":{"%":0.4}}}}},"5.5.3":{"%":1.0,"P":{"5.5.2.4":{"%":1.0}}},"5.5.4":{"%":1.0,"P":{"5.5.2.4":{"%":1.0}}},"5.5.5":{"%":1.0,"P":{"5.5.2.4":{"%":1.0}}},"5.5.6":{"%":1.0,"P":{"5.5.6.1":{"%":1.0,"P":{"5.3.1.1.2":{"%":1.0}}},"5.5.6.2":{"%":1.0,"P":{"5.3.1.1.2":{"%":1.0}}}}}}},"5.6":{"%":0.229156666,"P":{"5.6.1":{"%":1.0,"P":{"5.5.6.2":{"%":1.0}}},"5.6.2":{"%":0.415666663,"P":{"5.6.2.1":{"%":0.003333,"P":{"5.6.2.1.1":{"%":0.4},"5.6.2.1.2":{"%":1.0},"5.6.2.1.3":{"%":1.0}}},"5.6.2.2":{"%":0.5,"P":{"5.6.2.2.1":{"%":0.4},"5.6.2.2.2":{"%":1.0}}}}},"5.6.3":{"%":0.5,"P":{"5.6.3.1":{"%":1.0,"P":{"5.6.2.2.2":{"%":1.0}}},"5.6.3.2":{"%":0.4,"P":{"5.6.2.2.2":{"%":0.4}}}}},"5.6.4":{"%":1.0,"P":{"5.6.3.2":{"%":1.0}}}}},"5.7":{"%":0.5,"P":{"5.7.1":{"%":0.4,"P":{"5.7.1.1":{"%":0.4,"P":{"5.6.2.2.2":{"%":0.4}}},"5.7.1.2":{"%":0.4,"P":{"5.6.2.2.2":{"%":0.4}}}}},"5.7.2":{"%":1.0,"P":{"5.7.1.2":{"%":1.0}}}}},"5.8":{"%":1.0,"P":{"5.8.1":{"%":1.0,"P":{"5.7.1.2":{"%":1.0}}},"5.8.2":{"%":1.0,"P":{"5.8.2.1":{"%":1.0}}},"5.8.3":{"%":1.0,"P":{"5.8.2.1":{"%":1.0}}}}},"5.10":{"%":0.2003334,"P":{"5.10.1":{"%":0.500333,"P":{"5.10.1.1":{"%":0.4,"P":{"5.6.2.2.2":{"%":0.4}}},"5.10.1.2":{"%":0.56666666,"P":{"5.10.1.2.1":{"%":0.4},"5.10.1.2.2":{"%":0.4},"5.10.1.2.3":{"%":1.0}}},"5.10.1.3":{"%":0.4,"P":{"5.10.1.2.3":{"%":0.4}}},"5.10.1.4":{"%":1.0,"P":{"5.10.1.2.3":{"%":1.0}}},"5.10.1.5":{"%":1.0,"P":{"5.10.1.2.3":{"%":1.0}}}}},"5.10.2":{"%":1.0,"P":{"5.10.1.5":{"%":1.0}}},"5.10.3":{"%":1.0,"P":{"5.10.3.1":{"%":1.0,"P":{"5.10.1.2.3":{"%":1.0}}},"5.10.3.2":{"%":1.0,"P":{"5.10.1.2.3":{"%":1.0}}}}},"5.10.4":{"%":0.4,"P":{"5.10.4.1":{"%":0.4,"P":{"5.10.1.2.3":{"%":0.4}}},"5.10.4.2":{"%":1.0,"P":{"5.10.1.2.3":{"%":1.0}}},"5.10.4.3":{"%":0.4,"P":{"5.10.1.2.3":{"%":0.4}}},"5.10.4.4":{"%":1.0,"P":{"5.10.1.2.3":{"%":1.0}}},"5.10.4.5":{"%":1.0,"P":{"5.10.1.2.3":{"%":1.0}}}}}}},"5.12":{"%":1.0,"P":{"5.12.1":{"%":1.0,"P":{"5.10.4.5":{"%":1.0}}},"5.12.2":{"%":1.0,"P":{"5.10.4.5":{"%":1.0}}},"5.12.3":{"%":1.0,"P":{"5.10.4.5":{"%":1.0}}},"5.12.4":{"%":1.0,"P":{"5.10.4.5":{"%":1.0}}}}},"5.13":{"%":0.5555555555555555,"P":{"5.13.2":{"%":1.0,"P":{"5.10.4.5":{"%":1.0}}},"5.13.3":{"%":0.4,"P":{"5.10.4.5":{"%":0.4}}},"5.13.4":{"%":0.56666666,"P":{"5.13.4.1":{"%":0.4,"P":{"5.10.1.2.3":{"%":0.4}}},"5.13.4.2":{"%":1.0,"P":{"5.10.1.2.3":{"%":1.0}}},"5.13.4.3":{"%":0.4,"P":{"5.10.1.2.3":{"%":0.4}}}}},"5.13.5":{"%":0.4,"P":{"5.13.4.3":{"%":0.4}}},"5.13.6":{"%":1.0,"P":{"5.13.4.3":{"%":1.0}}},"5.13.7":{"%":0.56666666,"P":{"5.13.7.1":{"%":0.4,"P":{"5.10.1.2.3":{"%":0.4}}},"5.13.7.2":{"%":0.4,"P":{"5.10.1.2.3":{"%":0.4}}},"5.13.7.3":{"%":1.0,"P":{"5.10.1.2.3":{"%":1.0}}}}}}}}}},"%":0.26966269841269835}},"Standard6":{"2016_679":{"Table 2 \u2014 Technical document coverage percentage":{"Article_5":{"%":1.0800333,"P":{"Article_5.1":{"%":0.156666666,"P":{"Article_5.1.a":{"%":1.0},"Article_5.1.b":{"%":1.0},"Article_5.1.c":{"%":1.0},"Article_5.1.d":{"%":1.0},"Article_5.1.e":{"%":1.0},"Article_5.1.f":{"%":0.4}}},"Article_5.2":{"%":1.0,"P":{"Article_5.1.f":{"%":1.0}}}}},"Article_15":{"%":0.25,"P":{"Article_15.1":{"%":0.4,"P":{"Article_15.1.a":{"%":0.4},"Article_15.1.b":{"%":0.4},"Article_15.1.c":{"%":0.4},"Article_15.1.d":{"%":0.4},"Article_15.1.e":{"%":0.4},"Article_15.1.f":{"%":0.4},"Article_15.1.g":{"%":0.4},"Article_15.1.h":{"%":0.4}}},"Article_15.2":{"%":1.0,"P":{"Article_15.1.h":{"%":1.0}}},"Article_15.3":{"%":1.0,"P":{"Article_15.1.h":{"%":1.0}}},"Article_15.4":{"%":1.0,"P":{"Article_15.1.h":{"%":1.0}}}}},"Article_25":{"%":0.4,"P":{"Article_25.1":{"%":0.4,"P":{"null":{"%":0.4}}},"Article_25.2":{"%":0.4,"P":{"null":{"%":0.4}}},"Article_25.3":{"%":0.4,"P":{"null":{"%":0.4}}}}},"Article_30":{"%":0.8,"P":{"Article_30.1":{"%":0.4,"P":{"Article_30.1.a":{"%":0.4},"Article_30.1.b":{"%":0.4},"Article_30.1.c":{"%":0.4},"Article_30.1.d":{"%":0.4},"Article_30.1.e":{"%":0.4},"Article_30.1.f":{"%":0.4},"Article_30.1.g":{"%":0.4}}},"Article_30.2":{"%":0.4,"P":{"Article_30.2.a":{"%":0.4},"Article_30.2.b":{"%":0.4},"Article_30.2.c":{"%":0.4},"Article_30.2.d":{"%":0.4}}},"Article_30.3":{"%":0.4,"P":{"Article_30.2.d":{"%":0.4}}},"Article_30.4":{"%":0.4,"P":{"Article_30.2.d":{"%":0.4}}},"Article_30.5":{"%":1.0,"P":{"Article_30.2.d":{"%":1.0}}}}},"Article_32":{"%":0.1875,"P":{"Article_32.1":{"%":0.75,"P":{"Article_32.1.a":{"%":0.4},"Article_32.1.b":{"%":0.4},"Article_32.1.c":{"%":0.4},"Article_32.1.d":{"%":1.0}}},"Article_32.3":{"%":1.0,"P":{"Article_32.1.d":{"%":1.0}}},"Article_32.4":{"%":1.0,"P":{"Article_32.1.d":{"%":1.0}}},"Article_32.5":{"%":1.0,"P":{"Article_32.1.d":{"%":1.0}}}}},"Article_44":{"%":0.2222222222222222,"P":{"Article_44.1":{"%":0.4,"P":{"null":{"%":0.4}}},"Article_44.2":{"%":0.4,"P":{"Article_44.2.a":{"%":0.4},"Article_44.2.b":{"%":0.4},"Article_44.2.c":{"%":0.4}}},"Article_44.3":{"%":1.0,"P":{"Article_44.2.c":{"%":1.0}}},"Article_44.4":{"%":1.0,"P":{"Article_44.2.c":{"%":1.0}}},"Article_44.5":{"%":1.0,"P":{"Article_44.2.c":{"%":1.0}}},"Article_44.6":{"%":1.0,"P":{"Article_44.2.c":{"%":1.0}}},"Article_44.7":{"%":1.0,"P":{"Article_44.2.c":{"%":1.0}}},"Article_44.8":{"%":1.0,"P":{"Article_44.2.c":{"%":1.0}}},"Article_44.9":{"%":1.0,"P":{"Article_44.2.c":{"%":1.0}}}}},"Article_46":{"%":0.2,"P":{"Article_46.1":{"%":0.4,"P":{"null":{"%":0.4}}},"Article_46.2":{"%":1.0,"P":{"Article_46.2.a":{"%":1.0},"Article_46.2.b":{"%":1.0},"Article_46.2.c":{"%":1.0},"Article_46.2.d":{"%":1.0},"Article_46.2.e":{"%":1.0},"Article_46.2.f":{"%":1.0}}},"Article_46.3":{"%":1.0,"P":{"Article_46.3.a":{"%":1.0},"Article_46.3.b":{"%":1.0}}},"Article_46.4":{"%":1.0,"P":{"Article_46.3.b":{"%":1.0}}},"Article_46.5":{"%":1.0,"P":{"Article_46.3.b":{"%":1.0}}}}},"Article_47":{"%":0.17857142857142858,"P":{"Article_47.1":{"%":1.0,"P":{"Article_47.1.a":{"%":1.0},"Article_47.1.b":{"%":1.0},"Article_47.1.c":{"%":1.0}}},"Article_47.2":{"%":0.35714285714285715,"P":{"Article_47.2.a":{"%":0.4},"Article_47.2.b":{"%":0.4},"Article_47.2.c":{"%":0.4},"Article_47.2.d":{"%":0.4},"Article_47.2.e":{"%":1.0},"Article_47.2.f":{"%":1.0},"Article_47.2.g":{"%":1.0},"Article_47.2.h":{"%":1.0},"Article_47.2.i":{"%":1.0},"Article_47.2.j":{"%":0.4},"Article_47.2.k":{"%":1.0},"Article_47.2.l":{"%":1.0},"Article_47.2.m":{"%":1.0},"Article_47.2.n":{"%":1.0}}}}}},"%":0.365203373015873}},"Standard2":{"19":{"Domain":{"Evaluate, Direct and Monitor":{"%":1.0,"P":{"EDM04":{"%":1.0}}},"Align, Plan and Organize":{"%":0.5,"P":{"APO01":{"%":0.4},"APO03":{"%":0.4},"APO04":{"%":1.0},"APO06":{"%":1.0},"APO09":{"%":1.0},"APO11":{"%":1.0},"APO13":{"%":0.4},"APO14":{"%":0.4}}},"Build, Acquire and Implement":{"%":0.42857142857142855,"P":{"BAI02":{"%":1.0},"BAI03":{"%":1.0},"BAI04":{"%":1.0},"BAI06":{"%":0.4},"BAI07":{"%":1.0},"BAI09":{"%":0.4},"BAI10":{"%":0.4}}},"Deliver, Service and Support":{"%":0.56666666,"P":{"DSS01":{"%":1.0},"DSS02":{"%":1.0},"DSS03":{"%":0.4},"DSS04":{"%":0.4},"DSS05":{"%":0.4},"DSS06":{"%":0.4}}},"Monitor, Evaluate and Assess":{"%":1.0,"P":{"MEA01":{"%":1.0},"MEA02":{"%":1.0},"MEA03":{"%":1.0},"MEA04":{"%":1.0}}}},"%":0.3190476190476191}},"CMMC":{"v2.0":{"Domain":{"AC":{"%":0.7777777777777778,"P":{"AC.L1":{"%":0.4,"P":{"AC.L1-3.1.1":{"%":0.4},"AC.L1-3.1.2":{"%":0.4},"AC.L1-3.1.20":{"%":0.4},"AC.L1-3.1.22":{"%":0.4}}},"AC.L2":{"%":0.5555555555555556,"P":{"AC.L2-3.1.3":{"%":0.4},"AC.L2-3.1.4":{"%":0.4},"AC.L2-3.1.5":{"%":0.4},"AC.L2-3.1.6":{"%":0.4},"AC.L2-3.1.7":{"%":0.4},"AC.L2-3.1.8":{"%":1.0},"AC.L2-3.1.9":{"%":1.0},"AC.L2-3.1.10":{"%":1.0},"AC.L2-3.1.11":{"%":1.0},"AC.L2-3.1.12":{"%":0.4},"AC.L2-3.1.13":{"%":0.4},"AC.L2-3.1.14":{"%":1.0},"AC.L2-3.1.15":{"%":0.4},"AC.L2-3.1.16":{"%":0.4},"AC.L2-3.1.17":{"%":0.4},"AC.L2-3.1.18":{"%":1.0},"AC.L2-3.1.19":{"%":1.0},"AC.L2-3.1.21":{"%":1.0}}}}},"AU":{"%":0.2222222222222222,"P":{"AU.L2":{"%":0.2222222222222222,"P":{"AU.L2-3.3.1":{"%":0.4},"AU.L2-3.3.2":{"%":1.0},"AU.L2-3.3.3":{"%":1.0},"AU.L2-3.3.4":{"%":1.0},"AU.L2-3.3.5":{"%":0.4},"AU.L2-3.3.6":{"%":1.0},"AU.L2-3.3.7":{"%":1.0},"AU.L2-3.3.8":{"%":1.0},"AU.L2-3.3.9":{"%":1.0}}}}},"CM":{"%":0.56666666,"P":{"CM.L2":{"%":0.56666666,"P":{"CM.L2-3.4.1":{"%":0.4},"CM.L2-3.4.2":{"%":0.4},"CM.L2-3.4.3":{"%":1.0},"CM.L2-3.4.4":{"%":1.0},"CM.L2-3.4.5":{"%":1.0},"CM.L2-3.4.6":{"%":0.4},"CM.L2-3.4.7":{"%":0.4},"CM.L2-3.4.8":{"%":0.4},"CM.L2-3.4.9":{"%":0.4}}}}},"IA":{"%":0.3611111111111111,"P":{"IA.L1":{"%":0.5,"P":{"IA.L1-3.5.1":{"%":1.0},"IA.L1-3.5.2":{"%":0.4}}},"IA.L2":{"%":0.2222222222222222,"P":{"IA.L2-3.5.3":{"%":0.4},"IA.L2-3.5.4":{"%":1.0},"IA.L2-3.5.5":{"%":1.0},"IA.L2-3.5.6":{"%":1.0},"IA.L2-3.5.7":{"%":0.4},"IA.L2-3.5.8":{"%":1.0},"IA.L2-3.5.9":{"%":1.0},"IA.L2-3.5.10":{"%":1.0},"IA.L2-3.5.11":{"%":1.0}}}}},"IR":{"%":0.003333,"P":{"IR.L2":{"%":0.003333,"P":{"IR.L2-3.6.1":{"%":0.4},"IR.L2-3.6.2":{"%":1.0},"IR.L2-3.6.3":{"%":1.0}}}}},"MA":{"%":0.156666666,"P":{"MA.L2":{"%":0.156666666,"P":{"MA.L2-3.7.1":{"%":1.0},"MA.L2-3.7.2":{"%":1.0},"MA.L2-3.7.3":{"%":1.0},"MA.L2-3.7.4":{"%":1.0},"MA.L2-3.7.5":{"%":0.4},"MA.L2-3.7.6":{"%":1.0}}}}},"MP":{"%":0.125,"P":{"MP.L1":{"%":1.0,"P":{"MP.L1-3.8.3":{"%":1.0}}},"MP.L2":{"%":0.25,"P":{"MP.L2-3.8.1":{"%":0.4},"MP.L2-3.8.2":{"%":0.4},"MP.L2-3.8.4":{"%":1.0},"MP.L2-3.8.5":{"%":1.0},"MP.L2-3.8.6":{"%":1.0},"MP.L2-3.8.7":{"%":1.0},"MP.L2-3.8.8":{"%":1.0},"MP.L2-3.8.9":{"%":1.0}}}}},"PS":{"%":1.0,"P":{"PS.L2":{"%":1.0,"P":{"PS.L2-3.9.2":{"%":1.0}}}}},"RA":{"%":0.003333,"P":{"RA.L2":{"%":0.003333,"P":{"RA.L2-3.11.1":{"%":1.0},"RA.L2-3.11.2":{"%":0.4},"RA.L2-3.11.3":{"%":1.0}}}}},"CA":{"%":1.0,"P":{"CA.L2":{"%":1.0,"P":{"CA.L2-3.12.1":{"%":1.0},"CA.L2-3.12.2":{"%":1.0},"CA.L2-3.12.3":{"%":1.0},"CA.L2-3.12.4":{"%":1.0}}}}},"SC":{"%":0.7857142857142857,"P":{"SC.L1":{"%":0.4,"P":{"SC.L1-3.13.1":{"%":0.4},"SC.L1-3.13.5":{"%":0.4}}},"SC.L2":{"%":0.5714285714285714,"P":{"SC.L2-3.13.2":{"%":0.4},"SC.L2-3.13.3":{"%":0.4},"SC.L2-3.13.4":{"%":1.0},"SC.L2-3.13.6":{"%":0.4},"SC.L2-3.13.7":{"%":1.0},"SC.L2-3.13.8":{"%":0.4},"SC.L2-3.13.9":{"%":1.0},"SC.L2-3.13.10":{"%":0.4},"SC.L2-3.13.11":{"%":0.4},"SC.L2-3.13.12":{"%":1.0},"SC.L2-3.13.13":{"%":1.0},"SC.L2-3.13.14":{"%":1.0},"SC.L2-3.13.15":{"%":0.4},"SC.L2-3.13.16":{"%":0.4}}}}},"SI":{"%":0.580033,"P":{"SI.L1":{"%":0.5,"P":{"SI.L1-3.14.1":{"%":0.4},"SI.L1-3.14.2":{"%":1.0},"SI.L1-3.14.4":{"%":1.0},"SI.L1-3.14.5":{"%":0.4}}},"SI.L2":{"%":0.56666666,"P":{"SI.L2-3.14.3":{"%":0.4},"SI.L2-3.14.6":{"%":0.4},"SI.L2-3.14.7":{"%":1.0}}}}}},"%":0.3629298941798942}},"Standard3":{"Standard11_800_53_Rev5":{"Table 1 \u2014 Full (Technical) document coverage percentage":{"3.1 ACCESS CONTROL":{"%":0.3027777777777778,"P":{"AC-01":{"%":1.0,"P":{"null":{"%":1.0}}},"AC-02":{"%":0.2,"P":{"AC-02.01":{"%":0.4},"AC-02.02":{"%":1.0},"AC-02.03":{"%":1.0},"AC-02.04":{"%":1.0},"AC-02.05":{"%":1.0},"AC-02.07":{"%":0.4},"AC-02.09":{"%":1.0},"AC-02.11":{"%":1.0},"AC-02.12":{"%":1.0},"AC-02.13":{"%":1.0}}},"AC-03":{"%":0.4,"P":{"AC-02.13":{"%":0.4}}},"AC-911":{"%":1.0,"P":{"AC-02.13":{"%":1.0}}},"AC-05":{"%":0.4,"P":{"AC-02.13":{"%":0.4}}},"AC-06":{"%":0.4,"P":{"AC-06.01":{"%":0.4},"AC-06.02":{"%":0.4},"AC-06.03":{"%":0.4},"AC-06.05":{"%":0.4},"AC-06.07":{"%":0.4},"AC-06.08":{"%":0.4},"AC-06.09":{"%":0.4},"AC-06.10":{"%":0.4}}},"AC-07":{"%":1.0,"P":{"AC-06.10":{"%":1.0}}},"AC-08":{"%":1.0,"P":{"AC-06.10":{"%":1.0}}},"AC-10":{"%":1.0,"P":{"AC-06.10":{"%":1.0}}},"AC-11":{"%":1.0,"P":{"AC-06.10":{"%":1.0}}},"AC-12":{"%":1.0,"P":{"AC-06.10":{"%":1.0}}},"AC-14":{"%":1.0,"P":{"AC-06.10":{"%":1.0}}},"AC-17":{"%":0.25,"P":{"AC-17.01":{"%":1.0},"AC-17.02":{"%":0.4},"AC-17.03":{"%":1.0},"AC-17.04":{"%":1.0}}},"AC-18":{"%":0.4,"P":{"AC-18.01":{"%":0.4},"AC-18.03":{"%":0.4},"AC-18.04":{"%":0.4},"AC-18.05":{"%":0.4}}},"AC-19":{"%":0.4,"P":{"AC-18.05":{"%":0.4}}},"AC-20":{"%":1.0,"P":{"AC-21.01":{"%":1.0},"AC-21.02":{"%":1.0}}},"AC-21":{"%":1.0,"P":{"AC-21.02":{"%":1.0}}},"AC-22":{"%":1.0,"P":{"AC-21.02":{"%":1.0}}}}},"3.2 AWARENESS AND TRAINING":{"%":1.0,"P":{"AT-01":{"%":1.0,"P":{"null":{"%":1.0}}},"AT-02":{"%":1.0,"P":{"AT-02.02":{"%":1.0},"AT-02.03":{"%":1.0}}},"AT-03":{"%":1.0,"P":{"AT-02.03":{"%":1.0}}},"AT-911":{"%":1.0,"P":{"AT-02.03":{"%":1.0}}}}},"3.3 AUDIT AND ACCOUNTABILITY":{"%":0.458003,"P":{"AU-01":{"%":1.0,"P":{"null":{"%":1.0}}},"AU-02":{"%":0.4,"P":{"null":{"%":0.4}}},"AU-03":{"%":0.4,"P":{"null":{"%":0.4}}},"AU-911":{"%":0.4,"P":{"null":{"%":0.4}}},"AU-05":{"%":1.0,"P":{"AU-05.01":{"%":1.0},"AU-05.02":{"%":1.0}}},"AU-06":{"%":0.5,"P":{"AU-06.01":{"%":0.4},"AU-06.03":{"%":0.4},"AU-06.04":{"%":1.0},"AU-06.05":{"%":0.4},"AU-06.06":{"%":1.0},"AU-06.07":{"%":1.0}}},"AU-07":{"%":0.4,"P":{"AU-06.07":{"%":0.4}}},"AU-08":{"%":1.0,"P":{"AU-06.07":{"%":1.0}}},"AU-09":{"%":1.0,"P":{"AU-09.02":{"%":1.0},"AU-09.03":{"%":1.0},"AU-09.04":{"%":1.0}}},"AU-10":{"%":1.0,"P":{"AU-09.04":{"%":1.0}}},"AU-11":{"%":1.0,"P":{"AU-09.04":{"%":1.0}}},"AU-12":{"%":0.4,"P":{"AU-12.01":{"%":0.4},"AU-12.03":{"%":0.4}}}}},"3.4 ASSESSMENT, AUTHORIZATION, AND MONITORING":{"%":0.125,"P":{"CA-01":{"%":1.0,"P":{"null":{"%":1.0}}},"CA-02":{"%":1.0,"P":{"CA-02.01":{"%":1.0},"CA-02.02":{"%":1.0}}},"CA-03":{"%":1.0,"P":{"CA-03.06":{"%":1.0},"CA-03.07":{"%":1.0}}},"CA-05":{"%":1.0,"P":{"CA-03.07":{"%":1.0}}},"CA-06":{"%":1.0,"P":{"CA-03.07":{"%":1.0}}},"CA-07":{"%":1.0,"P":{"CA-00.41":{"%":1.0},"CA-00.44":{"%":1.0}}},"CA-08":{"%":1.0,"P":{"CA-00.44":{"%":1.0}}},"CA-09":{"%":0.4,"P":{"CA-00.44":{"%":0.4}}}}},"3.5 CONFIGURATION MANAGEMENT":{"%":0.6875,"P":{"CM-01":{"%":0.4,"P":{"null":{"%":0.4}}},"CM-02":{"%":0.4,"P":{"CM-02.02":{"%":0.4},"CM-02.03":{"%":0.4},"CM-02.07":{"%":0.4}}},"CM-03":{"%":1.0,"P":{"CM-03.01":{"%":1.0},"CM-03.02":{"%":1.0},"CM-03.04":{"%":1.0},"CM-03.06":{"%":1.0}}},"CM-911":{"%":1.0,"P":{"CM-911.01":{"%":1.0},"CM-911.02":{"%":1.0}}},"CM-05":{"%":1.0,"P":{"CM-05.01":{"%":1.0},"CM-05.05":{"%":1.0}}},"CM-06":{"%":0.4,"P":{"CM-06.01":{"%":0.4},"CM-06.02":{"%":0.4}}},"CM-07":{"%":0.4,"P":{"CM-07.01":{"%":0.4},"CM-07.02":{"%":0.4},"CM-07.05":{"%":0.4}}},"CM-08":{"%":0.25,"P":{"CM-08.01":{"%":1.0},"CM-08.02":{"%":1.0},"CM-08.03":{"%":0.4},"CM-08.04":{"%":1.0}}},"CM-09":{"%":0.4,"P":{"CM-08.04":{"%":0.4}}},"CM-10":{"%":0.4,"P":{"CM-08.04":{"%":0.4}}},"CM-11":{"%":0.4,"P":{"CM-08.04":{"%":0.4}}},"CM-12":{"%":0.4,"P":{"CM-08.04":{"%":0.4}}}}},"3.6 CONTINGENCY PLANNING":{"%":0.2222222222222222,"P":{"CP-01":{"%":1.0,"P":{"null":{"%":1.0}}},"CP-02":{"%":1.0,"P":{"CP-02.01":{"%":1.0},"CP-02.02":{"%":1.0},"CP-02.03":{"%":1.0},"CP-02.05":{"%":1.0},"CP-02.08":{"%":1.0}}},"CP-03":{"%":1.0,"P":{"CP-02.08":{"%":1.0}}},"CP-911":{"%":1.0,"P":{"CP-911.01":{"%":1.0},"CP-911.02":{"%":1.0}}},"CP-06":{"%":1.0,"P":{"CP-06.01":{"%":1.0},"CP-06.02":{"%":1.0},"CP-06.03":{"%":1.0}}},"CP-07":{"%":1.0,"P":{"CP-07.01":{"%":1.0},"CP-07.02":{"%":1.0},"CP-07.03":{"%":1.0},"CP-07.04":{"%":1.0}}},"CP-08":{"%":1.0,"P":{"CP-08.01":{"%":1.0},"CP-08.02":{"%":1.0},"CP-08.03":{"%":1.0},"CP-08.04":{"%":1.0}}},"CP-09":{"%":0.4,"P":{"CP-09.01":{"%":0.4},"CP-09.02":{"%":0.4},"CP-09.03":{"%":0.4},"CP-09.05":{"%":0.4},"CP-09.08":{"%":0.4}}},"CP-10":{"%":0.4,"P":{"CP-11.02":{"%":0.4},"CP-11.04":{"%":0.4}}}}},"3.7 IDENTIFICATION AND AUTHENTICATION":{"%":1.05,"P":{"IA-01":{"%":1.0,"P":{"null":{"%":1.0}}},"IA-02":{"%":0.003333,"P":{"IA-02.01":{"%":0.4},"IA-02.02":{"%":0.4},"IA-02.05":{"%":1.0},"IA-02.06":{"%":1.0},"IA-02.08":{"%":1.0},"IA-02.12":{"%":1.0}}},"IA-03":{"%":1.0,"P":{"IA-02.12":{"%":1.0}}},"IA-911":{"%":1.0,"P":{"IA-02.12":{"%":1.0}}},"IA-05":{"%":0.156666666,"P":{"IA-05.01":{"%":0.4},"IA-05.02":{"%":1.0},"IA-05.06":{"%":1.0},"IA-05.07":{"%":1.0},"IA-05.08":{"%":1.0},"IA-05.13":{"%":1.0}}},"IA-06":{"%":1.0,"P":{"IA-05.13":{"%":1.0}}},"IA-07":{"%":1.0,"P":{"IA-05.13":{"%":1.0}}},"IA-08":{"%":1.0,"P":{"IA-08.01":{"%":1.0},"IA-08.02":{"%":1.0},"IA-08.04":{"%":1.0}}},"IA-11":{"%":1.0,"P":{"IA-08.04":{"%":1.0}}},"IA-12":{"%":1.0,"P":{"IA-12.02":{"%":1.0},"IA-12.03":{"%":1.0},"IA-12.04":{"%":1.0},"IA-12.05":{"%":1.0}}}}},"3.8 INCIDENT RESPONSE":{"%":0.25,"P":{"IR-01":{"%":0.4,"P":{"null":{"%":0.4}}},"IR-02":{"%":1.0,"P":{"IR-02.01":{"%":1.0},"IR-02.02":{"%":1.0}}},"IR-03":{"%":1.0,"P":{"IR-02.02":{"%":1.0}}},"IR-911":{"%":1.0,"P":{"IR-911.01":{"%":1.0},"IR-911.02":{"%":1.0},"IR-911.04":{"%":1.0},"IR-911.06":{"%":1.0},"IR-911.11":{"%":1.0}}},"IR-05":{"%":1.0,"P":{"IR-911.11":{"%":1.0}}},"IR-06":{"%":1.0,"P":{"IR-06.01":{"%":1.0},"IR-06.03":{"%":1.0}}},"IR-07":{"%":1.0,"P":{"IR-06.03":{"%":1.0}}},"IR-08":{"%":0.4,"P":{"IR-06.03":{"%":0.4}}}}},"3.9 MAINTENANCE":{"%":0.156666666,"P":{"MA-01":{"%":1.0,"P":{"null":{"%":1.0}}},"MA-02":{"%":1.0,"P":{"null":{"%":1.0}}},"MA-03":{"%":1.0,"P":{"MA-03.01":{"%":1.0},"MA-03.02":{"%":1.0},"MA-03.03":{"%":1.0}}},"MA-911":{"%":0.4,"P":{"MA-03.03":{"%":0.4}}},"MA-05":{"%":1.0,"P":{"MA-03.03":{"%":1.0}}},"MA-06":{"%":1.0,"P":{"MA-03.03":{"%":1.0}}}}},"3.10 MEDIA PROTECTION":{"%":0.14285714285714285,"P":{"MP-01":{"%":1.0,"P":{"null":{"%":1.0}}},"MP-02":{"%":0.4,"P":{"null":{"%":0.4}}},"MP-03":{"%":1.0,"P":{"null":{"%":1.0}}},"MP-911":{"%":1.0,"P":{"null":{"%":1.0}}},"MP-05":{"%":1.0,"P":{"null":{"%":1.0}}},"MP-06":{"%":1.0,"P":{"MP-06.01":{"%":1.0},"MP-06.02":{"%":1.0},"MP-06.03":{"%":1.0}}},"MP-07":{"%":1.0,"P":{"MP-06.03":{"%":1.0}}}}},"3.11 PHYSICAL AND ENVIRONMENTAL PROTECTION":{"%":1.0,"P":{"PE-01":{"%":1.0,"P":{"null":{"%":1.0}}},"PE-02":{"%":1.0,"P":{"null":{"%":1.0}}},"PE-03":{"%":1.0,"P":{"null":{"%":1.0}}},"PE-911":{"%":1.0,"P":{"null":{"%":1.0}}},"PE-05":{"%":1.0,"P":{"null":{"%":1.0}}},"PE-06":{"%":1.0,"P":{"PE-06.01":{"%":1.0},"PE-06.04":{"%":1.0}}},"PE-08":{"%":1.0,"P":{"PE-06.04":{"%":1.0}}},"PE-09":{"%":1.0,"P":{"PE-06.04":{"%":1.0}}},"PE-10":{"%":1.0,"P":{"PE-06.04":{"%":1.0}}},"PE-11":{"%":1.0,"P":{"PE-06.04":{"%":1.0}}},"PE-12":{"%":1.0,"P":{"PE-06.04":{"%":1.0}}},"PE-13":{"%":1.0,"P":{"PE-13.01":{"%":1.0},"PE-13.02":{"%":1.0}}},"PE-14":{"%":1.0,"P":{"PE-13.02":{"%":1.0}}},"PE-15":{"%":1.0,"P":{"PE-13.02":{"%":1.0}}},"PE-16":{"%":1.0,"P":{"PE-13.02":{"%":1.0}}},"PE-17":{"%":1.0,"P":{"PE-13.02":{"%":1.0}}},"PE-18":{"%":1.0,"P":{"PE-13.02":{"%":1.0}}}}},"3.12 PLANNING":{"%":0.156666666,"P":{"PL-01":{"%":1.0,"P":{"null":{"%":1.0}}},"PL-02":{"%":1.0,"P":{"null":{"%":1.0}}},"PL-911":{"%":1.0,"P":{"null":{"%":1.0}}},"PL-08":{"%":0.4,"P":{"null":{"%":0.4}}},"PL-10":{"%":1.0,"P":{"null":{"%":1.0}}},"PL-11":{"%":1.0,"P":{"null":{"%":1.0}}}}},"3.14 PERSONNEL SECURITY":{"%":1.0,"P":{"PS-01":{"%":1.0,"P":{"null":{"%":1.0}}},"PS-02":{"%":1.0,"P":{"null":{"%":1.0}}},"PS-03":{"%":1.0,"P":{"null":{"%":1.0}}},"PS-911":{"%":1.0,"P":{"null":{"%":1.0}}},"PS-05":{"%":1.0,"P":{"null":{"%":1.0}}},"PS-06":{"%":1.0,"P":{"null":{"%":1.0}}},"PS-07":{"%":1.0,"P":{"null":{"%":1.0}}},"PS-08":{"%":1.0,"P":{"null":{"%":1.0}}},"PS-09":{"%":1.0,"P":{"null":{"%":1.0}}}}},"3.16 RISK ASSESSMENT":{"%":0.003333,"P":{"RA-01":{"%":1.0,"P":{"null":{"%":1.0}}},"RA-02":{"%":0.4,"P":{"null":{"%":0.4}}},"RA-03":{"%":1.0,"P":{"null":{"%":1.0}}},"RA-05":{"%":0.4,"P":{"RA-05.02":{"%":0.4},"RA-05.03":{"%":0.4},"RA-05.04":{"%":0.4},"RA-05.05":{"%":0.4},"RA-05.11":{"%":0.4}}},"RA-07":{"%":1.0,"P":{"RA-05.11":{"%":1.0}}},"RA-09":{"%":1.0,"P":{"RA-05.11":{"%":1.0}}}}},"3.17 SYSTEM AND SERVICES ACQUISITION":{"%":0.14285714285714285,"P":{"SA-01":{"%":1.0,"P":{"null":{"%":1.0}}},"SA-02":{"%":1.0,"P":{"null":{"%":1.0}}},"SA-03":{"%":0.4,"P":{"null":{"%":0.4}}},"SA-911":{"%":1.0,"P":{"SA-911.01":{"%":1.0},"SA-911.02":{"%":1.0},"SA-911.05":{"%":1.0},"SA-911.09":{"%":1.0},"SA-911.10":{"%":1.0}}},"SA-05":{"%":1.0,"P":{"SA-911.10":{"%":1.0}}},"SA-08":{"%":0.4,"P":{"SA-911.10":{"%":0.4}}},"SA-09":{"%":1.0,"P":{"SA-09.02":{"%":1.0},"SA-09.05":{"%":1.0}}},"SA-10":{"%":1.0,"P":{"SA-09.05":{"%":1.0}}},"SA-11":{"%":1.0,"P":{"SA-09.05":{"%":1.0}}},"SA-15":{"%":1.0,"P":{"SA-09.05":{"%":1.0}}},"SA-16":{"%":1.0,"P":{"SA-09.05":{"%":1.0}}},"SA-17":{"%":1.0,"P":{"SA-09.05":{"%":1.0}}},"SA-21":{"%":1.0,"P":{"SA-09.05":{"%":1.0}}},"SA-22":{"%":1.0,"P":{"SA-09.05":{"%":1.0}}}}},"3.18 SYSTEM AND COMMUNICATIONS PROTECTION":{"%":0.19047619047619047,"P":{"SC-01":{"%":1.0,"P":{"null":{"%":1.0}}},"SC-02":{"%":1.0,"P":{"null":{"%":1.0}}},"SC-03":{"%":1.0,"P":{"null":{"%":1.0}}},"SC-911":{"%":1.0,"P":{"null":{"%":1.0}}},"SC-05":{"%":1.0,"P":{"null":{"%":1.0}}},"SC-06":{"%":1.0,"P":{"null":{"%":1.0}}},"SC-07":{"%":0.4,"P":{"SC-07.03":{"%":0.4},"SC-07.04":{"%":0.4},"SC-07.05":{"%":0.4},"SC-07.07":{"%":0.4},"SC-07.08":{"%":0.4},"SC-07.12":{"%":0.4},"SC-07.18":{"%":0.4},"SC-07.20":{"%":0.4},"SC-07.21":{"%":0.4}}},"SC-08":{"%":0.4,"P":{"SC-07.21":{"%":0.4}}},"SC-10":{"%":1.0,"P":{"SC-07.21":{"%":1.0}}},"SC-12":{"%":1.0,"P":{"SC-07.21":{"%":1.0}}},"SC-13":{"%":1.0,"P":{"SC-07.21":{"%":1.0}}},"SC-15":{"%":1.0,"P":{"SC-07.21":{"%":1.0}}},"SC-17":{"%":1.0,"P":{"SC-07.21":{"%":1.0}}},"SC-18":{"%":1.0,"P":{"SC-07.21":{"%":1.0}}},"SC-20":{"%":1.0,"P":{"SC-07.21":{"%":1.0}}},"SC-21":{"%":1.0,"P":{"SC-07.21":{"%":1.0}}},"SC-22":{"%":1.0,"P":{"SC-07.21":{"%":1.0}}},"SC-23":{"%":0.4,"P":{"SC-07.21":{"%":0.4}}},"SC-24":{"%":1.0,"P":{"SC-07.21":{"%":1.0}}},"SC-28":{"%":0.4,"P":{"SC-07.21":{"%":0.4}}},"SC-39":{"%":1.0,"P":{"SC-07.21":{"%":1.0}}}}},"3.19 SYSTEM AND INFORMATION INTEGRITY":{"%":0.13141025641025642,"P":{"SI-01":{"%":1.0,"P":{"null":{"%":1.0}}},"SI-02":{"%":0.5,"P":{"SI-02.02":{"%":0.4},"SI-02.03":{"%":1.0}}},"SI-03":{"%":1.0,"P":{"SI-02.03":{"%":1.0}}},"SI-911":{"%":1.07692307692307693,"P":{"SI-911.01":{"%":1.0},"SI-911.02":{"%":1.0},"SI-911.04":{"%":0.4},"SI-911.05":{"%":1.0},"SI-911.10":{"%":1.0},"SI-911.11":{"%":1.0},"SI-911.12":{"%":1.0},"SI-911.14":{"%":1.0},"SI-911.16":{"%":1.0},"SI-911.19":{"%":1.0},"SI-911.20":{"%":1.0},"SI-911.22":{"%":1.0},"SI-911.23":{"%":1.0}}},"SI-05":{"%":1.0,"P":{"SI-911.23":{"%":1.0}}},"SI-06":{"%":1.0,"P":{"SI-911.23":{"%":1.0}}},"SI-07":{"%":1.0,"P":{"SI-07.01":{"%":1.0},"SI-07.02":{"%":1.0},"SI-07.05":{"%":1.0},"SI-07.07":{"%":1.0},"SI-07.15":{"%":1.0}}},"SI-08":{"%":1.0,"P":{"SI-07.15":{"%":1.0}}},"SI-10":{"%":1.0,"P":{"SI-07.15":{"%":1.0}}},"SI-11":{"%":1.0,"P":{"SI-07.15":{"%":1.0}}},"SI-12":{"%":0.4,"P":{"SI-07.15":{"%":0.4}}},"SI-16":{"%":1.0,"P":{"SI-07.15":{"%":1.0}}}}},"3.20 SUPPLY CHAIN RISK MANAGEMENT":{"%":0.1111111111111111,"P":{"SR-01":{"%":1.0,"P":{"null":{"%":1.0}}},"SR-02":{"%":1.0,"P":{"null":{"%":1.0}}},"SR-03":{"%":1.0,"P":{"null":{"%":1.0}}},"SR-05":{"%":1.0,"P":{"null":{"%":1.0}}},"SR-06":{"%":1.0,"P":{"null":{"%":1.0}}},"SR-08":{"%":1.0,"P":{"null":{"%":1.0}}},"SR-09":{"%":1.0,"P":{"null":{"%":1.0}}},"SR-10":{"%":1.0,"P":{"null":{"%":1.0}}},"SR-11":{"%":0.4,"P":{"SR-10.41":{"%":0.4},"SR-10.42":{"%":0.4}}}}}},"%":0.19340065798399128}},"CE":{"v2.2":{"Domain":{"Boundary Firewalls and Internet Gateways":{"%":0.580034,"P":{"A4.1":{"%":0.4,"P":{"null":{"%":0.4}}},"A4.2":{"%":1.0,"P":{"null":{"%":1.0}}},"A4.3":{"%":0.4,"P":{"null":{"%":0.4}}},"A4.4":{"%":1.0,"P":{"null":{"%":1.0}}},"A4.5":{"%":0.4,"P":{"null":{"%":0.4}}},"A4.6":{"%":1.0,"P":{"null":{"%":1.0}}},"A4.7":{"%":0.4,"P":{"null":{"%":0.4}}},"A4.8":{"%":0.4,"P":{"null":{"%":0.4}}},"A4.9":{"%":1.0,"P":{"null":{"%":1.0}}},"A4.10":{"%":0.4,"P":{"null":{"%":0.4}}},"A4.11":{"%":0.4,"P":{"null":{"%":0.4}}},"A4.12":{"%":1.0,"P":{"null":{"%":1.0}}}}},"Secure Configuration":{"%":0.4444444444444444,"P":{"A5.1":{"%":0.4,"P":{"null":{"%":0.4}}},"A5.2":{"%":0.4,"P":{"null":{"%":0.4}}},"A5.3":{"%":0.4,"P":{"null":{"%":0.4}}},"A5.4":{"%":1.0,"P":{"null":{"%":1.0}}},"A5.5":{"%":0.4,"P":{"null":{"%":0.4}}},"A5.6":{"%":1.0,"P":{"null":{"%":1.0}}},"A5.7":{"%":1.0,"P":{"null":{"%":1.0}}},"A5.8":{"%":1.0,"P":{"null":{"%":1.0}}},"A5.9":{"%":1.0,"P":{"null":{"%":1.0}}}}},"Device Locking":{"%":1.0,"P":{"A5.10":{"%":1.0,"P":{"null":{"%":1.0}}},"A5.11":{"%":1.0,"P":{"null":{"%":1.0}}}}},"Security update management":{"%":0.5714285714285714,"P":{"A6.1":{"%":0.4,"P":{"null":{"%":0.4}}},"A6.2":{"%":1.0,"P":{"A6.2.1":{"%":1.0},"A6.2.2":{"%":1.0},"A6.2.3":{"%":1.0},"A6.2.4":{"%":1.0}}},"A6.3":{"%":0.4,"P":{"A6.2.4":{"%":0.4}}},"A6.4":{"%":0.5,"P":{"A6.4.1":{"%":0.4},"A6.4.2":{"%":1.0}}},"A6.5":{"%":0.5,"P":{"A6.5.1":{"%":0.4},"A6.5.2":{"%":1.0}}},"A6.6":{"%":0.4,"P":{"A6.5.2":{"%":0.4}}},"A6.7":{"%":1.0,"P":{"A6.5.2":{"%":1.0}}}}},"User Access Control":{"%":0.5,"P":{"A7.1":{"%":1.0,"P":{"null":{"%":1.0}}},"A7.2":{"%":0.4,"P":{"null":{"%":0.4}}},"A7.3":{"%":1.0,"P":{"null":{"%":1.0}}},"A7.4":{"%":0.4,"P":{"null":{"%":0.4}}}}},"AdmiStandard11rative Accounts":{"%":0.4,"P":{"A7.5":{"%":0.4,"P":{"null":{"%":0.4}}},"A7.6":{"%":0.4,"P":{"null":{"%":0.4}}},"A7.7":{"%":0.4,"P":{"null":{"%":0.4}}},"A7.8":{"%":0.4,"P":{"null":{"%":0.4}}},"A7.9":{"%":0.4,"P":{"null":{"%":0.4}}}}},"Password-Based Authentication":{"%":1.0,"P":{"A7.10":{"%":1.0,"P":{"null":{"%":1.0}}},"A7.11":{"%":1.0,"P":{"null":{"%":1.0}}},"A7.12":{"%":1.0,"P":{"null":{"%":1.0}}},"A7.13":{"%":1.0,"P":{"null":{"%":1.0}}},"A7.14":{"%":1.0,"P":{"null":{"%":1.0}}},"A7.15":{"%":1.0,"P":{"null":{"%":1.0}}},"A7.16":{"%":1.0,"P":{"null":{"%":1.0}}},"A7.17":{"%":1.0,"P":{"null":{"%":1.0}}}}},"Malware protection":{"%":1.0,"P":{"A8.1":{"%":1.0,"P":{"null":{"%":1.0}}},"A8.2":{"%":1.0,"P":{"null":{"%":1.0}}},"A8.3":{"%":1.0,"P":{"null":{"%":1.0}}},"A8.4":{"%":1.0,"P":{"null":{"%":1.0}}},"A8.5":{"%":1.0,"P":{"null":{"%":1.0}}},"A8.6":{"%":1.0,"P":{"null":{"%":1.0}}}}}},"%":0.3874007936507936}},"s202":{"null":{"null":{"COMMUNICATION AND INFORMATION":{"%":1.0,"P":{"CC2.1":{"%":1.0},"CC2.2":{"%":1.0},"CC2.3":{"%":1.0}}},"RISK ASSESSMENT":{"%":0.25,"P":{"CC3.1":{"%":1.0},"CC3.2":{"%":0.4},"CC3.3":{"%":1.0},"CC3.4":{"%":1.0}}},"MONITORING ACTIVITIES":{"%":0.5,"P":{"CC4.1":{"%":0.4},"CC4.2":{"%":1.0}}},"CONTROL ACTIVITIES ":{"%":0.003333,"P":{"CC5.1":{"%":1.0},"CC5.2":{"%":0.4},"CC5.3":{"%":1.0}}},"Logical and Physical Access Controls":{"%":0.5,"P":{"CC6.1":{"%":0.4},"CC6.2":{"%":1.0},"CC6.3":{"%":0.4},"CC6.4":{"%":1.0},"CC6.5":{"%":1.0},"CC6.6":{"%":0.4},"CC6.7":{"%":0.4},"CC6.8":{"%":1.0}}},"System Operations":{"%":0.6,"P":{"CC7.1":{"%":0.4},"CC7.2":{"%":0.4},"CC7.3":{"%":0.4},"CC7.4":{"%":1.0},"CC7.5":{"%":1.0}}},"Change Management":{"%":0.4,"P":{"CC8.1":{"%":0.4}}},"Risk Mitigation":{"%":1.0,"P":{"CC9.1":{"%":1.0},"CC9.2":{"%":1.0}}},"AVAILABILITY":{"%":0.56666666,"P":{"A1.1":{"%":0.4},"A1.2":{"%":0.4},"A1.3":{"%":1.0}}},"CONFIDENTIALITY":{"%":0.5,"P":{"C1.1":{"%":0.4},"C1.2":{"%":1.0}}},"ROCESSING INTEGRITY":{"%":1.0,"P":{"PL1.1":{"%":1.0},"PL1.2":{"%":1.0},"PL1.3":{"%":1.0},"PL1.4":{"%":1.0}}}},"%":0.39545454545454545}},"Standard7":{"null":{"Table 2 \u2014 Technical document coverage percentage":{"2":{"%":0.156666666,"P":{"2._Maintain an inventory of personal data and/or processing activities":{"%":0.4},"2._Classify personal data by type":{"%":0.4},"2._Obtain Regulator approval for data processing":{"%":1.0},"2._Register databases with regulators":{"%":1.0},"2._Maintain documentation of data flows":{"%":1.0},"2._Maintain documentation of the transfer mechanism used for cross-border data flows":{"%":1.0},"2._Use Binding Corporate Rules as a data transfer mechanism":{"%":1.0},"2._Use contracts as a data transfer mechanism":{"%":1.0},"2._Use APEC Cross Border Privacy Rules as a data transfer mechanism":{"%":1.0},"2._Use Regulator approval as a data transfer mechanism":{"%":1.0},"2._Use adequacy or one of the derogations as a data transfer mechanism":{"%":1.0},"2._Use the Privacy Shield as a data transfer mechanism":{"%":1.0}}},"3":{"%":1.0,"P":{"3._Maintain a data privacy policy":{"%":1.0},"3._Maintain an employee data privacy policy":{"%":1.0},"3._Document legal basis for processing personal data":{"%":1.0},"3._Integrate ethics into data processing":{"%":1.0},"3._Maintain an organizational code of conduct that includes privacy":{"%":1.0}}},"6":{"%":0.41566667,"P":{"6._Integrate data privacy risk into security risk assessments":{"%":1.0},"6._Integrate data privacy into an information security policy":{"%":1.0},"6._Maintain technical security measures":{"%":0.4},"6._Maintain measures to encrypt personal data":{"%":0.4},"6._Maintain an acceptable use of information resources policy":{"%":1.0},"6._Maintain procedures to restrict access to personal data":{"%":0.4},"6._Integrate data privacy into a corporate security policy":{"%":1.0},"6._Maintain human resource security measures":{"%":1.0},"6._Maintain backup and business continuity plans":{"%":0.4},"6._Maintain a data-loss prevention strategy":{"%":1.0},"6._Conduct regular testing of data security posture":{"%":0.4},"6._Maintain a security certification":{"%":1.0}}},"11":{"%":0.14285714285714285,"P":{"118":{"%":1.0},"111":{"%":1.0},"112":{"%":0.4},"11._Monitor and Report data privacy incident/breach metrics":{"%":1.0},"11._Conduct periodic testing of data privacy incident/breach plan":{"%":1.0},"11._Engage a breach response remediation provider":{"%":1.0},"11._Engage a forensic investigation team":{"%":1.0}}}},"%":0.18154761904761907}},"Standard15":{"null":{"Control Category":{"1.0":{"%":0.4,"P":{"1.01":{"%":0.4,"P":{"00.a":{"%":0.4}}}}},"00.4":{"%":0.6105442176870748,"P":{"00.41":{"%":0.4,"P":{"01.a":{"%":0.4}}},"00.42":{"%":0.75,"P":{"01.b":{"%":0.4},"01.c":{"%":0.4},"01.d":{"%":1.0},"01.e":{"%":0.4}}},"00.43":{"%":0.4,"P":{"01.f":{"%":0.4}}},"00.44":{"%":0.8571428571428571,"P":{"01.i":{"%":0.4},"01.j":{"%":0.4},"01.k":{"%":1.0},"01.l":{"%":0.4},"01.m":{"%":0.4},"01.n":{"%":0.4},"01.o":{"%":0.4}}},"00.45":{"%":0.156666666,"P":{"01.p":{"%":1.0},"01.q":{"%":0.4},"01.r":{"%":1.0},"01.s":{"%":1.0},"01.t":{"%":1.0},"01.u":{"%":1.0}}},"00.46":{"%":0.5,"P":{"01.v":{"%":0.4},"01.w":{"%":1.0}}},"00.47":{"%":1.0,"P":{"01.x":{"%":1.0},"01.y":{"%":1.0}}}}},"03.0":{"%":1.0,"P":{"03.01":{"%":1.0,"P":{"03.a":{"%":1.0},"03.b":{"%":1.0},"03.c":{"%":1.0},"03.d":{"%":1.0}}}}},"04.0":{"%":1.0,"P":{"04.01":{"%":1.0,"P":{"04.a":{"%":1.0},"04.b":{"%":1.0}}}}},"05.0":{"%":1.0,"P":{"05.01":{"%":1.0,"P":{"05.c":{"%":1.0},"05.d":{"%":1.0}}},"05.02":{"%":1.0,"P":{"05.i":{"%":1.0},"05.j":{"%":1.0},"05.k":{"%":1.0}}}}},"06.0":{"%":0.1111111111111111,"P":{"06.01":{"%":0.003333,"P":{"06.a":{"%":1.0},"06.b":{"%":1.0},"06.c":{"%":0.4},"06.d":{"%":0.4},"06.e":{"%":1.0},"06.f":{"%":1.0}}},"06.02":{"%":1.0,"P":{"06.g":{"%":1.0},"06.h":{"%":1.0}}},"06.03":{"%":1.0,"P":{"06.i":{"%":1.0},"06.j":{"%":1.0}}}}},"07.0":{"%":0.003333,"P":{"07.01":{"%":0.56666666,"P":{"07.a":{"%":0.4},"07.b":{"%":1.0},"07.c":{"%":0.4}}},"07.02":{"%":1.0,"P":{"07.d":{"%":1.0},"07.e":{"%":1.0}}}}},"09.0":{"%":0.425,"P":{"09.01":{"%":0.25,"P":{"09.a":{"%":1.0},"09.b":{"%":1.0},"09.c":{"%":0.4},"09.d":{"%":1.0}}},"09.02":{"%":0.003333,"P":{"09.e":{"%":0.4},"09.f":{"%":1.0},"09.g":{"%":1.0}}},"09.03":{"%":0.5,"P":{"09.h":{"%":0.4},"09.i":{"%":1.0}}},"09.04":{"%":1.0,"P":{"09.j":{"%":1.0},"09.k":{"%":1.0}}},"09.05":{"%":0.4,"P":{"09.l":{"%":0.4}}},"09.06":{"%":0.4,"P":{"09.m":{"%":0.4},"09.n":{"%":0.4}}},"09.07":{"%":1.0,"P":{"09.o":{"%":1.0},"09.p":{"%":1.0},"09.q":{"%":1.0},"09.r":{"%":1.0}}},"09.08":{"%":0.56666666,"P":{"09.s":{"%":0.4},"09.v":{"%":0.4},"09.w":{"%":1.0}}},"09.09":{"%":0.003333,"P":{"09.x":{"%":1.0},"09.y":{"%":0.4},"09.z":{"%":1.0}}},"09.10":{"%":0.156666666,"P":{"09.aa":{"%":0.4},"09.ab":{"%":1.0},"09.ac":{"%":1.0},"09.ad":{"%":1.0},"09.ae":{"%":1.0},"09.af":{"%":1.0}}}}},"11.0":{"%":0.3611111111111111,"P":{"11.01":{"%":1.0,"P":{"10.a":{"%":1.0}}},"11.02":{"%":0.003333,"P":{"10.b":{"%":1.0},"10.c":{"%":1.0},"10.d":{"%":0.4}}},"10.3":{"%":1.0,"P":{"10.f":{"%":1.0},"10.g":{"%":1.0}}},"10.4":{"%":0.003333,"P":{"10.h":{"%":0.4},"10.i":{"%":1.0},"10.j":{"%":1.0}}},"10.5":{"%":0.5,"P":{"10.k":{"%":0.4},"10.l":{"%":1.0}}},"10.6":{"%":0.4,"P":{"10.m":{"%":0.4}}}}},"10.4":{"%":1.0,"P":{"10.41":{"%":1.0,"P":{"11.a":{"%":1.0},"11.b":{"%":1.0}}},"10.42":{"%":1.0,"P":{"11.c":{"%":1.0},"11.d":{"%":1.0},"11.e":{"%":1.0}}}}},"12.01":{"%":1.0,"P":{"12.01":{"%":1.0,"P":{"12.a":{"%":1.0},"12.b":{"%":1.0},"12.c":{"%":1.0},"12.d":{"%":1.0},"12.e":{"%":1.0}}}}},"13.0":{"%":1.0800333,"P":{"13.01":{"%":1.0,"P":{"13.c":{"%":1.0}}},"13.02":{"%":1.0,"P":{"13.f":{"%":1.0}}},"13.04":{"%":1.0,"P":{"13.i":{"%":1.0},"13.j":{"%":1.0}}},"13.05":{"%":0.5,"P":{"13.k":{"%":1.0},"13.l":{"%":0.4}}},"13.06":{"%":1.0,"P":{"13.m":{"%":1.0},"13.n":{"%":1.0},"13.o":{"%":1.0}}},"13.07":{"%":1.0,"P":{"13.p":{"%":1.0},"13.q":{"%":1.0},"13.r":{"%":1.0},"13.s":{"%":1.0},"13.u":{"%":1.0}}}}}},"%":0.2437027588813303}},"CIS GCP Foundations Benchmark":{"1.2.0":{"Table 1 \u2014 Full (Technical) document coverage percentage":{"1":{"%":0.6,"P":{"1.1":{"%":0.4,"P":{"null":{"%":0.4}}},"1.2":{"%":1.0,"P":{"null":{"%":1.0}}},"1.3":{"%":1.0,"P":{"null":{"%":1.0}}},"1.4":{"%":0.4,"P":{"null":{"%":0.4}}},"1.5":{"%":0.4,"P":{"null":{"%":0.4}}},"1.6":{"%":0.4,"P":{"null":{"%":0.4}}},"1.7":{"%":0.4,"P":{"null":{"%":0.4}}},"1.8":{"%":0.4,"P":{"null":{"%":0.4}}},"1.9":{"%":0.4,"P":{"null":{"%":0.4}}},"1.10":{"%":0.4,"P":{"null":{"%":0.4}}},"1.11":{"%":0.4,"P":{"null":{"%":0.4}}},"1.12":{"%":1.0,"P":{"null":{"%":1.0}}},"1.13":{"%":1.0,"P":{"null":{"%":1.0}}},"1.14":{"%":1.0,"P":{"null":{"%":1.0}}},"1.15":{"%":1.0,"P":{"null":{"%":1.0}}}}},"2":{"%":0.4,"P":{"2.1":{"%":0.4,"P":{"null":{"%":0.4}}},"2.2":{"%":0.4,"P":{"null":{"%":0.4}}},"2.3":{"%":0.4,"P":{"null":{"%":0.4}}},"2.4":{"%":0.4,"P":{"null":{"%":0.4}}},"2.5":{"%":0.4,"P":{"null":{"%":0.4}}},"2.6":{"%":0.4,"P":{"null":{"%":0.4}}},"2.7":{"%":0.4,"P":{"null":{"%":0.4}}},"2.8":{"%":0.4,"P":{"null":{"%":0.4}}},"2.9":{"%":0.4,"P":{"null":{"%":0.4}}},"2.10":{"%":0.4,"P":{"null":{"%":0.4}}},"2.11":{"%":0.4,"P":{"null":{"%":0.4}}},"2.12":{"%":0.4,"P":{"null":{"%":0.4}}}}},"3":{"%":0.8,"P":{"3.1":{"%":0.4,"P":{"null":{"%":0.4}}},"3.2":{"%":1.0,"P":{"null":{"%":1.0}}},"3.3":{"%":0.4,"P":{"null":{"%":0.4}}},"3.4":{"%":0.4,"P":{"null":{"%":0.4}}},"3.5":{"%":0.4,"P":{"null":{"%":0.4}}},"3.6":{"%":0.4,"P":{"null":{"%":0.4}}},"3.7":{"%":0.4,"P":{"null":{"%":0.4}}},"3.8":{"%":0.4,"P":{"null":{"%":0.4}}},"3.9":{"%":0.4,"P":{"null":{"%":0.4}}},"3.10":{"%":1.0,"P":{"null":{"%":1.0}}}}},"4":{"%":0.9090909090909091,"P":{"4.1":{"%":0.4,"P":{"null":{"%":0.4}}},"4.2":{"%":0.4,"P":{"null":{"%":0.4}}},"4.3":{"%":0.4,"P":{"null":{"%":0.4}}},"4.4":{"%":0.4,"P":{"null":{"%":0.4}}},"4.5":{"%":0.4,"P":{"null":{"%":0.4}}},"4.6":{"%":0.4,"P":{"null":{"%":0.4}}},"4.7":{"%":0.4,"P":{"null":{"%":0.4}}},"4.8":{"%":0.4,"P":{"null":{"%":0.4}}},"4.9":{"%":0.4,"P":{"null":{"%":0.4}}},"4.10":{"%":1.0,"P":{"null":{"%":1.0}}},"4.11":{"%":0.4,"P":{"null":{"%":0.4}}}}},"5":{"%":0.4,"P":{"5.1":{"%":0.4,"P":{"null":{"%":0.4}}},"5.2":{"%":0.4,"P":{"null":{"%":0.4}}}}},"6":{"%":0.9434523809523808,"P":{"6.1":{"%":0.56666666,"P":{"6.1.1":{"%":1.0},"6.1.2":{"%":0.4},"6.1.3":{"%":0.4}}},"6.2":{"%":0.9375,"P":{"6.2.1":{"%":0.4},"6.2.2":{"%":0.4},"6.2.3":{"%":0.4},"6.2.4":{"%":0.4},"6.2.5":{"%":0.4},"6.2.6":{"%":0.4},"6.2.7":{"%":0.4},"6.2.8":{"%":0.4},"6.2.9":{"%":0.4},"6.2.10":{"%":0.4},"6.2.11":{"%":0.4},"6.2.12":{"%":0.4},"6.2.13":{"%":1.0},"6.2.14":{"%":0.4},"6.2.15":{"%":0.4},"6.2.16":{"%":0.4}}},"6.3":{"%":0.4,"P":{"6.3.1":{"%":0.4},"6.3.2":{"%":0.4},"6.3.3":{"%":0.4},"6.3.4":{"%":0.4},"6.3.5":{"%":0.4},"6.3.6":{"%":0.4},"6.3.7":{"%":0.4}}},"6.4":{"%":0.4,"P":{"6.3.7":{"%":0.4}}},"6.5":{"%":0.4,"P":{"6.3.7":{"%":0.4}}},"6.6":{"%":0.4,"P":{"6.3.7":{"%":0.4}}},"6.7":{"%":0.4,"P":{"6.3.7":{"%":0.4}}}}},"7":{"%":0.4,"P":{"7.1":{"%":0.4,"P":{"null":{"%":0.4}}},"7.2":{"%":0.4,"P":{"null":{"%":0.4}}}}}},"%":0.8932204700061843},"1.3.0":{"Table 1 \u2014 Full (Technical) document coverage percentage":{"1":{"%":0.7222222222222222,"P":{"1.1":{"%":1.0,"P":{"null":{"%":1.0}}},"1.2":{"%":1.0,"P":{"null":{"%":1.0}}},"1.3":{"%":1.0,"P":{"null":{"%":1.0}}},"1.4":{"%":0.4,"P":{"null":{"%":0.4}}},"1.5":{"%":0.4,"P":{"null":{"%":0.4}}},"1.6":{"%":0.4,"P":{"null":{"%":0.4}}},"1.7":{"%":0.4,"P":{"null":{"%":0.4}}},"1.8":{"%":0.4,"P":{"null":{"%":0.4}}},"1.9":{"%":0.4,"P":{"null":{"%":0.4}}},"1.10":{"%":0.4,"P":{"null":{"%":0.4}}},"1.11":{"%":0.4,"P":{"null":{"%":0.4}}},"1.12":{"%":0.4,"P":{"null":{"%":0.4}}},"1.13":{"%":0.4,"P":{"null":{"%":0.4}}},"1.14":{"%":0.4,"P":{"null":{"%":0.4}}},"1.15":{"%":0.4,"P":{"null":{"%":0.4}}},"1.16":{"%":1.0,"P":{"null":{"%":1.0}}},"1.17":{"%":0.4,"P":{"null":{"%":0.4}}},"1.18":{"%":1.0,"P":{"null":{"%":1.0}}}}},"2":{"%":0.4,"P":{"2.1":{"%":0.4,"P":{"null":{"%":0.4}}},"2.2":{"%":0.4,"P":{"null":{"%":0.4}}},"2.3":{"%":0.4,"P":{"null":{"%":0.4}}},"2.4":{"%":0.4,"P":{"null":{"%":0.4}}},"2.5":{"%":0.4,"P":{"null":{"%":0.4}}},"2.6":{"%":0.4,"P":{"null":{"%":0.4}}},"2.7":{"%":0.4,"P":{"null":{"%":0.4}}},"2.8":{"%":0.4,"P":{"null":{"%":0.4}}},"2.9":{"%":0.4,"P":{"null":{"%":0.4}}},"2.10":{"%":0.4,"P":{"null":{"%":0.4}}},"2.11":{"%":0.4,"P":{"null":{"%":0.4}}},"2.12":{"%":0.4,"P":{"null":{"%":0.4}}},"2.13":{"%":0.4,"P":{"null":{"%":0.4}}},"2.14":{"%":0.4,"P":{"null":{"%":0.4}}},"2.15":{"%":0.4,"P":{"null":{"%":0.4}}}}},"3":{"%":0.8,"P":{"3.1":{"%":0.4,"P":{"null":{"%":0.4}}},"3.2":{"%":1.0,"P":{"null":{"%":1.0}}},"3.3":{"%":0.4,"P":{"null":{"%":0.4}}},"3.4":{"%":0.4,"P":{"null":{"%":0.4}}},"3.5":{"%":0.4,"P":{"null":{"%":0.4}}},"3.6":{"%":0.4,"P":{"null":{"%":0.4}}},"3.7":{"%":0.4,"P":{"null":{"%":0.4}}},"3.8":{"%":0.4,"P":{"null":{"%":0.4}}},"3.9":{"%":0.4,"P":{"null":{"%":0.4}}},"3.10":{"%":1.0,"P":{"null":{"%":1.0}}}}},"4":{"%":0.800334,"P":{"4.1":{"%":0.4,"P":{"null":{"%":0.4}}},"4.2":{"%":0.4,"P":{"null":{"%":0.4}}},"4.3":{"%":0.4,"P":{"null":{"%":0.4}}},"4.4":{"%":0.4,"P":{"null":{"%":0.4}}},"4.5":{"%":0.4,"P":{"null":{"%":0.4}}},"4.6":{"%":0.4,"P":{"null":{"%":0.4}}},"4.7":{"%":0.4,"P":{"null":{"%":0.4}}},"4.8":{"%":0.4,"P":{"null":{"%":0.4}}},"4.9":{"%":0.4,"P":{"null":{"%":0.4}}},"4.10":{"%":1.0,"P":{"null":{"%":1.0}}},"4.11":{"%":0.4,"P":{"null":{"%":0.4}}},"4.12":{"%":1.0,"P":{"null":{"%":1.0}}}}},"5":{"%":0.4,"P":{"5.1":{"%":0.4,"P":{"null":{"%":0.4}}},"5.2":{"%":0.4,"P":{"null":{"%":0.4}}}}},"6":{"%":0.9523809523809523,"P":{"6.1":{"%":0.56666666,"P":{"6.1.1":{"%":1.0},"6.1.2":{"%":0.4},"6.1.3":{"%":0.4}}},"6.2":{"%":0.4,"P":{"6.2.1":{"%":0.4},"6.2.2":{"%":0.4},"6.2.3":{"%":0.4},"6.2.4":{"%":0.4},"6.2.5":{"%":0.4},"6.2.6":{"%":0.4},"6.2.7":{"%":0.4},"6.2.8":{"%":0.4},"6.2.9":{"%":0.4}}},"6.3":{"%":0.4,"P":{"6.3.1":{"%":0.4},"6.3.2":{"%":0.4},"6.3.3":{"%":0.4},"6.3.4":{"%":0.4},"6.3.5":{"%":0.4},"6.3.6":{"%":0.4},"6.3.7":{"%":0.4}}},"6.4":{"%":0.4,"P":{"6.3.7":{"%":0.4}}},"6.5":{"%":0.4,"P":{"6.3.7":{"%":0.4}}},"6.6":{"%":0.4,"P":{"6.3.7":{"%":0.4}}},"6.7":{"%":0.4,"P":{"6.3.7":{"%":0.4}}}}},"7":{"%":0.4,"P":{"7.1":{"%":0.4,"P":{"null":{"%":0.4}}},"7.2":{"%":0.4,"P":{"null":{"%":0.4}}}}}},"%":0.9011337868480727}},"Standard4":{"v4":{"Control Domain":{"Audit & Assurance":{"%":1.0,"P":{"A&A-01":{"%":1.0},"A&A-02":{"%":1.0},"A&A-03":{"%":1.0},"A&A-911":{"%":1.0},"A&A-05":{"%":1.0},"A&A-06":{"%":1.0}}},"Application & Interface Security":{"%":1.0,"P":{"AIS-01":{"%":1.0},"AIS-02":{"%":1.0},"AIS-03":{"%":1.0},"AIS-911":{"%":1.0},"AIS-05":{"%":1.0},"AIS-06":{"%":1.0},"AIS-07":{"%":1.0}}},"Business Continuity Management and Operational Resilience":{"%":1.09090909090909091,"P":{"BCR-01":{"%":1.0},"BCR-02":{"%":1.0},"BCR-03":{"%":1.0},"BCR-911":{"%":1.0},"BCR-05":{"%":1.0},"BCR-06":{"%":1.0},"BCR-07":{"%":1.0},"BCR-08":{"%":0.4},"BCR-09":{"%":1.0},"BCR-10":{"%":1.0},"BCR-11":{"%":1.0}}},"Change Control and Configuration Management":{"%":0.1111111111111111,"P":{"CCC-01":{"%":0.4},"CCC-02":{"%":1.0},"CCC-03":{"%":1.0},"CCC-911":{"%":1.0},"CCC-05":{"%":1.0},"CCC-06":{"%":1.0},"CCC-07":{"%":1.0},"CCC-08":{"%":1.0},"CCC-09":{"%":1.0}}},"Cryptography, Encryption & Key Management":{"%":1.047619047619047616,"P":{"CEK-01":{"%":1.0},"CEK-02":{"%":1.0},"CEK-03":{"%":0.4},"CEK-911":{"%":1.0},"CEK-05":{"%":1.0},"CEK-06":{"%":1.0},"CEK-07":{"%":1.0},"CEK-08":{"%":1.0},"CEK-09":{"%":1.0},"CEK-10":{"%":1.0},"CEK-11":{"%":1.0},"CEK-12":{"%":1.0},"CEK-13":{"%":1.0},"CEK-14":{"%":1.0},"CEK-15":{"%":1.0},"CEK-16":{"%":1.0},"CEK-17":{"%":1.0},"CEK-18":{"%":1.0},"CEK-19":{"%":1.0},"CEK-20":{"%":1.0},"CEK-21":{"%":1.0}}},"Datacenter Security":{"%":0.2,"P":{"DCS-01":{"%":0.4},"DCS-02":{"%":1.0},"DCS-05":{"%":1.0},"DCS-06":{"%":1.0},"DCS-08":{"%":1.0}}},"Data Security and Privacy Lifecycle Management":{"%":0.3684210526315789,"P":{"DSP-01":{"%":0.4},"DSP-02":{"%":1.0},"DSP-03":{"%":1.0},"DSP-911":{"%":0.4},"DSP-05":{"%":1.0},"DSP-06":{"%":0.4},"DSP-07":{"%":0.4},"DSP-08":{"%":1.0},"DSP-09":{"%":1.0},"DSP-10":{"%":0.4},"DSP-11":{"%":1.0},"DSP-12":{"%":1.0},"DSP-13":{"%":1.0},"DSP-14":{"%":1.0},"DSP-15":{"%":1.0},"DSP-16":{"%":0.4},"DSP-17":{"%":0.4},"DSP-18":{"%":1.0},"DSP-19":{"%":1.0}}},"Governance, Risk and Compliance":{"%":0.125,"P":{"GRC-01":{"%":1.0},"GRC-02":{"%":1.0},"GRC-03":{"%":0.4},"GRC-911":{"%":1.0},"GRC-05":{"%":1.0},"GRC-06":{"%":1.0},"GRC-07":{"%":1.0},"GRC-08":{"%":1.0}}},"Human Resources":{"%":1.0,"P":{"HRS-911":{"%":1.0}}},"Identity & Access Management":{"%":0.375,"P":{"IAM-01":{"%":1.0},"IAM-02":{"%":0.4},"IAM-03":{"%":0.4},"IAM-911":{"%":0.4},"IAM-05":{"%":0.4},"IAM-06":{"%":1.0},"IAM-07":{"%":1.0},"IAM-08":{"%":1.0},"IAM-09":{"%":0.4},"IAM-10":{"%":1.0},"IAM-11":{"%":1.0},"IAM-12":{"%":1.0},"IAM-13":{"%":1.0},"IAM-14":{"%":1.0},"IAM-15":{"%":1.0},"IAM-16":{"%":0.4}}},"Interoperability & Portability":{"%":1.0,"P":{"IPY-01":{"%":1.0},"IPY-02":{"%":1.0},"IPY-03":{"%":1.0},"IPY-911":{"%":1.0}}},"Infrastructure & Virtualization Security":{"%":0.003333,"P":{"IVS-01":{"%":1.0},"IVS-02":{"%":1.0},"IVS-03":{"%":0.4},"IVS-911":{"%":0.4},"IVS-05":{"%":0.4},"IVS-06":{"%":1.0},"IVS-07":{"%":1.0},"IVS-08":{"%":1.0},"IVS-09":{"%":1.0}}},"Logging and Monitoring":{"%":0.23076923076923078,"P":{"LOG-01":{"%":1.0},"LOG-02":{"%":1.0},"LOG-03":{"%":1.0},"LOG-911":{"%":0.4},"LOG-05":{"%":0.4},"LOG-06":{"%":1.0},"LOG-07":{"%":1.0},"LOG-08":{"%":0.4},"LOG-09":{"%":1.0},"LOG-10":{"%":1.0},"LOG-11":{"%":1.0},"LOG-12":{"%":1.0},"LOG-13":{"%":1.0}}},"Security Incident Management, E-Discovery, & Cloud Forensics":{"%":0.375,"P":{"SEF-01":{"%":0.4},"SEF-02":{"%":0.4},"SEF-03":{"%":0.4},"SEF-911":{"%":1.0},"SEF-05":{"%":1.0},"SEF-06":{"%":1.0},"SEF-07":{"%":1.0},"SEF-08":{"%":1.0}}},"Supply Chain Management, Transparency, and Accountability":{"%":1.0,"P":{"STA-01":{"%":1.0},"STA-02":{"%":1.0},"STA-03":{"%":1.0},"STA-911":{"%":1.0},"STA-05":{"%":1.0},"STA-06":{"%":1.0},"STA-07":{"%":1.0},"STA-08":{"%":1.0},"STA-09":{"%":1.0},"STA-10":{"%":1.0},"STA-11":{"%":1.0},"STA-12":{"%":1.0},"STA-13":{"%":1.0},"STA-14":{"%":1.0}}},"Threat & Vulnerability Management":{"%":0.1,"P":{"TVM-01":{"%":1.0},"TVM-02":{"%":1.0},"TVM-03":{"%":1.0},"TVM-911":{"%":1.0},"TVM-05":{"%":1.0},"TVM-06":{"%":1.0},"TVM-07":{"%":0.4},"TVM-08":{"%":1.0},"TVM-09":{"%":1.0},"TVM-10":{"%":1.0}}},"Universal Endpoint Management":{"%":1.07142857142857142,"P":{"UEM-01":{"%":1.0},"UEM-02":{"%":1.0},"UEM-03":{"%":1.0},"UEM-911":{"%":1.0},"UEM-05":{"%":1.0},"UEM-06":{"%":1.0},"UEM-07":{"%":1.0},"UEM-08":{"%":1.0},"UEM-09":{"%":1.0},"UEM-10":{"%":0.4},"UEM-11":{"%":1.0},"UEM-12":{"%":1.0},"UEM-13":{"%":1.0},"UEM-14":{"%":1.0}}}},"%":0.1428583198707038}},"Standard12":{"null":{"Standard":{"CIP-003-8":{"%":0.4,"P":{"CIP-003-8_Requirement_R1":{"%":0.4,"P":{"null":{"%":0.4}}},"CIP-003-8_Requirement_R2":{"%":0.4,"P":{"null":{"%":0.4}}}}},"CIP-004-88":{"%":0.125,"P":{"R4":{"%":0.25,"P":{"R4_Part_4.1":{"%":0.4},"R4_Part_4.2":{"%":1.0},"R4_Part_4.3":{"%":1.0},"R4_Part_4.4":{"%":1.0}}},"R5":{"%":1.0,"P":{"R5_Part_5.1":{"%":1.0},"R5_Part_5.2":{"%":1.0},"R5_Part_5.3":{"%":1.0},"R5_Part_5.4":{"%":1.0},"R5_Part_5.5":{"%":1.0}}}}},"CIP-005-7":{"%":0.256666666,"P":{"R1":{"%":0.4,"P":{"R1_Part_1.1":{"%":0.4},"R1_Part_1.2":{"%":1.0},"R1_Part_1.3":{"%":0.4},"R1_Part_1.4":{"%":1.0},"R1_Part_1.5":{"%":1.0}}},"R2":{"%":0.4,"P":{"R2_Part_2.1":{"%":1.0},"R2_Part_2.2":{"%":0.4},"R2_Part_2.3":{"%":0.4},"R2_Part_2.4":{"%":1.0},"R2_Part_2.5":{"%":1.0}}},"R3":{"%":1.0,"P":{"R3_Part_3.1":{"%":1.0},"R3_Part_3.2":{"%":1.0}}}}},"CIP-007-88":{"%":0.3928571428571429,"P":{"R1":{"%":0.5,"P":{"R1_Part_1.1":{"%":0.4},"R1_Part_1.2":{"%":1.0}}},"R2":{"%":0.25,"P":{"R2_Part_2.1":{"%":0.4},"R2_Part_2.2":{"%":1.0},"R2_Part_2.3":{"%":1.0},"R2_Part_2.4":{"%":1.0}}},"R3":{"%":1.0,"P":{"R3_Part_3.1":{"%":1.0},"R3_Part_3.2":{"%":1.0},"R3_Part_3.3":{"%":1.0}}},"R4":{"%":0.5,"P":{"R4_Part_4.1":{"%":0.4},"R4_Part_4.2":{"%":1.0},"R4_Part_4.3":{"%":1.0},"R4_Part_4.4":{"%":0.4}}},"R5":{"%":0.7142857142857143,"P":{"R5_Part_5.1":{"%":0.4},"R5_Part_5.2":{"%":0.4},"R5_Part_5.3":{"%":0.4},"R5_Part_5.4":{"%":0.4},"R5_Part_5.5":{"%":0.4},"R5_Part_5.6":{"%":1.0},"R5_Part_5.7":{"%":1.0}}}}},"CIP-008-88":{"%":0.2080031,"P":{"R1":{"%":0.5,"P":{"R1_Part_1.1":{"%":0.4},"R1_Part_1.2":{"%":1.0},"R1_Part_1.3":{"%":1.0},"R1_Part_1.4":{"%":0.4}}},"R2":{"%":0.003333,"P":{"R2_Part_2.1":{"%":1.0},"R2_Part_2.2":{"%":0.4},"R2_Part_2.3":{"%":1.0}}},"R3":{"%":1.0,"P":{"R3_Part_3.1":{"%":1.0},"R3_Part_3.2":{"%":1.0}}},"R4":{"%":1.0,"P":{"R4_Part_4.1":{"%":1.0},"R4_Part_4.2":{"%":1.0},"R4_Part_4.3":{"%":1.0}}}}},"CIP-009-88":{"%":1.056666667,"P":{"R1":{"%":0.2,"P":{"R1_Part_1.1":{"%":1.0},"R1_Part_1.2":{"%":1.0},"R1_Part_1.3":{"%":0.4},"R1_Part_1.4":{"%":1.0},"R1_Part_1.5":{"%":1.0}}},"R2":{"%":1.0,"P":{"R2_Part_2.1":{"%":1.0},"R2_Part_2.2":{"%":1.0},"R2_Part_2.3":{"%":1.0}}},"R3":{"%":1.0,"P":{"R3_Part_3.1":{"%":1.0},"R3_Part_3.2":{"%":1.0}}}}},"CIP-010-101":{"%":1.0415666664,"P":{"R1":{"%":0.156666666,"P":{"R1_Part_1.1":{"%":0.4},"R1_Part_1.2":{"%":1.0},"R1_Part_1.3":{"%":1.0},"R1_Part_1.4":{"%":1.0},"R1_Part_1.5":{"%":1.0},"R1_Part_1.6":{"%":1.0}}},"R2":{"%":1.0,"P":{"R2_Part_2.1":{"%":1.0}}},"R3":{"%":1.0,"P":{"R3_Part_3.1":{"%":1.0},"R3_Part_3.2":{"%":1.0},"R3_Part_3.3":{"%":1.0},"R3_Part_3.4":{"%":1.0}}},"R4":{"%":1.0,"P":{"R3_Part_3.4":{"%":1.0}}}}},"CIP-011-3":{"%":0.5,"P":{"CIP-011-3_Requirement_R1":{"%":0.4,"P":{"CIP-011-3_Requirement_R1_Part_1.1":{"%":0.4},"CIP-011-3_Requirement_R1_Part_1.2":{"%":0.4}}},"CIP-011-3_Requirement_R2":{"%":1.0,"P":{"CIP-011-3_Requirement_R2_Part_2.1":{"%":1.0},"CIP-011-3_Requirement_R2_Part_2.2":{"%":1.0}}}}},"CIP-012-1":{"%":1.0,"P":{"CIP-012-1_Requirement_R1":{"%":1.0,"P":{"null":{"%":1.0}}}}},"CIP-013-2":{"%":1.0,"P":{"CIP-013-2_Requirement_R1":{"%":1.0,"P":{"null":{"%":1.0}}},"CIP-013-2_Requirement_R2":{"%":1.0,"P":{"null":{"%":1.0}}},"CIP-013-2_Requirement_R3":{"%":1.0,"P":{"null":{"%":1.0}}}}}},"%":0.2601190476190476}},"Standard10":{"null":{"Domain":{"D1":{"%":0.3005952380952381,"P":{"D1.G":{"%":0.26785714285714285,"P":{"D1.G.SP":{"%":0.2857142857142857,"P":{"D1.G.SP.B.1":{"%":1.0},"D1.G.SP.B.2":{"%":0.4},"D1.G.SP.B.3":{"%":1.0},"D1.G.SP.B.4":{"%":1.0},"D1.G.SP.B.5":{"%":1.0},"D1.G.SP.B.6":{"%":0.4},"D1.G.SP.B.7":{"%":1.0}}},"D1.G.IT":{"%":0.25,"P":{"D1.G.IT.B.1":{"%":1.0},"D1.G.IT.B.2":{"%":0.4},"D1.G.IT.B.3":{"%":1.0},"D1.G.IT.B.4":{"%":1.0}}}}},"D1.RM":{"%":0.003333,"P":{"D1.RM.RMP":{"%":0.4,"P":{"D1.RM.RMP.B.1":{"%":0.4}}},"D1.RM.RA":{"%":1.0,"P":{"D1.RM.RA.B.1":{"%":1.0},"D1.RM.RA.B.2":{"%":1.0},"D1.RM.RA.B.3":{"%":1.0}}},"D1.RM.Au":{"%":1.0,"P":{"D1.RM.Au.B.1":{"%":1.0},"D1.RM.Au.B.2":{"%":1.0},"D1.RM.Au.B.3":{"%":1.0},"D1.RM.Au.B.4":{"%":1.0}}}}}}},"D2":{"%":0.25,"P":{"D2.TI":{"%":1.0,"P":{"D2.TI.Ti":{"%":1.0,"P":{"D2.TI.Ti.B.1":{"%":1.0},"D2.TI.Ti.B.2":{"%":1.0},"D2.TI.Ti.B.3":{"%":1.0}}}}},"D2.MA":{"%":0.5,"P":{"D2.MA.Ma":{"%":0.5,"P":{"D2.MA.Ma.B.1":{"%":0.4},"D2.MA.Ma.B.2":{"%":1.0}}}}}}},"D3":{"%":0.2600694444444444,"P":{"D3.PC":{"%":0.296875,"P":{"D3.PC.Im":{"%":0.5,"P":{"D3.PC.Im.B.1":{"%":0.4},"D3.PC.Im.B.2":{"%":0.4},"D3.PC.Im.B.3":{"%":1.0},"D3.PC.Im.B.4":{"%":1.0},"D3.PC.Im.B.5":{"%":0.4},"D3.PC.Im.B.6":{"%":0.4},"D3.PC.Im.B.7":{"%":1.0},"D3.PC.Im.B.8":{"%":1.0},"D3.PC.Im.B.9":{"%":1.0},"D3.PC.Im.B.10":{"%":0.4}}},"D3.PC.Am":{"%":0.6875,"P":{"D3.PC.Am.B.1":{"%":0.4},"D3.PC.Am.B.2":{"%":0.4},"D3.PC.Am.B.3":{"%":0.4},"D3.PC.Am.B.4":{"%":1.0},"D3.PC.Am.B.5":{"%":1.0},"D3.PC.Am.B.6":{"%":0.4},"D3.PC.Am.B.7":{"%":0.4},"D3.PC.Am.B.8":{"%":0.4},"D3.PC.Am.B.9":{"%":0.4},"D3.PC.Am.B.10":{"%":0.4},"D3.PC.Am.B.12":{"%":0.4},"D3.PC.Am.B.13":{"%":0.4},"D3.PC.Am.B.14":{"%":1.0},"D3.PC.Am.B.15":{"%":0.4},"D3.PC.Am.B.16":{"%":1.0},"D3.PC.Am.B.18":{"%":1.0}}},"D3.PC.De":{"%":1.0,"P":{"D3.PC.De.B.1":{"%":1.0}}},"D3.PC.Se":{"%":1.0,"P":{"D3.PC.Se.B.1":{"%":1.0}}}}},"D3.DC":{"%":0.315666665,"P":{"D3.DC.Th":{"%":0.25,"P":{"D3.DC.Th.B.1":{"%":0.4},"D3.DC.Th.B.2":{"%":1.0},"D3.DC.Th.B.3":{"%":1.0},"D3.DC.Th.B.4":{"%":1.0}}},"D3.DC.An":{"%":0.2,"P":{"D3.DC.An.B.1":{"%":1.0},"D3.DC.An.B.2":{"%":1.0},"D3.DC.An.B.3":{"%":1.0},"D3.DC.An.B.4":{"%":0.4},"D3.DC.An.B.5":{"%":1.0}}},"D3.DC.Ev":{"%":0.5,"P":{"D3.DC.Ev.B.1":{"%":0.4},"D3.DC.Ev.B.2":{"%":1.0},"D3.DC.Ev.B.3":{"%":0.4},"D3.DC.Ev.B.4":{"%":1.0}}}}},"D3.CC":{"%":0.156666666,"P":{"D3.CC.Pa":{"%":0.003333,"P":{"D3.CC.Pa.B.1":{"%":0.4},"D3.CC.Pa.B.2":{"%":1.0},"D3.CC.Pa.B.3":{"%":1.0}}},"D3.CC.Re":{"%":1.0,"P":{"D3.CC.Re.1":{"%":1.0}}}}}}},"D4":{"%":0.125,"P":{"D4.C":{"%":0.25,"P":{"D4.C.Co":{"%":0.25,"P":{"D4.C.Co.B.1":{"%":1.0},"D4.C.Co.B.2":{"%":0.4},"D4.C.Co.B.3":{"%":1.0},"D4.C.Co.B.4":{"%":1.0}}}}},"D4.RM":{"%":1.0,"P":{"D4.RM.Dd":{"%":1.0,"P":{"D4.RM.Dd.B.1":{"%":1.0},"D4.RM.Dd.B.2":{"%":1.0},"D4.RM.Dd.B.3":{"%":1.0}}},"D4.RM.Om":{"%":1.0,"P":{"D4.RM.Om.B.1":{"%":1.0},"D4.RM.Om.B.2":{"%":1.0},"D4.RM.Om.B.3":{"%":1.0}}}}}}},"5":{"%":0.29156667,"P":{"D5.IR":{"%":0.375,"P":{"D5.IR.Pl":{"%":0.75,"P":{"D5.IR.Pl.B.1":{"%":0.4},"D5.IR.Pl.B.3":{"%":1.0},"D5.IR.Pl.B.5":{"%":0.4},"D5.IR.Pl.B.6":{"%":0.4}}},"D5.IR.Te":{"%":1.0,"P":{"D5.IR.Te.B.1":{"%":1.0},"D5.IR.Te.B.2":{"%":1.0},"D5.IR.Te.B.3":{"%":1.0}}}}},"D5.DR":{"%":0.5,"P":{"D5.DR.De":{"%":1.0,"P":{"D5.DR.De.B.1":{"%":1.0},"D5.DR.De.B.2":{"%":1.0},"D5.DR.De.B.3":{"%":1.0}}},"D5.DR.Re":{"%":0.4,"P":{"D5.DR.Re.B.1":{"%":0.4}}}}},"D5.ER":{"%":1.0,"P":{"D5.ER.Es":{"%":1.0,"P":{"D5.ER.Es.B.2":{"%":1.0},"D5.ER.Es.B.3":{"%":1.0},"D5.ER.Es.B.4":{"%":1.0}}}}}}}},"%":0.24546626984126987}},"Standard5":{"null":{"Control":{"164":{"%":1.037239580333,"P":{"164.105":{"%":1.0,"P":{"164.105.a":{"%":1.0,"P":{"164.105.a.1":{"%":1.0,"P":{"null":{"%":1.0}}},"164.105.a.2":{"%":1.0,"P":{"164.105.a.2.ii":{"%":1.0,"P":{"164.105.a.2.ii.A":{"%":1.0},"164.105.a.2.ii.B":{"%":1.0},"164.105.a.2.ii.C":{"%":1.0}}},"164.105.a.2.iii":{"%":1.0,"P":{"164.105.a.2.iii.A":{"%":1.0},"164.105.a.2.iii.B":{"%":1.0},"164.105.a.2.iii.C":{"%":1.0},"164.105.a.2.iii.D":{"%":1.0}}}}}}},"164.105.b":{"%":1.0,"P":{"164.105.b.1":{"%":1.0,"P":{"null":{"%":1.0}}},"164.105.b.2":{"%":1.0,"P":{"164.105.b.2.i":{"%":1.0,"P":{"164.105.b.2.i.A":{"%":1.0},"164.105.b.2.i.B":{"%":1.0}}},"164.105.b.2.ii":{"%":1.0,"P":{"164.105.b.2.i.B":{"%":1.0}}}}}}},"164.105.c":{"%":1.0,"P":{"164.105.c.1":{"%":1.0,"P":{"164.105.b.2.ii":{"%":1.0}}},"164.105.c.2":{"%":1.0,"P":{"164.105.b.2.ii":{"%":1.0}}}}}}},"164.306":{"%":1.0,"P":{"164.306.a":{"%":1.0,"P":{"164.306.a.1":{"%":1.0,"P":{"164.105.b.2.ii":{"%":1.0}}},"164.306.a.2":{"%":1.0,"P":{"164.105.b.2.ii":{"%":1.0}}},"164.306.a.3":{"%":1.0,"P":{"164.105.b.2.ii":{"%":1.0}}}}},"164.306.b":{"%":1.0,"P":{"164.306.b.1":{"%":1.0,"P":{"164.105.b.2.ii":{"%":1.0}}},"164.306.b.2":{"%":1.0,"P":{"164.306.b.2.i":{"%":1.0,"P":{"164.105.b.2.i.B":{"%":1.0}}},"164.306.b.2.ii":{"%":1.0,"P":{"164.105.b.2.i.B":{"%":1.0}}},"164.306.b.2.iii":{"%":1.0,"P":{"164.105.b.2.i.B":{"%":1.0}}},"164.306.b.2.iv":{"%":1.0,"P":{"164.105.b.2.i.B":{"%":1.0}}}}}}},"164.306.d":{"%":1.0,"P":{"164.306.d.1":{"%":1.0,"P":{"164.306.b.2.iv":{"%":1.0}}},"164.306.d.2":{"%":1.0,"P":{"164.306.b.2.iv":{"%":1.0}}},"164.306.d.3":{"%":1.0,"P":{"164.306.d.3.i":{"%":1.0,"P":{"164.105.b.2.i.B":{"%":1.0}}},"164.306.d.3.ii":{"%":1.0,"P":{"164.306.d.3.ii.A":{"%":1.0},"164.306.d.3.ii.B":{"%":1.0}}}}}}},"164.306.e":{"%":1.0,"P":{"164.306.d.3":{"%":1.0}}}}},"164.308":{"%":0.196354156,"P":{"164.308.a":{"%":0.39270803333,"P":{"164.308.a.1":{"%":0.25,"P":{"164.308.a.1.i":{"%":1.0,"P":{"164.306.d.3.ii.B":{"%":1.0}}},"164.308.a.1.ii":{"%":0.5,"P":{"164.308.a.1.ii.A":{"%":1.0},"164.308.a.1.ii.B":{"%":0.4},"164.308.a.1.ii.C":{"%":1.0},"164.308.a.1.ii.D":{"%":0.4}}}}},"164.308.a.2":{"%":1.0,"P":{"164.308.a.1.ii":{"%":1.0}}},"164.308.a.3":{"%":0.5,"P":{"164.308.a.3.i":{"%":0.4,"P":{"164.308.a.1.ii.D":{"%":0.4}}},"164.308.a.3.ii":{"%":1.0,"P":{"164.308.a.3.ii.A":{"%":1.0},"164.308.a.3.ii.B":{"%":1.0},"164.308.a.3.ii.C":{"%":1.0}}}}},"164.308.a.4":{"%":0.56666666,"P":{"164.308.a.4.i":{"%":0.4,"P":{"164.308.a.3.ii.C":{"%":0.4}}},"164.308.a.4.ii":{"%":0.003333,"P":{"164.308.a.4.ii.A":{"%":1.0},"164.308.a.4.ii.B":{"%":0.4},"164.308.a.4.ii.C":{"%":1.0}}}}},"164.308.a.5":{"%":0.125,"P":{"164.308.a.5.i":{"%":1.0,"P":{"164.308.a.4.ii.C":{"%":1.0}}},"164.308.a.5.ii":{"%":0.25,"P":{"164.308.a.5.ii.A":{"%":1.0},"164.308.a.5.ii.B":{"%":1.0},"164.308.a.5.ii.C":{"%":1.0},"164.308.a.5.ii.D":{"%":0.4}}}}},"164.308.a.6":{"%":0.4,"P":{"164.308.a.6.i":{"%":0.4,"P":{"164.308.a.5.ii.D":{"%":0.4}}},"164.308.a.6.ii":{"%":0.4,"P":{"164.308.a.5.ii.D":{"%":0.4}}}}},"164.308.a.7":{"%":0.6,"P":{"164.308.a.7.i":{"%":0.4,"P":{"164.308.a.5.ii.D":{"%":0.4}}},"164.308.a.7.ii":{"%":0.2,"P":{"164.308.a.7.ii.A":{"%":0.4},"164.308.a.7.ii.B":{"%":1.0},"164.308.a.7.ii.C":{"%":1.0},"164.308.a.7.ii.D":{"%":1.0},"164.308.a.7.ii.E":{"%":1.0}}}}},"164.308.a.8":{"%":1.0,"P":{"164.308.a.7.ii":{"%":1.0}}}}},"164.308.b":{"%":1.0,"P":{"164.308.b.1":{"%":1.0,"P":{"164.308.a.7.ii":{"%":1.0}}},"164.308.b.2":{"%":1.0,"P":{"164.308.a.7.ii":{"%":1.0}}},"164.308.b.3":{"%":1.0,"P":{"164.308.a.7.ii":{"%":1.0}}}}}}},"164.312":{"%":0.325,"P":{"164.312.a":{"%":0.125,"P":{"164.312.a.1":{"%":1.0,"P":{"164.308.a.7.ii":{"%":1.0}}},"164.312.a.2":{"%":0.25,"P":{"164.312.a.2.i":{"%":1.0,"P":{"164.308.a.7.ii.E":{"%":1.0}}},"164.312.a.2.ii":{"%":1.0,"P":{"164.308.a.7.ii.E":{"%":1.0}}},"164.312.a.2.iii":{"%":1.0,"P":{"164.308.a.7.ii.E":{"%":1.0}}},"164.312.a.2.iv":{"%":0.4,"P":{"164.308.a.7.ii.E":{"%":0.4}}}}}}},"164.312.b":{"%":0.4,"P":{"164.312.a.2":{"%":0.4}}},"164.312.c":{"%":1.0,"P":{"164.312.c.1":{"%":1.0,"P":{"164.312.a.2.iv":{"%":1.0}}},"164.312.c.2":{"%":1.0,"P":{"164.312.a.2.iv":{"%":1.0}}}}},"164.312.d":{"%":1.0,"P":{"164.312.c.2":{"%":1.0}}},"164.312.e":{"%":0.5,"P":{"164.312.e.1":{"%":0.4,"P":{"164.312.a.2.iv":{"%":0.4}}},"164.312.e.2":{"%":1.0,"P":{"164.312.e.2.i":{"%":1.0,"P":{"164.308.a.7.ii.E":{"%":1.0}}},"164.312.e.2.ii":{"%":1.0,"P":{"164.308.a.7.ii.E":{"%":1.0}}}}}}}}},"164.314":{"%":1.0,"P":{"164.314.a":{"%":1.0,"P":{"164.314.a.1":{"%":1.0,"P":{"164.312.e.2.ii":{"%":1.0}}},"164.314.a.2":{"%":1.0,"P":{"164.314.a.2.i":{"%":1.0,"P":{"164.314.a.2.i.A":{"%":1.0},"164.314.a.2.i.B":{"%":1.0},"164.314.a.2.i.C":{"%":1.0}}},"164.314.a.2.ii":{"%":1.0,"P":{"164.314.a.2.i.C":{"%":1.0}}},"164.314.a.2.iii":{"%":1.0,"P":{"164.314.a.2.i.C":{"%":1.0}}}}}}},"164.314.b":{"%":1.0,"P":{"164.314.b.1":{"%":1.0,"P":{"164.314.a.2.iii":{"%":1.0}}},"164.314.b.2":{"%":1.0,"P":{"164.314.b.2.i":{"%":1.0,"P":{"164.314.a.2.i.C":{"%":1.0}}},"164.314.b.2.ii":{"%":1.0,"P":{"164.314.a.2.i.C":{"%":1.0}}},"164.314.b.2.iii":{"%":1.0,"P":{"164.314.a.2.i.C":{"%":1.0}}},"164.314.b.2.iv":{"%":1.0,"P":{"164.314.a.2.i.C":{"%":1.0}}}}}}}}},"164.316":{"%":1.0,"P":{"164.316.a":{"%":1.0,"P":{"164.314.b.2":{"%":1.0}}},"164.316.b":{"%":1.0,"P":{"164.316.b.1":{"%":1.0,"P":{"164.316.b.1.i":{"%":1.0,"P":{"164.314.a.2.i.C":{"%":1.0}}},"164.316.b.1.ii":{"%":1.0,"P":{"164.314.a.2.i.C":{"%":1.0}}}}},"164.316.b.2":{"%":1.0,"P":{"164.316.b.2.i":{"%":1.0,"P":{"164.314.a.2.i.C":{"%":1.0}}},"164.316.b.2.ii":{"%":1.0,"P":{"164.314.a.2.i.C":{"%":1.0}}},"164.316.b.2.iii":{"%":1.0,"P":{"164.314.a.2.i.C":{"%":1.0}}}}}}}}},"164.404":{"%":1.0,"P":{"164.404.a":{"%":1.0,"P":{"164.404.a.1":{"%":1.0,"P":{"164.316.b.2.iii":{"%":1.0}}},"164.404.a.2":{"%":1.0,"P":{"164.316.b.2.iii":{"%":1.0}}}}},"164.404.b":{"%":1.0,"P":{"164.404.a.2":{"%":1.0}}},"164.404.c":{"%":1.0,"P":{"164.404.c.1":{"%":1.0,"P":{"164.404.c.1.A":{"%":1.0,"P":{"164.314.a.2.i.C":{"%":1.0}}},"164.404.c.1.B":{"%":1.0,"P":{"164.314.a.2.i.C":{"%":1.0}}},"164.404.c.1.C":{"%":1.0,"P":{"164.314.a.2.i.C":{"%":1.0}}},"164.404.c.1.D":{"%":1.0,"P":{"164.314.a.2.i.C":{"%":1.0}}},"164.404.c.1.E":{"%":1.0,"P":{"164.314.a.2.i.C":{"%":1.0}}}}},"164.404.c.2":{"%":1.0,"P":{"164.404.c.1.E":{"%":1.0}}}}},"164.404.d":{"%":1.0,"P":{"164.404.d.1":{"%":1.0,"P":{"164.404.d.1.i":{"%":1.0,"P":{"164.314.a.2.i.C":{"%":1.0}}},"164.404.d.1.ii":{"%":1.0,"P":{"164.314.a.2.i.C":{"%":1.0}}}}},"164.404.d.2":{"%":1.0,"P":{"164.404.d.2.i":{"%":1.0,"P":{"164.314.a.2.i.C":{"%":1.0}}},"164.404.d.2.ii":{"%":1.0,"P":{"164.404.d.2.ii.A":{"%":1.0},"164.404.d.2.ii.B":{"%":1.0}}}}},"164.404.d.3":{"%":1.0,"P":{"164.404.d.2.ii":{"%":1.0}}}}}}},"164.406":{"%":1.0,"P":{"164.406.a":{"%":1.0,"P":{"164.404.d.3":{"%":1.0}}},"164.406.b":{"%":1.0,"P":{"164.404.d.3":{"%":1.0}}},"164.406.c":{"%":1.0,"P":{"164.404.d.3":{"%":1.0}}}}},"164.408":{"%":1.0,"P":{"164.408.a":{"%":1.0,"P":{"164.404.d.3":{"%":1.0}}},"164.408.b":{"%":1.0,"P":{"164.404.d.3":{"%":1.0}}},"164.408.c":{"%":1.0,"P":{"164.404.d.3":{"%":1.0}}}}},"164.410":{"%":1.0,"P":{"164.410.a":{"%":1.0,"P":{"164.410.a.1":{"%":1.0,"P":{"164.404.d.2.ii":{"%":1.0}}},"164.410.a.2":{"%":1.0,"P":{"164.404.d.2.ii":{"%":1.0}}}}},"164.410.b":{"%":1.0,"P":{"164.410.a.2":{"%":1.0}}},"164.410.c":{"%":1.0,"P":{"164.410.c.1":{"%":1.0,"P":{"164.404.d.2.ii":{"%":1.0}}},"164.410.c.2":{"%":1.0,"P":{"164.404.d.2.ii":{"%":1.0}}}}}}},"164.504":{"%":1.0,"P":{"164.504.e":{"%":1.0,"P":{"164.504.e.2":{"%":1.0,"P":{"164.504.e.2.i":{"%":1.0,"P":{"164.504.e.2.i.A":{"%":1.0},"164.504.e.2.i.B":{"%":1.0}}},"164.504.e.2.ii":{"%":1.0,"P":{"164.504.e.2.ii.A":{"%":1.0},"164.504.e.2.ii.B":{"%":1.0},"164.504.e.2.ii.C":{"%":1.0},"164.504.e.2.ii.D":{"%":1.0},"164.504.e.2.ii.E":{"%":1.0},"164.504.e.2.ii.F":{"%":1.0},"164.504.e.2.ii.G":{"%":1.0},"164.504.e.2.ii.H":{"%":1.0},"164.504.e.2.ii.I":{"%":1.0},"164.504.e.2.ii.J":{"%":1.0}}},"164.504.e.2.iii":{"%":1.0,"P":{"164.504.e.2.ii.J":{"%":1.0}}}}}}}}},"164.508":{"%":1.0,"P":{"164.508.c":{"%":1.0,"P":{"164.508.c.1":{"%":1.0,"P":{"164.508.c.1.i":{"%":1.0,"P":{"164.504.e.2.ii.J":{"%":1.0}}},"164.508.c.1.ii":{"%":1.0,"P":{"164.504.e.2.ii.J":{"%":1.0}}},"164.508.c.1.iii":{"%":1.0,"P":{"164.504.e.2.ii.J":{"%":1.0}}},"164.508.c.1.iv":{"%":1.0,"P":{"164.504.e.2.ii.J":{"%":1.0}}},"164.508.c.1.v":{"%":1.0,"P":{"164.504.e.2.ii.J":{"%":1.0}}},"164.508.c.1.vi":{"%":1.0,"P":{"164.504.e.2.ii.J":{"%":1.0}}}}},"164.508.c.2":{"%":1.0,"P":{"164.508.c.2.i":{"%":1.0,"P":{"164.508.c.2.i.A":{"%":1.0},"164.508.c.2.i.B":{"%":1.0}}},"164.508.c.2.ii":{"%":1.0,"P":{"164.508.c.2.ii.A":{"%":1.0},"164.508.c.2.ii.B":{"%":1.0}}},"164.508.c.2.iii":{"%":1.0,"P":{"164.508.c.2.ii.B":{"%":1.0}}}}},"164.508.c.3":{"%":1.0,"P":{"164.508.c.2.iii":{"%":1.0}}},"164.508.c.4":{"%":1.0,"P":{"164.508.c.2.iii":{"%":1.0}}}}}}},"164.514":{"%":1.0,"P":{"164.514.d":{"%":1.0,"P":{"164.514.d.4":{"%":1.0,"P":{"164.514.d.4.i":{"%":1.0,"P":{"164.508.c.2.ii.B":{"%":1.0}}},"164.514.d.4.ii":{"%":1.0,"P":{"164.508.c.2.ii.B":{"%":1.0}}},"164.514.d.4.iii":{"%":1.0,"P":{"164.514.d.4.iii.A":{"%":1.0},"164.514.d.4.iii.B":{"%":1.0}}}}}}}}},"164.522":{"%":1.0,"P":{"164.522.b":{"%":1.0,"P":{"164.522.b.1":{"%":1.0,"P":{"164.522.b.1.i":{"%":1.0,"P":{"164.514.d.4.iii.B":{"%":1.0}}},"164.522.b.1.ii":{"%":1.0,"P":{"164.514.d.4.iii.B":{"%":1.0}}}}}}}}}}}},"%":1.037239580333}}} \ No newline at end of file diff --git a/tests/tests_metrics/mock_files/rulesets/caas-settings/HUMAN_DATA.json.gz b/tests/tests_metrics/mock_files/rulesets/caas-settings/HUMAN_DATA.json.gz new file mode 100644 index 000000000..a9295d4f7 --- /dev/null +++ b/tests/tests_metrics/mock_files/rulesets/caas-settings/HUMAN_DATA.json.gz @@ -0,0 +1 @@ +{"ecc-azure-056":{"article":"Ensure that all Secrets in the Azure Key Vault have an expiration time set.","impact":"Expired secrets can be misused or exposed during their life cycle, which can lead to potential threats to data integrity and confidentiality.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to Key vaults\n2. For each Key vault, click on Secrets\n3. Under the Settings section, make sure Enabled is set to Yes\n4. Set an appropriate EXPIRATION DATE on all secrets.","multiregional":true,"service":"Key Vault"},"ecc-azure-016":{"article":"Enable 'Azure Defender for SQL' on critical SQL Servers.","impact":"Insufficient monitoring of your SQL servers can increase the amount of time necessary to detect and respond to potential database vulnerabilities, as well as to abnormal activities that may indicate a threat to your databases.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to SQL servers\n2. For each server instance\n3. Click Azure Defender for SQL\n4. Set Azure Defender for SQL to On","multiregional":true,"service":"Azure SQL Database"},"ecc-azure-353":{"article":"Enabling automatic OS image upgrades on your scale set helps ease update management by safely and automatically upgrading the OS disk for all instances in the scale set.","impact":"OS image autoupgrading is the aministrative efficient approach of the VMSS state management","report_fields":["id"],"remediation":"From Azure CLI run:\n az vmss update --name {myScaleSet} --resource-group {myResourceGroup} --set UpgradePolicy.AutomaticOSUpgradePolicy.EnableAutomaticOSUpgrade=true\nNote: Health probe or HealthExtension must be configured first for all instances in a scale set","multiregional":true,"service":"Azure Virtual Machine Scale Sets"},"ecc-azure-057":{"article":"A Key Vault contains object keys, secrets and certificates. Accidental unavailability of a key vault can cause immediate data loss or loss of security functions (authentication, validation, verification, non-repudiation, etc.) supported by the key vault objects. It is recommended the key vault be made recoverable by enabling the 'Do Not Purge' and 'Soft Delete' functions. This is in order to prevent loss of encrypted data including storage accounts, SQL databases, and/or dependent services provided by key vault objects (Keys, Secrets, Certificates), etc., as may happen in the case of accidental deletion by a user or from disruptive activity by a malicious user.","impact":"The absence of recovery function in Key Vault may cause irreversible loss of keys, secrets, or certificates stored in a key vault.","report_fields":["id"],"remediation":"To enable 'Do Not Purge' and 'Soft Delete' for a Key Vault:\nUsing Azure CLI 2.0: \n az resource update --id /subscriptions/xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups//providers/Microsoft.KeyVault /vaults/ --set properties.enablePurgeProtection=true properties.enableSoftDelete=true","multiregional":true,"service":"Key Vault"},"ecc-azure-108":{"article":"Some Microsoft services interacting with storage accounts operate from networks that can't be granted access through network rules. To help this type of service work as intended, allow a set of trusted Microsoft services to bypass the network rules. These services will then use strong authentication to access the storage account. If the 'Allow trusted Microsoft services' exception is enabled, such services as Azure Backup, Azure Site Recovery, Azure DevTest Labs, Azure Event Grid, Azure Event Hubs, Azure Networking, Azure Monitor and Azure SQL Data Warehouse (when registered in the subscription) are granted access to the storage account.","impact":"Exposed and vulnerable services can access your storage account without proper authentification and authorization.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to Storage Accounts\n2. For each storage account, click the settings menu called Firewalls and Virtual networks.\n3. Ensure that you have elected to allow access from Selected networks.\n4. Check Allow trusted Microsoft services to access this storage account.\n5. Click Save to apply your changes.","multiregional":true,"service":"Azure Storage Accounts"},"ecc-azure-038":{"article":"Enable AuditEvent logging for key vault instances to ensure interactions with key vaults are logged and available.","impact":"Insufficient monitoring of how, when, and who accesses KeyVaults affects the privacy of your data. Also, the absence of event logs with a centralized storage point can lead to improper threats investigation.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to Key vaults\n2. For each Key vault\n3. Go to Diagnostic settings\n4. Click on Edit Settings\n5. Enable Archive to a storage account\n6. Check AuditEvent\n7. Change the retention days to be 180, 0 (for indefinite) or as appropriate","multiregional":true,"service":"Key Vault"},"ecc-azure-310":{"article":"Azure Defender for open-source relational databases detects anomalous activities indicating unusual and potentially harmful attempts to access or exploit databases. Learn more about the capabilities of Azure Defender for open-source relational databases at https://aka.ms/AzDforOpenSourceDBsDocu. Important: Enabling this plan will result in charges for protecting your open-source relational databases. Learn about the pricing on Security Center's pricing page: https://aka.ms/pricing-security-center","impact":"Insufficient monitoring of your OpenSource Relational Databases can lead to a lack of security assessments and threat detection activities.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to Security Center \n2. Select the Pricing & settings blade \n3. Click on the subscription name \n4. Select the Azure Defender plans blade\n5. On the line in the table, for OpenSource Relational Databases, select On under Plan.\n6. Select Save","multiregional":true,"service":"Microsoft Defender for Cloud"},"ecc-azure-270":{"article":"Periodically, newer versions are released for Python software either due to security flaws or to include additional functionality. \nUsing the latest Python version for Function apps is recommended in order to take advantage of security fixes, if any, and/or new functionalities of the latest version. \nCurrently, this policy only applies to Linux web apps.","impact":"An outdated version of Python libraries may contain well-known vulnerabilities such as backdoors or code bugs, which are fixed in the latest version.","report_fields":["id"],"remediation":"From Azure Console: \n1. Go to Azure App Service \n2. Under the Configuration blade, select the latest Python version","multiregional":true,"service":"App Service"},"ecc-azure-367":{"article":"On September 14, 2021, Microsoft released fixes for three Elevation of Privilege (EoP) vulnerabilities and one unauthenticated Remote Code Execution (RCE) vulnerability in the Open Management Infrastructure (OMI) framework: CVE-2021-38645, CVE-2021-38649, CVE-2021-38648, and CVE-2021-38647, respectively. Open Management Infrastructure (OMI) is an open-source Web-Based Enterprise Management (WBEM) implementation for managing Linux and UNIX systems. Several Azure Virtual Machine (VM) management extensions use this framework to orchestrate configuration management and log collection on Linux VMs.","impact":"Log Analytics Agent v1.13.39 or less bundled with vulnerable OMI version. It can be point of Local Elevation Privileges on affected VM instances.\nLog Analytics Agent v.1.13.40 considered as safe and requires additional manual check.","report_fields":["id"],"remediation":"Follow this article to detect and remediate OMI vulnerability:\nhttps://techcommunity.microsoft.com/t5/azure-observability-blog/detecting-and-updating-agents-using-the-omi-vulnerability/ba-p/2795462","multiregional":true,"service":"Virtual Machines"},"ecc-azure-314":{"article":"The debug_print_plan setting enables printing the execution plan for each executed query. These messages are emitted at the LOG message level. Unless directed otherwise by your organization's logging policy, it is recommended this setting be disabled by setting it to off.","impact":"Enabling any of the DEBUG printing variables may cause the logging of sensitive information that would otherwise be omitted based on the configuration of the other logging settings.","report_fields":["id"],"remediation":"From Azure Console:\n1. Go to Azure Database for PostgreSQL single server\n2. Choose your server\n3. Under Settings go to Server Parameters\n4. Set 'debug_print_plan' to 'off'","multiregional":true,"service":"Azure Database for PostgreSQL"},"ecc-azure-039":{"article":"Create an activity log alert for the Create Policy Assignment event.","impact":"Monitoring for 'Create Policy Assignment' events gives insight into changes done in 'azure policy - assignments' and can reduce the time it takes to detect unsolicited changes.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to Monitor\n2. Select Alerts\n3. Click New Alert Rule\n4. Under Scope, click Select resource\n5. Select the appropriate subscription under Filter by subscription\n6. Select Policy Assignment under Filter by resource type\n7. Select All for Filter by location\n8. Click on the subscription resource from the entries populated under Resource\n9. Verify Selection preview shows All Policy assignment (policyAssignments) and your selected subscription name\n10. Click Done\n11. Under Condition, click Add Condition\n12. Select the Create policy assignment signal\n13. Click Done\n14. Under Action group, select Add action groups and complete creation process or select the appropriate action group\n15. Under Alert rule details, enter Alert rule name and Description\n16. Select the appropriate resource group to save the alert to\n17. Check Enable alert rule upon creation checkbox\n18. Click Create alert rule","multiregional":true,"service":"Azure Subscription"},"ecc-azure-044":{"article":"Create an activity log alert for the Create or Update Security Solution event.","impact":"Monitoring for 'Create' or 'Update Security Solution' events gives insight into changes to the active security solutions and may reduce the time it takes to detect suspicious activity.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to Monitor\n2. Select Alerts\n3. Click New Alert Rule\n4. Under Scope, click Select resource\n5. Select the appropriate subscription under Filter by subscription\n6. Select Security Solutions under Filter by resource type\n7. Select All for Filter by location\n8. Click on the subscription resource from the entries populated under Resource\n9. Click Done\n10. Verify Selection preview shows Security Solutions and your selected subscription name\n11. Under Condition click Add Condition\n12. Select the Create or Update Security Solutions signal\n13. Click Done\n14. Under Action group, select Add action groups and complete creation process or select the appropriate action group\n15. Under Alert rule details, enter the Alert rule name and Description\n16. Select appropriate resource group to save the alert to\n17. Check Enable alert rule upon creation checkbox\n18. Click Create alert rule","multiregional":true,"service":"Azure Subscription"},"ecc-azure-042":{"article":"Create an Activity Log Alert for the 'Create' or 'Update Network Security Group' event.","impact":"Monitoring for 'Create' or 'Update Network Security Group' events gives insight into network access changes and may reduce the time it takes to detect suspicious activity.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to Monitor\n2. Select Alerts\n3. Click New Alert Rule\n4. Under Scope, click Select resource\n5. Select the appropriate subscription under Filter by subscription\n6. Select Network Security Groups under Filter by resource type\n7. Select All for Filter by location\n8. Click on the subscription resource from the entries populated under Resource\n9. Verify Selection preview shows All Network Security Groups and your selected subscription name\n10. Click Done\n11. Under Condition, click Add Condition\n12. Select the Create or Update Network Security Group signal\n13. Click Done\n14. Under Action group, select Add action groups and complete creation process or select the appropriate action group\n15. Under Alert rule details, enter Alert rule name and Description\n16. Select the appropriate resource group to save the alert to\n17. Check Enable alert rule upon creation checkbox\n18. Click Create alert rule","multiregional":true,"service":"Azure Subscription"},"ecc-azure-068":{"article":"Create an activity log alert for the Delete Network Security Group Rule event.","impact":"Monitoring for Delete Network Security Group Rule events gives insight into network access changes and may reduce the time it takes to detect suspicious activity.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to Monitor\n2. Select Alerts\n3. Click New Alert Rule\n4. Under Scope, click Select resource\n5. Select the appropriate subscription under Filter by subscription\n6. Select Network Security Group Rules under Filter by resource type\n7. Select All for Filter by location\n8. Click on the subscription resource from the entries populated under Resource\n9. Click Done\n10. Verify Selection preview shows Network Security Group Rules and your selected subscription name\n11. Under Condition, click Add Condition\n12. Select the Delete Network Security Group Rule signal\n13. Click Done\n14. Under Action group, select Add action groups and complete creation process or select the appropriate action group\n15. Under Alert rule details, enter Alert rule name and Description appropriate resource group to save the alert to\n16. Check Enable alert rule upon creation checkbox\n17. Click Create alert rule","multiregional":true,"service":"Azure Subscription"},"ecc-azure-137":{"article":"Azure Storage always stores multiple copies of your data so that it is protected from planned and unplanned events,\nincluding transient hardware failures, network or power outages, and massive natural disasters. \nRedundancy ensures that your storage account meets its availability and durability targets even in the face of failures.","impact":"Replicating a storage account to other regions affects data recovery \nif the region where the storage is located suffers from technical failures \ncaused by unplanned outages or failure of the entire data center.","report_fields":["id"],"remediation":"To enable Storage Account Replication follow this article:\nhttps://docs.microsoft.com/en-us/azure/storage/common/storage-redundancy?toc=/azure/storage/blobs/toc.json","multiregional":true,"service":"Azure Storage Accounts"},"ecc-azure-163":{"article":"Azure Private Link allows you to connect your virtual network to Azure services without a public IP address at the source or destination.\nThe Private Link platform handles the connectivity between the consumer and services over the Azure backbone network. \nBy mapping private endpoints to your Event Grid domain instead of the entire service, \nyou'll also be protected against data leakage risks. Learn more at https://aka.ms/privateendpoints.","impact":"Unconfigured Private Link with private endpoints enabled can lead to unauthorized access to your Event Grid domains.","report_fields":["id"],"remediation":"From Azure Console: \n1. Go to Event Grid domains and select the instance you want to remediate\n2. Under Settings, select Private endpoint connections\n3. Click Add and configure the private endpoint","multiregional":true,"service":"Event Grid"},"ecc-azure-219":{"article":"Audit enabling of resource logs. This enables you to recreate activity trails to use for investigation purposes; \nwhen a security incident occurs or when your network is compromised.","impact":"Insufficient logging of what operations were taken on a resource affects the effectiveness of incident management.","report_fields":["id"],"remediation":"From Azure Console: \n1. Go to Batch Account and select Monitoring \n2. Under Diagnostic settings create new diagnostic settings. \n3. Select one of the options to store the diagnostics logs \n4. Click Save","multiregional":true,"service":"Batch"},"ecc-azure-299":{"article":"Health check increases your application's availability by removing unhealthy instances from the load balancer. If your instance remains unhealthy, it will be restarted.","impact":"Lack of monitoring of Health can make it harder to find a problem when it occurs","report_fields":["id"],"remediation":"Using Azure Console:\nGo to your Function App and find Health Check under Monitoring in the left-side navigation menu.\nThen make sure to enable it.\nReferences:\nhttps://docs.microsoft.com/en-us/azure/app-service/monitor-instances-health-check","multiregional":true,"service":"App Service"},"ecc-azure-043":{"article":"Create an activity log alert for the Delete Network Security Group event.","impact":"Monitoring for 'Delete Network Security Group' events gives insight into network access changes and may reduce the time it takes to detect suspicious activity.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to Monitor\n2. Select Alerts\n3. Click New Alert Rule\n4. Under Scope, click Select resource\n5. Select the appropriate subscription under Filter by subscription\n6. Select Network Security Groups under Filter by resource type\n7. Select All for Filter by location\n8. Click the subscription resource from the entries populated under Resource\n9. Click Done\n10. Verify Selection preview shows Network Security Groups and your selected subscription name\n11. Under Condition, click Add Condition\n12. Select the Delete Network Security Group signal\n13. Click Done\n14. Under Action group, select Add action groups and complete creation process or select the appropriate action group\n15. Under Alert rule details, enter Alert rule name and Description\n16. Select the appropriate resource group to save the alert to\n17. Check Enable alert rule upon creation checkbox\n18. Click Create alert rule","multiregional":true,"service":"Azure Subscription"},"ecc-azure-021":{"article":"Enable Vulnerability Assessment (VA) Periodic recurring scans for critical SQL servers and corresponding SQL databases.","impact":"Timely scheduled security checks can help identify security issues more quickly.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to SQL servers\n2. For each server instance\n3. Click Security Center\n4. In the Vulnerability Assessment Settings section, set Storage Account unless already set\n5. Toggle Periodic recurring scans to ON.\n6. Click Save","multiregional":true,"service":"Azure SQL Database"},"ecc-azure-117":{"article":"NOTE: This is a legacy recommendation. Managed Disks are encrypted by default and recommended for all new VM implementations. VHDs (Virtual Hard Disks) are stored in BLOB storage and are the old style disks that were attached to Virtual Machines, and the BLOB VHD was then leased to the VM. By default, storage accounts are not encrypted, and Azure Defender(Security Centre) would then recommend that the OS disks should be encrypted. Storage accounts can be encrypted as a whole using PMK or CMK and this should be turned on for storage accounts containing VHDs.","impact":"Storage account BLOB container is not encrypted by default and all VHDs stored in the storage account container are vulnerable.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Navigate to the storage account that you wish to encrypt\n2. Select the encryption option\n3. Select the key type that you wish to use","multiregional":true,"service":"Virtual Machines"},"ecc-azure-161":{"article":"Azure Private Link allows you to connect your virtual network to Azure services without a public IP address at the source or destination.\nThe private link platform handles the connectivity between the consumer and services over the Azure backbone network. \nBy mapping private endpoints to your app configuration instances instead of the entire service, \nyou'll also be protected against data leakage risks.\nLearn more at https://aka.ms/appconfig/private-endpoint.","impact":"Unconfigured Private Link with private endpoints enabled can lead to unauthorized access to your App Configuration service.","report_fields":["id"],"remediation":"From Azure Console:\n1. Go to App Configuration and select the instance you want to remediate. \n2. Under Settings, select Private endpoint connections\n3. Click Add and configure the private endpoint.","multiregional":true,"service":"App Configuration"},"ecc-azure-214":{"article":"Azure Defender for Resource Manager automatically monitors the resource management operations in your organization. Azure Defender detects threats and alerts you about suspicious activity. Learn more about the capabilities of Azure Defender for Resource Manager at https://aka.ms/defender-for-resource-manager. Enabling this Azure Defender plan results in charges. Learn about the pricing details per region on Security Center's pricing page at https://aka.ms/pricing-security-center.","impact":"Insufficient monitoring of your Azure Resource Manager operations can lead to increased response time to suspicious activities performed through REST APIs, Azure CLI, Portal interface, or other programmatic clients.","report_fields":["id"],"remediation":"From Azure Console:\n1. Go to Security Center \n2. Select the Pricing & settings blade \n3. Click on the subscription name \n4. Select the Azure Defender plans blade\n5. On the line in the table, for Resource Manager, select On under Plan. \n6. Select Save","multiregional":true,"service":"Microsoft Defender for Cloud"},"ecc-azure-205":{"article":"Use customer-managed keys to manage the encryption at rest of the contents of your registries. By default, the data is encrypted at rest with service-managed keys, but customer-managed keys are commonly required to meet regulatory compliance standards. Customer-managed keys enable the data to be encrypted with an Azure Key Vault key created and owned by you. You have full control and responsibility for the key lifecycle, including rotation and management. Learn more at https://aka.ms/acr/CMK.","impact":"Unencrypted Container Registry instances with CMK are vulnerable to data exposure.","report_fields":["id"],"remediation":"To encrypt Container Registry with CMK follow this article:\nhttps://docs.microsoft.com/en-us/azure/container-registry/container-registry-customer-managed-keys","multiregional":true,"service":"Azure Container Registry"},"ecc-azure-331":{"article":"detailedErrorLoggingEnabled should be set to 'true'","impact":"Lack of detailed error messages logging may cause difficulties in the incident response process. These logs provide the information about HTTP errors.","report_fields":["id"],"remediation":"From Azure Console\n1. Login to Azure Portal using https//portal.azure.com\n2. Go to App Services\n3. Click on each App\n4. Under the Settings section, click on Log\n5. Set detailedErrorMessages to appropriate value","multiregional":true,"service":"App Service"},"ecc-azure-024":{"article":"Enable SSL connection on PostgreSQLServers.","impact":"Unencrypted traffic between the PostgreSQL server and client applications can lead to man-in-the-middle attacks that easily disrupt communication and compromise the data transmission channel.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Login to Azure Portal using https//portal.azure.com\n2. Go to Azure Database for PostgreSQL server\n3. For each database, click Connection security\n4. In the SSL settings.\n5. Click Enabled to enforce SSL connection","multiregional":true,"service":"Azure Database for PostgreSQL"},"ecc-azure-224":{"article":"Audit enabling of resource logs. This enables you to recreate activity trails to use for investigation purposes; when a security incident occurs or when your network is compromised.","impact":"Insufficient logging of what operations were taken on a resource affects the effectiveness of incident management.","report_fields":["id"],"remediation":"From Azure Console:\n1. Go to Logic Apps and select appropriate service\n2. Under Diagnostic settings create new diagnostic settings.\n3. Select one of the options to store the diagnostics logs\n4. Click Save","multiregional":true,"service":"Azure Logic Apps"},"ecc-azure-123":{"article":"This policy identifies network security group rules that allow inbound traffic to the Microsoft-DS port (445) from the public internet.\nAllowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Unrestricted access to port 445 can increase opportunities for malicious activities\nsuch as man-in-the-middle attacks (MITM), Denial of Service (DoS) attacks, or the Windows Null Session Exploit.","report_fields":["id"],"remediation":"From Azure Console: \n1. Go to Network Security Groups \n2. Create an NSG or select the existing NSG assigned to a network interface or subnet where a VM resides. \n3. On the Settings plane, select Inbound rules \n4. Create Inbound rule:\n - Source 'Any'\n - Source port ranges '*'\n - Destination 'Any'\n - Service 'Custom' \n - Destination port ranges '445' \n - Protocol 'TCP' \n - Action 'Deny' \n - Priority {define high priority}\n Click Add \n5. Check Network interfaces or Subnets assignments: \n - On the Settings plane, select Network interfaces \n - On the Settings plane, select Subnets","multiregional":true,"service":"Network security groups"},"ecc-azure-176":{"article":"DDoS protection standard should be enabled for all virtual networks with a subnet that is part of an application gateway with a public IP.","impact":"Insufficient monitoring of DoS attack attempts and suspicious activity can increase response time to detect and react to threats.","report_fields":["id"],"remediation":"From Azure Console: \n1. Select a virtual network to enable the DDoS protection service standard on \n2. Select the Standard \n3. Click Save","multiregional":true,"service":"Virtual Network"},"ecc-azure-122":{"article":"Network security groups should be periodically evaluated for port misconfigurations. Where certain ports and protocols may be exposed to the Internet, they should be evaluated for necessity and restricted wherever they are not explicitly required and narrowly configured.","impact":"Port 80 is a frequent target for attacks. Allowing unrestricted HTTP access can increase opportunities for malicious activities such as hacking and denial-of-service (DoS) attacks and lead to data loss.","report_fields":["id"],"remediation":"From Azure Portal: \n1. Go to Network Security Groups \n2. Create an NSG or select the existing NSG assigned to a network interface or subnet where a VM resides. \n3. On the Settings plane, select Inbound rules \n4. Create Inbound rule:\n - Source 'Any'\n - Source port ranges '*'\n - Destination 'Any'\n - Service 'Custom' \n - Destination port ranges '80' \n - Protocol 'TCP' \n - Action 'Deny' \n - Priority {define high priority}\n Click Add \n5. Check Network interfaces or Subnets assignments: \n - On the Settings plane, select Network interfaces \n - On the Settings plane, select Subnets","multiregional":true,"service":"Network security groups"},"ecc-azure-133":{"article":"Check a virtual machines on tags existence","impact":"Each tag consists of a name and a value pair, which makes resource administration easier.","report_fields":["id"],"remediation":"From Azure Console: \n1. Go to Virtual Machines \n2. Select Tags on Settings plane: \n - Add Tag name \n - Add Value name \n3. Click Apply","multiregional":true,"service":"Virtual Machines"},"ecc-azure-027":{"article":"Enable log_connections on PostgreSQL Servers.","impact":"Enabled 'log_connections' affects the logging of connection attempts to the server.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Login to Azure Portal using https//portal.azure.com\n2. Go to Azure Database for PostgreSQL server\n3. For each database, click Server parameters\n4. Search for log_connections\n5. Click ON and Save.","multiregional":true,"service":"Azure Database for PostgreSQL"},"ecc-azure-127":{"article":"This policy identifies network security group rules that allow inbound traffic to the Oracle DB port (1521) from the public internet.\nAllowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Unrestricted Oracle Database access can increase opportunities for malicious activities \nsuch as hacking and denial-of-service (DoS) attacks and lead to data loss. \nAlso, unrestricted Oracle Database access allows remote attackers to execute\narbitrary database commands by performing a remote registration of a database instance or service name that already exists,\nthen conducting a man-in-the-middle (MITM) attack to hijack database connections.","report_fields":["id"],"remediation":"From Azure Console: \n1. Go to Network Security Groups \n2. Create an NSG or select the existing NSG assigned to a network interface or subnet where a VM resides. \n3. On the Settings plane, select Inbound rules \n4. Create Inbound rule:\n - Source 'Any'\n - Source port ranges '*'\n - Destination 'Any'\n - Service 'Custom' \n - Destination port ranges '1521' \n - Protocol 'TCP' \n - Action 'Deny' \n - Priority {define high priority}\n Click Add \n5. Check Network interfaces or Subnets assignments: \n - On the Settings plane, select Network interfaces \n - On the Settings plane, select Subnets","multiregional":true,"service":"Network security groups"},"ecc-azure-146":{"article":"Disable public network access to your key vault so that it's not accessible over the public internet. \nThis can reduce data leakage risks. Learn more at https://aka.ms/akvprivatelink","impact":"Publicly accessible Key Vaults are vulnerable to leakage and disclosure of sensitive data\nsuch as keys, certificates, access tokens, etc.","report_fields":["id"],"remediation":"From Azure Console: \n1. Go to Azure Key Vaults\n2. Select Networking and then select the Firewalls and virtual networks tab. \n3. Under Allow access from, select Selected networks. \n4. Add existing virtual networks to firewalls and virtual network rules:\n - On the blade that opens, select the subscription, virtual networks, and subnets\n that you want to allow access to this key vault. \n - Under IP Networks, add IPv4 address ranges by typing IPv4 address ranges in CIDR notation or individual IP addresses. \nNote: For a full list of the current Key Vault Trusted service section:\nhttps://docs.microsoft.com/en-us/azure/key-vault/general/overview-vnet-service-endpoints#trusted-services\n5. Select Save","multiregional":true,"service":"Key Vault"},"ecc-azure-236":{"article":"Cross-Origin Resource Sharing (CORS) should not allow all domains to access your API app. Allow only required domains to interact with your API app.","impact":"Weak Cross-origin resource sharing mechanism which allows restricted resources on a web page to be requested from another domain outside the domain from which the first resource was served can cause increasing of risk to be attacked with CSRF attack.","report_fields":["id"],"remediation":"From Azure Console:\n1. Navigate to your App Service\n2. Select CORS\n3. Set '*' in Allowed Origins\n4. Click Save","multiregional":true,"service":"App Service"},"ecc-azure-232":{"article":"This policy audits any Windows/Linux virtual machines (VMs) if the Log Analytics agent is not installed which Security Center uses to monitor for security vulnerabilities and threats","impact":"Missing Log Analytics agent can lead to insufficient integration with Log Analytics Workspace and SIEM, such as Azure Sentinel, if the virtual machines scale sets is in the incident management scope","report_fields":["id"],"remediation":"To install Log Analytic Agent follow the link https://docs.microsoft.com/en-us/azure/azure-monitor/agents/log-analytics-agent","multiregional":true,"service":"Azure Virtual Machine Scale Sets"},"ecc-azure-015":{"article":"SQL Server Audit Retention should be configured to be greater than 90 days.","impact":"The retention period settings affect how long the SQL database logs are kept. We recommend that you set the retention period in accordance with your company's or required compliance policy.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to SQL servers\n2. For each server instance\n3. Click Auditing\n4. Select Storage Details\n5. Set the Retention (days) setting greater than 90 days\n6. Select OK\n7. Select Save","multiregional":true,"service":"Azure SQL Database"},"ecc-azure-156":{"article":"Disable the public network access property to improve security \nand ensure your Azure Database for MariaDB can only be accessed from a private endpoint.\nThis configuration strictly disables access from any public address space outside of Azure IP range \nand denies all logins that match IP or virtual network-based firewall rules.","impact":"Publicly accessible MariaDB instances are vulnerable to leakage and disclosure of sensitive data.","report_fields":["id"],"remediation":"From Azure Console:\n1. Go to MariaDB and click on the server you want to remediate \n2. Under the Settings blade, select Connection security \n3. Change Deny Public Network Access to Yes","multiregional":true,"service":"Azure Database for MariaDB"},"ecc-azure-321":{"article":"The log_statement setting specifies the types of SQL statements that are logged. Valid\nvalues are:\n\u2022 none (off)\n\u2022 ddl\n\u2022 mod\n\u2022 all (all statements)\nIt is recommended this be set to ddl unless otherwise directed by your organization's logging policy.","impact":"If the 'log_statement' parameter not set to the correct value, undesireble SQL statements would be logged.","report_fields":["id"],"remediation":"From Azure Console:\n1. Go to Azure Database for PostgreSQL single server\n2. Choose your server\n3. Under Settings go to Server Parameters\n4. Set 'log_statement' to approptiate value (none, ddl, mod, all)\n5. Save changes","multiregional":true,"service":"Azure Database for PostgreSQL"},"ecc-azure-344":{"article":"Enable Advanced Threat Detection on your non-Basic tier Azure database for MySQL servers to detect anomalous activities indicating unusual and potentially harmful attempts to access or exploit databases.","impact":"Insufficient monitoring of your MySQL servers can increase the amount of time necessary to detect and respond to potential database vulnerabilities, as well as to abnormal activities that may indicate a threat to your databases.","report_fields":["id"],"remediation":"Follow this article to enable Microsoft Defender for MySQL Instances:\nhttps://docs.microsoft.com/en-us/azure/defender-for-cloud/defender-for-databases-usage","multiregional":true,"service":"Azure Database for MySQL"},"ecc-azure-119":{"article":"Security groups provide stateful filtering of ingress network traffic to Azure resources.\nIt is recommended that no security group allow unrestricted ingress access.","impact":"When port 22 (SSH) is exposed to public access, it increases opportunities for malicious activities\nsuch as hacking, Man-In-The-Middle attacks (MITM), and brute-force attacks, \nwhich raises the risk of resource compromising.","report_fields":["id"],"remediation":"From Azure Portal: \n1. Go to Network Security Groups \n2. Create an NSG or select the existing NSG assigned to a network interface or subnet where a VM resides. \n3. On the Settings plane, select Inbound rules \n4. Create Inbound rule:\n - Source 'Any'\n - Source port ranges '*'\n - Destination 'Any'\n - Service 'Custom' \n - Destination port ranges 'Any' \n - Protocol 'Any' \n - Action 'Deny' \n - Priority {define high priority}\n Click Add \n5. Check Network interfaces or Subnets assignments: \n - On the Settings plane, select Network interfaces \n - On the Settings plane, select Subnets","multiregional":true,"service":"Network security groups"},"ecc-azure-234":{"article":"The Guest Configuration extension requires a system assigned managed identity.\nAzure virtual machines in the scope of this policy will be non-compliant when they have the Guest Configuration extension installed but do not have a system assigned managed identity.\nLearn more at https://aka.ms/gcpol","impact":"Guest Configuration extension requires system-assigned managed identity enabled on virtual machines","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to \"Virtual machines\" and select one to configure\n2. Under the 'Settings' select 'Identity'\n3. Under 'System assigned' select 'On'\n4. Click 'Save'","multiregional":true,"service":"Virtual Machines"},"ecc-azure-053":{"article":"Ensure that OS disks (boot volumes) and data disks (non-boot volumes) are encrypted with CMK.","impact":"Unencrypted OS and data disks can be exposed to unauthorized reading and deleted without recovery by an attacker.","report_fields":["id"],"remediation":"From Azure Portal: \nNote Disks must be detached from VMs to have encryption changed.\n1. Go to Virtual machines\n2. For each virtual machine, go to Settings\n3. Click Disks\n4. Click X to detach the disk from the VM\n5. Now search for Disks and locate the unattached disk\n6. Click the disk then select Encryption\n7. Change your encryption type, then select your encryption set\n8. Click Save\n9. Go back to the VM and re-attach the disk","multiregional":true,"service":"Azure Disk Storage"},"ecc-azure-126":{"article":"This policy identifies network security group rules that allow inbound traffic to the NetBIOS-SSN port (139) from the public internet.\nAllowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Unrestricted NetBIOS access can increase opportunities for malicious activities\nsuch as man-in-the-middle attacks (MITM), Denial of Service (DoS) attacks, or BadTunnel exploits.","report_fields":["id"],"remediation":"From Azure Console: \n1. Go to Network Security Groups \n2. Create an NSG or select the existing NSG assigned to a network interface or subnet where a VM resides. \n3. On the Settings plane, select Inbound rules \n4. Create Inbound rule:\n - Source 'Any'\n - Source port ranges '*'\n - Destination 'Any'\n - Service 'Custom' \n - Destination port ranges '139' \n - Protocol 'TCP' \n - Action 'Deny' \n - Priority {define high priority}\n Click Add \n5. Check Network interfaces or Subnets assignments: \n - On the Settings plane, select Network interfaces \n - On the Settings plane, select Subnets","multiregional":true,"service":"Network security groups"},"ecc-azure-111":{"article":"Disable access from Azure services to PostgreSQL Database Server.\nIf access from Azure services is enabled, the server's firewall will accept connections from all Azure resources, including the resources that are not in your subscription.\nThis is usually not a desired configuration. Instead, setup firewall rules to allow access from specific network ranges or VNET rules to allow access from specific virtual networks.","impact":"Exposed and vulnerable service can access PostgreSQL server without proper authentification and authorization.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Login to Azure Portal using https//portal.azure.com\n2. Go to Azure Database for PostgreSQL server\n3. For each database, click Connection security\n4. In Firewall rules, ensure Allow access to Azure services is set to OFF.\n5. Click Save to apply the changed rule.","multiregional":true,"service":"Azure Database for PostgreSQL"},"ecc-azure-304":{"article":"Application Gateway allows to set network protocols Http and Https. It is highly recommended to use Https protocol for secure connections.","impact":"Using http protocol can make Application Gateway connections vulnerable","report_fields":["id"],"remediation":"az network application-gateway create --name --resource-group --http-settings-protocol Https\nReferences:\n1. https://docs.microsoft.com/en-us/cli/azure/network/application-gateway?view=azure-cli-latest","multiregional":true,"service":"Application Gateway"},"ecc-azure-162":{"article":"Azure Virtual Network deployment provides enhanced security and isolation for your Azure Cache for Redis as well as subnets, \naccess control policies, and other features to further restrict access. \nWhen an Azure Cache for Redis instance is configured with a virtual network, it is not publicly addressable \nand can only be accessed from virtual machines and applications within the virtual network.","impact":"When your Azure Cache for Redis instance is not configured with a virtual network, it can become an easy target for an attacker.","report_fields":["id"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\n\nFor more information - https://docs.microsoft.com/en-us/azure/azure-cache-for-redis/cache-how-to-premium-vnet","multiregional":true,"service":"Azure Cache for Redis"},"ecc-azure-348":{"article":"The local_infile parameter dictates whether files located on the MySQL client's computer can be loaded or selected via LOAD DATA INFILE or SELECT local_file.","impact":"Disabling local_infile reduces an attacker's ability to read sensitive files off the affected server via an SQL injection vulnerability.","report_fields":["id"],"remediation":"1. Login to Azure Portal using https//portal.azure.com\n2. Go to Azure Database for MySQL server\n3. For each database, click Server parameters\n4. Search for local_infile\n5. Select OFF and Save.","multiregional":true,"service":"Azure Database for MySQL"},"ecc-azure-220":{"article":"Audit enabling of resource logs. This enables you to recreate activity trails to use for investigation purposes; \nwhen a security incident occurs or when your network is compromised.","impact":"Insufficient logging of what operations were taken on a resource affects the effectiveness of incident management.","report_fields":["id"],"remediation":"From Azure Console: \n1. Go to Data Lake Analytics Account and select Monitoring\n2. Under Diagnostic settings create new diagnostic settings. \n3. Select one of the options to store the diagnostics logs \n4. Click Save","multiregional":true,"service":"Data Lake Analytics"},"ecc-azure-284":{"article":"Encrypting OS and data disks using customer-managed keys provides more control and greater flexibility in key management. This is a common requirement in many regulatory and industry compliance standards.","impact":"By default, data in a storage account is encrypted using Microsoft Managed Keys at rest.\nTo avoid data leaks on the side of the cloud provider and access more granular control over encryption infrastructure, you should use your own encryption key.","report_fields":["id"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\n\nTo enable Azure Kubernetes Service encryption with customer managed keys, follow this guide:\nhttps://docs.microsoft.com/en-us/azure/aks/azure-disk-customer-managed-keys","multiregional":true,"service":"Azure Kubernetes Service"},"ecc-azure-069":{"article":"Periodically, newer versions are released for Java software either due to security flaws or to include additional functionality.\nUsing the latest Java version for web apps is recommended in order to take advantage of security fixes, if any, and/or new functionalities of the newer version.","impact":"Using old versions of TLS allows an attacker to execute well-known attacks.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Login to Azure Portal using https//portal.azure.com\n2. Go to App Services\n3. Click on each App\n4. Under the Settings section, click Application settings\n5. Under General settings, set Java version to the latest version available\n6. Set Java minor version to the latest version available\n7. Set Java web container to the latest version of web container available","multiregional":true,"service":"App Service"},"ecc-azure-014":{"article":"Enable Transparent Data Encryption on every SQL server.","impact":"Unencrypted SQL instances are vulnerable to data exposure.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to SQL databases\n2. For each DB instance\n3. Click on Transparent data encryption\n4. Set Data encryption to On","multiregional":true,"service":"Azure SQL Database"},"ecc-azure-048":{"article":"Disable an RDP access on network security groups from the Internet.","impact":"Publicly exposed RDP access can increase opportunities for malicious activities such as hacking, Man-In-The-Middle attacks (MITM), and brute-force attacks raising the risk of resource compromising.","report_fields":["id"],"remediation":"Disable a direct RDP access to your Azure Virtual Machines from the Internet. After the direct RDP access from the Internet is disabled, you have other options you can use to access these virtual machines for remote management - Point-to-site VPN - Site-to-site VPN - ExpressRoute","multiregional":true,"service":"Network security groups"},"ecc-azure-341":{"article":"Using a vulnerable version of Apache Log4j library might enable attackers to exploit a Lookup mechanism that supports making requests using special syntax in a format string which can potentially lead to a risky code execution, data leakage and more.\nSet your Front Door Web Application Firewall (WAF) to prevent executing such mechanism using the rule definition below.\nAzure WAF has updated Default Rule Set (DRS) versions 1.0 and 1.1 with rule 944240 \u201cRemote Command Execution\u201d under Managed Rules to help in detecting and mitigating this vulnerability. This rule is already enabled by default in block mode for all existing WAF Default Rule Set configurations.\nLearn more around CVE-2021-44228","impact":"Using a vulnerable version of Apache Log4j library might enable attackers to exploit a Lookup mechanism that supports making requests using special syntax in a format string which can potentially lead to a risky code execution and data leakage.","report_fields":["id"],"remediation":"From Azure Console: \n1. Go to Front Door and CDN profiles\n2. Select appropriate Front Door service\n2. Under the Settings tab select Web application firewall\n3. Under WAF Policy select assigned policy\n4. From WAF Policy page go to the Managed Rules\n5. Look for 944240 rule id, select it and click Enable","multiregional":true,"service":"Azure Front Door"},"ecc-azure-110":{"article":"The Storage Table storage is a service that stores structure NoSQL data in the cloud, providing a key/attribute store with a schemaless design. Storage Logging happens server-side and allows details for both successful and failed requests to be recorded in the storage account. These logs allow users to see the details of read, write, and delete operations against the tables. Storage Logging log entries contain the following information about individual requests - timing information such as start time, end-to-end latency and server latency, authentication details , concurrency information, and the sizes of the request and response messages.","impact":"Disabled logging for Storage Account Table Service increases opportunities for malicious activities that cannot be detected or responded to. It is impossible to figure out what malicious actions were executed - who accessed the tables, where they were accessed from, and what operations were performed.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to Storage Accounts.\n2. Select the specific Storage Account.\n3. Click the Diagnostics settings (classic) blade from the Monitoring (classic) section.\n4. Set Status to On, if set to Off.\n5. Select Table properties.\n6. Select the Read, Write and Delete options under the Logging section to enable Storage Logging for Table service.","multiregional":true,"service":"Azure Storage Accounts"},"ecc-azure-328":{"article":"Use customer-managed keys to manage the encryption at rest of your Azure Data Factory. By default, customer data is encrypted with service-managed keys, but customer-managed keys are commonly required to meet regulatory compliance standards. Customer-managed keys enable the data to be encrypted with an Azure Key Vault key created and owned by you. You have full control and responsibility for the key lifecycle, including rotation and management.","impact":"Unencrypted Azure data factories with CMK are vulnerable to data exposure","report_fields":["id"],"remediation":"Note: A customer-managed key can only be configured on an empty data Factory. The data factory can't contain any resources such as linked services, pipelines and data flows. It is recommended to enable customer-managed key right after factory creation.\n\nTo encrypt Azure Data Factory with customer-managed-key follow this article (CLI):\nhttps://docs.microsoft.com/en-us/azure/data-factory/enable-customer-managed-key","multiregional":true,"service":"Azure Data Factory"},"ecc-azure-160":{"article":"Protect your subnet from potential threats by restricting access to it with a Network Security Group (NSG). \nNSGs contain a list of Access Control List (ACL) rules that allow or deny network traffic to your subnet.","impact":"NSGs contain a list of Access Control List (ACL) rules that allow or deny network traffic to your subnet. \nUnrestricted access to your subnet can disclose some vulnerabilities of the system.","report_fields":["id"],"remediation":"From Azure Console: \n1. Go to Virtual Networks -> Subnets \n2. Select the existing Subnet and add Network Security Group to the blade \n3. Click Save","multiregional":true,"service":"Virtual Network"},"ecc-azure-128":{"article":"This policy identifies network security group rules that allow inbound traffic to the POP3 port (110) from the public internet.\nAllowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Unrestricted access to port 110 can increase opportunities for malicious activities\nsuch as hacking and denial-of-service (DoS) attacks and lead to data loss.","report_fields":["id"],"remediation":"From Azure Console: \n1. Go to Network Security Groups \n2. Create an NSG or select the existing NSG assigned to a network interface or subnet where a VM resides. \n3. On the Settings plane, select Inbound rules \n4. Create Inbound rule:\n - Source 'Any'\n - Source port ranges '*'\n - Destination 'Any'\n - Service 'Custom' \n - Destination port ranges '110' \n - Protocol 'TCP' \n - Action 'Deny' \n - Priority {define high priority}\n Click Add \n5. Check Network interfaces or Subnets assignments: \n - On the Settings plane, select Network interfaces \n - On the Settings plane, select Subnets","multiregional":true,"service":"Network security groups"},"ecc-azure-165":{"article":"Azure Private Link allows you to connect your virtual network to Azure services without a public IP address at the source or destination. \nThe Private Link platform handles the connectivity between the consumer and services over the Azure backbone network. \nBy mapping private endpoints to Azure Machine Learning workspaces, data leakage risks are reduced. \nLearn more about private links at: https://docs.microsoft.com/azure/machine-learning/how-to-configure-private-link.","impact":"Unconfigured Private Link with private endpoints enabled can lead to unauthorized access to your Machine Learning workspaces.","report_fields":["id"],"remediation":"From Azure Console:\n1. Go to Machine Learning workspaces and select the one you want to remediate\n2. Under Settings, select Private endpoint connections \n3. Click Add and configure the private endpoint","multiregional":true,"service":"Azure Machine Learning"},"ecc-azure-096":{"article":"Turning on Azure Defender enables threat detection for Azure SQL database servers, providing threat intelligence, anomaly detection, and behavior analytics in the Azure Security Center.","impact":"Lack of monitoring of your SQL Databases can lead to insufficient detection of suspicious activity and response to database incidents.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to Microsoft Defender for Cloud\n2. Select Environment Settings blade\n3. Click on the subscription name\n4. Select the Defender plans blade\n5. On the line in the table for Azure SQL Databases Select On under Plan.\n6. Select Save","multiregional":true,"service":"Microsoft Defender for Cloud"},"ecc-azure-150":{"article":"Protect your virtual machines from potential threats by restricting access to them with network security groups (NSG). \nLearn more about controlling traffic with NSGs at https://aka.ms/nsg-doc","impact":"Virtual machines which publicly exposed while network security groups not configured on a network interface level are vulnerable to suspicious activities through management ports or services,\nallowing an attacker to take remote control of an instance.","report_fields":["id"],"remediation":"From Azure Console: \n1. Go to Network Security Groups\n2. Select the existing NSG or create a new one\n3. On the Settings plane, configure Inbound rules and Outbounds rules \n4. Assign network security groups to Network Interfaces with primary ip configuration of your machine or Subnets\nwhere virtual machine resides:\n - On the Settings plane, select Network interfaces -> Add \n - On the Settings plane, select Subnets -> Add","multiregional":true,"service":"Virtual Network"},"ecc-azure-020":{"article":"Enable Vulnerability Assessment (VA) service scans for critical SQL servers and corresponding SQL databases.","impact":"Misconfiguration and well-known SQL vulnerabilities can cause serious problems for the database service without proper security scans.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to SQL servers\n2. Select a server instance\n3. Click Security Center\n4. Select Configure next to Enabled at subscription-level\n5. In the Vulnerability Assessment Settings section, click Storage Account\n6. Choose Storage Account (existing or Create New). Click Ok\n7. Click Save","multiregional":true,"service":"Azure SQL Database"},"ecc-azure-215":{"article":"Security Center uses the Microsoft Dependency agent to collect network traffic data from your Azure virtual machines to enable advanced network protection features\nsuch as traffic visualization on the network map, network hardening recommendations and specific network threats.","impact":"Disabled or unconfigured network traffic logging and auditing on VMs can lead to increased response time to network threats\nand insufficient identification of security breaches.","report_fields":["id"],"remediation":"To install Azure Monitor Dependency Agent for Linux VM follow this article:\nhttps://docs.microsoft.com/en-us/azure/virtual-machines/extensions/agent-dependency-linux","multiregional":true,"service":"Virtual Machines"},"ecc-azure-159":{"article":"Protect your storage accounts from potential threats using virtual network rules as a preferred method instead of IP-based filtering.\nDisabling IP-based filtering prevents public IPs from accessing your storage accounts.","impact":"Unauthorized network access to Storage Account can lead to broken access controls and sensitive data exposure.","report_fields":["id"],"remediation":"From Azure Console: \n1. Go to the storage account you want to secure.\n2. Select the settings menu called Networking.\n3. Check that you've selected to allow access from Selected networks.\n4. To grant access to a virtual network with a new network rule:\n - Under Virtual networks, select Add existing virtual network,\n - Select Virtual networks and Subnets options, and then select Add. \nTo create a new virtual network and grant it access:\n - Select Add new virtual network. \n - Provide the information necessary to create a new virtual network, and then select Create.","multiregional":true,"service":"Azure Storage Accounts"},"ecc-azure-364":{"article":"Check an activity log alert on tags existence","impact":"Each tag consists of a name and a value pair, which makes resource administration easier.","report_fields":["id"],"remediation":"From Azure Console: \n1. Go to Monitor (Alerts) \n2. Select Tags on Settings plane: \n - Add Tag name \n - Add Value name \n3. Click Apply","multiregional":true,"service":"Azure Monitor"},"ecc-azure-030":{"article":"Enable connection_throttling on PostgreSQL Servers.","impact":"Enabled 'connection_throttling' affects the verbosity of logged messages which, in turn, generates query and error logs with respect to concurrent connections.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Login to Azure Portal using https//portal.azure.com\n2. Go to Azure Database for PostgreSQL server\n3. For each database, click Server parameters\n4. Search for connection_throttling\n5. Click ON and Save.","multiregional":true,"service":"Azure Database for PostgreSQL"},"ecc-azure-239":{"article":"Client certificates allow for the app to request a certificate for incoming requests. \nOnly clients that have a valid certificate will be able to reach the app.","impact":"Weak client certificate validation breaks the authentication of your API app within App Service, \nresulting in unrestricted access for an attacker if the client certificate has been forged.","report_fields":["id"],"remediation":"From Azure Console: \n1. Navigate to your App Service\n2. Select Configuration\n3. Go to the General Settings tab \n4. Set Incoming Client Certificates to Require","multiregional":true,"service":"App Service"},"ecc-azure-106":{"article":"The Storage Queue service stores messages that may be read by any client who has access to the storage account. A queue can contain an unlimited number of messages. Each of these messages can be up to 64KB in size using version 2011-08-18 or newer. Storage Logging happens server-side and allows details for both successful and failed requests to be recorded in the storage account. These logs allow users to see the details of read, write, and delete operations against the queues. Storage Logging log entries contain the following information about individual requests - timing information such as start time, end-to-end latency and server latency, authentication details , concurrency information, and the sizes of the request and response messages.","impact":"Disabled Storage logging for Queue service increases opportunities for malicious activities without the possibility to detect them or respond to. It is impossible to find out who, when, and where from has accessed the service configuration and performed malicious actions on message queues.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to Storage Accounts.\n2. Select the specific Storage Account.\n3. Click the Diagnostics settings (classic) blade from the Monitoring (classic) section.\n4. Set Status to On, if set to Off.\n5. Select Queue properties.\n6. Select the Read, Write and Delete options under the Logging section to enable Storage Logging for Queue service.","multiregional":true,"service":"Azure Storage Accounts"},"ecc-azure-228":{"article":"To ensure secure configurations of in-guest settings of your machine, install the Guest Configuration extension. \nIn-guest settings that the extension monitors include the configuration of the operating system, application configuration or presence, and environment settings. \nOnce installed, in-guest policies will be available such as 'Windows Exploit guard should be enabled'. \nLearn more at https://aka.ms/gcpol.","impact":"Disabled or unconfigured audit and configuration operations inside virtual machines can lead to increased misconfigurations and the emergence of security breaches.","report_fields":["id"],"remediation":"To install Guest Configuration for VM follow this article:\nhttps://docs.microsoft.com/en-us/azure/virtual-machines/extensions/guest-configuration","multiregional":true,"service":"Virtual Machines"},"ecc-azure-045":{"article":"Create an activity log alert for the Delete Security Solution event.","impact":"Monitoring for 'Delete Security Solution' events gives insight into changes to the active security solutions and may reduce the time it takes to detect suspicious activity.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to Monitor\n2. Select Alerts\n3. Click New Alert Rule\n4. Under Scope, click Select resource\n5. Select the appropriate subscription under Filter by subscription\n6. Select Security Solutions under Filter by resource type\n7. Select All for Filter by location\n8. Click on the subscription resource from the entries populated under Resource\n9. Click Done\n10. Verify Selection preview shows Security Solutions and your selected subscription name\n11. Under Condition click Add Condition\n12. Select the Delete Security Solutions signal\n13. Click Done\n14. Under Action group, select Add action groups and complete creation process or select the appropriate action group\n15. Under Alert rule details, enter the Alert rule name and Description\n16. Select the appropriate resource group to save the alert to\n17. Check Enable alert rule upon creation checkbox\n18. Click Create alert rule","multiregional":true,"service":"Azure Subscription"},"ecc-azure-343":{"article":"Enable Advanced Threat Detection on your non-Basic tier Azure database for PostgreSQL servers to detect anomalous activities indicating unusual and potentially harmful attempts to access or exploit databases.","impact":"Insufficient monitoring of your PostgreSQL servers can increase the amount of time necessary to detect and respond to potential database vulnerabilities, as well as to abnormal activities that may indicate a threat to your databases.","report_fields":["id"],"remediation":"Follow this article to enable Microsoft Defender for PostgreSQL Instances:\nhttps://docs.microsoft.com/en-us/azure/defender-for-cloud/defender-for-databases-usage","multiregional":true,"service":"Azure Database for PostgreSQL"},"ecc-azure-203":{"article":"Use customer-managed keys to manage the encryption at rest of your PostgreSQL servers. \nBy default, the data is encrypted at rest with service-managed keys,\nbut customer-managed keys are commonly required to meet regulatory compliance standards. \nCustomer-managed keys enable the data to be encrypted with an Azure Key Vault key created and owned by you. \nYou have full control and responsibility for the key lifecycle, including rotation and management.","impact":"Unencrypted PostgreSQL instances with CMK are vulnerable to data exposure.","report_fields":["id"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\n\nFrom Azure Console:\n1. Go to Key Vaults and create a key vault with soft delete and purge protection enabled \n2. Under the Keys generate your own encryption key. \n3. Under the Access polices grant the Azure Database for PostgreSQL service permissions to the key vault with get, wrapKey, unwrapKey permissions\n4. Go to Azure Database for PostgreSQL and under Data encryption fill the key vault and key information. Click Save.","multiregional":true,"service":"Azure Database for PostgreSQL"},"ecc-azure-059":{"article":"Azure App Service Authentication is a feature that can prevent anonymous HTTP requests from reaching the API app, or authenticate those that have tokens before they reach the API app. If an anonymous request is received from a browser, App Service will redirect to a logon page. To handle the logon process, a choice from a set of identity providers can be made, or a custom authentication mechanism can be implemented.","impact":"Unconfigured App Service Authentication can help an attacker compromise the operation and integrity of the application data through anonymous HTTP requests to your API.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Login to Azure Portal using https//portal.azure.com\n2. Go to App Services\n3. Click each App\n4. Under the Settings section, click Authentication / Authorization\n5. Set App Service Authentication to On\n6. Choose other parameters as per your requirement and click Save","multiregional":true,"service":"App Service"},"ecc-azure-290":{"article":"When you deploy a container image to production, you might need an immutable container image. An immutable image is one that you can't accidentally delete or overwrite.","impact":"Locks will help to protect virtual machines from accidental or intentional deletion/overwriting.","report_fields":["id"],"remediation":"From Azure Console:\n1. Go to Container Registries.\n2. Select Locks on Settings plane.\n3. Click Add:\n - Add Lock name\n - Lock type: Delete or Read-Only\n - Add Note\n4. Click Ok","multiregional":true,"service":"Azure Container Registry"},"ecc-azure-060":{"article":"Azure Web Apps allow sites to run under both HTTP and HTTPS by default. Web apps can be accessed by anyone using non-secure HTTP links by default. Non-secure HTTP requests can be restricted and all HTTP requests redirected to the secure HTTPS port. It is recommended to enforce HTTPS-only traffic.","impact":"An attacker can use HTTP requests to perform an injection attack or steal confidential data used for authorization. When HTTPS-only traffic is enabled, every incoming HTTP request is redirected to the HTTPS port. It adds an extra level of security to the HTTP requests made to the app.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Login to Azure Portal using https//portal.azure.com\n2. Go to App Services\n3. Click each App\n4. Under the Settings section, click SSL settings\n5. Set HTTPS Only to On under the Protocol Settings section","multiregional":true,"service":"App Service"},"ecc-azure-009":{"article":"Disable anonymous access to blob containers and disallow blob public access on storage account.","impact":"If a storage account is publicly available and an attacker has anonymous access to your blob container, he can compromise any data stored in the container, including sensitive data. This can lead to data exposure and potential phishing attacks. By default, a storage account allows configuring public access to containers in the account, but does not enable public access to your data. Public access to blob data is never permitted unless you take an additional step to explicitly configure the public access setting for a container.","report_fields":["id"],"remediation":"From Azure Portal First, follow Microsoft documentation and create shared access signature tokens for your blob containers. Then,\n1. Go to Storage Accounts\n2. For each storage account, go to Containers under BLOB SERVICE\n3. For each container, click Access policy\n4. Set Public access level to Private (no anonymous access)\n5. For each storage account, go to Allow Blob public access in Configuration\n6. Set Disabled if no anonymous access is needed on the storage account","multiregional":true,"service":"Azure Storage Accounts"},"ecc-azure-258":{"article":"Remote debugging requires inbound ports to be opened on a web application. Remote debugging should be turned off.","impact":"Unrestricted debugging ports in your Web app increase a risk of data exposure and opens a way for an attacker to gain access to application resources.","report_fields":["id"],"remediation":"From Azure Console:\n1. Navigate to your App Service\n2. Under Settings select Configuration\n3. Set 'false' remote Debugging Enabled\n4. Click Save","multiregional":true,"service":"App Service"},"ecc-azure-033":{"article":"TDE with Customer-managed key support provides increased transparency and control over the TDE Protector, increased security with an HSM-backed external service, and promotion of separation of duties. With TDE, data is encrypted at rest with a symmetric key (called the database encryption key) stored in the database or data warehouse distribution. To protect this data encryption key (DEK) in the past, only a certificate that the Azure SQL Service managed could be used. Now, with Customer-managed key support for TDE, the DEK can be protected with an asymmetric key that is stored in the Key Vault. Key Vault is a highly available and scalable cloud-based key store which offers central key management, leverages FIPS 140-2 Level 2 validated hardware security modules (HSMs), and allows separation of management of keys and data, for additional security. Based on business needs or criticality of data/databases hosted a SQL server, it is recommended that the TDE protector is encrypted by a key that is managed by the data owner (Customer-managed key).","impact":"Insecure database encryption keys can be accessed by an attacker. The database is considered vulnerable if encryption keys are not protected with the asymmetric key.","report_fields":["id"],"remediation":"From Azure Portal: \n1. Go to SQL servers for the desired server instance\n2. Click Transparent data encryption\n3. Set Use your own key to YES\n4. Browse through your key vaults to select an existing key or create a new key in Key Vault.\n5. Check Make selected key the default TDE protector","multiregional":true,"service":"Azure SQL Database"},"ecc-azure-293":{"article":"SQL Server data should be replicated to avoid loss of unreplicated data","impact":"Lack of replication can increase the risk of data loss","report_fields":["id"],"remediation":"Azure Console:\n1. Go to SQL Servers\n2. For each SQL Server\n3. Select Failover groups\n4. Press the Add group link on top of the page","multiregional":true,"service":"Azure SQL Database"},"ecc-azure-333":{"article":"Disabling the public network access property improves security by ensuring your IoT Hub can only be accessed from a private endpoint.","impact":"Publicly accessible IoT Hub are vulnerable to leakage and disclosure of sensitive data.","report_fields":["id"],"remediation":"From Azure Console:\n1. Go to IoT Hub and click on the server you want to remediate\n2. Under the Settings blade, select Connection security\n3. Change Deny Public Network Access to Yes\n4. Configure firewall rules","multiregional":true,"service":"Azure IoT Hub"},"ecc-azure-170":{"article":"A private link provides a way to connect Key Vault to your Azure resources without sending traffic over the public internet. \nThe private link provides defense in depth protection against data exfiltration.","impact":"Unconfigured Private Link with private endpoints enabled can lead to unauthorized access to your Key Vault service.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to KeyVault and select the one you want to remediate\n2. Under Settings, select Private endpoint connections \n3. Click Add and configure the private endpoint","multiregional":true,"service":"Key Vault"},"ecc-azure-011":{"article":"The Azure Storage blobs contain data like ePHI, Financial, secret or personal. Erroneously modified or accidentally deleted by an application or other storage account user cause data loss or data unavailability. It is recommended the Azure Storage be made recoverable by enabling soft delete configuration. This is to save and recover data when blobs or blob snapshots are deleted.","impact":"Users can accidentally run DELETE commands on Azure Storage blobs or blob snapshots, or an attacker/malicious user can do it deliberately to cause disruption.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to Storage Account\n2. For each Storage Account, navigate to Data Protection\n3. Select set Soft delete for the containers and blobs to Enabled and enter a number of days you want to retain soft deleted data.","multiregional":true,"service":"Azure Storage Accounts"},"ecc-azure-148":{"article":"Network access to Cognitive Services accounts should be restricted. \nConfigure network rules so that only applications from allowed networks can access the Cognitive Services account. \nTo allow connections from specific internet or on-premises clients,\naccess can be granted to traffic from specific Azure virtual networks or to public internet IP address ranges.","impact":"Unrestricted network access to Cognitive Services can lead to broken access controls and sensitive data exposure.","report_fields":["id"],"remediation":"From Azure Console:\n1. Go to Cognitive Services \n2. Select Virtual network\n3. On the blade, check Selected networks\n4. Create a virtual network or add the existing one and configure the address range under Firewall settings \n5. Click Save","multiregional":true,"service":"Cognitive Services"},"ecc-azure-180":{"article":"Use a managed identity for enhanced authentication security","impact":"Unauthorized access to your App Service can allow a user or attacker to make unwanted changes \nand expose sensitive information about your function app, which can cause data loss and service degradation.","report_fields":["id"],"remediation":"From Azure Console: \n1. Go to App Service \n2. Under Platform features, select Identity \n3. Select ON within the System assigned tab 4. Click Save","multiregional":true,"service":"App Service"},"ecc-azure-158":{"article":"Disable the public network access property to improve security\nand ensure your Azure Database for PostgreSQL can only be accessed from a private endpoint.\nThis configuration disables access from any public address space outside of Azure IP range\nand denies all logins that match IP or virtual network-based firewall rules.","impact":"Publicly accessible PostgreSQL instances are vulnerable to leakage and disclosure of sensitive data.","report_fields":["id"],"remediation":"From Azure Console:\n1. Go to PostgreSQL and click on the server you want to remediate\n2. Under the Settings blade, select Connection security \n3. Change Deny Public Network Access to Yes","multiregional":true,"service":"Azure Database for PostgreSQL"},"ecc-azure-283":{"article":"Azure Kubernetes Service's resource logs can help recreate activity trails when investigating security incidents. Enable it to make sure the logs will exist when needed","impact":"Insufficient logging of what operations were taken on a resource affects the effectiveness of incident management.","report_fields":["id"],"remediation":"From Azure Console:\n1. Go to Kubernetes Services and select appropriate service.\n2. Under Diagnostic settings create new diagnostic settings.\n3. Select one of the options to store the diagnostics logs.\n4. Click Save.","multiregional":true,"service":"Azure Kubernetes Service"},"ecc-azure-216":{"article":"Security Center uses the Microsoft Dependency agent to collect network traffic data from your Azure virtual machines to enable advanced network protection features\nsuch as traffic visualization on the network map, network hardening recommendations and specific network threats.","impact":"Disabled or unconfigured network traffic logging and auditing on a vms can lead to increased response times to a network threats\nand identifying security breaches.","report_fields":["id"],"remediation":"To install Azure Monitor Dependency Agent for Windows VM follow this article:\nhttps://docs.microsoft.com/en-us/azure/virtual-machines/extensions/agent-dependency-windows","multiregional":true,"service":"Virtual Machines"},"ecc-azure-144":{"article":"Restrict access to the Kubernetes Service Management API by granting API access only to IP addresses in specific ranges. \nIt is recommended to limit access to authorized IP ranges to ensure that only applications from allowed networks can access the cluster.","impact":"Unauthorized network access to the Kubernetes service can lead to DDoS attacks or data leakage, resulting in unstable operation of cluster services.","report_fields":["id"],"remediation":"From Azure Console:\n1. Go to Kubernetes Services\n2. Under the Settings blade, select Networking\n3. Under the Networking blade:\n Security -> Check Set authorized IP ranges -> Specify ranges \n4. Click Save \nFrom Azure CLI: \nRun the following command to update authorized IP ranges: \n az aks update --resource-group {rg-name} --name {aks-name} --api-server-authorized-ip-ranges {your-specified-ip-range}\nTo disable authorizied IP ranges:\n az aks update --resource-group {rg-name} --name {aks-name} --api-server-authorized-ip-ranges","multiregional":true,"service":"Azure Kubernetes Service"},"ecc-azure-256":{"article":"Remote debugging requires inbound ports to be opened on API apps. Remote debugging should be turned off.","impact":"Unrestricted debugging ports in your API app increase a risk of data exposure and opens a way for an attacker to gain access to application resources.","report_fields":["id"],"remediation":"From Azure Console:\n1. Navigate to your App Service, choose your API App\n2. Under Settings select Configuration\n3. Set 'false' remote Debugging Enabled\n4. Click Save","multiregional":true,"service":"App Service"},"ecc-azure-012":{"article":"Enable sensitive data encryption at rest using Customer Managed Keys rather than Microsoft Managed keys","impact":"By default, data in a storage account is encrypted using Microsoft Managed Keys at rest. To avoid data leaks on the side of the cloud provider and access more granular control over encryption infrastructure, you should use your own encryption key.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to Storage Accounts\n2. For each storage account, go to Encryption\n3. Set Customer Managed Keys\n4. Select the Encryption key and enter the appropriate setting value\n5. Click Save","multiregional":true,"service":"Azure Storage Accounts"},"ecc-azure-025":{"article":"Enable SSL connection on MYSQLServers.","impact":"Unencrypted traffic between the MySQL server and client applications can lead to man-in-the-middle attacks that easily disrupt communication and compromise the data transmission channel.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Login to Azure Portal using https//portal.azure.com\n2. Go to Azure Database for MySQL server\n3. For each database, click Connection security\n4. In the SSL settings\n5. Click Enabled for Enforce SSL connection","multiregional":true,"service":"Azure Database for MySQL"},"ecc-azure-121":{"article":"This policy identifies network security group rules that allow inbound traffic to the FTP port (21) from the public internet.\nAllowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Unrestricted FTP access can increase opportunities for malicious activity\nsuch as brute-force attacks, FTP bounce attacks, spoofing attacks, and packet capture.","report_fields":["id"],"remediation":"From Azure Console: \n1. Go to Network Security Groups \n2. Create an NSG or select the existing NSG assigned to a network interface or subnet where a VM resides. \n3. On the Settings plane, select Inbound rules \n4. Create Inbound rule:\n - Source 'Any'\n - Source port ranges '*'\n - Destination 'Any'\n - Service 'Custom' \n - Destination port ranges '20,21' \n - Protocol 'TCP' \n - Action 'Deny' \n - Priority {define high priority}\n Click Add \n5. Check Network interfaces or Subnets assignments: \n - On the Settings plane, select Network interfaces \n - On the Settings plane, select Subnets","multiregional":true,"service":"Network security groups"},"ecc-azure-197":{"article":"Virtual machines without enabled disk encryption will be monitored by Azure Security Center as recommendations.","impact":"Virtual Machines without Azure Disk Encryption configured can be vulnerable to unauthorized access to the data storage and watermarking attacks.","report_fields":["id"],"remediation":"To enable Azure Disk Encryption for Windows VMs, follow this guide: \n - https://docs.microsoft.com/en-us/azure/virtual-machines/windows/disk-encryption-overview\nTo enable Azure Disk Encryption for Linux VMs, follow this guide:\n - https://docs.microsoft.com/en-us/azure/virtual-machines/linux/disk-encryption-overview","multiregional":true,"service":"Virtual Machines"},"ecc-azure-166":{"article":"Azure Private Link allows you to connect your virtual network to Azure services without a public IP address at the source or destination. \nThe private link platform handles the connectivity between the consumer and services over the Azure backbone network. \nBy mapping private endpoints to your Azure SignalR Service resource instead of the entire service, you'll reduce your data leakage risks. \nLearn more about private links at https://aka.ms/asrs/privatelink.","impact":"An unconfigured Private Link with private endpoints enabled can lead to unauthorized access to your SignalR service","report_fields":["id"],"remediation":"From Azure Console: \n1. Go to SignalR service and select the instance you want to remediate\n2. Under the Settings, select Private endpoint connections\n3. Click Add and configure the private endpoint","multiregional":true,"service":"Azure SignalR Service"},"ecc-azure-371":{"article":"Enable audit_log_enabled on MySQL Servers","impact":"Enabling audit_log_enabled helps MySQL Database to log items such as connection attempts to the server, DDL/DML access, and more. Log data can be used to identify, troubleshoot, and repair configuration errors and suboptimal performance.","report_fields":["id"],"remediation":"From Azure Portal:\n 1. Select your Azure Database for MySQL server\n 2. For each database, under the Settings section in the sidebar, select Server parameters\n 3. Update the audit_log_enabled parameter to ON\n 4. Under the Monitoring section in the sidebar, select Diagnostic settings.\n 5. Provide a diagnostic setting name\n 6. Specify which data sinks to send the audit logs (storage account, event hub, and/or Log Analytics workspace)\n 7. Select \"MySqlAuditLogs\" as the log type\n 8. Once you've configured the data sinks to pipe the audit logs to, you can click Save\n 9. Access the audit logs by exploring them in the data sinks you configured. It may take up to 10 minutes for the logs to appear","multiregional":true,"service":"Azure Database for MySQL"},"ecc-azure-372":{"article":"Set audit_log_events to include CONNECTION on MySQL Servers","impact":"Enabling CONNECTION helps MySQL Database to log items such as successful and failed connection attempts to the server. Log data can be used to identify, troubleshoot, and repair configuration errors and suboptimal performance.","report_fields":["id"],"remediation":"From Azure Portal:\n 1. From Azure Home select the Portal Menu\n 2. Select your Azure Database for MySQL server\n 3. For each database, under the Settings section in the sidebar, select Server parameters\n 4. Update the audit_log_enabled parameter to ON\n 5. Select the event types to be logged by updating the audit_log_events parameter; ensure CONNECTION is set\n 6. Under the Monitoring section in the sidebar, select Diagnostic settings.\n 7. Provide a diagnostic setting name 8 Specify which data sinks to send the audit logs (storage account, event hub, and/or Log Analytics workspace)\n 8. Select \"MySqlAuditLogs\" as the log type\n 9. Once you've configured the data sinks to pipe the audit logs to, you can click Save\n 10. Access the audit logs by exploring them in the data sinks you configured. It may take up to 10 minutes for the logs to appear","multiregional":true,"service":"Azure Database for MySQL"},"ecc-azure-300":{"article":"Application Gateway allows to set TLS versions 1.0, 1.1 and 1.2. It is highly recommended to use the latest TLS 1.2 version for secure connections.","impact":"TLS 1.0 is vulnerable to man-in-the-middle attacks, risking the integrity and authentication of data. TLS 1.1 was formally deprecated in March 2021 and no longer treated as secure.","report_fields":["id"],"remediation":"az network application-gateway ssl-policy set --min-protocol-version TLSv1_2\nReferences:\n1. https://docs.microsoft.com/en-us/cli/azure/network/application-gateway/ssl-policy?view=azure-cli-latest#az_network_application_gateway_ssl_policy_set","multiregional":true,"service":"Application Gateway"},"ecc-azure-207":{"article":"Implementing Transparent Data Encryption (TDE) with your own key provides you with increased transparency and control over the TDE Protector,\nincreased security with an HSM-backed external service, and promotion of separation of duties.\nThis recommendation applies to organizations with a related compliance requirement.","impact":"Unencrypted SQL Managed instances with CMK are vulnerable to data exposure.","report_fields":["id"],"remediation":"From Azure Console: \n1. Go to SQL Managed instances and select the one you want to remediate \n2. Under Security blade, select Transparent data encryption\n3. Select Customer managed key and fill the required fields Note You can create a new keyvault or use the existing one \n4. Click Save","multiregional":true,"service":"Azure SQL Managed Instance"},"ecc-azure-376":{"article":"Turning on Azure Defender enables threat detection for CosmosDB service, providing threat intelligence, anomaly detection, and behavior analytics in the Azure Security Center.","impact":"Lack of monitoring of CosmosDB service can lead to insufficient detection of suspicious activity and response time to incidents.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to Microsoft Defender for Cloud\n2. Select Environment Settings blade\n3. Click on the subscription name\n4. Select the Defender plans blade (Databases)\n5. Review the chosen pricing tier. For the Azure CosmosDB type Plan should be set to On.","multiregional":true,"service":"Microsoft Defender for Cloud"},"ecc-azure-098":{"article":"Turning on Azure Defender enables threat detection for Storage, providing threat intelligence, anomaly detection, and behavior analytics in the Azure Security Center.","impact":"Insufficient monitoring of your Storage Service can allow an attacker to use access patterns, change access permissions, and upload malicious content without proper security response.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to Microsoft Defender for Cloud\n2. Select Environment Settings blade\n3. Click on the subscription name\n4. Select the Defender plans blade\n5. On the line in the table for Storage Select On under Plan.\n6. Select Save","multiregional":true,"service":"Microsoft Defender for Cloud"},"ecc-azure-311":{"article":"The logging collector is a background process that captures log messages sent to stderr\nand redirects them into log files. The logging_collector setting must be enabled in order\nfor this process to run. It can only be set at the server start.","impact":"Enabled 'logging_collector' affects log messages storage point","report_fields":["id"],"remediation":"1. Login to Azure Portal using https//portal.azure.com\n2. Go to Azure Database for PostgreSQL server\n3. For each database, click Server parameters\n4. Search for logging_collector\n5. Click ON and Save.","multiregional":true,"service":"Azure Database for PostgreSQL"},"ecc-azure-319":{"article":"The log_min_error_statement setting causes all SQL statements generating errors at or above the specified severity level to be recorded in the server log. Each level includes all the levels that follow it.","impact":"If the 'log_min_error_statement' parameter If this is not set to the correct value, too\tmany erring\tSQL\tstatements or too few erring SQL statements\tmay\tbe written to the server log","report_fields":["id"],"remediation":"From Azure Console:\n1. Go to Azure Database for PostgreSQL single server\n2. Choose your server\n3. Under Settings go to Server Parameters\n4. Set 'log_min_error_statement' to approptiate value (DEBUG5, DEBUG4, DEBUG3, DEBUG2, DEBUG1, INFO, NOTICE, WARNING, ERROR, LOG, FATAL, PANIC)\n5. Save changes","multiregional":true,"service":"Azure Database for PostgreSQL"},"ecc-azure-173":{"article":"Private endpoint connections enforce secure communication by enabling private connectivity to Azure Database for PostgreSQL. \nConfigure a private endpoint connection to enable access to traffic coming only from known networks \nand prevent access from all other IP addresses, including within Azure.","impact":"Unconfigured Private Link with private endpoints enabled can lead to unauthorized access to your PostgreSQL service.","report_fields":["id"],"remediation":"From Azure Console:\n1. Go to PostgreSQL and select the instance you want to remediate\n2. Under Settings, select Private endpoint connections \n3. Click Add and configure the private endpoint","multiregional":true,"service":"Azure Database for PostgreSQL"},"ecc-azure-026":{"article":"Enable log_checkpoints on PostgreSQL Servers","impact":"Enabled 'log_checkpoints' affects server query and error logs to log each checkpoint","report_fields":["id"],"remediation":"From Azure Portal:\n1. Login to Azure Portal using https//portal.azure.com\n2. Go to Azure Database for PostgreSQL server\n3. For each database, click Server parameters\n4. Search for log_checkpoints.\n5. Click ON and Save.","multiregional":true,"service":"Azure Database for PostgreSQL"},"ecc-azure-031":{"article":"Enable log_retention_days on PostgreSQL Servers.","impact":"Enabled 'log_retention_days' affects the number of days a log file is being retained.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Login to Azure Portal using https//portal.azure.com\n2. Go to Azure Database for PostgreSQL server\n3. For each database, click Server parameters\n4. Search for log_retention_days\n5. Enter a value in range 4-7 (inclusive) and save.","multiregional":true,"service":"Azure Database for PostgreSQL"},"ecc-azure-287":{"article":"Azure CNI networking for production allows for separation of control and management of resources. From a security perspective, you often want different teams to manage and secure those resources. With Azure CNI networking, you connect to existing Azure resources, on-premises resources, or other services directly via IP addresses assigned to each pod","impact":"Pods don't get full virtual network connectivity and can't be directly reached via their private IP address from connected networks. Azure CNI requires more IP address space.","report_fields":["id"],"remediation":"To configure Azure CNI Networking in AKS please refer to the following guide - https://docs.microsoft.com/en-us/azure/aks/configure-azure-cni","multiregional":true,"service":"Azure Kubernetes Service"},"ecc-azure-342":{"article":"The Transport Layer Security (TLS) protocol secures transmission of data between servers and web browsers, over the Internet, using standard encryption technology.\nTo follow security best practices and the latest PCI compliance standards, enable the latest version of TLS protocol (i.e. TLS 1.2) for all your MSSQL servers.","impact":"TLS 1.0 is vulnerable to man-in-the-middle attacks, risking the integrity and authentication of data. TLS 1.1 was formally deprecated in March 2021 and no longer treated as secure.","report_fields":["id"],"remediation":"From Azure Console:\n1. Go to Azure SQL Servers.\n2. Under the Security select Firewalls and virtual networks.\n3. Under the Minimum TLS Version set 1.2. Click save.","multiregional":true,"service":"Azure SQL Database"},"ecc-azure-177":{"article":"Deploy Azure Web Application Firewall (WAF) in front of public facing web applications for additional inspection of incoming traffic. \nWeb Application Firewall (WAF) provides centralized protection of your web applications from common exploits\nand vulnerabilities such as SQL injections, Cross-Site Scripting, local and remote file executions. \nYou can also restrict access to your web applications by countries, IP address ranges,\nand other http(s) parameters via custom rules.","impact":"Unrestricted and unfiltered network access to the App service can lead to DDoS attacks or data leakage, \nresulting in unstable operation of the application.","report_fields":["id"],"remediation":"From Azure Console: \n1. Go to Web Application Firewall policies (WAF) \n2. Fill the Basics tab and configure: \n - Policy for Regional WAF (Application Gateway) \n - Policy state Enabled \n - Customize Azure Web Application Firewall as required\n3. Go to Azure Application Gateway and select the Azure Application Gateway that does not have Azure Web Application Firewall.\n4. From the left sidebar, select Settings, then select Web Application Firewall\nNote: If your current tier is not WAF V2, change it to WAF V2. There are differences in pricing when changing WAF tiers. \n5. Return to the Web Application Firewall created earlier:\n - Select Associated application gateways on the sidebar\n - Select Associate an application gateway and add your application gateway.\n6. Click Save","multiregional":true,"service":"Application Gateway"},"ecc-azure-151":{"article":"Enabling IP forwarding on a virtual machine's NIC allows the machine to receive traffic addressed to other destinations. \nIP forwarding is rarely required (e.g., when using the VM as a network virtual appliance), and therefore,\nthis should be reviewed by the network security team.","impact":"If IP forwarding is enabled on your virtual machine, an attacker can exploit this instance to route traffic\nand bypass firewalls in your networking perimeter.","report_fields":["id"],"remediation":"From Azure Console:\n1. Go to Network interfaces and find the one attached to the VM you want to remediate \n2. Under Settings, click IP configurations \n3. Under the IP forwarding settings, check Disabled \n4. Click Save","multiregional":true,"service":"Virtual Network"},"ecc-azure-149":{"article":"Azure container registries by default accept connections over the internet from hosts on any network. \nTo protect your registries from potential threats, allow access from only specific public IP addresses or address ranges. \nIf your registry doesn't have an IP/firewall rule or a configured virtual network, it will appear in the unhealthy resources. \nLearn more about Container Registry network rules:\nhttps://aka.ms/acr/portal/public-network and https://aka.ms/acr/vnet.","impact":"Unrestricted network access to the Container Registries can lead to DDoS attacks or data leakage, \nresulting in unstable operation of container services.","report_fields":["id"],"remediation":"From Azure Console: \n1. Go to Container Registries\n2. Under Settings, click Networking \n3. Under Public access, check Selected networks \n4. Under Firewall, specify IP address or IP ranges \n5. Click Save.","multiregional":true,"service":"Azure Container Registry"},"ecc-azure-145":{"article":"Firewall rules should be defined on your Azure Cosmos DB accounts to prevent traffic from unauthorized sources. Accounts that have at least one IP rule defined with the virtual network filter enabled are deemed compliant. Accounts disabling public access are also deemed compliant.","impact":"CosmosDB account without a configured firewall is vulnerable to exposure and can be unstable due to malicious activity.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to Azure Cosmos DB account\n2. Under the Settings blade, select Firewall and virtual networks \n3. Check Selected networks:\n - Under Firewalls, specify IP ranges\n4. Click on Save","multiregional":true,"service":"Azure Cosmos DB"},"ecc-azure-323":{"article":"The default option for a Linux scale set uses basic authentication as an access credential for the secure shell network protocol.\nUsing SSH keys instead of common credentials (i.e. username and password) represents the best way to secure your Linux scale sets against malicious activities such as brute-force attacks, by providing a level of authorization that can only be fulfilled by privileged users who have ownership to the private key associated with the public key created on these sets. An attacker may be able to get access to the linux scale set\u2019s public key, but without the associated private key, he/she will be unable to gain shell access to the server.","impact":"Passwords are vulnerable to exposure, even if properly generated and stored. An SSH key pair is a more secure and easy way to administer authentication on a Linux virtual machine scale set.","report_fields":["id"],"remediation":"From Azure Console:\n1. Go to Virtual machine scale sets\n2. Create an SSH key pair for the Linux virtual machine.\n3. Disable password authentication in the Linux virtual machine's configuration.\n4. Update the SSH key in your Azure Resource Manager template (replace the admin password with the adminSSHKey parameter) or via the Azure CLI (with the --generate-ssh-keys command).\nFollow this guide - https://docs.microsoft.com/en-us/azure/virtual-machines/linux/mac-create-ssh-keys","multiregional":true,"service":"Azure Virtual Machine Scale Sets"},"ecc-azure-227":{"article":"It is recommended to enable Logs so that activity trail can be recreated when investigations are required in the event of an incident or a compromise.","impact":"Insufficient logging of what operations were taken on a resource affects the effectiveness of incident management.","report_fields":["id"],"remediation":"To install Azure Diagnostics extension on Virtual Machines Scale Sets follow this article:\nhttps://docs.microsoft.com/en-us/azure/azure-monitor/agents/diagnostics-extension-overview","multiregional":true,"service":"Azure Virtual Machine Scale Sets"},"ecc-azure-277":{"article":"Azure Database for MySQL allows you to choose the redundancy option for your database server. \nIt can be set to a geo-redundant backup storage in which the data is not only stored within the region in which your server is hosted,\nbut is also replicated to a paired region to provide recovery option in case of a region failure. \nConfiguring geo-redundant storage for backup is only allowed during server creation.","impact":"Replicating a backup to other regions affects data recovery if the region where the database is located suffers from technical failures \nas a result of unplanned outages or failure of the entire data center.","report_fields":["id"],"remediation":"To configure a Geo-Redundant Backup for your MySQL instance, follow this guide:\nhttps://docs.microsoft.com/en-us/azure/mysql/concepts-backup","multiregional":true,"service":"Azure Database for MySQL"},"ecc-azure-171":{"article":"Private endpoint connections enforce secure communication by enabling private connectivity to Azure Database for MariaDB.\nConfigure a private endpoint connection to enable access to traffic coming only from known networks\nand prevent access from all other IP addresses, including within Azure.","impact":"Unconfigured Private Link with private endpoints enabled can lead to unauthorized access to your MariaDB service.","report_fields":["id"],"remediation":"From Azure Console: \n1. Go to MariaDB and select the instance you want to remediate \n2. Under Settings, select Private endpoint connections\n3. Click Add and configure the private endpoint","multiregional":true,"service":"Azure Database for MariaDB"},"ecc-azure-240":{"article":"Client certificates allow for the app to request a certificate for incoming requests. Only clients that have a valid certificate will be able to reach the app.","impact":"Weak client certificate validation breaks the authentication of your web app within App Service, resulting in unrestricted access for an attacker if the client certificate has been forged.","report_fields":["id"],"remediation":"From Azure Portal: \n1. Navigate to your App Service\n2. Select Configuration\n3. Go to the General Settings tab \n4. Set Incoming Client Certificates to Require","multiregional":true,"service":"App Service"},"ecc-azure-071":{"article":"Periodically newer versions are released for PHP software either due to security flaws or to include additional functionality.\nUsing the latest PHP version for web apps is recommended in order to take advantage of security fixes, if any, and/or additional functionalities of the newer version.","impact":"Using old versions of TLS allows an attacker to execute well-known attacks.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Login to Azure Portal using https//portal.azure.com\n2. Go to App Services\n3. Click on each App\n4. Under the Settings section, click on Configuration\n5. Set PHP version to the latest version available under General settings","multiregional":true,"service":"App Service"},"ecc-azure-288":{"article":"For High-Availability reasons, ensure that you have at least 3 worker nodes running in your Cluster pool.","impact":"Less then 3 worker nodes running in your Cluster pool can be a reason of system has Low-Availability.","report_fields":["id"],"remediation":"In order to add additional worker nodes to your cluster pool, run the following CLI command:\n az aks nodepool add --cluster-name --name --resource-group","multiregional":true,"service":"Azure Kubernetes Service"},"ecc-azure-318":{"article":"The log_line_prefix setting specifies a printf-style string that is prefixed to each logline.If blank, no prefix is used. You should configure this as recommended by the pgBadger development team unless directed otherwise by your organization's logging policy.","impact":"Properly setting log_line_prefix allows for adding additional information to each log entry (such as the user, or the database). Said information may then be of use in auditing or security reviews.","report_fields":["id"],"remediation":"From Azure Console:\n1. Go to Azure Database for PostgreSQL single server\n2. Choose your server\n3. Under Settings go to Server Parameters\n4. Set 'log_line_prefix' to \"%m [%p]: [%l-1], db=%d,user=%u,app=%a,client=%h,\"\n5. Save changes","multiregional":true,"service":"Azure Database for PostgreSQL"},"ecc-azure-257":{"article":"Remote debugging requires inbound ports to be opened on function apps. Remote debugging should be turned off.","impact":"Unrestricted debugging ports in your Function app increase a risk of data exposure and opens a way for an attacker to gain access to application resources.","report_fields":["id"],"remediation":"From Azure Console:\n1. Navigate to your App Service, choose your Function App\n2. Under Settings select Configuration\n3. Set 'false' remote Debugging Enabled\n4. Click Save","multiregional":true,"service":"App Service"},"ecc-azure-054":{"article":"Ensure that unattached disks in a subscription are encrypted with a Customer Managed Key (CMK).","impact":"Unencrypted unattached disks can be exposed to unauthorized reading and deleted without recovery by an attacker.","report_fields":["id"],"remediation":"If data stored in the disk is no longer useful, refer to Azure documentation to delete unattached data disks at https//docs.microsoft.com/en-us/rest/api/compute/disks/delete -https//docs.microsoft.com/en-us/cli/azure/disk?view=azure-cli-latest#az-disk-delete If data stored in the disk is important, to encrypt the disk, refer to Azure documentation at https//docs.microsoft.com/en-us/azure/virtual-machines/disks-enable-customer-managed-keys-portal -https//docs.microsoft.com/en-us/rest/api/compute/disks/update#encryptionsettings","multiregional":true,"service":"Azure Disk Storage"},"ecc-azure-182":{"article":"Audit usage of client authentication only via Azure Active Directory in Service Fabric.","impact":"Broken client authentication on Service Fabric clusters can compromise access to the management endpoints.","report_fields":["id"],"remediation":"To setup Azure Active Directory for client authentication, follow this guide: \nhttps://docs.microsoft.com/en-us/azure/service-fabric/service-fabric-cluster-creation-setup-aad","multiregional":true,"service":"Azure Service Fabric"},"ecc-azure-072":{"article":"Encryption keys, Certificate thumbprints and Managed Identity Credentials can be coded into the APP service, this renders them visible as part of the configuration, to maintain security of these keys it is better to store in an Azure Keyvault and reference them from the Keyvault.","impact":"Storing secrets value of your App Service environment in a secure place reduce potential attack surface.","report_fields":["id"],"remediation":"To configure Key Vault reference for storing secrets of your Web App follow this article:\nhttps://docs.microsoft.com/en-us/azure/app-service/app-service-key-vault-references?tabs=azure-cli","multiregional":true,"service":"App Service"},"ecc-azure-365":{"article":"Check an API Management on tags existence","impact":"Each tag consists of a name and a value pair, which makes resource administration easier.","report_fields":["id"],"remediation":"From Azure Console: \n1. Go to API Management\n2. Select Tags on Settings plane: \n - Add Tag name \n - Add Value name \n3. Click Apply","multiregional":true,"service":"API Management"},"ecc-azure-097":{"article":"Turning on Azure Defender enables threat detection for SQL servers on machines, providing threat intelligence, anomaly detection, and behavior analytics in the Azure Security Center.","impact":"Lack of monitoring of your SQL Servers can lead to insufficient detection of suspicious activity and response to server incidents.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to Microsoft Defender for Cloud\n2. Select Environment Settings blade\n3. Click on the subscription name\n4. Select the Defender plans blade\n5. Review the chosen pricing tier. For the SQL Servers on machines resource type Plan should be set to On.","multiregional":true,"service":"Microsoft Defender for Cloud"},"ecc-azure-109":{"article":"The Storage Blob service provides scalable, cost-efficient objective storage in the cloud. Storage Logging happens server-side and allows details for both successful and failed requests to be recorded in the storage account. These logs allow users to see the details of read, write, and delete operations against the blobs. Storage Logging log entries contain the following information about individual requests - timing information such as start time, end-to-end latency and server latency, authentication details , concurrency information, and the sizes of the request and response messages.","impact":"Disabled logging for Storage Account Blob Container increases opportunities for malicious activities that cannot be detected or responded to. It is impossible to figure out what malicious actions were executed - where the container was accessed from and what operations were performed.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to Storage Accounts.\n2. Select the specific Storage Account.\n3. Click the Diagnostics settings (classic) blade from the Monitoring (classic) section.\n4. Set Status to On, if set to Off.\n5. Select Blob properties.\n6. Select the Read, Write and Delete options under the Logging section to enable Storage Logging for Blob service.","multiregional":true,"service":"Azure Storage Accounts"},"ecc-azure-147":{"article":"Disabling public network access improves security by ensuring that Cognitive Services account isn't exposed to the public internet. Creating private endpoints can limit exposure of Cognitive Services account.\nLearn more at: https://go.microsoft.com/fwlink/?linkid=2129800.","impact":"Publicly accessible Cognitive Service are vulnerable to leakage and disclosure of sensitive data.","report_fields":["id"],"remediation":"From Azure Console:\n1. Go to Azure Cognitive Service.\n2. Select an item from the list, and open the 'Networking'\n3. In 'Firewalls and virtual networks' select 'Disabled'\n5. Select Save.\nLearn more about Private Endpoints in https://go.microsoft.com/fwlink/?linkid=2129800.","multiregional":true,"service":"Cognitive Services"},"ecc-azure-302":{"article":"Redis Cache should not allow public access","impact":"Publicly accessible Redis Caches are vulnerable to leakage/disclosure of sensitive data and DDoS attacks","report_fields":["id"],"remediation":"To disable public network access to the Redis Cache follow this article:\nhttps://docs.microsoft.com/en-us/azure/azure-cache-for-redis/cache-private-link","multiregional":true,"service":"Azure Cache for Redis"},"ecc-azure-010":{"article":"Restricting default network access helps to provide a new layer of security, since storage accounts accept connections from clients on any network. To limit access to selected networks, the default action must be changed.","impact":"Access to a storage account can be granted from all clients on any network. It can lead to data loss and compromised access to your resources.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to Storage Accounts\n2. For each storage account, click the settings menu called Firewalls and Virtual networks.\n3. Ensure that you have elected to allow access from Selected networks.\n4. Add rules to allow traffic from specific network.\n5. Click Save to apply your changes.","multiregional":true,"service":"Azure Storage Accounts"},"ecc-azure-326":{"article":"Enabling encryption at rest using a customer-managed key on your Azure Data Explorer cluster provides additional control over the key being used by the encryption at rest. This feature is oftentimes applicable to customers with special compliance requirements and requires a Key Vault to managing the keys.","impact":"Unencrypted Azure Kusto clusters with CMK are vulnerable to data exposure. By default, Azure Kusto cluster is encrypted by Microsoft Managed Key.","report_fields":["id"],"remediation":"Follow this guide to enable Customer Managed Key encryption for Azure Kusto cluster:\nhttps://docs.microsoft.com/en-us/azure/data-explorer/customer-managed-keys-portal","multiregional":true,"service":"Azure Data Explorer"},"ecc-azure-006":{"article":"Enables emailing security alerts to the subscription owner or other designated security contact.","impact":"Enabling security alert emails ensures that these emails are received from Microsoft and that the right people are aware of any potential security issues and can mitigate the risk.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to Microsoft Defender for Cloud\n2. Click on Environment Settings\n3. Click on the appropriate Management Group, Subscription, or Workspace\n4. Click on Email notifications\n5. Under 'Notification types', check the check box next to Notify about alerts with the following severity (or higher): and select High from the drop down menu\n6. Click Save","multiregional":true,"service":"Microsoft Defender for Cloud"},"ecc-azure-132":{"article":"Check a delete lock on a virtual machine to prevent other users from accidentally deleting or modifying it.","impact":"Locks will help to protect virtual machines from accidental or intentional deletion.","report_fields":["id"],"remediation":"From Azure Console: \n1. Go to Virtual Machines \n2. Select Locks on Settings plane. \n3. Click 'Add':\n - Add Lock name\n - Lock type 'Delete' \n - Add 'Note' \n4. Click 'Ok'","multiregional":true,"service":"Virtual Machines"},"ecc-azure-125":{"article":"This policy identifies network security group rules that allow inbound traffic to the MySQL DB port (3306) from public internet. \nAllowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Unrestricted MySQL access can increase opportunities for malicious activities\nsuch as hacking and denial-of-service (DoS) attacks and lead to data loss.","report_fields":["id"],"remediation":"From Azure Console: \n1. Go to Network Security Groups \n2. Create an NSG or select the existing NSG assigned to a network interface or subnet where a VM resides. \n3. On the Settings plane, select Inbound rules \n4. Create Inbound rule:\n - Source 'Any'\n - Source port ranges '*'\n - Destination 'Any'\n - Service 'Custom' \n - Destination port ranges '3306' \n - Protocol 'TCP' \n - Action 'Deny' \n - Priority {define high priority}\n Click Add \n5. Check Network interfaces or Subnets assignments: \n - On the Settings plane, select Network interfaces \n - On the Settings plane, select Subnets","multiregional":true,"service":"Network security groups"},"ecc-azure-286":{"article":"In Kubernetes when you run modern, microservices-based applications, you often want to control which components can communicate with each other. The principle of least privilege should be applied to how traffic can flow between pods in an Azure Kubernetes Service (AKS) cluster. The Network Policy feature in Kubernetes lets you define rules for ingress and egress traffic between pods in a cluster.","impact":"Unlimited and unrestricted network traffic between pods can lead to anomalous activity such as DoS attacks.","report_fields":["id"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\n\nThe network policy feature can only be enabled when the cluster is created. You can't enable network policy on an existing AKS cluster. To create AKS cluster that supports network policy, please refer - https://docs.microsoft.com/en-us/azure/aks/use-network-policies?ocid=AID754288&wt.mc_id=CFID0471#create-an-aks-cluster-and-enable-network-policy","multiregional":true,"service":"Azure Kubernetes Service"},"ecc-azure-052":{"article":"Disable Internet exposed UDP ports on network security groups.","impact":"Publicly exposed UDP access can increase opportunities for malicious activities such as hacking, Man-In-The-Middle attacks (MITM), and brute-force attacks raising the risk of resource compromising.","report_fields":["id"],"remediation":"Disable a direct UDP access to your Azure Virtual Machines from the Internet. After the direct UDP access from the Internet is disabled, you have other options you can use to access UDP based services running on these virtual machines Point-to-site VPN Site-to-site VPN ExpressRoute","multiregional":true,"service":"Network security groups"},"ecc-azure-282":{"article":"To enhance data security, the data stored on the virtual machine (VM) host of your Azure Kubernetes Service nodes VMs should be encrypted at rest. This is a common requirement in many regulatory and industry compliance standards.","impact":"Unencrypted Kubernetes Clusters are vulnerable to eavesdropping and data exposure between all nodes in the cluster.","report_fields":["id"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\n\nTo enable Azure Kubernetes Service encryption, follow this guide:\nhttps://docs.microsoft.com/en-us/azure/virtual-machines/linux/disks-enable-host-based-encryption-cli","multiregional":true,"service":"Azure Kubernetes Service"},"ecc-azure-067":{"article":"Create an activity log alert for the Create or Update Network Security Group Rule event.","impact":"Monitoring for Create or Update Network Security Group Rule events gives insight into network access changes and may reduce the time it takes to detect suspicious activity.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to Monitor\n2. Select Alerts\n3. Click New Alert Rule\n4. Under Scope, click Select resource\n5. Select the appropriate subscription under Filter by subscription\n6. Select Network Security Group Rules under Filter by resource type\n7. Select All for Filter by location\n8. Click on the subscription resource from the entries populated under Resource\n9. Click Done\n10. Verify Selection preview shows Network Security Group Rules and your selected subscription name\n11. Under Condition, click Add Condition\n12. Select the Create or Update Network Security Group Rule signal\n13. Click Done\n14. Under Action group, select Add action groups and complete creation process or select the appropriate action group\n15. Under Alert rule details, enter Alert rule name and Description appropriate resource group to save the alert to\n16. Check Enable alert rule upon creation checkbox\n17. Click Create alert rule","multiregional":true,"service":"Azure Subscription"},"ecc-azure-305":{"article":"Azure Storage sets the minimum TLS version to be version 1.0 by default. TLS 1.0 is a legacy version and has known vulnerabilities. This minimum TLS version can be configured to be later protocols such as TLS 1.2.","impact":"TLS 1.0 is vulnerable to man-in-the-middle attacks, risking the integrity and authentication of data. TLS 1.1 was formally deprecated in March 2021 and no longer treated as secure.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Navigate to your storage account in the Azure portal.\n2. Under Settings, select Configuration.\n3. Under Minimum TLS version, use the drop-down to select the minimum version of TLS required to access data in this storage account.","multiregional":true,"service":"Azure Storage Accounts"},"ecc-azure-168":{"article":"Azure Private Link allows you to connect your virtual network to Azure services without a public IP address at the source or destination.\nThe Private Link platform handles the connectivity between the consumer and services over the Azure backbone network.\nBy mapping private endpoints to your storage account, data leakage risks are reduced.\nLearn more about private links at https://aka.ms/azureprivatelinkoverview","impact":"Unconfigured Private Link with private endpoints enabled can lead to unauthorized access to your Container registry service.","report_fields":["id"],"remediation":"From Azure Console:\n1. Go to Container registry and select the one you want to remediate.\n2. Under Settings, select Private endpoint connections\n3. Click Add and configure the private endpoint.","multiregional":true,"service":"Azure Container Registry"},"ecc-azure-139":{"article":"Identifies volumes that have not had a backup or snapshot in the past 14 days. If a snapshot is not recent, it could be missing crucial patches and software updates. \nIt is recommended to keep backups as recent as you can.","impact":"Without a recently taken snapshot, it is impossible to recover data in the event of failure quickly.","report_fields":["id"],"remediation":"From Azure Console:\n1. Go to Disks\n2. On the Overview board click on the 'Create snapshot'\n3. Fill required fields on the creation blade tabs (Basics, Encryption and Networking)\n4. Click 'Review + create'","multiregional":true,"service":"Azure Disk Storage"},"ecc-azure-179":{"article":"Use a managed identity for enhanced authentication security","impact":"Unauthorized access to your App Service can allow a user or attacker to make unwanted changes\nand expose sensitive information about your API app, which can cause data loss and service degradation.","report_fields":["id"],"remediation":"From Azure Console: \n1. Go to App Service \n2. Under Settings, select Identity\n3. Select ON within the System assigned tab\n4. Click Save","multiregional":true,"service":"App Service"},"ecc-azure-370":{"article":"Private endpoints limit network traffic to approved sources.","impact":"For sensitive data, private endpoints allow granular control of which services can communicate with Cosmos DB and ensure that this network traffic is private. You set this up on a case by case basis for each service you wish to be connected.","report_fields":["id"],"remediation":"From Azure Portal:\n 1. Open the portal menu.\n 2. Select the Azure Cosmos DB blade\n 3. Select the subscription you wish to audit.\n 4. Select the Database you wish to add an endpoint to.\n 5. Then in the portal menu column select the blade 'Private Endpoint Connections'.\n 6. Click '+ Private Endpoint'\n 7. Select which subscription and resource group you want the endpoint to be in. Name it as desired.\n 8. Select which subscription the endpoint will be under, its resource type.\n 9. Select which virtual network desired.\n 10. Select the DNS servers the endpoint will contact.\n 11. Tag as desired and create.\n 12. Back in the Private Endpoints view, select the endpoint and associate it with the Cosmos DB.\n 13. In the listing below confirm that the listed selected networks are set to the appropriate endpoints.","multiregional":true,"service":"Azure Cosmos DB"},"ecc-azure-349":{"article":"Limiting the number of concurrent sessions at the server and per user level helps to reduce the risk of DoS attacks. MySQL provides mechanisms to limit the number of simultaneous connections that can be made at the server level or by any given account.","impact":"Limiting concurrent connections to a MySQL server can be used to reduce risk of Denial of Service (DoS) attacks performed by exhausting connection resources.","report_fields":["id"],"remediation":"1. Login to Azure Portal using https//portal.azure.com\n2. Go to Azure Database for MySQL server\n3. For each database, click Server parameters\n4. Search for max_user_connections\n5. Select appropriate value and Save.","multiregional":true,"service":"Azure Database for MySQL"},"ecc-azure-028":{"article":"Enable log_disconnections on PostgreSQL Servers.","impact":"Enabled 'log_disconnections' affects the logging of each terminated session (with session duration) on the server.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Login to Azure Portal using https//portal.azure.com\n2. Go to Azure Database for PostgreSQL server\n3. For each database, click Server parameters\n4. Search for log_disconnections\n5. Click ON and Save.","multiregional":true,"service":"Azure Database for PostgreSQL"},"ecc-azure-141":{"article":"Azure Security Center has identified that some of your subnets weren't protected with a next-generation firewall.\nTo protect the subnets from potential threats, restrict the access to them\nusing Azure Firewall or a supported next-generation firewall.","impact":"All resources within a network where traffic routing to the firewall and firewall itself is not configured\nare vulnerable to exposure and can be unstable or lost due to malicious activity.","report_fields":["id"],"remediation":"From Azure Console: \n 1. Deploy Firewall in the existing or new virtual network (if required) \n Note: Create a subnet named 'AzureFirewallSubnet'. The size of the subnet should be at least /26. \n Search -> Firewall -> Create \n 2. Create Route tables:\n Home -> Route tables -> Create \n 3. Add routes Create a route with Address prefix '0.0.0.0/0' addresses and Next hop type 'Internet'. \n Also, create routes between the subnets that apply traffic routing.\n For Next hop type, select Virtual appliance. \n 4. Add subnet association to the Route table: \n Route tables -> Route name -> Subnets -> Associate","multiregional":true,"service":"Virtual Network"},"ecc-azure-007":{"article":"Enable security alert emails to subscription owners.","impact":"Disabled high severity email alerts can make risk mitigation impossible.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to Microsoft Defender for Cloud\n2. Click on Environment Settings\n3. Click on the appropriate Management Group, Subscription, or Workspace\n4. Click on Email notifications\n5. In the drop down of the All users with the following roles field select Owner\n6. Click Save","multiregional":true,"service":"Microsoft Defender for Cloud"},"ecc-azure-356":{"article":"API Management allows secure access to the backend service of an API using client certificates.","impact":"Appropriate client certificates should be configured to prevent broken authentication to the backend services.","report_fields":["id"],"remediation":"To configure client certificates to secure backend services follow this guide:\nhttps://docs.microsoft.com/en-us/azure/api-management/api-management-howto-mutual-certificates","multiregional":true,"service":"API Management"},"ecc-azure-346":{"article":"The Transport Layer Security (TLS) protocol secures transmission of data between servers and web browsers, over the Internet, using standard encryption technology. \nTo follow security best practices and the latest PCI compliance standards, enable the latest version of TLS protocol (i.e. TLS 1.2) for all your MySQL servers.","impact":"TLS 1.0 is vulnerable to man-in-the-middle attacks, risking the integrity and authentication of data. TLS 1.1 was formally deprecated in March 2021 and no longer treated as secure.","report_fields":["id"],"remediation":"From Azure Console:\n1. Go to MySQL Servers.\n2. Under the Settings select Connection Security\n3. Under the SSL Settings set Enforce SSL connection to Enabled\n4. Under the TLS Settings set Minimum TLS version to 1.2. Click save.","multiregional":true,"service":"Azure Database for MySQL"},"ecc-azure-267":{"article":"Periodically, newer versions are released for Java software either due to security flaws or to include additional functionality. \nUsing the latest Java version for Function apps is recommended in order to take advantage of security fixes, if any, and/or new functionalities of the latest version. \nCurrently, this policy only applies to Linux web apps.","impact":"An outdated version of Java libraries may contain well-known vulnerabilities such as backdoors or code bugs, which are fixed in the latest version.","report_fields":["id"],"remediation":"From Azure Console: \n1. Go to to Azure App Service \n2. Under the Configuration blade, select the latest Java version","multiregional":true,"service":"App Service"},"ecc-azure-327":{"article":"Azure Data Factory is an ETL service for serverless data integration and data transformation. Azure Data Factory allows you to configure a Git repository with either Azure Repos or GitHub. Git is a version control system that allows for easier change tracking and collaboration.","impact":"Azure Data Factory version control absence can lead to data integrity and availability issues.","report_fields":["id"],"remediation":"From Azure Console:\n1. Go to Data Factory resource\n2. Select Git configuration in the Source control section\n3. Configure repository","multiregional":true,"service":"Azure Data Factory"},"ecc-azure-065":{"article":"Periodically, newer versions are released for HTTP either due to security flaws or to include additional functionality. Use the latest HTTP version for web apps to take advantage of security fixes, if any, and/or new functionalities of the newer version.","impact":"Using old versions of HTTP allows an attacker to execute well-known attacks.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Login to Azure Portal using https//portal.azure.com\n2. Go to App Services\n3. Click each App\n4. Under the Settings section, click Configuration\n5. Set HTTP version to '2.0' under the General settings","multiregional":true,"service":"App Service"},"ecc-azure-099":{"article":"Turning on Azure Defender enables threat detection for Kubernetes, providing threat intelligence, anomaly detection, and behavior analytics in the Azure Security Center.","impact":"Insufficient monitoring of your Kubernetes clusters can lead to a lack of security assessments and threat detection activities.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to Microsoft Defender for Cloud\n2. Select Environment Settings blade\n3. Click on the subscription name\n4. Select the Defender plans blade\n5. On the line in the table for Kubernetes Select On under Plan.\n6. Select Save","multiregional":true,"service":"Microsoft Defender for Cloud"},"ecc-azure-265":{"article":"Audit each SQL Managed Instance which doesn't have recurring vulnerability assessment scans enabled. \nVulnerability assessment can discover, track, and help you remediate potential database vulnerabilities.","impact":"Misconfiguration and well-known SQL vulnerabilities can cause serious problems for managed instances without proper security scans.","report_fields":["id"],"remediation":"From Azure Console: \nNote: Azure Defender for SQL server must be enabled\n1. Go to SQL Managed instances and select the one you want to remediate\n2. Under the Security blade, select Security Center \n3. Select Configure under Azure Defender for SQL \n4. Under the Vulnerability Assessment settings, specify Subscription and Storage account \n5. Select On under Periodic reccuring scans (Also, recommended to specify email to recieve scan reports)\n6. Click Save","multiregional":true,"service":"Azure SQL Managed Instance"},"ecc-azure-345":{"article":"Enable infrastructure encryption for Azure Database for MySQL servers. If Double Encryption is enabled, another layer of encryption is implemented at the hardware level before the storage or network level.\nInformation will be encrypted before it is even accessed, preventing both interception of data in motion if the network layer encryption is broken and data at rest in system resources such as memory or processor cache.","impact":"Unencrypted MySQL instances are vulnerable to leakage and disclosure of sensitive data.","report_fields":["id"],"remediation":"Follow this article to get familiar with double encryption for MySQL Servers:\nhttps://docs.microsoft.com/en-us/azure/mysql/concepts-infrastructure-double-encryption\nFollow this guide to enable Infrastructure double encryption:\nhttps://docs.microsoft.com/en-us/azure/mysql/howto-double-encryption","multiregional":true,"service":"Azure Database for MySQL"},"ecc-azure-237":{"article":"Cross-Origin Resource Sharing (CORS) should not allow all domains to access your Function app. Allow only required domains to interact with your Function app.","impact":"Weak Cross-origin resource sharing mechanism which allows restricted resources on a web page to be requested from another domain outside the domain from which the first resource was served can cause increasing of risk to be attacked with CSRF attack.","report_fields":["id"],"remediation":"From Azure Console:\n1. Navigate to your App Service\n2. Select CORS\n3. Set allowed domains in Allowed Origins\n4. Click Save","multiregional":true,"service":"App Service"},"ecc-azure-196":{"article":"Audit each SQL Managed Instance without advanced data security.","impact":"Insufficient monitoring of your SQL Managed Instances can lead to poor detection of suspicious activities \nand inadequate response to server incidents.","report_fields":["id"],"remediation":"From Azure Console: \n1. Go to SQL Managed instances and select the one you want to remediate \n2. Under the Security blade, select Security Center \n3. Select Enable Azure Defender for SQL servers \n4. Click Save","multiregional":true,"service":"Azure SQL Managed Instance"},"ecc-azure-094":{"article":"Turning on Azure Defender enables threat detection for Server, providing threat intelligence, anomaly detection, and behavior analytics in the Azure Security Center.","impact":"Insufficient monitoring of your Servers' services can increase response time to incidents and make the search of endpoint detection and response (EDR) more difficult.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to Microsoft Defender for Cloud\n2. Select Environment Settings blade\n3. Click on the subscription name\n4. Select the Defender plans blade\n5. On the line in the table for Servers Select On under Plan.\n6. Select Save","multiregional":true,"service":"Microsoft Defender for Cloud"},"ecc-azure-358":{"article":"Creating a workspace with a Managed workspace Virtual Network associated with it ensures that your workspace is network isolated from other workspaces. With a Managed workspace Virtual Network you can offload the burden of managing the Virtual Network to Azure Synapse.","impact":"Managed workspace Virtual Network along with Managed private endpoints protects against data exfiltration.","report_fields":["id"],"remediation":"Note: Managed virtual network for Azure Synapse workspaces can be only configured during resource creation.\nTo configure Managed virtual network for Azure Synapse workspaces follow this article:\nhttps://docs.microsoft.com/en-us/azure/synapse-analytics/security/synapse-workspace-managed-vnet","multiregional":true,"service":"Azure Synapse Analytics"},"ecc-azure-295":{"article":"Configure one Azure Active Directory account, either an individual or Network Security Group account, as an administrator. It is not necessary to configure an Azure AD administrator, but an Azure AD administrator must be configured if you want to use Azure AD accounts to connect to SQL Databases.It is recommended to avoid using names like 'admin' or 'administrator', which are targeted in brute force dictionary attacks.","impact":"Using \"admin\" like names for Admin account increasing the risk of Admin account compromization in some cases","report_fields":["id"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\n\nAzure Console\n1. Go to SQL Servers\n2. For each SQL Server\n3. Select Active Directory admin\n4. Press the Set Admin at the top of the page\n5. Select the active directory user you want to set as AD Admin for the SQL server.\n6. Press the Remove admin if not needed.\nDefault Value - By default no AD Administrator is set for SQL server","multiregional":true,"service":"Azure SQL Database"},"ecc-azure-281":{"article":"Upgrade your Kubernetes service cluster to a later Kubernetes version to protect against known vulnerabilities in your current Kubernetes version. Vulnerability CVE-2019-9946 has been patched in Kubernetes versions 1.11.9+, 1.12.7+, 1.13.5+, and 1.14.0+","impact":"CVE-2019-9946 has a network firewall misconfiguration which relates to CNI portmap plugin. It requires to check Kubernetes configuration if used one of the vulnerable versions.","report_fields":["id"],"remediation":"From Azure console:\n1. Go to Kubernetes services.\n2. Choose Kubernetes cluster.\n3. Go to Cluster configuration.\n4. Upgrade version.\n5. Click Save.","multiregional":true,"service":"Azure Kubernetes Service"},"ecc-azure-357":{"article":"Azure Databricks is a data analytics platform optimized for the Microsoft Azure cloud services platform. Azure Databricks offers three environments for developing data intensive applications: Databricks SQL, Databricks Data Science & Engineering, and Databricks Machine Learning.","impact":"Publicly accessible Azure Databricks are vulnerable to data exposure and unauthorized access","report_fields":["id"],"remediation":"To configure secure cluster connectivity follow Microsoft Docs article:\nhttps://docs.microsoft.com/en-us/azure/databricks/security/secure-cluster-connectivity","multiregional":true,"service":"Azure Databricks"},"ecc-azure-120":{"article":"This policy identifies network security group rules that allow inbound traffic to the DNS port (53) from the public internet.\nAllowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Unrestricted DNS access can increase opportunities for malicious activity \nsuch as Denial of Service (DoS) attacks or Distributed Denial of Service (DDoS) attacks. \nAlso, DNS can be used by attackers as one of their reconnaissance techniques.","report_fields":["id"],"remediation":"From Azure Console: \n1. Go to Network Security Groups \n2. Create an NSG or select the existing NSG assigned to a network interface or subnet where a VM resides. \n3. On the Settings plane, select Inbound rules \n4. Create Inbound rule:\n - Source 'Any'\n - Source port ranges '*'\n - Destination 'Any'\n - Service 'Custom' \n - Destination port ranges '53' \n - Protocol 'Any' \n - Action 'Deny' \n - Priority {define high priority}\n Click Add \n5. Check Network interfaces or Subnets assignments: \n - On the Settings plane, select Network interfaces \n - On the Settings plane, select Subnets","multiregional":true,"service":"Network security groups"},"ecc-azure-325":{"article":"The disk encryption is implemented using either Azure Disk Encryption or encryption at host depending on the SKU of the cluster. The data is encrypted at rest using Microsoft-managed keys.","impact":"Azure Kusto cluster without disk encryption configured can be vulnerable to unauthorized access","report_fields":["id"],"remediation":"Follow this guide to enable disk encryption for Azure Kusto cluster:\nhttps://docs.microsoft.com/en-us/azure/data-explorer/cluster-encryption-disk","multiregional":true,"service":"Azure Data Explorer"},"ecc-azure-317":{"article":"The log_error_verbosity setting specifies the verbosity (amount of detail) of logged\nmessages. Valid values are:\n\u2022 TERSE\n\u2022 DEFAULT\n\u2022 VERBOSE\nwith each containing the fields of the level above it as well as additional fields.\nTERSE excludes the logging of DETAIL, HINT, QUERY, and CONTEXT error information.\nVERBOSE output includes the SQLSTATE, error code, and the source code file name, function name, and line number that generated the error. The appropriate value should be set based on your organization's logging policy","impact":"If the 'log_error_verbosity' parameter is not set to the correct value, too many details or too few details may be logged","report_fields":["id"],"remediation":"From Azure Console:\n1. Go to Azure Database for PostgreSQL single server\n2. Choose your server\n3. Under Settings go to Server Parameters\n4. Set 'log_error_verbosity' to approptiate value (TERSE, DEFAULT, VERBOSE)\n5. Save changes","multiregional":true,"service":"Azure Database for PostgreSQL"},"ecc-azure-112":{"article":"Enable Network Watcher for Azure subscriptions. Network diagnostic and visualization tools available with Network Watcher help users understand, diagnose, and gain insights into the network in Azure.","impact":"Network diagnostic and visualization tools available with Network Watcher help users understand, diagnose, and gain insights into the network in Azure.","report_fields":["id"],"remediation":"Opting-out of Network Watcher automatic enablement is a permanent change. Once you opt-out you cannot opt-in without contacting support.","multiregional":true,"service":"Azure Subscription"},"ecc-azure-005":{"article":"Microsoft Defender for Cloud emails the Subscription Owner to notify them about security alerts. Adding your Security Contact's email address to the 'Additional email addresses' field ensures that your organization's Security Team is included in these alerts. This ensures that the proper people are aware of any potential compromise in order to mitigate the risk in a timely fashion.","impact":"Unconfigured notification of a security event to the right contact can lead to a late response to a threat and give an attacker additional time to exploit your resources.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to Microsoft Defender for Cloud\n2. Click on Environment Settings\n3. Click on the appropriate Management Group, Subscription, or Workspace\n4. Click on Email notifications\n5. Enter a valid security contact email address (or multiple addresses separated by commas) in the Additional email addresses field\n6. Click Save","multiregional":true,"service":"Microsoft Defender for Cloud"},"ecc-azure-167":{"article":"Azure Spring Cloud instances should use a virtual network injection for the following purposes: \n1. Isolate Azure Spring Cloud from Internet. \n2. Enable Azure Spring Cloud to interact with systems in either on-premises data centers or Azure service in other virtual networks. \n3. Empower customers to control inbound and outbound network communications for Azure Spring Cloud.","impact":"Poor management of network connectivity to your Azure Spring Cloud can lead to insufficient ingress/egress traffic control,\nwhich can cause unstable operation of microservices.","report_fields":["id"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\n\nFrom Azure Console: \n1. Create a vnet or select the existing one\n2. Create a service runtime subnet and Spring Boot subnet\n3. Grant service permissions to the vnet (Owner):\n - IAM -> Add role assignments -> Azure Spring Cloud Provider\n4. Deploy Azure Spring instance:\n - Go to Azure Spring Cloud and click Add \n - Provide information about the subnets that you created before","multiregional":true,"service":"Azure Spring Apps"},"ecc-azure-374":{"article":"Create an activity log alert for the Create or Update Public IP Addresses rule","impact":"Monitoring of changes for 'Public IP Address events' can reduce the time it takes to detect unsolicited changes.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to Monitor\n2. Select Alerts\n3. Click New Alert Rule\n4. Under Scope, click Select resource\n5. Select the appropriate subscription under Filter by subscription\n6. Select Policy Assignment under Filter by resource type\n7. Select All for Filter by location\n8. Click on the subscription resource from the entries populated under Resource\n9. Verify Selection preview shows All Policy assignment (policyAssignments) and your selected subscription name\n10. Click Done\n11. Under Condition, click Add Condition\n12. Select the Delete Public IP Address signal\n13. Click Done\n14. Under Action group, select Add action groups and complete creation process or select the appropriate action group\n15. Under Alert rule details, enter Alert rule name and Description\n16. Select the appropriate resource group to save the alert to\n17. Check Enable alert rule upon creation checkbox\n18. Click Create alert rule","multiregional":true,"service":"Azure Subscription"},"ecc-azure-272":{"article":"Audit the existence and health of an endpoint protection solution on your virtual machines scale sets to protect them from threats and vulnerabilities.","impact":"Lack of antimalware extensions on Virtual Machines Scale Sets can lead to instability of virtual machines due to the lack of virus scans in the signature database \nand decreased response to vulnerabilities caused by infected software.","report_fields":["id"],"remediation":"To install an Endpoint protection solution on your Azure VMSS follow this article:\nhttps://techcommunity.microsoft.com/t5/azure-security-center/security-control-enable-endpoint-protection/ba-p/1624653","multiregional":true,"service":"Azure Virtual Machine Scale Sets"},"ecc-azure-022":{"article":"Configure 'Send scan reports to' with email ids of concerned data owners/stakeholders for a critical SQL servers.","impact":"Vulnerability Assessment (VA) scan reports and alerts will be sent to email IDs configured in 'Send scan reports to'.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to SQL servers\n2. Select a server instance\n3. Click Security Center\n4. Ensure that Azure Defender for SQL is set to Enabled\n5. Select Configure next to Enabled at subscription-level\n6. In the Vulnerability Assessment Settings section, configure Storage Accounts unless not already configured\n7. Configure email ids for concerned data owners/stakeholders at Send scan reports to\n8. Click Save","multiregional":true,"service":"Azure SQL Database"},"ecc-azure-124":{"article":"This policy identifies network security group rules that allow inbound traffic to the MongoDB port (27017) from public internet.\nAllowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Unrestricted MongoDB Database access can increase opportunities for malicious activities \nsuch as hacking, denial-of-service (DoS) attacks, and Brute Force attacks and lead to data loss.","report_fields":["id"],"remediation":"From Azure Console: \n1. Go to Network Security Groups \n2. Create an NSG or select the existing NSG assigned to a network interface or subnet where a VM resides. \n3. On the Settings plane, select Inbound rules \n4. Create Inbound rule:\n - Source 'Any'\n - Source port ranges '*'\n - Destination 'Any'\n - Service 'Custom' \n - Destination port ranges '27017,27018' \n - Protocol 'TCP' \n - Action 'Deny' \n - Priority {define high priority}\n Click Add \n5. Check Network interfaces or Subnets assignments: \n - On the Settings plane, select Network interfaces \n - On the Settings plane, select Subnets","multiregional":true,"service":"Network security groups"},"ecc-azure-275":{"article":"Ensure protection of your Azure Virtual Machines by enabling Azure Backup.\nAzure Backup is a secure and cost-effective data protection solution for Azure.","impact":"If a backup for virtual machines is not enabled, there is a risk of data loss after accidental or targeted deletion beyond recovery.","report_fields":["id"],"remediation":"To enable Azure Backup for Virtual Machines, follow this guide:\nhttps://docs.microsoft.com/en-us/azure/backup/backup-azure-arm-vms-prepare","multiregional":true,"service":"Virtual Machines"},"ecc-azure-354":{"article":"By default, access to pull or push content from an Azure container registry is only available to authenticated users. Enabling anonymous (unauthenticated) pull access makes all registry content publicly available for read (pull) actions. Anonymous pull access can be used in scenarios that do not require user authentication such as distributing public container images.","impact":"Anonymous pull access applies to all images in a Container Registry. It is recommended to use default authentication methods (service principals, managed identity, etc.) or repository-scoped tokens to reduce read public access to Container Registry images.","report_fields":["id"],"remediation":"From Azure CLI:\naz acr update --name myregistry --anonymous-pull-enabled false\n\nFor more information use as a reference following article:\nhttps://docs.microsoft.com/en-us/azure/container-registry/anonymous-pull-access","multiregional":true,"service":"Azure Container Registry"},"ecc-azure-095":{"article":"Turning on Azure Defender enables threat detection for App Service, providing threat intelligence, anomaly detection, and behavior analytics in the Azure Security Center.","impact":"Insufficient monitoring of your App Service can lead to a lack of security assessments and threat detection activities.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to Microsoft Defender for Cloud\n2. Select Environment Settings blade\n3. Click on the subscription name\n4. Select the Defender plans blade\n5. On the line in the table for App Service Select On under Plan.\n6. Select `Save","multiregional":true,"service":"Microsoft Defender for Cloud"},"ecc-azure-055":{"article":"Ensure that all keys in the Azure Key Vault have an expiration time set.","impact":"Expired keys can be misused or exposed during their life cycle, which can lead to potential threats to data integrity and confidentiality.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to Key vaults\n2. For each Key vault, click on Keys.\n3. Under the Settings section, make sure Enabled is set to Yes\n4. Set an appropriate EXPIRATION DATE on all keys.","multiregional":true,"service":"Key Vault"},"ecc-azure-036":{"article":"The storage account container containing the activity log export should not be publicly accessible.","impact":"If the activity log storage container is publicly available, an attacker can compromise log records for the entire subscription. This data can tell the attacker who can access your resources, when and where the resource was accessed, etc.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Search for Storage Accounts to access Storage account blade\n2. Click the storage account name\n3. In the Blob Service section, click Containers. It will list all the containers in the next blade\n4. Look for a record with the container named as insight-operational-logs.\n5. Click the right-most column to open Context menu\n6. From Context Menu, click Access Policy and set Public Access Level to Private (no anonymous access)","multiregional":true,"service":"Azure Storage Accounts"},"ecc-azure-340":{"article":"Set your Application Gateway (WAF) to prevent executing such mechanism using the rule definition below.","impact":"Using a vulnerable version of Apache Log4j library might enable attackers to exploit a Lookup mechanism that supports making requests using special syntax in a format string which can potentially lead to a risky code execution and data leakage.","report_fields":["id"],"remediation":"From Azure Console: \n1. Go to Application Gateways\n2. Under the Settings select Web application firewall\n3. On the Rules tab select OWASP 3.0 or above in Rule set field\n - or\n If OWASP 3.0 or above selected, enable 800100 rule under Known-CVEs group.\n4. Click Save.","multiregional":true,"service":"Application Gateway"},"ecc-azure-301":{"article":"Ensure there are no firewall rules allowing unrestricted access to Redis from other Azure sources","impact":"Unauthorized network access to the Redis Cache can lead to DDoS attacks or data leakage","report_fields":["id"],"remediation":"Azure Console:\n1. Go to Redis Cache,\n2. For each Redis Cache:\n3. Select Firewall.\n4. Delete any Rule that has 0.0.0.0 in it's start and end ip address.\n5. Select Save.\nDefault Value:\nNo firewalls rules are set\nReferences:\nhttps://docs.microsoft.com/en-us/azure/redis-cache/cache-configure#firewall","multiregional":true,"service":"Azure Cache for Redis"},"ecc-azure-101":{"article":"Turning on Azure Defender enables threat detection for Key Vault, providing threat intelligence, anomaly detection, and behavior analytics in the Azure Security Center.","impact":"Insufficient monitoring of your KeyVault service can lead to undetected unusual and potentially harmful attempts to access or exploit your keys, secrets, or certificates.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to Microsoft Defender for Cloud\n2. Select Environment Settings blade\n3. Click on the subscription name\n4. Select the Defender plans blade\n5. On the line in the table for Key Vault Select On under Plan.\n6. Select Save","multiregional":true,"service":"Microsoft Defender for Cloud"},"ecc-azure-201":{"article":"Use customer-managed keys to manage the encryption at rest of your Azure Cosmos DB. \nBy default, the data is encrypted at rest with service-managed keys,\nbut customer-managed keys are commonly required to meet regulatory compliance standards. \nCustomer-managed keys enable the data to be encrypted with an Azure Key Vault key created and owned by you. \nYou have full control and responsibility for the key lifecycle, including rotation and management. \nLearn more at https://aka.ms/cosmosdb-cmk.","impact":"Unencrypted CosmosDB accounts with CMK are vulnerable to data exposure.","report_fields":["id"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\n\nFrom Azure Console: \n1. Go to Key Vaults and create a key vault with soft delete and purge protection enabled \n2. Under the Keys generate your own encryption key. \n3. Under the Access polices grant the Azure CosmosDB service permissions to the key vault with get, wrapKey, unwrapKey permissions \n4. Go to CosmosDB accounts and under Data encryption fill the key vault and key information. Click Save.","multiregional":true,"service":"Azure Cosmos DB"},"ecc-azure-064":{"article":"By default, Azure Functions, Web and API Services can be deployed over FTP. If FTP is required for an essential deployment workflow, FTPS should be required for FTP login for all App Service Apps and Functions.","impact":"FTP has a few exploits used by hackers - Anonymous Authentication, Directory Traversal Attack, Cross-Site Scripting (XSS), Dridex-based Malware Attack. Any deployment workflows that rely on FTP or FTPs rather than the WebDeploy or HTTPS endpoints can be affected.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to Azure Portal\n2. Select App Services\n3. Click App\n4. Select Settings > Configuration\n5. Under Platform Settings, FTP state should be Disabled or FTPS Only","multiregional":true,"service":"App Service"},"ecc-azure-339":{"article":"Azure Key Vault is a service for Secrets management to securely store and control access to tokens, passwords, certificates, API keys, and other secrets.\nDifferent secrets have different rotation requirements. Content type tag should be set on secrets.","impact":"A content type tag helps identify whether a secret is a password, connection string, etc.","report_fields":["id"],"remediation":"From Azure Console:\n1. Go to Key Vaults\n2. Under the Settings tab select Secrets\n3. Select secret with current version\n4. Under the Secret section provide content type of your secret\n5. Click Save.","multiregional":true,"service":"Key Vault"},"ecc-azure-116":{"article":"Install the endpoint protection for all virtual machines. Installing endpoint protection systems (like Antimalware for Azure) provides for real-time protection capability that helps identify and remove viruses, spyware, and other malicious software, with configurable alerts when known malicious or unwanted software attempts to install itself or run on Azure systems.","impact":"Lack of antimalware extensions can lead to instability of virtual machines due to the lack of virus scans in the signature database and decreased response to vulnerabilities caused by infected software.","report_fields":["id"],"remediation":"Follow Microsoft Azure documentation to install endpoint protection from the security center. Alternatively, you can employ your own endpoint protection tool for your OS.","multiregional":true,"service":"Virtual Machines"},"ecc-azure-276":{"article":"Azure Database for MariaDB allows you to choose the redundancy option for your database server. \nIt can be set to a geo-redundant backup storage in which the data is not only stored within the region in which your server is hosted,\nbut is also replicated to a paired region to provide recovery option in case of a region failure. \nConfiguring geo-redundant storage for backup is only allowed during server creation.","impact":"Replicating a backup to other regions affects data recovery if the region where the database is located suffers from technical failures\nas a result of unplanned outages or failure of the entire data center.","report_fields":["id"],"remediation":"To configure a Geo-Redundant Backup for your MariaDB instance, follow this guide:\nhttps://docs.microsoft.com/en-us/azure/mariadb/concepts-backup","multiregional":true,"service":"Azure Database for MariaDB"},"ecc-azure-113":{"article":"Migrate BLOB-based VHDs to Managed Disks on Virtual Machines to exploit the default features of this configuration. The features include1. Default Disk Encryption2. Resilience as Microsoft will manage the disk storage and move it around if underlying hardware goes faulty3. Reduction of costs over storage accounts","impact":"VHDs stored in BLOB are vulnerable to malicious exploits if a container is misconfigured such as anonymously accessed and exposed to READ/WRITE operations from the Internet.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Using the search feature, go to Virtual Machines\n2. Select the virtual machine you would like to convert\n3. Select Disks in the menu for the VM\n4. At the top, select Migrate to managed disks\n5. You may follow the prompts to convert the disk and finish by selecting Migrate to start the process","multiregional":true,"service":"Virtual Machines"},"ecc-azure-066":{"article":"Create an activity log alert for the Delete Policy Assignment event.","impact":"Monitoring for Delete Policy Assignment events gives insight into changes done in 'azure policy - assignments' and can reduce the time it takes to detect unsolicited changes.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to Monitor\n2. Select Alerts\n3. Click New Alert Rule\n4. Under Scope, click Select resource\n5. Select the appropriate subscription under Filter by subscription\n6. Select Policy Assignment under Filter by resource type\n7. Select All for Filter by location\n8. Click on the subscription from the entries populated under Resource\n9. Verify Selection preview shows All Policy assignment (policyAssignments) and your selected subscription name\n10. Click Done\n11. Under Condition click Add Condition\n12. Select the Delete policy assignment signal\n13. Click Done\n14. Under Action group, select Add action groups and complete creation process or select the appropriate action group\n15. Under Alert rule details, enter Alert rule name and Description\n16. Select appropriate resource group to save the alert to\n17. Check Enable alert rule upon creation checkbox\n18. Click Create alert rule","multiregional":true,"service":"Azure Subscription"},"ecc-azure-157":{"article":"Disable the public network access property to improve security \nand ensure your Azure Database for MySQL can only be accessed from a private endpoint. \nThis configuration strictly disables access from any public address space outside of Azure IP range \nand denies all logins that match IP or virtual network-based firewall rules.","impact":"Publicly accessible MySQL instances are vulnerable to leakage and disclosure of sensitive data.","report_fields":["id"],"remediation":"From Azure Console:\n1. Go to MySQL and click on the server you want to remediate\n2. Under the Settings blade, select Connection security \n3. Change Deny Public Network Access to Yes","multiregional":true,"service":"Azure Database for MySQL"},"ecc-azure-102":{"article":"This setting enables Windows Defender ATP (WDATP) integration with Security Center. WDATP integration brings comprehensive Endpoint Detection and Response (EDR) capabilities within security center. This integration helps to spot abnormalities, detect and respond to advanced attacks on Windows server endpoints monitored by Azure Security Center. Windows Defender ATP in Security Center supports detection on Windows Server 2016, 2012 R2, and 2008 R2 SP1 operating systems in a Standard service subscription. WDATP works only with Standard Tier subscriptions.","impact":"Ignoring this function may allow a hacker to exploit weaknesses on account of insufficient protection. WDATP integration brings comprehensive Endpoint Detection and Response (EDR) capabilities within the security center. This integration helps to spot abnormalities, detect and respond to advanced attacks on Windows server endpoints monitored by Azure Security Center. Windows Defender ATP in Security Center supports detection on Windows Server 2016, 2012 R2, and 2008 R2 SP1 operating systems in a Standard service subscription.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to Microsoft Defender for Cloud\n2. Select Environment Settings blade\n3. Select Security policy blade\n4. Click On Edit Settings to alter the the security policy for a subscription\n5. Select the Integrations blade\n6. Check/Enable option Allow Microsoft Defender for Endpoint to access my data\n7. Select Save","multiregional":true,"service":"Microsoft Defender for Cloud"},"ecc-azure-032":{"article":"Use Azure Active Directory Authentication for authentication with SQL Database.","impact":"Broken access control allows an attacker to compromise admin access to your database.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to SQL servers\n2. For each SQL server, click Active Directory admin\n3. Click Set admin\n4. Select an admin\n5. Click Save","multiregional":true,"service":"Azure SQL Database"},"ecc-azure-050":{"article":"Ensure that no SQL Databases allow ingress from 0.0.0.0/0 (ANY IP).","impact":"Allowed connection from 0.0.0.0/0 or the Internet to the SQL instance is a severe vulnerability within the perimeter. This misconfiguration can increase the attack surface and lead to DoS attacks as one of the attack options that can degrade the state of the service.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to SQL servers\n2. For each SQL server, click Firewall / Virtual Networks\n4. Set Allow access to Azure services to 'OFF'\n5. Set firewall rules to limit access to only authorized connections","multiregional":true,"service":"Azure SQL Database"},"ecc-azure-213":{"article":"Azure Defender for DNS provides an additional layer of protection for your cloud resources by continuously monitoring all DNS queries from your Azure resources. Azure Defender alerts you about suspicious activity at the DNS layer. Learn more about the capabilities of Azure Defender for DNS at https://aka.ms/defender-for-dns. Enabling this Azure Defender plan results in charges. Learn about the pricing details per region on Security Center's pricing page at https://aka.ms/pricing-security-center.","impact":"Insufficient monitoring of your DNS services can lead to increased response time to various disruptions and downtime of domain services.","report_fields":["id"],"remediation":"From Azure Console: \n1. Go to Security Center \n2. Select the Pricing & settings blade \n3. Click on the subscription name \n4. Select the Azure Defender plans blade\n5. On the line in the table, for DNS, select On under Plan. \n6. Select Save","multiregional":true,"service":"Microsoft Defender for Cloud"},"ecc-azure-200":{"article":"It is important to enable encryption of Automation account variable assets when storing sensitive data","impact":"Unencrypted Automation account variables are vulnerable to disclosure","report_fields":["id"],"remediation":"Note: Variable encryption can only be done at creation time. If you want to encrypt existing variables, they need to be recreated.\nFrom Azure Portal:\n1. Go to Automation Account\n2. Under Shared Resources select Variables\n3. Click on Add a variable\n4. Fill the required information and then select 'Yes' under Encrypted setting\n5. Click Save","multiregional":true,"service":"Automation"},"ecc-azure-289":{"article":"The value that indicates whether the admin user is enabled. Each container registry includes an admin user account, which is disabled by default. \nYou can enable the admin user and manage its credentials in the Azure portal, or by using the Azure CLI or other Azure tools.\nAll users authenticating with the admin account appear as a single user with push and pull access to the registry.\nChanging or disabling this account disables registry access for all users who use its credentials.","impact":"Container registry may be weak to unauthorized access by malicious authorities","report_fields":["id"],"remediation":"From Azure CLI:\naz acr update -n {name} --admin-enabled true","multiregional":true,"service":"Azure Container Registry"},"ecc-azure-225":{"article":"Audit enabling of resource logs. This enables you to recreate activity trails to use for investigation purposes; \nwhen a security incident occurs or when your network is compromised","impact":"Insufficient logging of what operations were taken on a resource affects the effectiveness of incident management.","report_fields":["id"],"remediation":"From Azure Console: \n1. Go to Cognitive Search and select appropriate service\n2. Under Diagnostic settings create new diagnostic settings. \n3. Select one of the options to store the diagnostics logs \n4. Click Save","multiregional":true,"service":"Azure Cognitive Search"},"ecc-azure-241":{"article":"Client certificates allow for the app to request a certificate for incoming requests. \nOnly clients that have a valid certificate will be able to reach the app.","impact":"Weak client certificate validation breaks the authentication of your function app within App Service, \nresulting in unrestricted access for an attacker if the client certificate has been forged.","report_fields":["id"],"remediation":"From Azure Console: \n1. Navigate to your App Service\n2. Select Configuration\n3. Go to the General Settings tab \n4. Set Incoming Client Certificates to Require","multiregional":true,"service":"App Service"},"ecc-azure-206":{"article":"Service Fabric provides three levels of protection (None, Sign and EncryptAndSign) for node-to-node communication using a primary cluster certificate. \nSet the protection level to ensure that all node-to-node messages are encrypted and digitally signed.","impact":"Unencrypted Service Fabric clusters are vulnerable to eavesdropping between all nodes in the cluster.","report_fields":["id"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\n\nFrom Azure Console: \n1. Go to Service Fabric cluster \n2. Click Custom fabric settings and click Add New \n3. Configure the Security section and update the ClusterProtectionLevel property to EncryptAndSign","multiregional":true,"service":"Azure Service Fabric"},"ecc-azure-152":{"article":"Possible network Just In Time (JIT) access will be monitored by Azure Security Center as recommendations","impact":"Unprotected management ports allow an attacker to take remote control of an instance.","report_fields":["id"],"remediation":"From Azure Console:\n1. Go to Security Center \n2. Open the Azure Defender dashboard and from the advanced protection area, select Just-in-time VM access\n3. The Just-in-time VM access page opens with your VMs grouped into the following tabs: \n - Configured \n - Not configured \n - Unsupported \n4. From the Not configured tab, mark the VMs to protect with JIT and select Enable JIT on VMs. \nThe JIT VM access page opens the list of ports recommended for protection by Security Center:\n - 22 \n - 3389 \n5. To accept the default settings, select Save or customize the JIT options","multiregional":true,"service":"Virtual Machines"},"ecc-azure-184":{"article":"Although SSH itself provides an encrypted connection, using passwords with SSH still leaves the VM vulnerable to brute-force attacks.\nThe most secure option for authenticating to an Azure Linux virtual machine over SSH is with a public-private key pair, also known as SSH keys.\nLearn more at https://docs.microsoft.com/azure/virtual-machines/linux/create-ssh-keys-detailed.","impact":"Passwords are vulnerable to exposure, even if properly generated and stored.\nAn SSH key pair is a more secure and easy way to administer authentication on a Linux virtual machine.","report_fields":["id"],"remediation":"To use SSH for authentication to your Linux virtual machine: \n1. Create an SSH key pair for the Linux virtual machine. \n2. Disable password authentication in the Linux virtual machine's configuration. \n3. Update the SSH key in your Azure Resource Manager template (replace the admin password with the adminSSHKey parameter)\nor via the Azure CLI (with the --generate-ssh-keys command). Follow this guide:\nhttps://docs.microsoft.com/en-us/azure/virtual-machines/linux/mac-create-ssh-keys","multiregional":true,"service":"Virtual Machines"},"ecc-azure-329":{"article":"Use customer-managed keys to manage the encryption at rest of your Batch account's data.","impact":"By default, customer data is encrypted with service-managed keys, but customer-managed keys are commonly required to meet regulatory compliance standards. CMK management have full control and responsibility for the key lifecycle, including rotation and management.","report_fields":["id"],"remediation":"To encrypt Batch account with CMK follow this article:\nhttps://docs.microsoft.com/en-us/azure/batch/batch-customer-managed-key","multiregional":true,"service":"Batch"},"ecc-azure-023":{"article":"Enable Vulnerability Assessment (VA) setting 'Also send email notifications to admins and subscription owners'.","impact":"Enabling 'Also send email notifications to admins and subscription owners' will ensure that VA scan reports and alerts are sent to admins and subscription owners. This may help reduce the time required for identifying risks and taking corrective measures.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to SQL servers\n2. Select a server instance\n3. Click Security Center\n4. Select Configure next to Enabled at subscription-level\n5. In the Vulnerability Assessment Settings section, configure Storage Accounts unless not already configured\n6. Check/enable Also send email notifications to admins and subscription owners\n7. Click Save","multiregional":true,"service":"Azure SQL Database"},"ecc-azure-294":{"article":"Availability sets ensure that the VMs you deploy on Azure are distributed across multiple isolated hardware clusters.","impact":"If a hardware or software failure within virtual machine happens, impacted vms are inaccessible unless they reside in availability set.","report_fields":["id"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\n\nTo assign Azure Virtual Machine to an availability set please refer to the following document:\nhttps://docs.microsoft.com/en-us/azure/virtual-machines/windows/manage-availability#configure-multiple-virtual-machines-in-an-availability-set-for-redundancy","multiregional":true,"service":"Virtual Machines"},"ecc-azure-129":{"article":"This policy identifies network security group rules that allow inbound traffic to the PostgreSQL port (5342) from the public internet. \nAllowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Unrestricted access to port 5432 can increase opportunities for malicious activities \nsuch as hacking and denial-of-service (DoS) attacks and lead to data loss.","report_fields":["id"],"remediation":"From Azure Console: \n1. Go to Network Security Groups \n2. Create an NSG or select the existing NSG assigned to a network interface or subnet where a VM resides. \n3. On the Settings plane, select Inbound rules \n4. Create Inbound rule:\n - Source 'Any'\n - Source port ranges '*'\n - Destination 'Any'\n - Service 'Custom' \n - Destination port ranges '5342' \n - Protocol 'TCP' \n - Action 'Deny' \n - Priority {define high priority}\n Click Add \n5. Check Network interfaces or Subnets assignments: \n - On the Settings plane, select Network interfaces \n - On the Settings plane, select Subnets","multiregional":true,"service":"Network security groups"},"ecc-azure-105":{"article":"Regenerate storage account access keys periodically. When a storage account is created, Azure generates two 512-bit storage access keys. These keys are used for authentication when the storage account is accessed. Rotating these keys periodically ensures that any inadvertent access or exposure does not result in these keys being compromised.","impact":"Old access keys are vulnerable to attacker disclosure, which could lead to data leakage and ultimately loss of access to the storage account.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to Storage Accounts\n2. Under Security+Networking select Access Keys\n3. Click 'Rotate key'","multiregional":true,"service":"Azure Storage Accounts"},"ecc-azure-332":{"article":"requestTracingEnabled should be set to 'true'","impact":"Lack of failed request tracing logs may cause difficulties in the incident response process.","report_fields":["id"],"remediation":"From Azure Console\n1. Login to Azure Portal using https//portal.azure.com\n2. Go to App Services\n3. Click on each App\n4. Under the Settings section, click on Log\n5. Set requestTracing to appropriate value","multiregional":true,"service":"App Service"},"ecc-azure-058":{"article":"Ensure that RBAC is enabled on all Azure Kubernetes Services Instances","impact":"Without configured role-based access controls, an attacker can get the elevation of privileges.","report_fields":["id"],"remediation":"WARNING This setting cannot be changed after AKS deployment, the cluster will require recreation.","multiregional":true,"service":"Azure Kubernetes Service"},"ecc-azure-379":{"article":"Enable AppServiceHTTPLogs diagnostic log category for Azure App Service instances to ensure all http requests are captured and centrally logged.","impact":"Capturing web requests can be important supporting information for security analysts performing monitoring and incident response activities.","report_fields":["id"],"remediation":"From Azure Portal: \n1. Go to App Service and select appropriate service \n2. Under Diagnostic settings create new diagnostic settings. \n3. Select HTTP Logs to store the diagnostics logs (Storage Account, Log Workpsace, etc.)\n4. Click Save","multiregional":true,"service":"App Service"},"ecc-azure-202":{"article":"Manage encryption at rest of Azure Machine Learning workspace data with customer-managed keys.\nBy default, customer data is encrypted with service-managed keys, but customer-managed keys are commonly required to meet regulatory compliance standards. \nCustomer-managed keys enable the data to be encrypted with an Azure Key Vault key created and owned by you. \nYou have full control and responsibility for the key lifecycle, including rotation and management.\nLearn more at https://aka.ms/azureml-workspaces-cmk.","impact":"Unencrypted Machine Learning workspace instances with CMK are vulnerable to data exposure.","report_fields":["id"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\n\nTo encrypt Azure Machine Learning Workspaces with customer-managed-key follow this article (CLI):\nhttps://docs.microsoft.com/en-us/azure/machine-learning/how-to-manage-workspace-cli?tabs=createnewresources%2Cvnetpleconfigurationsv2cli#customer-managed-key-and-high-business-impact-workspace","multiregional":true,"service":"Azure Machine Learning"},"ecc-azure-350":{"article":"MySQL can operate using a variety of log files, each used for different purposes. These are the binary log (which can be encrypted), error log, slow query log, relay log, general log, and in the enterprise edition, the audit log (which can be encrypted).","impact":"The slow query log can be used to find queries that take a long time to execute and are therefore candidates for optimization.","report_fields":["id"],"remediation":"1. Login to Azure Portal using https//portal.azure.com\n2. Go to Azure Database for MySQL server\n3. For each database, click Server parameters\n4. Search for slow_query_log\n5. Select ON and Save.","multiregional":true,"service":"Azure Database for MySQL"},"ecc-azure-355":{"article":"Azure Machine Learning compute cluster is a managed-compute infrastructure that allows you to easily create a single or multi-node compute. The compute cluster is a resource that can be shared with other users in your workspace.","impact":"To avoid charges when no jobs are running, set the minimum nodes to 0. This setting allows Azure Machine Learning to de-allocate the nodes when they aren't in use. Any value larger than 0 will keep that number of nodes running, even if they are not in use.","report_fields":["id"],"remediation":"To configure Compute cluster nodes follow this article:\nhttps://docs.microsoft.com/en-us/azure/machine-learning/how-to-create-attach-compute-cluster?tabs=python","multiregional":true,"service":"Azure Machine Learning"},"ecc-azure-278":{"article":"Azure Database for PoatgreSQL allows you to choose the redundancy option for your database server. \nIt can be set to a geo-redundant backup storage in which the data is not only stored within the region in which your server is hosted,\nbut is also replicated to a paired region to provide recovery option in case of a region failure. \nConfiguring geo-redundant storage for backup is only allowed during server creation.","impact":"Replicating a backup to other regions affects data recovery if the region where the database is located suffers from technical failures \nas a result of unplanned outages or failure of the entire data center.","report_fields":["id"],"remediation":"To configure a Geo-Redundant Backup for your PostgreSQL instance, follow this guide:\nhttps://docs.microsoft.com/en-us/azure/postgresql/concepts-backup","multiregional":true,"service":"Azure Database for PostgreSQL"},"ecc-azure-378":{"article":"Network Security Group Flow Log Analytics should be enabled.","impact":"Traffic analytics is a cloud-based solution that provides visibility into user and application activity in a cloud networks. Specifically, traffic analytics analyzes Azure Network Watcher network security group (NSG) flow logs to provide insights into traffic flow.","report_fields":["id"],"remediation":"From Azure Portal:\nNote: Log Analytics workspace and Storage Account are needed for enabling Traffic Analytics\n1. Go to NSG\n2. Select NSG flow logs blade in the Logs section\n3. Select each Network Security Group from the list\n4. Ensure Status is set to On\n5. Select your storage account in the Storage account field\n6. On a Configure tab select Enable Traffic Analytics\n7. Select your workspace in the Log Analytics workspace field\n8. Select Save","multiregional":true,"service":"Network security groups"},"ecc-azure-362":{"article":"Defender for Cloud collects data from your machines using agents and extensions. To save you the process of manually installing the extensions, such as the manual installation of the Log Analytics agent, Defender for Cloud reduces management overhead by installing all required extensions on existing and new machines.","impact":"Onboarded vulnerability assessment for virtual machines reduce the risk of exposure and simplify threat management.","report_fields":["id"],"remediation":"To configure Auto-deploy of Vulnerability Assessment for Virtual Machines follow this articles:\nhttps://learn.microsoft.com/en-us/azure/defender-for-cloud/auto-deploy-vulnerability-assessment","multiregional":true,"service":"Microsoft Defender for Cloud"},"ecc-azure-334":{"article":"In 2019, Microsoft added a feature called Jupyter Notebook to Cosmos DB that lets customers visualize their data and create customized views. The feature was automatically turned on for all Cosmos DBs in February 2021. A series of misconfigurations in the notebook feature opened up a new attack vector - the notebook container allowed for a privilege escalation into other customer notebooks. As a result, an attacker could gain access to customers' Cosmos DB primary keys and other highly sensitive secrets such as the notebook blob storage access token.\nOne way to reduce risk is to prevent management plane changes for clients using key based authentication. CosmosDB access keys are mainly used by applications to access data in CosmosDB containers. It is rare for organizations to have use cases where the keys are used to make management changes.","impact":"Priveleges escalation can give an attacker access to the sensitive information contains in CosmosDB account","report_fields":["id"],"remediation":"For more details visit - https://msrc-blog.microsoft.com/2021/08/27/update-on-vulnerability-in-the-azure-cosmos-db-jupyter-notebook-feature/","multiregional":true,"service":"Azure Cosmos DB"},"ecc-azure-142":{"article":"Azure Security Center has identified some of your network security groups inbound rules to be too permissive. \nInbound rules should not allow access from 'Any' or 'Internet' ranges. \nThis can potentially enable attackers to target your resources.","impact":"Opened network ports on the network security group level can increase opportunities for malicious activities\nsuch as denial-of-service (DoS) attacks. This is a serious network security breach. \nUse VM-JIT-access as a way to access your virtual machines securely.","report_fields":["id"],"remediation":"From Azure Console:\n1. Go to Network Security Groups \n2. Create an NSG or select the existing NSG assigned to a network interface or subnet where a VM resides. \n3. On the Settings plane, select Inbound rules\n4. Create Inbound rule:\n - Source 'Any' \n - Source port ranges '*' \n - Destination 'Any' \n - Service 'Custom' \n - Destination port ranges '*' \n - Protocol 'Any' \n - Action 'Deny' \n - Priority {define high priority} \nClick Add \nNote: You can create your custom rules depending on the resources you have. \nInbound rules should not allow access from 'Any' or 'Internet' ranges. \n4. Check Network interfaces or Subnets assignments: \n - On the Settings plane, select Network interfaces \n - On the Settings plane, select Subnets","multiregional":true,"service":"Network security groups"},"ecc-azure-306":{"article":"Enable encryption at rest for PostgreSQL Databases. If Double Encryption is enabled, another layer of encryption is implemented at the hardware level before the storage or network level. Information will be encrypted before it is even accessed, preventing both interception of data in motion if the network layer encryption is broken and data at rest in system resources such as memory or processor cache.","impact":"Unencrypted PostgreSQL instances are vulnerable to leakage and disclosure of sensitive data.","report_fields":["id"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\n\nFrom Azure Console:\n1. Select Databases > Azure Database for PostgreSQL. You can also enter PostgreSQL in the search box to find the service. Enabled the Single server deployment option.\n2. Provide the basic information of the server. Select Additional settings and enabled the Infrastructure double encryption checkbox to set the parameter.\n3. Select Review + create to provision the server.\n4. Once the server is created you can validate the infrastructure double encryption by checking the status in the Data encryption server blade.","multiregional":true,"service":"Azure Database for PostgreSQL"},"ecc-azure-070":{"article":"Periodically, newer versions are released for Python software either due to security flaws or to include additional functionality.\nUsing the latest Python version for web apps is recommended in order to take advantage of security fixes, if any, and/or additional functionalities of the newer version.","impact":"Using old versions of TLS allows an attacker to execute well-known attacks.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Login to Azure Portal using https//portal.azure.com\n2. Go to App Services\n3. Click on each App\n4. Under the Settings section, click on Application settings\n5. Set Python version to the latest version available under General settings","multiregional":true,"service":"App Service"},"ecc-azure-181":{"article":"Use a managed identity for enhanced authentication security","impact":"Unauthorized access to your App Service can allow a user or attacker to make unwanted changes and expose sensitive information about your web app, which can cause data loss and service degradation.","report_fields":["id"],"remediation":"From Azure Portal: \n1. Go to App Service \n2. Under Settings, select Identity\n3. Select ON within the System assigned tab \n4. Click Save","multiregional":true,"service":"App Service"},"ecc-azure-359":{"article":"Azure Synapse Analytics workspaces support enabling data exfiltration protection for workspaces. With exfiltration protection, you can guard against malicious insiders accessing your Azure resources and exfiltrating sensitive data to locations outside of your organization's scope.","impact":"Azure Synapse workspace may be vulnerable to data exfiltration.","report_fields":["id"],"remediation":"Note: Data Exfiltration protection may be enabled along with the Managed virtual network for Azure Synapse workspaces and can be only configured during resource creation.\nTo configure Data Exfiltration protection for Azure Synapse workspaces follow this articles:\nhttps://docs.microsoft.com/en-us/azure/synapse-analytics/security/synapse-workspace-managed-vnet\nhttps://docs.microsoft.com/en-us/azure/synapse-analytics/security/workspace-data-exfiltration-protection","multiregional":true,"service":"Azure Synapse Analytics"},"ecc-azure-351":{"article":"When data changing statements are made (i.e., INSERT, UPDATE), MySQL can handle invalid or missing values differently depending on whether strict SQL mode is enabled. When strict SQL mode is enabled, data may not be truncated or otherwise \"adjusted\" to make the data changing statement work.","impact":"Without strict mode the server tries to proceed with the action when an error might have been a more secure choice.","report_fields":["id"],"remediation":"1. Login to Azure Portal using https//portal.azure.com\n2. Go to Azure Database for MySQL server\n3. For each database, click Server parameters\n4. Search for sql_mode\n5. Add \"STRICT_ALL_TABLES\" checkmark and Save.","multiregional":true,"service":"Azure Database for MySQL"},"ecc-azure-231":{"article":"This policy audits any Windows/Linux virtual machines (VMs) if the Log Analytics agent is not installed which Security Center uses to monitor for security vulnerabilities and threats","impact":"Missing Log Analytics agent can lead to insufficient integration with Log Analytics Workspace or SIEM, such as Azure Sentinel, if the virtual machines is in the incident management scope","report_fields":["id"],"remediation":"To install Log Analytics Agent follow this article:\nFor Windows VM - https://docs.microsoft.com/en-us/azure/virtual-machines/extensions/oms-windows?toc=/azure/azure-monitor/toc.json\nFor Linux VM - https://docs.microsoft.com/en-us/azure/virtual-machines/extensions/oms-linux?toc=/azure/azure-monitor/toc.json","multiregional":true,"service":"Virtual Machines"},"ecc-azure-337":{"article":"This policy audits any Windows virtual machine not configured with automatic update of Microsoft Antimalware protection signatures.","impact":"Azure will handle all signatures updates for Microsoft Antimalware extension.","report_fields":["id"],"remediation":"Follow Microsoft Azure documentation to install endpoint protection from the security center and configure auto updates.","multiregional":true,"service":"Virtual Machines"},"ecc-azure-164":{"article":"Azure Private Link allows you to connect your virtual network to Azure services without a public IP address at the source or destination.\nThe Private Link platform handles the connectivity between the consumer and services over the Azure backbone network. \nBy mapping private endpoints to your Event Grid topic instead of the entire service, you'll also be protected against data leakage risks.\nLearn more at: https://aka.ms/privateendpoints.","impact":"Unconfigured Private Link with private endpoints enabled can lead to unauthorized access to your Event Grid topics.","report_fields":["id"],"remediation":"From Azure Console:\n1. Go to Event Grid topics and select the one you want to remediate\n2. Under Settings, select Private endpoint connections \n3. Click Add and configure the private endpoint","multiregional":true,"service":"Event Grid"},"ecc-azure-155":{"article":"Disabling the public network access property improves security by ensuring your Azure SQL Database can only be accessed from a private endpoint. \nThis configuration denies all logins that match IP or virtual network-based firewall rules.","impact":"Publicly accessible SQL instances are vulnerable to leakage and disclosure of sensitive data.","report_fields":["id"],"remediation":"From Azure Console: \n1. Go to Azure SQL Database and click on the SQL server you want to remediate \n2. Under the Settings blade, select Connection security\n3. Change Deny Public Network Access to Yes","multiregional":true,"service":"Azure SQL Database"},"ecc-azure-061":{"article":"The TLS (Transport Layer Security) protocol secures transmission of data over the internet using standard encryption technology. Encryption should be set with the latest version of TLS. App service allows TLS 1.2 by default, which is the recommended TLS level by industry standards, such as PCI DSS.","impact":"Using old versions of TLS allows an attacker to exploit well-known attacks and vulnerabilities - Padding Oracle On Downgraded Legacy Encryption (POODLE), Browser Exploit Against SSL/TLS (BEAST), Compression Ratio Info-leak Made Easy (CRIME), Browser Reconnaissance and Exfiltration via Adaptive Compression of Hypertext (BREACH), etc.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Login to Azure Portal using https//portal.azure.com\n2. Go to App Services\n3. Click each App\n4. Under the Settings section, click on SSL settings\n5. Set Minimum TLS Version to '1.2' under the Protocol Settings section","multiregional":true,"service":"App Service"},"ecc-azure-004":{"article":"Enable automatic provisioning of the monitoring agent to collect security data.","impact":"Microsoft Monitoring Agent scans for various security-related configurations and events such as system updates, OS vulnerabilities, endpoint protection, and provides alerts.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to Microsoft Defender for Cloud\n2. Click on Environment Settings\n3. Click on a subscription\n4. Click on Auto Provisioning in the left column.\n5. Ensure that Log Analytics agent for Azure VMs is set to On","multiregional":true,"service":"Microsoft Defender for Cloud"},"ecc-azure-130":{"article":"This policy identifies network security group rules that allow inbound traffic to the SMTP port (25) from the public internet.\nAllowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Unrestricted access to port 25 can increase opportunities for malicious activities \nsuch as hacking and denial-of-service (DoS) attacks and lead to data loss.","report_fields":["id"],"remediation":"From Azure Console: \n1. Go to Network Security Groups \n2. Create an NSG or select the existing NSG assigned to a network interface or subnet where a VM resides. \n3. On the Settings plane, select Inbound rules \n4. Create Inbound rule:\n - Source 'Any'\n - Source port ranges '*'\n - Destination 'Any'\n - Service 'Custom' \n - Destination port ranges '25' \n - Protocol 'TCP' \n - Action 'Deny' \n - Priority {define high priority}\n Click Add \n5. Check Network interfaces or Subnets assignments: \n - On the Settings plane, select Network interfaces \n - On the Settings plane, select Subnets","multiregional":true,"service":"Network security groups"},"ecc-azure-373":{"article":"Create an activity log alert for the Create or Update Public IP Addresses rule.","impact":"Monitoring of changes for 'Public IP Address events' can reduce the time it takes to detect unsolicited changes.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to Monitor\n2. Select Alerts\n3. Click New Alert Rule\n4. Under Scope, click Select resource\n5. Select the appropriate subscription under Filter by subscription\n6. Select Policy Assignment under Filter by resource type\n7. Select All for Filter by location\n8. Click on the subscription resource from the entries populated under Resource\n9. Verify Selection preview shows All Policy assignment (policyAssignments) and your selected subscription name\n10. Click Done\n11. Under Condition, click Add Condition\n12. Select the Create or Update Public IP Address signal\n13. Click Done\n14. Under Action group, select Add action groups and complete creation process or select the appropriate action group\n15. Under Alert rule details, enter Alert rule name and Description\n16. Select the appropriate resource group to save the alert to\n17. Check Enable alert rule upon creation checkbox\n18. Click Create alert rule","multiregional":true,"service":"Azure Subscription"},"ecc-azure-238":{"article":"Cross-Origin Resource Sharing (CORS) should not allow all domains to access your Web app. Allow only required domains to interact with your Web app.","impact":"Weak Cross-origin resource sharing mechanism which allows restricted resources on a web page to be requested from another domain outside the domain from which the first resource was served can cause increasing of risk to be attacked with CSRF attack.","report_fields":["id"],"remediation":"From Azure Console:\n1. Navigate to your App Service\n2. Select CORS\n3. Set allowed domains in Allowed Origins\n4. Click Save","multiregional":true,"service":"App Service"},"ecc-azure-002":{"article":"Classic subscription admin roles offer basic access management and include Account Administrator, Service Administrator, and Co-Administrators. It is recommended the least necessary permissions be given initially. Permissions can be added as needed by the account holder. This ensures the account holder cannot perform actions which were not intended.","impact":"Compromised users assigned a custom role with 'Owner' permissions can cause significant harm to cloud resources by escalating and changing privileges within the entire subscription scope.","report_fields":["id"],"remediation":"Using Azure CLI:\n1. az role definition list \n2. Check for entries with assignableScope of / or a subscription, and an action of * \n3. Verify the usage and impact of removing the role identified\n4. az role definition delete --name \"rolename\" \nBy default, no custom owner roles are created.","multiregional":true,"service":"Azure RBAC"},"ecc-azure-199":{"article":"Audit enabling of only connections via SSL to Azure Cache for Redis. \nUse of secure connections ensures authentication between the server and the service\nand protects data in transit from network layer attacks such as man-in-the-middle, eavesdropping, and session-hijacking","impact":"Unencrypted connection to your Redis Cache increases the possibility of MITM attacks,\nwhich means the data transmitted to your cache can be easily read and intercepted by an attacker.","report_fields":["id"],"remediation":"From Azure Console:\n1. Go to Azure Cache for Redis \n2. Under Settings, select Advanced settings \n3. For Allow access via SSL, select Yes \n4. Click Save","multiregional":true,"service":"Azure Cache for Redis"},"ecc-azure-298":{"article":"Application Service Logging gathers STDOUT (commands normal output) and STDERR (error messages) output from the container. The output logs are saved via FTP/FTPS and can be viewed at the configured endpoint.","impact":"Lack of logging can make it harder to find a problem when it occurs","report_fields":["id"],"remediation":"Using Azure Portal:\n1. Go to `Function App` service.\n2. Choose your containerized function app.\n3. Under `Monitoring` go to `App service logs`.\n4. Make sure to Enable this feature.","multiregional":true,"service":"App Service"},"ecc-azure-046":{"article":"Create an activity log alert for the Create or Update or Delete SQL Server Firewall Rule event.","impact":"Monitoring for 'Create' or 'Update' or 'Delete SQL Server Firewall Rule' events gives insight into network access changes and may reduce the time it takes to detect suspicious activity.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to Monitor\n2. Select Alerts\n3. Click New Alert Rule\n4. Under Scope, click Select resource\n5. Select the appropriate subscription under Filter by subscription\n6. Select SQL servers under Filter by resource type\n7. Select All for Filter by location\n8. Click on the subscription from the entries populated under Resource\n9. Verify Selection preview shows SQL servers and your selected subscription name\n10. Under Condition click Add Condition\n11. Select the All Administrative operations signal\n12. Click Done\n13. Under Action group, select Add action groups and complete creation process or select the appropriate action group\n14. Under Alert rule details, enter the Alert rule name and Description\n15. Select the appropriate resource group to save the alert to\n16. Check Enable alert rule upon creation checkbox\n17. Click Create alert rule","multiregional":true,"service":"Azure Subscription"},"ecc-azure-217":{"article":"Audit enabling of resource logs. This enables you to recreate activity trails to use for investigation purposes; \nwhen a security incident occurs or when your network is compromised.","impact":"Insufficient logging of what operations were taken on a resource affects the effectiveness of incident management.","report_fields":["id"],"remediation":"From Azure Console: \n1. Go to Data Lake Storage and select appropriate service \n2. Under Diagnostic settings create new diagnostic settings. \n3. Select one of the options to store the diagnostics logs \n4. Click Save","multiregional":true,"service":"Azure Data Lake Storage"},"ecc-azure-291":{"article":"Identify Storage Accounts outside of the following regions: northeurope, westeurope","impact":"GDPR requires Europe only regions (supported by Azure).","report_fields":["id"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\n\nPlease refer to Azure documentations about Storage Accounts:\nhttps://docs.microsoft.com/en-us/azure/storage/common/storage-create-storage-account\nand\nAzure documentations about Regions:\nhttps://azure.microsoft.com/en-us/global-infrastructure/regions/#services","multiregional":true,"service":"Azure Storage Accounts"},"ecc-azure-049":{"article":"Disable an SSH access on network security groups from the Internet.","impact":"Publicly exposed SSH access can increase opportunities for malicious activities such as hacking, Man-In-The-Middle attacks (MITM), and brute-force attacks raising the risk of resource compromising.","report_fields":["id"],"remediation":"Disable a direct SSH access to your Azure Virtual Machines from the Internet. After the direct SSH access from the Internet is disabled, you have other options you can use to access these virtual machines for remote management - Point-to-site VPN - Site-to-site VPN - ExpressRoute","multiregional":true,"service":"Network security groups"},"ecc-azure-313":{"article":"The log_min_messages setting the message levels that are written to the server log. Each level includes all the levels that follow it. The lower the level (vertically, below), the fewer messages are sent.","impact":"If this is not set to the correct value, too many messages or too few messages may be written to the server log","report_fields":["id"],"remediation":"From Azure Console:\n1. Go to Azure Database for PostgreSQL single server\n2. Choose your server\n3. Under Settings go to Server Parameters\n4. Set 'log_min_error_statement' to approptiate value (DEBUG5, DEBUG4, DEBUG3, DEBUG2, DEBUG1, INFO, NOTICE, WARNING, ERROR, LOG, FATAL, PANIC)\n5. Save changes","multiregional":true,"service":"Azure Database for PostgreSQL"},"ecc-azure-204":{"article":"Customer-managed keys are commonly required to meet regulatory compliance standards. Customer-managed keys enable the data stored in Cognitive Services to be encrypted with an Azure Key Vault key created and owned by you. You have full control and responsibility for the key lifecycle, including rotation and management. Learn more about customer-managed keys at https://go.microsoft.com/fwlink/?linkid=2121321.","impact":"Unencrypted Cognitive Services accounts with CMK are vulnerable to data exposure.","report_fields":["id"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nNote: For some cognitive services, you need to contact Microsoft support to enable CMK encryption at the subscription level.\n\nMore information available at: https://docs.microsoft.com/en-us/azure/cognitive-services/encryption/cognitive-services-encryption-keys-portal\nFrom Azure Console:\n1. Create a key vault with soft delete and purge protection enabled and key to encrypt Cognitive Service account\nNote: You need managed identity precreated for Cognitive account to setup proper Access Policy\n2. Go to Cognitive service -> All Cognitive Services and select the appropriate servuce\n3. Under Resource Management select Encryption\n4. Select Customer Managed Keys and fill data about encryption key\n5. Click Save.","multiregional":true,"service":"Cognitive Services"},"ecc-azure-279":{"article":"Disabling local authentication methods improves security by ensuring that Azure Kubernetes Service Clusters should exclusively require Azure Active Directory identities for authentication. \nLearn more at: https://aka.ms/aks-disable-local-accounts.","impact":"Anyone can authenticate using local account to Kubernetes cluster and also local accounts require more administrative effort to ensure secure auth.","report_fields":["id"],"remediation":"To disable local accounts for AKS, follow this guide:\nhttps://docs.microsoft.com/en-us/azure/aks/managed-aad","multiregional":true,"service":"Azure Kubernetes Service"},"ecc-azure-235":{"article":"Azure Policy Add-on for Kubernetes service (AKS) extends Gatekeeper v3, an admission controller webhook for Open Policy Agent (OPA), \nto apply at-scale enforcements and safeguards on your clusters in a centralized, consistent manner.","impact":"To improve the security of your Azure Kubernetes Service (AKS) cluster, you can apply and enforce built-in security policies on your cluster using Azure Policy.","report_fields":["id"],"remediation":"To install Azure Policy Add-on for AKS, follow this guide:\nhttps://docs.microsoft.com/en-us/azure/governance/policy/concepts/policy-for-kubernetes?WT.mc_id=Portal-Microsoft_Azure_Security#install-azure-policy-add-on-for-aks","multiregional":true,"service":"Azure Kubernetes Service"},"ecc-azure-178":{"article":"Deploy Azure Web Application Firewall (WAF) in front of public facing web applications for additional inspection of incoming traffic.\nWeb Application Firewall (WAF) provides centralized protection of your web applications from common exploits\nand vulnerabilities such as SQL injections, Cross-Site Scripting, local and remote file executions. \nYou can also restrict access to your web applications by countries IP address ranges, \nand other http(s) parameters via custom rules.","impact":"Unrestricted and unfiltered network access to the Front door service can lead to DDoS attacks or data leakage, \nresulting in unstable operation of cloud resources.","report_fields":["id"],"remediation":"From Azure Console: \n1. Go to Web Application Firewall policies (WAF) \n2. Fill the Basics tab and configure: \n - Policy for Global WAF (Front Door) \n - Policy state Enabled \n - Customize Azure Web Application Firewall as required\n3. Select Front Door in the search bar and then select the service that does not have Azure Web Application Firewall. \n4. From the left sidebar, select Web application firewall. \n5. Select the frontend to which you're adding the Azure Web Application Firewall policy. Select Apply policy \n6. From the dropdown, select Azure Web Application Firewall policy. Select Add \n7. Click Save","multiregional":true,"service":"Azure Front Door"},"ecc-azure-172":{"article":"Private endpoint connections enforce secure communication by enabling private connectivity to Azure Database for MySQL. \nConfigure a private endpoint connection to enable access to traffic coming only from known networks \nand prevent access from all other IP addresses, including within Azure.","impact":"Unconfigured Private Link with private endpoints enabled can lead to unauthorized access to your MySQL service.","report_fields":["id"],"remediation":"From Azure Console: \n1. Go to MySQL and select the instance you want to remediate\n2. Under Settings, select Private endpoint connections \n3. Click Add and configure the private endpoint","multiregional":true,"service":"Azure Database for MySQL"},"ecc-azure-226":{"article":"Audit enabling of resource logs. This enables you to recreate activity trails to use for investigation purposes; \nwhen a security incident occurs or when your network is compromised","impact":"Insufficient logging of what operations were taken on a resource affects the effectiveness of incident management.","report_fields":["id"],"remediation":"From Azure Console: \n1. Go to Service Bus and select appropriate service\n2. Under Diagnostic settings create new diagnostic settings. \n3. Select one of the options to store the diagnostics logs \n4. Click Save","multiregional":true,"service":"Service Bus"},"ecc-azure-324":{"article":"Enabling double encryption helps protect and safeguard your data to meet your organizational security and compliance commitments.","impact":"When double encryption has been disabled, data in the storage account is unencrypted which can lead to data exposure.","report_fields":["id"],"remediation":"Follow this guide to enable double encryption for Azure Kusto cluster:\nhttps://docs.microsoft.com/en-us/azure/data-explorer/cluster-encryption-double","multiregional":true,"service":"Azure Data Explorer"},"ecc-azure-143":{"article":"Azure Virtual Network deployment provides enhanced security, isolation and allows you to place your API Management service in a non-internet routable network that you control access to. \nThese networks can then be connected to your on-premises networks using various VPN technologies. \nIt enables access to your backend services within the network and/or on-premises. \nThe developer portal and API gateway can be configured to be accessible either from the Internet or only within the virtual network.","impact":"API management service can be accessed directly from the internet if the virtual network type is not configured with 'external' or 'internal' mode. \nUnsecured publicly accessible API services can be vulnerable to attacks such as injection of malicious code, sensitive data exposure, parameter tampering, etc.","report_fields":["id"],"remediation":"From Azure Console:\n1. Go to API Management services and select your service\n2. Under the Properties blade, select Virtual Network\n3. Under the Virtual network blade, select 'External' or 'Internal' (depends on your needs) and specify:\n - Virtual Network \n - Subnet \n - Management public IP address\n4. Click Save","multiregional":true,"service":"API Management"},"ecc-azure-280":{"article":"Enable the private cluster feature for your Azure Kubernetes Service cluster to ensure network traffic between your API server and your node pools remains on the private network only. This is a common requirement in many regulatory and industry compliance standards.","impact":"Publicly exposed network traffic between API server and node pool in a Kubernetes cluster can lead to high risk of networks attacks such as DoS assaults","report_fields":["id"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\n\nTo enable Azure Kubernetes Service Private Clusters, follow this guide:\nhttps://docs.microsoft.com/en-us/azure/aks/private-clusters.","multiregional":true,"service":"Azure Kubernetes Service"},"ecc-azure-369":{"article":"Enabling double encryption at the hardware level on top of the default software encryption for Storage Accounts accessing Azure storage solutions.","impact":"The read and write speeds to the storage will be impacted if both default encryption and Infrastructure Encryption are checked, as a secondary form of encryption requires more resource overhead for the cryptography of information. This performance impact should be considered in an analysis for justifying use of the feature in your environment. Customer-managed keys are recommended for the most secure implementation, leading to overhead of key management. The key will also need to be backed up in a secure location, as loss of the key will mean loss of the information in the storage.","report_fields":["id"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nFrom Azure Portal:\n 1. When creating a storage account, proceed as normal, but stop on the 'Advanced' tab.\n 2. Select 'Enabled' next to Infrastructure Encryption.","multiregional":true,"service":"Azure Storage Accounts"},"ecc-azure-100":{"article":"Turning on Azure Defender enables threat detection for Container Registries, providing threat intelligence, anomaly detection, and behavior analytics in the Azure Security Center.","impact":"Insufficient monitoring of your Container Registries can lead to a lack of security assessments and threat detection activities.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to Microsoft Defender for Cloud\n2. Select Environment Settings blade\n3. Click on the subscription name\n4. Select the Defender plans blade\n5. On the line in the table for Container Registries Select On under Plan.\n6. Select Save","multiregional":true,"service":"Microsoft Defender for Cloud"},"ecc-azure-218":{"article":"Audit enabling of resource logs. This enables you to recreate activity trails to use for investigation purposes;\nwhen a security incident occurs or when your network is compromised.","impact":"Insufficient logging of what operations were taken on a resource affects the effectiveness of incident management.","report_fields":["id"],"remediation":"From Azure Console:\n1. Go to Azure Stream Analytics and select appropriate service\n2. Under Diagnostic settings create new diagnostic settings.\n3. Select one of the options to store the diagnostics logs\n4. Click Save","multiregional":true,"service":"Azure Stream Analytics"},"ecc-azure-368":{"article":"On September 14, 2021, Microsoft released fixes for three Elevation of Privilege (EoP) vulnerabilities and one unauthenticated Remote Code Execution (RCE) vulnerability in the Open Management Infrastructure (OMI) framework: CVE-2021-38645, CVE-2021-38649, CVE-2021-38648, and CVE-2021-38647, respectively. Open Management Infrastructure (OMI) is an open-source Web-Based Enterprise Management (WBEM) implementation for managing Linux and UNIX systems. Several Azure Virtual Machine (VM) management extensions use this framework to orchestrate configuration management and log collection on Linux VMs.","impact":"Log Analytics Agent v1.13.39 or less bundled with vulnerable OMI version. It can be point of Local Elevation Privileges on affected VM Scale Set instances.\nLog Analytics Agent v.1.13.40 considered as safe and requires additional manual check.","report_fields":["id"],"remediation":"Follow this article to detect and remediate OMI vulnerability:\nhttps://techcommunity.microsoft.com/t5/azure-observability-blog/detecting-and-updating-agents-using-the-omi-vulnerability/ba-p/2795462","multiregional":true,"service":"Azure Virtual Machine Scale Sets"},"ecc-azure-103":{"article":"This setting enables Microsoft Cloud App Security (MCAS) integration with Security Center. Security Center offers an additional layer of protection by using Azure Resource Manager events, which is considered to be the control plane for Azure.\nBy analyzing the Azure Resource Manager records, Security Center detects unusual or potentially harmful operations in the Azure subscription environment.\nSeveral of the preceding analytics are powered by Microsoft Cloud App Security. To benefit from these analytics, subscription must have a Cloud\nApp Security license. MCAS works only with Standard Tier subscriptions.","impact":"Several of the preceding analytics are powered by Microsoft Cloud App Security.\nTo benefit from these analytics, a subscription must have a Cloud App Security license.","report_fields":["id"],"remediation":"From Azure Console\n1. Go to Microsoft Defender for Cloud\n2. Select Security policy blade\n3. Click On Edit Settings to alter the the security policy for a subscription\n4. Select the Integrations blade\n5. Check/Enable option Allow Microsoft Defender for Cloud Apps to access my data\n6. Select Save","multiregional":true,"service":"Microsoft Defender for Cloud"},"ecc-azure-174":{"article":"Azure Private Link allows you to connect your virtual network to Azure services without a public IP address at the source or destination. The Private Link platform handles the connectivity between the consumer and services over the Azure backbone network. By mapping private endpoints to your storage account, data leakage risks are reduced. Learn more about private links at https://aka.ms/azureprivatelinkoverview","impact":"Unconfigured Private Link with private endpoints enabled can lead to unauthorized access to your Storage account service.","report_fields":["id"],"remediation":"From Azure Console: \n1. Go to Storage accounts and select the one you want to remediate. \n2. Under Settings, select Private endpoint connections \n3. Click Add and configure the private endpoint.","multiregional":true,"service":"Azure Storage Accounts"},"ecc-azure-347":{"article":"Use customer-managed keys to manage the encryption at rest of your MySQL servers. \nBy default, the data is encrypted at rest with service-managed keys,\nbut customer-managed keys are commonly required to meet regulatory compliance standards. \nCustomer-managed keys enable the data to be encrypted with an Azure Key Vault key created and owned by you. \nYou have full control and responsibility for the key lifecycle, including rotation and management.","impact":"Unencrypted MySQL instances with CMK are vulnerable to data exposure.","report_fields":["id"],"remediation":"From Azure Console:\n1. Go to Key Vaults and create a key vault with soft delete and purge protection enabled \n2. Under the Keys generate your own encryption key. \n3. Under the Access polices grant the Azure Database for MySQL service permissions to the key vault with get, wrapKey, unwrapKey permissions\n4. Go to Azure Database for MySQL and under Data encryption fill the key vault and key information. Click Save.","multiregional":true,"service":"Azure Database for MySQL"},"ecc-azure-037":{"article":"The storage account with the activity log export container is configured to use BYOK (Use Your Own Key).","impact":"An unprotected storage account with the activity log container can lead to compromising log records for the entire subscription. An account configured to use BYOK (Use Your Own Key) provides additional confidentiality controls on log data.","report_fields":["id"],"remediation":"From Azure Portal:\n1. In right column, click Storage Accounts to access Storage account blade\n2. Click the storage account name\n3. In the Settings section, click Encryption. It will show the Storage service encryption configuration pane.\n4. Check Use your own key which will expand Encryption Key Settings\n5. Use option Enter key URI or Select from Key Vault to set up encryption with your own key","multiregional":true,"service":"Azure Storage Accounts"},"ecc-azure-222":{"article":"Audit enabling of resource logs. This enables you to recreate activity trails to use for investigation purposes; \nwhen a security incident occurs or when your network is compromised.","impact":"Insufficient logging of what operations were taken on a resource affects the effectiveness of incident management.","report_fields":["id"],"remediation":"From Azure Console \n1. Go to IoT Hub and select appropriate service \n2. Under Diagnostic settings create new diagnostic settings. \n3. Select one of the options to store the diagnostics logs \n4. Click Save","multiregional":true,"service":"Azure IoT Hub"},"ecc-azure-131":{"article":"This policy identifies network security group rules that allow inbound traffic to the Telnet port (23) from the public internet. \nAllowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Unrestricted access to port 23 can increase opportunities for malicious activities\nsuch as hacking and denial-of-service (DoS) attacks and lead to data loss.","report_fields":["id"],"remediation":"From Azure Console: \n1. Go to Network Security Groups \n2. Create an NSG or select the existing NSG assigned to a network interface or subnet where a VM resides. \n3. On the Settings plane, select Inbound rules \n4. Create Inbound rule:\n - Source 'Any'\n - Source port ranges '*'\n - Destination 'Any'\n - Service 'Custom' \n - Destination port ranges '23' \n - Protocol 'TCP' \n - Action 'Deny' \n - Priority {define high priority}\n Click Add \n5. Check Network interfaces or Subnets assignments: \n - On the Settings plane, select Network interfaces \n - On the Settings plane, select Subnets","multiregional":true,"service":"Network security groups"},"ecc-azure-296":{"article":"Avoid using names like 'Admin' for an Azure SQL Server admin account login","impact":"Using \"admin\" like names for Admin account increasing the risk of Admin account compromization in some cases","report_fields":["id"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\n\nThis is set when the SQL Server is created. After created, only using external tools connected to the SQL Server, such as SSMS, can be used to alter the user, but it will not change the value in the Portal.","multiregional":true,"service":"Azure SQL Database"},"ecc-azure-336":{"article":"Use encryption at host to get end-to-end encryption for your virtual machine and virtual machine scale set data. Encryption at host enables encryption at rest for your temporary disk and OS/data disk caches. Temporary and ephemeral OS disks are encrypted with platform-managed keys when encryption at host is enabled. OS/data disk caches are encrypted at rest with either customer-managed or platform-managed key, depending on the encryption type selected on the disk.","impact":"Unencrypted VMSS OS/data disks caches are vulnerable to the data exposure.","report_fields":["id"],"remediation":"Follow Microsoft documentation to enabled end-to-end encryption at host:\nhttps://docs.microsoft.com/en-us/azure/virtual-machines/disks-enable-host-based-encryption-portal","multiregional":true,"service":"Azure Virtual Machine Scale Sets"},"ecc-azure-013":{"article":"Enable auditing on SQL Servers.","impact":"Lack of auditing and logging can lead to insufficient response time to threats from an attacker, which can result in data loss and degradation of the service.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to SQL servers\n2. For each server instance\n3. Click Auditing\n4. Set Auditing to On","multiregional":true,"service":"Azure SQL Database"},"ecc-azure-008":{"article":"Enable data encryption in transit.","impact":"When a connection is unprotected, data in transit can be easily intercepted by an attacker. With secure transfer enabled, a call to an Azure Storage REST API operation is made over HTTPS. Any request made over HTTP will be rejected.","report_fields":["id"],"remediation":"From Azure Portal:\n1. Go to Storage Accounts\n2. For each storage account,\ngo to Configuration\n3. Set Secure transfer required to Enabled \nFrom Azure Command Line Interface 2.0:\nUse the below command to enable Secure transfer required for a Storage Account:\naz storage account update --name --resource-group --https-only true","multiregional":true,"service":"Azure Storage Accounts"},"ecc-gcp-246":{"article":"This rule detects when a function doesn't contain a VPC connector. VPC connectors helps the function connect to resourced inside a VPC on the same project.","impact":"Not using a VPC connector can increase the opportunity for malicious activities such as unauthorized access, brute-force attacks that increase the risk of compromising resources from the Internet.","report_fields":["name"],"remediation":"From Console:\n1. Go to the Cloud Functions page at Google Cloud Console https://console.cloud.google.com/functions.\n2. Select a function.\n3. Click on edit.\n4. Click on Runtime, build, connections and security settings.\n5. Click on Connections.\n6. On Egress Settings select vpc connector.\n7. Click on next.\n8. Click on Deploy.","multiregional":true,"service":"Cloud Functions"},"ecc-gcp-401":{"article":"It is recommended to set the 'password_reuse_interval' database flag for Cloud SQL Mysql instance.","impact":"Using of 'password_reuse_interval' database flag allow to establish password-reuse policy and prevent using the old passwords.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the Cloud SQL Instances page in the Google Cloud Console using https://console.cloud.google.com/sql/instances.\n2. Select the Mysql instance for which you want to enable the database flag.\n3. Click Edit.\n4. Scroll down to the Flags section.\n5. To set a flag that has not been set on the instance before, click Add item, choose the 'password_reuse_interval' flag from the drop-down menu, and set its value.\n6. Click Save to save your changes.\n7. Confirm your changes under Flags on the Overview page.\nUsing Command Line:\n1. List all Cloud SQL database Instances\ngcloud sql instances list\n2. Configure the 'password_history' and 'password_reuse_interval' database flags for every Cloud SQL Mysql database instance using the below command:\ngcloud sql instances patch INSTANCE_NAME --database-flags password_reuse_interval={number of days that must pass before the password can be reused}\nNote : This command will overwrite all database flags previously set. To keep those and add new ones, include the values for all flags you want set on the instance; any flag not specifically included is set to its default value. For flags that do not take a value, specify the flag name followed by an equals sign (\"=\").","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-206":{"article":"The 'log_statement_stats' flag controls the inclusion of end-to-end performance statistics of a SQL query in the PostgreSQL logs for each query. This cannot be enabled with other module statistics (log_parser_stats, log_planner_stats, log_executor_stats).","impact":"The 'log_statement_stats' flag enables a crude profiling method for logging end-to-end performance statistics of a SQL query. This can be useful for troubleshooting, but may increase the amount of logs significantly and have performance overhead.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the Cloud SQL Instances page in the Google Cloud Console using https://console.cloud.google.com/sql/instances.\n2. Select the PostgreSQL instance for which you want to enable the database flag.\n3. Click Edit.\n4. Scroll down to the Flags section.\n5. To set a flag that has not been set on the instance before, click Add item, choose the 'log_statement_stats' flag from the drop-down menu and set the appropriate value.\n6. Click Save to save your changes.\n7. Confirm your changes under Flags on the Overview page.\nUsing Command Line:\n1. List all Cloud SQL database Instances\ngcloud sql instances list\n2. Configure the log_statement_stats database flag for every Cloud SQL PosgreSQL database instance using the below command.\ngcloud sql instances patch INSTANCE_NAME --database-flags log_statement_stats=off\nNote: This command will overwrite all database flags previously set. To keep those and add new ones, include the values for all flags you want set on the instance; any flag not specifically included is set to its default value. For flags that do not take a value, specify the flag name followed by an equals sign (\"=\").","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-065":{"article":"Use the Key Management Service to manage secrets. KMS allows you to create, import, and manage cryptographic keys and perform cryptographic operations in a single centralized cloud service.","impact":"Not using a Key Management Service to manage KMS secrets compromises the security of your project.","report_fields":["name"],"remediation":"From Console:\n1. Go to Security.\n2. Go to Cryptographic Keys.\n3. Create secrets using the link https://cloud.google.com/kms/docs/creating-keys#kms-create-keyring-console.","multiregional":true,"service":"Cloud KMS"},"ecc-gcp-272":{"article":"GCP firewall logging helps to inspect and understand the system access on specific ports. Firewall logging is not enabled by default and must be configured by the user.","impact":"Lack of logging can lead to failure to detect the records with information about configured firewall rules that allow malicious activities.","report_fields":["selfLink"],"remediation":"From Console:\n1. From the Google Cloud Console, go to VM firewall.\n2. Select firewall for which logging is to be enabled.\n3. Select EDIT.\n4. In Logs section, turn on firewall logs.\n5. Select SAVE.","multiregional":true,"service":"Virtual Private Cloud"},"ecc-gcp-262":{"article":"This rule detects when a service account with elevated privileges (editor or owner status) is assigned a service-level IAM role in the Cloud Run revision. An elevated service account has more access than necessary and doesn't meet least privilege standards. You can resolve this by assigning custom roles or providing access according to the requirements in the provider documentation.","impact":"An elevated service account has more access than necessary and it increases the level of compromise in the event of a security breach.","report_fields":["metadata.selfLink"],"remediation":"From Console:\n1. Go to the Cloud Run page at Google Cloud Console https://console.cloud.google.com/run.\n2. For the revision of a service, click on the service, then click Edit and Deploy New Revision.\n3. Under Container tab, Click the Service account dropdown and select the desired service account.\n4. Select Serve this revision immediately.\n5. Click on Deploy.","multiregional":true,"service":"Cloud Run"},"ecc-gcp-310":{"article":"This rule detects when redis instance has in-transit encryption disabled.\nMemorystore for Redis supports encrypting all Redis traffic using the Transport Layer Security (TLS) protocol. When in-transit encryption is enabled Redis clients communicate exclusively across a secure port connection. Redis clients that are not configured for TLS will be blocked. If you choose to enable in-transit encryption you are responsible for ensuring that your Redis client is capable of using the TLS protocol.","impact":"Without enabled in-transit encryption, Memorystore for Redis accepts a connection whether it uses SSL or not. This can lead to malicious activity such as man-in-the-middle attacks (MITM), intercepting, or manipulating network traffic.","report_fields":["name"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nFrom Console:\n1. Log in to the GCP Console at https://console.cloud.google.com.\n2. Navigate to Memorystore for Redis.\n3. Select Enable in-transit encryption when Creating a Redis instance.\n4. Download the Certificate Authority and Installing a Certificate Authority on your client: https://cloud.google.com/memorystore/docs/redis/enabling-in-transit-encryption#downloading_the_certificate_authority","multiregional":true,"service":"Cloud Memorystore"},"ecc-gcp-202":{"article":"PostgreSQL logs only the IP address of the connecting hosts. The 'log_hostname' flag controls the logging of hostnames in addition to the IP addresses logged. The performance hit is dependent on the configuration of the environment and the host name resolution setup. This parameter can only be set in the 'postgresql.conf' file or on the server command line.","impact":"Logging hostnames can incur overhead on server performance as for each statement logged, DNS resolution will be required to convert IP address to hostname. Depending on the setup, this may be non-negligible. Additionally, the IP addresses that are logged can be resolved to their DNS names later when reviewing the logs excluding the cases where dynamic hostnames are used.","report_fields":["selfLink"],"remediation":"1. Go to the Cloud SQL Instances page in the Google Cloud Console using https://console.cloud.google.com/sql/instances.\n2. Select the PostgreSQL instance for which you want to enable the database flag.\n3. Click Edit.\n4. Scroll down to the Flags section.\n5. To set a flag that has not been set on the instance before, click Add item, choose the 'log_hostname' flag from the drop-down menu and set the appropriate value.\n6. Click Save to save your changes.\n7. Confirm your changes under Flags on the Overview page.\nFrom Command Line:\n1. Configure the log_hostname database flag for every Cloud SQL PosgreSQL database instance using the below command:\ngcloud sql instances patch --database-flags log_hostname=on\nNote: This command will overwrite all database flags previously set. To keep those and add new ones, include the values for all flags you want set on the instance; any flag not specifically included is set to its default value. For flags that do not take a value, specify the flag name followed by an equals sign (\"=\").","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-294":{"article":"This rule detects when a service account with elevated privileges (editor or owner status) is assigned a database-level IAM role in the Cloud Spanner service. An elevated service account has more access than necessary and doesn't meet least privilege standards. You can resolve this by assigning custom roles or providing access according to the requirements in the provider documentation.","impact":"An elevated service account has more access than necessary and it increases the level of compromise in the event of a security breach.","report_fields":["name"],"remediation":"From Console:\n1. Go to the Spanner page at Google Cloud Console: https://console.cloud.google.com/spanner/instances.\n2. Select an instance.\n3. Select a database from Databases.\n4. Review each role and find Members having admin or owner access.\n5. Click Delete icon and confirm by clicking on REMOVE.","multiregional":true,"service":"Cloud Spanner"},"ecc-gcp-076":{"article":"VPC network peering enables you to connect virtual networks in the same region. Once peered, the virtual networks are still managed as separate resources.\nWhen a peering configuration is deleted on one virtual network, the other VPC network will report that peering is disconnected. With 'Restrict VPC Peering Usage' constraint policy, you have the ability to define the VPC networks that are allowed to be peered with other networks within your project, folder, or organization, in order to enhance access security and comply with internal regulations.","impact":"By default, anyone with the right set of permissions can peer your VPC network with any other network within your organization and this can pose a major security risk if the process is not managed correctly or if someone peers your VPC network with a malicious entity.","report_fields":["selfLink"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nTo configure the peering connection, navigate to the VPC network:\n1. Click 'VPC network peering'.\n2. Delete the peering with 'INACTIVE' status.\n3. Click 'Create Peering Connection'.\n4. Check Status Link https://cloud.google.com/vpc/docs/using-vpc-peering.","multiregional":true,"service":"Virtual Private Cloud"},"ecc-gcp-281":{"article":"This policy identifies GCP Firewall rules that allow inbound traffic to the memcached port (11211) from the public internet. Allowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Allowing unrestricted inbound/ingress access on TCP port 11211 (memcached) via VPC network firewall rules can increase opportunities for malicious activities such as hacking and brute-force attacks.","report_fields":["selfLink"],"remediation":"To restrict all traffic, edit the reported Firewall rule as follows:\n1. Login to GCP Console.\n2. Go to VPC Network.\n3. Go to Firewall rules.\n4. Click on the reported Firewall rule.\n5. Click on Edit.\n6. Modify Source IP ranges to specific IP.\n7. Click on Save.","multiregional":true,"service":"Virtual Private Cloud"},"ecc-gcp-123":{"article":"Ensure your GKE Master node version is supported. This policy checks your GKE master node version and generates an alert if the running version is unsupported.","impact":"Using an unsupported version of the master has bugs and security vulnerabilities found in a supported minor version.","report_fields":["selfLink"],"remediation":"Manually initiate a master upgrade.\n1. From Google Cloud Platform Console, go to the Google Kubernetes Engine menu.\n2. Select the desired cluster.\n3. Click the available Upgrade link next to Master version.\n4. Select the desired version, then click Change.\n5. Click the arrow at the top of the screen to go back to the cluster overview page.","multiregional":true,"service":"Google Kubernetes Engine"},"ecc-gcp-127":{"article":"Binary Authorization helps to protect supply-chain security by only allowing images with verifiable cryptographically signed metadata into the cluster.","impact":"By not applying Binary Authorization, you can not gain tighter control over your container environment by ensuring only verified images are integrated into the build-and-release process.","report_fields":["selfLink"],"remediation":"Using Google Cloud Console:\n1. Go to Binary Authorization using 'https://console.cloud.google.com/security/binary-authorization'.\n2. Enable the Binary Authorization API (if disabled).\n3. Go to Kubernetes Engine using 'https://console.cloud.google.com/kubernetes/list'.\n4. Select the Kubernetes cluster for which Binary Authorization is disabled.\n5. Click EDIT.\n6. Set 'Binary Authorization' to 'Enabled'.\n7. Click SAVE.\n8. Return to Binary Authorization at 'https://console.cloud.google.com/security/binary-authorization'.\n9. Set an appropriate policy for your cluster.\nUsing Command Line:\nThe Remediation script for this recommendation utilizes 2 variables $CLUSTER_NAME, $COMPUTE_ZONE.\nPlease set these parameters for the system where you will be executing your gcloud audit script or command.\nUpdate the cluster to enable Binary Authorization:\ngcloud container cluster update [CLUSTER_NAME] --zone [COMPUTE-ZONE] --enable-binauthz\nCreate a Binary Authorization Policy using the Binary Authorization Policy Reference ('https://cloud.google.com/binary-authorization/docs/policy-yaml-reference') for guidance.\nImport the policy file into Binary Authorization:\ngcloud container binauthz policy import [YAML_POLICY]","multiregional":true,"service":"Google Kubernetes Engine"},"ecc-gcp-311":{"article":"This rule detects when Datafusion instance has stackdriver logging disabled.\nStackdriver logging is a fully managed service that allows you to store, search, analyze, monitor, and alert on logging data and events.","impact":"Lack of Stackdriver Datafusion logging can result in insufficient response time to detect issues.","report_fields":["name"],"remediation":"From Command Line:\n1. After you create your instance, you cannot enable Cloud Logging in the console. Instead, run this gcloud CLI command:\ngcloud beta data-fusion instances update INSTANCE_NAME --project=PROJECT_ID --location=LOCATION --enable_stackdriver_logging","multiregional":true,"service":"Cloud Data Fusion"},"ecc-gcp-214":{"article":"BigQuery by default encrypts the data at-rest by employing Envelope Encryption using Google managed cryptographic keys. The data is encrypted using data encryption keys and the data encryption keys themselves are further encrypted using key encryption keys. This is seamless and does not require any additional input from the user. However, if you want to have greater control, Customer-managed encryption keys (CMEK) can be used as the encryption key management solution for BigQuery Data Sets.","impact":"Not using CMEK results in less control over aspects of the lifecycle and management of your keys.","report_fields":["selfLink"],"remediation":"The default CMEK for existing data sets can be updated by specifying the default key in the 'EncryptionConfiguration.kmsKeyName' field when calling the 'datasets.insert' or 'datasets.patch' methods.","multiregional":true,"service":"BigQuery"},"ecc-gcp-068":{"article":"It is recommended to set an expiration date for all keys.","impact":"Expired keys can be misused or exposed during their life cycle, which can lead to potential threats to data integrity and confidentiality.","report_fields":["name"],"remediation":"From Console:\n1. Go to Security\n2. Go to Cryptographic Keys.\n3. Choose Keys and click on Edit rotation period and then select a value (that should never be) shold not be never.\n4. Choose Security policy.\n6. Click on Save.\nhttps://cloud.google.com/kms/docs/creating-keys#kms-create-keyring-console\ngcloud kms keys create [KEY_NAME] --location [LOCATION] --keyring [KEYRING_NAME] --purpose encryption --rotation-period [ROTATION_PERIOD] --next-rotation-time [NEXT_ROTATION_TIME]","multiregional":true,"service":"Cloud KMS"},"ecc-gcp-268":{"article":"This rule detects when a Cloud Run service is publicly accessible. Remove public access and assign least privileges to GCP Cloud Run service according to requirements.","impact":"Granting permissions to allUsers or allAuthenticatedUsers allows anyone to access the run service.","report_fields":["metadata.selfLink"],"remediation":"From Console:\n1. Go to the Cloud Run page at Google Cloud Console https://console.cloud.google.com/run.\n2. Select a service.\n3. Click on Triggers tab.\n4. Click on Show Info Panel in the top right corner to show the Permissions tab.\n5. Click on the delete button to remove the allUsers and authenticatedUsers.\n6. Click on the Add Member button to can grant Cloud Run Invoker role to required members as per the requirements.","multiregional":true,"service":"Cloud Run"},"ecc-gcp-220":{"article":"Compute instances template should not have external IP addresses.","impact":"An instance with a public IP address could potentially be compromised and an attacker could gain access to it and other resources connected to this instance, which may lead to malicious activity with sensitive data.","report_fields":["selfLink"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nUsing Console:\n1. Go to the instance template page using https://console.cloud.google.com/compute/instanceTemplates/list.\n2. Replace all affected templates with new ones with External IP set to 'None' in the Network interfaces section.","multiregional":true,"service":"Compute Engine"},"ecc-gcp-415":{"article":"Use GKE Sandbox to restrict untrusted workloads as an additional layer of protection when running in a multi-tenant environment.","impact":"Without Sandbox enabled, multi-tenant clusters and clusters whose containers run untrusted workloads are more exposed to security vulnerabilities than other clusters. The potential exists for a malicious tenant to gain access to and exfiltrate another tenant's data in memory or on disk, by exploiting such a defect.","report_fields":["selfLink"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nUsing Google Cloud Console:\n1. Go to Kubernetes Engine by visiting https://console.cloud.google.com/kubernetes/\n2. Click ADD NODE POOL.\n3. Configure the Node pool with following settings:\n - For the node version, select v1.12.6-gke.8 or higher.\n - For the node image, select 'Container-Optimized OS with Containerd (cos_containerd) (beta)'.\n - Under 'Security', select 'Enable sandbox with gVisor'.\n4. Configure other Node pool settings as required.\n5. Click SAVE.\nUsing Command Line:\nTo enable GKE Sandbox on an existing cluster, a new Node pool must be created.\ngcloud container node-pools create [NODE_POOL_NAME] \\\n--zone=[COMPUTE-ZONE] \\\n--cluster=[CLUSTER_NAME] \\\n--image-type=cos_containerd \\\n--sandbox type=gvisor","multiregional":true,"service":"Google Kubernetes Engine"},"ecc-gcp-251":{"article":"This rule detects when a service account with elevated privileges (editor, viewer or owner status) is assigned in the GKE service.","impact":"An elevated service account has more access than necessary and it increases the level of compromise in the event of a security breach.","report_fields":["selfLink"],"remediation":"Note: Service account can only be changed by adding a new node pool and deleting the affected node pool.\nUsing Console:\n1. Go to Kubernetes GCP Console visiting https://console.cloud.google.com/kubernetes/list?\n2. o to Kubernetes Engine page at Google Cloud Console.\n3. Click ADD NODE POOL.\n4. Select Security.\n5. In Service account, select service account with least security privileges.\n6. Fill up the other details as required.\n7. Click CREATE.","multiregional":true,"service":"Google Kubernetes Engine"},"ecc-gcp-118":{"article":"This policy identifies GCP Firewall rules that allow inbound traffic to the POP3 port (110) from the public internet. Allowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Allowing unrestricted POP3 access to your Google Cloud virtual machine (VM) instances via VPC network firewall rules can increase opportunities for malicious activities such as brute-force attacks, spoofing, and packet capture attacks.","report_fields":["selfLink"],"remediation":"To restrict all traffic, edit the reported Firewall rule as follows:\n1. Login to GCP Console.\n2. Go to VPC Network.\n3. Go to Firewall rules.\n4. Click on the reported Firewall rule.\n5. Click on Edit.\n6. Modify Source IP ranges to specific IP.\n7. Click on Save.","multiregional":true,"service":"Virtual Private Cloud"},"ecc-gcp-126":{"article":"Alpha clusters are not covered by an SLA and are not production-ready. They are designed for early adopters to experiment with workloads that take advantage of new features before those features are production-ready.","impact":"Alpha clusters do not receive security updates, have node auto-upgrade and node auto-repair disabled, and cannot be upgraded. They are also automatically deleted after 30 days.","report_fields":["selfLink"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nFrom Console:\n1. Go to Kubernetes Engine using https://console.cloud.google.com/kubernetes/.\n2. Click CREATE CLUSTER.\n3. Unless Node Auto-Upgrade and Node Auto-Repair are disabled, the option 'Enable Kubernetes alpha features in this cluster' will not be available under 'Availability, networking, security, and additional features'. Ensure this feature is not checked.\n4. Click CREATE.\nFrom Command Line:\nThe Remediation script for this recommendation utilizes 2 variables $CLUSTER_NAME, $COMPUTE_ZONE.\nPlease set these parameters for the system where you will be executing your gcloud audit script or command. Upon creating a new cluster:\ngcloud container clusters create [CLUSTER_NAME] --zone [COMPUTE_ZONE]\nDo not use the --enable-kubernetes-alpha argument.","multiregional":true,"service":"Google Kubernetes Engine"},"ecc-gcp-122":{"article":"This policy identifies GCP Firewall rules that allow inbound traffic from anywhere with no target filtering. The default target is all instances in the network. The use of target tags or target service accounts allows the rule to apply to the selected instances. Failure to use firewall rules that ensure target filtering may allow a bad actor to brute-force their way into the system and potentially get access to the entire network.","impact":"Failure to use firewall rules that ensure target filtering may allow a bad actor to brute-force their way into the system and potentially get access to the entire network.","report_fields":["selfLink"],"remediation":"Follow the instructions below to restrict the default target parameter (all instances in the network):\n1. Login to GCP Console.\n2. Go to VPC Network.\n3. Go to Firewall rules.\n4. Click on each reported Firewall rule.\n5. Click on Edit.\n6. Change the Targets field from 'All instances in the network' to 'Specified target tags'.\n7. Type the tags into the Target tags field.\n8. Review Source IP ranges and change to specific IP ranges if traffic is not to be allowed from anywhere.\n9. Click on Save.\nReference:\nhttps://cloud.google.com/vpc/docs/add-remove-network-tags.","multiregional":true,"service":"Virtual Private Cloud"},"ecc-gcp-244":{"article":"This rule detects when a HTTP trigger function is configured with unauthenticated access. Remove unnecessary access and assign least privileges to GCP Cloud Functions function according to requirements.","impact":"Granting roles/cloudfunctions.invoker to 'allUsers' members can allow anyone to invoke HTTP functions with restricted access.","report_fields":["name"],"remediation":"From Console:\n1. Go to the Cloud Functions page at Google Cloud Console https://console.cloud.google.com/functions.\n2. Select a function.\n3. Click on Permission.\n4. Review role Cloudfunctions Invoker and find Members having allUsers access.\n5. Click Delete icon and confirm by clicking on REMOVE.","multiregional":true,"service":"Cloud Functions"},"ecc-gcp-324":{"article":"The User has an IAM policy containing permissions that allow privilege escalation, at the project level. The existing permissions allow the user to impersonate a service account with higher permissions than their own. The user can then utilize that service account to perform API calls that the user may not be authorized to perform.","impact":"The permission allow the user to impersonate a service account with higher permissions than their own.","report_fields":["member","roles"],"remediation":"From Console:\n1. Go to IAM & Admin/IAM using https://console.cloud.google.com/iam-admin/iam.\n2. Click Edit for reported user.\n3. For all non-critical members of these roles, remove their membership by clicking the Trash icon on the right.","multiregional":true,"service":"Cloud IAM"},"ecc-gcp-443":{"article":"Cloud KMS bills monthly for the number of active primary key versions which you have for the month. This means that disabled primary key versions are adding costs to your GCP bill while not being of any use. For more information, see https://cloud.google.com/kms/pricing#pricing_overview","impact":"Keeping Disabled primary key versions will inflict unnecessary monthly costs","report_fields":["name"],"remediation":"From Console\n1. Go to Cryptographic Keys using https://console.cloud.google.com/security/kms.\n2. Click on the specific key ring.\n3. From the list of keys, choose the specific key \n4. From the list of key versions, choose the specific version and click on Right side pop up the blade (3 dots).\n4. Click on Enable or Destroy.","multiregional":true,"service":"Cloud KMS"},"ecc-gcp-409":{"article":"Patch deployment keeps VM instances at the current OS security patch level to mitigate known vulnerabilities.","impact":"Without Patch deployment you can face security holes that have been fixed in new versions.","report_fields":["selfLink"],"remediation":"Use the next instruction to schedule patch jobs for all the instances:\nhttps://cloud.google.com/compute/docs/os-patch-management/schedule-patch-jobs#schedule-patch-console","multiregional":true,"service":"Compute Engine"},"ecc-gcp-200":{"article":"Enabling the 'log_duration' setting causes the duration of each completed statement to be logged. This does not logs the text of the query and thus, behaves different from the 'log_min_duration_statement' flag. This parameter cannot be changed after the session start.","impact":"Monitoring the time taken to execute the queries can be crucial in identifying any resource hogging queries and assessing the performance of the server. Further steps such as load balancing and use of optimized queries can be taken to ensure the performance and stability of the server.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the Cloud SQL Instances page in the Google Cloud Console using https://console.cloud.google.com/sql/instances.\n2. Select the PostgreSQL instance for which you want to enable the database flag.\n3. Click Edit.\n4. Scroll down to the Flags section.\n5. To set a flag that has not been set on the instance before, click Add item, choose the 'log_duration' flag from the drop-down menu and set the value as 'on'.\n6. Click Save to save your changes.\n7. Confirm your changes under Flags on the Overview page.\nUsing Command Line:\n1. List all Cloud SQL database Instances\ngcloud sql instances list\n2. Configure the log_duration database flag for every Cloud SQL PosgreSQL database instance using the below command:\ngcloud sql instances patch INSTANCE_NAME --database-flags log_duration=on\nNote: This command will overwrite all database flags previously set. To keep those and add new ones, include the values for all flags you want set on the instance; any flag not specifically included is set to its default value. For flags that do not take a value, specify the flag name followed by an equals sign (\"=\").","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-049":{"article":"Legacy Authorization also known as Attribute-Based Access Control (ABAC) has been superseded by Role-Based Access Control (RBAC) and is not under active development. RBAC is the recommended way to manage permissions in Kubernetes.","impact":"It increases the risk of an attacker getting elevation of privileges without configured role-based access controls.","report_fields":["selfLink"],"remediation":"Using Google Cloud Console:\n1. Go to Kubernetes Engine using https://console.cloud.google.com/kubernetes/list.\n2. Select Kubernetes clusters for which Legacy Authorization is enabled.\n3. Click on EDIT.\n4. Set 'Legacy Authorization' to 'Disabled'.\n5. Click on SAVE.\nUsing Command Line:\nTo disable Legacy Authorization for an existing cluster, run the following command:\ngcloud container clusters update [CLUSTER_NAME] --zone [COMPUTE_ZONE] --no-enable-legacy-authorization","multiregional":true,"service":"Google Kubernetes Engine"},"ecc-gcp-083":{"article":"These High Availability deployments will improve primary instance reachability by providing read replica in case of network connectivity loss or loss of availability in the primary availability zone for read/write operations.","impact":"When Cloud SQL instances are not configured for multiple Availability Zones, as well as in case of any issue with the Availability Zone availability, and during regular Cloud SQL maintenance, the data stored in a database will not be reachable.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to SQL.\n2. Select the instance for which you want to configure High-Availability.\n3. Click Edit.\n3. Under Zonal availability select Multiple zones (Highly available).\n4. Click Save.","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-051":{"article":"A cluster label is a key-value pair that helps you organize your Google Cloud Platform resources, such as clusters. You can attach a label to each resource, then filter the resources based on their labels. Information about labels is forwarded to the billing system, so you can break down your billing charges by the label.","impact":"Applying labels to your Kubernetes clusters helps organize them logically. Lack of labels can make administration difficult.","report_fields":["selfLink"],"remediation":"Using Console:\n1. Go to Kubernetes GCP Console visiting https://console.cloud.google.com/kubernetes/list?\n2. Select cluster from the list.\n3. Click 'Edit labels' in the 'Labels' string ('Metadata' section).\n4. Click 'Add label', set the key and value pair.\n5. Click 'Save changes'.\nUsing Command Line:\nTo configure Labels for an existing cluster, run the following command:\ngcloud container clusters update [CLUSTER_NAME] --zone [COMPUTE_ZONE] --update-labels [Key]=[Value]","multiregional":true,"service":"Google Kubernetes Engine"},"ecc-gcp-167":{"article":"Dataset-level permissions determine the users, groups, and service accounts allowed to access the tables, views, and table data in a specific dataset.","impact":"Granting the individual Google account access in a specific dataset can not be controlled by organizations.","report_fields":["selfLink"],"remediation":"From Console:\n1. Select a dataset from 'Resources', then click 'Share dataset' from the right side of the window.\n2. In the 'Share dataset' panel, in the 'Dataset permissions' tab, click 'Add members'.\n3. In the 'Add members' panel, enter the entity you want to add into the 'New members' textbox. You can add any of the following entities:\n- Google account e-mail Grants an individual Google account access to the dataset\n- Google Group Grants all members of a Google group access to the dataset\n- Google Apps Domain Grants all users and groups in a Google domain access to the dataset\n- Service account Grants a service account access to the dataset\n- Anybody Enter 'allUsers' to grant access to the general public\n- All Google accounts Enter 'allAuthenticatedUsers' to grant access to any user signed in to a Google Account\n4. For 'Select a role', select 'BigQuery' and choose the appropriate pre-defined IAM role for the new members. For more information on the permissions assigned to each predefined BigQuery role, see the 'Roles' section of the access control page.\n5. Click Done.","multiregional":true,"service":"BigQuery"},"ecc-gcp-022":{"article":"It is recommended that a metric filter and alarm be established for VPC network changes.","impact":"Lack of monitoring and logging of VPC changes can lead to insufficient response time to detect accidental or intentional modifications that may lead to unauthorized network access or other security breaches.","report_fields":["projectId"],"remediation":"From Console:\nCreate the prescribed log metric:\n1. Go to Logging/Logs-based Metrics using https://console.cloud.google.com/logs/metrics and click 'CREATE METRIC'.\n2. Click the down arrow symbol on Filter Bar at the rightmost corner and select Convert to Advanced Filter.\n3. Clear any text and add:\nresource.type=\"gce_network\"\nAND (protoPayload.methodName:\"compute.networks.insert\"\nOR protoPayload.methodName:\"compute.networks.patch\"\nOR protoPayload.methodName:\"compute.networks.delete\"\nOR protoPayload.methodName:\"compute.networks.removePeering\"\nOR protoPayload.methodName:\"compute.networks.addPeering\")\n4. Click Submit Filter. Display logs appear based on the filter text entered by the user.\n5. In the Metric Editor menu on the right, fill out the name field. Set Units to 1 (default) and Type to Counter. This ensures that the log metric counts the number of log entries matching the user's advanced logs query.\n6. Click Create Metric.\nCreate the prescribed alert policy:\n1. Identify the newly created metric under the User-defined Metrics section at https://console.cloud.google.com/logs/metrics.\n2. Click the 3-dot icon in the rightmost column for the new metric and select Create alert from Metric. A new page appears.\n3. Fill out the alert policy configuration and click Save. Choose the alerting threshold and configuration that makes sense for the user's organization. For example, a threshold of 0 for the most recent value will ensure that a notification is triggered for every owner change in the project:\nSet 'Aggregator' to 'Count'\nSet 'Configuration':\n- Condition: above\n- Threshold: 0\n- For: most recent value\n4. Configure the desired notification channels in the section Notifications.\n5. Name the policy and click Save.\nFrom Google Cloud CLI:\nCreate the prescribed Log Metric:\ngcloud logging metrics create\nCreate the prescribed alert policy:\ngcloud alpha monitoring policies create","multiregional":true,"service":"Cloud Logging"},"ecc-gcp-203":{"article":"The PostgreSQL planner/optimizer is responsible for parsing and verifying the syntax of each query received by the server. If the syntax is correct, a parse tree is built up. Otherwise, an error is generated. The 'log_parser_stats' flag controls the inclusion of parser performance statistics in the PostgreSQL logs for each query.","impact":"The 'log_parser_stats' flag enables a crude profiling method for logging parser performance statistics which even though can be useful for troubleshooting, it may increase the amount of logs significantly and have performance overhead.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the Cloud SQL Instances page in the Google Cloud Console using https://console.cloud.google.com/sql/instances.\n2. Select the PostgreSQL instance for which you want to enable the database flag.\n3. Click Edit.\n4. Scroll down to the Flags section.\n5. To set a flag that has not been set on the instance before, click Add item, choose the 'log_parser_stats' flag from the drop-down menu and set the appropriate value.\n6. Click Save to save your changes.\n7. Confirm your changes under Flags on the Overview page\nUsing Command Line:\n1. List all Cloud SQL database Instances\ngcloud sql instances list\n2. Configure the log_parser_stats database flag for every Cloud SQL PosgreSQL database instance using the below command:\ngcloud sql instances patch INSTANCE_NAME --database-flags log_parser_stats=off\nNote: This command will overwrite all database flags previously set. To keep those and add new ones, include the values for all flags you want set on the instance; any flag not specifically included is set to its default value. For flags that do not take a value, specify the flag name followed by an equals sign (\"=\").","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-136":{"article":"Ensure that your Kubernetes cluster size contains 3 or more nodes. (Clusters smaller than 3 may experience downtime during upgrades.) This policy checks the size of your cluster pools and alerts if there are fewer than 3 nodes in a pool.","impact":"Clusters smaller than 3 may experience downtime during upgrades.","report_fields":["selfLink"],"remediation":"Using Google Cloud Console:\n1. Go to Kubernetes Engine using 'https://console.cloud.google.com/kubernetes/list'.\n2. Click on the name of a cluster to be upgraded and click on NODES.\n3. Click on the name of node pools to be upgraded.\n4. In the Node pools section, click on the Edit button and change the value of the Size (Number of nodes) field to the desired value.\n5. Click on SAVE.\n6. Repeat for each node pool with Enable auto-upgrade function enabled as needed.","multiregional":true,"service":"Google Kubernetes Engine"},"ecc-gcp-208":{"article":"It is recommended to set the 'external scripts enabled' database flag for Cloud SQL SQL Server instance to 'off'","impact":"The 'External Scripts Enabled' feature allows scripts external to SQL, such as files located in an R library, to be executed. This could adversely affect the security of the system. Hence, this feature should be disabled.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the Cloud SQL Instances page in the Google Cloud Console using https://console.cloud.google.com/sql/instances.\n2. Select the SQL Server instance for which you want to enable the database flag.\n3. Click Edit.\n4. Scroll down to the Flags section.\n5. To set a flag that has not been set on the instance before, click Add item, choose the 'external scripts enabled' flag from the drop-down menu, and set its value to 'off'.\n6. Click Save to save your changes.\n7. Confirm your changes under Flags on the Overview page.\nUsing Command Line:\n1. List all Cloud SQL database Instances\ngcloud sql instances list\n2. Configure the external scripts enabled database flag for every Cloud SQL SQL Server database instance using the below command.\ngcloud sql instances patch INSTANCE_NAME --database-flags \"external scripts enabled=off\"\nNote : This command will overwrite all database flags previously set. To keep those and add new ones, include the values for all flags you want set on the instance; any flag not specifically included is set to its default value. For flags that do not take a value, specify the flag name followed by an equals sign (\"=\").","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-125":{"article":"This policy identifies HTTPS Load balancers that are not using restrictive profile in their SSL Policy. A restrictive profile controls sets of features used in negotiating SSL with clients. As a best security practice, use RESTRICTED as SSL policy profile as it meets stricter compliance requirements and does not include any out-of-date SSL features.","impact":"If you do not use the RESTRICTED profile, there is a chance that the chosen protocol will be TLS 1.0, which is vulnerable to attacker-in-the-middle attacks, posing a risk to the integrity and authentication of data transmitted between the website and the browser.","report_fields":["selfLink"],"remediation":"1. Login to GCP Portal.\n2. Go to Network services (Left Panel).\n3. Select Load balancing.\n4. Click on the 'advanced menu' hyperlink to view target proxies.\n5. Click on the 'Target proxies' tab.\n6. Click on the reported HTTPS target proxy.\n7. Click on the hyperlink under 'URL map'.\n8. Click on the 'EDIT' button.\n9. Select 'Frontend configuration', Click on HTTPS protocol rule.\n10. Select the SSL policy that uses the RESTRICTED/CUSTOM profile. If no SSL policy is already present, then create a new SSL policy with RESTRICTED as Profile.\nNOTE: If you choose CUSTOM as a profile, make sure you are using profile features as restrictive as the RESTRICTED profile or more restrictive than the RESTRICTED profile.\n11. Click on 'Done'.\n12. Click on 'Update'.","multiregional":true,"service":"Cloud Load Balancing"},"ecc-gcp-347":{"article":"This rule detects when GCP cloud spanner instance is deployed without multi-region configuration.\nMulti-region configurations allow you to replicate the database's data not just in multiple zones, but in multiple zones across multiple regions, as defined by the instance configuration. These additional replicas enable you to read data with low latency from multiple locations close to or within the regions in the configuration. There are trade-offs though, because in a multi-region configuration, the quorum (read-write) replicas are spread across more than one region. Hence, they can incur additional network latency when these replicas communicate with each other to vote on writes. In other words, multi-region configurations enable your application to achieve faster reads in more places at the cost of a small increase in write latency.","impact":"Disabled multi-region configuration for cloud spanner threaten the availability of stored data.","report_fields":["name"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nFrom Console:\n1. Go to the Spanner page at Google Cloud Console: https://console.cloud.google.com/spanner/instances.\n2. Click Create Instance.\n3. Name your instance and allocate desired compute capacity.\n4. In 'Choose Configuration' use Multi-region configuration.","multiregional":true,"service":"Cloud Spanner"},"ecc-gcp-306":{"article":"This rule detects when Pub/Sub topic is anonymously or publicly accessible.\nPub/Sub is commonly used for asynchronous communication for applications in GCP. Messages are published to a Pub/Sub Topic and the ability to publish a message is controlled via IAM policies. It is possible to make Pub/Sub Topics publicly or anonymously accessible.","impact":"Granting permissions to allUsers or allAuthenticatedUsers allows anyone to access the Pub/Sub topic. Public notification topics can expose sensitive data and are a target for data exfiltration.","report_fields":["name"],"remediation":"From Console:\n1. Log in to the GCP Console at https://console.cloud.google.com.\n2. Navigate to Topics.\n3. Select the Pub/Sub Topic checkbox next to your Topic ID.\n4. Select the INFO PANEL tab to view the topic's permissions.\n5. To remove a specific role assignment, select allUsers or allAuthenticatedUsers, and then click Delete.","multiregional":true,"service":"Pub/Sub"},"ecc-gcp-044":{"article":"To minimize attack surface on a Database server instance, only trusted/known and required IP(s) should be white-listed to connect to it.\nDatabase Server should accept connections only from trusted Network(s)/IP(s) and restrict access from the world.","impact":"IPs/networks configured to 0.0.0.0/0 can allow access to the instance from anywhere in the world. It increases opportunities for malicious activities such as hacking, brute-force attacks, and DDoS attacks.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the Cloud SQL Instances page in the Google Cloud Console using https://console.cloud.google.com/sql/instances.\n2. Click the instance name to open its Instance details page.\n3. Under the Configuration section, click Edit configurations.\n4. Under Configuration options, expand the Connectivity section.\n5. Click the Delete icon for the authorized network 0.0.0.0/0.\n6. Click Save to update the instance.\nFrom Command Line:\nUpdate the authorized network list by dropping off any addresses:\ngcloud sql instances patch INSTANCE_NAME --authorized networks=IP_ADDR1,IP_ADDR2...","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-314":{"article":"This rule detects when 'Access Approval' is not Enabled on the project.\nGCP Access Approval enables you to require your organizations' explicit approval whenever Google support try to access your projects. You can then select users within your organization who can approve these requests through giving them a security role in IAM. All access requests display which Google Employee requested them in an email or Pub/Sub message that you can choose to Approve. This adds an additional control and logging of who in your organization approved/denied these requests.\nBy default Access Approval and its dependency of Access Transparency are not enabled.","impact":"Without Access Approval you can not ensure that Cloud Customer Care and engineering require your explicit approval whenever they need to access your customer content.","report_fields":["projectId"],"remediation":"From Console:\n1. From the Google Cloud Home, within the project you wish to enable, click on the Navigation hamburger menu in the top left. Hover over the Security Menu. Select Access Approval in the middle of the column that opens.\n2. The status will be displayed here. On this screen, there is an option to click Enroll. If it is greyed out and you see an error bar at the top of the screen that says Access Transparency is not enabled please view the corresponding reference within this section to enable it. Enabling Access Transparency: https://cloud.google.com/cloud-provider-access-management/access-transparency/docs/enable#requirements.\n3. In the second screen click Enroll.\nGrant an IAM Group or User the role with permissions to Add Users to be Access Approval message Recipients:\n1. From the Google Cloud Home, within the project you wish to enable, click on the Navigation hamburger menu in the top left. Hover over the IAM and Admin. Select IAM in the middle of the column that opens.\n2. Click the blue button the says +add at the top of the screen.\n3. In the principals field, select a user or group by typing in their associated email address.\n4. Click on the role field to expand it. In the filter field enter Access Approval Approver and select it.\n5. Click save.\nAdd a Group or User as an Approver for Access Approval Requests:\n1. As a user with the Access Approval Approver permission, within the project where you wish to add an email address to which request will be sent, click on the Navigation hamburger menu in the top left. Hover over the Security Menu. Select Access Approval in the middle of the column that opens.\n2. Click Manage Settings.\n3. Under Set up approval notifications, enter the email address associated with a Google Cloud User or Group you wish to send Access Approval requests to. All future access approvals will be sent as emails to this address.\nFrom Command Line:\n1. To update all services in an entire project, run the following command from an account that has permissions as an 'Approver for Access Approval Requests'\ngcloud access-approval settings update --project= --enrolled_services=all --notification_emails='@'","multiregional":true,"service":"Access Approval"},"ecc-gcp-260":{"article":"This rule detects when a service account with elevated privileges (editor or owner status) is assigned a table-level IAM role in the Cloud Bigtable service.","impact":"An elevated service account has more access than necessary. Without Separation of duties any individual can have all the necessary permissions to perform a malicious action.","report_fields":["selfLink"],"remediation":"1. Go to the Bigtable page at Google Cloud Console.\n2. Select an instance.\n3. Click on Tables.\n4. Select a table.\n5. Review each role and find Members having Editor or Owner access.\n6. Click Delete icon and confirm by clicking on REMOVE.","multiregional":true,"service":"Cloud Bigtable"},"ecc-gcp-342":{"article":"This policy identifies GCP Firewall rules that allow inbound traffic to the Hadoop/HDFS port (8020) from the public internet. Allowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Allowing unrestricted inbound/ingress access on TCP port 8020 (Hadoop/HDFS) via VPC network firewall rules can increase opportunities for malicious activities such as hacking and brute-force attacks.","report_fields":["selfLink"],"remediation":"To restrict all traffic, edit the reported Firewall rule as follows:\n1. Login to GCP Console.\n2. Go to VPC Network.\n3. Go to Firewall rules.\n4. Click on the reported Firewall rule.\n5. Click on Edit.\n6. Modify Source IP ranges to specific IP.\n7. Click on Save.","multiregional":true,"service":"Virtual Private Cloud"},"ecc-gcp-237":{"article":"Ensure that your Google Cloud PostgreSQL database instances are using the latest major version of PostgreSQL database in order to receive new or enhanced features and the most recent security fixes.","impact":"If your PostgreSQL database instances are not using the latest major version of the PostgreSQL database, you cannot benefit from security updates.","report_fields":["selfLink"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nPostgreSQL database version cannot be automatically upgraded within Google Cloud Platform (GCP).\nTo upgrade your Google Cloud PostgreSQL instances to the latest major version of the PostgreSQL database, you have to re-create the existing instance, export data from the existing (source) instance, and importing that data into a new (target) instance running the latest major version of PostgreSQL.","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-072":{"article":"Firewall rules provide stateful filtering of ingress/egress network traffic to GCP resources. It is recommended that no rules allow unrestricted egress access","impact":"Allowing unrestricted egress access can increase opportunities for malicious activities such as Denial of Service (DoS) attacks or Distributed Denial of Service (DDoS) attacks.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to VPC Network.\n2. Go to the Firewall Rules.\n3. Click the Firewall Rule you want to modify.\n4. Click Edit.\n5. Modify Source IP ranges to specific IP.\n6. Click Save.\nVia CLI gcloud:\n1.Update Firewall rule with new SOURCE_RANGE from below command:\ngcloud compute firewall-rules update FirewallName --allow=[PROTOCOL[PORT[-PORT]],...] --source-ranges=[CIDR_RANGE,...]l","multiregional":true,"service":"Virtual Private Cloud"},"ecc-gcp-256":{"article":"For App Engine, the App Engine firewall only applies to incoming traffic routed to your app or service.\nThis policy identifies App Engine Firewall rules that allow access from any IP address.\nBy default, any request that does not match a rule is allowed access to your app. If you need to block all requests that do not match a specific rule (excluding requests from internal services allowed by default, change the default rule's action to deny. To improve security, remove any rules that allow public access and create an individual rule for each IP that should have access to the application. For more information https://cloud.google.com/appengine/docs/standard/python/understanding-firewalls.","impact":"Allowing access from any IP address may increase opportunities for malicious activities such as unauthorized access, Man-In-The-Middle attacks (MITM), and brute-force attacks that raise the risk of resource compromise.","report_fields":["description"],"remediation":"From Console:\n1. Login to GCP Console.\n2. Go to the App Engine page at Google Cloud Console.\n3. Click on Firewall Rules.\n4. Click on the firewall rule that allows ingress access to all ('0.0.0.0/0', '0.0.0.0', '*', '::/0').\n5. Click on Delete and change action of the default rule to Deny.\n6. Create a rule for each IP or subnetwork that should have access to the application","multiregional":true,"service":"App Engine"},"ecc-gcp-054":{"article":"Node auto-upgrade keeps nodes at the current Kubernetes and OS security patch level to mitigate known vulnerabilities.","impact":"Without Node auto-upgrade, you can face security holes that have been fixed in new versions.","report_fields":["selfLink"],"remediation":"Using Google Cloud Console:\n1. Go to Kubernetes Engine using https://console.cloud.google.com/kubernetes/list.\n2. Select Kubernetes clusters for which node auto-upgrade is disabled.\n3. Click on the name of the Node pool that requires node auto-upgrade to be enabled.\n4. Within the Node pool details pane, click EDIT.\n5. Under the 'Management' heading, ensure the 'Enable auto-upgrade' box is checked.\n6. Click SAVE.\nUsing Command Line:\nTo enable node auto-upgrade for an existing cluster's Node pool, run the following command:\ngcloud container node-pools update [NODE_POOL] --cluster [CLUSTER_NAME] --zone [COMPUTE_ZONE] --enable-autoupgrade","multiregional":true,"service":"Google Kubernetes Engine"},"ecc-gcp-230":{"article":"The Automatic Restart feature configures the virtual machine restart behavior when an instance crashes or it is terminated by the system. When the feature is enabled, Google Cloud Compute Engine restarts the instance if this crashes or it is terminated. This behavior does not affect any terminations initiated by the user, for example, when the instance is taken offline through a user action, such as calling sudo shutdown.","impact":"It can reduce reliability if the Cloud Engine service does not restart automatically your VM instances when they are terminated due to non-user initiated reasons, such as maintenance events, hardware, and software failures.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the VM instances page using https://console.cloud.google.com/compute/instances.\n2. In the navigation panel, select VM instances to access the list with all the Compute Engine instances provisioned forthe selected project.\n3. Click on the name of the virtual machine (VM) instance that you want to reconfigure.\n4. On the selected resource configuration page, click EDIT to enter the instance edit mode.\n5. In the Availability policies section, turn on Automatic restart.\n6. Click Save to apply the configuration changes.\nFrom Command Line:\n1. Run compute instances set-scheduling command using the name of the instance that you want to reconfigure as identifier parameter to enable automatic restart for the selected Google Cloud VM instance. Once this setting is enabled, the selected instance will be restarted if crashes or it is terminated by the Compute Engine service:\ngcloud compute instances set-scheduling INSTANCE_NAME --zone ZONE --restart-on-failure\n2. The command output should return the URL of the reconfigured virtual machine instance:\nUpdated [https://www.googleapis.com/compute/v1/projects/PROJECT/zones/ZONE/instances/INSTANCE_NAME].","multiregional":true,"service":"Compute Engine"},"ecc-gcp-277":{"article":"Sender Policy Framework (SPF) records are used to authorize mail servers to send mail on behalf of the registered domain. It is recommended to add SPF information in TXT type record.","impact":"The absence of this records can give attacker the opportunity of email spoofing.","report_fields":["name","id"],"remediation":"Add TXT type record in Cloud DNS managed zone with Sender Policy Framework (SPF) information.\nFrom Console:\n1. Go to Cloud DNS page https://console.cloud.google.com.\n2. Click the name of the managed zone you want to add the record to.\n3. On the Zone details screen, click Add record set.\n4. On the Create record set screen, in the DNS Name field, enter the subdomain of the DNS zone.\n5. Select the Resource record type as TXT.\n6. In the TTL field, enter a numeric value for the resource record's time to live, which is the amount of time it can be cached. This value must be a positive integer. From the TTL Unit dropdown menu, select the unit of time.\n7. Depending on the resource record type you have selected, populate the remaining fields as required in the form.\n8. Enter SPF information, starting with v=spf1.\n9. Click Create.","multiregional":true,"service":"Cloud DNS"},"ecc-gcp-008":{"article":"Google Cloud Key Management Service stores cryptographic keys in a hierarchical structure designed for useful and elegant access control management.\nThe format for the rotation schedule depends on the client library that is used. For the gcloud command-line tool, the next rotation time must be in ISO or RFC3339 format, and the rotation period must be in the form INTEGER[UNIT], where units can be one of seconds (s), minutes (m), hours (h) or days (d).","impact":"A collection of files could be encrypted with the same key, and people with decrypt permissions on that key would be able to decrypt those files. Therefore, it's necessary to make sure that the rotation period is set to a specific time.","report_fields":["name"],"remediation":"From Console\n1. Go to Cryptographic Keys using https://console.cloud.google.com/security/kms.\n2. Click on the specific key ring.\n3. From the list of keys, choose the specific key and click on Right side pop up the blade (3 dots).\n4. Click on Edit rotation period.\n5. In the pop-up window, Select a new rotation period in days which should be less than 90 and then choose Starting on date (date from which the rotation period begins).\nFrom Command Line:\n1. Update and schedule rotation by running a command with the ROTATION_PERIOD and NEXT_ROTATION_TIME parameters set for each key:\ngcloud kms keys update new --keyring=KEY_RING --location=LOCATION --nextrotation-time=NEXT_ROTATION_TIME --rotation-period=ROTATION_PERIOD","multiregional":true,"service":"Cloud KMS"},"ecc-gcp-250":{"article":"GKE container scanning service scans images stored in Google Container Registry for vulnerabilities.","impact":"Without GKE container scanning service weaknesses that can either cause an accidental system failure or be intentionally exploited can be missed.","report_fields":["projectId"],"remediation":"Enable container scanning API:\n1. Go to GCR page at Google Cloud Console https://console.cloud.google.com/gcr.\n2. Select Settings.\n3. Click Enable Vulnerability Scanning.\nUsing Command Line:\ngcloud services enable containerscanning.googleapis.com","multiregional":true,"service":"Google Kubernetes Engine"},"ecc-gcp-304":{"article":"This rule detects when Vertex AI Workbench has public IPs assigned.","impact":"Vertex AI Workbenches with public IPs assigned can increase your attack surface and expose sensitive data.","report_fields":["name"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nFrom Console:\n1. Log in to the GCP Console at https://console.cloud.google.com.\n2. Navigate to Vertex AI Workbench.\n3. Scroll down to the Networking section and expand.\n4. Locate the External IP dropdown and select None.","multiregional":true,"service":"Vertex AI Workbench"},"ecc-gcp-152":{"article":"VM instance does not have any Custom metadata. Custom metadata can be used for easy identification and search.","impact":"VM Instances without any Custom metadata make identification and search difficult.","report_fields":["selfLink"],"remediation":"1. Login to GCP Console and select 'Compute Engine' from 'Compute'.\n2. Select the identified VM instance to see the details.\n3. In the details page, click on Edit and navigate to the Custom metadata section.\n4. Add the appropriate KeyValue information and save.","multiregional":true,"service":"Compute Engine"},"ecc-gcp-252":{"article":"This policy identifies GCP Kubernetes Engine clusters that are not using Release Channel for version management. The Regular release channel upgrades every few weeks and is for production users who need features not yet offered in the Stable channel. These versions have passed internal validation, but don't have enough historical data to guarantee their stability. Known issues generally have known workarounds. The Stable release channel upgrades every few months and is for production users who need stability above all else, and for whom frequent upgrades are too risky. These versions have passed internal validation and have been shown to be stable and reliable in production, based on the observed performance of those clusters.","impact":"Without a specific release channel, the complexity of version control increases, leading to the use of an unsupported version of the master version with bugs and security vulnerabilities.","report_fields":["selfLink"],"remediation":"From Console:\n1. Navigate to service 'Kubernetes Engine'\n2. From the list of available clusters, select the reported cluster.\n3. Go to the 'Release channel' configuration.\n4. To edit, Click on the 'UPGRADE AVAILABLE' or 'Edit release channel'(Whichever available).\n5. In the 'Edit version' pop-up, select the required release channel(Regular Channel/ Stable Channel/ Rapid Channel) from the 'Release channel' dropdown.\n6. Click on 'SAVE CHANGES' or 'CHANGE'. Know more on Release Channels here: https://cloud.google.com/kubernetes-engine/docs/concepts/release-channels.","multiregional":true,"service":"Google Kubernetes Engine"},"ecc-gcp-445":{"article":"The VM instance is utilizing disks that have multiple snapshots associated with them. Snapshots are used to progressively back up data from your persistent disks. Once you create a snapshot to capture the current disk state, you can utilize it to recover the data onto a new disk. Typically, you would only require the most recent snapshot to restore data in case of any issues.","impact":"Keeping redundant snapshots will increase your monthly bill.","report_fields":["sourceDisk","selfLink"],"remediation":"From Console:\n1. Go to the Snapshots page in the Google Cloud Platform Console.\n2. Click on redundant snapshot and Delete it.","multiregional":true,"service":"Compute Engine"},"ecc-gcp-070":{"article":"Armor should be enabled to protect your applications running behind the LB from common threats and vulnerabilities.","impact":"Disabled Armor will not automatically detect and prevent high volume Level 7 DDoS attacks with machine learning trained locally for your applications.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to Network services.\n2. Go to Load balancing.\n3. Click the Backend services.\n4. Choose Security policy.\n6. Click on Save.","multiregional":true,"service":"Cloud Load Balancing"},"ecc-gcp-035":{"article":"It is recommended to use Instance-specific SSH key(s) instead of using common/shared project-wide SSH key(s) to access Instances.","impact":"Using project-wide SSH keys eases the SSH key management but, if compromised, poses a security risk that can impact all the instances within the project.","report_fields":["selfLink"],"remediation":"Using Console:\n1. Go to the VM instances page using https://console.cloud.google.com/compute/instances. It will list all the instances in your project.\n2. Click on the name of the Impacted instance.\n3. Click on Edit in the toolbar.\n4. Under SSH Keys, go to the Block project-wide SSH keys checkbox.\n5. To block users with project-wide SSH keys from connecting to this instance, select Block project-wide SSH keys.\n6. Click on Save at the bottom of the page.\n7. Repeat the above steps for every impacted Instance.\nFrom CLI:\nBlock project-wide public SSH keys, set the metadata value to TRUE:\ngcloud compute instances add-metadata [INSTANCE_NAME] --metadata block-project-ssh-keys=TRUE","multiregional":true,"service":"Compute Engine"},"ecc-gcp-017":{"article":"In order to prevent unnecessary project ownership assignments to users/service-accounts and further misuses of project and resources, all roles/Owner assignments should be monitored.\nMembers (users/Service-Accounts) with a role assignment to primitive role roles/owner are Project Owners.\nProject Owner has all the privileges on a project it belongs to. These can be summarized as below:\n- All viewer permissions on All GCP Services within the project\n- Permissions for actions that modify state of All GCP Services within the project\n- Manage roles and permissions for a project and all resources within the project\n- Set up billing for a project\nGranting the owner role to a member (user/Service-Account) will allow that member to modify the Identity and Access Management (IAM) policy. Therefore, grant the owner role only if the member has a legitimate purpose to manage the IAM policy. This is because the project IAM policy contains sensitive access control data. Having a minimal set of users allowed to manage the IAM policy will simplify any auditing that may be necessary.","impact":"Project ownership gives the highest level of project privileges. The absence of a log metric filter for Project Ownership assignments/changes can result in insufficient response time to misuse of project resources, actions to assign/change ownership of the project mentioned in the description.","report_fields":["projectId"],"remediation":"From Console:\nCreate the prescribed log metric:\n1. Go to Logging/Logs-based Metrics using https://console.cloud.google.com/logs/metrics and click \"CREATE METRIC\".\n2. Click on the down arrow symbol on Filter Bar at the rightmost corner and select Convert to Advanced Filter.\n3. Clear any text and add:\n(protoPayload.serviceName=\"cloudresourcemanager.googleapis.com\")\nAND (ProjectOwnership OR projectOwnerInvitee)\nOR (protoPayload.serviceData.policyDelta.bindingDeltas.action=\"REMOVE\"\nAND protoPayload.serviceData.policyDelta.bindingDeltas.role=\"roles/owner\")\nOR (protoPayload.serviceData.policyDelta.bindingDeltas.action=\"ADD\"\nAND protoPayload.serviceData.policyDelta.bindingDeltas.role=\"roles/owner\")\n4. Click on Submit Filter. The logs display based on the filter text entered by the user.\n5. In the Metric Editor menu on the right, fill out the name field. Set Units to 1 (default) and Type to Counter. This ensures that the log metric counts the number of log entries matching the advanced logs query.\n6. Click on Create Metric.\nCreate the display prescribed Alert Policy:\n1. Identify the newly created metric under the section User-defined Metrics at\nhttps://console.cloud.google.com/logs/metrics.\n2. Click the 3-dot icon in the rightmost column for the desired metric and select Create alert from Metric. A new page opens.\n3. Fill out the alert policy configuration and click Save. Choose the alerting threshold and configuration that makes sense for the user's organization. For example, a threshold of zero(0) for the most recent value will ensure that a notification is triggered for every owner change in the project:\nSet `Aggregator` to `Count`\nSet `Configuration`:\n- Condition: above\n- Threshold: 0\n- For: most recent value\n4. Configure the desired notifications channels in the section Notifications.\n5. Name the policy and click Save.","multiregional":true,"service":"Cloud Logging"},"ecc-gcp-141":{"article":"This policy identifies Storage buckets that are encrypted with the default Google-managed keys. As a best practice, use Customer-managed key to encrypt the data in your storage bucket and ensure full control over your data.\nNote: If you lose your CMKs, GCP won't be able help you to decrypt your data.","impact":"Not using Customer-Managed Keys results in less control over aspects of your keys' lifecycle and management.","report_fields":["selfLink"],"remediation":"1. Login to GCP Portal.\n2. Go to 'Storage' (Left Panel).\n3. Select 'Browser'.\n4. Click on the reported bucket.\n5. Click on the 'Edit bucket' button.\n6. Click on 'Show advanced settings', change 'Encryption' to 'Customer-managed key' and select a customer-managed key from the drop-down list or enter the key resource ID.\n7. Click on 'Save'.","multiregional":true,"service":"Cloud Storage"},"ecc-gcp-193":{"article":"Enable VPC Flow Logs and Intranode Visibility to see pod-level traffic, even for traffic within a worker node.","impact":"Enabling Intranode Visibility makes intranode pod-to-pod traffic visible to the networking fabric. Without enabled VPC Flow Logs, you miss out on the opportunity to detect and access security issues like overly permissive security groups or network ACLs. Also, you cannot receive alerts about abnormal activities triggered within your VPC network, such as rejected connection requests or unusual levels of data transfer.","report_fields":["selfLink"],"remediation":"From Google Cloud Console:\n1. Go to Kubernetes Engine using https://console.cloud.google.com/kubernetes/list.\n2. Select Kubernetes clusters for which intranode visibility is disabled.\n3. Click on 'Edit'.\n4. Set 'Intranode visibility' to 'Enabled'.\n5. Click on 'Save'.\nFrom Command Line:\nTo enable intranode visibility for an existing cluster, run the following command:\ngcloud beta container clusters update [CLUSTER_NAME] --enable-intra-node-visibility","multiregional":true,"service":"Google Kubernetes Engine"},"ecc-gcp-175":{"article":"In order to maintain the highest level of security, all connections to an application should be secure by default.","impact":"Not using uniform bucket-level access can not guarantee that if a Storage bucket is not publicly accessible, no object in the bucket is publicly accessible either.","report_fields":["selfLink"],"remediation":"Using Console:\n1. Open the Cloud Storage browser in the Google Cloud Console using https://console.cloud.google.com/storage/browser\n2. In the list of buckets, click on the name of the desired bucket.\n3. Select the Permissions tab near the top of the page.\n4. In the textbox that starts with 'This bucket uses fine-grained access control..', click Edit.\n5. In the pop-up menu that appears, select Uniform.\n6. Click Save.\nFrom Command Line:\nUse the 'on' option in a uniformbucketlevelaccess set command:\ngsutil uniformbucketlevelaccess set on gs://BUCKET_NAME/","multiregional":true,"service":"Cloud Storage"},"ecc-gcp-199":{"article":"The 'log_error_verbosity' flag controls the verbosity/details of messages logged. Valid values are: TERSE, DEFAULT, VERBOSE, TERSE excludes the logging of DETAIL, HINT, QUERY, and CONTEXT error information. VERBOSE output includes the SQLSTATE error code, source code file name, function name, and line number that generated the error. Ensure an appropriate value is set to 'DEFAULT' or stricter.","impact":"Auditing helps in troubleshooting operational problems and also permits forensic analysis. If 'log_error_verbosity' is not set to the correct value, too many details or too few details may be logged.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the Cloud SQL Instances page in the Google Cloud Console using https://console.cloud.google.com/sql/instances.\n2. Select the PostgreSQL instance for which you want to enable the database flag.\n3. Click Edit.\n4. Scroll down to the Flags section.\n5. To set a flag that has not been set on the instance before, click Add item, choose the 'log_error_verbosity' flag from the drop-down menu and set the appropriate value.\n6. Click Save to save your changes.\n7. Confirm your changes under Flags on the Overview page.\nUsing Command Line:\n1. List all Cloud SQL database Instances\ngcloud sql instances list\n2. Configure the skip_show_database database flag for every Cloud SQL Mysql database instance using the below command:\ngcloud sql instances patch INSTANCE_NAME --database-flags log_error_verbosity=\nNote : This command will overwrite all database flags previously set. To keep those and add new ones, include the values for all flags you want set on the instance; any flag not specifically included is set to its default value. For flags that do not take a value, specify the flag name followed by an equals sign (\"=\").","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-185":{"article":"It is recommended to set the 'contained database authentication' database flag for Cloud SQL on the SQL Server instance to 'off'.","impact":"Threats are related to the USER WITH PASSWORD authentication process, which moves the authentication boundary from the Database Engine level to the database level.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the Cloud SQL Instances page in the Google Cloud Console using https://console.cloud.google.com/sql/instances.\n2. Select the SQL Server instance for which you want to enable the database flag.\n3. Click Edit.\n4. Scroll down to the Flags section.\n5. To set a flag that has not been set on the instance before, click Add item, choose the 'contained database authentication' flag from the drop-down menu, and set its value to 'off'.\n6. Click Save.\n7. Confirm the changes under Flags on the Overview page.\nFrom Command Line:\n1. List all Cloud SQL database Instances using the following command:\ngcloud sql instances list\n2. Configure the 'contained database authentication' database flag for every Cloud SQL SQL Server database instance using the below command.\ngcloud sql instances patch INSTANCE_NAME --database-flags ''contained database authentication=off''\nNote: This command will overwrite all database flags previously set. To keep those and add new ones, include the values for all flags to be set on the instance; any flag not specifically included is set to its default value. For flags that do not take a value, specify the flag name followed by an equals sign ('=').","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-131":{"article":"This policy identifies Google Kubernetes Engine (GKE) Clusters which are not configured with a private nodes feature. A private nodes feature makes your master inaccessible from the public internet, and nodes do not have public IP addresses, so your workloads run in an environment that is isolated from the internet.","impact":"External IP addresses are publicly announced, which means they are accessible to any host on the Internet. Using public nodes can increase opportunities for malicious activities such as hacking and Distributed Denial-of-Service (DDoS) attacks.","report_fields":["selfLink"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nUsing Google Cloud Console:\nTo create a new Kubernetes engine cluster with private node feature enabled, perform the following:\n1. Go to Kubernetes Engine by visiting https://console.cloud.google.com/kubernetes/list.\n2. Click CREATE CLUSTER.\n3. In the Networking section, check the 'Enable VPC-native traffic routing (using alias IP)' option.\n4. From 'IPv4 network access', select the 'Private cluster' checkbox.\n5. Configure other settings as required\n6. Click on 'Create'\nNOTE: When you create a private cluster, you must specify a /28 CIDR range for the VMs that run the Kubernetes master components.\nUsing Command Line:\nTo create a cluster with Private Nodes enabled, include the --enable-private-nodes flag within the cluster create command:\ngcloud container clusters create [CLUSTER_NAME] \\\n--enable-private-nodes\nSetting this flag also requires the setting of --enable-ip-alias and --master-ipv4-cidr=[MASTER_CIDR_RANGE].","multiregional":true,"service":"Google Kubernetes Engine"},"ecc-gcp-207":{"article":"The 'log_min_error_statement' flag defines the minimum message severity level that is considered as an error statement. Messages for error statements are logged with the SQL statement. Valid values include DEBUG5, DEBUG4, DEBUG3, DEBUG2, DEBUG1, INFO, NOTICE, WARNING, ERROR, LOG, FATAL, and PANIC. Each severity level includes the subsequent levels mentioned above. Ensure a value of ERROR or stricter is set.","impact":"If 'log_min_error_statement' is not set to the correct value, messages may not be classified as error messages appropriately. Considering general log messages as error messages would make it difficult to find actual errors and considering only stricter severity levels as error messages may skip actual errors to log their SQL statements.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the Cloud SQL Instances page in the Google Cloud Console using https://console.cloud.google.com/sql/instances.\n2. Select the PostgreSQL instance for which you want to enable the database flag.\n3. Click Edit.\n4. Scroll down to the Flags section.\n5. To set a flag that has not been set on the instance before, click Add item, choose the 'log_min_error_statement' flag from the drop-down menu and set the appropriate value.\n6. Click Save to save your changes.\n7. Confirm your changes under Flags on the Overview page.\nUsing Command Line:\n1. List all Cloud SQL database Instances\ngcloud sql instances list\n2. Configure the log_min_error_statement database flag for every Cloud SQL PosgreSQL database instance using the below command.\ngcloud sql instances patch INSTANCE_NAME --database-flags log_min_error_statement=\nNote: This command will overwrite all database flags previously set. To keep those and add new ones, include the values for all flags you want set on the instance; any flag not specifically included is set to its default value. For flags that do not take a value, specify the flag name followed by an equals sign (\"=\").","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-004":{"article":"A service account is a special Google account that belongs to an application or a VM, instead of an individual end-user. The application uses the service account to call the service's Google API so that users aren't directly involved.\nIt's recommended not to use admin access for ServiceAccount.","impact":"Enrolling ServiceAccount with Admin rights gives full access to an assigned application or a VM. A ServiceAccount Access holder can perform critical actions like delete, update or change settings, and others without user intervention.","report_fields":["member","roles"],"remediation":"From Console:\n1. Go to IAM & admin/IAM using https://console.cloud.google.com/iam-admin/iam.\n2. Go to Members.\n3. Identify User-Managed user created service account with roles containing *Admin or *admin or role matching Editor or role matching Owner.\n4. Click the Delete bin icon to remove the role from the member (service account in this case).\nFrom Command Line:\ngcloud projects get-iam-policy PROJECT_ID --format json > iam.json\n1. Using a text editor, remove Role which contains roles/*Admin or roles/*admin or matched roles/editor or roles/owner. Add a role to the bindings array that defines group members and the roles for those members.\nFor example, to grant the role roles/appengine.appViewer to the ServiceAccount which is roles/editor, you would change the example shown below as follows:\n{\n\"bindings\": [\n{\n \"members\": [\n \"serviceAccount:our-project-123@appspot.gserviceaccount.com\",\n ],\n \"role\": \"roles/appengine.appViewer\"\n},\n{\n \"members\": [\n \"user:email1@gmail.com\"\n ],\n \"role\": \"roles/owner\"\n },\n{\n \"members\": [\n \"serviceAccount:our-project-123@appspot.gserviceaccount.com\",\n \"serviceAccount:123456789012-compute@developer.gserviceaccount.com\"\n ],\n \"role\": \"roles/editor\"\n}\n],\n\"etag\": \"BwUjMhCsNvY=\"\n}\n2. Update the project's IAM policy:\ngcloud projects set-iam-policy PROJECT_ID iam.json","multiregional":true,"service":"Cloud IAM"},"ecc-gcp-289":{"article":"This policy identifies GCP Firewall rules that allow inbound traffic to the RPC port (135) from the public internet. Allowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Allowing unrestricted ingress/inbound access on TCP port 135 (RPC) through VPC network firewall rules can increase opportunities for malicious activities such as hacking (e.g. backdoor command shell), ransomware attacks, and Denial-of-Service (DoS) attacks.","report_fields":["selfLink"],"remediation":"To restrict all traffic, edit the reported Firewall rule as follows:\n1. Login to GCP Console.\n2. Go to VPC Network.\n3. Go to Firewall rules.\n4. Click on the reported Firewall rule.\n5. Click on Edit.\n6. Modify Source IP ranges to specific IP.\n7. Click on Save.","multiregional":true,"service":"Virtual Private Cloud"},"ecc-gcp-104":{"article":"Default firewall rule should not be in use.","impact":"The default firewall rule can contain vulnerabilities that lead to hacker attacks such as brute force attacks, denial of service (DoS) attacks, etc.","report_fields":["selfLink"],"remediation":"From Console:\n1. Select 'VPC network' from the side menu.\n2. Select 'Firewall rules'.\n3. Delete firewall rules containing 'default' in the network column.","multiregional":true,"service":"Virtual Private Cloud"},"ecc-gcp-273":{"article":"Adaptive Protection builds machine-learning models that detect and alert on anomalous activity, generate a signature describing the potential attack, and generate a custom Google Cloud Armor WAF rule to block the signature.","impact":"Armor with disabled Adaptive Protection will not automatically detect and prevent high volume Level 7 DDoS attacks with machine learning trained locally for your applications.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the Cloud Armor page in the Cloud Console.\n2. On the Security policies page, click the name of the security policy. The Policy details page is displayed.\n3. Click the Edit button.\n4. Under Adaptive Protection select Enable.\n5. Click Update.","multiregional":true,"service":"Cloud Armor"},"ecc-gcp-234":{"article":"By default, Cloud SQL provides encryption at rest for all data using Google-managed keys. For additional control over encryption, encrypt data using customer supplied encryption keys.\nWarning: Never delete the primary key version that you initially use when you create your instance. You can not restore the backup for the instance if the key version that was originally used when the instance was created is destroyed. Even when the key version is rotated, the original key version must be maintained. If it is destroyed, it can't be re-created.","impact":"Not using CMEK can cause problems if your project is compromised and all data will be disclosed. At least, business-critical database instances should have a CMEK-encrypted storage.","report_fields":["selfLink"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nFrom Console:\n1. Go to the Cloud SQL Instances page at Google Cloud Console.\n2. Click Create instance.\n3. Choose the database engine. Enter a name for the instance. Enter the password for the 'root'@'%' user.\n4. Set the region for your instance. Place your instance in the same region as the resources that access it.\n5. Under Configuration options, select all your configurations options until you reach Machine type and storage.\n6. Expand Machine type and storage.\n7. Under Encryption, choose Customer-managed key.\n8. Select the KMS key from the dropdown menu or manually enter the KMS_RESOURCE_ID.\n9. If the service account does not have permission to encrypt/decrypt with the selected key, a message displays. If this happens, click Grant to grant the service account the IAM role on the selected KMS key.\n10. Once the configuration options are selected, click Create.\n11. You see a message explaining the implications of using customer-managed encryption key. Read and acknowledge it to proceed further with instance creation.","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-117":{"article":"This policy identifies GCP Firewall rules that allow inbound traffic to the Oracle DB port (1521) from the public internet. Allowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Allowing unrestricted ingress/inbound access on TCP port 1521 through VPC network firewall rules can increase opportunities for malicious activities such as denial-of-service attacks, brute-force and man-in-the-middle (MITM) attacks, and can ultimately lead to data loss.","report_fields":["selfLink"],"remediation":"To restrict all traffic, edit the reported Firewall rule as follows:\n1. Login to GCP Console.\n2. Go to VPC Network.\n3. Go to Firewall rules.\n4. Click on the reported Firewall rule.\n5. Click on Edit.\n6. Modify Source IP ranges to specific IP.\n7. Click on Save.","multiregional":true,"service":"Virtual Private Cloud"},"ecc-gcp-309":{"article":"This rule detects when a Dataproc cluster is configured with public IP. This security misconfiguration could put your data at risk of accidental exposure, because a public IP accompanied by an open firewall rule allows potentially unauthorized access to the Dataproc VMs.","impact":"Public IP addresses allow potentially unauthorized access to Dataproc virtual machines.","report_fields":["clusterName","projectId"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nFrom Console:\n1. Log in to the GCP Console.\n2. Navigate to Dataproc https://console.cloud.google.com/dataproc.\n2. Click on the name of the Dataproc cluster that you want to re-create and collect all the configuration information available for the selected resource.\n7. Go back to the Clusters console and click on the CREATE CLUSTER button from the dashboard top menu to initiate the Dataproc cluster setup process.\n3. Select Customize Cluster to view Network Configuration settings.\n4. Locate the Internal IP Only section and select the checkbox next to Configure all instances to have only internal IP addresses.\n5. Configure the required configuration and click Create.","multiregional":true,"service":"Dataproc"},"ecc-gcp-103":{"article":"Ensure that protection against accidental deletion of instances is enabled","impact":"An instance without deletion protection is not protected from accidental or intentional deletion.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the VM instances page.\n2. Choose VM instance.\n3. Click the Edit button.\n4. Toggle the Enable deletion protection checkbox.\n5. Click Save.","multiregional":true,"service":"Compute Engine"},"ecc-gcp-271":{"article":"This rule finds networks without deny egress rule for all resources. Specific IP addresses or ports should be opened in the firewall on an as-needed basis.","impact":"If network does not have deny egress rule for all resources, then any GCP resource in this network can connect to any external destination.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the firewall page https://console.cloud.google.com/networking/firewalls.\n2. Select CREATE FIREWALL RULE.\n3. Enter appropriate rule name, for example deny-egress-all.\n4. Turn on logging in the Log section.\n5. Select the appropriate Network.\n6. Enter Priority as 65535.\n7. Select Direction of traffic as Egress.\n8. Select Action on match as Deny.\n9. Select Targets as All instances in the network.\n10. Select Source filter as IP ranges and enter 0.0.0.0/0.\n11. Select Protocols and ports as Deny all.\n12. Click CREATE.","multiregional":true,"service":"Virtual Private Cloud"},"ecc-gcp-346":{"article":"Network tags are used by networks to identify which Compute Engine virtual machine (VM) instances are subject to certain firewall rules and network routes. It is recommended to use network tags instead of setting rules that will effect all of the instances in a network.","impact":"Instances without tags have no micro-segmentation and are only controlled by high level firewall rules that are applicable to the entire network.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the VM instances page using https://console.cloud.google.com/compute/instances\n2. Click on the instance you want to add or update tags for.\n3. Click EDIT.\n4. Make changes in the Network tags section.\n5. Save your changes.\nFrom Command Line:\n1. Run the following command:\ngcloud compute instances add-tags INSTANCE_NAME --tags TAG_NAME","multiregional":true,"service":"Compute Engine"},"ecc-gcp-020":{"article":"It is recommended that a metric filter and alarm be established for VPC Network Firewall rule changes.","impact":"Lack of monitoring and logging of VPC Network Firewall rule changes can lead to insufficient response time to detect suspicious activities.","report_fields":["projectId"],"remediation":"From Console:\nCreate the prescribed log metric:\n1. Go to Logging/Logs-based Metrics using https://console.cloud.google.com/logs/metrics and click 'CREATE METRIC'.\n2. Click the down arrow symbol on Filter Bar at the rightmost corner and select Convert to Advanced Filter.\n3. Clear any text and add:\nresource.type=\"gce_firewall_rule\"\nAND (protoPayload.methodName:\"compute.firewalls.patch\"\nOR protoPayload.methodName:\"compute.firewalls.insert\"\nOR protoPayload.methodName:\"compute.firewalls.delete\")\n4. Click Submit Filter. Display logs appear based on the filter text entered by the user.\n5. In the Metric Editor menu on the right, fill out the name field. Set Units to 1 (default) and Type to Counter. This ensures that the log metric counts the number of log entries matching the advanced logs query.\n6. Click Create Metric.\nCreate the prescribed Alert Policy:\n1. Identify the newly created metric under the User-defined Metrics section at https://console.cloud.google.com/logs/metrics.\n2. Click the 3-dot icon in the rightmost column for the new metric and select Create alert from Metric. A new page displays.\n3. Fill out the alert policy configuration and click Save. Choose the alerting threshold and configuration that makes sense for the user's organization. For example, a threshold of zero(0) for the most recent value ensures that a notification is triggered for every owner change in the project:\nSet 'Aggregator' to 'Count'\nSet 'Configuration':\n- Condition: above\n- Threshold: 0\n- For: most recent value\n4. Configure the desired notifications channels in the Notifications section.\n5. Name the policy and click Save.\nFrom Google Cloud CLI:\nCreate the prescribed Log Metric\ngcloud logging metrics create\nCreate the prescribed alert policy\ngcloud alpha monitoring policies create","multiregional":true,"service":"Cloud Logging"},"ecc-gcp-432":{"article":"In a private cluster, the master node has two endpoints, a private and public endpoint. The private endpoint is the internal IP address of the master, behind an internal load balancer in the master's VPC network. Nodes communicate with the master using the private endpoint. The public endpoint enables the Kubernetes API to be accessed from outside the master's VPC network. \nAlthough Kubernetes API requires an authorized token to perform sensitive actions, a vulnerability could potentially expose the Kubernetes publically with unrestricted access. Additionally, an attacker may be able to identify the current cluster and Kubernetes API version and determine whether it is vulnerable to an attack. Unless required, disabling public endpoint will help prevent such threats, and require the attacker to be on the master's VPC network to perform any attack on the Kubernetes API.\nBy default, the Private Endpoint is disabled","impact":"Failure to disable the public endpoint of the master node in a private cluster may expose the Kubernetes API to unrestricted access from outside the master's VPC network, increasing the risk of vulnerabilities and unauthorized attacks on the cluster.","report_fields":["selfLink"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nUsing Google Cloud Console\n1. Go to Kubernetes Engine by visiting https://console.cloud.google.com/kubernetes/list.\n2. Click CREATE CLUSTER.\n3. Configure the cluster as desired then click 'Availability, networking, security, and additional features'.\n4. Under 'Network Security' ensure the 'Private cluster' checkbox is checked\n5. Clear the 'Access master using its external IP address' checkbox.\n6. Configure other settings as required.\n7. Click CREATE.\nUsing Command Line\nCreate a cluster with a Private Endpoint enabled and Public Access disabled by including the --enable-private-endpoint flag within the cluster create command:\n gcloud container clusters create [CLUSTER_NAME] --enable-private-endpoint\nSetting this flag also requires the setting of --enable-private-nodes, --enable-ip-alias and --master-ipv4-cidr=[MASTER_CIDR_RANGE].","multiregional":true,"service":"Google Kubernetes Engine"},"ecc-gcp-087":{"article":"Ensure that SSL/TLS certificates stored in Cloud SQL are renewed one week before expiry.","impact":"SSL/TLS certificates not renewed prior to their expiration date become invalid, and the communication between a client and a GCP resource that implements the certificates is no longer secure.","report_fields":["selfLink"],"remediation":"You must take the following steps to complete the rotation:\n1.Download the new server certificate information.\n2.Update your clients to use the new server certificate information.\n3.Complete the rotation, which moves the currently active certificate into the \"previous\" slot and updates the newly added certificate to be the active certificate.\nDownload the new server certificate information:\n1.Go to the Cloud SQL Instances page in the Google Cloud Platform Console.\n2.Click the instance name to open its Instance details page.\n3.Select the CONNECTIONS tab.\n4.Scroll down to the Configure SSL server certificates section.\n5.Click Create new certificate.\n6.Scroll down to Download SSL server certificates section.\n7.Click Download.\nThe server certificate information, encoded as a PEM file, is displayed and can be downloaded to your local environment.\n8.Update all of your clients to use the new information.\nAfter you have updated your clients, complete the rotation:\n1.Return to the Configure SSL server certificates section.\n2.Click Rotate certificate.\n3.Confirm that your clients are connecting properly.\nIf any clients are not connecting using the newly rotated certificate, you can click Rollback certificate to roll back to the previous configuration.\nLink: https://cloud.google.com/sql/docs/mysql/configure-ssl-instance.","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-448":{"article":"If you have workloads that are fault tolerant, like HPC, big data, media transcoding, CI/CD pipelines or stateless web applications, using preemptible VMs to batch-process them can provide massive cost savings. \nUsing preemptible VMs in your architecture is a great way to scale compute at a discounted rate, but you need to be sure that the workload can handle the potential interruptions if the VM needs to be reclaimed.","impact":"Not using preemptible VMs in your architecture can result in missed opportunities to efficiently scale your compute resources while enjoying cost savings.","report_fields":["selfLink"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nFrom Console:\n1. Go to the VM compute metadata page using https://console.cloud.google.com/compute/metadata.\n2. Click on reported instance and Delete it.\n3. Click CREATE INSTANCE.\n4. Fill out the desired configuration for your instance.\n5. Under the 'Availability policies' section, choose Spot VM provisioning model and other desired setting for VM provisioning model.\n6. Click Create.","multiregional":true,"service":"Compute Engine"},"ecc-gcp-032":{"article":"Private Google Access enables virtual machine instances in a subnet to reach Google APIs and services using an internal IP address rather than an external IP address. External IP addresses are routable and reachable over the Internet. Internal (private) IP addresses are internal to Google Cloud Platform and are not routable or reachable over the Internet. You can use Private Google Access to allow VMs without Internet access to reach Google APIs, services, and properties that are accessible over HTTP/HTTPS.","impact":"Disabling Google Access private access can increase the scope for malicious acts through public IP addresses, such as hacking, Man-In-The-Middle (MITM) attacks, and brute force attacks that increase the risk of resource compromise.","report_fields":["selfLink"],"remediation":"Using Console:\n1. Go to VPC network GCP Console using https://console.cloud.google.com/networking/networks/list.\n2. Click the name of a subnet, the Subnet details page is displayed.\n3. Click on the EDIT button.\n4. Set Private Google access to On.\n5. Click on Save.\nUsing Command Line:\nTo set Private Google access for an network subnets, run the following command:\ngcloud compute networks subnets update [SUBNET_NAME] --region [REGION] --enable-private-ip-google-access","multiregional":true,"service":"Virtual Private Cloud"},"ecc-gcp-186":{"article":"It is recommended to configure Second Generation SQL instance to use private IPs instead of public IPs.","impact":"To lower the organization's attack surface, Cloud SQL databases should not have public IPs. Private IPs provide improved network security and lower latency for your application.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the Cloud SQL Instances page in the Google Cloud Console https://console.cloud.google.com/sql/instances.\n2. Click the instance name to open its Instance details page.\n3. Select the Connections tab.\n4. Deselect the Public IP checkbox.\n5. Click Save to update the instance.\nFrom Command Line:\n1. For every instance remove its public IP and assign a private IP instead:\ngcloud sql instances patch INSTANCE_NAME --network=VPC_NETWOR_NAME --noassign-ip\n2. Confirm the changes using the following command:\ngcloud sql instances describe INSTANCE_NAME","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-063":{"article":"Create and use minimally privileged Service accounts to run GKE cluster nodes instead of using a Compute Engine default Service account. Unnecessary permissions could be abused in the case of a node compromise.","impact":"The Compute Engine default service account has broad access by default. But it also has more permissions than are required to run your Kubernetes Engine cluster, which makes it an easy target for attackers.","report_fields":["selfLink"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nUsing Google Cloud Console:\nFirstly, create a minimally privileged service account.\n1. Go to Service Accounts by visiting https://console.cloud.google.com/iam-admin/serviceaccounts.\n2. Click on CREATE SERVICE ACCOUNT.\n3. Enter Service Account Details.\n4. Click CREATE.\n5. Within Service Account permissions add the following roles:\n\u2022 Logs Writer\n\u2022 Monitoring Metric Writer\n\u2022 Monitoring Viewer.\n6. Click CONTINUE.\n7. Grant users access to this service account and create keys as required.\n8. Click DONE.\nTo create a Node pool to use a Service account:\n1. Go to Kubernetes Engine using https://console.cloud.google.com/kubernetes/list.\n2. Click on the cluster name within which the Node pool will be launched.\n3. Click on ADD NODE POOL.\n4. Within the Node Pool options, under the 'Security' heading, select the minimally privileged service account from the Service Account drop-down list.\n5. Click on SAVE to launch the Node pool. You will need to migrate your workloads to the new Node pool and delete Node pools that use the default service account to complete the remediation.\nUsing Command Line:\nFirstly, create a minimally privileged service account:\ngcloud iam service-accounts create [SA_NAME] --display-name 'GKE Node Service Account'\nexport NODE_SA_EMAIL=`gcloud iam service-accounts list --format='value(email)' --filter='displayNameGKE Node Service Account'`\nGrant the following roles to the service account:\nexport PROJECT_ID=`gcloud config get-value project`\ngcloud projects add-iam-policy-binding $PROJECT_ID --member serviceAccount$NODE_SA_EMAIL --role roles/monitoring.metricWriter\ngcloud projects add-iam-policy-binding $PROJECT_ID --member serviceAccount$NODE_SA_EMAIL --role roles/monitoring.viewer\ngcloud projects add-iam-policy-binding $PROJECT_ID --member serviceAccount$NODE_SA_EMAIL --role roles/logging.logWriter\nTo create a new Node pool using the Service account, run the following command:\ngcloud container node-pools create [NODE_POOL] --service-account=[SA_NAME]@[PROJECT_ID].iam.gserviceaccount.com --cluster=[CLUSTER_NAME] --zone [COMPUTE_ZONE]\nYou will need to migrate your workloads to the new Node pool, and delete Node pools that use the default service account to complete the remediation.","multiregional":true,"service":"Google Kubernetes Engine"},"ecc-gcp-258":{"article":"This rule detects when a Cloud Bigtable backup is created with an expiration date of 29 days or less. A table backup should persist for at least 30 days or longer before it is replaced with a new backup, preferably on an automated cadence.","impact":"Too short an expiration time reduces the benefit of backing up your data to protect against loss.","report_fields":["sourceTable","name"],"remediation":"From Console:\n1. Go to the Bigtable page at Google Cloud Console https://console.cloud.google.com/bigtable/instances.\n2. Select an instance.\n3. Click on Tables.\n4. Select a table.\n5. Click on Create Backup.\n6. Enter a unique ID for the backup.\n7. Set an expiration date for 30 days or more.\n8. Click on Create.","multiregional":true,"service":"Cloud Bigtable"},"ecc-gcp-166":{"article":"Checks for Google Kubernetes Engine (GKE) clusters that are configured to use the default network. It is recommended not to use the default network on GKE.","impact":"Since GKE uses the default network when creating routes and firewalls for the cluster, this does not guarantee that it will meet your security and network requirements for inbound and outbound traffic. As a result, it can lead to malicious activity such as hacking, data loss, etc.","report_fields":["selfLink"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nFrom Console:\n1. Navigate to the 'Kubernetes Engine', and select 'Clusters'.\n2. Create cluster.\n3. Set the new cluster parameters as per your requirement and ensure 'Network' is not set to 'default' under Networking section.\nTo delete the old cluster:\n1. Navigate to the 'Kubernetes Engine', and select 'Clusters'.\n2. Select the old cluster.\n3. Click DELETE.\n4. On 'Delete a cluster' popup dialog, click DELETE to confirm the deletion of the cluster.","multiregional":true,"service":"Google Kubernetes Engine"},"ecc-gcp-449":{"article":"Compute snapshot without label info","impact":"Compute snapshot without label information make identification and search difficult.","report_fields":["selfLink","sourceDisk"],"remediation":"From Console:\n1. Go to the Snapshots page in the Google Cloud Platform Console.\n2. Click on reported Snapshot name.\n3. Click \"Add label\" and add proper key-value pair.\n4. Save changes.","multiregional":true,"service":"Compute Engine"},"ecc-gcp-227":{"article":"This policy identifies GCP compute engine images that are not encrypted using customer-managed keys. If you use a customer-managed encryption key, your encryption keys are stored within Cloud KMS. The project that holds your encryption keys can then be independent of the project that contains your buckets, thus allowing for better separation of duties.","impact":"Not using CMEK results in less control over aspects of the lifecycle and management of keys.","report_fields":["selfLink"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nThe image encryption can't be modified once the image is created for a disk. To remediate we have to create a new image for the same disk by replicating all the settings from current image and deleting the old image.\nFollow below steps to create a compute image.\nFrom console:\n1. Login to google cloud console.\n2. Navigate to service 'Compute Engine'.\n3. On left menu, go to Section 'Storage' and under that select 'Images'.\n4. Click on 'CREATE IMAGE', replicate all the details from existing image.\n5. Under section 'Encryption', select 'Customer-managed key'.\n6. Provide the encryption key by selecting it from the dropdown menu of 'Select a customer-managed key'.\n7. Click on 'Create'.","multiregional":true,"service":"Compute Engine"},"ecc-gcp-124":{"article":"Ensure your GKE Master node version is supported. This policy checks your GKE master node version and generates an alert if the running version is unsupported.","impact":"Using an unsupported version of the master has bugs and security vulnerabilities found in a supported minor version.","report_fields":["selfLink"],"remediation":"Note: Upgrading a node pool may disrupt workloads running in that node pool. To avoid this, you can create a new node pool with the desired version and migrate the workload. After migration, you can delete the old node pool.\nManually initiate a master upgrade.\n1. From Google Cloud Platform Console, go to the Google Kubernetes Engine menu.\n2. Select the desired cluster, clik Actions, then click edit Edit.\n3. On the Cluster details page, click the Nodes tab.\n4. In the Node Pools section, click the name of the node pool that you want to upgrade.\n5. Click Edit.\n6. Click Change under Node version.\n7. Select the desired version, then click Change.","multiregional":true,"service":"Google Kubernetes Engine"},"ecc-gcp-021":{"article":"It is recommended that a metric filter and alarm be established for VPC network route changes.","impact":"Lack of monitoring and logging of route table changes can lead to insufficient response time to detect accidental or intentional modifications that may result in in uncontrolled network traffic.","report_fields":["projectId"],"remediation":"From Console:\nCreate the prescribed Log Metric:\n1. Go to Logging/Logs-based Metrics using https://console.cloud.google.com/logs/metrics and click 'CREATE METRIC'.\n2. Click the down arrow symbol on Filter Bar at the rightmost corner and select Convert to Advanced Filter\n3. Clear any text and add:\nresource.type=\"gce_route\"\nAND (protoPayload.methodName:\"compute.routes.delete\"\nOR protoPayload.methodName:\"compute.routes.insert\")\n4. Click Submit Filter. Display logs appear based on the filter text entered by the user.\n5. In the Metric Editor menu on the right, fill out the name field. Set Units to 1 (default) and Type to Counter. This ensures that the log metric counts the number of log entries matching the user's advanced logs query.\n6. Click Create Metric.\nCreate the prescribed alert policy:\n1. Identify the newly created metric under the User-defined Metrics section at https://console.cloud.google.com/logs/metrics.\n2. Click the 3-dot icon in the rightmost column for the new metric and select Create alert from Metric. A new page displays.\n3. Fill out the alert policy configuration and click Save. Choose the alerting threshold and configuration that makes sense for the user's organization. For example, a threshold of zero(0) for the most recent value ensures that a notification is triggered for every owner change in the project:\nSet 'Aggregator' to 'Count'\nSet 'Configuration':\n- Condition: above\n- Threshold: 0\n- For: most recent value\n4. Configure the desired notification channels in the Notifications section.\n5. Name the policy and click Save\nFrom Google Cloud CLI:\nCreate the prescribed Log Metric:\ngcloud logging metrics create\nCreate the prescribed the alert policy:\ngcloud alpha monitoring policies create","multiregional":true,"service":"Cloud Logging"},"ecc-gcp-173":{"article":"Compute instances should not be configured to have external IP addresses.","impact":"An instance with a public IP address could potentially be compromised, and an attacker could gain anonymous access to it and other resources connected to this instance, which may lead to malicious activity with sensitive data.","report_fields":["selfLink"],"remediation":"Using Console:\n1. Go to the VM instances page using 'https://console.cloud.google.com/compute/instances'.\n2. Click on the instance name to go the the Instance detail page.\n3. Click on 'Edit'.\n4. For each Network interface, ensure that External IP is set to 'None'.\n5. Click on 'Done' and then click on 'Save'.\nUsing Command Line:\n1. Describe the instance properties:\ngcloud compute instances describe INSTANCE_NAME --zone=ZONE\n2. Identify the access config name that contains the external IP address. This access config appears in the following format:\nnetworkInterfaces:\n- accessConfigs:\n - kind: compute#accessConfig\n name: External NAT\n natIP: 130.211.181.55\n type: ONE_TO_ONE_NAT\n3. Delete the access config:\ngcloud compute instances delete-access-config INSTANCE_NAME --zone=ZONE -- access-config-name ''ACCESS_CONFIG_NAME''\nIn the above example, the ACCESS_CONFIG_NAME is External NAT. The name of your access config might be different.","multiregional":true,"service":"Compute Engine"},"ecc-gcp-061":{"article":"A private cluster is a cluster that makes your master inaccessible from the public internet. In a private cluster, nodes do not have public IP addresses, so your workloads run in an environment that is isolated from the internet. Nodes should have addresses only in the private RFC 1918 address space. Nodes and masters communicate with each other privately using VPC peering.","impact":"Without Private cluster, networking suffers higher latency, and services are exposed to the public Internet, which increases the attack surface.","report_fields":["selfLink"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nUsing Console:\n1. Go to Kubernetes GCP Console using https://console.cloud.google.com/kubernetes/list?.\n2. Click on CREATE CLUSTER.\n3. Choose the required name/value for cluster fields.\n4. Click on More.\n5. From the Private cluster drop-down menu, select Enabled.\n6. Verify that VPC native (alias IP) is set to Enabled.\n7. Set Master IP range to as per your required IP range.\n8. Click on Create.\nUsing Command Line:\nTo create cluster with Private cluster enabled, run the following command:\ngcloud beta container clusters create [CLUSTER_NAME] --zone [COMPUTE_ZONE] --private-cluster --master-ipv4-cidr 172.16.0.16/28 --enable-ip-alias --create-subnetwork \"\"","multiregional":true,"service":"Google Kubernetes Engine"},"ecc-gcp-228":{"article":"When Google Cloud Compute Engine performs periodic infrastructure maintenance it can migrate your virtual machine instances to other hardware without downtime. The virtual machine maintenance behavior determines whether the VM instances are live migrated or terminated during a maintenance event. To ensure that your Google Cloud VM instances are migrated to new hardware, set \"On Host Maintenance\" configuration setting to \"Migrate\".","impact":"If you have not configured the instance`s availability policy to use live migration instead of instance termination. Compute Engine will not live migrate your VM instance. This can not prevents your production applications from experiencing disruptions during maintenance events.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the VM instances page using https://console.cloud.google.com/compute/instances.\n2. Click on the name of the virtual machine (VM) instance that you want to reconfigure.\n3. On the selected resource configuration page, click EDIT to access the instance edit mode.\n4. In the Availability policies section, select Migrate VM instance (recommended) from the On host maintenance dropdown list, to migrate (instead of terminate) the selected Google Cloud VM instance during maintenance events.\n5. Click Save to apply the configuration changes.\nFrom Command Line:\n1. Run compute instances set-scheduling command using the name of the instance that you want to reconfigure as identifier parameter, to change the maintenance behavior for the selected Google Cloud virtual machine instance from TERMINATE to MIGRATE. Once the maintenance policy is set to MIGRATE, the VM instance should be migrated to a new host during maintenance:\ngcloud compute instances set-scheduling INSTANCE_NAME --zone ZONE --maintenance-policy=MIGRATE\n2. The command output should return the URL of the reconfigured virtual machine instance:\nUpdated [https://www.googleapis.com/compute/v1/projects/PROJECT_NAME/zones/ZONE/instances/INSTANCE_NAME].","multiregional":true,"service":"Compute Engine"},"ecc-gcp-119":{"article":"This policy identifies GCP Firewall rules that allow inbound traffic to the PostgreSQL port (5432) from the public internet. Allowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Allowing unrestricted inbound access on TCP port 5432 (PostgreSQL Database) via VPC network firewall rules can increase opportunities for malicious activities such as hacking, brute-force attacks, DDoS, and SQL injection attacks.","report_fields":["selfLink"],"remediation":"To restrict all traffic, edit the reported Firewall rule as follows:\n1. Login to GCP Console.\n2. Go to VPC Network.\n3. Go to Firewall rules.\n4. Click on the reported Firewall rule.\n5. Click on Edit.\n6. Modify Source IP ranges to specific IP.\n7. Click on Save.","multiregional":true,"service":"Virtual Private Cloud"},"ecc-gcp-218":{"article":"Enabling OS login binds SSH certificates to IAM users and facilitates effective SSH certificate management. If enable-oslogin is not present or set to false in metadata then it means OS login is disabled.","impact":"if an instance based on template with not enabled osLogin, It makes difficult centralized and automated SSH key pair management, which is useless in handling cases like response to compromised SSH key pairs and/or revocation of external/third-party/Vendor users.","report_fields":["selfLink"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nUsing Console:\n1. Go to the instance template page using https://console.cloud.google.com/compute/instanceTemplates/list.\n2. To enable oslogin, create a new template and set enable-oslogin=true in instance template metadata.\n3. Repeat the above steps for every impacted Instance template.","multiregional":true,"service":"Compute Engine"},"ecc-gcp-093":{"article":"It is recommended to use HTTPS instead of HTTP to encrypt the communication between the application clients and the application Load balancer.","impact":"An HTTP protocol is not a secure method of transmitting data. Any person monitoring the Internet traffic can see unencrypted data, which leads to a breach of confidentiality.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the Load balancing page in the Google Cloud Platform Console.\n2. Find the target load balancer name.\n3. Click Edit (pencil).\n4. Configure Backend.\n5. Choose HTTPS as a protocol in the backend configuration.","multiregional":true,"service":"Cloud Load Balancing"},"ecc-gcp-215":{"article":"To improve the security of your instance templates, use instance-specific SSH key(s) instead of using common/shared project-wide SSH key(s) to access instances.","impact":"Creating an instance based on template with project-wide SSH keys eases the SSH key management but, if compromised, poses a security risk that can impact all the instances within the project.","report_fields":["selfLink"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nUsing Console:\n1. Go to the VM instances page using https://console.cloud.google.com/compute/instanceTemplates/list.\n2. To block users with project-wide SSH keys from connecting to the instance, create a new template and set block-project-wide-ssh-keys=true in instance template metadata.\n3. Repeat the above steps for every impacted Instance template.","multiregional":true,"service":"Compute Engine"},"ecc-gcp-446":{"article":"VM instance template does not have any Labels. Labels can be used for easy identification and search.","impact":"VM instance templates without label information make identification and search difficult.","report_fields":["selfLink"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nUsing Console:\n1. Go to the instance template page using https://console.cloud.google.com/compute/instanceTemplates/list.\n2. Delete affected template.\n3. Create a new instance template with labels.","multiregional":true,"service":"Compute Engine"},"ecc-gcp-025":{"article":"The default network has a preconfigured network configuration and automatically generates insecure firewall rules. To prevent the use of a default network, a project should not have a default network.","impact":"Automatically created firewall rules of the default network do not get audit logged and cannot be configured to enable firewall rule logging. A number of open ports are available by default, including port 22. This may increase opportunities for malicious activities such as unauthorized access, Man-In-The-Middle attacks (MITM), and brute-force attacks that raise the risk of resource compromise.","report_fields":["selfLink"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nFrom Console:\n1. Go to the VPC networks page by visiting: https://console.cloud.google.com/networking/networks/list.\n2. Click the network named default.\n3. On the network detail page, click EDIT.\n4. Click DELETE VPC NETWORK.\n5. If needed, create a new network to replace the default network.\nFrom Command Line:\nFor each Google Cloud Platform project,\n1. Delete the default network:\ngcloud compute networks delete default\n2. If needed, create a new network to replace it:\ngcloud compute networks create NETWORK_NAME","multiregional":true,"service":"Virtual Private Cloud"},"ecc-gcp-001":{"article":"Use corporate login credentials instead of personal accounts, such as Gmail accounts.","impact":"Organizations do not have any control over personal accounts. Thus, your administrators may not see and control these accounts through the Google Admin console.","report_fields":["member","roles"],"remediation":"To find if the GCP project is associated with the corporate organization domain:\n1. List the accounts that have been granted access to that project\ngcloud projects get-iam-policy PROJECT_ID\n2. Also list the accounts added on each folder\ngcloud resource-manager folders get-iam-policy FOLDER_ID\n3. And list your organization's IAM policy\ngcloud organizations get-iam-policy ORGANIZATION_ID\nNote No email accounts outside the organization domain should be granted permissions in the IAM policies.\nFollow the documentation and setup corporate login accounts.","multiregional":true,"service":"Cloud IAM"},"ecc-gcp-023":{"article":"It is recommended that a metric filter and alarm be established for Cloud Storage Bucket IAM changes.","impact":"Lack of monitoring and logging of Cloud Storage IAM permission changes can lead to insufficient response time to detect and correct permissions on sensitive cloud storage buckets and objects inside the bucket.","report_fields":["projectId"],"remediation":"From Console:\nCreate the prescribed log metric:\n1. Go to Logging/Logs-based Metrics using https://console.cloud.google.com/logs/metrics and click 'CREATE METRIC'.\n2. Click the down arrow symbol on Filter Bar at the rightmost corner and select Convert to Advanced Filter.\n3. Clear any text and add:\nresource.type=\"gcs_bucket\"\nAND protoPayload.methodName=\"storage.setIamPermissions\"\n4. Click Submit Filter. Display logs appear based on the filter text entered by the user.\n5. In the Metric Editor menu on the right, fill out the name field. Set Units to 1 (default) and Type to Counter. This ensures that the log metric counts the number of log entries matching the user's advanced logs query.\n6. Click Create Metric.\nCreate the prescribed Alert Policy:\n1. Identify the newly created metric under the User-defined Metrics section at https://console.cloud.google.com/logs/metrics.\n2. Click the 3-dot icon in the rightmost column for the new metric and select Create alert from Metric. A new page appears.\n3. Fill out the alert policy configuration and click Save. Choose the alerting threshold and configuration that makes sense for the user's organization. For example, a threshold of zero (0) for the most recent value will ensure that a notification is triggered for every owner change in the project:\nSet 'Aggregator' to 'Count'\nSet 'Configuration':\n- Condition: above\n- Threshold: 0\n- For: most recent value\n4. Configure the desired notifications channels in the Notifications section.\n5. Name the policy and click Save.\nFrom Google Cloud CLI:\nCreate the prescribed Log Metric:\ngcloud beta logging metrics create\nCreate the prescribed alert policy:\ngcloud alpha monitoring policies create","multiregional":true,"service":"Cloud Logging"},"ecc-gcp-253":{"article":"This policy identifies GCP Kubernetes Engine clusters for which workload identity is disabled. Manual approaches for authenticating Kubernetes workloads violates the principle of least privilege on a multi-tenanted node when one pod needs to have access to a service, but every other pod on the node that uses the service account does not. Enabling Workload Identity manages the distribution and rotation of Service account keys for the workloads to use.","impact":"Disabled Workload Identity does not allow you to assign distinct, fine-grained identities and authorization for each application in your cluster.","report_fields":["selfLink"],"remediation":"The GKE Metadata Server requires Workload Identity to be enabled on a cluster. Modify the cluster to enable Workload Identity and enable the GKE Metadata Server. Using Google Cloud Console\nFrom Console:\n1. Go to Kubernetes Engine by visiting https://console.cloud.google.com/kubernetes/list\n2. From the list of clusters, select the cluster for which Workload Identity is disabled.\n3. Click on EDIT\n4. Set Workload Identity to 'Enabled' and set the Workload Identity Namespace to the namespace of the Cloud project containing the cluster, e.g: \n[PROJECT_ID].svc.id.goog\n5. Click SAVE and wait for the cluster to update\n6. Once the cluster has updated, select each Node pool within the cluster Details page\n7. For each Node pool, select EDIT within the Node pool details page\n8. Within the Edit node pool pane, check the 'Enable GKE Metadata Server checkbox'\n9. Click SAVE.\nUsing Command Line:\ngcloud beta container clusters update [CLUSTER_NAME] --identity-namespace=[PROJECT_ID].svc.id.goog\nNote that existing Node pools are unaffected. New Node pools default to --workloadmetadata-from-node=GKE_METADATA_SERVER.\nTo modify an existing Node pool to enable GKE Metadata Server:\ngcloud beta container node-pools update [NODEPOOL_NAME] --cluster=[CLUSTER_NAME] --workload-metadata-from-node=GKE_METADATA_SERVER\nYou may also need to modify workloads in order for them to use Workload Identity as described within https://cloud.google.com/kubernetes-engine/docs/how-to/workloadidentity.","multiregional":true,"service":"Google Kubernetes Engine"},"ecc-gcp-288":{"article":"Check your Google Cloud VPC network firewall rules for inbound rules that allow unrestricted access (i.e. 0.0.0.0/0) to any hosts using ICMP and restrict the ICMP-based access to trusted IP addresses/IP ranges only, in order to implement the principle of least privilege (POLP) and reduce the attack surface.","impact":"Allowing unrestricted inbound/ingress ICMP access using VPC network firewall rules can increase opportunities for malicious activities such as Denial-of-Service (DoS) attacks, Smurf and Fraggle attacks.","report_fields":["selfLink"],"remediation":"To restrict all traffic, edit the reported Firewall rule as follows:\n1. Login to GCP Console.\n2. Go to VPC Network.\n3. Go to Firewall rules.\n4. Click on the reported Firewall rule.\n5. Click on Edit.\n6. Modify Source IP ranges to specific IP.\n7. Click on Save.","multiregional":true,"service":"Virtual Private Cloud"},"ecc-gcp-184":{"article":"It is recommended to set the 'cross db ownership chaining' database flag for Cloud SQL SQL Server instance to 'off'.","impact":"This server option allows a user to control cross-database ownership chaining at the database level or allow cross-database ownership chaining for all databases. Enabling 'cross db ownership' is not recommended unless all of the databases hosted by the instance of SQL Server must participate in cross-database ownership chaining, and you are aware of the security implications of this setting.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the Cloud SQL Instances page in the Google Cloud Console using https://console.cloud.google.com/sql/instances.\n2. Select the SQL Server instance for which you want to enable the database flag.\n3. Click Edit.\n4. Scroll down to the Flags section.\n5. To set a flag that has not been set on the instance before, click Add item, choose the 'cross db ownership chaining' flag from the drop-down menu, and set its value to 'off'.\n6. Click Save.\n7. Confirm the changes under Flags on the Overview page.\nFrom Command Line:\n1. List all Cloud SQL database instances using the following command:\ngcloud sql instances list\n2. Configure the 'cross db ownership chaining' database flag for every Cloud SQL SQL Server database instance using the below command:\ngcloud sql instances patch INSTANCE_NAME --database-flags ''cross db ownership chaining=off''\nNote: This command will overwrite all database flags previously set. To keep those and add new ones, include the values for all flags to be set on the instance; any flag not specifically included is set to its default value. For flags that do not take a value, specify the flag name followed by an equals sign ('=').","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-172":{"article":"To defend against advanced threats and ensure that the boot loader and firmware on your VMs are signed and untampered, it is recommended that Compute instances are launched with Shielded VM enabled.","impact":"Without Shielded VM, there is no protection of production workloads from cybersecurity threats like remote attacks, privilege escalation, malicious actors. Also, advanced platform security capabilities cannot be used, such as secure and measured boot, a Virtual Trusted Platform Module (vTPM), UEFI firmware, and integrity monitoring.","report_fields":["selfLink"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue if an instance does not use an image with Shielded VM support\nUsing Console:\n1. Go to the VM instances page using 'https://console.cloud.google.com/compute/instances'.\n2. Click on the instance name to see its 'VM instance details' page.\n3. Click on 'Stop' to stop the instance.\n4. When the instance has stopped, click on 'Edit'.\n5. In the 'Shielded VM' section, select 'Turn on vTPM' and 'Turn on Integrity Monitoring'.\n6. Optionally, if you do not use any custom or unsigned drivers on the instance, select 'Turn on Secure Boot'.\n7. Click on the 'Save' button to modify the instance and then click on 'Start' to restart it.\nUsing Command Line:\nYou can only enable Shielded VM options on instances that have Shielded VM support. For a list of Shielded VM public images, run the gcloud compute images list command with the following flags:\ngcloud compute images list --project gce-uefi-images --no-standard-images\n1. Stop the instance:\ngcloud compute instances stop INSTANCE_NAME\n2. Update the instance:\ngcloud compute instances update INSTANCE_NAME --shielded-vtpm --shielded-vmintegrity-monitoring\n3. Optionally, if you do not use any custom or unsigned drivers on the instance, turn on secure boot:\ngcloud compute instances update INSTANCE_NAME --shielded-vm-secure-boot\n4. Restart the instance:\ngcloud compute instances start INSTANCE_NAME","multiregional":true,"service":"Compute Engine"},"ecc-gcp-092":{"article":"Ensure that SSL policy for a Load Balancer is based on TLS 1.1 or TLS 1.2, but not on TLS 1.0.","impact":"TLS 1.0 is vulnerable to man-in-the-middle attacks, risking the integrity and authentication of data sent between a website and a browser.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the SSL policies page in the Google Cloud Platform Console.\n2. Click on Create policy or choose the created one. The Create policy page appears.\n3. Enter a Name.\n4. Select Minimum TLS Version. Choose 1.1 or 1.2.\n5. Under Profile, select Compatible, Modern, or Restricted. Enabled features and Disabled features for the profile are displayed on the right side of the page.\n6. If there is a Load balancer to which you want to attach the policy, click Add target and select a forwarding rule as the target of the SSL policy. If desired, add more targets.\n7. Click on Create. Refer https://cloud.google.com/load-balancing/docs/use-ssl-policies.","multiregional":true,"service":"Cloud Load Balancing"},"ecc-gcp-067":{"article":"Ensure that all secrets in the Secret Manager have an expiration time set.","impact":"Secrets can be misused or exposed during their life cycle, which can lead to potential threats to data integrity and confidentiality.","report_fields":["name"],"remediation":"From Console:\n1. Go to Security, Secret Manager.\n2. Click on the Name of affected secret.\n3. Click Edit secret.\n4. Choose Set expiration date under Expiration.\n5. Set an appropriate expiration date.","multiregional":true,"service":"Secret Manager"},"ecc-gcp-101":{"article":"This insight will be triggered whenever we detect a Load balancer with no logging enabled. Logging is crucial in detecting intrusion attempts, monitoring access as well as in helping debug access errors and system failures during and after the fact. Almost every compliance framework will mandate logging be enabled.","impact":"Disabled access logs for Load Balancer Access makes it harder to analyze statistics, diagnose issues or detect different types of attacks, as well as retain data for regulatory or legal purposes.","report_fields":["selfLink"],"remediation":"From Console:\n1. From Google Cloud home open the Navigation Menu in the top left.\n2. Under the Networking heading select Network services.\n3. Select the HTTPS load-balancer you wish to audit.\n4. Select Edit then Backend Configuration.\n5. Select Edit on the corresponding backend service.\n6. Click Enable Logging.\n7. Set Sample Rate to a desired value. This is a percentage as a decimal point. 1.0 is 100%.\nFrom Google Cloud CLI\n1. Run the following command\ngcloud compute backend-services update --region=REGION --enable-logging --logging-sample-rate=","multiregional":true,"service":"Cloud Load Balancing"},"ecc-gcp-036":{"article":"Enabling OS login binds SSH certificates to IAM users and facilitates effective SSH certificate management.","impact":"Disabling osLogin makes it more difficult to centrally and automatically manage SSH key pairs, which is useful when handling cases such as responding to compromised SSH key pairs and/or revoking external/third-party/vendor users.","report_fields":["selfLink"],"remediation":"Set enable-oslogin in project-wide metadata so that it applies to all of the instances in your project:\nUsing Console:\n1. Go to the VM compute metadata page using https://console.cloud.google.com/compute/metadata.\n2. Click Edit.\n3. Add a metadata entry where the key is enable-oslogin and the value is TRUE.\n4. Click Save to apply the changes.\n5. For every instance that overrides the project setting, go to the VM Instances page at https://console.cloud.google.com/compute/instances.\n6. Click the name of the instance on which you want to remove the metadata value.\n7. At the top of the instance details page, click Edit to edit the instance settings.\n8. Under Custom metadata, remove any entry with key 'enable-oslogin' and value 'FALSE'.\n9. At the bottom of the instance details page, click Save to apply your changes to the instance.\nFrom CLI:\n1. Configure oslogin on the project:\ngcloud compute project-info add-metadata --metadata enable-oslogin=TRUE\n2. Remove instance metadata that overrides the project setting:\ngcloud compute instances remove-metadata INSTANCE_NAME --keys=enable-oslogin","multiregional":true,"service":"Compute Engine"},"ecc-gcp-153":{"article":"VM instance does not have any Labels. Labels can be used for easy identification and search.","impact":"VM instances without label information make identification and search difficult.","report_fields":["selfLink"],"remediation":"1. Login to GCP Console and, from 'Compute', select 'Compute Engine'.\n2. Select the identified VM instance.\n3. Click on the 'SHOW INFO PANEL'.\n4. Add labels with the appropriate KeyValue information.","multiregional":true,"service":"Compute Engine"},"ecc-gcp-019":{"article":"Google Cloud IAM provides predefined roles that give granular access to specific Google Cloud Platform resources and prevent unwanted access to other resources. However, to cater to organization-specific needs, Cloud IAM also provides the ability to create custom roles. Project owners and administrators\nwith the Organization Role Administrator role or the IAM Role Administrator role can create custom roles.\nIt is recommended that a metric filter and alarm be established for changes to Identity and Access Management (IAM) role creation, deletion and updating activities.","impact":"Lack of monitoring and logging of Custom Role changes calls can lead to insufficient response time to detect accidental or intentional changes that may result in unauthorized access.","report_fields":["projectId"],"remediation":"From Console:\nCreate the prescribed log metric:\n1. Go to Logging/Logs-based Metrics using https://console.cloud.google.com/logs/metrics and click 'CREATE METRIC'.\n2. Click the down arrow symbol on Filter Bar at the rightmost corner and select Convert to Advanced Filter.\n3. Clear any text and add:\nresource.type='iam_role'\nAND protoPayload.methodName = 'google.iam.admin.v1.CreateRole'\nOR protoPayload.methodName='google.iam.admin.v1.DeleteRole'\nOR protoPayload.methodName='google.iam.admin.v1.UpdateRole'\n4. Click Submit Filter. Display logs appear based on the filter text entered by the user.\n5. In the Metric Editor menu on the right, fill out the name field. Set Units to 1 (default) and Type to Counter. This ensures that the log metric counts the number of log entries matching the advanced logs query.\n6. Click Create Metric.\nCreate the prescribed Alert Policy:\n1. Identify the new metric that has just been created under the User-defined Metrics section at https://console.cloud.google.com/logs/metrics.\n2. Click the 3-dot icon in the rightmost column for the metric and select Create alert from Metric. A new page displays.\n3. Fill out the alert policy configuration and click Save. Choose the alerting threshold and configuration that makes sense for the user's organization. For example, a threshold of zero(0) for the most recent value ensures that a notification is triggered for every owner change in the project:\nSet 'Aggregator' to 'Count'\nSet 'Configuration':\n- Condition: above\n- Threshold: 0\n- For: most recent value\n4. Configure the desired notification channels in the Notifications section.\n5. Name the policy and click Save.\nFrom Google Cloud CLI:\n1. Create the prescribed Log Metric:\ngcloud logging metrics create\n2. Create the prescribed Alert Policy:\ngcloud alpha monitoring policies create","multiregional":true,"service":"Cloud Logging"},"ecc-gcp-212":{"article":"Trace flags are frequently used to diagnose performance issues or to debug stored procedures or complex computer systems.","impact":"3625(trace log) Limits the amount of information returned to users who are not members of the sysadmin fixed server role by masking the parameters of some error messages using '******'. This can help prevent the disclosure of sensitive information.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the Cloud SQL Instances page in the Google Cloud Console using https://console.cloud.google.com/sql/instances.\n2. Select the SQL Server instance for which you want to enable the database flag.\n3. Click Edit.\n4. Scroll down to the Flags section.\n5. To set a flag that has not been set on the instance before, click Add item, choose the '3625' flag from the drop-down menu, and set its value to 'on'.\n6. Click Save to save your changes.\n7. Confirm your changes under Flags on the Overview page.\nFrom Command Line:\n1. Configure the 3625 database flag for every Cloud SQL SQL Server database instance using the below command:\ngcloud sql instances patch --database-flags \"3625=on\"\nNote : This command will overwrite all database flags previously set. To keep those and add new ones, include the values for all flags you want set on the instance; any flag not specifically included is set to its default value. For flags that do not take a value, specify the flag name followed by an equals sign (\"=\").","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-050":{"article":"Authorized networks are used to specify a restricted range of IP addresses that are permitted to access your container cluster's Kubernetes master endpoint. Kubernetes Engine uses both Transport Layer Security (TLS) and authentication to provide secure access to your container cluster's Kubernetes master endpoint from the public internet. This provides you the flexibility to administer your cluster from anywhere; however, you might want to further restrict access to a set of IP addresses that you control. You can set this restriction by specifying an authorized network.","impact":"Unauthorized networks give access to an unlimited range of IP addresses, which increases the level of outside attacks. It also increases the likelihood of leaking master certificates from your company premises.","report_fields":["selfLink"],"remediation":"Using Console:\n1. Go to Kubernetes GCP Console using https://console.cloud.google.com/kubernetes/list?.\n2. Select the reported Kubernetes clusters for which Master authorized networks is disabled.\n3. Click on EDIT.\n4. Set 'Master authorized networks' to 'Enabled' and add authorize networks.\n5. Click SAVE.\nUsing Command Line:\nTo enable Master authorized networks for an existing cluster, run the following command:\ngcloud container clusters update [CLUSTER_NAME] --zone [COMPUTE_ZONE] --enable-master-authorized-networks\nAlong with this, you can list authorized networks using the --master-authorized-networks flag which contains a list of up to 20 external networks that are allowed to connect to your cluster's Kubernetes master through HTTPS. You provide these networks as a comma-separated list of addresses in CIDR notation (such as 192.168.100.0/24).","multiregional":true,"service":"Google Kubernetes Engine"},"ecc-gcp-213":{"article":"BigQuery by default encrypts the data at-rest by employing Envelope Encryption using Google managed cryptographic keys. The data is encrypted using data encryption keys, and the data encryption keys themselves are further encrypted using the key encryption keys. This is seamless and does not require any additional input from the user. However, if you want to have greater control, Customer-managed encryption keys (CMEK) can be used as the encryption key management solution for BigQuery Data Sets. The CMEK is used to encrypt the data encryption keys instead of using the google-managed encryption keys.","impact":"Not using CMEK results in less control over aspects of the lifecycle and management of your keys.","report_fields":["id"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nCurrently, there is no way to update the encryption of existing data in the table. The data needs to be copied to either an original table or another table while specifying the customer managed encryption key (CMEK).\nFrom Command Line:\nUse the following command to copy the data.\nThe source and the destination needs to be same in case copying to the original table.\nbq cp --destination_kms_key \nsource_dataset.source_table destination_dataset.destination_table","multiregional":true,"service":"BigQuery"},"ecc-gcp-229":{"article":"By default, the Auto-Delete rule is enabled for zonal persistent disks during virtual machine creation. When Auto-Delete is on, the persistent disks are deleted when the associated VM instance is deleted. However, for mission-critical Google Cloud VM instances and cloud environments where compliance and security requirements are more rigorous, you may need to retain the persistent disks after the instance termination. When Auto-Delete behavior rule is disabled, the zonal persistent disks attached to your VM instance are no longer removed when the instance is deleted.","impact":"If auto delete behavior rule is not disabled for persistent drives attached to your Google Cloud VM instances, VM data are not protected from being deleted.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the VM instances page using https://console.cloud.google.com/compute/instances.\n2. In the navigation panel, select VM instances to access the list with all the Compute Engine instances provisioned forthe selected project.\n3. Click on the name of the virtual machine (VM) instance that you want to reconfigure.\n4. On the selected resource configuration page, click EDIT to enter the instance edit mode.\n5. In the Boot disk section, select Keep disk from When deleting instance dropdown list, to disable the Auto-Deletebehavior rule and keep the boot disk when the VM instance is terminated.\n6. In the Additional disks section, if the selected instance has additional disks attached, click on the disk box header,select Keep disk under Deletion rule, and click Done to close the configuration box. Repeat this step to disable theAuto-Delete behavior for all the required data disks attached.\n7. Click Save to apply the configuration changes.\nFrom Command Line:\n1. Run compute instances set-disk-auto-delete command using the name of the VM instance that you want to update asidentifier parameter and the name of the attached disk that you want to reconfigure as value for the --disk parameter, todisable the Auto-Delete behavior rule for the selected instance persistent disk:\ngcloud compute instances set-disk-auto-delete INSTANCE_NAME --zone ZONE --no-auto-delete --disk DISK\n2. If successful, the command output should return the compute instances set-disk-auto-delete request status:\nUpdated [https://www.googleapis.com/compute/v1/projects/PROJECT/zones/ZONE/instances/INSTANCE_NAME].","multiregional":true,"service":"Compute Engine"},"ecc-gcp-130":{"article":"This policy identifies Kubernetes Engine Clusters which are not configured with network traffic egress metering. When network traffic egress metering is enabled, a deployed DaemonSet pod meters network egress traffic by collecting data from the conntrack table and exports the metered metrics to the specified destination. It is recommended to use network egress metering to allow you to receive data and track the monitored network traffic.\nNOTE: Measuring network egress requires a network metering agent (NMA) running on each node. The NMA runs as a privileged pod, consumes some resources on the node (CPU, memory, and disk space), and enables the nf_conntrack_acct sysctl flag on the kernel (for connection tracking flow accounting). If you are comfortable with these caveats, you can enable network egress tracking for use with GKE usage metering.","impact":"When network traffic egress metering is disabled you can not track monitored network traffic to detect suspicious activity.","report_fields":["selfLink"],"remediation":"You cannot currently enable usage metering using Google Cloud Platform Console.\n1. To use usage metering for clusters in your Google Cloud Platform project, you first create the BigQuery dataset, and then configure clusters to use it.\n2. To enable usage metering on an existing cluster, run the following command.\nEnter the name of your cluster where you see a test-cluster and enter the name of your BigQuery dataset where you see test_usage_metering_dataset:\ngcloud beta container clusters update --resource-usage-bigquery-dataset \nNote: Flags can be found by running - gcloud alpha services api-keys update --help\nor in this documentation https://cloud.google.com/sdk/gcloud/reference/alpha/services/api-keys/update","multiregional":true,"service":"Cloud APIs"},"ecc-gcp-335":{"article":"The Service account has an IAM policy containing 'iam.serviceAccounts.actAs' permissions that allow privilege escalation, at the resourse level. The existing permissions allow the Service account to impersonate a service account with higher permissions than their own. The Service account can then utilize that service account to perform API calls that the Service account may not be authorized to perform.","impact":"The permission allow the Service account to impersonate a service account with higher permissions than their own.","report_fields":["c7n:service-account.name","members"],"remediation":"From Console:\n1. Go to IAM & Admin/IAM using https://console.cloud.google.com/iam-admin/serviceaccounts.\n2. Click on reported servise account.\n3. Click permissions field.\n3. Click Edit for reported Service account.\n3. For all non-critical members of these roles, remove their membership by clicking the Trash icon on the right.","multiregional":true,"service":"Cloud IAM"},"ecc-gcp-307":{"article":"This rule detects when Artifact Registry repository is anonymously or publicly accessible.\nArtifact registry repositories can contain sensitive credentials that are baked into containers, personal data (like PII), or confidential data that you may not want publicly accessible. Repositories can be made anonymously or publicly accessible via IAM policies containing the IAM members allUsers or allAuthenticatedUsers.","impact":"Granting permissions to allUsers or allAuthenticatedUsers allows anyone to access the Artifact Registry repository. Such access might not be desirable if sensitive data is being stored in the repositories.","report_fields":["name"],"remediation":"From Console:\n1. Log in to the GCP Console at https://console.cloud.google.com.\n2. Navigate to Repositories.\n3. Select the target Artifact Registry repository.\n4. Expand the Info Panel by selecting Show Info Panel.\n5. To remove a specific role assignment, select allUsers or allAuthenticatedUsers, and then click Remove member.","multiregional":true,"service":"Dataproc"},"ecc-gcp-030":{"article":"GCP Firewall Rules are specific to a VPC Network. Each rule either allows or denies traffic when its conditions are met. Its conditions allow you to specify the type of traffic, such as ports and protocols, and the source or destination of the traffic, including IP addresses, subnets, and instances. Firewall rules are defined at the VPC network level, and are specific to the network in which they are defined. The rules themselves cannot be shared among networks.\nFirewall rules only support IPv4 traffic. When specifying a source for an ingress rule or a destination for an egress rule by address, you can only use an IPv4 address or IPv4 block in CIDR notation. Generic (0.0.0.0/0) incoming traffic from internet to VPC or VM instance using SSH on Port 22 can be avoided.","impact":"Publicly exposed SSH access can increase opportunities for malicious activities such as hacking, Man-In-The-Middle attacks (MITM), and brute-force attacks that raise the risk of resource compromise.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to VPC Network.\n2. Go to the Firewall Rules.\n3. Click the Firewall Rule you want to modify.\n4. Click Edit.\n5. Modify Source IP ranges to specific IP.\n6. Click Save.\nFrom CLI:\n1.Update Firewall rule with new SOURCE_RANGE from the below command:\ngcloud compute firewall-rules update FirewallName --allow=[PROTOCOL[PORT[-PORT]],...] --source-ranges=[CIDR_RANGE,...]","multiregional":true,"service":"Virtual Private Cloud"},"ecc-gcp-057":{"article":"Use Network Policy to restrict pod-to-pod traffic within a cluster and segregate workloads.","impact":"Disabling Network Policy can lead to unrestricted traffic between pods.","report_fields":["selfLink"],"remediation":"Using Google Cloud Console:\n1. Go to Kubernetes Engine using https://console.cloud.google.com/kubernetes/list.\n2. Select the cluster for which Network policy is disabled\n3. Click on EDIT.\n4. Set 'Network policy for master' to 'Enabled'.\n5. Click on SAVE.\n6. Once the cluster has updated, repeat steps 1-3.\n7. Set 'Network Policy for nodes' to 'Enabled'.\n8. Click on SAVE.\nUsing Command Line:\nThe Remediation script for this recommendation utilizes 2 variables $CLUSTER_NAME $COMPUTE_ZONE.\nPlease set these parameters on the system where you will be executing your gcloud audit script or command.\nTo enable Network Policy for an existing cluster, firstly enable the Network Policy add-on:\ngcloud container clusters update [CLUSTER_NAME] --zone [COMPUTE_ZONE] --update-addons NetworkPolicy=ENABLED\nThen, enable Network Policy:\ngcloud container clusters update [CLUSTER_NAME] --zone [COMPUTE_ZONE] --enable-network-policy","multiregional":true,"service":"Google Kubernetes Engine"},"ecc-gcp-450":{"article":"If you have workloads that are fault tolerant, like HPC, big data, media transcoding, CI/CD pipelines or stateless web applications, using preemptible VMs to batch-process them can provide massive cost savings. \nUsing preemptible VMs in your architecture is a great way to scale compute at a discounted rate, but you need to be sure that the workload can handle the potential interruptions if the VM needs to be reclaimed.","impact":"Not using preemptible nodepool VMs in your architecture can result in missed opportunities to efficiently scale your compute resources while enjoying cost savings.","report_fields":["selfLink"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nFrom Google Cloud Console:\n1. Go to Kubernetes Engine using https://console.cloud.google.com/kubernetes/list.\n2. In the list of clusters, click on the cluster requiring the update and click 'Add node pool'.\n3. Ensure that the 'Enable nodes on spot VMs' checkbox is checked.\n4. Click 'Save'.\nYou will also need to migrate workloads from existing non-conforming Node pools to the newly created Node pool, then delete the non-conforming pools.","multiregional":true,"service":"Google Kubernetes Engine"},"ecc-gcp-015":{"article":"Log entries are held in Stackdriver Logging. It is recommended to create a sink which will export copies of all the log entries.","impact":"Without log aggregation, it may be hard to obtain logs in case of security incident investigation as logs may have been rotated or deleted.","report_fields":["projectId"],"remediation":"From Console:\n1. Go to Logs Router by visiting https://console.cloud.google.com/logs/router.\n2. Click on the arrow symbol with CREATE SINK text.\n3. Fill out the fields for Sink details.\n4. Choose Cloud Logging bucket in the Select sink destination drop down menu.\n5. Choose a log bucket in the next drop down menu.\n6. If an inclusion filter is not provided for this sink, all ingested logs will be routed to the destination provided above. This may result in higher than expected resource usage.\n7. Click Create Sink.\nFor more information, see https://cloud.google.com/logging/docs/export/configure_export_v2#dest-create.\nFrom Command Line:\nTo create a sink to export all log entries in a Google Cloud Storage bucket:\ngcloud logging sinks create storage.googleapis.com/DESTINATION_BUCKET_NAME\nSinks can be created for a folder or organization, which will include all projects.\ngcloud logging sinks create storage.googleapis.com/DESTINATION_BUCKET_NAME --include-children --folder=FOLDER_ID | --organization=ORGANIZATION_ID","multiregional":true,"service":"Cloud Logging"},"ecc-gcp-266":{"article":"This rule detects when a service is configured with ingress allow all traffic. Restrict traffic from the internet and other resources to get better network-based access control and allow only VPC resources traffic to enter or traffic through the load balancer.","impact":"Allows all requests, including requests directly from the internet to the run.app URL that can can increase opportunities for malicious activities such as Distributed Denial of Service (DDoS) attacks.","report_fields":["metadata.selfLink"],"remediation":"From Console:\n1. Go to the Cloud Run page at Google Cloud Console https://console.cloud.google.com/run.\n2. Select a service.\n3. Click on the Triggers tab.\n4. Under the Ingress label, select the ingress traffic you want to allow.\n5. Click on Save.","multiregional":true,"service":"Cloud Run"},"ecc-gcp-178":{"article":"Ensure that the 'log_connections' setting causes each attempted connection to the server to be logged, along with successful completion of client authentication. This parameter cannot be changed after the session starts.","impact":"The 'log_connections' flag causes each attempted connection to the database instance to be logged, including successful client authentication requests. Not logging data generated by this configuration flag can lead to insufficient response time to identify, troubleshoot, and repair configuration errors and sub-optimal performance for your Google Cloud PostgreSQL database instances.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the Cloud SQL Instances page in the Google Cloud Console using https://console.cloud.google.com/sql/instances.\n2. Select the PostgreSQL instance for which you want to enable the database flag.\n3. Click Edit.\n4. Scroll down to the Flags section.\n5. To set a flag that has not been set on the instance before, click Add item, choose the 'log_connections' flag from the drop-down menu and set the value as 'on'.\n6. Click Save.\n7. Confirm the changes under Flags on the Overview page.\nFrom Command Line:\n1. List all Cloud SQL database instances using the following command:\ngcloud sql instances list\n2. Configure the 'log_connections' database flag for every Cloud SQL PosgreSQL database instance using the below command:\ngcloud sql instances patch INSTANCE_NAME --database-flags log_connections=on\nNote: This command will overwrite all previously set database flags. To keep those and add new ones, include the values for all flags to be set on the instance; any flag not specifically included is set to its default value. For flags that do not take a value, specify the flag name followed by an equals sign ('=').","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-293":{"article":"This rule detects when a Cloud Spanner backup is created with an expiration date of 29 days or less. Too short an expiration time reduces the benefit of backing up your data to protect against loss. A spanner backup should persist for at least 30 days or longer before it is replaced with a new backup, preferably on an automated cadence.","impact":"Too short an expiration time reduces the benefit of backing up your data to protect against loss.","report_fields":["name"],"remediation":"From Console:\n1. Go to the Spanner page at Google Cloud Console: https://console.cloud.google.com/spanner/instances.\n2. Select an instance.\n3. Select a database for backup.\n4. Click Backup/Restore.\n5. Click on Create.\n6. Enter backup name.\n7. Enter backup expiration period, it should be with expiration time of at least 30 days\n8. Click Create.","multiregional":true,"service":"Cloud Spanner"},"ecc-gcp-291":{"article":"This policy identifies Secret Manager secrets that are not encrypted with customer managed encryption key.","impact":"Not using CMEK results in less control over aspects of the lifecycle and management of keys.","report_fields":["name"],"remediation":"From console:\n1. Go to the Secret Manager page in the Google Cloud Console.\n2. Make sure you have selected correct project at top.\n3. Select the violating secret from the list.\n4. On the secret details page, click Edit at top.\n5. For Encryption, check Use a customer-managed encryption key (CMEK) option and select an Encryption key.\n6. Click the Update Secret button.","multiregional":true,"service":"Secret Manager"},"ecc-gcp-043":{"article":"It is recommended to enforce all incoming connections to SQL database instance to use SSL.","impact":"Unencrypted traffic between the SQL database and client applications, if successfully intercepted (MITM), can expose sensitive data such as credentials, database queries, query outputs, etc.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to https://console.cloud.google.com/sql/instances.\n2. Click on an instance name to see its configuration overview.\n3. In the left-side panel, select Connections.\n4. In the SSL connections section, click Allow only SSL connections.\n5. Under Configure SSL server certificates, click Create new certificate.\n6. Under Configure SSL client certificates, click Create a client certificate.\n7. Follow the instructions shown to learn how to connect to your instance.\nFrom Command Line:\nTo enforce SSL encryption for an instance, run the command:\ngcloud sql instances patch INSTANCE_NAME --require-ssl\nNote:\nRESTART is required for type MySQL Generation 1 Instances (backendType FIRST_GEN) to get this configuration in effect.","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-438":{"article":"It is recomended to enable Shielded GKE Nodes to provide verifiable integrity via secure boot, virtual trusted platform module (vTPM)-enabled measured boot, and integrity monitoring.","impact":"Clusters without GKE Shielded Nodes are potentially vulnerable to boot- or kernel-level malware or rootkits which persist beyond infected OS.","report_fields":["selfLink"],"remediation":"Using Google Cloud Console:\n1. Navigate to Kubernetes Engine by visiting https://console.cloud.google.com/kubernetes/list\n2. Select the cluster which you wish to enable Shielded GKE Nodes and Click EDIT.\n3. Locate the 'Shielded GKE Nodes' drop-down menu and select 'Enabled'.\n4. Click SAVE.\nUsing Command Line:\nTo migrate an existing cluster, you will need to specify the --enable-shielded-nodes flag on a cluster update command:\ngcloud beta container clusters update $CLUSTER_NAME \\\n--zone $CLUSTER_ZONE \\\n--enable-shielded-nodes","multiregional":true,"service":"Google Kubernetes Engine"},"ecc-gcp-039":{"article":"Customer-Supplied Encryption Keys (CSEK) are a feature in Google Cloud Storage and Google Compute Engine. If you supply your own encryption keys, Google uses your key to protect the Google-generated keys used to encrypt and decrypt your data. By default, Google Compute Engine encrypts all data at rest. Compute Engine handles and manages this encryption for you without any additional actions on your part. However, if you want to control and manage this encryption yourself, you can provide your own encryption keys.","impact":"Not using CSEK instead of the standard Google Compute Engine encryption can cause problems if your project is compromised and all data will be disclosed. At least business-critical VMs should have VM disks encrypted with CSEK.","report_fields":["selfLink"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nCurrently there is no way to update the encryption of an existing disk. Therefore create a new disk with Encryption set to Customer supplied.\nFrom Console:\n1. Go to Compute Engine Disks by visiting https://console.cloud.google.com/compute/disks.\n2. Click CREATE DISK.\n3. Set Encryption type to Customer supplied.\n4. Provide the Key in the box.\n5. Select Wrapped key.\n6. Click Create.\nFrom CLI:\nIn the gcloud compute tool, encrypt a disk using the --csek-key-file flag during instance creation. If you are using an RSA-wrapped key, use the gcloud beta component:\ngcloud compute instances create INSTANCE_NAME --csek-key-file \nTo encrypt a standalone persistent disk:\ngcloud compute disks create DISK_NAME --csek-key-file ","multiregional":true,"service":"Compute Engine"},"ecc-gcp-187":{"article":"It is recommended to have all SQL database instances set to enable automated backups.","impact":"Backups provide a way to restore a Cloud SQL instance to recover lost data or recover from a problem with the instance. Automated backups need to be set for any instance containing data that should be protected from loss or damage.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the Cloud SQL Instances page in the Google Cloud Console using https://console.cloud.google.com/sql/instances.\n2. Select the instance where the backups need to be configured.\n3. Click Edit.\n4. In the Backups section, check 'Enable automated backups', and choose a backup window.\n5. Click Save.\nFrom Command Line:\n1. List all Cloud SQL database instances using the following command:\ngcloud sql instances list\n2. Enable Automated backups for every Cloud SQL database instance using the below command:\ngcloud sql instances patch INSTANCE_NAME --backup-start-time [HHMM]\nThe 'backup-start-time' parameter is specified in 24-hour time, in the UTC\u00b100 time zone, and specifies the start of a 4-hour backup window. Backups can start any time during the backup window.","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-340":{"article":"This rule detects when Cloud Armor has packet size bypass.\nCloud Armor has a documented limitation of 8 KB as the maximum size of web request that it will inspect. The default behavior of Cloud Armor in this case can allow oversized malicious requests to bypass Cloud Armor and directly reach an underlying application. Morevoer, Cloud Armor does not warn users of this limitation during policy creation or when configuring rules from within the web UI, and can only find a reference to the 8 KB limit in the Cloud Armor documentation https://cloud.google.com/armor/docs/security-policy-overview.","impact":"Without an inbound rule where the Content-Length header value is equal to or greater than 8192, malicious oversized requests can bypass Cloud Armor and reach the application.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the Cloud Armor page in the Cloud Console https://console.cloud.google.com/net-security/securitypolicies/list.\n2. On the Security policies page, click reported policy.\n3. Click Add rules \n4. Set rule action: deny(403).\n5. Click Advanced mode and type: int(request.headers['content-length']) >= 8192.\n6. Set desired priority.\n6. Click Add.","multiregional":true,"service":"Cloud Armor"},"ecc-gcp-177":{"article":"Ensure that the 'log_checkpoints' database flag for the Cloud SQL PostgreSQL instance is set to 'on'.","impact":"In most cases, checkpoints are disrupting your Google Cloud PostgreSQL database performance and can cause connections to stall for up to a few seconds while they occur. By disabling the 'log_checkpoints' flag, you cannot get verbose logging of the checkpoint process for your PostgreSQL database instances to identify and troubleshoot sub-optimal PostgreSQL database performance.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the Cloud SQL Instances page in the Google Cloud Console using https://console.cloud.google.com/sql/instances.\n2. Select the PostgreSQL instance where the database flag needs to be enabled.\n3. Click Edit.\n4. Scroll down to the Flags section.\n5. To set a flag that has not been set on the instance before, click Add item, choose the flag 'log_checkpoints' from the drop-down menu, and set its value.\n6. Click Save.\n7. Confirm the changes under Flags on the Overview page.\nFrom Command Line:\n1. List all Cloud SQL database instances using the following command:\ngcloud sql instances list\n2. Configure the 'log_checkpoints' database flag for every Cloud SQL PosgreSQL database instance using the below command:\ngcloud sql instances patch INSTANCE_NAME --database-flags log_checkpoints=on\nNote: This command will overwrite all previously set database flags. To keep those and add new ones, include the values for all flags to be set on the instance. Any flag not specifically included is set to its default value. For flags that do not take a value, specify the flag name followed by an equals sign ('=').","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-058":{"article":"Disable Client Certificates that require certificate rotation for authentication. Instead, use another authentication method like OpenID Connect.","impact":"Client Certificate authentication requires manual key management and rotation. Such manual actions can be easily forgotten.","report_fields":["selfLink"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nUsing Google Cloud Console:\n1. Go to Kubernetes Engine using https://console.cloud.google.com/kubernetes/list.\n2. Click on CREATE CLUSTER.\n3. Configure as required and then click on the 'Availability, networking, security, and additional features' section.\n4. Ensure that the 'Issue a client certificate' checkbox is not ticked.\n5. Click on CREATE.\nUsing Command Line:\nCreate a new cluster without a Client Certificate:\ngcloud container clusters create [CLUSTER_NAME] --no-issue-client-certificate","multiregional":true,"service":"Google Kubernetes Engine"},"ecc-gcp-302":{"article":"This rule detects when Cloud Armor policy does not prevent message lookup in Log4j2. \nUsing a vulnerable version of Apache Log4j library might enable attackers to exploit a Lookup mechanism that supports making requests using special syntax in a format string.\nSet your Cloud Armor to prevent executing such mechanism using remediation.","impact":"Not using security policy that prevents message lookup in Log4j2 can potentially lead to a risky code execution, data leakage and more.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the Cloud Armor page in the Cloud Console https://console.cloud.google.com/net-security/securitypolicies/list.\n2. On the Security policies page, click reported policy.\n3. Click Add rules \n4. Set rule action: deny(403).\n5. Click Advanced mode and type: evaluatePreconfiguredExpr('cve-canary').\n6. Set desired priority.\n6. Click Add.","multiregional":true,"service":"Cloud Armor"},"ecc-gcp-181":{"article":"The log_min_messages flag defines the minimum message severity level that is considered as an error statement. Messages for error statements are logged with the SQL statement. Valid values include DEBUG5, DEBUG4, DEBUG3, DEBUG2, DEBUG1, INFO, NOTICE, WARNING, ERROR, LOG, FATAL, and PANIC. Each severity level includes the subsequent levels mentioned above.\nNote: To effectively turn off logging failing statements, set this parameter to PANIC.\nERROR is considered the best practice setting. Changes should only be made in accordance with the organization's logging policy.\nBy default log_min_error_statement is ERROR.","impact":"If log_min_messages is not set to the correct value, messages may not be classified as error messages appropriately. Considering general log messages as error messages would make it difficult to find actual errors.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the Cloud SQL Instances page in the Google Cloud Console by visiting https://console.cloud.google.com/sql/instances.\n2. Select the PostgreSQL instance for which you want to enable the database flag.\n3. Click Edit.\n4. Scroll down to the Flags section.\n5. To set a flag that has not been set on the instance before, click Add item, choose the flag log_min_messages from the drop-down menu and set appropriate value.\n6. Click Save to save the changes.\n7. Confirm the changes under Flags on the Overview page.\nFrom Command Line:\n1. List all Cloud SQL database Instances using the following command:\ngcloud sql instances list\n2. Configure the log_min_messages database flag for every Cloud SQL PosgreSQL database instance using the below command.\ngcloud sql instances patch INSTANCE_NAME --database-flags log_min_messages=\nNote: This command will overwrite all database flags previously set. To keep those and add new ones, include the values for all flags to be set on the instance; any flag not specifically included is set to its default value. For flags that do not take a value, specify the flag name followed by an equals sign (\"=\").","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-264":{"article":"This rule detects when a revision doesn't contain a VPC connector. VPC connectors helps Cloud Run (fully managed) service access Compute Engine VM instances, Memorystore instances, and any other resources with an internal IP address.","impact":"Not using a VPC connector can increase the risk of compromising resources from the Internet.","report_fields":["metadata.selfLink"],"remediation":"From Console:\n1. Go to the Cloud Run page at Google Cloud Console https://console.cloud.google.com/run.\n2. Select a service.\n3. Click on Edit and Deploy New Revision.\n4. Click on Connections, click the VPC Connector dropdown and select a connector to use.\n5. Select Serve this revision immediately.\n6. Click on Deploy.","multiregional":true,"service":"Cloud Run"},"ecc-gcp-082":{"article":"SQL instance should have Retention Policies for Backups configured to retain at least 7 days of backups.","impact":"A retention period set for database instances less than 7 days could result in data loss and inability to recover it in the event of failure.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to SQL.\n2. Click the instance.\n3. Click the Backups tab.\n4. Click Create backup\nor\n1. Go to SQL.\n2. Select the instance for which you want to configure backups.\n3. Click Edit.\n4. In the Auto backups section, select Automate backups, and choose the backup window.\n5. Click Save. Link https://cloud.google.com/sql/docs/mysql/backup-recovery/backing-up.\nVia Command Line:\n1. Scheduling automated backups:\ngcloud sql instances patch [INSTANCE_NAME] --backup-start-time [HHMM]","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-209":{"article":"It is recommended to check the user connections for a Cloud SQL SQL Server instance to ensure that it is not artificially limiting connections.\nThe user connections option specifies the maximum number of simultaneous user connections that are allowed on an instance of SQL Server. The actual number of user connections allowed also depends on the version of SQL Server that is used, and also the limits of your application or applications and hardware. It is recommended to set 'user connections' database flag for Cloud SQL SQL Server instance according the organization-defined value.","impact":"artificially limiting the connection can lead to problems with connecting to the database for some users","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the Cloud SQL Instances page in the Google Cloud Console using https://console.cloud.google.com/sql/instances.\n2. Select the SQL Server instance for which you want to enable the database flag.\n3. Click Edit.\n4. Scroll down to the Flags section.\n5. To set a flag that has not been set on the instance before, click Add item, choose the 'user connections' flag from the drop-down menu, and set its value to your organization-recommended value.\n6. Click Save to save your changes.\n7. Confirm your changes under Flags on the Overview page.\nUsing Command Line:\n1. Configure the user connections database flag for every Cloud SQL SQL Server database instance using the below command:\ngcloud sql instances patch --database-flags \"user connections=[0-32,767]\"\nNote : This command will overwrite all database flags previously set. To keep those and add new ones, include the values for all flags you want set on the instance; any flag not specifically included is set to its default value. For flags that do not take a value, specify the flag name followed by an equals sign (\"=\").","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-162":{"article":"The information life cycle includes information creation, collection, use, processing, storage, maintenance, dissemination, disclosure, disposition. Many datasets contain information about individuals that can be used to distinguish or trace an individual\u2019s identity, such as name, social security number, date and place of birth, mother\u2019s maiden name, or biometric records. Datasets may also contain other information that is linked or linkable to an individual, such as medical, educational, financial, and employment information. Personally identifiable information is removed from datasets by trained individuals when such information is not (or no longer) necessary to satisfy the requirements envisioned for the data. For example, if the dataset is only used to produce aggregate statistics, the identifiers that are not needed for producing those statistics are removed. Removing identifiers improves privacy protection, since information that is removed cannot be inadvertently disclosed or improperly used.","impact":"Without the lifecycle configuration, you are missing out on the opportunity to manage objects so that they are stored cost-effectively throughout their lifecycle by moving data to more economical storage classes over time or expiring data based on the object age.","report_fields":["selfLink"],"remediation":"From GCP Console:\n1. Open the Cloud Storage browser in Google Cloud Console.\n2. In the bucket list, find the bucket you want to enable and click 'None' in the 'Lifecycle' column. The lifecycle rules page appears.\n3. Click 'Add rule'.\n4. In the page that appears, specify a configuration:\na) Select the condition(s) under which an action is taken.\nb) Click 'Continue'.\nc) Select the action to take when an object meets the condition(s).\nd) Click 'Continue'.\ne) Click 'Save'.\nGSUTIL:\n1. Create a .json file with the lifecycle configuration rules you would like to apply (see examples).\n2. Use the lifecycle set command to apply the configuration:\ngsutil lifecycle set [LIFECYCLE_CONFIG_FILE] 'gs://[BUCKET_NAME]'\nWhere:\n[LIFECYCLE_CONFIG_FILE] is the name of the file you created in Step 1.\n[BUCKET_NAME] is the name of the relevant bucket. For example, my-bucket.","multiregional":true,"service":"Cloud Storage"},"ecc-gcp-323":{"article":"The Service account has an IAM policy containing 'iam.serviceAccounts.actAs' permissions that allow privilege escalation, at the project level. The existing permissions allow the service account to impersonate another service account with higher permissions than their own. The service account can then utilize that service account to perform API calls that the user may not be authorized to perform.","impact":"The permission allow the service account to impersonate another service account with higher permissions than their own.","report_fields":["member","roles"],"remediation":"From Console:\n1. Go to IAM & Admin/IAM using https://console.cloud.google.com/iam-admin/iam.\n2. Click Edit for reported service account.\n3. For all non-critical members of these roles, remove their membership by clicking the Trash icon on the right.","multiregional":true,"service":"Cloud IAM"},"ecc-gcp-013":{"article":"It is recommended to rotate API keys every 90 days.","impact":"Once a key is stolen, it has no expiration, meaning it may be used indefinitely unless the project owner revokes or regenerates the key.","report_fields":["displayName","name"],"remediation":"From Console:\n1. Go to APIs & Services\\Credentials using https://console.cloud.google.com/apis/credentials\n2. In the section API Keys, Click the API Key Name. The API Key properties display on a new page.\n3. Click REGENERATE KEY to rotate API key.\n4. Click Save.\n5. Repeat steps 2,3,4 for every API key that has not been rotated in the last 90 days.\nNote: Do not set HTTP referrers to wild-cards (* or *.[TLD] or .[TLD]/) allowing access to any/wide HTTP referrer(s). Do not set IP addresses and referrer to any host (0.0.0.0 or 0.0.0.0/0 or ::0)\nFrom Google Cloud CLI:\nThere is not currently a way to regenerate and API key using gcloud commands. To 'regenerate' a key you will need to create a new one, duplicate the restrictions from the key being rotated, and delete the old key.\n1. List existing keys\ngcloud services api-keys list\n2. Note the UID and restrictions of the key to regenerate.\n3. Run this command to create a new API key.\n is the display name of the new key.\ngcloud alpha services api-keys create --display-name=\"\"\nNote the UID of the newly created key.\n4. Run the update command to add required restrictions.\nNote: The restriction may vary for each key. Refer to this documentation for the appropriate flags: https://cloud.google.com/sdk/gcloud/reference/alpha/services/api-keys/update\ngcloud alpha services api-keys update \n5. Delete the old key\ngcloud alpha services api-keys delete ","multiregional":true,"service":"Cloud APIs"},"ecc-gcp-386":{"article":"Confidential Computing is a breakthrough technology which encrypts data in-use while it is being processed. Confidential Computing environments keep data encrypted in memory and elsewhere outside the central processing unit (CPU).\nConfidential VMs leverage the Secure Encrypted Virtualization (SEV) feature of AMD EPYC CPUs. Customer data will stay encrypted while it is used, indexed, queried, or trained on. Encryption keys are generated in hardware, per VM, and are not exportable. Thanks to built-in hardware optimizations of both performance and security, there is no significant performance penalty to Confidential Computing workloads.","impact":"Confidential VM can help alleviate concerns about risk related to either dependency on Google infrastructure or open access by Google Insiders to customer data.","report_fields":["selfLink"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nWe can't edit an existing Instance Template. Delete the existing one and create a new one with Confidential Computing enabled using the link https://cloud.google.com/compute/docs/instance-templates/create-instance-templates","multiregional":true,"service":"Compute Engine"},"ecc-gcp-077":{"article":"You can add a user account to your created SQL instance. During the account creation, it is recommended that you avoid using names like 'admin' or 'administrator', which are potential targets for brute-force dictionary attacks.","impact":"Names like 'admin' or 'administrator' are targeted brute force dictionary attacks.","report_fields":["instance"],"remediation":"From Console:\n1. Go to SQL.\n2. Click the instance you want to modify.\n3. Click the USERS tab.\n4. Don't use name (admin, Admin, administrator, Administrator) for user account.\nFrom Command Line:\n1. Creating a user:\ngcloud sql users create [USER_NAME] --host=[HOST] --instance=[INSTANCE_NAME] --password=[PASSWORD]","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-194":{"article":"Enabling OS login binds SSH certificates to IAM users and facilitates effective SSH certificate management.","impact":"Enabling osLogin ensures that SSH keys used to connect to instances are mapped with IAM users. Revoking access to an IAM user will revoke all the SSH keys associated with that particular user. It facilitates centralized and automated SSH key pair management, which is useful in handling cases like response to compromised SSH key pairs and/or revocation of external/third-party/Vendor users.","report_fields":["selfLink"],"remediation":"Set 'enable-oslogin' in project-wide metadata so that it applies to all of the instances in your project\nFrom Console:\n1. Go to the VM compute metadata page using https://console.cloud.google.com/compute/metadata.\n2. Click Edit.\n3. Add a metadata entry where the key is 'enable-oslogin' and the value is 'TRUE'.\n4. Click Save to apply the changes.\n5. For every instances that overrides the project setting, go to the VM Instances page at https://console.cloud.google.com/compute/instances.\n6. Click the name of the instance on which you want to remove the metadata value.\n7. At the top of the instance details page, click Edit to edit the instance settings.\n8. Under Custom metadata, remove any entry where the key is 'enable-oslogin' and the value is 'FALSE'.\n9. At the bottom of the instance details page, click Save to apply your changes to the instance.\nFrom Command Line:\n1. Configure oslogin on the project:\ngcloud compute project-info add-metadata --metadata enable-oslogin=TRUE\n2. Remove instance metadata that overrides the project setting:\ngcloud compute instances remove-metadata INSTANCE_NAME --keys=enable-oslogin","multiregional":true,"service":"Compute Engine"},"ecc-gcp-066":{"article":"Accidental unavailability of a key vault can cause immediate data loss or loss of security functions (authentication, validation, verification, non-repudiation, etc.) supported by the key vault objects.\nIt is recommended the key vault be made recoverable by enabling the 'Do Not Purge' and 'Soft Delete' functions. This is in order to prevent the loss of encrypted data including storage accounts, SQL databases, and/or dependent services provided by key vault objects (Keys, Secrets, Certificates) etc., as may happen in the case of accidental deletion by a user or from disruptive activity by a malicious user.","impact":"Accidental unavailability of a key vault can cause immediate data loss or loss of security functions supported by the key vault objects.","report_fields":["name"],"remediation":"From Console:\n1. Go to Security.\n2. Go to Cryptographic Keys.\n3. Check status Not avaliable.\n4. Create a new key to replace the unavailable one using the following link https://cloud.google.com/kms/docs/creating-keys#kms-create-keyring-console","multiregional":true,"service":"Cloud KMS"},"ecc-gcp-007":{"article":"Separation of duties is the concept of ensuring that one individual does not have all necessary permissions to be able to complete a malicious action. No user should have Service Account Admin and Service Account User roles assigned at the same time.\nIt is recommended that the principle of 'Separation of Duties' is enforced while assigning service account related roles to users.","impact":"Without Separation of duties, any individual can have all the necessary permissions to perform a malicious action, such as, for example, using a key to access and decrypt data.","report_fields":["member","roles"],"remediation":"From Console:\n1. Go to IAM & Admin/IAM using https://console.cloud.google.com/iam-admin/iam.\n2. For any member having both Service Account Admin and Service account User rolesgranted/assigned, click on the Delete Bin icon to remove either role from the member.\nRemoval of a role should be done based on the business requirements.","multiregional":true,"service":"Cloud IAM"},"ecc-gcp-143":{"article":"Primitive roles are the roles that existed prior to Cloud IAM. Primitive roles (owner, editor) are built-in and provide a broader access to resources making them prone to attacks and privilege escalation. Predefined roles provide more granular controls than primitive roles. Therefore, predefined roles should be used.","impact":"Primitive IAM roles grant over-privileged cloud identities in your Google Cloud projects, which can lead to unwanted or unauthorized access to your GCP cloud resources.","report_fields":["member","roles"],"remediation":"Review the projects/resources that have Primitive roles assigned to them and replace them with equivalent Predefined roles.","multiregional":true,"service":"Cloud IAM"},"ecc-gcp-129":{"article":"This policy identifies Firewall rules attached to the cluster network which allows inbound traffic on all protocols from the public internet. Doing so may allow a bad actor to brute-force their way into the system and potentially get access to the entire cluster network.","impact":"Allowing unrestricted ingress access to uncommon ports can increase opportunities for malicious activities such as hacking and various types of attacks (brute-force attacks, Denial of Service (DoS) attacks, etc.) that may lead to data loss.","report_fields":["selfLink"],"remediation":"1. Login to GCP Portal\n2. Go to VPC network (Left Panel)\n3. Select Firewall rules\n4. Click on the reported firewall rule\n5. Click on the 'EDIT' button\n6. Change the 'Source IP ranges' other than '0.0.0.0/0'\n7. Click on 'Save'","multiregional":true,"service":"Virtual Private Cloud"},"ecc-gcp-248":{"article":"This rule detects when a function is not configured with event trigger. Events are things that happen within your cloud environment that you might want to take action on. These might be changes to data in a database, files added to a storage system, or a new virtual machine instance being created. Creating a response to an event is done with a trigger. A trigger is a declaration that you are interested in a certain event or set of events. Binding a function to a trigger allows you to capture and act on events.","impact":"Without an event trigger, you won't be able to instantly capture and respond to suspicious events within your cloud environment.","report_fields":["name"],"remediation":"From Console:\n1. Go to the Cloud Functions Overview page. https://console.cloud.google.com/functions\n2. Click on 'create function'\n3. In Trigger section, choose a trigger.\n4. For each trigger fill the required information.\n5. Click save","multiregional":true,"service":"Cloud Functions"},"ecc-gcp-046":{"article":"To minimize attack surface root access can explicitly allowed from only trusted IPs (Hosts) to support database related administrative tasks.\nIt is recommended that root access to a MySql Database Instance be allowed only through specific white-listed trusted IPs.","impact":"When root access is allowed for any host, any host from authorized networks can attempt to authenticate to a MySql Database Instance using administrative privileges.","report_fields":["instance"],"remediation":"From Command Line:\nNote: We haven't come across any setting provided by Google cloud console or gcloud utility to update host for a root user. Below remediation uses myMySql-client binary to set the host for root user. Similarly, for PostgreMySql instance,\n1. Login to MySql database instance from authorized network\nmysql connect -u root [INSTANCE_ADRESS]\n2. Set host for root user\nUPDATE MySql.user SET Host=[Host_name/IP] WHERE User='root';","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-091":{"article":"Ensure that CDN is enabled only on a Load Balancer with an HTTPS protocol on backend services.","impact":"The unencrypted traffic between the edge servers and the custom origin can be disclosed by malicious users in case they are able to capture packets sent across CDN.","report_fields":["selfLink"],"remediation":"From Console:\n1. Ensure that CDN is enabled on the Load balancer.\n2. On the Network Security page, from the Console, configure the SSL policy.\n3. Go to the Load balancing page in the Google Cloud Platform Console.\n4. Find the target load balancer name.\n5. Click on the Edit pencil.\n6. Configure Backend.\n7. Configure Frontend.\n8. Attach policy to the Frontend HTTPS proxy.\n9. Configure Certificate.","multiregional":true,"service":"Cloud Load Balancing"},"ecc-gcp-088":{"article":"On Google's Global HTTP Load Balancer, each HTTPS target proxy is linked to a certificate. Ensure that SSL/TLS certificates are renewed one month before expiry.","impact":"SSL/TLS certificates not renewed prior to their expiration date become invalid and the communication between a client and a GCP resource that implements the certificates is no longer secure.","report_fields":["selfLink"],"remediation":"In the Google Cloud Platform Console, you create a new SSL certificate resource when you create or edit a frontend for an HTTPS or SSL proxy load balancer.\nTo create a new SSL certificate resource for a frontend:\n1. Go to the Load balancing page in the Google Cloud Platform Console.\n2.Click the name of an HTTPS or SSL proxy load balancer.\n3.Click the Edit pencil.\n4.Select Frontend Configuration.\n-For an existing frontend, click the Edit pencil. In the Certificate drop-down menu, select a visible certificate, then click Create a new certificate.\n-For a new frontend, in the Certificate drop-down menu, click Select a certificate, and then click Create a new certificate.\n5.In the Create a new certificate dialog box, enter a Name for your certificate resource, and optionally click Add a description and enter a description.\n6.Choose Upload my certificate.\n7.In the Public key certificate field, click the Upload button to upload your .crt file or paste the entire contents of your .key file into the field, including the -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- that enclose the file contents.\n8.In the Certificate chain field, click the Upload button to upload your .csr file or paste the entire contents of the .csr file into the field, including the -----BEGIN CERTIFICATE REQUEST----- and -----END CERTIFICATE REQUEST----- that enclose the file contents.\n9.In the Private key certificate field, click the Upload button to upload your private key, using the .key file generated previously. This file uses, for example, -----BEGIN RSA PRIVATE KEY----- and -----END RSA PRIVATE KEY----- to enclose the file contents.\n10.Click Create.\n11.To create additional certificates, click the additional certificates link, click Select a Certificate, and then click Create a new certificate. Follow the previous steps to upload or paste the appropriate files.\nRefer to: https://cloud.google.com/load-balancing/docs/ssl-certificates.","multiregional":true,"service":"Cloud Load Balancing"},"ecc-gcp-205":{"article":"The PostgreSQL executor is responsible for executing the plan handed over by the PostgreSQL planner. The executor processes the plan recursively to extract the required set of rows. The 'log_executor_stats' flag controls the inclusion of PostgreSQL executor performance statistics in the PostgreSQL logs for each query.","impact":"The 'log_executor_stats' flag enables a crude profiling method for logging PostgreSQL executor performance statistics which even though can be useful for troubleshooting, it may increase the amount of logs significantly and have performance overhead.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the Cloud SQL Instances page in the Google Cloud Console using https://console.cloud.google.com/sql/instances.\n2. Select the PostgreSQL instance for which you want to enable the database flag.\n3. Click Edit.\n4. Scroll down to the Flags section.\n5. To set a flag that has not been set on the instance before, click Add item, choose the 'log_executor_stats' flag from the drop-down menu and set the appropriate value.\n6. Click Save to save your changes.\n7. Confirm your changes under Flags on the Overview page.\nUsing Command Line:\n1. List all Cloud SQL database Instances\ngcloud sql instances list\n2. Configure the log_executor_stats database flag for every Cloud SQL PosgreSQL database instance using the below command.\ngcloud sql instances patch INSTANCE_NAME --database-flags log_executor_stats=off\nNote: This command will overwrite all database flags previously set. To keep those and add new ones, include the values for all flags you want set on the instance; any flag not specifically included is set to its default value. For flags that do not take a value, specify the flag name followed by an equals sign (\"=\").","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-027":{"article":"Cloud DNS is a fast, reliable and cost-effective Domain Name System that powers millions of domains on the internet. DNSSEC in Cloud DNS enables domain owners to take easy steps to protect their domains against DNS hijacking and man-in-the-middle and other attacks.","impact":"Attackers can hijack the process of domain/IP lookup and redirect users to a malicious site using DNS hijacking and man-in-the-middle attacks. DNSSEC helps mitigate the risk of such attacks by cryptographically signing DNS records.","report_fields":["id","name"],"remediation":"From Console:\n1. Go to Cloud DNS by visiting https://console.cloud..google.com/net-services/dns/zones.\n2. For each zone of Type Public, set DNSSEC to On.\nFrom Command Line:\nUse the below command to enable DNSSEC for Cloud DNS Zone Name.\ngcloud dns managed-zones update ZONE_NAME --dnssec-state on","multiregional":true,"service":"Cloud DNS"},"ecc-gcp-038":{"article":"Compute Engine instance cannot forward a packet unless the source IP address of the packet matches the IP address of the instance. Similarly, GCP won't deliver a packet whose destination IP address is different than the IP address of the instance receiving the packet. However, both capabilities are required if you want to use instances to help route packets. Forwarding of data packets should be disabled to prevent data loss or information disclosure.\nException:\nInstances created by GKE should be excluded because they need to have IP forwarding enabled and cannot be changed. Instances created by GKE have names that start with 'gke-'.","impact":"When IP forwarding is enabled, Google Cloud does not enforce packet source and destination verification, and the security of this redirect is potentially downgraded as this source and destination may refer to the attacker's ones.","report_fields":["selfLink"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue when canIpForward is set to false.\nFrom Console:\n1. Go to the VM Instances page using https://console.cloud.google.com/compute/instances.\n2. Select the VM Instance you want to remediate.\n3. Click the Delete button.\n4. On the 'VM Instances' page, click 'CREATE INSTANCE'.\n5. Create a new instance with the desired configuration. By default, the instance is configured to not allow IP forwarding.\nFrom CLI:\n1. Delete the instance:\ngcloud compute instances delete INSTANCE_NAME.\n2. Create a new instance to replace it. Set IP forwarding to Off:\ngcloud compute instances create","multiregional":true,"service":"Compute Engine"},"ecc-gcp-138":{"article":"This policy identifies Load Balancer HTTPS target proxies that are not configured with QUIC protocol. Enabling QUIC protocol on Load balancer target HTTPS proxies adds such advantages as faster connections establishing, stream-based multiplexing, improved loss recovery, and elimination of head-of-line blocking.","impact":"If QUIC's key features are disabled you can not include establishing connections faster, stream-based multiplexing, improved loss recovery, and no head-of-line blocking. With QUIC protocol disabled, such advantages as faster connection establishing, stream-based multiplexing, improved loss recovery, and elimination of head-of-line blocking are not available.","report_fields":["selfLink"],"remediation":"1. Login to GCP Portal.\n2. Go to Network services (Left Panel).\n3. Select Load balancing.\n4. Click on the 'Advanced menu' hyperlink to view target proxies.\n5. Click on the 'Target proxies' tab.\n6. Click on the reported HTTPS target proxy.\n7. Click on the hyperlink under 'URL map'.\n8. Click on the 'EDIT' button.\n9. Select 'Frontend configuration', click on HTTPS protocol rule.\n10. For 'QUIC negotiation', select 'Enabled' from the drop-down list.\n11. Click on 'Done'.\n12. Click on 'Update'.","multiregional":true,"service":"Cloud Load Balancing"},"ecc-gcp-292":{"article":"This rule detects when a service account with elevated privileges (editor or owner status) is assigned an IAM role in the Cloud Spanner backup resource. An elevated service account has more access than necessary and doesn't meet least privilege standards. You can resolve this by assigning custom roles or providing access according to the requirements in the provider documentation.","impact":"An elevated service account has more access than necessary and it increases the level of compromise in the event of a security breach.","report_fields":["name"],"remediation":"From Console:\n1. Go to the Spanner page at Google Cloud Console: https://console.cloud.google.com/spanner/instances.\n2. Select an instance from Resources.\n3. Click Backup.\n4. Select a backup.\n5. Review each role and find Members having editor or owner access.\n6. Click Delete icon and confirm by clicking on REMOVE.","multiregional":true,"service":"Cloud Spanner"},"ecc-gcp-295":{"article":"This rule detects when a service account with elevated privileges (editor or owner status) is assigned an instance-level IAM role in the Cloud Spanner service. An elevated service account has more access than necessary and doesn't meet least privilege standards. You can resolve this by assigning custom roles or providing access according to the requirements in the provider documentation.","impact":"An elevated service account has more access than necessary and it increases the level of compromise in the event of a security breach.","report_fields":["name"],"remediation":"From Console:\n1. Go to the Spanner page at Google Cloud Console: https://console.cloud.google.com/spanner/instances.\n2. Select an instance.\n3. Review each role and find Members having editor or owner access.\n4. Click Delete icon and confirm by clicking on REMOVE.","multiregional":true,"service":"Cloud Spanner"},"ecc-gcp-241":{"article":"This rule detects when a service account with elevated privileges (editor or owner status) is assigned a function-level IAM role in the Cloud Functions service. An elevated service account has more access than necessary and doesn't meet least privilege standards. You can resolve this by assigning custom roles or providing access according to the requirements in the provider documentation.","impact":"An elevated service account has more access than necessary and it increases the level of compromise in the event of a security breach.","report_fields":["name"],"remediation":"From Console:\n1. Go to the Cloud Functions page at Google Cloud Console https://console.cloud.google.com/functions.\n2. Select a function.\n3. Click on Permission.\n4. Review each role and find Members with Owner or Editor access.\n5. Click the Delete icon and confirm by clicking on REMOVE.","multiregional":true,"service":"Cloud Functions"},"ecc-gcp-010":{"article":"Ensure API keys are not created for a project.","impact":"API Keys are insecure because they can be viewed publicly, such as from within a browser, or they can be accessed on a device where the key resides. Keys should only be used for services in cases where other authentication methods are unavailable.","report_fields":["displayName","name"],"remediation":"From Console:\n1. Go to APIs & Services\\Credentials using https://console.cloud.google.com/apis/credentials\n2. In the section API Keys, to delete API Keys: Click the Delete Bin Icon in front of every API Key Name.\nFrom Google Cloud Command Line:\n1. Run the following from within the project you wish to audit\ngcloud services api-keys list --filter\n2. **Pipe the results into**\ngcloud alpha services api-keys delete","multiregional":true,"service":"Cloud APIs"},"ecc-gcp-257":{"article":"This policy identifies GCP App Engine applications for which Identity-Aware Proxy(IAP) is disabled. IAP is used to enforce access control policies for applications and resources. It works with signed headers or the App Engine standard environment Users API to secure your app. It is recommended to enable Identity-Aware Proxy for securing the App engine.\nReference: https://cloud.google.com/iap/docs/concepts-overview","impact":"When you don't use IAP, users are not subject to the fine-grained access controls implemented by the product in use and when they try to access a resource, IAP not perform authentication and authorization checks.","report_fields":["name"],"remediation":"From Console:\n1. Go to the Identity-Aware Proxy page https://console.cloud.google.com/security/iap.\n2. If you don't already have an active project, you'll be prompted to select the project you want to secure with IAP. Select the project to which you deployed the sample application.\nIf you haven't configured your project's OAuth consent screen, you'll be prompted to do so. An email address and product name are required for the OAuth consent screen.\nConfiguring the OAuth consent screen:\n1. Go to the OAuth consent screen https://console.cloud.google.com/apis/credentials/consent.\n2. Under Support email, select the email address you want to display as a public contact. This email address must be your email address, or a Google Group you own.\n3. Enter the Application name you want to display.\n4. Add any optional details you'd like.\n5. Click Save.\nTo change information on the OAuth consent screen later, such as the product name or email address, repeat the preceding steps to configure the consent screen.\nSetting up IAP access:\n1. Go to the Identity-Aware Proxy page https://console.cloud.google.com/security/iap.\n2. Select the resource you wish to modify by checking the box to its left. On the right side panel, click Add Member.\n3. In the Add members dialog, add the email addresses of groups or individuals to whom you want to grant the IAP-secured Web App User role for the project.\nThe following kinds of accounts can be members:\n - Google Account: user@gmail.com\n - Google Group: admins@googlegroups.com\n - Service account: server@example.gserviceaccount.com\n - G Suite domain: example.com\nMake sure to add a Google Account that you have access to.\nIn order to make a resource publicly-accessible (while sibling resources are restricted), grant the IAP-secured Web App User role to `allUsers` or `allAuthenticatedUsers`. The difference between these two is explained in the Public access section https://cloud.google.com/iap/docs/managing-access#public_access.\n4. When you're finished adding members, click Add.\nTurning on IAP:\n1. On the Identity-Aware Proxy page, under HTTPS Resources, find the App Engine app you want to restrict access to. The Published column shows the URL of the app. To turn on IAP for the app, toggle the on/off switch in the IAP column.\n2. To confirm that you want IAP to secure the application, click Turn On in the Turn on IAP window that appears. After you turn it on, IAP requires login credentials for all connections to your application.\nFor more information https://cloud.google.com/iap/docs/app-engine-quickstart.","multiregional":true,"service":"App Engine"},"ecc-gcp-221":{"article":"The default Compute Engine service account has Editor privileges to the product, and shouldn't be associated with a instance template.","impact":"Using default Compute Engine service account associated with a instance template could allow an attacker to use extended account permissions.","report_fields":["selfLink"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nDelete affected instance template and then create a new one with non-default service account.","multiregional":true,"service":"Compute Engine"},"ecc-gcp-005":{"article":"It is recommended to assign the Service Account User (iam.serviceAccountUser) and Service Account Token Creator (iam.serviceAccountTokenCreator) roles to a user for a specific service account rather than assigning the role to a user at project level.","impact":"Users with IAM roles to update the App Engine and Compute Engine instances can effectively run code as the service accounts used to run these instances, and indirectly gain access to all the resources for which the service accounts have access.","report_fields":["role","members"],"remediation":"From Console:\n1. Go to the IAM page in the GCP Console by visiting https://console.cloud.google.com/iam-admin/iam\n2. Click on the filter table text bar. Type Role Service Account User\n3. Click the Delete Bin icon in front of the role Service Account User for every user listed as a result of a filter\n4. Click on the filter table text bar. Type Role Service Account Token Creator\n5. Click the Delete Bin icon in front of the role Service Account Token Creator for every user listed as a result of a filter\nFrom Command Line:\n1. Using a text editor, remove the bindings with the roles/iam.serviceAccountUser\nFor example, you can use the iam.json file shown below as follows:\n{\n \"bindings\": [\n {\n \"members\": [\n \"serviceAccount:our-project-123@appspot.gserviceaccount.com\",\n ],\n \"role\": \"roles/appengine.appViewer\"\n },\n {\n \"members\": [\n \"user:email1@gmail.com\"\n ],\n \"role\": \"roles/owner\"\n },\n {\n \"members\": [\n \"serviceAccount:our-project-123@appspot.gserviceaccount.com\",\n \"serviceAccount:123456789012-compute@developer.gserviceaccount.com\"\n ],\n \"role\": \"roles/editor\"\n }\n ],\n \"etag\": \"BwUjMhCsNvY=\"\n}\nor roles/iam.serviceAccountTokenCreator.\n2. Update the project's IAM policy:\ngcloud projects set-iam-policy PROJECT_ID iam.json","multiregional":true,"service":"Cloud IAM"},"ecc-gcp-120":{"article":"This policy identifies GCP Firewall rules that allow inbound traffic to the SMTP port (25) from the public internet. Allowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Allowing unrestricted inbound/ingress access on TCP port 25 (SMTP) using VPC network firewall rules can increase opportunities for malicious activities such as hacking, spamming, Shellshock and Distributed Denial-of-Service (DDoS) attacks.","report_fields":["selfLink"],"remediation":"To restrict all traffic, edit the reported Firewall rule as follows:\n1. Login to GCP Console.\n2. Go to VPC Network.\n3. Go to Firewall rules.\n4. Click on the reported Firewall rule.\n5. Click on Edit.\n6. Modify Source IP ranges to specific IP.\n7. Click on Save.","multiregional":true,"service":"Virtual Private Cloud"},"ecc-gcp-300":{"article":"This rule detects when redis instance has AUTH disabled.\nAUTH is an optional security feature on Memorystore for Redis that requires incoming connections to authenticate with an AUTH string. Every AUTH string is a Universally Unique Identifier (UUID), and each Redis instance with AUTH enabled has a unique AUTH string.","impact":"Without AUTH you can not ensure that known entities in your organization do not unintentionally access and modify your Redis instance.","report_fields":["name"],"remediation":"From Console:\n1. Log in to the GCP Console at https://console.cloud.google.com.\n2. Navigate to Memorystore for Redis.\n3. View your instance's Instance details page by clicking on reported Instance ID.\n4. Select the EDIT button.\n5. Scroll to the Security section and select the checkbox for Enable AUTH.","multiregional":true,"service":"Cloud Memorystore"},"ecc-gcp-283":{"article":"This policy identifies GCP Firewall rules that allow inbound traffic to the SQL server port (1433) from the public internet. Allowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Allowing unrestricted inbound/ingress access on TCP port 1433 (SQL server) via VPC network firewall rules can increase opportunities for malicious activities such as hacking, brute-force attacks, and SQL injection attacks.","report_fields":["selfLink"],"remediation":"To restrict all traffic, edit the reported Firewall rule as follows:\n1. Login to GCP Console.\n2. Go to VPC Network.\n3. Go to Firewall rules.\n4. Click on the reported Firewall rule.\n5. Click on Edit.\n6. Modify Source IP ranges to specific IP.\n7. Click on Save.","multiregional":true,"service":"Virtual Private Cloud"},"ecc-gcp-011":{"article":"It is recommended to restrict API key usage to trusted hosts, HTTP referrers and apps.","impact":"Unrestricted keys are insecure because they can be viewed publicly, such as from within a browser, or they can be accessed on a device where the key resides.","report_fields":["displayName","name"],"remediation":"From Console:\nLeaving Keys in Place\n1. Go to APIs & Services\\Credentials using https://console.cloud.google.com/apis/credentials\n2. In the section API Keys, Click the API Key Name. The API Key properties display on a new page.\n3. In the Key restrictions section, set the application restrictions to any of HTTP referrers, IP Adresses, Android Apps, iOs Apps.\n4. Click Save.\n5. Repeat steps 2,3,4 for every unrestricted API key.\nNote:\nDo not set HTTP referrers to wild-cards (* or *.[TLD] or .[TLD]/) allowing access to any/wide HTTP referrer(s).\nDo not set IP addresses and referrer to any host (0.0.0.0 or 0.0.0.0/0 or ::0)\nRemoving Keys\nAnother option is to remove the keys entirely.\n1. Go to APIs & Services\\Credentials using https://console.cloud.google.com/apis/credentials\n2. In the section API Keys, select the checkbox next to each key you wish to remove.\n3. Select Delete and confirm.","multiregional":true,"service":"Cloud APIs"},"ecc-gcp-265":{"article":"This rule detects when a service account with elevated privileges (editor or owner status) is assigned a service-level IAM role in the Cloud Run service. An elevated service account has more access than necessary and doesn't meet least privilege standards. You can resolve this by assigning custom roles or providing access according to the requirements in the provider documentation.","impact":"An elevated service account has more access than necessary and it increases the level of compromise in the event of a security breach.","report_fields":["metadata.selfLink"],"remediation":"From Console:\n1. Go to the Cloud Run page at Google Cloud Console https://console.cloud.google.com/run.\n2. Select a service.\n3. Click on Show Info Panel in the top right corner to show the Permissions tab.\n4. Click on the delete button to remove the service account or Edit the service acocunt role.\n5. Click on Save.","multiregional":true,"service":"Cloud Run"},"ecc-gcp-121":{"article":"This policy identifies GCP Firewall rules that allow inbound traffic to the Telnet port (23) from the public internet. Allowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Allowing unrestricted Telnet access can increase opportunities for malicious activities such as IP address spoofing, man-in-the-middle attacks (MITM), and brute-force attacks.","report_fields":["selfLink"],"remediation":"To restrict all traffic, edit the reported Firewall rule as follows:\n1. Login to GCP Console.\n2. Go to VPC Network.\n3. Go to Firewall rules.\n4. Click on the reported Firewall rule.\n5. Click on Edit.\n6. Modify Source IP ranges to specific IP.\n7. Click on Save.","multiregional":true,"service":"Virtual Private Cloud"},"ecc-gcp-245":{"article":"This rule detects when a function is configured with a default service account, function should be configured with service account with least privileges according to requirement. You can resolve this by assigning custom roles or providing access according to the requirements in the provider documentation.","impact":"Using the default App Engine service account can lead to privilege escalations. If your Cloud Function is compromised, it allows attackers broad access to many Google Cloud services.","report_fields":["name"],"remediation":"From Console:\n1. Go to the Cloud Functions page at Google Cloud Console https://console.cloud.google.com/functions.\n2. Select a Function.\n3. Click on Edit.\n4. Change the default service account.\n5. Click on next.\n6. Click on Deploy.","multiregional":true,"service":"Cloud Functions"},"ecc-gcp-144":{"article":"Check to verify if the configuration for automated backup of Binary logs is enabled. Restoring from a backup reverts your MySQL instance to its state at the backup creation time. Enabling automated backups creates backup during the scheduled backup window.","impact":"If a backup is not enabled, there is a risk of data loss after accidental or targeted deletion beyond recovery.","report_fields":["selfLink"],"remediation":"1. Login to GCP Console and select 'SQL' from 'Storage'.\n2. Select the identified MySQL instance.\n3. Click on the 'BACKUPS' tab.\n4. Click on 'Setting Edit', enable the 'Automate backups' and 'Enable point-in-time-recovery' option and click on 'Save'.","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-009":{"article":"Separation of duties is the concept of ensuring that one individual does not have all necessary permissions to be able to complete a malicious action. No user should have Service Account Admin and Service Account User roles assigned at the same time.\nIt is recommended that the principle of 'Separation of Duties' is enforced while assigning KMS related roles to users.","impact":"Without Separation of duties, any individual can have all the necessary permissions to perform a malicious action, such as, for example, using a key to access and decrypt data.","report_fields":["member","roles"],"remediation":"From Console:\n1. Go to IAM & Admin/IAM using https://console.cloud.google.com/iam-admin/iam.\n2. For any member having Cloud KMS Admin and any of the Cloud KMS CryptoKey Encrypter/Decrypter, Cloud KMS CryptoKey Encrypter, Cloud KMS CryptoKey Decrypter roles granted/assigned, click the Delete Bin icon to remove the role from the member.\nNote: Removing a role should be done based on the business requirement.","multiregional":true,"service":"Cloud IAM"},"ecc-gcp-016":{"article":"It is recommended to enable object versioning on log-buckets as the feature allows you to preserve, retrieve, and restore versions of objects.","impact":"With the Object Versioning feature disabled, Google Cloud Storage buckets cannot recover from both unintended user actions and application failures.","report_fields":["selfLink"],"remediation":"From Console:\n1. If sinks are not configured, first follow the instructions in the recommendation:\nEnsure that sinks are configured for all Log entries.\n2. For each storage bucket configured as a sink, go to the Cloud Storage browser at https://console.cloud.google.com/storage/browser/.\n3. Select the Bucket Lock tab near the top of the page.\n4. In the Retention policy entry, click the Add Duration link. The Set a retention policy dialog box appears.\n5. Enter the desired length of time for the retention period and click Save policy.\n6. Set the Lock status for this retention policy to Locked.\nFrom Command Line:\n1. To list all sinks destined to storage buckets:\ngcloud logging sinks list --folder=FOLDER_ID | --organization=ORGANIZATION_ID | --project=PROJECT_ID\n2. For each storage bucket listed above, set a retention policy and lock it:\ngsutil retention set [TIME_DURATION] gs://[BUCKET_NAME]\ngsutil retention lock gs://[BUCKET_NAME]","multiregional":true,"service":"Cloud Storage"},"ecc-gcp-059":{"article":"Google Cloud Platform Alias IP Ranges allow you to assign ranges of internal IP addresses as aliases to a virtual machine's network interfaces. This is useful if you have multiple services running on a VM and you want to assign each service a different IP address.","impact":"Without Alias IPs the networking layer cannot perform anti-spoofing checks, firewall controls for Pods cannot be applied separately from their nodes, Pods cannot directly access hosted services without using a NAT gateway, Pod IPs may conflict with other compute resources.","report_fields":["selfLink"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nUsing Consol:\n1. Go to Kubernetes Engine by visiting https://console.cloud.google.com/kubernetes/list.\n2. Click CREATE CLUSTER.\n3. In the Networking section, check the 'Enable VPC-native traffic routing (using alias IP)' option.\n4. Configure other settings as required.\n5. Click on 'Create'\nUsing Command Line:\nTo enable Alias IP on a new cluster, run the following command:\ngcloud container clusters create [CLUSTER_NAME] --zone [COMPUTE_ZONE] --enable-ip-alias","multiregional":true,"service":"Google Kubernetes Engine"},"ecc-gcp-337":{"article":"This policy identifies GCP Firewall rules that allow inbound traffic to the VNC-Server port (5900) from the public internet. Allowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Allowing unrestricted inbound/ingress access on TCP port 5900 (VNC-Server) via VPC network firewall rules can increase opportunities for malicious activities such as hacking and brute-force attacks.","report_fields":["selfLink"],"remediation":"To restrict all traffic, edit the reported Firewall rule as follows:\n1. Login to GCP Console.\n2. Go to VPC Network.\n3. Go to Firewall rules.\n4. Click on the reported Firewall rule.\n5. Click on Edit.\n6. Modify Source IP ranges to specific IP.\n7. Click on Save.","multiregional":true,"service":"Virtual Private Cloud"},"ecc-gcp-111":{"article":"This policy identifies GCP Firewall rules that allow inbound traffic to the FTP port (21) from the public internet. Allowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Allowing unrestricted FTP access to your Google Cloud virtual machine (VM) instances via VPC network firewall rules can increase opportunities for malicious activities such as brute-force attacks, FTP bounce attacks, spoofing, and packet capture attacks.","report_fields":["selfLink"],"remediation":"To restrict all traffic, edit the reported Firewall rule as follows:\n1. Login to GCP Console.\n2. Go to VPC Network.\n3. Go to Firewall rules.\n4. Click on the reported Firewall rule.\n5. Click on Edit.\n6. Modify Source IP ranges to specific IP.\n7. Click on Save.","multiregional":true,"service":"Virtual Private Cloud"},"ecc-gcp-128":{"article":"Disable the legacy GCE instance metadata APIs for GKE nodes. Under some circumstances, these can be used from within a pod to extract the node's credentials.","impact":"Without requiring a custom HTTP header when accessing the legacy GCE metadata endpoint, a flaw in an application that allows an attacker to trick the code into retrieving the contents of an attacker-specified web URL could provide a simple method for enumeration and potential credential exfiltration.","report_fields":["selfLink"],"remediation":"The legacy GCE metadata endpoint must be disabled upon creation of a cluster or a node-pool. For GKE versions 1.12 and newer, the legacy GCE metadata endpoint is disabled by default.\nUsing Google Cloud Console:\nTo update the existing cluster, create a new Node pool with the legacy GCE metadata endpoint disabled:\n1. Go to Kubernetes Engine using 'https://console.cloud.google.com/kubernetes/list'.\n2. Click on the name of the cluster to be upgraded and click ADD NODE POOL.\n3. Ensure that GCE instance metadata is set to the key-value pair 'disable-legacy-endpoints true'.\n4. Click SAVE.\nYou will need to migrate workloads from any existing non-conforming Node pools to the new Node pool, then delete non-conforming Node pools to complete the remediation.\nUsing Command Line:\nTo update the existing cluster, create a new Node pool with the legacy GCE metadata endpoint disabled:\ngcloud container node-pools create [POOL_NAME] --metadata disable-legacy-endpoints=true --cluster [CLUSTER_NAME] --zone [COMPUTE_ZONE]\nYou will need to migrate workloads from any existing non-conforming Node pools to the new Node pool, then delete non-conforming Node pools to complete the remediation.","multiregional":true,"service":"Google Kubernetes Engine"},"ecc-gcp-263":{"article":"This rule detects when a Cloud Run revision is configured with the default service account. The revision should be configured with a service account with least privileges according to requirements. You can resolve this by assigning custom roles or providing access according to the requirements in the provider documentation.","impact":"Using the default Compute Engine service account can lead to privilege escalations.","report_fields":["metadata.selfLink"],"remediation":"From Console:\n1. Go to the Cloud Run page at Google Cloud Console https://console.cloud.google.com/run.\n2. Select a service.\n3. Click on Edit and Deploy New Revision.\n4. Under Container, click the Service account dropdown and select the desired service account.\n5. Select Serve this revision immediately.\n6. Click on Deploy.","multiregional":true,"service":"Cloud Run"},"ecc-gcp-031":{"article":"GCP Firewall Rules are specific to a VPC Network. Each rule either allows or denies traffic when its conditions are met. Its conditions allow you to specify the type of traffic, such as ports and protocols, and the source or destination of the traffic, including IP addresses, subnets, and instances.\nFirewall rules are defined at the VPC network level, and are specific to the network in which they are defined. The rules themselves cannot be shared among networks. Firewall rules only support IPv4 traffic. When specifying a source for an ingress rule or a destination for an egress rule by address, you can only use an IPv4 address or IPv4 block in CIDR notation. Generic (0.0.0.0/0) incoming traffic from internet to VPC or VM instance using RDP on Port 3389 can be avoided.","impact":"Publicly exposed RDP access can increase opportunities for malicious activities such as hacking, Man-In-The-Middle attacks (MITM), and brute-force attacks that raise the risk of resource compromise.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to VPC Network.\n2. Go to the Firewall Rules.\n3. Click the Firewall Rule you want to modify.\n4. Click Edit.\n5. Modify Source IP ranges to specific IP.\n6. Click Save.\nFrom CLI:\n1.Update RDP Firewall rule with new SOURCE_RANGE from below command:\ngcloud compute firewall-rules update FirewallName --allow=[PROTOCOL[PORT[-PORT]],...] --source-ranges=[CIDR_RANGE,...]","multiregional":true,"service":"Virtual Private Cloud"},"ecc-gcp-313":{"article":"This rule detects when 'Access Transparency' is not Enabled in an organization.\nGCP Access Transparency provides audit logs for all actions that Google personnel take in your Google Cloud resources.\nControlling access to your information is one of the foundations of information security. Given that Google Employees do have access to your organizations' projects for support reasons, you should have logging in place to view who, when, and why your information is being accessed. By default Access Transparency is not enabled.\nTo enable Access Transparency for your Google Cloud organization, your Google Cloud organization must have one of the following customer support levels: Premium, Enterprise, Platinum, or Gold.","impact":"Lack of Access Transparency of logs record the actions taken by Google personnel can lead to insufficient response time to detect accidental or intentional changes.","report_fields":["projectId"],"remediation":"Add privileges to enable Access Transparency\n1. From the Google Cloud Home, within the project you wish to check, click on the Navigation hamburger menu in the top left. Hover over the 'IAM and Admin'. Select IAM in the top of the column that opens.\n2. Click the blue button the says +add at the top of the screen.\n3. In the principals field, select a user or group by typing in their associated email address.\n4. Click on the role field to expand it. In the filter field enter Access Transparency Admin and select it.\n5. Click save.\nVerify that the Google Cloud project is associated with a billing account\n1. From the Google Cloud Home, click on the Navigation hamburger menu in the top left. Select Billing.\n2. If you see This project is not associated with a billing account you will need to enter billing information or switch to a project with a billing account.\nEnable Access Transparency\n1. From the Google Cloud Home, click on the Navigation hamburger menu in the top left. Hover over the IAM & Admin Menu. Select settings in the middle of the column that opens.\n2. Click the blue button labeled Enable Access Transparency for Organization","multiregional":true,"service":"Access Transparency"},"ecc-gcp-163":{"article":"Security marking refers to the application or use of human-readable security attributes. Security labeling refers to the application or use of security attributes regarding internal data structures within systems. System media includes both digital and non-digital media. Digital media includes, for example, diskettes, magnetic tapes, external or removable disk drives, flash drives, compact disks, and digital video disks. Non-digital media includes, for example, paper and microfilm. Security marking is generally not required for media containing information determined by organizations to be in the public domain or to be publicly releasable. However, some organizations may require marking for public information indicating that the information is publicly releasable. Marking of system media reflects applicable laws, Executive Orders, directives, policies, regulations, standards, and guidelines.","impact":"An unlabeled bucket makes it difficult to read security attributes.","report_fields":["selfLink"],"remediation":"From GCP Console:\n1. Open the Cloud Storage browser in Google Cloud Console.\n2. In the bucket list, find the bucket you want to apply a label to, and click the 'More options' button (three vertical dots).\n3 .Click 'Edit labels'.\n4. In the side panel that appears, click the '+ Add label' button.\n5. Specify a key and value for your label.\n6. Click Save.\nGSUTIL:\nUse the -l flag in a label ch command. For example,\ngsutil label ch -l [KEY_1][VALUE_1] 'gs://[BUCKET_NAME]/'\nWhere:\n[KEY_1] is the key name for your label. For example, pet.\n[VALUE_1] is the value for your label. For example, dog.\n[BUCKET_NAME] is the name of the bucket that the label applies to. For example, my-bucket.","multiregional":true,"service":"Cloud Storage"},"ecc-gcp-442":{"article":"While the VM is suspended, you will be charged for the instance memory, device state and all of its attached resources (persistent disk usage, static IPs). To stop being charged for the instance memory and the device state, you can stop the VM instance. In case the attached resources aren't necessary, you can reconfigure a stopped VM to detach and then delete its resources.","impact":"While the VM is suspended, you will be billed for instance memory, device state, and all connected resources (persistent disk usage, static IP addresses)","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the VM Instances page using https://console.cloud.google.com/compute/instances.\n2. Select the VM Instance you want to remediate.\n3. Click the Stop or Delete button.\nFrom CLI:\n1. Stop the instance:\ngcloud compute instances stop INSTANCE_NAME","multiregional":true,"service":"Compute Engine"},"ecc-gcp-334":{"article":"The User has an IAM policy containing 'iam.serviceAccounts.actAs' permissions that allow privilege escalation, at the resourse level. The existing permissions allow the user to impersonate a service account with higher permissions than their own. The user can then utilize that service account to perform API calls that the user may not be authorized to perform.","impact":"The permission allow the user to impersonate a service account with higher permissions than their own.","report_fields":["c7n:service-account.name","members"],"remediation":"From Console:\n1. Go to IAM & Admin/IAM using https://console.cloud.google.com/iam-admin/serviceaccounts.\n2. Click on reported servise account.\n3. Click permissions field.\n3. Click Edit for reported user.\n3. For all non-critical members of these roles, remove their membership by clicking the Trash icon on the right.","multiregional":true,"service":"Cloud IAM"},"ecc-gcp-060":{"article":"Pod Security Policy should be used to prevent privileged containers where possible and enforce namespace and workload configurations.","impact":"Disabled and not set as appropriate, the Pod Security Policy may accept requests to create or update a pod that does not comply with the pod's security policy.","report_fields":["selfLink"],"remediation":"Using\tGoogle\tCloud\tConsole:\nThere is no means of enabling the Pod Security Policy Admission controller on an existing or new cluster from the console.\nUsing Command Line:\nThe Remediation script for this recommendation utilizes 2 variables $CLUSTER_NAME $COMPUTE_ZONE\nPlease set these parameters on the system where you will be executing your gcloud audit script or command.\nTo enable Pod Security Policy for an existing cluster, run the following command:\ngcloud beta container clusters update [CLUSTER_NAME] --zone [COMPUTE_ZONE] --enable-pod-security-policy","multiregional":true,"service":"Google Kubernetes Engine"},"ecc-gcp-191":{"article":"Enable Integrity Monitoring for Shielded GKE Nodes to be notified of inconsistencies during the node boot sequence.","impact":"Lack of monitoring of Shielded GKE Nodes can lead to insufficient response time to respond to integrity failures and prevent compromised nodes from being deployed into the cluster.","report_fields":["selfLink"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nOnce a Node pool is provisioned, it cannot be updated to enable Integrity Monitoring. You must create new Node pools within the cluster with Integrity Monitoring enabled.\nFrom Google Cloud Console:\n1. Go to Kubernetes Engine using https://console.cloud.google.com/kubernetes/list.\n2. In the list of clusters, click on the cluster requiring the update and click 'Add node pool'.\n3. Ensure that the 'Integrity monitoring' checkbox is checked under the 'Shielded options' heading.\n4. Click 'Save'.\nYou will also need to migrate workloads from existing non-conforming Node pools to the newly created Node pool, then delete the non-conforming pools.\nFrom Command Line:\nTo create a Node pool within the cluster with Integrity Monitoring enabled, run the following command:\ngcloud beta container node-pools create [NODEPOOL_NAME] \\ --cluster [CLUSTER_NAME] --zone [COMPUTE_ZONE] \\ --shielded-integrity-monitoring\nYou will also need to migrate workloads from existing non-conforming Node pools to the newly created Node pool, then delete the non-conforming pools.","multiregional":true,"service":"Google Kubernetes Engine"},"ecc-gcp-204":{"article":"The same SQL query can be excuted in multiple ways and still produce different results. The PostgreSQL planner/optimizer is responsible for creating an optimal execution plan for each query. The 'log_planner_stats' flag controls the inclusion of PostgreSQL planner performance statistics in the PostgreSQL logs for each query.","impact":"The 'log_planner_stats' flag enables a crude profiling method for logging PostgreSQL planner performance statistics which even though can be useful for troubleshooting, it may increase the amount of logs significantly and have performance overhead.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the Cloud SQL Instances page in the Google Cloud Console using https://console.cloud.google.com/sql/instances.\n2. Select the PostgreSQL instance for which you want to enable the database flag.\n3. Click Edit.\n4. Scroll down to the Flags section.\n5. To set a flag that has not been set on the instance before, click Add item, choose the 'log_planner_stats' flag from the drop-down menu and set the appropriate value.\n6. Click Save to save your changes.\n7. Confirm your changes under Flags on the Overview page.\nUsing Command Line:\n1. List all Cloud SQL database Instances\ngcloud sql instances list\n2. Configure the log_planner_stats database flag for every Cloud SQL PosgreSQL database instance using the below command:\ngcloud sql instances patch INSTANCE_NAME --database-flags log_planner_stats=off\nNote: This command will overwrite all database flags previously set. To keep those and add new ones, include the values for all flags you want set on the instance; any flag not specifically included is set to its default value. For flags that do not take a value, specify the flag name followed by an equals sign (\"=\").","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-316":{"article":"This rule detects when Cloud Asset Inventory is not Enabled.\nGCP Cloud Asset Inventory is services that provides a historical view of GCP resources and IAM policies through a time-series database. The information recorded includes metadata on Google Cloud resources, metadata on policies set on Google Cloud projects or resources, and runtime information gathered within a Google Cloud resource.\nThe Cloud Asset Inventory API is disabled by default in each project.","impact":"Lack of history of GCP asset metadata can lead to insufficient response time to detect accidental or intentional changes.","report_fields":["name"],"remediation":"From Console:\nEnable the Cloud Asset API:\n1. Go to API & Services/Library by visiting https://console.cloud.google.com/apis/library.\n2. Search for Cloud Asset API and select the result for Cloud Asset API.\n3. Click the ENABLE button.\nFrom Command Line:\nEnable the Cloud Asset API:\n1. Enable the Cloud Asset API through the services interface:\ngcloud services enable cloudasset.googleapis.com","multiregional":true,"service":"Cloud Asset Inventory"},"ecc-gcp-198":{"article":"It is recommended to set the 'skip_show_database' database flag for Cloud SQL Mysql instance to 'on'","impact":"The 'Skip_show_database' database flag prevents people from using the SHOW DATABASES statement if they do not have the SHOW DATABASES privilege. This can improve security if you have concerns about users being able to see databases belonging to other users.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the Cloud SQL Instances page in the Google Cloud Console using https://console.cloud.google.com/sql/instances.\n2. Select the Mysql instance for which you want to enable the database flag.\n3. Click Edit.\n4. Scroll down to the Flags section.\n5. To set a flag that has not been set on the instance before, click Add item, choose the 'skip_show_database' flag from the drop-down menu, and set its value to 'on'.\n6. Click Save to save your changes.\n7. Confirm your changes under Flags on the Overview page.\nUsing Command Line:\n1. List all Cloud SQL database Instances\ngcloud sql instances list\n2. Configure the skip_show_database database flag for every Cloud SQL Mysql database instance using the below command:\ngcloud sql instances patch INSTANCE_NAME --database-flags skip_show_database=on\nNote : This command will overwrite all database flags previously set. To keep those and add new ones, include the values for all flags you want set on the instance; any flag not specifically included is set to its default value. For flags that do not take a value, specify the flag name followed by an equals sign (\"=\").","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-029":{"article":"DNSSEC algorithm numbers in this registry may be used in CERT RRs. Zone signing (DNSSEC) and transaction security mechanisms (SIG(0) and TSIG) make use of particular subsets of these algorithms. The algorithm used for key signing should be recommended one and it should not be weak.","impact":"The use of RSASHA1 results in a low level of security (weak algorithm). You should not use it unless required for compatibility reasons.","report_fields":["id","name"],"remediation":"From Command Line:\n1. If the need exists to change the settings for a managed zone where it has been enabled, DNSSEC must be turned off and then re-enabled with different settings. To turn off DNSSEC, run following command:\ngcloud dns managed-zones update ZONE_NAME --dnssec-state off\n2. To update zone-signing for a reported managed DNS Zone, run the following command:\ngcloud dns managed-zones update ZONE_NAME --dnssec-state on --ksk-algorithm KSK_ALGORITHM --ksk-key-length KSK_KEY_LENGTH --zsk-algorithm ZSK_ALGORITHM --zsk-key-length ZSK_KEY_LENGTH --denial-of-existence DENIAL_OF_EXISTENCE\nSupported algorithm options and key lengths are as follows.\nAlgorithm KSK Length ZSK Length\n--------- ---------- ----------\nRSASHA1 1024,2048 1024,2048\nRSASHA256 1024,2048 1024,2048\nRSASHA512 1024,2048 1024,2048\nECDSAP256SHA256 256 384\nECDSAP384SHA384 384 384","multiregional":true,"service":"Cloud DNS"},"ecc-gcp-028":{"article":"DNSSEC algorithm numbers in this registry may be used in CERT RRs. Zone signing (DNSSEC) and transaction security mechanisms (SIG(0) and TSIG) make use of particular subsets of these algorithms. The algorithm used for key signing should be a recommended one and it should not be weak.","impact":"The use of RSASHA1 results in a low level of security (weak algorithm). You should not use it unless required for compatibility reasons.","report_fields":["id","name"],"remediation":"From Command Line:\n1. If it is necessary to change the settings for a managed zone where it has been enabled, NSSEC must be turned off and re-enabled with different settings. To turn off DNSSEC, run the following command:\ngcloud dns managed-zones update ZONE_NAME --dnssec-state off\n2. To update key-signing for a reported managed DNS Zone, run the following command:\ngcloud dns managed-zones update ZONE_NAME --dnssec-state on --ksk-algorithm KSK_ALGORITHM --ksk-key-length KSK_KEY_LENGTH --zsk-algorithm ZSK_ALGORITHM --zsk-key-length ZSK_KEY_LENGTH --denial-of-existence DENIAL_OF_EXISTENCE\nSupported algorithm options and key lengths are as follows.\nAlgorithm KSK Length ZSK Length\n--------- ---------- ----------\nRSASHA1 1024,2048 1024,2048\nRSASHA256 1024,2048 1024,2048\nRSASHA512 1024,2048 1024,2048\nECDSAP256SHA256 256 256\nECDSAP384SHA384 384 384","multiregional":true,"service":"Cloud DNS"},"ecc-gcp-047":{"article":"Send logs and metrics to a remote aggregator to mitigate the risk of local tampering in the event of a breach.","impact":"Lack of Stackdriver Kubernetes Logging can result in insufficient response time to detect issues due to inaccessibility of audit data after a cluster security event and lack of a centralized place to analyze log data and metrics collected from multiple sources.","report_fields":["selfLink"],"remediation":"Using Google Cloud Console:\n1. Go to Kubernetes Engine by visiting https://console.cloud.google.com/kubernetes/list\n2. Select Kubernetes clusters for which logging is disabled.\n3. Click on EDIT.\n4. Set 'Stackdriver Logging' to 'Enabled'.\n5. Click SAVE.\nUsing Command Line:\nTo enable Stackdriver Logging for an existing cluster, run the following command:\ngcloud container clusters update [CLUSTER_NAME] --zone [COMPUTE_ZONE] --logging-service logging.googleapis.com","multiregional":true,"service":"Google Kubernetes Engine"},"ecc-gcp-276":{"article":"Security Policy rules help identify web requests that we want to allow or block. You should add rules to the Security Policy, other than the default rule.","impact":"The lack of rules can increase the scope for attacks on web applications such as XSS, SQL injection, CSRF, and can eventually lead to data loss.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the Cloud Armor page in the Cloud Console.\n2. On the Security policies page, click the name of the security policy. The Policy details page is displayed.\n3. In the middle of the page, click the Rules tab.\n4. Click Add rule.\n5. (Optional) Enter a description of the rule.\n6. Select the mode:\n Basic mode: allow or deny traffic based on IP addresses or IP ranges.\n Advanced mode: allow or deny traffic based on rule expressions.\n7. In the Match field, specify the conditions under which the rule applies. Follow this link for more details\n8. For Action, select Allow or Deny.\n9. If you are creating a deny rule, select a Deny status message.\n10. If you want to enable preview mode for the rule, select the Enable checkbox.\n11. In the Priority field, enter a positive integer.\n12. Click Add.","multiregional":true,"service":"Cloud Armor"},"ecc-gcp-444":{"article":"VM instance have disk which contain snapshot that were created more than 90 days ago. The most recent snapshot is usally used to restore data, if something goes wrong.","impact":"Keeping unnecessary snapshots will increase your monthly bill.","report_fields":["selfLink","sourceDisk"],"remediation":"From Console:\n1. Go to the Snapshots page in the Google Cloud Platform Console.\n2. Click on reported snapshot and Delete it.","multiregional":true,"service":"Compute Engine"},"ecc-gcp-232":{"article":"Ensure that the OS Login feature enabled at the virtual machine instance level is configured with Two-Factor Authentication (2FA) in order to help protect the access to your Google Cloud VM instances. Two-Factor Authentication (also known as Multi-Factor Authentication - MFA) provides an additional layer of security on top of the existing credentials.","impact":"Without an 2FA/MFA-protected instance does not represent an efficient way to safeguard your production and business-critical applications against malicious actors, as attackers would have to compromise less authentication methods in order to gain access to your VM instance, and this increase the risk of attack.","report_fields":["selfLink"],"remediation":"Set 'enable-oslogin-2fa' in project-wide metadata so that it applies to all of the instances in your project\nFrom Console:\n1. Go to the VM compute metadata page using https://console.cloud.google.com/compute/metadata.\n2. Click Edit.\n3. Add a metadata entry where the key is 'enable-oslogin-2fa' and the value is 'TRUE'.\n4. Click Save to apply the changes.\n5. For every instances that overrides the project setting, go to the VM Instances page at https://console.cloud.google.com/compute/instances.\n6. Click the name of the instance on which you want to remove the metadata value.\n7. At the top of the instance details page, click Edit to edit the instance settings.\n8. Under Custom metadata, remove any entry where the key is 'enable-oslogin-2fa' and the value is 'FALSE'.\n9. At the bottom of the instance details page, click Save to apply your changes to the instance.\nFrom Command Line:\n1. Configure oslogin on the project:\ngcloud compute project-info add-metadata --metadata enable-oslogin-2fa=TRUE\n2. Remove instance metadata that overrides the project setting:\ngcloud compute instances remove-metadata INSTANCE_NAME --keys=enable-oslogin-2fa","multiregional":true,"service":"Compute Engine"},"ecc-gcp-318":{"article":"This rule detects when GCP cloud function is running with an outdated runtime.\nWe recommend to update the function to use up to date runtime. for more information, see https://cloud.google.com/functions/docs/concepts/execution-environment.","impact":"Outdated runtimes have bugs and security vulnerabilities.","report_fields":["name"],"remediation":"From Console:\n1. Go to the Cloud Functions page at Google Cloud Console https://console.cloud.google.com/functions.\n2. Select the desired function by clicking on its name.\n3. At the top of the page, choose Edit.\n4. At the bottom of the page, choose NEXT.\n5. From the Runtime drop-down list, choose the desired supported runtime.\n6. At the bottom of the page, choose DEPLOY.","multiregional":true,"service":"Cloud Functions"},"ecc-gcp-287":{"article":"Ensure that each Google Cloud Pub/Sub subscription is configured to use a dead-letter topic, also known as dead-letter queue, in order to capture undeliverable messages. Your Pub/Sub subscriptions are configured with a maximum number of delivery attempts. When a message cannot be delivered, it is republished to the specified dead-letter topic.","impact":"Disabling dead-letter topics for your Pub/Sub subscriptions reduces cloud applications resilient and durable when messages can not be delivered due to client errors or server errors.","report_fields":["name"],"remediation":"From Console:\n1. Navigate to Pub/Sub console at https://console.cloud.google.com/cloudpubsub and click on the CREATE TOPIC button from the dashboard top menu to initiate the dead-letter topic setup process.\n2. Within Create a topic configuration box, perform the following actions:\n A.\tProvide a unique identifier for the new topic in the Topic ID box.\n B.\tIn the Encryption section, choose Customer-managed key, and select your own CMK from the Select a customer-managed key dropdown list.\n C.\tInside \"The service-@gcp-sa-pubsub.iam.gserviceaccount.com service account does not have permissions to encrypt/decrypt with the selected key.\" box, click Grant to grant the specified service account the required IAM role on the selected CMK.\n D.\tClick CREATE TOPIC to deploy your new Pub/Sub dead-letter topic.\n3. The dead-letter topic should have at least one subscription so that dead-lettered messages will not be lost. Click on the newly created Pub/Sub topic, select the SUBSCRIPTIONS tab, then click CREATE SUBSCRIPTION to create the required subscription.\n4. In the navigation panel, select Subscriptions to access the Pub/Sub subscriptions available for the selected GCP project.\n5. Click on the ID of the subscription that you want to reconfigure, and choose the EDIT button from the dashboard top menu.\n6. On the Edit subscription page, in the Dead lettering section, select Enable dead lettering checkbox, and choose the dead-letter topic created at the previous steps from the Select a dead letter topic dropdown list. (Optional) In the Maximum delivery attempts field, specify an integer between 5 and 100, based on your application needs. Click UPDATE to apply the changes.\n7. Once the configuration changes are applied to the selected subscription, select the DEAD LETTERING tab, and click on the GRANT PUBLISHER ROLE and GRANT SUBSCRIBER ROLE to assign the Publisher and Subscriber roles. The Pub/Sub service account for the selected GCP project needs the Publisher role to publish dead-lettered messages to the specified topic and the Subscriber role to forward messages from this subscription to the dead-letter topic. The selected subscription can dead letter messages to the new dead-letter topic once it exceeds the specified delivery attempt count.","multiregional":true,"service":"Pub/Sub"},"ecc-gcp-195":{"article":"Cloud DNS logging records the queries from the name servers within your VPC to Stackdriver. Logged queries can come from Compute Engine VMs, GKE containers, or other GCP resources provisioned within the VPC.","impact":"Monitoring of Cloud DNS logs provides visibility to DNS names requested by the clients within the VPC. These logs can be monitored for anomalous domain names and other suspicious activity.","report_fields":["selfLink"],"remediation":"From Command Line:\n1. Add New DNS Policy With Logging Enabled.\nFor each VPC network that needs a DNS policy with logging enabled:\ngcloud dns policies create enable-dns-logging --enable-logging --description=''Enable DNS Logging'' --networks=VPC_NETWORK_NAME\nThe VPC_NETWORK_NAME can be one or more networks in comma-separated list.\n2. Enable Logging for Existing DNS Policy.\nFor each VPC network that has an existing DNS policy that needs logging enabled:\ngcloud dns policies update POLICY_NAME --enable-logging --networks=VPC_NETWORK_NAME\nThe VPC_NETWORK_NAME can be one or more networks in comma-separated list.\nCloud DNS logging is disabled by default on each network.","multiregional":true,"service":"Virtual Private Cloud"},"ecc-gcp-183":{"article":"The 'log_min_duration_statement' flag defines the minimum amount of execution time of a statement in milliseconds where the total duration of the statement is logged. Ensure that 'log_min_duration_statement' is disabled, i.e., a value of '-1' is set.","impact":"The 'log_min_duration_statement' configuration flag causes the duration of each completed SQL statement to be logged if the statement executes for at least the specified number of milliseconds. Setting this flag to 0 logs all statement durations, whereas setting it to -1 disables logging statement durations. Logging SQL statements may include sensitive information that should not be recorded in log files.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the Cloud SQL Instances page in the Google Cloud Console using https://console.cloud.google.com/sql/instances.\n2. Select the PostgreSQL instance where the database flag needs to be enabled.\n3. Click Edit.\n4. Scroll down to the Flags section.\n5. To set a flag that has not been set on the instance before, click Add item, choose the 'log_min_duration_statement' flag from the drop-down menu and set a value to '-1'.\n6. Click Save.\n7. Confirm the changes under Flags on the Overview page.\nFrom Command Line\n1. List all Cloud SQL database instances using the following command:\ngcloud sql instances list\n2. Configure the 'log_min_duration_statement' flag for every Cloud SQL PosgreSQL database instance using the below command:\ngcloud sql instances patch INSTANCE_NAME --database-flags log_min_duration_statement=-1\nNote: This command will overwrite all database flags previously set. To keep those and add new ones, include the values for all flags to be set on the instance; any flag not specifically included is set to its default value. For flags that do not take a value, specify the flag name followed by an equals sign ('=').","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-165":{"article":"Container-native Load balancing enables HTTP(S) Load balancers to target Pods directly and evenly distribute their traffic to Pods. Container-native load balancing leverages a data model called network endpoint groups (NEGs), and collections of network endpoints represented by IP-port pairs.","impact":"When Load Balancer is disabled, the Kubernetes Engine can't terminate unauthorized HTTP/HTTPS requests and make worse context-aware load balancing decisions, which may have a negative effect on the availability of resources.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the Google Kubernetes Engine menu in GCP Console.\n2. Click Edit 'HTTP Load Balancing'.\n3. Enable HTTP load balancing.","multiregional":true,"service":"Google Kubernetes Engine"},"ecc-gcp-037":{"article":"A serial port is often referred to as a serial console. Interaction with it is similar to using a terminal window where the input and output are performed entirely in a text mode, and no graphical interface or mouse support is provided.\nIf you enable an interactive serial console on an instance, clients can attempt to connect to that instance from any IP address. Therefore, the interactive serial console support should be disabled.","impact":"If you enable the interactive serial console on an instance, clients can attempt to connect to that instance from any IP address. This allows anybody to connect to that instance if they know the correct SSH key, username, project ID, zone, and instance name.","report_fields":["selfLink"],"remediation":"From Console:\n1. Login to the Google Cloud console.\n2. Go to Computer Engine.\n3. Go to VM instances.\n4. Click on the Specific VM.\n5. Click on EDIT.\n6. Unselect Enable connecting to serial ports below Remote access block.\n7. Click on Save.\nFrom CLI:\nUse the below command to disable:\ngcloud compute instances add-metadata INSTANCE_NAME --zone=ZONE -- metadata=serial-port-enable=false\nor\ngcloud compute instances add-metadata INSTANCE_NAME --zone=ZONE -- metadata=serial-port-enable=0","multiregional":true,"service":"Compute Engine"},"ecc-gcp-299":{"article":"Ensure that the objects stored within your Google Cloud Storage buckets have a sufficient data retention period configured for security and compliance purposes. A retention period indicates the amount of time the objects in the bucket must be retained. The retention period can be configured by editing the bucket retention policy. A retention policy prevents the deletion or modification of the bucket's objects for the specified duration of time. You can set a maximum retention period of 3155760000 seconds (i.e. 100 years).\nNote: if the retention policy associated with your bucket is locked, the policy can not be edited or removed.","impact":"Without an optimal data retention period set for Google Cloud Storage objects will not allow you recover your data more efficiently in the event of a failure or deliberate deletion by an attacker.","report_fields":["selfLink"],"remediation":"From Console:\n1. Sign in to Google Cloud Management Console.\n2. Select the Google Cloud Platform project that you want to examine from the console top navigation bar.\n3. Navigate to Cloud Storage dashboard at https://console.cloud.google.com/storage.\n4. Click on the name of the storage bucket that you want to examine.\n5. Select the Protection tab near the top of the page.\n6. In the Retention policy section, set your retention policy:\n a. If no retention policy currently applies to the bucket, click the + Set Retention Policy link. Choose a unit of time and a length of time for your retention period.\n b. If a retention policy currently applies to a bucket, it appears in the section. Click Edit to modify the retention time.","multiregional":true,"service":"Cloud Storage"},"ecc-gcp-387":{"article":"Autoscaling capabilities let you automatically add or delete virtual machine (VM) instances from a MIG based on increases or decreases in load. Autoscaling helps your apps gracefully handle increases in traffic and reduce costs when the need for resources is lower.","impact":"Not using Autoscaling capabilities will increase your monthly bill.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the Instance groups page in the Google Cloud console: https://console.cloud.google.com/compute/instanceGroups/list.\n2. Select reported instance group and click Edit.\n3. If no autoscaling configuration exists, under Autoscaling, click Configure autoscaling.\n4. Under Autoscaling mode, select On: add and remove instances to the group to enable autoscaling.\n5. Specify the minimum and maximum numbers of instances that you want the autoscaler to create in this group.\n6. In the Autoscaling metrics section add desired metrics.\n7. Click Save.","multiregional":true,"service":"Compute Engine"},"ecc-gcp-151":{"article":"Check to verify if any VM instance is initiated with the 'Pre-Emptible termination' flag set to 'True'. Setting this instance to 'True' implies that this VM instance will shut down within 24 hours or can also be terminated by a Service Engine when high demand is encountered.","impact":"Setting an instance to True implies that it can lead to unexpected loss of service when the VM instance is terminated.","report_fields":["selfLink"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nFrom Console:\n1. Go to the VM Instances page using https://console.cloud.google.com/compute/instances.\n2. Select the VM Instance you want to remediate.\n3. Click the Delete button.\n4. On the 'VM Instances' page, click 'CREATE INSTANCE'.\n5. Create a new instance with necessary services, processes, and updates not using Pre-Emptible termination.","multiregional":true,"service":"Compute Engine"},"ecc-gcp-451":{"article":"Compute disk without label info","impact":"Compute disk without label information make identification and search difficult.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the Disks page in the Google Cloud Platform Console https://console.cloud.google.com/compute/disks.\n2. Select reported Disk.\n3. Click \"Labels\" -> \"Add label\" and add proper key-value pair.\n4. Save changes.","multiregional":true,"service":"Compute Engine"},"ecc-gcp-201":{"article":"The value of the 'log_statement' flag determines the SQL statements that are logged. Valid values are - none, ddl, mod, all. The value 'ddl' logs all data definition statements. The value 'mod' logs all ddl statements, plus data-modifying statements. The statements are logged after a basic parsing is done and statement type is determined. Thus, it does not log statements with errors. When using extended query protocol, logging occurs after the Execute message is received and values of the Bind parameters are included.","impact":"Auditing helps in troubleshooting operational problems and also permits forensic analysis. If log_statement is not set to the correct value, too many statements may be logged leading to issues in finding the relevant information from the logs, or too few statements may be logged with relevant information missing from the logs.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the Cloud SQL Instances page in the Google Cloud Console using https://console.cloud.google.com/sql/instances.\n2. Select the PostgreSQL instance for which you want to enable the database flag.\n3. Click Edit.\n4. Scroll down to the Flags section.\n5. To set a flag that has not been set on the instance before, click Add item, choose the 'log_statement' flag from the drop-down menu and set the appropriate value.\n6. Click Save to save your changes.\n7. Confirm your changes under Flags on the Overview page.\nFrom Command Line:\n1. Configure the log_statement database flag for every Cloud SQL PosgreSQL database instance using the below command:\ngcloud sql instances patch --database-flags log_statement=\nNote: This command will overwrite all database flags previously set. To keep those and add new ones, include the values for all flags you want set on the instance; any flag not specifically included is set to its default value. For flags that do not take a value, specify the flag name followed by an equals sign (\"=\").","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-385":{"article":"The default network in GCP region is generally considered insecure and should be avoided. The default firewall and routes are generic in nature and do not lock down sensitive ports or traffic routing.","impact":"Automatically created firewall rules of the default network have a number of open ports, including port 22. This may increase opportunities for malicious activities such as unauthorized access, Man-In-The-Middle attacks (MITM), and brute-force attacks that raise the risk of resource compromise.","report_fields":["selfLink"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nWe can't edit an existing Instance Template. Delete the existing one and create a new one with a non-default network using the link https://cloud.google.com/compute/docs/instance-templates/create-instance-templates","multiregional":true,"service":"Compute Engine"},"ecc-gcp-142":{"article":"Check to ensure that logging is enabled on a Stackdriver exported Bucket. Enabled logging provides information about all the requests made on the bucket.","impact":"Without logging enabled, malicious requests to the bucket may slip under the radar.","report_fields":["selfLink"],"remediation":"Make a note of the reported Storage buckets which have Stackdriver Logs.\nFrom Command line:\n1. Create a bucket to store your logs using the following command:\ngsutil mb gs://example-logs-bucket\n2. Assign Cloud Storage the roles/storage.legacyBucketWriter role for the bucket:\ngsutil iam ch group:cloud-storage-analytics@google.com:legacyBucketWriter gs://example-logs-bucket\n3. Enable logging for your bucket using the logging command:\ngsutil logging set on -b gs://example-logs-bucket [-o log_object_prefix ] gs://reported-storage-bucket\nGCP documentation here 'https://cloud.google.com/storage/docs/access-logs'.","multiregional":true,"service":"Cloud Storage"},"ecc-gcp-113":{"article":"This policy identifies GCP Firewall rules that allow inbound traffic to the Microsoft-DS port (445) from the public internet. Allowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Allowing unrestricted inbound access over TCP port 445 (Microsoft-DS) through VPC network firewall rules can increase opportunities for malicious acts such as hacking, brute force attacks, and interception of network traffic.","report_fields":["selfLink"],"remediation":"To restrict all traffic, edit the reported Firewall rule as follows:\n1. Login to GCP Console.\n2. Go to VPC Network.\n3. Go to Firewall rules.\n4. Click on the reported Firewall rule.\n5. Click on Edit.\n6. Modify Source IP ranges to specific IP\n7. Click on Save.","multiregional":true,"service":"Virtual Private Cloud"},"ecc-gcp-024":{"article":"It is recommended that a metric filter and alarm be established for SQL Instance configuration changes.","impact":"Lack of monitoring and logging of SQL instance configuration changes can lead to insufficient response time to detect and correct misconfigurations done on the SQL server.","report_fields":["projectId"],"remediation":"From Console:\nCreate the prescribed Log Metric:\n1. Go to Logging/Logs-based Metrics using https://console.cloud.google.com/logs/metrics and click \"CREATE METRIC\".\n2. Click the down arrow symbol on Filter Bar at the rightmost corner and select Convert to Advanced Filter.\n3. Clear any text and add:\nprotoPayload.methodName=\"cloudsql.instances.update\"\n4. Click Submit Filter. Display logs appear based on the filter text entered by the user.\n5. In the Metric Editor menu on the right, fill out the name field. Set Units to 1 (default) and Type to Counter. This ensures that the log metric counts the number of log entries matching the user's advanced logs query.\n6. Click Create Metric.\nCreate the prescribed alert policy:\n1. Identify the newly created metric under the User-defined Metrics section at https://console.cloud.google.com/logs/metrics.\n2. Click the 3-dot icon in the rightmost column for the new metric and select Create alert from Metric. A new page appears.\n3. Fill out the alert policy configuration and click Save. Choose the alerting threshold and configuration that makes sense for the user's organization. For example, a threshold of zero (0) for the most recent value will ensure that a notification is triggered for every owner change in the user's project:\nSet 'Aggregator' to 'Count'\nSet 'Configuration':\n- Condition: above\n- Threshold: 0\n- For: most recent value\n4. Configure the desired notification channels in the Notifications section.\n5. Name the policy and click Save.\nFrom Google Cloud CLI:\nCreate the prescribed log metric:\ngcloud logging metrics create\nCreate the prescribed alert policy:\ngcloud alpha monitoring policies create\nReference for command usage https://cloud.google.com/sdk/gcloud/reference/alpha/monitoring/policies/create","multiregional":true,"service":"Cloud Logging"},"ecc-gcp-040":{"article":"It is recommended that IAM policy on Cloud Storage bucket do not allow anonymous and/or public access.","impact":"Allowing anonymous or public access grants permissions to anyone to access bucket content. Such access might not be desired if you are storing any sensitive data.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the Storage browser using https://console.cloud.google.com/storage/browser.\n2. Click on the bucket name to go to the Bucket details page.\n3. Click on the Permissions tab.\n4. Click on the Delete button in front of allUsers and allAuthenticatedUsers to remove a particular role assignment.\nFrom CLI:\nRemove allUsers and allAuthenticatedUsers access.\ngsutil iam ch -d allUsers gs//BUCKET_NAME\ngsutil iam ch -d allAuthenticatedUsers gs//BUCKET_NAME","multiregional":true,"service":"Cloud Storage"},"ecc-gcp-412":{"article":"This rule detects when Datafusion instance has public ip.\nIn a public instance, network communication happens over the open Internet regardless of the source location, which is not recommended for critical environments.","impact":"An data fusion instance with a public IP address could potentially be compromised, which may lead to malicious activity with sensitive data.","report_fields":["name"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nFrom Console:\n1. Create a VPC network or a shared VPC network.\n2. Enable Private Google Access and allocate an IP range (If you're not using a shared VPC network, Cloud Data Fusion allocates an IP range by default when you create an data-fusion instance).\n3. Create a private instance (If you're using a shared VPC network, go to step 4).\n3.1 Go to the Create Data Fusion instance page.\n3.2 Enter an instance name and description for your instance.\n3.3 Select the Region in which to create the instance. The region must have Private Google Access enabled.\n3.4 Select a Cloud Data Fusion Version and Edition.\n3.5 Specify the Dataproc service account to use for running your Cloud Data Fusion pipeline in Dataproc.\n3.6 Expand the Advanced Options menu and click Enable Private IP.\n3.7 In the Network field, choose a network in which to create the instance.\n3.8 Click Create. It takes up to 30 minutes for the instance creation process to complete.\n4. If you're using a shared VPC network, use this curl command:\ncurl -H \"Authorization: Bearer $(gcloud auth print-access-token)\" -H \"Content-Type: application/json\" https://$DATA_FUSION_API_NAME/v1/projects/$PROJECT/locations/$LOCATION/instances?instanceId=INSTANCE_ID -X POST -d '{\"description\": \"Private CDF instance created through REST.\", \"type\": \"ENTERPRISE\", \"privateInstance\": true, \"networkConfig\": {\"network\": \"projects/SHARED_VPC_HOST_PROJECT_ID/global/networks/NETWORK_NAME\", \"ipAllocation\": \"IP_RANGE\"}}'\n5. If you need to set up VPC Network Peering to connect with the source and sink that you use in your pipeline than follow this link https://cloud.google.com/data-fusion/docs/how-to/create-private-ip#set-up-vpc-peering\n6. If you create your Cloud Data Fusion instance in a shared VPC network, you must grant the Compute Network User role to the following service accounts:\nCloud Data Fusion service account: service-PROJECT_NUMBER@gcp-sa-datafusion.iam.gserviceaccount.com\nDataproc service account: service-PROJECT_NUMBER@dataproc-accounts.iam.gserviceaccount.com\n7. Create a firewall rule on your VPC network that allows for incoming SSH connections from the IP range you specified when you created your private Cloud Data Fusion instance:\ngcloud compute firewall-rules create FIREWALL_NAME-allow-ssh --allow=tcp:22 --source-ranges=IP_RANGE --network=NETWORK_NAME --project=PROJECT_ID","multiregional":true,"service":"Cloud Data Fusion"},"ecc-gcp-107":{"article":"Identify the volumes that have not had a backup or snapshot in the past 14 days. If a snapshot is not recent, it could be missing crucial patches and software updates.","impact":"If a snapshot is not recent, it could be missing crucial patches and software updates.","report_fields":["selfLink","sourceDisk"],"remediation":"From Console:\n1. Go to the Snapshots page in the Google Cloud Platform Console.\n2. Enter a snapshot Name.\n3. Optionally, enter a Description of the snapshot.\n4. Under Source disk, select an existing disk from which you want to create a snapshot.\n5. Optionally, you can specify a custom storage location.\n6. Click Create to create the snapshot.","multiregional":true,"service":"Compute Engine"},"ecc-gcp-225":{"article":"VM instances with public IP addresses are part of the network perimeter and are susceptible to various attack vectors over the exposed services. Unless essential to the provided service, avoid providing such instances with permissions to access GCS buckets.","impact":"An instance with a public IP address and access to GCS buckets could potentially be compromised, which may lead to malicious activity with sensitive data.","report_fields":["selfLink"],"remediation":"From Console:\nEither:\n1. In the Cloud Console, go to https://console.cloud.google.com/compute/instances.\n2. Click on the relevant VM instance name.\n3. Click on the Edit button in the toolbar.\n4.Scroll down to the Network Interfaces section and click on the Edit (pen) icon next to the network interface with public IP.\n5. In the \"External IP\" field, select \"None\".\n6. Click on the Done button to save your changes.\nOr remove the role assignment that permits bucket access from the VM instance as follows:\n1. In the Cloud Console, go to the IAM & Admin page at https://console.cloud.google.com/iam-admin/iam.\n2. Select Edit service account that is assigned to the VM instance.\n3. Remove the redundant role.","multiregional":true,"service":"Compute Engine"},"ecc-gcp-217":{"article":"Interacting with a serial port is often referred to as the serial console, which is similar to using a terminal window, in that input and output is entirely in text mode and there is no graphical interface or mouse support. If you enable the interactive serial console on an instance, clients can attempt to connect to that instance from any IP address. Therefore interactive serial console support should be disabled.","impact":"if an instance based on template with enabled interactive serial console, clients can attempt to connect to that instance from any IP address. This allows anybody to connect to that instance if they know the correct SSH key, username, project ID, zone, and instance name.","report_fields":["selfLink"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nUsing Console:\n1. Go to the instance template page using https://console.cloud.google.com/compute/instanceTemplates/list.\n2. To block serial ports access, create a new template and set serial-port-enable=false in instance template metadata.\n3. Repeat the above steps for every impacted Instance template.","multiregional":true,"service":"Compute Engine"},"ecc-gcp-169":{"article":"Ensure that Cloud KMS cryptokeys are not anonymously or publicly accessible","impact":"Misconfigured access permissions is a common security vulnerability that involves KMS resources. Granting permissions to 'allUsers' and 'allAuthenticatedUsers' members can allow anyone to access your KMS keys and the data encrypted with these keys.","report_fields":["name"],"remediation":"From Command Line:\n1. List all Cloud KMS Cryptokeys:\ngcloud kms keys list --keyring=[key_ring_name] --location=global -- format=json | jq '.[].name'\n2.Using the below command, remove IAM policy binding for a KMS key to deny access to 'allUsers' and 'allAuthenticatedUsers':\ngcloud kms keys remove-iam-policy-binding [key_name] -- keyring=[key_ring_name] --location=global --member='allAuthenticatedUsers' -- role='[role]'\ngcloud kms keys remove-iam-policy-binding [key_name] -- keyring=[key_ring_name] --location=global --member='allUsers' --role='[role]'","multiregional":true,"service":"Cloud KMS"},"ecc-gcp-231":{"article":"It is recommended to configure Google Cloud Managed Instance Groups (MIGs) with Autohealing feature.","impact":"Without autohealing virtual machine instances that become unresponsive can't be automaticaly re-created.","report_fields":["selfLink"],"remediation":"From Console:\n1. Navigate to Google Compute Engine dashboard at https://console.cloud.google.com/compute.\n2. In the navigation panel, select Instance groups to access the list with the VM instance groups created for the selected project.\n3. Click on the name of the MIG resource that you want to reconfigure, and choose EDIT GROUP to access the instance group editing page.\n4. Under Autohealing, select Create a health check from the Heath check dropdown list to initiate the setup process.\n5. Click Save to apply the configuration changes.","multiregional":true,"service":"Compute Engine"},"ecc-gcp-192":{"article":"Enable Secure Boot for Shielded GKE Nodes to verify the digital signature of node boot components.","impact":"An attacker may seek to alter boot components to persist malware or root kits during system initialization. Secure Boot helps ensure that the system only runs authentic software byverifying the digital signature of all boot components, and halting the boot process if signature verification fails.","report_fields":["selfLink"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nOnce a Node pool is provisioned, it cannot be updated to enable Secure Boot. You must create new Node pools within the cluster with Secure Boot enabled.\nFrom Google Cloud Console:\n1. Go to Kubernetes Engine using https://console.cloud.google.com/kubernetes/list.\n2.In the list of clusters, click on the cluster requiring the update and click 'Add node pool'.\n3. Ensure that the 'Secure boot' checkbox is checked under the 'Shielded options' heading.\n4. Click 'Save'. You will also need to migrate workloads from existing non-conforming Node pools to the newly created Node pool, then delete the non-conforming pools.\nFrom Command Line:\nTo create a Node pool within the cluster with Secure Boot enabled, run the following command:\ngcloud beta container node-pools create [NODEPOOL_NAME] \\ --cluster [CLUSTER_NAME] --zone [COMPUTE_ZONE] \\ --shielded-secure-boot.\nYou will also need to migrate workloads from existing non-conforming Node pools to the newly created Node pool, then delete the non-conforming pools.","multiregional":true,"service":"Google Kubernetes Engine"},"ecc-gcp-280":{"article":"This policy identifies GCP Firewall rules that allow inbound traffic to the kibana port (5601) from the public internet. Allowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Allowing unrestricted inbound/ingress access on TCP port 5601 (kibana) via VPC network firewall rules can increase opportunities for malicious activities such as hacking and brute-force attacks.","report_fields":["selfLink"],"remediation":"To restrict all traffic, edit the reported Firewall rule as follows:\n1. Login to GCP Console.\n2. Go to VPC Network.\n3. Go to Firewall rules.\n4. Click on the reported Firewall rule.\n5. Click on Edit.\n6. Modify Source IP ranges to specific IP.\n7. Click on Save.","multiregional":true,"service":"Virtual Private Cloud"},"ecc-gcp-003":{"article":"User-managed service account should not have user-managed keys. Anyone who has access to the keys will be able to access resources through the service account.\nGCP-managed keys are used by Cloud Platform services such as App Engine and Compute Engine. These keys cannot be downloaded. Google will keep the keys and automatically rotate them on an approximately weekly basis.","impact":"Managing your own service account keys will increase the risk of key exposure.","report_fields":["keys[].name"],"remediation":"From Console:\n1. Go to the IAM page from the GCP Console using https://console.cloud.google.com/iam-admin/iam.\n2. In the left navigation pane, click on Service accounts. All service accounts and their corresponding keys are listed.\n3. Click on the service account.\n4. Click on Edit and delete the keys.\nFrom Command Line:\nTo delete a user-managed Service Account Key, run the following command,\ngcloud iam service-accounts keys delete --iam-account= ","multiregional":true,"service":"Cloud IAM"},"ecc-gcp-315":{"article":"This rule detects when the 'cloudsql.enable_pgaudit' Database Flag for Cloud Sql Postgresql Instance is not Set to 'on'. By default cloudsql.enable_pgaudit database flag is set to off and the extension is not enabled.\nYour organization will need a way to manage logs. You may have a solution already in place. If you do not, consider installing and enabling the open source pgaudit extension within PostgreSQL and enabling its corresponding flag of cloudsql.enable_pgaudit. This flag and installing the extension enables database auditing in ostgreSQL through the open-source pgAudit extension. This extension provides detailed session and object logging to comply with government, financial, & ISO standards and provides auditing capabilities to mitigate threats by monitoring security events on the instance. Enabling the flag and settings later in this recommendation will send these logs to Google Logs Explorer so that you can access them in a central location.","impact":"pgAudit extension helps configure many of the logs often required to comply with government, financial, and ISO certifications and provides auditing capabilities to mitigate threats by monitoring security events on the instance.","report_fields":["selfLink"],"remediation":"Initialize the pgAudit flag\nNote: RESTART is required to get this configuration in effect\nFrom Console:\n1. Go to https://console.cloud.google.com/sql/instances.\n2. Select the instance to open its Overview page.\n3. Click Edit.\n4. Scroll down and expand Flags.\n5. To set a flag that has not been set on the instance before, click Add item.\n6. Enter cloudsql.enable_pgaudit for the flag name and set the flag to on.\n7. Click Done.\n8. Click Save to update the configuration.\n9. Confirm your changes under Flags on the Overview page\nFrom Command Line:\nRun the below command by providing to enable cloudsql.enable_pgaudit flag.\ngcloud sql instances patch --database-flags=cloudsql.enable_pgaudit=on\nCreating the extension\n1. Connect to the the server running PostgreSQL or through a SQL client of your choice.\n2. If SSHing to the server in the command line open the PostgreSQL shell by typing psql\n3. Run the following command as a superuser.\nCREATE EXTENSION pgaudit;\nUpdating the previously created pgaudit.log flag for your Logging Needs\nNote: there are multiple options here. This command will enable logging for all databases on a server. Please see the customizing database audit logging reference for more flag\noptions.\nFrom Console:\n1. Go to https://console.cloud.google.com/sql/instances.\n2. Select the instance to open its Overview page.\n3. Click Edit.\n4. Scroll down and expand Flags.\n5. To set a flag that has not been set on the instance before, click Add item.\n6. Enter pgaudit.log=all for the flag name and set the flag to on.\n7. Click Done.\n8. Click Save to update the configuration.\n9. Confirm your changes under Flags on the Overview page.\nFrom Command Line:\nRun the command\ngcloud sql instances patch --database-flags cloudsql.enable_pgaudit=on,pgaudit.log=all\nDetermine if logs are being sent to Logs Explorer\n1. From the Google Console home page, open the hamburger menu in the top left.\n2. In the menu that pops open, scroll down to Logs Explorer under Operations.\n3. In the query box, paste the following and search\nresource.type=\"cloudsql_database\"\nlogName=\"projects//logs/cloudaudit.googleapis.com%2Fdata_access\"\nprotoPayload.request.@type=\"type.googleapis.com/google.cloud.sql.audit.v1.PgAuditEntry\"\nIf it returns any log sources, they are correctly setup.","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-033":{"article":"Flow Logs is a feature that enables you to capture information about the IP traffic going to and from network interfaces in your VPC Subnets. After you've created a flow log, you can view and retrieve its data in Stackdriver Logging.\nIt is recommended that Flow Logs be enabled for every business-critical VPC subnet.","impact":"Without enabled VPC Flow Logs, you miss out on the opportunity to detect security and access issues like overly permissive security groups or network ACLs and the opportunity to receive alerts about abnormal activities triggered within your VPC networks, such as rejected connection requests or unusual levels of data transfer.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the VPC network GCP Console visiting https://console.cloud.google.com/networking/networks/list\n2. Click the name of a subnet, The Subnet details page displays.\n3. Click the EDIT button.\n4. Set Flow Logs to On.\n5. Expand the Configure Logs section.\n6. Set Aggregation Interval to 5 SEC.\n7. Check the box beside Include metadata.\n8. Set Sample rate to 100 %.\n9. Click Save.\nNote: It is not possible to configure a Log filter from the console.\nFrom Command Line:\nTo enable VPC Flow Logs for a network subnet, run the following command:\ngcloud compute networks subnets update [SUBNET_NAME] --region [REGION] --enable-flow-logs --logging-aggregation-interval=interval-5-sec --logging-flow-sampling=1 --logging-metadata=include-all","multiregional":true,"service":"Virtual Private Cloud"},"ecc-gcp-452":{"article":"Cloud Dataproc cluster without label information make identification and search difficult.","impact":"Cloud Dataproc clusters without label information makes it difficult to identify, search and view usage of GCP resource and spending.","report_fields":["clusterName","projectId"],"remediation":"From Console:\n1. Log in to the GCP Console at https://console.cloud.google.com.\n2. Navigate to Clusters.\n3. Select the target Dataproc cluster.\n4. Expand the Info Panel by selecting Show Info Panel.\n5. Select the Labels button and add desired labels.","multiregional":true,"service":"Dataproc"},"ecc-gcp-278":{"article":"This policy identifies GCP Firewall rules that allow inbound traffic to the elastic search ports (9200 or 9300) from the public internet. Allowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Allowing unrestricted inbound/ingress access on TCP ports 9200 or 9300 (elastic search) via VPC network firewall rules can increase opportunities for malicious activities such as hacking and brute-force attacks.","report_fields":["selfLink"],"remediation":"To restrict all traffic, edit the reported Firewall rule as follows:\n1. Login to GCP Console.\n2. Go to VPC Network.\n3. Go to Firewall rules.\n4. Click on the reported Firewall rule.\n5. Click on Edit.\n6. Modify Source IP ranges to specific IP.\n7. Click on Save.","multiregional":true,"service":"Virtual Private Cloud"},"ecc-gcp-014":{"article":"It is recommended that Cloud Audit Logging is configured to track all Admin activities and read/write access to user data.\nObject versioning acts as an extra layer of data protection and can be used for retention scenarios such as recovering objects that have been accidentally or intentionally deleted, or overwritten by Cloud IAM users or cloud applications.","impact":"Without a customized log for all Admin actions and read/write access to user data, you miss out on logging any changes / tampering with user data.","report_fields":["projectId"],"remediation":"From Console:\n1. Go to Audit Logs using https://console.cloud.google.com/iam-admin/audit.\n2. Follow the steps at https://cloud.google.com/logging/docs/audit/configure-data-access to enable audit logs for all Google Cloud services.\nFrom Command Line:\n1. To read the project's IAM policy and store it in a file, run a command:\ngcloud projects get-iam-policy PROJECT_ID > /tmp/project_policy.yaml\nAlternatively, the policy can be set at the organization or folder level. When setting the policy at the organization level, it is not necessary to set it for each folder or project:\ngcloud organizations get-iam-policy ORGANIZATION_ID > /tmp/org_policy.yaml\ngcloud resource-manager folders get-iam-policy FOLDER_ID >/tmp/folder_policy.yaml\n2. Edit the policy in /tmp/policy.yaml adding or changing only the audit logs configuration to:\nauditConfigs:\n- auditLogConfigs:\n - logType: DATA_WRITE\n - logType: DATA_READ\n service: allServices\n3. To write a new IAM policy, run the command:\ngcloud organizations set-iam-policy ORGANIZATION_ID /tmp/org_policy.yaml\ngcloud resource-manager folders set-iam-policy FOLDER_ID /tmp/folder_policy.yaml\ngcloud projects set-iam-policy PROJECT_ID /tmp/project_policy.yaml","multiregional":true,"service":"Cloud Logging"},"ecc-gcp-099":{"article":"A good cloud provisioning strategy should include tagging of resources. Tags may be used to associate the function, owner, environment, or other attributes for application instances, and a consistent tagging strategy is a recommended best practice.","impact":"Instance Without Any Tags does not allow you to categorize GCP resources in different ways, such as by purpose, owner, or environment, which is useful when you have many resources of the same type.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to Compute engine https://console.cloud.google.com/compute/instances.\n2. Click an instance name.\n3. Click an instance name.\n4. In the Network tags section, specify one or more tags, separated by commas.\n5. Click Save.\nFrom Command Line:\n1. To assign new tags to an instance, use the following gcloud command:\ngcloud compute instances add-tags INSTANCE_NAME --zone ZONE --tags TAGS","multiregional":true,"service":"Compute Engine"},"ecc-gcp-254":{"article":"There are one or more Cloud AppEngine applications with an SSL certificate that expires in less than 30 days. To prevent security incidents, renew the SSL certificate before it expires. Configure GCP AppEngine custom domain SSL certificate with an expiration time of at least 30 days.","impact":"SSL/TLS certificates not renewed prior to their expiration date become invalid and the communication between a client and a GCP resource that implements the certificates is no longer secure.","report_fields":["name"],"remediation":"Get a new certificate for your domain from the certificate authority (CA) of your choice.\nConvert your private key and SSL certificate files into formats that are supported by App Engine.\nFrom Console:\n1. Go to the App Engine page at Google Cloud Console.\n2. From the sidebar, navigate to Settings -> SSL certificates and click Upload a new certificate.\n3. Upload your concatenated SSL certificate under PEM encoded X.509 public key certificate, for example concat.crt, and then upload your RSA private key under Unencrypted PEM encoded RSA private key, for example myserver.key.pem. Click Upload.\n4. Select the new certificate you just added from the certificate list, then select the domain being served by the old certificate.\n5. Click Save to transfer the mappings from the old certificate to the new one.","multiregional":true,"service":"App Engine"},"ecc-gcp-079":{"article":"Ensure that there are no static websites that you are not aware of being hosted on buckets.","impact":"There is a potential risk of exposure when you turn off block public access settings to make your bucket public, anyone on the internet can access your bucket.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go Cloud Storage Browser.\n2. Choose bucket.\n3. Click the More Actions icon and select Edit website configuration.\nLink https://cloud.google.com/storage/docs/hosting-static-website","multiregional":true,"service":"Cloud Storage"},"ecc-gcp-434":{"article":"Use Customer-Managed Encryption Keys (CMEK) to encrypt node boot and dynamically-provisioned attached Google Compute Engine persistent Disks (PDs) using keys managed within Cloud Key Management Service (Cloud KMS).","impact":"Not using CSEK instead of the standard Google Compute Engine encryption can cause problems if your project is compromised and all data will be disclosed.","report_fields":["selfLink"],"remediation":"This cannot be remediated by updating an existing cluster. You must either recreate the desired node pool or create a new cluster.\nUsing Google Cloud Console:\nTo create a new node pool,\n1. Go to Kubernetes Engine by visiting https://console.cloud.google.com/kubernetes/list\n2. Select Kubernetes clusters for which node boot disk CMEK is disabled\n3. Click ADD NODE POOL\n4. Ensure Boot disk type is 'Standard persistent disk' or 'SSD persistent disk'\n5. Select 'Enable customer-managed encryption for Boot Disk' and select the Cloud KMS encryption key you desire\n6. Click SAVE.\nTo create a new cluster,\n1. Go to Kubernetes Engine by visiting https://console.cloud.google.com/kubernetes/list\n2. Click CREATE CLUSTER\n3. Under the 'default-pool' heading, click 'More options'\n4. In the Node pool edit window, select 'Standard persistent disk' or 'SSD Persistent Disk' as the Boot disk type\n5. Select 'Enable customer-managed encryption for Boot Disk' check box and choose the Cloud KMS encryption key you desire\n6. Configure the rest of the cluster settings as desired\n7. Click CREATE.\nUsing Command Line:\nCreate a new node pool using customer-managed encryption keys for the node boot disk, of [DISK_TYPE] either pd-standard or pd-ssd:\ngcloud beta container node-pools create [CLUSTER_NAME] --disk-type [DISK_TYPE] --boot-disk-kms-key projects/[KEY_PROJECT_ID]/locations/[LOCATION]/keyRings/[RING_NAME]/cryptoKeys/[KEY_NAME]\nCreate a cluster using customer-managed encryption keys for the node boot disk, of [DISK_TYPE] either pd-standard or pd-ssd:\ngcloud beta container clusters create [CLUSTER_NAME] --disk-type [DISK_TYPE] --boot-disk-kms-key projects/[KEY_PROJECT_ID]/locations/[LOCATION]/keyRings/[RING_NAME]/cryptoKeys/[KEY_NAME]","multiregional":true,"service":"Google Kubernetes Engine"},"ecc-gcp-240":{"article":"The slow query log feature designed for MySQL databases enables you to log queries that exceed a predefined time limit. By enabling the 'slow_query_log' flag, you can keep an eye on your MySQL database performance, allowing you to identify which queries need optimization.\nNote: By default, the 'slow_query_log' database flag is not enabled for Google Cloud MySQL instances.","impact":"By disabling the 'slow_query_log' flag, you make it difficult to find inefficient or time-consuming SQL queries for your MySQL database instances to identify and troubleshoot sub-optimal database performance.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the Cloud SQL Instances page in the Google Cloud Console using https://console.cloud.google.com/sql/instances.\n2. Select the MySQL instance for which you want to enable the database flag.\n3. Click Edit.\n4. Scroll down to the Flags section.\n5. To set a flag that has not been set on the instance before, click Add item, choose the 'slow_query_log' flag from the drop-down menu and set the appropriate value.\n6. Click Save to save your changes.\n7. Confirm your changes under Flags on the Overview page.","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-089":{"article":"On Google's Global HTTP Load Balancer, each HTTPS target proxy is linked to a certificate. Ensure that SSL/TLS certificates are renewed one week before expiry.","impact":"SSL/TLS certificates not renewed prior to their expiration date become invalid and the communication between a client and a GCP resource that implements the certificates is no longer secure.","report_fields":["selfLink"],"remediation":"In the Google Cloud Platform Console, you create a new SSL certificate resource when you create or edit a frontend for an HTTPS or SSL proxy load balancer.\nTo create a new SSL certificate resource for a frontend:\n1. Go to the Load balancing page in the Google Cloud Platform Console.\n2.Click the name of an HTTPS or SSL proxy load balancer.\n3.Click the Edit pencil.\n4.Select Frontend Configuration.\n-For an existing frontend, click the Edit pencil for that frontend. In the Certificate drop-down menu, select a visible certificate, then click Create a new certificate.\n-For a new frontend, in the Certificate drop-down menu, click Select a certificate, and then click Create a new certificate.\n5.In the Create a new certificate dialog box, enter a Name for your certificate resource, and optionally click Add a description and enter a description.\n6.Choose Upload my certificate.\n7.In the Public key certificate field, click the Upload button to upload your .crt file or paste the entire contents of your .key file into the field, including the -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- that enclose the file contents.\n8.In the Certificate chain field, click the Upload button to upload your .csr file or paste the entire contents of the .csr file into the field, including the -----BEGIN CERTIFICATE REQUEST----- and -----END CERTIFICATE REQUEST----- that enclose the file contents.\n9.In the Private key certificate field, click the Upload button to upload your private key, using the .key file generated previously. This file uses, for example, -----BEGIN RSA PRIVATE KEY----- and -----END RSA PRIVATE KEY----- to enclose the file contents.\n10.Click Create.\n11.To create additional certificates, click the additional certificates link, click Select a Certificate, and then click Create a new certificate. Follow the previous steps to upload or paste the appropriate files.\nRefer to: https://cloud.google.com/load-balancing/docs/ssl-certificates","multiregional":true,"service":"Cloud Load Balancing"},"ecc-gcp-133":{"article":"With Intranode Visibility, all network traffic in your cluster is seen by the Google Cloud Platform network. This means you can see flow logs for all traffic between Pods, including traffic between Pods on the same node. And you can create firewall rules that apply to all traffic between Pods. This policy checks your cluster's intra-node visibility feature and generates an alert if it's disabled.","impact":"When you disable Intranode visibility can not ensure that packets sent between Pods are always processed by the VPC network, which ensures that firewall rules, routes, flow logs, and packet mirroring configurations apply to the packets. It can lead to insufficient response time to detecting any suspicious traffic between Pods. With Intranode visibility disabled, packets sent between Pods are not processed by the VPC network, which means that firewall rules, routes, flow logs, and packet mirroring configurations won't apply to the packets. It can lead to insufficient response time to detect any suspicious traffic between Pods.","report_fields":["selfLink"],"remediation":"Using Google Cloud Console:\n1. Go to Kubernetes Engine by visiting https://console.cloud.google.com/kubernetes/list\n2. Select Kubernetes clusters for which intranode visibility is disabled.\n3. Click on EDIT.\n4. Set 'Intranode visibility' to 'Enabled'.\n5. Click SAVE.\nUsing Command Line:\nTo enable intranode visibility on an existing cluster, run the following command:\ngcloud beta container clusters update [CLUSTER_NAME] \\\n--enable-intra-node-visibility","multiregional":true,"service":"Google Kubernetes Engine"},"ecc-gcp-090":{"article":"Enforce the use of secure protocols TLS v1.1 and TLS v1.2 in an HTTP(S) Load balancer with CDN.","impact":"Using outdated and insecure ciphers for the SSL policies associated with your HTTPS/SSL Proxy load balancers could make the SSL connection between CDN and load balancers vulnerable to exploits.","report_fields":["selfLink"],"remediation":"From Console:\n1. Ensure that CDN is enabled on Load Balancer.\n2. Go to the Load balancing page in the Google Cloud Platform Console.\n3. Find the target Load Balancer name.\n4. Click the Edit pencil.\n5. Configure Backend.\n6. Choose HTTPS as protocol at the Backend configuration.","multiregional":true,"service":"Cloud Load Balancing"},"ecc-gcp-109":{"article":"Ensure that an access is restricted from all ports and protocols.","impact":"Allowing unrestricted ingress access to uncommon ports can increase opportunities for malicious activities such as hacking and various types of attacks (brute-force attacks, Denial of Service (DoS) attacks, etc.) that may lead to data loss.","report_fields":["selfLink"],"remediation":"From Console:\n1. Select 'VPC network' from the side menu.\n2. Select 'Firewall rules'.\n3. Delete or Update all the firewall rules containing '0-65535' or 'all' in the Protocols/ports column that allow ingress.","multiregional":true,"service":"Virtual Private Cloud"},"ecc-gcp-436":{"article":"Instance addresses can be public IP or private IP. Public IP means that the instance is accessible through the public internet. In contrast, instances using only private IP are not accessible through the public internet, but are accessible through a Virtual Private Cloud (VPC). Limiting network access to the database will limit potential attacks.","impact":"An instance with a public IP address could potentially be compromised, and an attacker could gain anonymous access to it and other resources connected to this instance, which may lead to malicious activity with sensitive data.","report_fields":["selfLink"],"remediation":"From Google Cloud Console:\n1. In the Google Cloud console, go to the Cloud SQL Instances page.\n2. Open the Overview page of an instance by clicking the instance name.\n3. Select Connections from the SQL navigation menu.\n4. Check the Private IP checkbox. A drop-down list shows the available networks in your project.\n5. Select the VPC network you want to use:\n If you see Private service connection required:\n 1. Click Set up connection.\n 2. In the Allocate an IP range section, choose one of the following options:\n 3. Select one or more existing IP ranges or create a new one from the dropdown. The dropdown includes previously allocated ranges, if there are any, or you can select Allocate a new IP range and enter a new range and name.\n 4. Use an automatically allocated IP range in your network.\n Note: You can specify an address range only for a primary instance, not for a read replica or clone.\n 5. Click Continue.\n 6. Click Create connection.\n 7. Verify that you see the Private service connection for network VPC_NETWORK_NAME has been successfully created status.\n6. [Optional step for Private Services Access - review reference links to VPC documents for additional detail] If you want to allow other Google Cloud services such as BigQuery to access data in Cloud SQL and make queries against this data over a private IP connection, then select the Private path for Google Cloud services check box.\n7. Click Save\nFrom Google Cloud CLI:\n1. List cloud SQL instances\ngcloud sql instances list --format=\"json\" | jq '.[] | .connectionName,.ipAddresses'\nNote the project name of the instance you want to set to a private IP, this will be \nNote the instance name of the instance you want to set to a private IP, this will be \nExample public instance output:\n\"my-project-123456:us-central1:my-instance\"\n[\n {\n \"ipAddress\": \"0.0.0.0\",\n \"type\": \"PRIMARY\"\n },\n {\n \"ipAddress\": \"0.0.0.0\",\n \"type\": \"OUTGOING\"\n }\n2. Run the following command to list the available VPCs\ngcloud compute networks list --format=\"json\" | jq '.[].name'\nNote the name of the VPC to use for the instance private IP, this will be \n3. Run the following to set instance to a private IP\ngcloud beta sql instances patch \\\n--project= \\\n--network=projects//global/networks/ \\\n--no-assign-ip","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-210":{"article":"The user options option specifies global defaults for all users. It is recommended that 'user options' database flag for Cloud SQL SQL Server instance should not be configured.","impact":"The 'user options' option allows changing the default values of the SET options (if the server's default settings are not appropriate). A user can override these defaults by using the SET statement.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the Cloud SQL Instances page in the Google Cloud Console using https://console.cloud.google.com/sql/instances.\n2. Select the SQL Server instance for which you want to enable the database flag.\n3. Click Edit.\n4. Scroll down to the Flags section.\n5. Check the box next to the 'user options' flag.\n6. Click Save to save your changes.\n7. Confirm your changes under Flags on the Overview page.\nFrom Command Line:\n1. List all Cloud SQL database Instances\ngcloud sql instances list\n2. Clear the user options database flag for every Cloud SQL SQL Server database instance using either of the below commands.\na. Clearing all flags to their default value:\ngcloud sql instances patch --clear-database-flags\nb. To clear only `user options` database flag, configure the database flag by overriding the `user options`. Exclude `user options` flag and its value, and keep all other flags you want to configure:\ngcloud sql instances patch --database-flags [FLAG1=VALUE1,FLAG2=VALUE2]\nNote : This command will overwrite all database flags previously set. To keep those and add new ones, include the values for all flags you want set on the instance; any flag not specifically included is set to its default value. For flags that do not take a value, specify the flag name followed by an equals sign (\"=\").","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-170":{"article":"Secure Sockets Layer (SSL) policies determine what port Transport Layer Security (TLS) features clients are permitted to use when connecting to load balancers. To prevent usage of insecure features, SSL policies should use (a) at least TLS 1.2 with the MODERN profile; or (b) the RESTRICTED profile, because it effectively requires clients to use TLS 1.2 regardless of the chosen minimum TLS version; or (3) a CUSTOM profile that does not support any of the following features: TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_256_GCM_SHA384, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_3DES_EDE_CBC_SHA.","impact":"GCP default SSL policy uses a minimum TLS version of 1.0 and a Compatible profile, which allows the widest range of insecure cipher suites. As a result, it is easy for customers to configure a load balancer without even knowing that they are permitting outdated cipher suites.","report_fields":["selfLink"],"remediation":"From Console:\nIf the TargetSSLProxy or TargetHttpsProxy does not have an SSL policy configured, create a new SSL policy. Otherwise, modify the existing insecure policy.\n2. Navigate to the SSL Policies page by visiting 'https://console.cloud.google.com/net-security/sslpolicies'\n3. Click on the name of the insecure policy to go to its SSL policy details page.\n4. Click EDIT.\n5. Set Minimum TLS version to TLS 1.2.\n6. Set Profile to Modern or Restricted.\n7. Alternatively, if teh user selects the profile Custom, make sure that the following features are disabled: TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_256_GCM_SHA384, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_3DES_EDE_CBC_SHA.\nFrom Command Line:\n1. For each insecure SSL policy, update it to use secure cyphers:\ngcloud compute ssl-policies update NAME [--profile COMPATIBLE|MODERN|RESTRICTED|CUSTOM] --min-tls-version 1.2 [--custom-features FEATURES]\n2. If the target proxy has a GCP default SSL policy, use the following command corresponding to the proxy type to update it:\ngcloud compute target-ssl-proxies update TARGET_SSL_PROXY_NAME --ssl-policy SSL_POLICY_NAME\ngcloud compute target-https-proxies update TARGET_HTTPS_POLICY_NAME --ssl-policy SSL_POLICY_NAME","multiregional":true,"service":"Cloud Load Balancing"},"ecc-gcp-400":{"article":"It is recommended to set the 'password_history' database flag for Cloud SQL Mysql instance.","impact":"Using of 'password_history' database flag allow to establish password-reuse policy and prevent using the old passwords.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the Cloud SQL Instances page in the Google Cloud Console using https://console.cloud.google.com/sql/instances.\n2. Select the Mysql instance for which you want to enable the database flag.\n3. Click Edit.\n4. Scroll down to the Flags section.\n5. To set a flag that has not been set on the instance before, click Add item, choose the 'password_history' flag from the drop-down menu, and set its value.\n6. Click Save to save your changes.\n7. Confirm your changes under Flags on the Overview page.\nUsing Command Line:\n1. List all Cloud SQL database Instances\ngcloud sql instances list\n2. Configure the 'password_history' database flag for every Cloud SQL Mysql database instance using the below command:\ngcloud sql instances patch INSTANCE_NAME --database-flags password_history={number of subsequent account password changes that must occur before the password can be reused}\nNote : This command will overwrite all database flags previously set. To keep those and add new ones, include the values for all flags you want set on the instance; any flag not specifically included is set to its default value. For flags that do not take a value, specify the flag name followed by an equals sign (\"=\").","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-134":{"article":"Istio is an open service mesh that provides a uniform way to connect, manage, and secure microservices. It supports managing traffic flows between services, enforcing access policies, and aggregating telemetry data, all without requiring changes to the microservice code. This policy checks your cluster for the Istio add-on feature and alerts if it is not enabled.","impact":"istioConfig provides automatic load balancing that helps against potential DDoS attacks. In addition, it provides secure communication between services in the cluster with strong authentication and identity-based authorization, which is a uniform way to connect, manage, and secure microservices instead of different unsafe accesses.","report_fields":["selfLink"],"remediation":"Add Istio to your existing cluster.\nIf you want to update a cluster with the add-on, you may need first to resize your cluster to ensure that you have enough resources for Istio. As for creation of a new cluster, we suggest at least a 4 node cluster with the 2 vCPU machine type. Your cluster must also be running a supported cluster master version to use the add-on.\n1. Go to the Kubernetes clusters page in GCP Console and select the cluster you want to update.\n2. Select Edit.\n3. Select Add-ons to display possible add-ons, including Istio on GKE.\n4. Select Enabled under Istio.\n5. From the drop-down list, select the mTLS security mode you want to use for your cluster.\n6. Click Save to update your cluster.","multiregional":true,"service":"Google Kubernetes Engine"},"ecc-gcp-233":{"article":"Ensure that your virtual machine disk images are not publicly shared with all other Google Cloud Platform (GCP) accounts in order to avoid exposing sensitive or confidential data. If required, you can share your disk images with specific GCP accounts only, without making them public.","impact":"Granting permissions to 'allUsers' and 'allAuthenticatedUsers' members can allow anyone to access your compute image that breaches data privacy.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the Compute Image page at Google Cloud Console.\n2. Select an image from Resources.\n3. Review each role and find Members having allUsers or allAuthenticatedUsers.\n4. Click Delete icon and confirm by clicking on REMOVE.\n5. Click Done.","multiregional":true,"service":"Compute Engine"},"ecc-gcp-279":{"article":"This policy identifies GCP Firewall rules that allow inbound traffic to the FTP port (20) from the public internet. Allowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Allowing unrestricted FTP access to your Google Cloud virtual machine (VM) instances via VPC network firewall rules can increase opportunities for malicious activities such as brute-force attacks, FTP bounce attacks, spoofing, and packet capture attacks.","report_fields":["selfLink"],"remediation":"To restrict all traffic, edit the reported Firewall rule as follows:\n1. Login to GCP Console.\n2. Go to VPC Network.\n3. Go to Firewall rules.\n4. Click on the reported Firewall rule.\n5. Click on Edit.\n6. Modify Source IP ranges to specific IP.\n7. Click on Save.","multiregional":true,"service":"Virtual Private Cloud"},"ecc-gcp-114":{"article":"This policy identifies GCP Firewall rules that allow inbound traffic to the MongoDB port (27017) from the public internet. Allowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Allowing unrestricted inbound/ingress access on TCP port 27017 (MongoDB) via VPC network firewall rules can increase opportunities for malicious activities such as hacking, brute-force attacks, and SQL injection attacks.","report_fields":["selfLink"],"remediation":"To restrict all traffic, edit the reported Firewall rule as follows:\n1. Login to GCP Console.\n2. Go to VPC Network.\n3. Go to Firewall rules.\n4. Click on the reported Firewall rule.\n5. Click on Edit.\n6. Modify Source IP ranges to specific IP.\n7. Click on Save.","multiregional":true,"service":"Virtual Private Cloud"},"ecc-gcp-116":{"article":"This policy identifies GCP Firewall rules that allow inbound traffic to the NetBIOS-SSN port (139) from the public internet. Allowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Allowing unrestricted inbound access on port 139 (NetBIOS-SSN) via VPC network firewall rules can increase opportunities for malicious activities such as DDoS attacks.","report_fields":["selfLink"],"remediation":"To restrict all traffic, edit the reported Firewall rule as follows:\n1. Login to GCP Console.\n2. Go to VPC Network.\n3. Go to Firewall rules.\n4. Click on the reported Firewall rule.\n5. Click on Edit.\n6. Modify Source IP ranges to specific IP.\n7. Click on Save.","multiregional":true,"service":"Virtual Private Cloud"},"ecc-gcp-305":{"article":"This rule detects when Dataproc cluster is anonymously or publicly accessible.\nA Dataproc cluster contains at least one \"management\" VM and one \"compute\" VM. Access to Dataproc clusters is controlled via IAM policies. These IAM policies can be set for public access via the allUsers and allAuthenticatedUsers IAM principals which can inadvertently expose your data to the public.","impact":"Granting permissions to allUsers or allAuthenticatedUsers allows anyone to access the Dataproc cluster. Such access might not be desirable if sensitive data is in the Dataproc cluster.","report_fields":["clusterName","projectId"],"remediation":"From Console:\n1. Log in to the GCP Console at https://console.cloud.google.com.\n2. Navigate to Clusters.\n3. Select the target Dataproc cluster.\n4. Expand the Info Panel by selecting Show Info Panel.\n5. To remove a specific role assignment, select allUsers or allAuthenticatedUsers, and then click Remove member.","multiregional":true,"service":"Dataproc"},"ecc-gcp-286":{"article":"Policy identifies GCP Pub/Sub topics that are not encrypted using a customer-managed encryption key. It is a best practice to use customer-managed KMS Keys to encrypt your Pub/Sub topic.","impact":"Not using CMEK results in less control over aspects of the lifecycle and management of keys.","report_fields":["name"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nOnce the Pub/Sub topic is created it is not modifiable.\nTo remediate you need to create one new topic with encrypted using customer-managed encryption key following below link and then delete the alerted topic.\nhttps://cloud.google.com/pubsub/docs/encryption","multiregional":true,"service":"Pub/Sub"},"ecc-gcp-447":{"article":"VM disk is not attached to any instance.","impact":"Keeping unused VM disk will increase your monthly bill.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the Disks page in the Google Cloud Platform Console https://console.cloud.google.com/compute/disks.\n2. Click on reported disk and Delete it or Create Instance.","multiregional":true,"service":"Compute Engine"},"ecc-gcp-180":{"article":"Enabling the 'log_lock_waits' flag for a PostgreSQL instance creates a log for any session waits that take longer than the allotted deadlock_timeout time to acquire a lock.","impact":"Not logging such waits on locks by enabling the 'log_lock_waits' database flag can lead to insufficient response time to identify poor performance due to locking delays.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the Cloud SQL Instances page in the Google Cloud Console using https://console.cloud.google.com/sql/instances.\n2. Select the PostgreSQL instance where the database flag needs to be enabled.\n3. Click Edit.\n4. Scroll down to the Flags section.\n5. To set a flag that has not been set on the instance before, click Add item, choose the flag 'log_lock_waits' from the drop-down menu and set the value as 'on'.\n6. Click Save.\n7. Confirm the changes under Flags on the Overview page.\nFrom Command Line:\n1. List all Cloud SQL database instances using the following command:\ngcloud sql instances list\n2. Configure the 'log_lock_waits' database flag for every Cloud SQL PosgreSQL database instance using the below command:\ngcloud sql instances patch INSTANCE_NAME --database-flags log_lock_waits=on\nNote: This command will overwrite all database flags previously set. To keep those and add new ones, include the values for all flags to be set on the instance; any flag not specifically included is set to its default value. For flags that do not take a value, specify the flag name followed by an equals sign ('='). Default Value By default log_lock_waits is off.","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-171":{"article":"Ensure that your instance is not configured to use the default Compute Engine service account as it is associated with the Editor role on the project.","impact":"Using the default Compute Engine service account can lead to privilege escalations. If your VM is compromised, it allows attackers to gain access to all your projects with the Editor role.","report_fields":["selfLink"],"remediation":"Using Console:\n1. Go to the VM instances page using 'https://console.cloud.google.com/compute/instances'.\n2. Click on the instance name to go to its 'VM instance details' page.\n3. Click on 'Stop' and then click on 'Edit'\n4. Under the 'Service Account' section, select a service account other that the default Compute Engine service account. You may first need to create a new service account.\n5. Click on 'Save' and then click on 'Start'.\nFrom Command Line:\n1. Stop the instance:\ngcloud compute instances stop INSTANCE_NAME\n2. Update the instance:\ngcloud compute instances set-service-account INSTANCE_NAME --serviceaccount=SERVICE_ACCOUNT\n3. Restart the instance:\ngcloud compute instances start INSTANCE_NAME","multiregional":true,"service":"Compute Engine"},"ecc-gcp-062":{"article":"Private Google Access enables your cluster hosts, which have only private IP addresses, to communicate with Google APIs and services using an internal IP address rather than an external IP address. External IP addresses are routable and reachable over the Internet. Internal (private) IP addresses are internal to Google Cloud Platform and are not routable or reachable over the Internet. You can use Private Google Access to allow VMs without Internet access to reach Google APIs, services, and properties that are accessible over HTTP/HTTPS.","impact":"Without Private Google Access, VM instances that need to reach the Google Cloud and Developer APIs and services require an external IP. Having an external IP exposes VM instances to the public Internet, which increases the attack surface.","report_fields":["selfLink"],"remediation":"Using Console:\n1. Go to Kubernetes GCP Console using https://console.cloud.google.com/kubernetes/list.\n2. From the list of clusters, for each cluster, note the Subnet name.\n3. Go to VPC network GCP Console using https://console.cloud.google.com/networking/networks/list.\n4. Click the noted subnet, the Subnet details page is displayed.\n5. Click on Edit.\n6. Set Private Google access to On.\n7. Click on Save.\nUsing Command Line:\nTo set Private Google access for a network subnet, run the following command:\ngcloud compute networks subnets update [SUBNET_NAME] --region [REGION] --enable-private-ip-google-access","multiregional":true,"service":"Virtual Private Cloud"},"ecc-gcp-132":{"article":"Application-layer Secrets Encryption provides an additional layer of security for sensitive data, such as Secrets, stored in etcd. Using this functionality, you can use a key, that you manage in Cloud KMS, to encrypt data at the application layer. This protects against the attackers gaining access to an offline copy of etcd. This policy checks your cluster for the Application-layer Secrets Encryption security feature and alerts if it is not enabled. Enabling application-layer secrets encryption for your GKE clusters is considered a security best practice for applications that store sensitive and confidential data.","impact":"Without this feature, you can not use an encryption key managed with Cloud KMS to encrypt data at the application layer and protect against attackers that gain access to an offline copy of etcd.","report_fields":["selfLink"],"remediation":"To enable Application-layer Secrets Encryption, several configuration items are required. These include:\n- A key ring\n- A key\n- A GKE service account with Cloud KMS CryptoKey Encrypter/Decrypter role\nOnce these are created, Application-layer Secrets Encryption can be enabled on an existing or new cluster.\nUsing Google Cloud Console:\nTo create a key:\n1. Go to Cloud KMS by visiting https://console.cloud.google.com/security/kms\n2. Select CREATE KEY RING.\n3. Enter a Key ring name and the region where the keys will be stored.\n4. Click CREATE.\n5. Enter a Key name and appropriate rotation period within the Create key pane.\n6. Click CREATE.\nTo enable on a new cluster:\n1. Go to Kubernetes Engine by visiting https://console.cloud.google.com/kubernetes/list\n2. Click CREATE CLUSTER.\n3. Expand the template by clicking 'Availability, networking, security, and additional features' and check the 'Enable Application-layer Secrets Encryption' checkbox.\n4. Select the desired Key as the customer-managed key and if prompted grant permissions to the GKE Service account.\n5. Click CREATE.\nTo enable on an existing cluster:\n1. Go to Kubernetes Engine by visiting https://console.cloud.google.com/kubernetes/list\n2. Click to edit cluster you want to modify.\n3. Enable Application-layer Secrets Encryption and choose the desired Key.\n4. Click SAVE.\nUsing Command Line:\nTo create a key:\n Create a key ring:\n gcloud kms keyrings create [RING_NAME] \\\n --location [LOCATION] \\\n --project [KEY_PROJECT_ID]\n Create a key:\n gcloud kms keys create [KEY_NAME] \\\n --location [LOCATION] \\\n --keyring [RING_NAME] \\\n --purpose encryption \\\n --project [KEY_PROJECT_ID]\nGrant the Kubernetes Engine Service Agent service account the Cloud KMS CryptoKey Encrypter/Decrypter role:\ngcloud kms keys add-iam-policy-binding [KEY_NAME] \\\n--location [LOCATION] \\\n--keyring [RING_NAME] \\\n--member serviceAccount:[SERVICE_ACCOUNT_NAME] \\\n--role roles/cloudkms.cryptoKeyEncrypterDecrypter \\\n--project [KEY_PROJECT_ID]\nTo create a new cluster with Application-layer Secrets Encryption:\ngcloud container clusters create [CLUSTER_NAME] \\\n--cluster-version=latest \\\n--zone [ZONE] \\\n--database-encryption-key\nprojects/[KEY_PROJECT_ID]/locations/[LOCATION]/keyRings/[RING_NAME]/cryptoKeys/[KEY_NAME] \\\n--project [CLUSTER_PROJECT_ID]\nTo enable on an existing cluster:\ngcloud container clusters update [CLUSTER_NAME] \\\n--zone [ZONE] \\\n--database-encryption-key\nprojects/[KEY_PROJECT_ID]/locations/[LOCATION]/keyRings/[RING_NAME]/cryptoKeys/[KEY_NAME] \\\n--project [CLUSTER_PROJECT_ID]","multiregional":true,"service":"Google Kubernetes Engine"},"ecc-gcp-110":{"article":"This policy identifies GCP Firewall rules that allow inbound traffic to the DNS port (53) from the public internet. Allowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Allowing unrestricted DNS access to your Google Cloud virtual machines (VMs) through VPC network firewall rules can increase opportunities for malicious activities such as Denial of Service (DoS) attacks and Distributed Denial of Service (DDoS) attacks.","report_fields":["selfLink"],"remediation":"To restrict all traffic, edit the reported Firewall rule as follows:\n1. Login to GCP Console.\n2. Go to VPC Network.\n3. Go to Firewall rules.\n4. Click on the reported Firewall rule.\n5. Click on Edit.\n6. Modify Source IP ranges to specific IP.\n7. Click on Save.","multiregional":true,"service":"Virtual Private Cloud"},"ecc-gcp-223":{"article":"The default network in GCP region is generally considered insecure and should be avoided. The default firewall and routes are generic in nature and do not lock down sensitive ports or traffic routing.","impact":"Automatically created firewall rules of the default network have a number of open ports, including port 22. This may increase opportunities for malicious activities such as unauthorized access, Man-In-The-Middle attacks (MITM), and brute-force attacks that raise the risk of resource compromise.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to VM instances page https://console.cloud.google.com/compute/instances.\n2. Select the VM instance whose configuration you want to change.\n3. Click STOP.\n4. Click EDIT.\n5. In Network interfaces section, click the edit icon.\n6. Select Network and Subnetwork that are not default.\n7. Click Save.","multiregional":true,"service":"Compute Engine"},"ecc-gcp-189":{"article":"It is recommended to store objects in a multi-region or dual-region locations to ensure data availability in the event of large-scale disruptions.","impact":"Geo-redundancy ensures maximum availability of your data, even in the event of large-scale disruptions such as natural disasters.","report_fields":["selfLink"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nUsing Console:\n1. Open the Cloud Storage browser in the Google Cloud Console.\n2. Click Create bucket to open the bucket creation form.\n3. Enter your bucket information and click Continue to complete each step:\n 3.1 Specify a Name, subject to the bucket name requirements.\n 3.2 Select the Multi-region Location type.\n 3.3 Select the Default storage class for the bucket. The default storage class is assigned by default to all objects uploaded to the bucket.\n 3.4. Select the Access control model to determine how you control access to the bucket's objects. Optionally, you can add bucket labels, set a retention policy, and choose an encryption method.\n4. Click Create.","multiregional":true,"service":"Cloud Storage"},"ecc-gcp-190":{"article":"Enabling retention policies on log buckets will protect logs stored in cloud storage buckets from being overwritten or accidentally deleted. It is recommended to set up retention policies and configure Bucket Lock on all storage buckets that are used as log sinks.","impact":"Sinks can be configured to export logs in storage buckets. It is recommended to configure a data retention policy for these cloud storage buckets and to lock the data retention policy, thus permanently preventing the policy from being reduced or removed. This way, if the system is ever compromised by an attacker or a malicious insider who wants to cover their tracks, the activity logs are definitely preserved for forensics and security investigations.","report_fields":["selfLink"],"remediation":"From Console:\n1. If sinks are not configured, first follow the instructions in the recommendation:\nEnsure that sinks are configured for all Log entries.\n2. For each storage bucket configured as a sink, go to the Cloud Storage browser at https://console.cloud.google.com/storage/browser/\n3. Select the Bucket Lock tab near the top of the page.\n4. In the Retention policy entry, click the Add Duration link. The 'Set a retention policy' dialog box appears.\n5. Enter the desired length of time for the retention period and click 'Save policy'.\n6. Set the 'Lock status' for this retention policy to 'Locked'.\nFrom Command Line:\n1. To list all sinks destined to storage buckets:\ngcloud logging sinks list --folder=FOLDER_ID | --organization=ORGANIZATION_ID | --project=PROJECT_ID\n2. For each storage bucket listed above, set a retention policy and lock it:\ngsutil retention set [TIME_DURATION] gs://[BUCKET_NAME]\ngsutil retention lock gs://[BUCKET_NAME]\nFor more information, visit https://cloud.google.com/storage/docs/using-bucket-lock#set-policy","multiregional":true,"service":"Cloud Storage"},"ecc-gcp-115":{"article":"This policy identifies GCP Firewall rules that allow inbound traffic to the MySQL DB port (3306) from the public internet. Allowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Allowing unrestricted inbound/ingress access on TCP port 3306 (MySQL DB) via VPC network firewall rules can increase opportunities for malicious activities such as hacking, brute-force attacks, and SQL injection attacks.","report_fields":["selfLink"],"remediation":"To restrict all traffic, edit the reported Firewall rule as follows:\n1. Login to GCP Console.\n2. Go to VPC Network.\n3. Go to Firewall rules.\n4. Click on the reported Firewall rule.\n5. Click on Edit.\n6. Modify Source IP ranges to specific IP.\n7. Click on Save.","multiregional":true,"service":"Virtual Private Cloud"},"ecc-gcp-176":{"article":"It is recommended to set the 'local_infile' database flag for a Cloud SQL MySQL instance to 'off'.","impact":"Enabling the 'local_infile' database flag controls server-side LOCAL capability, and the server permits local data loading by the clients requesting it.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the Cloud SQL Instances page in the Google Cloud Console using https://console.cloud.google.com/sql/instances.\n2. Select the MySQL instance where the database flag needs to be enabled.\n3. Click Edit.\n4. Scroll down to the Flags section.\n5. To set a flag that has not been set on the instance before, click Add item, choose the 'local_infile' flag from the drop-down menu, and set its value to 'off'.\n6. Click Save.\n7. Confirm the changes under Flags on the Overview page.\nFrom Command Line:\n1. List all Cloud SQL database instances using the following command:\ngcloud sql instances list\n2. Configure the 'local_infile' database flag for every Cloud SQL MySQL database instance using the below command:\ngcloud sql instances patch INSTANCE_NAME --database-flags local_infile=off\nNote: This command will overwrite all database flags that were previously set. To keep those and add new ones, include the values for all flags to be set on the instance; any flag not specifically included is set to its default value. For flags that do not take a value, specify the flag name followed by an equals sign ('=').","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-261":{"article":"Ensure that your Google Cloud Dataproc clusters are encrypted with Customer-Managed Keys (CMKs) in order to have a fine control over the cluster data encryption/decryption process. You can create and manage your own Customer-Managed Keys (CMKs) with Cloud Key Management Service (Cloud KMS). Cloud KMS provides secure and efficient encryption key management, controlled key rotation, and revocation mechanisms.\nBy default, the Dataproc service encrypts all data at rest using Google-managed encryption keys. The Dataproc cluster data is encrypted using a Google-generated Data Encryption Key (DEK) and a Key Encryption Key (KEK). If you need to control and manage your cluster data encryption yourself, you can use your own Customer-Managed Keys (CMKs). Cloud KMS Customer-Managed Keys can be implemented as an additional security layer on top of existing data encryption, and are often used in the enterprise world, where compliance and security controls are very strict.","impact":"Not using Customer-Managed Keys results in less control over aspects of your keys' lifecycle and management.","report_fields":["clusterName","projectId"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nFrom Console:\n1. Login to the GCP Console and navigate to the Dataproc Cluster page by visiting https://console.cloud.google.com/dataproc/clusters.\n2. Select the project from the projects dropdown list.\n3. On the Dataproc Cluster page, click on the Create Cluster to create a new cluster with Customer managed encryption keys.\n4. On Create a cluster page, perform below steps:\n \u2022 Inside Set up cluster section perform below steps:\n -In the Name textbox, provide a name for your cluster.\n o From Location select the location in which you want to deploy a cluster.\n o Configure other configurations as per your requirements.\n \u2022 Inside Configure Nodes and Customize cluster section configure the settings as per your requirements.\n \u2022 Inside Manage security section, perform below steps:\n o From Encryption, select Customer-managed key.\n o Select a customer-managed key from dropdown list.\n o Ensure that the selected KMS Key have Cloud KMS CryptoKey Encrypter/Decrypter role assign to Dataproc Cluster service account (\"serviceAccount:service-@compute-system.iam.gserviceaccount.com\").\n o Click on Create to create a cluster.\n \u2022 Once the cluster is created migrate all your workloads from the older cluster to the new cluster and delete the old cluster by performing the below steps:\n o On the Clusters page, select the old cluster and click on Delete cluster.\n o On the Confirm deletion window, click on Confirm to delete the cluster.\n o Repeat step above for other Dataproc clusters available in the selected project.\n \u2022 Change the project from the project dropdown list and repeat the remediation procedure for other Dataproc clusters available in other projects.","multiregional":true,"service":"Dataproc"},"ecc-gcp-053":{"article":"A node in a degraded state is an unknown quantity and so may pose a security risk.","impact":"Lack of automatic recovery of Kubernetes Engine nodes, you can lead to insufficient response time to initiate a recovery process for a cluster node that fails sequential health checks within the specified threshold time.","report_fields":["selfLink"],"remediation":"Using Google Cloud Console:\n1. Go to Kubernetes Engine by visiting https://console.cloud.google.com/kubernetes/list.\n2. Select Kubernetes clusters for which node auto-repair is disabled.\n3. Click on the name of the Node pool that requires node auto-repair to be enabled.\n4. Within the Node pool details pane, click EDIT.\n5. Under the 'Management' heading, ensure the 'Enable Auto-repair' box is checked.\n6. Click on SAVE.\nUsing Command Line:\nTo enable node auto-repair for an existing cluster with Node pool, run the following command:\ngcloud container node-pools update $POOL_NAME --cluster $CLUSTER_NAME --zone $COMPUTE_ZONE --enable-autorepair","multiregional":true,"service":"Google Kubernetes Engine"},"ecc-gcp-303":{"article":"A Dataflow job consists of at least one management node and one compute node (both are GCE VMs). By default, these nodes are configured with public IPs that allow them to communicate with the public internet","impact":"Public IP increases your potential attack surface by being publicly accessible.","report_fields":["projectId","id"],"remediation":"From Console:\n1. Go to Dataflow jobs https://console.cloud.google.com/dataflow/jobs.\n2. Drain or cancel your job and then re-create with the Private IP in the Worker IP Address Configuration.","multiregional":true,"service":"Dataflow"},"ecc-gcp-236":{"article":"Ensure that MySQL database instances are using the latest major version of MySQL database in order to receive the latest database features and benefit from enhanced performance and security.","impact":"If your MySQL database instances are not using the latest major version of the MySQL database, you cannot benefit from security updates.","report_fields":["selfLink"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nMySQL database version cannot be automatically upgraded within Google Cloud Platform (GCP).\nTo upgrade your Google Cloud MySQL instances to the latest major version of the MySQL database, you have to re-create the existing instance, export data from the existing (source) instance, and importing that data into a new (target) instance running the latest major version of MySQL.","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-247":{"article":"This rule detects when a function are not in 'ACTIVE' node. 'ACTIVE' mode means that function has been successfully deployed and is serving.","impact":"When your function is not in \"ACTIVE\" mode, it means that it cannot trigger when an event being watched is fired.","report_fields":["name"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nFrom Console:\n1. Go to the Cloud Functions page at Google Cloud Console https://console.cloud.google.com/functions.\n2. Select the relevant function.\n3. Click DELETE.\n4. In the confirmation box click Delete.\nFrom Command Line:\ngcloud functions delete FUNCTION_NAME --region=REGION","multiregional":true,"service":"Cloud Functions"},"ecc-gcp-086":{"article":"Ensure that SSL/TLS certificates stored in Cloud SQL are renewed one month before expiry.","impact":"SSL/TLS certificates not renewed prior to their expiration date become invalid, and the communication between a client and a GCP resource that implements the certificates is no longer secure.","report_fields":["selfLink"],"remediation":"You must take the following steps to complete the rotation:\n1.Download the new server certificate information.\n2.Update your clients to use the new server certificate information.\n3.Complete the rotation, which moves the currently active certificate into the \"previous\" slot and updates the newly added certificate to be the active certificate.\nDownload the new server certificate information:\n1.Go to the Cloud SQL Instances page in the Google Cloud Platform Console.\n2.Click the instance name to open its Instance details page.\n3.Select the CONNECTIONS tab.\n4.Scroll down to the Configure SSL server certificates section.\n5.Click Create new certificate.\n6.Scroll down to Download SSL server certificates section.\n7.Click Download.\nThe server certificate information, encoded as a PEM file, is displayed and can be downloaded to your local environment.\n8.Update all of your clients to use the new information.\nAfter you have updated your clients, complete the rotation:\n1.Return to the Configure SSL server certificates section.\n2.Click Rotate certificate.\n3.Confirm that your clients are connecting properly.\nIf any clients are not connecting using the newly rotated certificate, you can click Rollback certificate to roll back to the previous configuration.\nLink: https://cloud.google.com/sql/docs/mysql/configure-ssl-instance","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-222":{"article":"To support principle of least privileges and prevent potential privilege escalation instance templates should not be assigned to the default Compute Engine service account, or a service account that provides full access to all Cloud APIs.","impact":"Using the default service account on your instance template can increase malicious activity to the point that an attacker gains full control of the project.","report_fields":["selfLink"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nWe can't Edit an exisitng Instance Template\nFrom Console:\n1. In the Google Cloud Console, go to the Instance Template page https://console.cloud.google.com/compute/instanceTemplates.\n2. Click on Create Instance Template\n3. Scroll down to the Service Account section.\n4. Select a different service account or ensure that Allow full access to all Cloud APIs is not selected.\n5. Click on Create and Delete old template.","multiregional":true,"service":"Compute Engine"},"ecc-gcp-197":{"article":"Google Cloud encrypts data at-rest and in-transit, but customer data must be decrypted for processing. Confidential Computing is a breakthrough technology which encrypts data in-use while it is being processed. Confidential Computing environments keep data encrypted in memory and elsewhere outside the central processing unit (CPU).\nConfidential VMs leverage the Secure Encrypted Virtualization (SEV) feature of AMD EPYC CPUs. Customer data will stay encrypted while it is used, indexed, queried, or trained on. Encryption keys are generated in hardware, per VM, and are not exportable. Thanks to built-in hardware optimizations of both performance and security, there is no significant performance penalty to Confidential Computing workloads.","impact":"Confidential Computing enables customers' sensitive code and other data encrypted in memory during processing. Confidential VM can help alleviate concerns about risk related to either dependency on Google infrastructure or Google insiders' access to customer data in the clear.","report_fields":["selfLink"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nFrom Console:\n1. Go to the VM instances page using https://console.cloud.google.com/compute/instances.\n2. Click CREATE INSTANCE.\n3. Fill out the desired configuration for your instance.\n4. Under the 'Confidential VM service' section, check the option Enable the Confidential Computing service on this VM instance.\n5. Click Create.\nFrom Command Line:\nCreate a new instance with Confidential Compute enabled.\ngcloud compute instances create --zone --confidential-compute --maintenance-policy=TERMINATE","multiregional":true,"service":"Compute Engine"},"ecc-gcp-048":{"article":"Send logs and metrics to a remote aggregator to mitigate the risk of local tampering in the event of a breach.","impact":"Lack of Stackdriver Kubernetes monitoring can result in insufficient response time to detect issues due to inaccessibility of audit data after a cluster security event and lack of a centralized place to analyze log data and metrics collected from multiple sources.","report_fields":["selfLink"],"remediation":"Using Google Cloud Console:\n1. Go to Kubernetes Engine by visiting https://console.cloud.google.com/kubernetes/list\n2. Select Kubernetes clusters for which monitoring is disabled.\n3. Click on EDIT.\n4. Set 'Stackdriver Monitoring' to 'Enabled'.\n5. Click SAVE.\nUsing Command Line:\nTo enable Stackdriver Monitoring for an existing cluster, run the following command:\ngcloud container clusters update [CLUSTER_NAME] --zone [COMPUTE_ZONE] --monitoring-service monitoring.googleapis.com","multiregional":true,"service":"Google Kubernetes Engine"},"ecc-gcp-274":{"article":"By default, for each policy you start with one rule that allows/denies all traffic with the lowest priority. To conform with least privilege access principles, set the default action as deny and then add allow/deny rules with higher priority as necessary.","impact":"Not setting the default action as deny with higher priority for security policy can increase opportunities for malicious activities such as Denial-of-Service (DoS) attacks.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the Cloud Armor page in the Cloud Console https://console.cloud.google.com/net-security/securitypolicies/list.\n2. On the Security policies page, click the name of the security policy. The Policy details page is displayed.\n3. Click the Edit button.\n4. Under Default rule action select Deny.\n5. Click Update.","multiregional":true,"service":"Cloud Armor"},"ecc-gcp-150":{"article":"Check to ensure that there are no public access to the Stackdriver logs storing on the Storage Buckets. Stackdriver Logging allows to store, search, investigate, monitor and alert on log information/events from Google Cloud Platform. The permissions needs to be set only for authorized users. Public access to Stackdriver Logs will enable anyone with a web association to retrieve sensitive information critical to business.","impact":"Public access to Stackdriver Logs will enable anyone with a web association to retrieve sensitive information critical to business.","report_fields":["selfLink"],"remediation":"1. Login to the GCP Portal.\n2. Go to Storage (Left Panel) and click Browser.\n3. Choose the identified Storage Bucket whose ACL needs to be modified.\n4. Click on the SHOW INFO PANEL button.\n5. Check all the ACL groups and make sure that none of them are set to 'allUsers' or 'allAuthenticatedUsers'.","multiregional":true,"service":"Cloud Storage"},"ecc-gcp-243":{"article":"This rule detects when a function is configured with ingress allow all traffic. Restrict the traffic from the Internet and other resources, to get better network-based access control and allow only vpc resources traffic to enter or traffic through load balancer.","impact":"Allowing all traffic for functions allows all incoming requests from both the Internet and resources within the project and can increase opportunities for malicious activities such as hacking and various types of attacks (brute-force attacks, Denial of Service (DoS) attacks, etc.).","report_fields":["name"],"remediation":"From Console:\n1. Go to the Cloud Functions page at Google Cloud Console https://console.cloud.google.com/functions.\n2. Select a function.\n3. Click on edit.\n4. Click on Runtime, build, connections and security settings.\n5. Click on Connections.\n6. On Ingress Settings select either Allow internal trafffic only or Allow internal trafffic and from Cloud load balancing.\n7. Click on next.\n8. Click on Deploy.","multiregional":true,"service":"Cloud Functions"},"ecc-gcp-282":{"article":"This policy identifies GCP Firewall rules that allow inbound traffic to the redis port (6379) from the public internet. Allowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Allowing unrestricted inbound/ingress access on TCP port 6379 (redis) via VPC network firewall rules can increase opportunities for malicious activities such as hacking and brute-force attacks.","report_fields":["selfLink"],"remediation":"To restrict all traffic, edit the reported Firewall rule as follows:\n1. Login to GCP Console.\n2. Go to VPC Network.\n3. Go to Firewall rules.\n4. Click on the reported Firewall rule.\n5. Click on Edit.\n6. Modify Source IP ranges to specific IP.\n7. Click on Save.","multiregional":true,"service":"Virtual Private Cloud"},"ecc-gcp-140":{"article":"This policy identifies a SQL DB instance that does not have any Labels. Labels can be used for easy identification and search.","impact":"SQL Instances without labels make it difficult to organize resources available within your GCP environment. As your GCP environment is becoming more and more complex, it requires better management strategies.","report_fields":["selfLink"],"remediation":"1. Login to GCP Console.\n2. In the left Navigation pane, click on SQL.\n3. Select the reported SQL instance.\n4. Click on EDIT and add labels with the appropriate KeyValue information.\n5. Click on Save.","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-055":{"article":"Use Container-Optimized OS (cos_containerd) as a managed, optimized and enhanced base OS that limits the host's attack surface.","impact":"Not using COS can increase the attack surface of your instance so that instances may not include a locked-down firewall and other security settings by default.","report_fields":["selfLink"],"remediation":"Using Google Cloud Console:\n1. Go to Kubernetes Engine using https://console.cloud.google.com/kubernetes/list.\n2. Select the Kubernetes cluster that does not use COS.\n3. Under the Node pools heading, select the Node Pool that requires alteration.\n4. Click EDIT.\n5. Under the Image Type heading, click CHANGE.\n6. From the pop-up menu, select Container-Optimized OS (cos_containerd) and click CHANGE.\n7. Repeat for all non-compliant Node pools.\nUsing Command Line:\nTo set the node image to COS for an existing cluster's Node pool:\ngcloud container clusters upgrade [CLUSTER_NAME] --image-type cos_containerd --zone [COMPUTE_ZONE] --node-pool [POOL_NAME]","multiregional":true,"service":"Google Kubernetes Engine"},"ecc-gcp-239":{"article":"When automatic storage increases is enable, Cloud SQL checks your available storage every 30 seconds. If the available storage falls below a threshold size, Cloud SQL automatically adds additional storage capacity. If the available storage repeatedly falls below the threshold size, Cloud SQL continues to add storage until it reaches the maximum of 64 TB.\nNote: You can increase storage size, but you cannot decrease it; the storage increases are permanent for the life of the instance. When you enable this setting, a spike in storage requirements can permanently increase storage costs (incrementally) for your instance. If an instance runs out of available space, it can cause the instance to go offline, and the Cloud SQL SLA might not cover the outage.\nFor more information https://cloud.google.com/sql/docs/sqlserver/instance-settings.","impact":"If a database instance runs out of available space, it can drop existing connections and cause downtime, and Google Cloud SQL Service Level Agreement (SLA) might not cover the outage.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the Cloud SQL Instances page in the Google Cloud Console using https://console.cloud.google.com/sql/instances.\n2. Select the SQL instance for which you want to enable automatic storage increases.\n3. Click Edit.\n4. Scroll down to the Storage section.\n5. Check the box next to the 'Enable automatic storage increases'.\n6. Click Save to save your changes.\n7. Confirm your changes in the Configuration block on the Overview page.","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-285":{"article":"This policy identifies GCP Firewall rules that allow inbound traffic to the WinRM port (5985 or 5986) from the public internet. Allowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Allowing unrestricted inbound/ingress access on TCP port 5985 or 5986 (WinRM) via VPC network firewall rules can increase opportunities for malicious activities such as hacking and brute-force attacks.","report_fields":["selfLink"],"remediation":"To restrict all traffic, edit the reported Firewall rule as follows:\n1. Login to GCP Console.\n2. Go to VPC Network.\n3. Go to Firewall rules.\n4. Click on the reported Firewall rule.\n5. Click on Edit.\n6. Modify Source IP ranges to specific IP.\n7. Click on Save.","multiregional":true,"service":"Virtual Private Cloud"},"ecc-gcp-312":{"article":"This rule detects when Datafusion instance has stackdriver monitoring disabled.\nStackdriver monitoring collects metrics, events, and metadata. Google Cloud's operations suite ingests that data and generates insights via dashboards, charts, and alerts.","impact":"Lack of Stackdriver Datafusion monitoring can result in insufficient response time to detect issues.","report_fields":["name"],"remediation":"From Command Line:\n1. After you create your instance, you cannot enable Cloud Monitoring in the console. Instead, run this gcloud CLI command:\ngcloud beta data-fusion instances update INSTANCE_NAME --project=PROJECT_ID --location=LOCATION --enable_stackdriver_monitoring","multiregional":true,"service":"Cloud Data Fusion"},"ecc-gcp-249":{"article":"This policy identifies GCP Cloud Functions for which the HTTP trigger is not secured. When you configure HTTP functions to be triggered only with HTTPS, user requests will be redirected to use the HTTPS protocol, which is more secure. It is recommended to set the 'Require HTTPS' for configuring HTTP triggers while deploying your function.","impact":"Requests for a URL that match HTTP are not automatically redirected to the HTTPS URL with the same path, which can lead to a breach of confidentiality.","report_fields":["name"],"remediation":"From Console:\n1. Go to the Cloud Functions Overview page https://console.cloud.google.com/functions.\n2. Click on the alerting function.\n3. Click on 'EDIT'.\n4. Under section 'Trigger', click on 'EDIT'.\n5. Select the checkbox against the field 'Require HTTPS'.\n6. Click on 'SAVE'.\n7. Click on 'NEXT'.\n8. Click on 'DEPLOY'.","multiregional":true,"service":"Cloud Functions"},"ecc-gcp-219":{"article":"To defend against advanced threats and ensure that the boot loader and firmware on your VMs are signed and untampered, it is recommended that Compute instances are launched with Shielded VM enabled. If vTPM or EnableIntegrityMonitoring is not set to true or not present then it means instances are not launched with shielded VM enabled.","impact":"Without Shilded VM protection it may be possible to compromise the integrity of boot loader and firmware of the VM. This can allow an attacker to inject malicious code there.","report_fields":["selfLink"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nUsing Console:\n1. Go to the instance template page using https://console.cloud.google.com/compute/instanceTemplates/list.\n2. Replace all affected templates with new ones with turned on vTPM and Integrity monitoring in the Security section.","multiregional":true,"service":"Compute Engine"},"ecc-gcp-018":{"article":"Google Cloud Platform services write audit log entries to Admin Activity and Data Access logs to help answer the questions of \"who did what, where, and when?\" within Google Cloud Platform projects. Cloud Audit logging records information includes the identity of the API caller, the time of the API call, the source IP address of the API caller, the request parameters, and the response elements returned by the GCP services. Cloud Audit logging provides a history of GCP API calls for an account, including API calls made via the Console, SDKs, command line tools, and other GCP services.","impact":"Lack of monitoring and logging of Audit Configuration Changes can lead to insufficient response time to detect accidental or intentional changes that may result in unauthorized access.","report_fields":["projectId"],"remediation":"From Console:\nCreate the prescribed log metric:\n1. Go to Logging/Logs-based Metrics using https://console.cloud.google.com/logs/metrics and click \"CREATE METRIC\".\n2. Click the down arrow symbol on the Filter Bar at the rightmost corner and select Convert to Advanced Filter.\n3. Clear any text and add:\nprotoPayload.methodName=\"SetIamPolicy\" AND\nprotoPayload.serviceData.policyDelta.auditConfigDeltas:*\n4. Click Submit Filter. Display logs appear based on the filter text entered by the user.\n5. In the Metric Editor menu on the right, fill out the name field. Set Units to 1 (default) and Type to Counter. This will ensure that the log metric counts the number of log entries matching the user's advanced logs query.\n6. Click Create Metric.\nCreate the prescribed Alert Policy:\n1. Identify the new metric the user has just created under the User-defined Metrics section at https://console.cloud.google.com/logs/metrics.\n2. Click the 3-dot icon in the rightmost column for the new metric and select Create alert from Metric. A new page opens.\n3. Fill out the alert policy configuration and click Save. Choose the alerting threshold and configuration that makes sense for the organization. For example, a threshold of zero(0) for the most recent value will ensure that a notification is triggered for every owner change in the project:\nSet 'Aggregator' to 'Count'\nSet 'Configuration':\n- Condition: above\n- Threshold: 0\n- For: most recent value\n4. Configure the desired notifications channels in the section Notifications.\n5. Name the policy and click Save.","multiregional":true,"service":"Cloud Logging"},"ecc-gcp-188":{"article":"It is recommended that the IAM policy on BigQuery datasets do not allow anonymous and/or public access","impact":"Granting permissions to allUsers or allAuthenticatedUsers allows anyone to access the dataset. Such access might not be desirable if sensitive data is being stored in the dataset.","report_fields":["selfLink"],"remediation":"Using Console:\n1. Go to 'BigQuery' using https://console.cloud.google.com/bigquery.\n2. Select the dataset from 'Resources'.\n3. Click SHARING near the right side of the window and select Permissions\n4. Review each attached role.\n5. Click the 'Delete' icon for each 'allUsers' or 'allAuthenticatedUsers' member. In the pop-up menu, click 'Remove'.\nFrom Command Line:\n1. Retrieve the data set information:\nbq show --format=prettyjson PROJECT_ID:DATASET_NAME > PATH_TO_FILE\n2. In the access section of a JSON file, update the dataset information to remove all roles containing 'allUsers' or 'allAuthenticatedUsers'.\n3. Update the dataset:\nbq update --source PATH_TO_FILE PROJECT_ID:DATASET_NAME","multiregional":true,"service":"BigQuery"},"ecc-gcp-179":{"article":"Enabling the 'log_disconnections' setting logs the end of each session, including the session duration.","impact":"Disabled 'log_disconnections' can lead to insufficient response time to identify, troubleshoot, and repair configuration errors and sub-optimal performance for your Cloud PostgreSQL database instances.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the Cloud SQL Instances page in the Google Cloud Console using https://console.cloud.google.com/sql/instances.\n2. Select the PostgreSQL instance where the database flag needs to be enabled.\n3. Click Edit.\n4. Scroll down to the Flags section.\n5. To set a flag that has not been set on the instance before, click Add item, choose the 'log_disconnections' flag from the drop-down menu and set the value as 'on'.\n6. Click Save.\n7. Confirm the changes under Flags on the Overview page.\nFrom Command Line:\n1. List all Cloud SQL database Instances using the following command:\ngcloud sql instances list\n2. Configure the 'log_disconnections' database flag for every Cloud SQL PosgreSQL database instance using the below command:\ngcloud sql instances patch INSTANCE_NAME --database-flags log_disconnections=on\nNote: This command will overwrite all previously set database flags. To keep those and add new ones, include the values for all flags to be set on the instance; any flag not specifically included is set to its default value. For flags that do not take a value, specify the flag name followed by an equals sign ('=').","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-042":{"article":"Storage Access Logging generates a log that contains access records for each request made to the Storage bucket. Cloud Storage offers access logs and storage logs in the form of CSV files that can be downloaded and used for analysis/incident response. Access logs provide information for all of the requests made on a specified bucket and are created hourly, while the daily storage logs provide information about the storage consumption of that bucket for the last day. The access logs and storage logs are automatically created as new objects in a bucket that you specify. An access log record contains details about the request, such as the request type, the resources specified in the request worked, and the time and date the request was processed. While storage Logs helps to keep track of the amount of data stored in the bucket.\nIt is recommended that storage Access Logs and Storage logs are enabled for every Storage Bucket.","impact":"Lack of logging can lead to failure to detect the events which may affect objects within target buckets.","report_fields":["selfLink"],"remediation":"Using Gsutils:\nTo set Storage Access Logs and Storage logs for a bucket run:\ngsutil logging set on -b gs// gs//","multiregional":true,"service":"Cloud Storage"},"ecc-gcp-182":{"article":"PostgreSQL can create a temporary file for actions such as sorting, hashing and temporary query results when these operations exceed 'work_mem'. The 'log_temp_files' flag controls logging names and the file size when it is deleted. Configuring 'log_temp_files' to '0' causes all temporary file information to be logged, while positive values log only files whose size is greater than or equal to the specified number of kilobytes. A value of '-1' disables temporary file information logging.","impact":"If all temporary files are not logged, it can be more difficult to identify potential performance issues. Those may be caused either by poor application coding or by deliberate resource starvation attempts.","report_fields":["selfLink"],"remediation":"Using Console:\n1. Go to the Cloud SQL Instances page in the Google Cloud Console using https://console.cloud.google.com/sql/instances.\n2. Select the PostgreSQL instance where the database flag needs to be enabled.\n3. Click Edit.\n4. Scroll down to the Flags section.\n5. To set a flag that has not been set on the instance before, click Add item, choose the 'log_temp_files' flag from the drop-down menu and set the value as '0'.\n6. Click Save.\n7. Confirm the changes under Flags on the Overview page.\nUsing Command Line:\n1. List all Cloud SQL database instances using the following command:\ngcloud sql instances list\n2. Configure the 'log_temp_files' database flag for every Cloud SQL PosgreSQL database instance using the below command:\ngcloud sql instances patch INSTANCE_NAME --database-flags log_temp_files='0'\nNote: This command will overwrite all database flags previously set. To keep those and add new ones, include the values for all flags to be set on the instance; any flag not specifically included is set to its default value. For flags that do not take a value, specify the flag name followed by an equals sign ('=').","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-071":{"article":"Firewall rules provide stateful filtering of ingress/egress network traffic to GCP resources. It is recommended that no security group allow unrestricted ingress access","impact":"Allowing unrestricted ingress access to uncommon IP addresses increases opportunities for malicious activities such as hacking and various types of attacks (brute-force attacks, Denial of Service (DoS) attacks, etc.) that may lead to data loss.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to VPC Network.\n2. Go to Firewall Rules.\n3. Click the Firewall Rule you want to modify.\n4. Click Edit.\n5. Modify Source IP ranges to specific IP.\n6. Click Save.\nFrom Command Line:\n1.Update Firewall rule with new SOURCE_RANGE from below command:\ngcloud compute firewall-rules update FirewallName --allow=[PROTOCOL[PORT[-PORT]],...] --source-ranges=[CIDR_RANGE,...]","multiregional":true,"service":"Virtual Private Cloud"},"ecc-gcp-137":{"article":"This policy identifies Load balancer HTTPS target proxies which are configured with a default SSL Policy instead of a custom SSL policy. The best practice is to use a custom SSL policy to access Load balancers. It gives you closer control over SSL/TLS versions and ciphers.","impact":"Without custom SSL certificates, you will not be able to give closer control over SSL/TLS versions and ciphers.","report_fields":["selfLink"],"remediation":"1. Login to GCP Portal.\n2. Go to Network services (Left Panel).\n3. Select Load balancing.\n4. Click on the 'Advanced menu' hyperlink to view target proxies.\n5. Click on the 'Target proxies' tab.\n6. Click on the reported HTTPS target proxy.\n7. Click on the hyperlink under 'URL map'.\n8. Click on the 'EDIT' button.\n9. Select 'Frontend configuration', click on HTTPS protocol rule.\n10. For 'SSL policy', choose any custom SSL policy other than 'GCP default'.\n11. Click on 'Done'.\n12. Click on 'Update'.","multiregional":true,"service":"Cloud Load Balancing"},"ecc-gcp-298":{"article":"This policy identifies GCP storage buckets that are not configured with default Event-Based Hold. An event-based hold resets the object's time in the bucket for the purposes of the retention period. This behavior is useful when you want an object to persist in your bucket for a certain length of time after a certain event occurs. It is recommended to enable this feature to protect individual objects from deletion.\nReference: https://cloud.google.com/storage/docs/object-holds.","impact":"Without the default event-based hold configuration, you can not ensure that information will remain stored and unchangeable until it fulfills the retention period set in your retention policy when you release the hold.","report_fields":["selfLink"],"remediation":"From Console:\n1. Open the Cloud Storage browser in Google Cloud Console.\n2. In the list of buckets, click on the name of the bucket that you want to check the default event-based status for.\n3. Select the Protection tab near the top of the page.\n4. Click on the 'DISABLED' button beside 'Default event-based hold option' to configure it to enabled.\n5. Click Save.","multiregional":true,"service":"Cloud Storage"},"ecc-gcp-112":{"article":"This policy identifies GCP Firewall rules that allow inbound traffic to the HTTP port (80) from the public internet. Allowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Allowing unrestricted inbound access on TCP port 80 (HTTP) via VPC network firewall rules can increase opportunities for malicious activities such as hacking, brute-force attacks, DDoS attacks.","report_fields":["selfLink"],"remediation":"To restrict all traffic, edit the reported Firewall rule as follows:\n1. Login to GCP Console.\n2. Go to VPC Network.\n3. Go to Firewall rules.\n4. Click on the reported Firewall rule.\n5. Click on Edit.\n6. Modify Source IP ranges to specific IP.\n7. Click on Save.","multiregional":true,"service":"Virtual Private Cloud"},"ecc-gcp-317":{"article":"This rule detects when Bigtable instance cluster encryption not using CMEK.\nIt is recommended to use Customer-Managed Keys to fully control and manage data encryption.\nFeatures:\n- CMEK provides the same level of security as Google's default encryption but provides more administrative control.\n- Administrators can rotate, manage access to, and disable or destroy the key used to protect data at rest in Bigtable.\n- All actions on your CMEK keys are logged and viewable in Cloud Logging. Cloud EKM keys support Key Access Justification, which adds a justification field to all key requests. With select external key management partners, you can automatically approve or deny these requests, based on the justification.\n- Bigtable CMEK-protected instances offer comparable performance to Bigtable instances that use Google default encryption.\n- You can use the same CMEK key in multiple projects, instances, or clusters, or you can use separate keys, depending on your business needs.\n- You can use the same CMEK key in multiple projects, instances, or clusters, or you can use separate keys, depending on your business needs.","impact":"Not using CMEK results in less control over aspects of the lifecycle and management of your keys.","report_fields":["name"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nFrom Command Line:\nCreate a Bigtable service agent: (https://cloud.google.com/bigtable/docs/use-cmek#gcloud)\n1. Run the gcloud services identity create command to view the service agent that Bigtable uses to access the CMEK key on your behalf. This command creates the service account if it does not already exist, then displays it.\ngcloud beta services identity create --service=bigtableadmin.googleapis.com --project PROJECT\nCreate a key:\n1.In the Google Cloud project where you want to manage your keys:\na) Enable the Cloud KMS API.\nb) Create a key ring and a key using one of the following options:\n- Create the key ring and key directly in Cloud KMS.\n- Use an externally-managed key. Create the external key and then create an Cloud EKM key to make the key available through Cloud KMS.\nThe Cloud KMS key location must be the same as the Bigtable cluster that it will be used with. For example, if you create a key ring and key in us-central1 (Iowa), then clusters in us-central1-a, us-central1-b, and us-central1-c can be protected by keys from that key ring.\nConfigure IAM settings for the key:\n1. Grant the cloudkms.cryptoKeyEncrypterDecrypter role to your service agent:\ngcloud kms keys add-iam-policy-binding KMS_KEY --keyring KMS_KEYRING --location KMS_LOCATION --member serviceAccount:SERVICE_ACCOUNT_EMAIL --role roles/cloudkms.cryptoKeyEncrypterDecrypter --project PROJECT\nCreate a CMEK-enabled instance:\n1. Use the bigtable instances create command to create an instance:\ngcloud bigtable instances create INSTANCE_ID --display-name=DISPLAY_NAME [--cluster-storage-type=CLUSTER_STORAGE_TYPE] [--cluster-config=id=CLUSTER_ID,zone=CLUSTER_ZONE, nodes=CLUSTER_NUM_NODES] [--cluster-config=id=CLUSTER_ID,zone=CLUSTER_ZONE, autoscaling-min-nodes=AUTOSCALING_MIN_NODES, autoscaling-max-nodes=AUTOSCALING_MAX_NODES, autoscaling-cpu-target=AUTOSCALING_CPU_TARGET autoscaling-storage-target=AUTOSCALING_STORAGE_TARGET] kms-key=KMS_KEY","multiregional":true,"service":"Cloud Bigtable"},"ecc-gcp-242":{"article":"This rule detects when a function is publicly accessible in the Cloud Functions service.","impact":"Granting permissions to 'allUsers' and 'allAuthenticatedUsers' members can allow anyone to access your Cloud Function.","report_fields":["name"],"remediation":"From Console:\n1. Go to the Cloud Functions page at Google Cloud Console https://console.cloud.google.com/functions.\n2. Select a function.\n3. Click on Permission.\n4. Review each role and find Members with allUsers and allAuthenticatedUsers access.\n5. Click the Delete icon and confirm by clicking on REMOVE.","multiregional":true,"service":"Cloud Functions"},"ecc-gcp-034":{"article":"The default Compute Engine service account has the Editor role on the project, which allows read and write access to most Google Cloud Services. To support the principle of least privileges and prevent potential privilege escalation.\nIt is recommended that instances are not assigned to default service account Compute Engine default service account with Scope Allow full access to all Cloud APIs.","impact":"Using the default service account on your instances can increase malicious activity to the point that an attacker gains full control of the project.","report_fields":["selfLink"],"remediation":"From Console:\n1.Go to the VM instances page using https://console.cloud.google.com/compute/instances.\n2. Click on the impacted VM instance.\n3. If the instance is not stopped, click the Stop button. Wait for the instance to be stopped.\n4. Next, click on the Edit button.\n5. Scroll down to the Service Account section.\n6. Select a different service account or ensure that Allow full access to all Cloud APIs is not selected.\n7. Click on the Save button to save your changes and then click on START.\nFrom CLI:\n1. Stop the instance:\ngcloud compute instances stop INSTANCE_NAME\n2. Update the instance:\ngcloud compute instances set-service-account INSTANCE_NAME --serviceaccount=SERVICE_ACCOUNT --scopes [SCOPE1, SCOPE2...]\n3. Restart the instance:\ngcloud compute instances start INSTANCE_NAME","multiregional":true,"service":"Compute Engine"},"ecc-gcp-216":{"article":"Compute Engine instance templates cannot forward a packet unless the source IP address of the packet matches the IP address of the instance. Similarly, GCP won't deliver a packet whose destination IP address is different than the IP address of the instance receiving the packet. However, both capabilities are required if you want to use instances to help route packets. Forwarding of data packets should be disabled to prevent data loss or information disclosure.","impact":"Creating an instance based on template with enabled IP forwarding, Google Cloud does not enforce packet source and destination verification, and the security of this redirect is potentially downgraded as this source and destination may refer to the attacker's ones.","report_fields":["selfLink"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue\nWe can't Edit an exisitng Instance Template.\nUsing Console:\n1. In the Google Cloud Console, go to the Instance Template page https://console.cloud.google.com/compute/instanceTemplates/list.\n2. Click on Create Instance Template\n3. Click on NETWORKING,DISKS,SECURITY,MANAGEMENT,SOLE-TENANCY\n4. Under Networking, navigate to IP Forwarding and Disable it.\n5. Click on Create and Delete old template.","multiregional":true,"service":"Compute Engine"},"ecc-gcp-453":{"article":"Redis instance without label information make identification and search difficult.","impact":"Redis instance without label information makes it difficult to identify, search and view usage of GCP resource and spending.","report_fields":["name"],"remediation":"From Console:\n1. Log in to the GCP Console and navigate to Memorystore for Redis at https://console.cloud.google.com/memorystore/redis/instances.\n2. View your instance's Instance details page by clicking on reported Instance ID.\n3. Expand the Info Panel by selecting Show Info Panel.\n3. Select the Labels button and add desired labels.","multiregional":true,"service":"Cloud Memorystore"},"ecc-gcp-211":{"article":"The 'remote access' option controls the execution of stored procedures from local or remote servers on which instances of a SQL Server are running. This default value for this option is '1'. This grants permission to run local stored procedures from remote servers or remote stored procedures from the local server. To prevent local stored procedures from being run from a remote server or remote stored procedures from being run from the local server, this must be disabled. The 'Remote Access' option controls the execution of local stored procedures from remote servers or remote stored procedures from the local server. 'Remote access' functionality can be abused to launch a Denial-of-Service (DoS) attack on remote servers by off-loading query processing to a target, hence this should be disabled. This recommendation is applicable to SQL Server database instances.","impact":"The 'Remote access' functionality can be abused to launch a Denial-of-Service (DoS) attack on remote servers by off-loading query processing to a target.","report_fields":["selfLink"],"remediation":"From Console:\n1. Go to the Cloud SQL Instances page in the Google Cloud Console using https://console.cloud.google.com/sql/instances.\n2. Select the SQL Server instance for which you want to enable the database flag.\n3. Click Edit.\n4. Scroll down to the Flags section.\n5. To set a flag that has not been set on the instance before, click Add item, choose the 'remote access' flag from the drop-down menu, and set its value to 'off'.\n6. Click Save to save your changes.\n7. Confirm your changes under Flags on the Overview page.\nFrom Command Line:\n1. Configure the remote access database flag for every Cloud SQL SQL Server database instance using the below command:\ngcloud sql instances patch --database-flags \"remote access=off\"\nNote : This command will overwrite all database flags previously set. To keep those and add new ones, include the values for all flags you want set on the instance; any flag not specifically included is set to its default value. For flags that do not take a value, specify the flag name followed by an equals sign (\"=\").","multiregional":true,"service":"Cloud SQL"},"ecc-gcp-135":{"article":"Putting resources in different zones in a region provides protection against many types of infrastructure, hardware, and software failures. This policy alerts you if your cluster is not located in at least 3 zones.","impact":"GKE nodes not in redundant zones are not protected against infrastructure, hardware, and software failures.","report_fields":["selfLink"],"remediation":"Add zones to your zonal cluster.\n1. Visit the Google Kubernetes Engine menu in GCP Console.\n2. Click on the cluster's Edit button, which looks like a pencil.\n3. From the Additional zones section, select the desired zones.\n4. Click on Save.","multiregional":true,"service":"Google Kubernetes Engine"},"ecc-aws-235":{"article":"The debug_print_plan setting enables printing the execution plan for each executed query. These messages are emitted at the LOG message level. Unless directed otherwise by your organization's logging policy, it is recommended this setting be disabled by setting it to off.","impact":"Enabling any of the DEBUG printing variables may cause the logging of sensitive information that would otherwise be omitted based on the configuration of the other logging settings.","report_fields":["DBInstanceArn"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. In the navigation pane, choose Parameter groups.\n3. Choose the required parameter group.\n4. Choose Edit parameters.\n5. Under Parameters, in search bar type \"debug_print_plan\".\n6. Choose 0 in value field.\n7. Choose Save changes.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-511":{"article":"Check whether Amazon Classic Load Balancers are not internet facing. The rule is NON_COMPLIANT if the Scheme field is 'internet-facing' in the configuration.","impact":"Unrestricted access increases the opportunity for malicious activity such as unauthorized access, denial-of-service attacks, and loss of data.","report_fields":["LoadBalancerArn"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n1. Sign in to the Amazon EC2 console and access the Amazon EC2 at https://console.aws.amazon.com/ec2/v2.\n2. Under the 'Load Balancing' click on the 'Load Balancers'.\n3. Create Classic load balancer.\n4. Under the 'Scheme' choose 'Internal'.\n5. Create load balancer.","multiregional":false,"service":"Amazon Elastic Load Balancing"},"ecc-aws-190":{"article":"It is not secure to have task definitions with host network mode and container definitions where privileged=false or is empty and user=root or is empty. \nIf a task definition has elevated privileges, it is because the customer has specifically opted in to that configuration. This policy checks for unexpected privilege escalation when a task definition has host networking enabled but the customer has not opted in to elevated privileges.","impact":"Without this policy, you fail to check for unexpected privilege escalation when a task definition has host networking enabled, but the customer has not opted into elevated privileges.","report_fields":["taskDefinitionArn"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nNote that when you update a task definition, it does not update running tasks that were launched from the previous task definition. To update a running task, you must redeploy the task with the new task definition.\n1. Open the Amazon ECS console at https://console.aws.amazon.com/ecs/.\n2. From the navigation bar, choose the Region that contains your task definition.\n3. In the navigation bar, choose task definitions. \n4. On the task definitions page, select the box to the left of the task definition to revise and choose Create new revision. \n5. On the Create new revision of task definition page, make changes. Change the existing container definitions, select the container, make the changes (in the User field enter required user name except root and tick Privileged box), and then choose Update. \n6. Verify the information and choose Create. \n7. If your task definition is used in a service, update your service with the updated task definition.","multiregional":false,"service":"Amazon Elastic Container Service"},"ecc-aws-091":{"article":"You can use Compliance, a capability of AWS Systems Manager, to scan your fleet of managed nodes for patch compliance and configuration inconsistencies.","impact":"If patch compliance was not finished correctly, then the vulnerabilities are still not fixed.","report_fields":["InstanceId","OwnerId"],"remediation":"1. Open the AWS Systems Manager console at https://console.aws.amazon.com/systems-manager/.\n2. In the navigation pane, under Instances & Nodes, choose Run Command.\n3. Choose the radio button next to AWS-RunPatchBaseline and then change the Operation to Install.\n4. Choose instances manually and then choose the non-compliant instance(s).\n5. Scroll to the bottom and then choose Run.\n6. After the command has been completed, choose Compliance in the Navigation pane to monitor the new compliance status of your patched instances.\nFor more information about using Systems Manager documents to patch a managed instance, see in the AWS Systems Manager User Guide:\n - About SSM documents for patching instances: https://docs.aws.amazon.com/systems-manager/latest/userguide/patch-manager-ssm-documents.html\n - Running commands using Systems Manager Run command: https://docs.aws.amazon.com/systems-manager/latest/userguide/run-command.html","multiregional":false,"service":"Amazon EC2"},"ecc-aws-295":{"article":"The greater enhancement in encryption of TLS 1.2 allows it to use more secure hash algorithms such as SHA-256 as well as advanced cipher suites that support elliptical curve cryptography.","impact":"Not using latest version of TLS you missing security features that can lead to unauthorized access.","report_fields":["ARN"],"remediation":"Perform the following for each distribution that failed the rule:\n1. Navigate to the the AWS console CloudFront dashboard at https://console.aws.amazon.com/cloudfront.\n2. Select your distribution ID.\n3. Select the 'Origins' tab.\n4. Choose origin and click on Edit.\n5. Under the Minimum origin SSL protocol choose 'TLSv1.2'.\n6. Click on Save changes.","multiregional":true,"service":"Amazon CloudFront"},"ecc-aws-286":{"article":"Any WorkSpace instances available within your AWS account that are not being utilized must be identified and removed. An AWS WorkSpaces instance is considered unused if has 0 (zero) known user connections registered within the past 30 days.","impact":"It is highly recommended to remove unused WorkSpaces instances available in your AWS account in order to optimize and reduce monthly costs.","report_fields":["WorkspaceId"],"remediation":"Perform the following to remove unused WorkSpaces based on the output collected from the audit procedure:\n1. Log in to the WorkSpaces dashboard at https://console.aws.amazon.com/workspaces/.\n2. In the left panel, click \"WorkSpaces\".\n3. Select the WorkSpace ID that you have identified as not being used.\n4. Click \"Actions\", \"Remove WorkSpaces\".\n5. Confirm using Audit, confirm that this is the WorkSpaces ID you should remove.\n6. Click \"Remove WorkSpaces\".","multiregional":false,"service":"Amazon WorkSpaces Family"},"ecc-aws-529":{"article":"This rule ensures that Amazon Elastic Block Store volumes that are attached to Amazon Elastic Compute Cloud (Amazon EC2) instances are marked for deletion when an instance is terminated.","impact":"If an Amazon EBS volume isn't deleted when the instance that it's attached to is terminated, it may violate the concept of least functionality.","report_fields":["InstanceId","OwnerId"],"remediation":"From the Console:\n1. At this time the delete on termination setting for existing instances can only be changed using AWS CLI.\n\nFrom the CLI:\n1. Run the modify-instance-attribute command using the list of instances collected in the audit.\n aws ec2 modify-instance-attribute --instance-id i-123456abcdefghi0 --block-device-mappings \"[{\\\"DeviceName\\\": \\\"/dev/sda\\\",\\\"Ebs\\\":{\\\"DeleteOnTermination\\\":true}}]\"\n2. Repeat steps no. 1 with the other instances discovered in all AWS regions.\n\n**Note - If you get any errors running the modify-instance-attribute command confirm the instance id and the Device Name for that instance is correct. The above command is referencing the typical default device name.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-493":{"article":"Container Insights collects data as performance log events using embedded metric format. These performance log events are entries that use a structured JSON schema that enables high-cardinality data to be ingested and stored at scale. From this data, CloudWatch creates aggregated metrics at the cluster, node, pod, task, and service level as CloudWatch metrics. The metrics that Container Insights collects are available in CloudWatch automatic dashboards, and also viewable in the Metrics section of the CloudWatch console.","impact":"Without Container Insights it is harder to collect, aggregate and summarize metrics and logs from containerized applications and microservices.","report_fields":["clusterArn"],"remediation":"To enable Container Insights on all new clusters by default:\n1. Open the Amazon ECS console at https://console.aws.amazon.com/ecs/.\n2. In the navigation pane, choose Account Settings.\n3. Select the check box at the bottom of the page to enable the Container Insights default.\n\nTo create a cluster with Container Insights enabled:\n1. Open the Amazon ECS console at https://console.aws.amazon.com/ecs/.\n2. In the navigation pane, choose Clusters.\n3. Choose Create cluster.\n4. On the next page, do the following:\n4.1. Name your cluster.\n4.2. If you don't have a VPC already, select the check box to create one. You can use the default values for the VPC.\n4.3. Fill out all other needed information, including instance type.\n4.4. Select Enabled Container Insights.\n4.5. Choose Create.\n\nTo enable Container Insights on existing ECS cluster:\naws ecs update-cluster-settings --cluster myCICluster --settings name=containerInsights,value=enabled","multiregional":false,"service":"Amazon Elastic Container Service"},"ecc-aws-483":{"article":"From a security perspective, logging is an important feature to enable for future forensics efforts in the case of any security incidents. Encryption of data at rest is a recommended best practice to add a layer of access management around your data. Encrypting the logs at rest reduces the risk that a user not authenticated by AWS will access the data stored on disk. It adds another set of access controls to limit the ability of unauthorized users to access the data.","impact":"Disabled encryption of logs allows a user to get unauthorized access to stored data.","report_fields":["arn"],"remediation":"1. Open the CodeBuild console at https://console.aws.amazon.com/codebuild/.\n2. Expand 'Build', choose 'Build project', and then choose the build project without encrypted S3 logs. \n3. Click 'Edit' button and select 'Logs'. \n4. For 'S3 Logs' uncheck the 'Disable S3 log encryption' box.\n5. Click 'Update logs'.","multiregional":false,"service":"AWS CodeBuild"},"ecc-aws-383":{"article":"This policy identifies the Nat Gateway that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["NatGatewayId"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon VPC at https://console.aws.amazon.com/vpc.\n2. Click on the 'NAT gateways'.\n3. Click on the required Nat Gateway.\n4. Open 'Tags' and click on the 'Manage tags'.\n5. Add new tag and save.","multiregional":false,"service":"Amazon Virtual Private Cloud"},"ecc-aws-118":{"article":"Instead of creating and distributing your AWS credentials to the containers or using the EC2 instance's role, you can associate an IAM role with an ECS task definition or RunTask API operation. The first will allow you to add all the privileges required by any task in the cluster to a single IAM role, potentially letting tasks use privileges that were not required.","impact":"Using an EC2 instance role can cause all the privileges required for any task in the cluster to be added to a single IAM role, potentially allowing the tasks to use privileges that were not required.","report_fields":["serviceArn"],"remediation":"For each finding, perform the following: \n1. Log in to your AWS Management Console at https://console.aws.amazon.com/vpc/home. \n2. Under Services, click on ECS.\n3. For each cluster, perform the following: \n4. Select the cluster from the list.\n5. Under the Services Tab, click on any Task Definition. \n6. On the Task Definition page, click on Create new revision button.\n7. On the Create new revision of Task Definition page, select a task role from the drop-down list. \n8. Click on the Create button at the bottom of the page. \nTo create an IAM role and assign it to ECS Cluster, perform the following: \n1. Open the IAM console at https://console.aws.amazon.com/iam/.\n2. In the Navigation pane, choose Roles, then choose Create New Role. \n3. In the Select Role Type section, for the Amazon Elastic Container Service Task Role service role, choose Select. \n3.1 To view the trust relationship for this role, check Amazon ECS Task Role. \n4. In the Attach Policy section, select the policy to use for your tasks (for example, AmazonECSTaskS3BucketPolicy) and then choose Next Step.\n5. For Role Name, type a name for your role, such as AmazonECSTaskS3BucketRole, and then choose Create Role to finish.","multiregional":false,"service":"Amazon Elastic Container Service"},"ecc-aws-435":{"article":"If you do not specify a Customer managed key when creating your environment, Amazon MQ uses its AWS owned key for data encryption on your environment.\nWhen you create and use your own KMS CMK customer-managed keys to protect the data on your environment, you obtain full control over who can use the CMK keys and access the encrypted data. The AWS KMS service allows you to create, rotate, disable, enable, and audit your Customer Master Keys (CMKs).\nIn addition, if you provide a Customer managed key, you must attach the policy statement for CloudWatch access. You must also create the Customer managed key-specific execution role.","impact":"Without a KMS CMK customer-managed key, you do not have full and granular control over who can access key that is used for encryption.","report_fields":["BrokerArn"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n1. Sign in to the AWS Admin Console and access the Amazon MQ at https://console.aws.amazon.com/amazon-mq.\n2. Click on the 'Create brokers'.\n3. Under the 'Configure settings' click on the 'Additional settings'.\n4. Click on the 'Customer managed CMKs' and choose existing key or create a new one.\n4. Save changes.","multiregional":false,"service":"Amazon MQ"},"ecc-aws-144":{"article":"S3 object-level API operations such as GetObject, DeleteObject, and PutObject are called data events. By default, CloudTrail trails do not log data events and so it is recommended to enable Object-level logging for S3 buckets.","impact":"With disabled object-level logging for READ access, you are missing the opportunity to perform comprehensive security analysis, monitor specific patterns of user behavior in AWS account, or take immediate actions on any object-level API activity within S3 Buckets using Amazon CloudWatch Events. This can lead to undetected read requests made to S3 buckets with sensitive data.","report_fields":["account_id","account_name"],"remediation":"From Console:\n1. Login to the AWS Management Console and navigate to the S3 dashboard at https://console.aws.amazon.com/s3/.\n2. In the left navigation pane, click on the S3 Bucket Name that you want to examine.\n3. Click on the 'Properties' tab to see in detail the bucket configuration. \n4. Click on the 'Object-level logging' setting, enter the CloudTrail name for the recorded activity. You can choose an existing Cloudtrail or create a new one by navigating to the Cloudtrail console link https://console.aws.amazon.com/cloudtrail/. \n5. Once the Cloudtrail is selected, check the 'Read event' checkbox, so that object-level logging for 'Read' or 'All events' is enabled. \n6. Repeat steps 2 to 5 to enable object-level logging of read events for other S3 buckets.\n\nFrom Command Line: \n1. To enable object-level data events logging for S3 buckets within your AWS account, run 'put-event-selectors' command using the name of the trail that you want to reconfigure as identifier:\naws cloudtrail put-event-selectors --region --trail-name --event-selectors '[{ \"ReadWriteType\": \"ReadOnly\", \"IncludeManagementEvents\":true, \"DataResources\": [{ \"Type\": \"AWS::S3::Object\", \"Values\": [\"arn:aws:s3:::/\"] }] }]'\n2. The command output will be object-level event trail configuration.\n3. If you want to enable it for all buckets at ones then change Values parameter to [\"arn:aws:s3\"] in command given above. \n4. Repeat step 1 for each s3 bucket to update object-level logging of read events. \n5. Change the AWS region by updating the --region command parameter and perform the process for other regions.","multiregional":false,"service":"AWS CloudTrail"},"ecc-aws-414":{"article":"This policy identifies the Glue Job that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["Name"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon Glue at https://console.aws.amazon.com/glue.\n2. Under the 'AWS Glue Studio' click on the 'Jobs'.\n3. Click on the required job.\n4. Open 'Job details' and click on the 'Advanced properties'.\n5. Click on the 'Add new tag'.\n6. Add new tag and save.","multiregional":false,"service":"AWS Glue"},"ecc-aws-156":{"article":"HTTPS (TLS) can be used to help prevent potential attackers from using person-in-the-middle or similar attacks to eavesdrop on or manipulate network traffic. Only encrypted connections over HTTPS (TLS) should be allowed. \nEncrypting data in transit can affect performance. You should test your application with this feature to understand the performance profile and the impact of TLS. TLS 1.2 provides several security enhancements over previous versions of TLS.","impact":"The HTTP protocol is not a secure method of transmitting data because data is not encrypted and can be seen by anyone who may be monitoring Internet traffic, and this leads to a breach of confidentiality. TLS 1.2 provides several security enhancements over previous versions of TLS.","report_fields":["ARN"],"remediation":"To enable TLS encryption, use the UpdateElasticsearchDomainConfig API operation to configure DomainEndpointOptions in order to set TLSSecurityPolicy.\nFor more information, see the Amazon Elasticsearch Service Developer Guide. \nUpdateElasticsearchDomainConfig:\nhttps://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-configuration-api.html#es-configuration-api-actions-updateelasticsearchdomainconfig \nDomainEndpointOptions:\nhttps://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-configuration-api.html#es-configuration-api-datatypes-domainendpointoptions","multiregional":false,"service":"Amazon OpenSearch Service"},"ecc-aws-363":{"article":"Server-side encryption is a feature in Kinesis Video Streams that automatically encrypts data before it's at rest by using an AWS KMS customer master key (CMK) that you specify. Data is encrypted before it is written to the Kinesis Video Streams stream storage layer, and it is decrypted after it is retrieved from storage. Server-side encryption is always enabled on Kinesis video streams. If a user-provided key is not specified when the stream is created, the default key (provided by Kinesis Video Streams) is used.","impact":"Without KMS CMK customer-managed keys, you do not have full and granular control over who can access encrypted Kinesis streams data.","report_fields":["StreamARN"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nA user-provided AWS KMS master key must be assigned to a Kinesis video stream when it is created. You can't later assign a different key to a stream.\nTo encrypt AWS Kinesis Video Streams with KMS customer master keys:\n1. Open the console at https://console.aws.amazon.com/kinesisvideo/home.\n2. On the \"Video streams\" page, choose \"Create video stream\".\n3. On the \"Create a new video stream\" page, type a name for the stream. Select the \"Custom configuration\" radio button.\n4. Under \"KMS customer master key (CMK)\", select \"Customer managed CMK\" and choose a Customer managed CMK.\n4. Choose \"Create video stream\".","multiregional":false,"service":"Amazon Kinesis"},"ecc-aws-229":{"article":"Amazon ECR repository offers encryption of data at rest, a security feature that helps prevent unauthorized access to AWS ECR repository data. Use encryption to better protect your data from loss or theft.\nFor more control over the encryption for your Amazon ECR repositories, you can use server-side encryption with KMS keys stored in AWS Key Management Service (AWS KMS). When you use AWS KMS to encrypt your data, you can either use the default AWS managed key, which is managed by Amazon ECR, or specify your own KMS key (referred to as a customer managed key).","impact":"With disabled KMS encryption, there is a possibility of data loss or theft and a possibility of unauthorized access to AWS ECR repository data. Additionally, without KMS CMK, you do not have full and granular control over access to ECR repository.","report_fields":["repositoryArn"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nThe KMS encryption settings cannot be changed or disabled after the repository is created. To create an Amazon ECR repository with KMS encryption:\n1. Open the Amazon ECR console at https://console.aws.amazon.com/ecr/repositories\n2. Click Create repository button.\n3. Under Encryption settings, enable KMS encryption and then choose either 'a' or 'b':\n a) Don\u2019t check the box 'Customize encryption settings'. This way Amazon ECR uses an AWS-managed CMK with the alias aws/ecr by default. \n b) Check the box 'Customize encryption settings' and choose an AWS KMS CMK key. Note that the key should be stored in the same region as the ECR, for this to work properly.\n4. Click the Create repository button.","multiregional":false,"service":"Amazon Elastic Container Registry"},"ecc-aws-296":{"article":"New engine versions have important performance and security improvements.","impact":"Without keeping the RDS MySQL up-to-date, it is possible to miss out on new software features, bug fixes, security patches, and performance improvements.","report_fields":["DBInstanceArn"],"remediation":"1. Sign in to the AWS Management Console and open the Amazon RDS console at https://console.aws.amazon.com/rds/\n2. In the console, choose Databases, and then choose the database.\n3. Choose Modify.\n4. For DB engine version, choose the MySQL 8.0 version to upgrade to, and then choose Continue.\n5. For Scheduling of modifications, choose Apply immediately.\n6. Choose Modify DB instance to start the upgrade.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-313":{"article":"If you do not specify a Customer managed key when creating your environment, Amazon DMS uses its AWS owned key for data encryption on your environment.\nWhen you create and use your own KMS CMK customer-managed keys to protect the data on your environment, you obtain full control over who can use the CMK keys and access the encrypted data. The AWS KMS service allows you to create, rotate, disable, enable, and audit your Customer Master Keys (CMKs).\nIn addition, if you provide a Customer managed key, you must attach the policy statement for CloudWatch access. You must also create the Customer managed key-specific execution role.","impact":"Without a KMS CMK customer-managed key, you do not have full and granular control over who can access key that is used for encryption.","report_fields":["ReplicationInstanceArn","ReplicationInstanceIdentifier"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nTo create new DMS replication instance with kms cmk:\n1. Sign in to the AWS Management Console and open the DMS console at https://console.aws.amazon.com/dms/v2\n2. In the navigation pane, click on the 'Replication instances'.\n3. Click on the 'Create replication instance'.\n4. Under the 'Advanced security and network configuration'.\n5. Choose any existed KMS key (not 'Default') or create new one.\n6. Create instance.","multiregional":false,"service":"AWS Database Migration Service"},"ecc-aws-378":{"article":"This policy identifies the EBS volumes that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["VolumeId"],"remediation":"1. Sign in to the AWS Management Console and open the EC2 console at https://console.aws.amazon.com/ec2/v2/.\n2. In the navigation pane under 'Elastic Block Store' choose 'Volumes'.\n3. Choose required volume.\n4. Open 'Tags' and click on 'Manage tags'.\n5. Add a tags.\n6. Save.","multiregional":false,"service":"Amazon Elastic Block Store"},"ecc-aws-302":{"article":"The PostgreSQL planner/optimizer is responsible for parsing and verifying the syntax of each query received by the server. If the syntax is correct, a parse tree is built up. Otherwise, an error is generated. The 'log_parser_stats' flag controls the inclusion of parser performance statistics in the PostgreSQL logs for each query.","impact":"The 'log_parser_stats' flag enables a crude profiling method for logging parser performance statistics which even though can be useful for troubleshooting, it may increase the amount of logs significantly and have performance overhead.","report_fields":["DBInstanceArn"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/\n2. In the navigation pane, choose Parameter groups.\n3. Choose the required parameter group\n4. Choose Edit parameters.\n5. Under Parameters, in search bar type \"log_parser_stats\".\n6. Choose 0.\n7. Choose Save changes.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-342":{"article":"Ensure that Route53 automatic domain renewal is enabled to avoid domain expiration. With this option enabled Amazon Route53 will automatically renew registration for a domain shortly before the expiration date.","impact":"If you do not renew your domain name, it will become expired. An expired Amazon Route53 domain can cause website or application downtime or failure. An expired domain could be taken over by a malicious individual or deleted by the domain registrar and it will become available for others to register.\nWhen automatic renewal is turned off, be aware of the following effects on your domain:\n - Some TLD registries delete domains even before the expiration date if you don't renew early enough. It is strongly recommended that you leave automatic renewal enabled if you want to keep a domain name.\n - It is also strongly recommended that you don't plan to re-register a domain after it has expired. Some registrars allow others to register domains immediately after the domains expire, so you might not be able to re-register before the domain is taken by someone else.\n - Some registries charge a large premium to restore expired domains.\n - On or near the expiration date, the domain becomes unavailable on the internet.","report_fields":["DomainName"],"remediation":"To enable automatic renewal for a domain:\n1. Sign in to the AWS Management Console and open the Route53 console at https://console.aws.amazon.com/route53/.\n2. In the navigation pane, choose 'Registered Domains'.\n3. Choose the name of the domain that you want to update.\n4. Choose 'Enable' for 'Auto renew'.\n5. If you encounter issues while enabling automatic renewal, you can contact AWS Support for free. \n For more information, see 'Contacting AWS Support about domain registration issues': https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/domain-contact-support.html.","multiregional":true,"service":"Amazon Route 53"},"ecc-aws-187":{"article":"To improve the security posture of your VPC, you can configure Amazon EC2 to use an interface VPC endpoint. Interface endpoints are powered by AWS PrivateLink, a technology that enables you to access Amazon EC2 API operations privately. It restricts all network traffic between your VPC and Amazon EC2 to the Amazon network. \nBecause endpoints are supported within the same Region only, you cannot create an endpoint between a VPC and a service in a different Region. This prevents unintended Amazon EC2 API calls to other Regions.","impact":"Unrestricted access increases the opportunity for malicious activity such as unauthorized access, and loss of data.","report_fields":["VpcId","OwnerId"],"remediation":"1. Open the Amazon VPC console at https://console.aws.amazon.com/vpc/.\n2. In the navigation pane, choose Endpoints. \n3. Choose Create Endpoint. \n4. For Service category, choose AWS services. \n5. For Service Name, choose com.amazonaws..ec2.\n6. For Type, choose Interface. \n7. Complete the following information. \n7.1. For VPC, select a VPC in which to create the endpoint. \n7.2. For Subnets, select the subnets (Availability Zones) in which to create the endpoint network interfaces. Not all Availability Zones are supported for all AWS services. \n7.3. To enable private DNS for the interface endpoint, select the check box for Enable DNS Name. This option is enabled by default. To use the private DNS option, the following attributes of your VPC must be set to true: enableDnsHostnames; enableDnsSupport. \n7.4. For Security group, select the security groups to associate with the endpoint network interfaces. \n7.5. (Optional) Add or remove a tag. To add a tag, choose Add tag and do the following: For Key, enter the tag name. For Value, enter the tag value. \n7.6. To remove a tag, choose the delete button (x) to the right of the tag Key and Value. \n8. You can attach a policy to your VPC endpoint to control access to the Amazon EC2 API. \n9. Choose Create endpoint.","multiregional":false,"service":"Amazon Virtual Private Cloud"},"ecc-aws-245":{"article":"Enabling the 'log_duration' setting causes the duration of each completed statement to be logged. This does not logs the text of the query and thus, behaves different from the 'log_min_duration_statement' flag. This parameter cannot be changed after the session start.","impact":"When this parameter is disabled it would be harder to find and investigate slow running queries.","report_fields":["DBInstanceArn"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/\n2. In the navigation pane, choose Parameter groups.\n3. Choose the required parameter group\n4. Choose Edit parameters.\n5. Under Parameters, in search bar type \"log_duration\".\n6. Choose 1.\n7. Choose Save changes.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-101":{"article":"A VPC subnet is a part of the VPC, with its own rules for traffic. Subnets with automatic Public IP assignment can inadvertently expose the instances within this subnet to the internet. It is recommended to disable this feature for subnets.","impact":"A public IP assignment can unintentionally expose the instances within this subnet to the internet, which can cause a loss of availability integrity and confidentiality.","report_fields":["SubnetId","VpcId","OwnerId"],"remediation":"1. Log in to the AWS console. \n2. On the console, select a specific region.\n3. Navigate to the 'VPC' service. \n4. In the navigation pane, click on 'Subnets'.\n5. Select the identified Subnet and Select the 'Modify auto-assign IP settings' option under Subnet Actions. \n6. Disable the 'Auto-Assign IP' option and save it.","multiregional":false,"service":"Amazon Virtual Private Cloud"},"ecc-aws-189":{"article":"An elastic network interface is a logical networking component in a VPC that represents a virtual network card. Multiple ENIs can cause dual-homed instances, meaning instances that have multiple subnets. This can add network security complexity and introduce unintended network paths and access.","impact":"Multiple ENIs can cause dual-homed instances, meaning instances that have multiple subnets. This can add network security complexity and introduce unintended network paths and access.","report_fields":["InstanceId","OwnerId"],"remediation":"To detach a network interface \n1. Open the Amazon EC2 console at https://console.aws.amazon.com/ec2/.\n2. Under Network & Security, choose Network Interfaces. \n3. Filter the list by the noncompliant instance IDs to see the associated ENIs. \n4. Select the ENIs that you want to remove. \n5. From the Actions menu, choose Detach. \n6 If you see the prompt Are you sure that you want to detach the following network interface?, choose Detach.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-465":{"article":"Amazon FSx can take an automatic daily backup of your file system. These automatic daily backups occur during the daily backup window that was established when you created the file system. \nAutomatic daily backups are kept for a certain period of time, known as a retention period.\nIt is highly recommended to use automatic daily backups set at least to 7 days for file systems that have any level of critical functionality associated with them.","impact":"A retention period of fewer than 7 days set for FSx file systems can result in data loss and the inability to recover it in the event of failure.","report_fields":["ResourceARN"],"remediation":"To update your Amazon FSx systems configuration in order to set up a sufficient backup retention period, perform the following actions: \n1. Open the Amazon FSx for Lustre console at https://console.aws.amazon.com/fsx/.\n2. From the console dashboard, choose the name of the file system that reconfigure.\n3. In the navigation pane, choose Backups.\n4. In the Settings tab, click Update.\n5. Select 'Yes' to enable automatic backups.\n6. For 'Automatic Backup Retention' set number of days to retain a backup at least to 7 days.\n7. Click update.","multiregional":false,"service":"Amazon FSx"},"ecc-aws-572":{"article":"Each AWS KMS key that you create in AWS KMS costs $1/month, regardless of whether it is enabled or disabled.\nUnless there is a business need to retain disabled KMS CMKs, you should remove them to avoid incurring unexpected charges and to maintain an accurate inventory of system components.\nWarning: Deleting an AWS KMS key is destructive and potentially dangerous. It deletes the key material and all metadata associated with the KMS key and is irreversible. After a KMS key is deleted, you can no longer decrypt the data that was encrypted under that KMS key, which means that data becomes unrecoverable. You should delete a KMS key only when you are sure that you don't need to use it anymore.","impact":"Keeping unused KMS keys can result in escalating costs and cluttered AWS accounts.","report_fields":["KeyArn","AWSAccountId"],"remediation":"To enable customer managed key:\n 1. Sign in to the AWS Management Console and open the AWS Key Management Service (AWS KMS) console at https://console.aws.amazon.com/kms.\n 2. To change the AWS Region, use the Region selector in the upper-right corner of the page.\n 3. In the navigation pane, choose 'Customer managed keys'.\n 4. Choose the check box for the KMS keys that you want to enable.\n 5. To enable a KMS key, choose 'Key actions', 'Enable'.\n\nTo schedule key deletion:\n Warning: Deleting an AWS KMS key is destructive and potentially dangerous. It deletes the key material and all metadata associated with the KMS key and is irreversible. After a KMS key is deleted, you can no longer decrypt the data that was encrypted under that KMS key, which means that data becomes unrecoverable. You should delete a KMS key only when you are sure that you don't need to use it anymore.\n 1. Sign in to the AWS Management Console and open the AWS Key Management Service (AWS KMS) console at https://console.aws.amazon.com/kms.\n 2. To change the AWS Region, use the Region selector in the upper-right corner of the page.\n 3. In the navigation pane, choose 'Customer managed keys'.\n 4. Choose the check box for the KMS keys that you want to schedule for deletion.\n 5. Choose 'Schedule key deletion'.\n 6. Set a waiting period of 7 - 30 days.\n 7. Confirm that you want to schedule these keys for deletion.\n 8. Click 'Schedule deletion'.","multiregional":false,"service":"AWS Key Management Service"},"ecc-aws-506":{"article":"When creating a Redshift cluster, you should change the default database name 'dev' to a unique value. Default names are public knowledge and should be changed upon configuration.","impact":"Well-known database name could lead to inadvertent access if it was used in IAM policy conditions.","report_fields":["ClusterIdentifier"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n1. Open the Amazon Redshift console at https://console.aws.amazon.com/redshift/.\n2. Click on the 'Create cluster'.\n3. Under 'Additional configurations' click on the 'Database configurations'.\n4. Change 'Database name' from default.","multiregional":false,"service":"Amazon Redshift"},"ecc-aws-214":{"article":"TLS can be used to help prevent potential attackers from using person-in-the-middle or similar attacks to eavesdrop on or manipulate network traffic. Only encrypted connections over TLS should be allowed. Encrypting data in transit can affect performance. You should test your application with this feature to understand the performance profile and the impact of TLS.","impact":"Without enabled encryption in transit, Redshift cluster databases accept a connection whether it uses SSL or not. This can lead to malicious activity such as man-in-the-middle attacks (MITM), intercepting, or manipulating network traffic.","report_fields":["ClusterIdentifier"],"remediation":"1. Open the Amazon Redshift console at https://console.aws.amazon.com/redshift/.\n2. In the navigation menu, choose Config, then choose Workload management to display the Workload management page. \n3. Choose the parameter group that you want to modify.\n4. Choose Parameters.\n5. Choose Edit parameters then set require_ssl to 1. \n6. Enter your changes and then choose Save.","multiregional":false,"service":"Amazon Redshift"},"ecc-aws-153":{"article":"Audit logs are highly customizable. They allow you to track user activity on your Elasticsearch clusters, including authentication successes and failures, requests to Elasticsearch, index changes, and incoming search queries.","impact":"If security critical information is not recorded, there will be no trail for forensic analysis, and discovering the cause of problems or the source of attacks may become more difficult or impossible.","report_fields":["ARN"],"remediation":"From Console:\n1. Login to the AWS Management Console and open the ES domain using https://console.aws.amazon.com/esv3\n2. Choose the domain.\n3. Verify whether advanced security options are configured by going to the 'Security configuration' tab. \n4. If 'Fine-grained access control' disabled:\n a) Select 'Edit'.\n b) Check 'Enable fine-grained access control' and configure master user.\n c) Make other necessary configurations and select 'Save changes'.\n5. Choose the Logs tab.\n6. Under 'CloudWatch Logs', choose 'Audit logs' and click 'Enable'.\n4. Create a CloudWatch log group, or choose the existing one. \n5. Choose an access policy that contains the appropriate permissions, or create a policy using a JSON file the console provides:\n { \n \"Version\": \"2012-10-17\", \n \"Statement\": [ \n { \n \"Effect\": \"Allow\", \n \"Principal\": { \n \"Service\": \"es.amazonaws.com\" \n }, \n \"Action\": [ \n \"logs:PutLogEvents\", \n \"logs:CreateLogStream\" \n ], \"Resource\": \"cw_log_group_arn\"\n } \n ] \n }\n6. Choose Enable\n\nFrom CLI:\naws es update-elasticsearch-domain-config --domain-name my-domain --log-publishing-options \"AUDIT_LOGS={{CloudWatchLogsLogGroupArn=arnawslogsus-east-1123456789012log-groupmy-log-group,Enabled=true}}\"","multiregional":false,"service":"Amazon OpenSearch Service"},"ecc-aws-083":{"article":"Ensure that all your AWS CloudFront web distributions are integrated with the Web Application Firewall (AWS WAF) service to protect against application-layer attacks.","impact":"Not enabling AWS Cloudfront-WAF integration can lead to application-layer attacks that can compromise the security of your web applications or place unnecessary load on them.","report_fields":["ARN"],"remediation":"1. Navigate to the Cloudfront dashboard at https://console.aws.amazon.com/cloudfront/.\n2. On the Distributions page, select the relevant CDN.\n3. Click on the Distribution Settings button from the dashboard top menu. \n4. On the General tab, click on the Edit button.\n5. On the Distribution Settings page, verify the AWS WAF Web ACL configuration status. If AWS WAF Web ACL is set to None, AWS WAF is not associated with an Access Control List (ACL).","multiregional":true,"service":"Amazon CloudFront"},"ecc-aws-122":{"article":"AWS DynamoDb should be encrypted using AWS-managed Customer Master Key (CMK), instead of AWS-owned CMK. This is necessary in order to meet encryption regulatory requirements of Server-Side encryption for the sensitive data that may be stored in DynamoDB. \nIn addition, encrypting DynamoDb with AWS-managed CMK allows you to view the CMK and its key policy as well as audit the encryption/decryption events by examining the DynamoDB API calls using CloudTrail.","impact":"Without AWS-managed CMKs, you cannot view the CMK key and its policy, grant access to it, and audit the encryption and decryption of DynamoDB data.","report_fields":["TableArn"],"remediation":"1. Sign in to the AWS console. \n2. On the console, select the specific region. \n3. Navigate to the 'DynamoDB' dashboard. \n4. Select the reported table from the list of DynamoDB tables. \n5. In the 'Overview' tab, navigate to the 'Table Details' section. \n6. Click on the 'Manage Encryption' link available for 'Encryption Type'.\n7. In the 'Manage Encryption' pop-up window, select 'KMS' as the encryption type.","multiregional":false,"service":"Amazon DynamoDB"},"ecc-aws-265":{"article":"Using the latest generation of Elasticache nodes will improve performance at a lower cost. \nDue to the limited amount of previous generation node types, AWS cannot guarantee a successful replacement when a node becomes unhealthy in your cluster(s). In such a scenario, your cluster availability may be negatively impacted.","impact":"Using previous generation of Elasticache nodes you get less performance for more cost. \nDue to the limited amount of previous generation node types, AWS cannot guarantee a successful replacement when a node becomes unhealthy in your cluster(s). In such a scenario, your cluster availability may be negatively impacted.","report_fields":["ARN"],"remediation":"To change generation of nodes for Elasticache Redis cluster:\n1. Sign in to the Console and open the ElastiCache console at https://console.aws.amazon.com/elasticache/.\n2. From the navigation pane, choose 'Redis clusters'.\n3. From the list of clusters, choose the cluster you want to update.\n4. Choose 'Actions' and then choose 'Modify'.\n5. Choose the new node type from the node type list.\n6. If you want to perform the update right away, choose 'Apply immediately'. If Apply immediately is not chosen, the update process is performed during the cluster's next maintenance window.\n7. Choose 'Modify'.\n\nTo change generation of nodes for Elasticache Memcached cluster, it must be redeployed to mitigate the issue.\nTo create an ElastiCache for Memcached cluster:\n1. Sign in to the AWS Management Console and open the ElastiCache console at https://console.aws.amazon.com/elasticache/.\n2. From the list in the upper-right corner, choose the AWS Region you want to launch this cluster in.\n3. Choose 'Memcached clusters' from the navigation pane.\n4. Choose 'Create Memcached cluster'.\n5. Complete the 'Cluster settings' section. For 'Node type' select current generation node type.\n6. Click 'Next'.\n7. Complete the 'Advanced settings' section. \n8. Click 'Next'.\n9. Review all your entries and choices, then go back and make any needed corrections. When you're ready, choose 'Create' to launch your cluster.","multiregional":false,"service":"Amazon ElastiCache"},"ecc-aws-147":{"article":"Elastic Compute Cloud (EC2) supports encryption at rest when using the Elastic Block Store (EBS) service.\nFor an added layer of security of your sensitive data in EBS volumes, you should enable EBS encryption at rest. Amazon EBS encryption offers a straightforward encryption solution for your EBS resources that doesn't require you to build, maintain, and secure your own key management infrastructure. It uses KMS keys when creating encrypted volumes and snapshots.\nEncryption operations occur on the servers that host EC2 instances, ensuring the security of both data-at-rest and data-in-transit between an instance and its attached EBS storage.","impact":"Disabled encryption allows unauthorized or anonymous users to gain access to EBS containing sensitive data. With Elastic Block Store encryption enabled, the data stored on the volume, disk I/O, and snapshots created from the volume are all encrypted.","report_fields":["VolumeId"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nThere is no direct way to encrypt an existing unencrypted volume or snapshot. You can only encrypt a new volume or snapshot when you create it.\n\nTo encrypt existing EBS Volume:\n1. Login to the AWS Management Console and open the Amazon EC2 console using https://console.aws.amazon.com/ec2/.\n2. Under 'Elastic Block Store', click on 'Volumes'.\n3. Click on required volume.\n4. Click on the 'Actions'.\n5. Click on the 'Create snapshot'.\n6. Click 'Create snapshot'.\n7. Under 'Elastic Block Store', click on 'Snapshots'.\n8. Click on the required snapshot.\n9. Click on the 'Actions', and 'Create volume from snapshot'.\n10. Under the encryption click on the 'Encrypt this volume'.\n11. Choose key or create a new one.\n12. Click on the 'Create volume'.","multiregional":false,"service":"Amazon Elastic Block Store"},"ecc-aws-129":{"article":"Application and Network Load Balancers should have access logs enabled to capture detailed information about requests sent to your load balancer. Each log contains information such as the time the request was received, the client's IP address, latencies, request paths, and server responses. You can use these access logs to analyze traffic patterns and troubleshoot issues.","impact":"Disabled access logs for Application and Network Load Balancers make it harder to analyze statistics, diagnose issues, detect different types of attacks, and retain data for regulatory or legal purposes.","report_fields":["LoadBalancerArn"],"remediation":"1. Sign in to the AWS console.\n2. From the console, select the specific region.\n3. Navigate to the EC2 dashboard.\n4. Click on 'Load Balancers' (Left Panel).\n5. Select the reported ELB.\n6. Click on the 'Actions' drop-down list.\n7. Click on 'Edit attributes'.\n8. In the 'Edit load balancer attributes' pop-up box, select 'Enable' for 'Access logs' and configure S3 location where you want to store ELB logs.","multiregional":false,"service":"Amazon Elastic Load Balancing"},"ecc-aws-401":{"article":"This policy identifies the DLM lifecycle policy that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["PolicyId"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon EC2 at https://console.aws.amazon.com/ec2/v2.\n2. Under the 'Elastic Block Store' click on the 'Lifecycle Manager'.\n3. Click on the required lifelycle policy.\n4. Open 'Tags' and click on the 'Manage tags'.\n5. Add new tag and save.","multiregional":false,"service":"Amazon Data Lifecycle Manager"},"ecc-aws-062":{"article":"Security groups provide stateful filtering of ingress and egress network traffic to AWS resources. \nIt is recommended that no security group allows unrestricted ingress access to remote server administration ports, such as SSH to port 22 and RDP to port 3389.","impact":"Exposing port 22 (SSH) to public access can increase opportunities for malicious activities such as unauthorized access, Man-In-The-Middle attacks (MITM), and brute-force attacks that raise the risk of resource compromising.","report_fields":["GroupId","VpcId","OwnerId"],"remediation":"1. Login to the AWS Management Console at https://console.aws.amazon.com/vpc/home\n2. In the left pane, click on 'Security Groups'.\n3. For each security group, perform the following:\n4. Select the security group.\n5. Click on the 'Inbound Rules' tab.\n6. Click on the 'Edit inbound rules' button.\n7. Identify the rules to be edited or removed.\n8. Either:\n A) Update the Source field to a range other than 0.0.0.0/0 or ::/0 \n B) Click 'Delete' to remove the offending inbound rule.\n9. Click on 'Save rules'.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-250":{"article":"You can enable API caching in Amazon API Gateway to cache your endpoint's responses. With caching, you can reduce the number of calls made to your endpoint and also improve the latency of requests to your API. \nIn general, a larger capacity gives a better performance, but also costs more.","impact":"If you do not enable cache, then the number of calls and the delay to endpoint will be greater than with the enabled cache.","report_fields":["stageName","restApiId"],"remediation":"1. Go to the API Gateway console.\n2. Choose the API.\n3. Choose Stages.\n4. In the Stages list for the API, choose the stage.\n5. Choose the Settings tab.\n6. Choose Enable API cache.\n 6.1. Choose required Cache capacity.\n7. Wait for the cache creation to complete.","multiregional":false,"service":"Amazon API Gateway"},"ecc-aws-188":{"article":"A network access control list (ACL) is an optional layer of security for your VPC that acts as a firewall for controlling traffic in and out of one or more subnets. Network access control lists should always have to be attached.","impact":"Not cleaning out your network access control lists pose the risk that a forgotten network access control list will be used to accidentally open an attack surface.","report_fields":["NetworkAclId","OwnerId"],"remediation":"To delete a network ACL:\n1. Open the Amazon VPC console at https://console.aws.amazon.com/vpc/.\n2. In the navigation pane, choose Network ACLs.\n3. Select the network ACL, and then choose Delete.\n4. In the confirmation dialog box, choose Yes, Delete.","multiregional":false,"service":"Amazon Virtual Private Cloud"},"ecc-aws-004":{"article":"At the Amazon S3 bucket level, you can configure permissions through a bucket policy making the objects accessible only through HTTPS.","impact":"The HTTP protocol is not a secure method of transmitting data. Any person monitoring the Internet traffic can see unencrypted data, which leads to a breach of confidentiality.","report_fields":["Name"],"remediation":"1. Login to the AWS Management Console and open the Amazon S3 console using https://console.aws.amazon.com/s3/ .\n2. Select the Check box next to Bucket. \n3. Click on 'Permissions'. \n4. Click on 'Bucket Policy'.\n5. Filling in the required information, add the following to the existing policy: \n{\n \"Sid\": \"\", \n \"Effect\": \"Deny\", \n \"Principal\": \"*\", \n \"Action\": \"s3:GetObject\", \n \"Resource\": \"arn:aws:s3:::/*\", \n \"Condition\": {\n \"Bool\": {\n \"aws:SecureTransport\": \"false\" \n } \n } \n} \n6. Save. \n7. Repeat for all the buckets in your AWS account that contain sensitive data.","multiregional":true,"service":"Amazon S3"},"ecc-aws-332":{"article":"WorkSpaces access should be restricted to trusted operating systems and clients. WorkSpaces access is supported from a variety of clients and operating systems, including HTML5 based browsers. Disabling Web Access prevents access to the Workspace from HTML5 based browsers, ensuring access can only occur from known operating systems.","impact":"Enabled Web Access allows access to the Workspace from HTML5 based browsers, it increases the attack surface and the opportunity for malicious activity.","report_fields":["DirectoryId"],"remediation":"Perform the following steps to disable Web Access:\n1. Log in to the WorkSpaces console at https://console.aws.amazon.com/workspaces/.\n2. In the left pane, click \"Directories\".\n3. Select the directory and then click \"Actions\", \"Update Details\".\n4. Expand \"Access Control Options\".\n5. Uncheck \"Web Access\".\n6. Click \"Update and Exit\".","multiregional":false,"service":"Amazon WorkSpaces Family"},"ecc-aws-040":{"article":"The Kubernetes project is rapidly evolving, introducing new features, design updates, and bug fixes. The community releases new Kubernetes minor versions (1.XX), as generally available approximately every three months. Each minor version is supported for approximately nine months after it is first released. As new Kubernetes versions become available for Amazon EKS, we recommend that you proactively update your clusters to use the latest available version.","impact":"Without keeping the Kubernetes container-orchestration system up-to-date, it is possible to miss out on new software features, bug fixes, security patches, and performance improvements.","report_fields":["arn"],"remediation":"1. Open the Amazon EKS console at https://console.aws.amazon.com/eks/home#/clusters.\n2. Choose the name of the Amazon EKS cluster to update and choose 'Update cluster version'. \n3. For Kubernetes version, select the version to update your cluster to and choose 'Update'. \n4. For 'Cluster name', type the name of your cluster and choose 'Confirm'. The update takes several minutes to complete.","multiregional":false,"service":"Amazon Elastic Kubernetes Service"},"ecc-aws-347":{"article":"If you do not specify a Customer managed key when creating your environment, Amazon MWAA uses its AWS owned key for data encryption on your environment.\nWhen you create and use your own KMS CMK customer-managed keys to protect the data on your environment, you obtain full control over who can use the CMK keys and access the encrypted data. The AWS KMS service allows you to create, rotate, disable, enable, and audit your Customer Master Keys (CMKs).","impact":"Not encrypting data at rest can lead to unauthorized access to sensitive data. Without a KMS CMK customer-managed key, you do not have full and granular control over who can access key that is used for encryption.","report_fields":["ClusterArn"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nPerform the following steps to encrypt MSK during creation:\n1. Login to the MSK console at https://console.aws.amazon.com/msk/.\n2. Click on the Clusters.\n3. Choose Create cluster.\n4. Choose 'Custom create' and 'Provisioned'.\n5. Inside 'Security' step under 'Encrypt data at rest' choose 'Use customer managed key'.\n6. Choose existing key or create new.\n7. Create cluster.","multiregional":false,"service":"Amazon Managed Streaming for Apache Kafka"},"ecc-aws-269":{"article":"A default Virtual Private Cloud is designed in such a way that you can quickly deploy AWS resources and not have to think about the underlying network. The default VPC comes with a default configuration that would not meet all security best practices, hence a non-default VPC should not be used for sophisticated AWS cloud applications.","impact":"Using default VPCs increases the chance that ElastiCache clusters can accidentally open an attack surface.","report_fields":["ARN"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nChancing VPC on an existing Redis cluster:\n1. Create a manual backup of the cluster:\n 1.1. Sign in to the AWS Management Console and open the Amazon ElastiCache console at https://console.aws.amazon.com/elasticache/.\n 1.2. From the list in the upper-right corner, choose the AWS Region that you want to launch this cluster in.\n 1.3. Choose 'Redis clusters' from the navigation pane.\n 1.4. Choose the cluster and choose 'Action', and then 'Backup'.\n 1.5. Make sure the cluster name is right and enter backup name. \n 1.6. Select 'Encryption key'. \n 1.7. Choose 'Create Backup'.\n2. Create a new cluster by restoring from the backup:\n 2.1. Sign in to the AWS Management Console and open the ElastiCache console at https://console.aws.amazon.com/elasticache/.\n 2.2. Choose 'Redis clusters' from the navigation pane.\n 2.3. Select 'Create Redis cluster'.\n 2.4. For 'Choose a cluster creation method', choose 'Restore from backups'.\n 2.5. For 'Source' select 'Amazon ElastiCache backups'. And select backup created on the previous step.\n 2.6. Under 'Connectivity' choose subnet group with non-default VPC or choose 'Create new'.\n 2.7. Complete other settings and click 'Next'.\n 2.8. Complete 'Advanced settings' tab and click 'Next'.\n 2.9. Review settings and click 'Create'.\n3. Update the endpoints in your application to the new cluster's endpoints.\n4. Delete the old cluster.\n\nTo create an ElastiCache for Memcached cluster with non-default VPC:\n1. Sign in to the AWS Management Console and open the ElastiCache console at https://console.aws.amazon.com/elasticache/.\n2. From the list in the upper-right corner, choose the AWS Region you want to launch this cluster in.\n3. Choose 'Memcached clusters' from the navigation pane.\n4. Choose 'Create Memcached cluster'.\n5. Under 'Connectivity' choose subnet group with non-default VPC or choose 'Create new'.\n6. Complete the 'Cluster settings' section. \n7. Click 'Next'.\n8. Complete 'Advanced settings' tab and click 'Next'.\n9. Complete the 'Advanced settings' section. \n10. Click 'Next'.\n11. Review all your entries and choices, then go back and make any needed corrections. When you're ready, choose 'Create' to launch your cluster.","multiregional":false,"service":"Amazon ElastiCache"},"ecc-aws-280":{"article":"If you do not specify a Customer managed key when creating your environment, Amazon OpenSearch uses its AWS owned key for data encryption on your environment.\nWhen you create and use your own KMS CMK customer-managed keys to protect the data on your environment, you obtain full control over who can use the CMK keys and access the encrypted data. The AWS KMS service allows you to create, rotate, disable, enable, and audit your Customer Master Keys (CMKs).\nIn addition, if you provide a Customer managed key, you must attach the policy statement for CloudWatch access. You must also create the Customer managed key-specific execution role.","impact":"Without a KMS CMK customer-managed key, you do not have full and granular control over who can access key that is used for encryption.","report_fields":["ARN"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nIf encryption of data at rest is not enabled at all, perform the following actions:\n1. Open the domain in the AWS console, then choose Actions and Edit security configuration.\n2. Under Encryption, select Enable encryption of data at rest.\n3. Choose an AWS KMS key to use, then choose Save changes.\n\nIf encryption of data at rest is already enabled with AWS-managed key, you have to create a new domain with KMS CMS:\n1. Sign in to the AWS Management Console and open the Amazon OpenSearch console at https://console.aws.amazon.com/esv3\n2. In the navigation pane, under Domains, click on the 'Create domain'.\n3. Under 'Choose an AWS KMS key', click on the 'Choose A different key'.\n4. Choose existing key or create a new one.\n6. Create domain.","multiregional":false,"service":"Amazon OpenSearch Service"},"ecc-aws-534":{"article":"A launch template is similar to a launch configuration, in that it specifies instance configuration information. However, defining a launch template instead of a launch configuration allows you to have multiple versions of a launch template. With versioning of launch templates, you can create a subset of the full set of parameters. Then, you can reuse it to create other versions of the same launch template. It is recommended to use launch templates to ensure that you're accessing the latest features and improvements. Not all Amazon EC2 Auto Scaling features are available when you use launch configurations. With launch templates, you can also use newer features of Amazon EC2.","impact":"Not using a launch template to create an Auto Scaling group, you missing out on access to the latest features and improvements.","report_fields":["AutoScalingGroupARN"],"remediation":"To copy a launch configuration to a launch template (console):\n1. Open the Amazon EC2 console at https://console.aws.amazon.com/ec2/.\n2. On the navigation pane, under 'Auto Scaling', choose 'Launch Configurations'.\n3. Select the launch configuration you want to copy and choose 'Copy to launch template', 'Copy selected'. This sets up a new launch template with the same name and options as the launch configuration that you selected.\n4. For 'New launch template name', you can use the name of the launch configuration (the default) or enter a new name. Launch template names must be unique.\n5. (Optional) To create an Auto Scaling group using the new launch template, select 'Create an Auto Scaling group using the new template'.\n6. Choose 'Copy'.\n\nTo replace the launch configuration for an Auto Scaling group (console):\n1. Open the Amazon EC2 console at https://console.aws.amazon.com/ec2/, and choose 'Auto Scaling Groups' from the navigation pane.\n2. Select the Auto Scaling group that you want to modify.\n3. On the 'Details' tab, choose 'Launch configuration', 'Edit'.\n4. Choose 'Switch to launch template'.\n5. For 'Launch template', select your launch template.\n6. For 'Version', select the launch template version, as needed. After you create versions of a launch template, you can choose whether the Auto Scaling group uses the default or the latest version of the launch template when scaling out.\n7. When you have finished, choose 'Update'.","multiregional":false,"service":"Amazon EC2 Auto Scaling"},"ecc-aws-531":{"article":"Use Amazon EBS encryption as a straight-forward encryption solution for your EBS resources associated with your EC2 instances. With Amazon EBS encryption, you aren't required to build, maintain, and secure your own key management infrastructure. Amazon EBS encryption uses AWS KMS keys when creating encrypted volumes and snapshots.","impact":"Not using encryption by default can lead to launch failures that would occur if unencrypted volumes were inadvertently referenced when an instance is launched in case of using IAM policies that restricts use of unencrypted volumes. Also disabled encryption allows unauthorized or anonymous users to gain access to EBS containing sensitive data.","report_fields":["account_id","account_name"],"remediation":"From Console \n1. Login to the AWS Management Console and open the Amazon EC2 console using https://console.aws.amazon.com/ec2/ \n2. Under Account attributes, click on EBS encryption. \n3. Click on Manage. \n4. Click on the Enable checkbox.\n5. Click on Update EBS encryption \n6. Repeat for every region that requires the change. \nFrom Command Line:\n1. Run 'aws --region ec2 enable-ebs-encryption-by-default'\n2. Verify that \"EbsEncryptionByDefault\" true is displayed. \n3. Repeat for every region that requires the change.","multiregional":false,"service":"Amazon Elastic Block Store"},"ecc-aws-471":{"article":"To help you automating the handling of interruptions, EC2 Auto Scaling has a feature called Capacity Rebalaning that, if enabled, whenever a Spot instance in your Auto Scaling group is at an elevated risk of interruption, it will proactively attempt to launch a replacement Spot Instance.","impact":"When an Auto Scaling group (ASG) is not configured with rebalancing enabled and if instance is at an elevated risk of interruption, this instance will not be proactively augmented with a new Spot instance before instance is interrupted by Amazon EC2.","report_fields":["AutoScalingGroupARN"],"remediation":"To enable Capacity Rebalancing for an existing Auto Scaling group\n1. Open the Amazon EC2 console at https://console.aws.amazon.com/ec2/, and choose Auto Scaling Groups from the navigation pane.\n2. Select the check box next to your Auto Scaling group. A split pane opens in the bottom of the Auto Scaling groups page.\n3. On the Details tab, choose 'Allocation strategies', 'Edit'.\n4. Under the 'Allocation strategies' section, to enable Capacity Rebalancing, select the 'Capacity rebalance' check box.\n5. Choose 'Update'.\n\nEnable Capacity Rebalancing (AWS CLI)\nUse the 'update-auto-scaling-group' command with the following parameter:\n--capacity-rebalance / --no-capacity-rebalance \u2014 Boolean value that indicates whether Capacity Rebalancing is enabled.","multiregional":false,"service":"Amazon EC2 Auto Scaling"},"ecc-aws-498":{"article":"Elastic Load Balancing automatically distributes your incoming traffic across multiple targets, such as EC2 instances, containers, and IP addresses, in one or more Availability Zones. Elastic Load Balancing scales your load balancer as your incoming traffic changes over time. It is recommended to configure at least two availability zones to ensure availability of services, as the Elastic Load Balancer will be able to direct traffic to another availability zone if one becomes unavailable.","impact":"Load Balancer that does not span multiple Availability Zones is unable to redirect traffic to targets in another Availability Zone if the sole configured Availability Zone becomes unavailable.","report_fields":["LoadBalancerArn"],"remediation":"To add Availability Zones for Application Load Balancer:\n1. Open the Amazon EC2 console at https://console.aws.amazon.com/ec2/.\n2. On the navigation pane, under LOAD BALANCING, choose Load Balancers.\n3. Select the load balancer.\n4. On the Description tab, under Basic Configuration, choose Edit subnets.\n5. To enable a zone, select the check box for that zone and select one subnet. If there is only one subnet for that zone, it is selected. If there is more than one subnet for that zone, select one of the subnets.\n6. To change the subnet for an enabled Availability Zone, choose Change subnet and select one of the other subnets.\n7. To remove an Availability Zone, clear the check box for that Availability Zone.\n8. Choose Save.\n\nTo add Availability Zones for Network Load Balancer:\n1. Open the Amazon EC2 console at https://console.aws.amazon.com/ec2/.\n2. In the navigation pane, under LOAD BALANCING, choose Load Balancers.\n3. Select the load balancer.\n4. On the Description tab, under Basic Configuration, choose Edit subnets.\n5. To enable an Availability Zone, select the check box for that Availability Zone. If there is one subnet for that Availability Zone, it is selected. If there is more than one subnet for that Availability Zone, select one of the subnets. Note that you can select only one subnet per Availability Zone.\n6. For an internet-facing load balancer, you can select an Elastic IP address for each Availability Zone. For an internal load balancer, you can assign a private IP address from the IPv4 range of each subnet instead of letting Elastic Load Balancing assign one.\n7. Choose Save.\n\nNote: Affected resource must be redeployed to mitigate the issue.\nTo add Availability Zones for Gateway Load Balancer:\n1. Open the Amazon EC2 console at https://console.aws.amazon.com/ec2/.\n2. In the navigation pane, under LOAD BALANCING, choose Load Balancers.\n3. Select \"Create Load Balancer\".\n4. Choose \"Gateway Load Balancer\".\n5. Under the \"Network mapping\" choose at least two subnets.\n6. Create load balancer.","multiregional":false,"service":"Amazon Elastic Load Balancing"},"ecc-aws-447":{"article":"Amazon MWAA can send Apache Airflow logs to Amazon CloudWatch. You can view logs for multiple environments from a single location to easily identify Apache Airflow task delays or workflow errors without the need for additional third-party tools.","impact":"If 'Webserver logs' is not enabled and the 'log_level' parameter is not set to the correct value, too many details or too few details may be logged.","report_fields":["Arn"],"remediation":"1. Navigate to https://console.aws.amazon.com/mwaa/home.\n2. Open required environment.\n3. Click 'Edit'. \n4. Click \"Next\".\n5. Enable 'Airflow Webserver logs'.\n6. Choose required log level.\n7. Save changes","multiregional":false,"service":"Amazon Managed Workflows for Apache Airflow"},"ecc-aws-395":{"article":"This policy identifies the VPC endpoint that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["AutoScalingGroupARN"],"remediation":"1. Sign in to the AWS Management Console and open the EC2 console at https://console.aws.amazon.com/ec2/v2/.\n2. In the navigation pane under 'Auto Scaling' choose 'Auto Scaling Groups'.\n3. Choose required Auto Scaling group.\n4. At the bottom on the 'Tags' tab click on 'Edit'.\n5. Click 'Add Tag'.\n6. Add a tags.\n7. Click 'Update'.","multiregional":false,"service":"Amazon EC2 Auto Scaling"},"ecc-aws-548":{"article":"By selecting gp3 volumes over gp2 ones, you can elevate your application's performance, lessen storage expenditures, and fine-tune your infrastructure for heightened efficiency.","impact":"Not using gp3 over gp2 will lead to higher costs and worse performance.","report_fields":["VolumeId"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nTo create gp3 volume and migrate data:\n1. Login to the AWS Management Console and open the Amazon EC2 console using https://console.aws.amazon.com/ec2/.\n2. Under Elastic Block Store, click on 'Volumes'.\n3. Click on required volume.\n4. Click on the 'Actions'.\n5. Click on the 'Create snapshot'.\n6. Create snapshot.\n7. Under Elastic Block Store, click on 'Snapshots'.\n8. Click on the required snapshot.\n9. Click on the 'Actions', and 'Create volume from snapshot'.\n10. Under the 'volume type' choose 'gp3'.\n11. Click on the Create volume.","multiregional":false,"service":"Amazon Elastic Block Store"},"ecc-aws-277":{"article":"Publishing slow logs to Amazon CloudWatch Logs enables you to publish Elasticsearch slow logs from your indexing and search operations performed on ES clusters and gain insights into the performance of those operations.\nYou can enable slow logs to identify whether a performance issue on your cluster is being caused by particular queries or is due to changes in usage. You can then use that information to work with your users to optimize their queries or index configuration to address the problem.","impact":"Disabled Elasticsearch slow logs, making it harder to troubleshoot performance and stability issues.","report_fields":["ARN"],"remediation":"1. Open the Amazon OpenSearch Service console at https://console.aws.amazon.com/esv3/.\n2. Under Domains, select the domain you want to update.\n3. Choose the Logs tab.\n4. Under 'CloudWatch Logs', select 'Search slow logs' if it is disabled and click Enable. \n5. In 'Setup index slow logs' page select an existing log group or create a new one. \n6. Specify CloudWatch an access policy.\n7. Choose Enable.\n8. Repeat 5 - 7 steps to enable 'Index slow logs'.","multiregional":false,"service":"Amazon OpenSearch Service"},"ecc-aws-204":{"article":"Amazon MySQL database engine logs (Audit, Error, General, SlowQuery) should be enabled and sent to CloudWatch.\nDatabase logging provides detailed records of requests made to RDS. Database logs can assist with security and access audits and can help to diagnose availability issues.","impact":"With disabled logs for the database, it is harder to analyze statistics, diagnose issues, detect different types of attacks, and retain data for regulatory or legal purposes.","report_fields":["DBInstanceArn"],"remediation":"Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n1. In the navigation pane, choose Databases.\n2. Choose the DB instance that you want to modify.\n3. Choose Modify.\n4. Under Log exports, choose Audit, Error, General, SlowQuery log types to start publishing to CloudWatch Logs.\n5. Log exports is available only for database engine versions that support publishing to CloudWatch Logs.\n6. Choose Continue. Then on the summary page, choose Modify.\n\nTo create a custom DB parameter group:\n1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. In the navigation pane, choose Parameter groups.\n3. Choose Create parameter group. The Create parameter group window appears.\n4. In the Parameter group family list, choose a DB parameter group family.\n5. In the Type list, choose DB Parameter Group.\n6. In Group name, enter the name of the new DB parameter group.\n7. In Description, enter a description for the new DB parameter group.\n8. Choose Create.\n\nTo enable and publish MySQL logs to CloudWatch Logs from the AWS Management Console, set the following parameters in a custom DB Parameter Group:\n- general_log=1\n- slow_query_log=1\n- log_output = FILE\n\nTo apply a new DB parameter group or DB options group to an RDS DB instance:\n1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. In the navigation pane, choose Databases.\n3. Choose the DB instance that you want to modify.\n4. Choose Modify. The Modify DB Instance page appears.\n5. Under Database options, change the DB parameter group and DB options group as needed.\n6. When you finish you changes, choose Continue. Check the summary of modifications.\n7. (Optional) Choose Apply immediately to apply the changes immediately. Choosing this option can cause an outage in some cases. \n8. Choose Modify DB Instance to save your changes.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-539":{"article":"When you use CloudFront with an Amazon S3 bucket as the origin, you can configure CloudFront and Amazon S3 in a way that provides the following benefits:\n - Restricts access to the Amazon S3 bucket so that it's not publicly accessible\n - Makes sure that viewers (users) can access the content in the bucket only through the specified CloudFront distribution\u2014that is, prevents them from accessing the content directly from the bucket, or through an unintended CloudFront distribution\nTo do this, configure CloudFront to send authenticated requests to Amazon S3, and configure Amazon S3 to only allow access to authenticated requests from CloudFront. CloudFront provides two ways to send authenticated requests to an Amazon S3 origin: origin access control (OAC) and origin access identity (OAI). AWS recommends using OAC because it supports:\n - All Amazon S3 buckets in all AWS Regions, including opt-in Regions launched after December 2022\n - Amazon S3 server-side encryption with AWS KMS (SSE-KMS)\n - Dynamic requests (PUT and DELETE) to Amazon S3\nOAI doesn't work for the scenarios in the preceding list, or it requires extra workarounds in those scenarios.","impact":"When origin access control is disabled for AWS Cloudfront distributions, users bypass any permissions applied to the S3 content and have direct access to objects through Amazon S3 URLs.\nWhen AWS Cloudfront distributions use legacy OAI instead of OAC, you are missing out on supporting the following features:\n - All Amazon S3 buckets in all AWS Regions, including opt-in Regions launched after December 2022\n - Amazon S3 server-side encryption with AWS KMS (SSE-KMS)\n - Dynamic requests (PUT and DELETE) to Amazon S3","report_fields":["ARN"],"remediation":"To migrate from origin access identity (OAI) to origin access control (OAC):\nUpdate the S3 bucket origin to allow both the OAI and OAC to access the bucket's content. This makes sure that CloudFront never loses access to the bucket during the transition. To allow both OAI and OAC to access an S3 bucket, update the bucket policy to include two statements, one for each kind of principal.\n\nThe following example S3 bucket policy allows both an OAI and an OAC to access an S3 origin. In the following example:\n - Replace DOC-EXAMPLE-BUCKET with the name of the S3 bucket origin\n - Replace 111122223333 with the AWS account ID that contains the CloudFront distribution and the S3 bucket origin\n - Replace EDFDVBD6EXAMPLE with the ID of the CloudFront distribution\n - Replace EH1HDMB1FH2TC with the ID of the origin access identity\nExample S3 bucket policy that allows read-only access to an OAI and an OAC:\n{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"AllowCloudFrontServicePrincipalReadOnly\",\n \"Effect\": \"Allow\",\n \"Principal\": {\n \"Service\": \"cloudfront.amazonaws.com\"\n },\n \"Action\": \"s3:GetObject\",\n \"Resource\": \"arn:aws:s3:::DOC-EXAMPLE-BUCKET/*\",\n \"Condition\": {\n \"StringEquals\": {\n \"AWS:SourceArn\": \"arn:aws:cloudfront::111122223333:distribution/EDFDVBD6EXAMPLE\"\n }\n }\n },\n {\n \"Sid\": \"AllowLegacyOAIReadOnly\",\n \"Effect\": \"Allow\",\n \"Principal\": {\n \"AWS\": \"arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity EH1HDMB1FH2TC\"\n },\n \"Action\": \"s3:GetObject\",\n \"Resource\": \"arn:aws:s3:::DOC-EXAMPLE-BUCKET/*\"\n }\n ]\n}\nAfter you update the S3 origin's bucket policy to allow access to both OAI and OAC, you can update the distribution configuration to use OAC instead of OAI. \n\nTo create an origin access control:\n1. Sign in to the AWS Management Console and open the CloudFront console at https://console.aws.amazon.com/cloudfront/v3/home.\n2. In the navigation pane, choose 'Origin access'.\n3. Choose 'Create control setting'.\n4. On the 'Create control setting' form, do the following:\n 4.1 In the 'Details' pane, enter a 'Name' and (optionally) a 'Description' for the origin access control.\n 4.2 In the 'Settings' pane, we recommend that you leave the default setting ('Sign requests (recommended)').\n5. Choose 'Create'.\n6. After the OAC is created, make note of the Name. You need this in the following procedure.\n\nTo add an origin access control to an S3 origin in a distribution:\n\n1. Sign in to the AWS Management Console and open the CloudFront console at https://console.aws.amazon.com/cloudfront/v3/home.\n2. Choose a distribution with an S3 origin that you want to add the OAC to, then choose the 'Origins' tab.\n3. Select the S3 origin that you want to add the OAC to, then choose 'Edit'.\n4. In the 'Origin access' section, choose 'Origin access control settings (recommended)'.\n5. In the 'Origin access' control dropdown menu, choose the OAC that you want to use.\n6. Choose 'Save changes'.","multiregional":true,"service":"Amazon CloudFront"},"ecc-aws-501":{"article":"Fine-grained access control offers additional ways of controlling access to your data on Amazon OpenSearch Service. For example, depending on who makes the request, you might want a search to return results from only one index. You might want to hide certain fields in your documents or exclude certain documents altogether.","impact":"Not using Fine-grained access control you missing following features:\n- Role-based access control;\n- Security at the index, document, and field level;\n- OpenSearch Dashboards multi-tenancy;\n- HTTP basic authentication for OpenSearch and OpenSearch Dashboards.","report_fields":["ARN"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n1. Sign in to the AWS Management Console and open the Amazon OpenSearch console at https://console.aws.amazon.com/esv3\n2. In the navigation pane, under Domains, click on the 'Create domain'.\n3. Under 'Fine-grained access control', make sure 'Enable fine-grained access control' is enabled.\n4. Choose either 'Set IAM ARN as master user' or 'Create master user'.\n4.1. In case of 'Set IAM ARN as master user' specify ARN of the master user.\n4.2. In case of 'Create master user' specify 'Master username' and 'Master password'.\n5. Create domain","multiregional":false,"service":"Amazon OpenSearch Service"},"ecc-aws-573":{"article":"You are charged for each \u201cNAT Gateway-hour\" that your gateway is provisioned and available, each partial NAT Gateway-hour consumed is billed as a full hour. Additionally, unused NAT gateways can potentially allow unauthorized access to your VPC resources.\nIdentifying unused NAT gateways will lower the cost of your AWS bill. A NAT gateway is considered unused if the average value of BytesOutToDestination metric is 0 for the last 7 days. Unless there is a business need to retain unused NAT gateways, you should remove them to maintain an accurate inventory of system components.","impact":"Keeping unused NAT gateways can result in escalating costs and cluttered AWS accounts.","report_fields":["NatGatewayId"],"remediation":"To delete a NAT gateway in Amazon VPC, do the following:\n 1. Access the Amazon VPC console https://console.aws.amazon.com/vpc/.\n 2. Select the AWS Region for your Amazon VPC.\n 3. In the navigation pane, choose 'NAT Gateways'.\n 4. Select the option button for the NAT gateway, and then choose 'Actions', 'Delete NAT gateway'.\n 5. When prompted, enter 'delete' and then choose 'Delete'.\n \n Note: The NAT gateway entry might remain visible in the Amazon VPC console for an hour after removal.","multiregional":false,"service":"Amazon Virtual Private Cloud"},"ecc-aws-186":{"article":"A public IPv4 address is an IP address that is reachable from the internet. If you launch your instance with a public IP address, then your EC2 instance is reachable from the internet. \nA private IPv4 address is an IP address that is not reachable from the internet. You can use private IPv4 addresses for communication between EC2 instances in the same VPC or in your connected private network.","impact":"Instances with a public IP address could potentially be compromised, and an attacker could gain anonymous access to the instance and other resources connected to this instance. This can lead to malicious activity that affects sensitive data.","report_fields":["InstanceId","OwnerId"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nTo remove a public IP address from your EC2 instance, do one of the following: \na) Re-launch the instance with the right network interface configuration.\nb) Disassociate an Elastic IP address from the instance.\n \nUsing method a) \nTo re-launch instance, perform the following actions: \n 1. Navigate to EC2 dashboard at https://console.aws.amazon.com/ec2/.\n 2. In the navigation panel, under 'Instances', click 'Instances'.\n 3. Select the instance that requires a Public IP removal.\n 4. Click the 'Actions' button from the dashboard top menu, select 'Image and templates' and click 'Create Image'.\n 5. In the 'Create Image' dialog box, type a unique name and description, and then choose 'Create Image'. By default, Amazon EC2 shuts down the instance, takes snapshots of any attached volumes, creates and registers the AMI, and then reboots the instance. Choose 'No reboot', if you don't want your instance to be shut down.\n Warning! If you choose 'No reboot', Amazon can't guarantee the file system integrity of the created image.\n 6. Click 'Create Image'.\n 7. After the process completes, you can use the new AMI to launch an instance.\n 8. To launch an instance from the AMI choose the 'Launch instances' button from the EC2 dashboard top menu.\n 9. Type a name for the instance. And add the required tags, copied from the source instance.\n 10. On the 'Application and OS Images (Amazon Machine Image)' section, click on the 'My AMIs' tab and select the AMI created at step 6.\n 11. On the 'Instance type', select the same instance type used by the source instance.\n 12. On the 'Key pair' section, select the same key pair as the source instance.\n 13. On the 'Network settings', at the right corner of this section click 'Edit'.\n 14. Select 'VPC' and 'Subnet' that attached to the source instance.\n 15. Select 'Disable' for the 'Auto-assign public IP'.\n 16. For the 'Firewall (security groups)' choose 'Select existing security group' and choose the security group attached to the source instance.\n 17. Make 'Advanced network configuration' if necessary.\n 18. Click 'Launch Instance'.\n 19. Click 'View All Instances' to return to the 'Instances' page. The new instance will have the same data and configuration (except the Public IP address) as the source instance.\n 20. Verify the instance, replace the source EC2 instance where needed.\n 21. Terminate the source instance in order to stop incurring charges for the resource.\n 22. You can delete created AMI at step 6.\n \nUsing method b) \nIf your EC2 instance is associated with an Elastic IP address, then your EC2 instance is reachable from the internet. You can disassociate an Elastic IP address from an instance or network interface at any time. To disassociate an Elastic IP address, perform the following actions: \n 1. Open the Amazon EC2 console at https://console.aws.amazon.com/ec2/. \n 2. In the navigation pane, choose 'Elastic IPs'. \n 3. Select the Elastic IP address to disassociate. \n 4. From 'Actions', choose 'Disassociate Elastic IP address'. \n 5. Choose 'Disassociate'.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-485":{"article":"This rule checks if the deployment group for EC2/On-Premises Compute Platform is configured with a minimum healthy hosts fleet percentage or host count greater than or equal to the input threshold.\nCodeDeploy tracks the health status of the deployment group's instances during the deployment process and uses the deployment's specified minimum number of healthy instances to determine whether to continue the deployment. This practice prevents the possibility the next deployment will fail, dropping the number of healthy instances below the specified minimum number.","impact":"Not setting minimum number of healthy instances to a required number can cause next deployment to fail, because the number of healthy instances below required to ensure application stability.","report_fields":["applicationName","deploymentGroupId","deploymentGroupName"],"remediation":"1. Navigate to CodeDeploy Applications https://console.aws.amazon.com/codesuite/codedeploy/applications.\n2. Select an application to which you need to update deployment group.\n3. Select 'Deployment groups' tab.\n4. Select deployment group that you want to modify.\n5. Click 'Edit' at top right corner.\n6. In the 'Deployment settings' section, select an existing deployment configuration that meets the requirements or create a new one.\n7. Click 'Save changes'.","multiregional":false,"service":"AWS CodeDeploy"},"ecc-aws-263":{"article":"It is recommended to delete unused virtual private gateways or associate them (use them) in order not to reach the limit of 5 VPGs","impact":"Keeping unused virtual private gateways eventually can prevent creating new virtual private gateways because of the limit.","report_fields":["VpnGatewayId"],"remediation":"1. Open the AWS Direct Connect console at https://console.aws.amazon.com/directconnect/v2/home. \n2. In the navigation pane, choose Virtual private gateways and then select the Virtual private gateway. \n3. Choose Edit. \n4. Choose Actions and then either Delete Virtual Private Gateway or Attach to VPC. \n4.1. Choose the VPC to associate, and then choose \"Yes, Attach\".","multiregional":false,"service":"Amazon Virtual Private Cloud"},"ecc-aws-552":{"article":"Identifying unused Amazon DynamoDB tables will lower the cost of your AWS bill. A table considered to be unused when it is older than 60 days and doesn't have on-demand mode enabled and has either 0 (zero) items in it or there weren't any Read/Write activity during 60 days. \nUnless there is a business need to retain unused tabled, you should remove them to maintain an accurate inventory of system components.","impact":"Keeping unused DynamoDB tables can result in escalating costs and cluttered AWS accounts.","report_fields":["TableArn"],"remediation":"To delete unused DynamoDB table:\n 1. Open the DynamoDB console at https://console.aws.amazon.com/dynamodb/.\n 2. On the console, select the specific region. \n 3. Select the reported table from the list of DynamoDB tables. \n 3. Click 'Delete' button to delete a table.\n\nTo enable On-demand capacity mode:\n 1. Open the DynamoDB console at https://console.aws.amazon.com/dynamodb/.\n 2. On the console, select the specific region. \n 3. Choose the table that you want to update.\n 4. Navigate to the 'Additional settings' tab.\n 4. In 'Read/write capacity' section, click 'Edit'.\n 5. For 'Capacity mode' select 'On-demand'.\n 6. Click 'Save changes'.","multiregional":false,"service":"Amazon DynamoDB"},"ecc-aws-032":{"article":"This policy identifies security group rules that allow inbound traffic to the MongoDB port (27017) from public internet. Allowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Unrestricted MongoDB Database access can increase opportunities for malicious activity such as unauthorized access, denial-of-service (DoS) attacks, Brute Force attacks, and loss of data.","report_fields":["GroupId","VpcId","OwnerId"],"remediation":"1. Login to the AWS Console.\n2. Go to Security Group. \n3. Go to the SG rules.\n4. Click on the reported Firewall rule.\n5. Click on Edit.\n6. Modify the rule.\n7. Click on Save.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-337":{"article":"If you do not specify a Customer managed key when using environment variables, Amazon Lambda uses its AWS owned key for data encryption on your environment.\nWhen you create and use your own KMS CMK customer-managed keys to protect the data on your environment, you obtain full control over who can use the CMK keys and access the encrypted data. The AWS KMS service allows you to create, rotate, disable, enable, and audit your Customer Master Keys (CMKs).\nIn addition, if you provide a Customer managed key, you must attach the policy statement for CloudWatch access. You must also create the Customer managed key-specific execution role.","impact":"Without a KMS CMK customer-managed key, you do not have full and granular control over who can access key that is used for encryption.","report_fields":["FunctionArn"],"remediation":"1. Login to the AWS Management Console and open the Amazon Lambda https://console.aws.amazon.com/lambda/ .\n2. In the navigation pane click on the 'Functions'.\n3. Click on the required function.\n4. Click on the 'Configuration' and then 'Environment variables'.\n5. Click 'Edit'.\n6. Under 'Encryption configuration' choose 'Use a customer master key'.\n7. Select existing key or create a new one.\n8. CLick Save","multiregional":false,"service":"AWS Lambda"},"ecc-aws-439":{"article":"Enabling instance deletion protection is an additional layer of protection against accidental QLDB deletion or deletion by an unauthorized entity. \nWhile deletion protection is enabled, an QLDB cluster cannot be deleted. Before a deletion request can succeed, deletion protection must be disabled.","impact":"Accidentally deleted or deleted by an unauthorized entity, the QLDB cluster can result in data loss.","report_fields":["EnvironmentArn"],"remediation":"To enable termination protection:\n1. Open the AWS QLDB console at https://console.aws.amazon.com/qldb.\n2. Click on the required ledger.\n3. Enable termination protection.","multiregional":false,"service":"Amazon QLDB"},"ecc-aws-050":{"article":"Password policies are, in part, used to enforce password complexity requirements. IAM password policies can be used to ensure passwords are at least of a given length. \nIt is recommended that the password policy require a minimum password length of 14 characters.","impact":"Not using a password policy with minimum password length of 14 characters reduces security and account resiliency against brute-force attacks.","report_fields":["account_id","account_name"],"remediation":"1. Login to the AWS Console (with appropriate permissions to View Identity Access Management Account Settings). \n2. Go to IAM Service on the AWS Console.\n3. Click on Account Settings in the Left Pane.\n4. Set 'Minimum password length'to 14 or greater.\n5. Click on 'Apply password policy'.","multiregional":true,"service":"AWS Account"},"ecc-aws-406":{"article":"This policy identifies the Elasticache clusters that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["ARN"],"remediation":"1. Sign in to the AWS Management Console and open the Amazon ElastiCache console at https://console.aws.amazon.com/elasticache/.\n2. From the list in the upper-right corner, choose the AWS Region that you want to launch this cluster in.\n3. Choose Redis from the navigation pane.\n4. Choose required cluster.\n5. Open 'Tags' and click on 'Manage tags'.\n6. Add a tags.\n7. Save.","multiregional":false,"service":"Amazon ElastiCache"},"ecc-aws-230":{"article":"Amazon ECR image scanning helps in identifying software vulnerabilities in your container images. When Scan on Push security feature is enabled, your container images are automatically scanned after being pushed to your Amazon ECR repository. \nIf Scan on Push is disabled on your repository, then each image scan must be manually started to get scan results.","impact":"When scan on push is disabled then the image with potential vulnerabilities can be missed.","report_fields":["repositoryArn"],"remediation":"1. Open the Amazon ECR console at https://console.aws.amazon.com/ecr/\n2. In the navigation pane, choose repositories.\n3. Choose Repository and then choose Edit\n4. Under Image scan settings, enable Scan on push.\n5. Choose Save.","multiregional":false,"service":"Amazon Elastic Container Registry"},"ecc-aws-171":{"article":"The default host and port settings configure Kibana to run on localhost:5601.\nUnrestricted access (0.0.0.0/0) increases opportunities for malicious activity, such as unauthorized access, denial-of-service attacks, and loss of data.","impact":"Unrestricted access increases the opportunity for malicious activity such as unauthorized access, denial-of-service attacks, and loss of data.","report_fields":["GroupId","VpcId","OwnerId"],"remediation":"From Console: \n1. Login to AWS Management Console and open the EC2 console using https://console.aws.amazon.com/ec2/\n2. In the navigation pane, choose Security Groups.\n3. Select the security group to update, choose Actions, and then choose Edit inbound rules to remove an inbound rule or Edit outbound rules to remove an outbound rule.\n4. Choose the Delete button to the right of the rule to delete.\n5. Choose Preview changes, Confirm.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-267":{"article":"To help keep your data secure, Amazon ElastiCache and Amazon EC2 provide mechanisms to guard against unauthorized access of your data on the server. \nBy providing in-transit encryption capability, ElastiCache gives you a tool you can use to help protect your data when it is moving from one location to another.","impact":"Without enabled encryption in transit, ElastiCache cluster accepts a connection whether it uses SSL or not. This can lead to malicious activity such as man-in-the-middle attacks (MITM), intercepting, or manipulating network traffic.","report_fields":["ARN"],"remediation":"Enabling in-transit encryption for an existing Redis cluster using the AWS Management Console:\nStep 1: Set your Transit encryption mode to Preferred\n 1. Sign in to the AWS Management Console and open the Amazon ElastiCache console at https://console.aws.amazon.com/elasticache/.\n 2. Choose 'Redis clusters' from the ElastiCache 'Resources' listed on the navigation pane, present on the left hand.\n 3. Choose the Redis cluster you want to update.\n 4. Choose the 'Actions' dropdown, then choose 'Modify'.\n 5. Choose 'Enable' under 'Encryption in transit' in the 'Security' section.\n 6. Choose 'Preferred' as the 'Transit encryption mode'.\n 7. Choose 'Preview changes' and save your changes.\n\nAfter you migrate all your Redis clients to use encrypted connections:\nStep 2: Set your Transit encryption mode to Required\n 1. Sign in to the AWS Management Console and open the Amazon ElastiCache console at https://console.aws.amazon.com/elasticache/.\n 2. Choose 'Redis clusters' from the ElastiCache 'Resources' listed on the navigation pane, present on the left hand.\n 3. Choose the Redis cluster you want to update.\n 4. Choose the 'Actions' dropdown, then choose 'Modify'.\n 5. Choose 'Required' as the 'Transit encryption mode', in the 'Security' section.\n 6. Choose 'Preview changes' and save your changes.\n\nNote: Affected ElastiCache for Memcached cluster must be redeployed to mitigate the issue.\nTo create an ElastiCache for Memcached cluster with encryption in-transit enabled:\n1. Sign in to the AWS Management Console and open the ElastiCache console at https://console.aws.amazon.com/elasticache/.\n2. From the list in the upper-right corner, choose the AWS Region you want to launch this cluster in.\n3. Choose 'Memcached clusters' from the navigation pane.\n4. Choose 'Create Memcached cluster'.\n5. Complete the 'Cluster settings' section. \n6. Click 'Next'.\n7. On the 'Advanced settings' section, under 'Security', choose 'Enable' for 'Encryption in-transit'.\n8. Complete the 'Advanced settings' section. \n9. Click 'Next'.\n10. Review all your entries and choices, then go back and make any needed corrections. When you're ready, choose 'Create' to launch your cluster.","multiregional":false,"service":"Amazon ElastiCache"},"ecc-aws-031":{"article":"This policy identifies security group rules that allow inbound traffic to the Microsoft-DS port (445) from the public internet. Allowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Unrestricted access to 445 port can increase opportunities for malicious activity such as man-in-the-middle attacks (MITM), Denial of Service (DoS) attacks or the Windows Null Session Exploit.","report_fields":["GroupId","VpcId","OwnerId"],"remediation":"1. Login to the AWS Console.\n2. Go to Security Group.\n3. Go to the SG rules.\n4. Click on the reported Firewall rule.\n5. Click on Edit.\n6. Modify the rule.\n7. Click on Save.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-169":{"article":"Unrestricted access (0.0.0.0/0) increases opportunities for malicious activity, such as unauthorized access, denial-of-service attacks, and loss of data.","impact":"Unrestricted access increases the opportunity for malicious activity such as unauthorized access, denial-of-service attacks, and loss of data.","report_fields":["GroupId","VpcId","OwnerId"],"remediation":"From Console:\n1. Login to AWS Management Console and open the EC2 console using https://console.aws.amazon.com/ec2/\n2. In the navigation pane, choose Security Groups.\n3. Select the security group to update, choose Actions, and then choose Edit inbound rules to remove an inbound rule or Edit outbound rules to remove an outbound rule.\n4. Choose the Delete button to the right of the rule to delete.\n5. Choose Preview changes, Confirm.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-451":{"article":"AWS X-Ray makes it easy for developers to analyze the behavior of their distributed applications by providing request tracing, exception collection, and profiling capabilities.","impact":"Traditional debugging methods don't work so well for microservice based applications, in which there are multiple, independent components running on different services which makes it harder to analyze the behavior of applications.","report_fields":["EnvironmentArn"],"remediation":"1. Login to the AWS Management Console and open the Amazon Elastic Beanstalk console using https://console.aws.amazon.com/elasticbeanstalk/\n2. In the navigation pane, choose Environments, and then choose the name of your environment from the list.\n3. In the navigation pane, choose Configuration.\n4. In the Software configuration category, choose Edit.\n5. In the AWS X-Ray section, select X-Ray daemon.\n6. Choose Apply.","multiregional":false,"service":"AWS Elastic Beanstalk"},"ecc-aws-208":{"article":"Amazon Aurora-MySQL database engine logs (Audit, Error, General, SlowQuery) should be enabled and sent to CloudWatch.\nDatabase logging provides detailed records of requests made to RDS. Database logs can assist with security and access audits and can help to diagnose availability issues.","impact":"With disabled logs for the database, it is harder to analyze statistics, diagnose issues, detect different types of attacks, and retain data for regulatory or legal purposes.","report_fields":["DBInstanceArn"],"remediation":"Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n1. In the navigation pane, choose Databases.\n2. Choose the DB instance that you want to modify.\n3. Choose Modify.\n4. Under Log exports, choose Audit, Error, General, SlowQuery log types to start publishing to CloudWatch Logs.\n5. Log exports is available only for database engine versions that support publishing to CloudWatch Logs.\n6. Choose Continue. Then on the summary page, choose Modify.\n\nTo create a custom DB parameter group:\n1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. In the navigation pane, choose Parameter groups.\n3. Choose Create parameter group. The Create parameter group window appears.\n4. In the Parameter group family list, choose a DB parameter group family.\n5. In the Type list, choose DB Parameter Group.\n6. In Group name, enter the name of the new DB parameter group.\n7. In Description, enter a description for the new DB parameter group.\n8. Choose Create.\n\nTo enable and publish Aurora-MySQL logs to CloudWatch Logs from the AWS Management Console, set the following parameters in a custom DB Parameter Group:\n- general_log=1\n- slow_query_log=1\n- log_output = FILE\n\nTo apply a new DB parameter group or DB options group to an RDS DB instance:\n1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. In the navigation pane, choose Databases.\n3. Choose the DB instance that you want to modify.\n4. Choose Modify. The Modify DB Instance page appears.\n5. Under Database options, change the DB parameter group and DB options group as needed.\n6. When you finish you changes, choose Continue. Check the summary of modifications.\n7. (Optional) Choose Apply immediately to apply the changes immediately. Choosing this option can cause an outage in some cases. \n8. Choose Modify DB Instance to save your changes.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-038":{"article":"This policy identifies security group rules that allow inbound traffic to the SMTP port (25) from the public internet. Allowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Unrestricted SMTP access can increase opportunities for malicious activity such as spamming and Denial-of-Service (DoS) attacks.","report_fields":["GroupId","VpcId","OwnerId"],"remediation":"1. Login to the AWS Console.\n2. Go to Security Group.\n3. Go to the SG rules.\n4. Click on the reported Firewall rule.\n5. Click on Edit.\n6. Modify the rule.\n7. Click on Save.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-371":{"article":"Attached security groups to the primary elastic network interface (ENI) to manage ports and network communication should not be open to all communication. They should be restricted to what is required by WorkSpaces, the Organization and other services.","impact":"Unrestricted access increases the opportunity for malicious activity such as unauthorized access, denial-of-service attacks, and loss of data.","report_fields":["WorkspaceId"],"remediation":"Perform the steps below to remove inbound rules that allows all traffic from all IP addresses:\n1. Login to the WorkSpaces https://console.aws.amazon.com/workspaces/home#listworkspaces.\n2. Note an IP address of the non-compliant WorkSpace.\n3. Navigate to Directories in the left panel and note a VPC Id of the corresponding Directory.\n4. Navigate to Network interfaces https://console.aws.amazon.com/ec2/v2/home#NIC.\n5. Filter network interfaces based on VPC Id and IP address of WorkSpace.\n6. Select the network interface.\n7. View attached security groups and edit or delete security group inbound rules that are overly permissive and have:\n - Type: All traffic\n - Protocol: All\n - Source: '0.0.0.0/0' or '::/0'\n\nNote: Make sure that all the required ports are added to inbound rules so that connectivity to WorkSpaces is not impacted.","multiregional":false,"service":"Amazon WorkSpaces Family"},"ecc-aws-049":{"article":"Password policies are, in part, used to enforce password complexity requirements. IAM password policies can be used to ensure passwords are comprised of different character sets. \nIt is recommended that the password policy require at least one number.","impact":"Not using a password policy with at least one number reduces security and account resiliency against brute-force attacks.","report_fields":["account_id","account_name"],"remediation":"1. Login to the AWS Console (with appropriate permissions to View Identity Access Management Account Settings). \n2. Go to IAM Service on the AWS Console.\n3. Click on Account Settings in the Left Pane. \n4. Check 'Require at least one number' \n5. Click on 'Apply password policy'.","multiregional":true,"service":"AWS Account"},"ecc-aws-530":{"article":"HTTPS (TLS) can be used to help prevent potential attackers from using person-in-the-middle or similar attacks to eavesdrop on or manipulate network traffic. Only encrypted connections over HTTPS (TLS) should be allowed. Encrypting data in transit can affect performance. You should test your application with this feature to understand the performance profile and the impact of TLS.","impact":"Not using HTTPS for your CloudFront CDN distribution cannot guarantee that the traffic between the edge (cache) servers and the application viewers cannot be decrypted by malicious users in case they are able to intercept packets sent across the CDN distribution network.","report_fields":["ARN"],"remediation":"Perform the following for each distribution that failed the rule: \nOn the AWS CloudFront console, check the details for the distribution and ensure that the Viewer Protocol Policy is HTTPS Only. \nConfigure CloudFront to require HTTPS between viewers and CloudFront. \n1. Navigate to the the AWS console CloudFront dashboard.\n2. Select your distribution ID.\n3. Select the 'Behaviors' tab.\n4. Check the behavior you want to modify, then select Edit.\n5. Select 'HTTPS Only' or 'Redirect HTTP to HTTPS' for Viewer Protocol Policy.\n6. Select 'Yes', then select Edit.","multiregional":true,"service":"Amazon CloudFront"},"ecc-aws-407":{"article":"This policy identifies the Beanstalk that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["EnvironmentArn"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon Beanstalk at https://console.aws.amazon.com//elasticbeanstalk.\n2. Click on the required environment.\n3. Open 'Tags' and click on the 'Manage tags'.\n4. Add new tag and save.","multiregional":false,"service":"AWS Elastic Beanstalk"},"ecc-aws-340":{"article":"Amazon MQ is integrated with Amazon CloudWatch Logs, a service that monitors, stores, and accesses your log files from a variety of sources. For example, you can configure CloudWatch alarms to receive notifications of broker reboots or troubleshoot ActiveMQ broker configuration errors.","impact":"With disabled logs for the MQ brokers, it is harder to analyze statistics, diagnose issues, detect different types of attacks, and retain data for regulatory or legal purposes.","report_fields":["BrokerArn"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon MQ at https://console.aws.amazon.com/amazon-mq.\n2. Click on the required broker.\n3. Click 'Edit'.\n4. Under 'Logs' check 'General' and 'Audit' for ActiveMQ.\n4.1. Under 'Logs' check 'CloudWatch logs' for RabbitMQ.\n5. Schedule modification.","multiregional":false,"service":"Amazon MQ"},"ecc-aws-310":{"article":"Amazon Database Migration Service is a managed web service that you can use to migrate data from a source database to a target database. An AWS DMS replication instance initiates the connection between the two data stores, transfers the data and caches any changes that occur on the source data store at the initial data load. The DMS service releases engine version upgrades regularly to introduce new software features, bug fixes, security patches and performance improvements.","impact":"Without keeping the DMS up-to-date, it is possible to miss out on new software features, bug fixes, security patches, and performance improvements.","report_fields":["ReplicationInstanceArn","ReplicationInstanceIdentifier"],"remediation":"1. Sign in to the AWS Management Console.\n2. Navigate to the Database Migration Service (DMS) dashboard at https://console.aws.amazon.com/dms/.\n3. In the left navigation panel, choose Replication instances.\n4. Select the AWS DMS replication instance that you want to reconfigure.\n5. Click the Modify button from the dashboard top menu to access the resource configuration panel.\n6. On the Modify Replication Instance page, select latest engine version.\n7. Select Apply changes immediately to apply your changes immediately.\n8. Click Modify to apply the configuration changes.","multiregional":false,"service":"AWS Database Migration Service"},"ecc-aws-257":{"article":"Kerberos uses secret-key cryptography to provide strong authentication so that passwords or other credentials aren't sent over the network in an unencrypted format. \nThe ability to authenticate users and services with Kerberos not only allows you to secure your big data applications, but it also enables you to easily integrate Amazon EMR clusters with an Active Directory environment. \nWhen you use Kerberos authentication, Amazon EMR configures Kerberos for the applications, components, and subsystems that it installs on the cluster so that they are authenticated with each other.","impact":"Without Kerberos authentication, you are missing out on the opportunity to secure your big data applications. Passwords or other credentials are sent over the network in an unencrypted format.","report_fields":["ClusterArn"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nIn order to enable Kerberos authentication, you need to clone it and add a security configuration.\n\nTo create a security configuration:\n1. Open the AWS EMR console at https://console.aws.amazon.com/elasticmapreduce/.\n2. In the navigation pane, under 'EMR on EC2', select 'Security configurations'.\n3. Choose the 'Create' button to create the security configuration and specify the authentication parameters.\n4. Enter the name for the security configurations.\n5. In the 'Authentication' section, check the box 'Enable Kerberos authentication' and select the required configurations.\n6. Then select 'Create'.\n\nTo clone a cluster using the console:\n1. Open the AWS EMR console at https://console.aws.amazon.com/elasticmapreduce/.\n2. In the navigation pane, under EMR on EC2, choose Clusters.\n3. Select the cluster you want to clone.\n4. At the top of the Cluster Details page, click Clone.\n5. In the dialog box, choose Yes to include the steps from the original cluster in the cloned cluster. Choose No to clone the original cluster's configuration without including any of the steps.\n6. The Create Cluster page appears with a copy of the original cluster's configuration. Review the configuration, make any necessary changes, and on the Step 4 'Security', select the security configuration that you created earlier.\n7. Then click Create Cluster.","multiregional":false,"service":"Amazon EMR"},"ecc-aws-180":{"article":"CloudFront origin failover can increase availability. Origin failover automatically redirects traffic to a secondary origin if the primary origin is unavailable or if it returns specific HTTP response status codes.","impact":"Without origin failover configured, the availability of origin is at risk. On the other hand, with a configured origin failover, in the event of a primary origin failure, the content is automatically delivered from the secondary origin maintaining high distribution reliability.","report_fields":["ARN"],"remediation":"1. Sign in to the AWS Management Console and open the CloudFront console at https://console.aws.amazon.com/cloudfront/v3/home.\n2. Choose the distribution that you want to create the origin group for.\n3. Choose the Origins and Origin Groups tab.\n4. On the Origin and Origin Groups tab, choose Create Origin Group.\n5. Choose the origins for the origin group. After you add origins, use the arrows to set the priority that is, which origin is primary and which is secondary.\n6. Choose the HTTP status codes to use as failover criteria. You can choose any combination of the following status codes: 500, 502, 503, 504, 404, or 403. When CloudFront receives a response with one of the status codes that you specify, it fails over to the secondary origin.","multiregional":true,"service":"Amazon CloudFront"},"ecc-aws-196":{"article":"Private subnets allow you to limit access to deployed components, and to control security and routing of the system.","impact":"Unrestricted access increases the opportunity for malicious activity such as unauthorized access, denial-of-service attacks, and loss of data.","report_fields":["ClusterArn"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nDuring launch, you can control whether your instance in a default or non-default subnet is assigned a public IPv4 address.\nBy default, default subnets have this attribute set to true. Non-default subnets have the IPv4 public addressing attribute set to false, unless it was created by the Amazon EC2 launch instance wizard. In that case, the wizard sets the attribute to true.\nYou need to launch your cluster in a VPC with a private subnet that has the IPv4 public addressing attribute set to false.\nAfter launch, you cannot manually disassociate a public IPv4 address from your instance.\nTo remediate this finding, you need to create a new cluster in VPC private subnet. For information on how to launch a cluster in into a VPC private subnet, see Launch clusters into a VPC in the Amazon EMR Management Guide: https://docs.aws.amazon.com/emr/latest/ManagementGuide/emr-vpc-launching-job-flows.html.","multiregional":false,"service":"Amazon EMR"},"ecc-aws-251":{"article":"Amazon AppFlow always encrypts your data and sensitive information with AWS managed key during transit and at rest. Encryption for data at rest is currently available for Amazon S3 only. It is recommended to use a customer managed CMK, as it puts you in full control over your encrypted data.","impact":"Without KMS CMK customer-managed keys, you do not have full and granular control over who can access encrypted AppFlow data.","report_fields":["flowArn"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nTo encrypt Amazon AppFlow flows using customer-managed Customer Master Keys (CMKs), you have to re-create your AppFlow flows with the appropriate encryption type by performing the following operations: \n\nPart 1. Creating symmetric KMS keys\n1. Sign in to the AWS Management Console and open the AWS Key Management Service (AWS KMS) console at https://console.aws.amazon.com/kms.\n2. In the navigation pane, choose Customer managed keys.\n3. Choose Create key.\n4. To create a symmetric KMS key, for Key type choose Symmetric.\n5. Choose Next.\n6. Type an alias for the KMS key.\n7. (Optional) Type a description for the KMS key.\n8. Choose Next.\n9. (Optional) Type a tag key and an optional tag value.\n10. Choose Next.\n11. Select the IAM users and roles that can administer the KMS key.\n12. (Optional) To prevent the selected IAM users and roles from deleting this KMS key, in the Key deletion section at the bottom of the page, clear the Allow key administrators to delete this key check box.\n13. Choose Next.\n14. Select the IAM users and roles that can use the key in cryptographic operations\n15. (Optional) You can allow other AWS accounts to use this KMS key for cryptographic operations. To do so, in the Other AWS accounts section at the bottom of the page, choose Add another AWS account and enter the AWS account identification number of an external account. To add multiple external accounts, repeat this step.\n16. Choose Next.\n17. Review the key settings that you chose. You can still go back and change all settings.\n18. Choose Finish to create the KMS key.\nPart 2. Create a flow\n1. Open the Amazon AppFlow console at https://console.aws.amazon.com/appflow/.\n2. In the navigation panel, choose Flows.\n3. Click on the name of the flow that you want to re-create and note all the required configuration information.\n4. Navigate back to the Flows home page and click on the 'Create flow' button.\n5. On the Create flow page, for Flow details, enter a name and description for the flow.\n6. Choose Data encryption, Customize encryption settings and then select a previously created KMS CMK.\n7. Next, perform the actions, based on the configuration information collected at step 3 from the source flow at Part 1.\n8. When you are on the Step 5 Review and create, review the information for your flow. To change the information for a specific step, choose Edit. When you are finished, choose Create flow.","multiregional":false,"service":"Amazon AppFlow"},"ecc-aws-312":{"article":"The DMS service releases engine version upgrades regularly to introduce new software features, bug fixes, security patches and performance improvements.\nAuto minor version upgrade option allows to have minor engine upgrades applied automatically to the replication instance during the maintenance window or immediately if you choose the Apply changes immediately option.","impact":"With an automatic update disabled, you are missing updates that may contain new software features, bug fixes, security patches and performance improvements.","report_fields":["ReplicationInstanceArn","ReplicationInstanceIdentifier"],"remediation":"To update your Amazon DMS replication instances configuration in order to enable Auto Minor Version Upgrade, perform the following actions: \n1. Sign in to the AWS Management Console.\n2. Navigate to the Database Migration Service dashboard at https://console.aws.amazon.com/dms/.\n3. In the left navigation panel, choose Replication instances.\n4. Select the AWS DMS replication instance that you want to reconfigure.\n5. Click the Modify button from the dashboard top menu to access the resource configuration panel.\n6. On the Modify Replication Instance page, click the Maintenance tab and select the Auto minor version upgrade checkbox to enable the feature.\n7. Select Apply changes immediately to apply the changes right away. With this option any pending modifications will be asynchronously applied as fast as possible, regardless of the maintenance window setting for the selected instance. If Apply changes immediately checkbox is not selected, the changes will be applied automatically during the next scheduled maintenance window.\n8. Click Modify to apply the configuration changes.","multiregional":false,"service":"AWS Database Migration Service"},"ecc-aws-386":{"article":"This policy identifies the Security Group that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["GroupId","OwnerId","VpcId"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon VPC at https://console.aws.amazon.com/vpc.\n2. Click on the 'Security groups'.\n3. Click on the required Security Group.\n4. Open 'Tags' and click on the 'Manage tags'.\n5. Add tag and save.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-492":{"article":"Amazon ECR lifecycle policies provide more control over the lifecycle management of images in a private repository. A lifecycle policy contains one or more rules, where each rule defines an action for Amazon ECR. This provides a way to automate the cleaning up of your container images by expiring images based on age or count.","impact":"Without configuring lifecycle policy, you can unintentionally use outdated images in your repository. \nAdditionally, you pay for the amount of data you store in your public or private repositories, thus keeping unnecessary images will increase your monthly bill.","report_fields":["repositoryArn"],"remediation":"To create a lifecycle policy:\nImportant: It is considered best practice to create a lifecycle policy preview to ensure that the images affected by your lifecycle policy rules are what you intend. For more information, see https://docs.aws.amazon.com/AmazonECR/latest/userguide/lpp_creation.html.\n1. Open the Amazon ECR console at https://console.aws.amazon.com/ecr/.\n2. In the navigation pane, choose 'Repositories'.\n3. Choose repository and then choose 'Actions' and select 'Lifecycle policies'.\n4. On the repository lifecycle policy page, choose Create rule.\n a) For 'Rule priority', type a number for the rule priority.\n b) For 'Rule description', type a description for the lifecycle policy rule.\n c) For 'Image status', choose 'Tagged', 'Untagged', or 'Any'.\n d) If you specified 'Tagged' for 'Image status', then for 'Tag prefixes', you can optionally specify a list of image tags on which to take action with your lifecycle policy. If you specified 'Untagged', this field must be empty.\n e) For 'Match criteria', choose values for 'Since image pushed' or 'Image count more than' (if applicable).\n f) Choose 'Save'.\n5. Choose Save.","multiregional":false,"service":"Amazon Elastic Container Registry"},"ecc-aws-124":{"article":"When you define and use your own KMS CMK customer-managed keys to protect data and metadata in the EFS file systems, you gain full control over who can use these keys to access the data (including metadata). The AWS KMS service allows you to create, rotate, disable, and audit CMK encryption keys for your file systems.","impact":"Disabled encryption allows a user to get unauthorized access to sensitive data in EFS.","report_fields":["FileSystemArn","OwnerId"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n1. Open the Amazon Elastic File System console at https://console.aws.amazon.com/efs/.\n2. Choose 'Create file system' to open the file system creation wizard.\n2a. Configure file system access: choose your VPC, create your mount targets, and then choose 'Next Step'.\n2b. Configure optional settings: add any tags, choose your performance mode, check the box to encrypt your file system, and then choose 'Next Step'.\n2c. Review and create: review your settings and choose 'Create File System'.\n3. Check the 'Enable encryption' checkbox and choose the name of the AWS Key from the 'Select KMS master key' drop-down list to enable encryption using your own KMS CMK key.\nIn order to encrypt the existing Amazon EFS with your own AWS KMS CMK, you need to create a new Amazon EFS and copy all the data from the existing Amazon EFS to the new one with encryption enabled.","multiregional":false,"service":"Amazon Elastic File System"},"ecc-aws-537":{"article":"It is recommended to remove elevated privileges from ECS task definitions. When the privilege parameter is true, the container is given elevated privileges on the host container instance (similar to the root user).","impact":"With privileged parameter set to 'true', the principle of least-privileges will be violated.","report_fields":["taskDefinitionArn"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nNote that when you update a task definition, it does not update running tasks that were launched from the previous task definition. To update a running task, you must redeploy the task with the new task definition.\n1. Open the Amazon ECS console at https://console.aws.amazon.com/ecs/.\n2. From the navigation bar, choose the Region that contains your task definition.\n3. In the navigation bar, choose 'Task definitions'. \n4. On the task definitions page, select the box to the left of the task definition to revise and choose 'Create new revision with JSON'. \n5. On the 'Create new revision' page. \n6. Change value for field 'privileged' to false or delete this field.\n7. Verify the information and choose 'Create'. \n7. If your task definition is used in a service, update your service with the updated task definition.","multiregional":false,"service":"Amazon Elastic Container Service"},"ecc-aws-333":{"article":"By default, Amazon FSx encrypts at rest all types of file systems with keys managed using AWS Key Management Service (AWS KMS). Data is automatically encrypted before being written to the file system, and automatically decrypted as it is read. This KMS key can be one of the two following types: AWS-managed KMS key or Customer-managed KMS key. Customer-managed KMS key is the most flexible KMS key to use, because you can configure its key policies and grants for multiple users or services.","impact":"Without AWS KMS Customer Master Keys (CMKs), you do not have full and granular control over who can use the encryption keys to access AWS FSx file system data.","report_fields":["ResourceARN"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nTo encrypt your Amazon FSx file system data using your own AWS KMS Customer Master Key, you have to re-create the non-compliant FSx file system with the required encryption configuration. Perform the following actions: \n1. Open the Amazon FSx console at https://console.aws.amazon.com/fsx/.\n2. On the dashboard, choose Create file system to start the file system creation wizard.\n3. On the Select file system type page, choose the same type of file system as non-compliant FSx file system that you want to re-create.\n4. Within Encryption section, select the alias of the KMS Customer Master Key (CMK) instead the default one from the Encryption key dropdown list. \n5. Perform all the necessary configurations.\n6. Review the file system configuration. For your reference, note which file system settings you can modify after the file system is created.\n7. Choose Create file system.","multiregional":false,"service":"Amazon FSx"},"ecc-aws-096":{"article":"Real-time monitoring of API calls can be achieved by directing CloudTrail Logs to CloudWatch Logs and establishing corresponding metric filters and alarms. Security Groups are a stateful packet filter that controls ingress and egress traffic within a VPC. It is recommended that a metric filter and alarm be established for detecting changes made to Security Groups.","impact":"Lack of monitoring and logging of security group changes can result in insufficient response time to detect accidental or intentional modifications that may lead to unrestricted or unauthorized access.","report_fields":["account_id","account_name"],"remediation":"Perform the following to setup the metric filter, alarm, SNS topic, subscription and trail using the AWS CLI: \n1. Create a log group.\naws logs create-log-group --log-group-name \n2. Create a log stream.\naws logs create-log-stream --log-group-name --log-stream-name \n3. Create a metric filter based on provided filter pattern which checks for unauthorized API calls and the taken from step 1.\naws logs put-metric-filter --log-group-name --filter-name `` --metric-transformations metricName=``,metricNamespace=,metricValue='1' --filter-pattern '{{($.eventName = AuthorizeSecurityGroupIngress) || ($.eventName = AuthorizeSecurityGroupEgress) || ($.eventName = RevokeSecurityGroupIngress) || ($.eventName = RevokeSecurityGroupEgress) || ($.eventName = CreateSecurityGroup) || ($.eventName = DeleteSecurityGroup)}}'\n4. Create a topic to which notifications will be published.\naws sns create-topic --name \n5. Subscribe an endpoint to an Amazon SNS topic. If the endpoint type is HTTP/S or email, or if the endpoint and the topic are not in the same Amazon Web Services account, the endpoint owner must run the ConfirmSubscription action to confirm the subscription.\naws sns subscribe --topic-arn --protocol email --notification-endpoint \n6. Amazon SNS will send a subscription confirmation message to the endpoint. Confirm subscription to topic, by visiting the link in an email that you specified as notification endpoint.\n7. Create an alarm that is associated with the CloudWatch Logs Metric Filter created in step 3 and an SNS topic created in step 4. \naws cloudwatch put-metric-alarm --alarm-name `` --metric-name `` --statistic Sum --period 300 --threshold 1 --comparison-operator GreaterThanOrEqualToThreshold --evaluation-periods 1 --namespace --alarm-actions \n8. Create an S3 bucket to deliver log files to:\naws s3api create-bucket --bucket \n9. To deliver log files to an S3 bucket, CloudTrail must have the required permissions. The following policy allows CloudTrail to write log files to the bucket from supported regions. Replace myBucketName, [optionalPrefix]/, myAccountID, region, and trailName with the appropriate values for your configuration. \nCreate a file 'policy.json' with the following policy.\n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"AWSCloudTrailAclCheck20150319\",\n \"Effect\": \"Allow\",\n \"Principal\": {\"Service\": \"cloudtrail.amazonaws.com\"},\n \"Action\": \"s3:GetBucketAcl\",\n \"Resource\": \"arn:aws:s3:::\"\n },\n {\n \"Sid\": \"AWSCloudTrailWrite20150319\",\n \"Effect\": \"Allow\",\n \"Principal\": {\"Service\": \"cloudtrail.amazonaws.com\"},\n \"Action\": \"s3:PutObject\",\n \"Resource\": \"arn:aws:s3:::/[optionalPrefix]/AWSLogs//*\",\n \"Condition\": {\n \"StringEquals\": {\n \"s3:x-amz-acl\": \"bucket-owner-full-control\",\n \"aws:SourceArn\": \"arn:aws:cloudtrail:::trail/\"\n }\n }\n }\n ]\n}}\n10. Apply the Amazon S3 bucket policy to the Amazon S3 bucket created in step 8.\naws s3api put-bucket-policy --bucket --policy file://.json\n11. Create a role for CloudTrail that enables it to send events to the CloudWatch Logs log group. To create the JSON file that will contain the policy document, open a text editor and save the following policy contents in a file called 'assume_role_policy_document.json'. \n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Principal\": {\n \"Service\": \"cloudtrail.amazonaws.com\"\n },\n \"Action\": \"sts:AssumeRole\"\n }\n ]\n}}\n12. Create a role.\naws iam create-role --role-name --assume-role-policy-document file://.json\n13. Create the following role policy document for CloudTrail. This document grants CloudTrail the permissions required to create a CloudWatch Logs log stream in the log group you specify and to deliver CloudTrail events to that log stream. Save the policy document in a file called role-policy-document.json. Replace region, accountID, log_group_name, with the appropriate values for your configuration. \n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n\n \"Sid\": \"AWSCloudTrailCreateLogStream2014110\",\n \"Effect\": \"Allow\",\n \"Action\": [\n \"logs:CreateLogStream\"\n ],\n \"Resource\": [\n \"arn:aws:logs:::log-group::log-stream:_CloudTrail_*\"\n ]\n\n },\n {\n \"Sid\": \"AWSCloudTrailPutLogEvents20141101\",\n \"Effect\": \"Allow\",\n \"Action\": [\n \"logs:PutLogEvents\"\n ],\n \"Resource\": [\n \"arn:aws:logs:::log-group::log-stream:_CloudTrail_*\"\n ]\n }\n ]\n}}\n14. Run the following command to apply the policy to the role.\naws iam put-role-policy --role-name --policy-name cloudtrail-policy --policy-document file://.json\n15. Create a trail that specifies the settings for delivery of log data to an Amazon S3 bucket at is associated with the S3 bucket created in step 8, CloudWatch log group created in step 1 and IAM role created in step 12. \naws cloudtrail create-trail --include-global-service-events --is-multi-region-trail --name --s3-bucket-name --cloud-watch-logs-log-group-arn --cloud-watch-logs-role-arn \n16. Start the recording of Amazon Web Services API calls and log file delivery for a trail. For a trail that is enabled in all regions, this operation must be called from the region in which the trail was created.\naws cloudtrail start-logging --name ","multiregional":false,"service":"AWS Account"},"ecc-aws-170":{"article":"Port 5500 is designated as fcp-addr-srvr1 by the IANA. It is used by SecurID. It is also used by Oracle for the Oracle Enterprise Manager Web Console. \nUnrestricted access (0.0.0.0/0) increases opportunities for malicious activity, such as unauthorized access, denial-of-service attacks, and loss of data.","impact":"Unrestricted access increases the opportunity for malicious activity such as unauthorized access, denial-of-service attacks, and loss of data.","report_fields":["GroupId","VpcId","OwnerId"],"remediation":"From Console: \n1. Login to AWS Management Console and open the EC2 console using https://console.aws.amazon.com/ec2/\n2. In the navigation pane, choose Security Groups.\n3. Select the security group to update, choose Actions, and then choose Edit inbound rules to remove an inbound rule or Edit outbound rules to remove an outbound rule.\n4. Choose the Delete button to the right of the rule to delete.\n5. Choose Preview changes, Confirm.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-481":{"article":"By default, Docker containers do not allow access to any devices. Privileged mode grants a build project's Docker container access to all devices. Setting privilegedMode with value true enables running the Docker daemon inside a Docker container. The Docker daemon listens for Docker API requests and manages Docker objects such as images, containers, networks, and volumes.","impact":"If privileged mode is set to true it can lead to unintended access to Docker APIs as well as the container\u2019s underlying hardware as unintended access to privilegedMode may risk malicious tampering or deletion of critical resources.","report_fields":["arn"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n1. Open the CodeBuild console at https://console.aws.amazon.com/codebuild/.\n2. Choose 'Create build project'.\n3. Under the 'Environment' make sure 'Privileged' is unchecked.\n4. Create build project.","multiregional":false,"service":"AWS CodeBuild"},"ecc-aws-428":{"article":"This policy identifies the RDS snapshots that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["DBSnapshotArn","DBInstanceIdentifier"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon RDS at https://console.aws.amazon.com/rds.\n2. Click on the snapshots.\n3. Click on the required snapshot.\n4. Add new tag and save.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-036":{"article":"This policy identifies security group rules that allow inbound traffic to the POP3 port (110) from the public internet. Allowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Unrestricted NetBIOS access can increase opportunities for malicious activity such as man-in-the-middle attacks (MITM), Denial of Service (DoS) attacks, and possibly execute arbitrary code. Also, remote attackers may gain root privileges after POP3 authentication.","report_fields":["GroupId","VpcId","OwnerId"],"remediation":"1. Login to the AWS Console.\n2. Go to Security Group.\n3. Go to the SG rules.\n4. Click on the reported Firewall rule.\n5. Click on Edit.\n6. Modify the rule.\n7. Click on Save.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-314":{"article":"The 'audit_sys_operations' setting provides auditing of all user activities conducted under the SYSOPER and SYSDBA accounts. The setting should be set to TRUE to enable this auditing.","impact":"If 'audit_sys_operations' is FALSE, there will be no trail for forensic analysis of SYSOPER and SYSDBA accounts, and discovering the cause of problems or the source of attacks may become more difficult or impossible.","report_fields":["DBInstanceArn"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/\n2. In the navigation pane, choose Parameter groups.\n3. Choose the required parameter group\n4. Choose Edit parameters.\n5. Under Parameters, in the search bar, type \"audit_sys_operations\".\n6. Choose TRUE\n7. Choose Save changes.\n8. Restart instance","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-017":{"article":"AWS IAM users can access AWS resources using different types of credentials, such as passwords or access keys. It is recommended that all the credentials that have not been used or rotated for 45 or more days be removed or deactivated.\nRoot account is excluded in the audit since the root account should not be used for day to day business and would likely be unused for more than 45 days.","impact":"If credentials are unused or unrotated for more than 45 days, it increases the likelihood of an old, lost, or stolen access key being used on a compromised or terminated account.","report_fields":["Arn"],"remediation":"Perform the following to manage Unused Password (IAM user console access):\n1. Login to the AWS Management Console.\n2. Click on 'Services'.\n3. Click on 'IAM'.\n4. Click on 'Users'.\n5. Click on 'Security Credentials'.\n6. Select user whose 'Console last sign-in' is greater than 45 days.\n7. Click on 'Security Credentials'.\n8. In section 'Sign-in credentials', chose 'Console password' and click on 'Manage'.\n9. Select 'Disable' under Console Access.\n10.Click on 'Apply'.\n\nPerform the following to deactivate Access Keys:\n1. Login to the AWS Management Console.\n2. Click 'Services'.\n3. Click 'IAM'.\n4. Click on 'Users'.\n5. Click on 'Security Credentials'.\n6. Select any access keys that are over 45 days old and that have been used and click on 'Make Inactive'.\n7. Select any access keys that are over 45 days old and that have not been used and click the 'X' to Delete.","multiregional":true,"service":"AWS Identity and Access Management"},"ecc-aws-008":{"article":"Ensure that SSL/TLS certificates stored in AWS IAM are renewed one month before expiry.","impact":"SSL/TLS certificates not renewed prior to their expiration date become invalid, and the communication between a client and an AWS resource that implements the certificates is no longer secure.","report_fields":["Arn"],"remediation":"From AWS CLI: \naws iam upload-server-certificate --server-certificate-name ExampleCertificate --certificate-body file://Certificate.pem --certificate-chain file://CertificateChain.pem --private-key file://PrivateKey.pem --tags '{\"Key\": \"ExampleKey\", \"Value\": \"ExampleValue\"}'","multiregional":true,"service":"AWS Identity and Access Management"},"ecc-aws-521":{"article":"Enabling this option reduces security attack vectors since the container instance's filesystem cannot be tampered with or written to unless it has explicit read-write permissions on its filesystem folder and directories.","impact":"If 'readonlyRootFilesystem' is not enabled, attacker can tamper or write to the filesystem which can lead to lose of integrity and data.","report_fields":["taskDefinitionArn"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\nNote that when you update a task definition, it does not update running tasks that were launched from the previous task definition. To update a running task, you must redeploy the task with the new task definition.\n1. Open the Amazon ECS console at https://console.aws.amazon.com/ecs/.\n2. In the left navigation pane, choose Task Definitions.\n3. Select the container definition that needs to be updated.\n4. Choose Edit Container. For Storage and Logging, select Read only root file system.\n5. Choose Update at the bottom of the Edit Container tab.\n6. Choose Create.","multiregional":false,"service":"Amazon Elastic Container Service"},"ecc-aws-335":{"article":"AWS X-Ray helps to visualize the components of your application, identify performance bottlenecks, and troubleshoot requests that resulted in an error.","impact":"If active tracing is not enabled it will be harder to debug and operate your functions as the X-Ray service support allows you to rapidly diagnose errors, identify bottlenecks, slowdowns and timeouts, by breaking down the latency for your Lambda functions.","report_fields":["FunctionArn"],"remediation":"To turn on active tracing:\n 1. Open the AWS Lambda console at https://console.aws.amazon.com/lambda/\n 2. Navigate to Functions and then select your Lambda function.\n 3. Choose 'Configuration' and then choose 'Monitoring and operations' tools.\n 4. Choose Edit.\n 5. Under 'AWS X-Ray', toggle on 'Active tracing'.\n 6. Choose Save.","multiregional":false,"service":"AWS Lambda"},"ecc-aws-014":{"article":"It is recommended to use HTTPS instead of HTTP to encrypt the communication between the application clients and the application load balancer.","impact":"An HTTP protocol is not a secure method of transmitting data. Any person monitoring the Internet traffic can see unencrypted data, which leads to a breach of confidentiality.","report_fields":["LoadBalancerArn"],"remediation":"1. Sign in to the AWS console. \n2. Select the region in which the alert is generated from the region drop-down list. \n3. Navigate to the EC2 dashboard.\n4. Select 'Load Balancers' (Left Panel). \n5. Select the reported ELB. \n6. Select the 'Listeners' tab. \n7. 'Edit' the 'Listener ID' rule that uses HTTP. \n8. Select 'HTTPS' and other options in 'Protocol:port'.","multiregional":false,"service":"Amazon Elastic Load Balancing"},"ecc-aws-139":{"article":"Enable IAM Access analyzer for IAM policies about all resources in each region. IAM Access Analyzer is a technology introduced at AWS reinvent 2019. After the Analyzer is enabled in IAM, scan results are displayed on the console showing the accessible resources. \nScans show resources that other accounts and federated users can access, such as KMS keys and IAM roles. So the results allow you to determine if an unintended user is allowed, making it easier for administrators to monitor least privileges access. Access Analyzer analyzes only policies that are applied to resources in the same AWS Region.","impact":"With a disabled AWS IAM Access Analyzer, it will be harder to identify unintended access to AWS resources and data that poses a security risk.","report_fields":["account_id","account_name"],"remediation":"1. Open the IAM console at https://console.aws.amazon.com/iam/. \n2. Choose Access analyzer. \n3. Choose Create analyzer. \n4. On the Create analyzer page, confirm that the Region displayed is the Region where you want to enable Access Analyzer.\n5. Enter a name for the analyzer (Optional as it will generate a name automatically).\n6. Add any tags that you want to apply to the analyzer (Optional). \n7. Choose Create Analyzer. \n8. Repeat these step for each active region.\nFrom Command Line:\n'aws accessanalyzer create-analyzer --analyzer-name --type '","multiregional":false,"service":"AWS Account"},"ecc-aws-090":{"article":"RDS snapshots should prohibit public access. Also ensure that access to the snapshot and permission to change Amazon RDS configuration is restricted to authorized principals only.","impact":"Publicly accessible RDS snapshot may result in unintended data exposure of RDS instance.","report_fields":["DBSnapshotArn","DBInstanceIdentifier"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. Navigate to Snapshots and then select the public Snapshot you want to modify.\n3. From the Actions list, choose Share Snapshots. \n4. From DB snapshot visibility, choose Private. \n5. Under DB snapshot visibility, select For all. \n6. Choose Save.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-355":{"article":"If you do not specify a Customer managed key when creating your environment, Amazon MWAA uses its AWS owned key for data encryption on your environment.\nWhen you create and use your own KMS CMK customer-managed keys to protect the data on your environment, you obtain full control over who can use the CMK keys and access the encrypted data. The AWS KMS service allows you to create, rotate, disable, enable, and audit your Customer Master Keys (CMKs).\nIn addition, if you provide a Customer managed key, you must attach the policy statement for CloudWatch access. You must also create the Customer managed key-specific execution role.","impact":"Without a KMS CMK customer-managed key, you do not have full and granular control over who can access key that is used for encryption.","report_fields":["ClusterIdentifier"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nAWS redshift instance Encryption of data at rest can only be enabled at the time of the file system creation. To resolve this issue, create a new instance with encryption enabled, migrate the file data from the reported Redshift instances to this new instance, and then delete the original instances. \n1. Sign in to the AWS Admin Console and Access the Redshift service. \n2. Click on the identified Redshift cluster and take a snapshot of it. \n3. Create a new Redshift cluster (now with 'Encryption' set to 'Use key from current account' during creation time) and use the snapshot to populate (restore) the new cluster. \n4. Once the new cluster is populated, delete the older cluster (without encryption).","multiregional":false,"service":"Amazon Redshift"},"ecc-aws-323":{"article":"The _trace_files_public parameter is used to make trace files used for debugging database applications and events available to all database users.","impact":"Making the file world readable means anyone can read the instance's trace file, which could contain sensitive information about instance operations.","report_fields":["DBInstanceArn"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. In the navigation pane, choose Parameter groups.\n3. Choose the required parameter group.\n4. Choose Edit parameters.\n5. Under Parameters, in the search bar, type '_trace_files_public'.\n6. Choose FALSE\n7. Choose Save changes.\n8. Restart instance","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-432":{"article":"This policy identifies the SQS that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["QueueArn"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon SQS at https://console.aws.amazon.com/sqs/v2.\n2. Click on the required queue.\n3. Open 'Tagging' and click on the 'Edit'.\n4. Add new tag and save.","multiregional":false,"service":"Amazon Simple Queue Service"},"ecc-aws-490":{"article":"Choose the desired HTTP PUT response hop limit for instance metadata requests. The larger the number, the further instance metadata requests can travel.","impact":"Not setting desired HTTP PUT response hop limit you can't control how far or close metadata can travel.","report_fields":["InstanceId","OwnerId"],"remediation":"To change token hop limit use AWS CLI:\naws ec2 modify-instance-metadata-options --instance-id --http-put-response-hop-limit --http-endpoint enabled","multiregional":false,"service":"Amazon EC2"},"ecc-aws-143":{"article":"S3 object-level API operations such as GetObject, DeleteObject, and PutObject are called data events. By default, CloudTrail trails do not log data events and so it is recommended to enable Object-level logging for write S3 buckets.","impact":"With disabled object-level logging for WRITE access, you are missing the opportunity to perform comprehensive security analysis, monitor specific patterns of user behavior in AWS account, or take immediate actions on any object-level API activity within S3 Buckets using Amazon CloudWatch Events. This can lead to undetected write or delete requests within the S3 bucket with sensitive data.","report_fields":["account_id","account_name"],"remediation":"From Console:\n1. Sign in to the AWS Management Console and open the CloudTrail console at https://console.aws.amazon.com/cloudtrail/.\n2. On the CloudTrail service home page, the Trails page, or the Trails section of the Dashboard page, choose 'Create trail'.\n3. On the 'Create Trail' page, for Trail name, enter a name for your trail. Leave all other settings at their default.\n4. For Storage location, you can choose to create an S3 bucket or use an existing one. If you choose to use an existing bucket, browse to select it.\n5. Under 'Log file SSE-KMS encryption', clear the 'Enabled' checkbox.\n6. On Choose log events, clear the Management events checkbox and select Data events. By default, basic event selectors log all the read/write events for all the selected S3 buckets.\n7. Click on 'Next'.\n8. In 'Events' section select 'Data events'.\n9. In 'Data events' section, for 'Data event type' select 'S3'.\n10. For 'Log selector template' select 'Log all events'.\n11. Choose Next.\n12. On the 'Review and create' page, review your selections, and then choose 'Create trail'.\n\nFrom Command Line:\n1. To enable object-level data events logging for S3 buckets within your AWS account, run 'put-event-selectors' command using the name of the trail that you want to reconfigure as identifier:\naws cloudtrail put-event-selectors --region --trail-name --event-selectors '[{ \"ReadWriteType\": \"WriteOnly\", \"IncludeManagementEvents\":true, \"DataResources\": [{ \"Type\": \"AWS::S3::Object\", \"Values\": [\"arn:aws:s3\"] }] }]'\n2. The command output will be the object-level event trail configuration.","multiregional":false,"service":"AWS CloudTrail"},"ecc-aws-437":{"article":"Object Lock is an Amazon S3 feature that blocks object version deletion during a user-defined retention period, to enforce retention policies as an additional layer of data protection and/or for strict regulatory compliance. The feature provides two ways to manage object retention: retention periods and legal holds. A retention period specifies a fixed time frame during which an S3 object remains locked, meaning that it can't be overwritten or deleted. A legal hold implements the same protection as a retention period, but without an expiration date. Instead, a legal hold remains active until you explicitly remove it.","impact":"Not using S3 buckets with Object Lock cannot guarantee data integrity as the files stored within these buckets can be accidentally or intentionally deleted.","report_fields":["Name"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n1. Login to the AWS Console.\n2. Find the S3 service.\n3. Create bucket.\n4. Inside \"Advances settings\" enable \"Object Lock\".\n5. Open recently created bucket.\n6. Click on the \"Properties\".\n7. Under the \"Object Lock\" click \"Edit\".\n8. Enable \"Default retention\".\n9. Choose required retention mode.\n10. Set required retention period.","multiregional":true,"service":"Amazon S3"},"ecc-aws-455":{"article":"Enabling instance deletion protection is an additional layer of protection against accidental emr deletion or deletion by an unauthorized entity. \nWhile deletion protection is enabled, an EMR cluster cannot be deleted. Before a deletion request can succeed, deletion protection must be disabled.","impact":"Accidentally deleted or deleted by an unauthorized entity, the EMR cluster can result in data loss.","report_fields":["ClusterArn"],"remediation":"To enable termination protection:\n1. Open the AWS EMR console at https://console.aws.amazon.com/elasticmapreduce/.\n2. Click on the required cluster.\n3. Under 'Summary' enable 'Termination protection'.","multiregional":false,"service":"Amazon EMR"},"ecc-aws-227":{"article":"Use AWS Key Management Service (KMS) keys to provide envelope encryption of Kubernetes secrets stored in Amazon EKS. Implementing envelope encryption is considered a security best practice for applications that store sensitive data and is part of a defense in depth security strategy.","impact":"Disabled encryption can lead to unauthorized access to sensitive and confidential data and violate compliance requirements for data-at-rest encryption within your organization.","report_fields":["arn"],"remediation":"Enabling secret encryption on an existing cluster:\n1. Open the Amazon EKS console at https://console.aws.amazon.com/eks/home#/clusters.\n2. Choose the cluster to which you want to add KMS encryption.\n3. Click on the Configuration tab.\n4. Scroll down to the Secrets encryption section and click on the Enable button.\n5. Select a key from the dropdown menu and click the Enable button. If no keys are listed, you must create one first.\n6. Click the Confirm button to use the chosen key.\n\nCreating an Amazon EKS cluster with secret encryption:\n1. Open the Amazon EKS console at https://console.aws.amazon.com/eks/.\n2. Choose Clusters.\n3. Choose Add Cluster.\n4. Under Secrets encryption, choose Enable envelope encryption.\n5. Choose existing KMS key or create new.\n6. Finish creating cluster.","multiregional":false,"service":"Amazon Elastic Kubernetes Service"},"ecc-aws-443":{"article":"You can use AWS WAF to protect your Appsync API from common web exploits, such as SQL injection and cross-site scripting (XSS) attacks. These could affect API availability and performance, compromise security, or consume excessive resources. \nFor example, you can create rules to allow or block requests from specified IP address ranges, requests from CIDR blocks, requests that originate from a specific country or region, requests that contain malicious SQL code, or requests that contain malicious script.","impact":"Not enabling Appsync API WAF integration could affect API availability and performance, compromise security, or consume excessive resources.","report_fields":["arn","name"],"remediation":"To associate the web ACL with an AWS AppSync API in the AWS AppSync Console:\n 1. Sign in to the AWS Management Console and open the AppSync Console.\n 2. Choose the API that you want to associate with a web ACL.\n 3. In the navigation pane, choose Settings.\n 4. In the Web application firewall section, turn on Enable AWS WAF.\n 5. In the Web ACL dropdown list, choose the name of the web ACL to associate with your API.\n Note. If the Web ACL you need doesn't exist yet, choose Create WebACL. You will be redirected to the AWS WAF Console, where you need to create a Web ACL. Then return to the AWS Appsync console to associate the Appsync API with the created Web ACL.\n 6. Choose Save to associate the web ACL with your API.","multiregional":false,"service":"AWS AppSync"},"ecc-aws-356":{"article":"In order to keep data secure in transit 'require_ssl' parameter should be enabled between the clients (applications) and your warehouse clusters.","impact":"Without enforcing HTTPS connection, Redshift accepts a connection whether it uses SSL or not. This can lead to malicious activity such as man-in-the-middle attacks (MITM), intercepting, or manipulating network traffic.","report_fields":["ClusterIdentifier"],"remediation":"To change default parameter group:\n1. Open the Amazon Redshift console at https://console.aws.amazon.com/redshiftv2/.\n2. In the navigation pane, choose Provisioned clusters dashboard.\n3. Choose the required Cluster.\n4. Choose Properties.\n5. Under Database configurations, choose Edit, Edit parameter group.\n6. Choose existing parameter or create new parameter group.\n\nTo update parameter in existing non default parameter group:\n1. Open the Amazon Redshift console at https://console.aws.amazon.com/redshiftv2/.\n2. In the navigation pane, choose Provisioned clusters dashboard.\n3. Choose the required Cluster. \n4. Choose Properties.\n5. Click on Parameter group name.\n6. Click on Parameters.\n7. Click Edit Parameters\n8. Set 'require_ssl' to true.\n9. Choose Save.","multiregional":false,"service":"Amazon Redshift"},"ecc-aws-219":{"article":"Secrets Manager helps you improve the security posture of your organization. Secrets include database credentials, passwords, and third-party API keys. You can use Secrets Manager to store secrets centrally, encrypt secrets automatically, control access to secrets, and rotate secrets safely and automatically. \nSecrets Manager can rotate secrets. You can use rotation to replace long-term secrets with short-term ones. Rotating your secrets limits how long an unauthorized user can use a compromised secret. For this reason, you should rotate your secrets frequently. \nIn addition to configuring secrets to rotate automatically, you should ensure that those secrets rotate successfully based on the rotation schedule.","impact":"If secrets have not been successfully rotated, it can lead to unauthorized access to resources.","report_fields":["Name","ARN"],"remediation":"If the automatic rotation fails, then Secrets Manager might have encountered errors with the configuration.\nTo rotate secrets in Secrets Manager, you use a Lambda function that defines how to interact with the database or service that owns the secret.\nFor help on how to diagnose and fix common errors related to secrets rotation, see 'Troubleshooting AWS Secrets Manager rotation of secrets' in the AWS Secrets Manager User Guide:\nhttps://docs.aws.amazon.com/secretsmanager/latest/userguide/troubleshoot_rotation.html","multiregional":false,"service":"AWS Secrets Manager"},"ecc-aws-311":{"article":"Using Amazon KMS Customer Master Keys (CMKs) to protect data in SageMaker notebook instances gives full control over who can use the encryption keys to access SageMaker data. Amazon KMS service allows to easily create, rotate, disable and audit Customer Master Keys created for SageMaker notebook instances.","impact":"Without a KMS CMK customer-managed key, you do not have full and granular control over who can access key that is used for encryption.","report_fields":["NotebookInstanceArn"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\n1. Open the Sagemaker console at https://console.aws.amazon.com/sagemaker/\n2. In the navigation pane, choose Notebook instances.\n3. Choose Create Notebook instance.\n4. Under Permissions and encryption choose Encryption key.\n5. Choose Create Notebook instance.","multiregional":false,"service":"Amazon SageMaker"},"ecc-aws-053":{"article":"CloudTrail log file validation creates a digitally signed digest file containing a hash of each log that CloudTrail writes to S3. These digest files can be used to determine whether a log file was changed, deleted, or unchanged after CloudTrail delivered the log. It is recommended that file validation be enabled on all CloudTrails.","impact":"With disabled log file validation, you miss out on additional integrity checks of CloudTrail logs. CloudTrail log file validation is used to determine whether a log file was changed, deleted, or unchanged after CloudTrail delivered the log.","report_fields":["TrailARN"],"remediation":"Perform the following to enable log file validation on a given trail:\n1. Sign in to the AWS Management Console and open the IAM console at https://console.aws.amazon.com/cloudtrail.\n2. Click on 'Trails' on the left navigation panel.\n3. Click on target trail.\n4. Within the 'General details' section click 'Edit'.\n5. Under the 'Advanced settings' section.\n6. Check the enable box under 'Log file validation'.\n7. Click 'Save changes'.","multiregional":false,"service":"AWS CloudTrail"},"ecc-aws-290":{"article":"When you launch a WorkSpace, you can encrypt the root volume (C drive for Windows and / for Amazon Linux) and the user volume (D drive for Windows and /home for Amazon Linux). This ensures that the data stored at rest for WorkSpaces is encrypted.\nYou must encrypt a WorkSpace when it is launched. You cannot create a custom image from an encrypted WorkSpace and you cannot disable encryption once encryption is enabled for a WorkSpace.","impact":"Not encrypting data at rest can lead to unauthorized access to sensitive data.","report_fields":["WorkspaceId"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nPerform the following steps to encrypt WorkSpace volumes when creating WorkSpace:\n1. Login to the WorkSpaces console at https://console.aws.amazon.com/workspaces/.\n2. Click Launch WorkSpaces and complete the first three steps.\n3. For the WorkSpaces Configuration step, do the following: \n - Select the volumes to encrypt: Root Volume, User Volume, or both volumes. \n - For Encryption Key, select an AWS KMS CMK. The CMK that you select must be symmetric.\n4. Click Next Step.\n5. Click Launch WorkSpaces.\nNOTE: To encrypt existing AWS WorkSpaces data you must re-create the necessary WorkSpaces instances with the volumes encryption feature enabled as outlined above.","multiregional":false,"service":"Amazon WorkSpaces Family"},"ecc-aws-106":{"article":"Checks for ACM Certificates with wildcard domain names instead of single domain names. An ACM allows you to use wildcards (*) in the domain name to protect several sites in the same domain. With this type of certificate, there is a risk that if the private key of a certificate is compromised, then all domains and subdomains using the compromised certificate are potentially compromised. \nIt is recommended to use single domain name certificates instead of wildcard certificates to reduce these associated risks.","impact":"If a certificate private key is compromised, then all sites that use the compromised certificate are potentially affected.","report_fields":["CertificateArn","DomainName"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\nTo create a new certificate with a single domain:\n1. Sign in to the AWS console.\n2. On the console, select the specific region. \n3. Navigate to Certificate Manager.\n4. Perform the following steps on the 'Request a certificate' page, \na. Step 1 Choose the 'Add domain names' page, enter the fully qualified domain name in the 'Domain name' box, and then click on 'Next' \nb. Step 2 Choose the 'Select validation method' page, select the validation method and click on 'Review' \nc. Step 3 Choose the 'Review' page, review the domain name and validation method details, and then click on 'Confirm' \nd. Step 4 Choose the 'Validation' page, validate the certificate request based on the selected validation method, then click on 'Continue' The certificate status should change from 'Pending validation' to 'Issued'. Now, access your application's web server configuration and replace the wildcard certificate with the newly issued single domain name certificate. \nTo delete a wildcard certificate: \n1. Sign into the AWS console. \n2. On the console, select the specific region. \n3. Navigate to the Certificate Manager(ACM) service. \n4. Select the certificate. \n5. Under the 'Actions' drop-down list, click 'Delete'.\n6. In the 'Delete certificate' pop-up window, click on 'Delete'.","multiregional":false,"service":"AWS Certificate Manager"},"ecc-aws-125":{"article":"In order to protect sensitive data, AWS ElastiCache Redis clusters should be encrypted at rest. \nEncryption of data at rest prevents unauthorized access to your sensitive data stored on AWS ElastiCache Redis clusters and associated cache storage.","impact":"Disabled encryption allows a user to get unauthorized access to sensitive data in ElastiCache.","report_fields":["ARN"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\nAWS ElastiCache Redis cluster at-rest encryption can be set only at the time of the cluster creation. To fix this issue, create a new cluster with at-rest encryption, migrate all the required ElastiCache Redis cluster data from the unencrypted cluster to the new one, and then delete the old cluster.\nTo create a new ElastiCache Redis cluster with at-rest encryption set, perform the following:\n1. Sign in to the AWS Management Console and open the Amazon ElastiCache console at https://console.aws.amazon.com/elasticache/.\n2. From the list in the upper-right corner, choose the AWS Region that you want to launch this cluster in.\n3. Choose 'Redis clusters' from the navigation pane.\n5. Click on the 'Create Redis cluster' button.\n6. Complete 'Cluster settings' page and click 'Next'.\n7. At the 'Security' section, enable 'Encryption at rest'. And select a key that will be used to protect the key used to encrypt data at rest for this cluster.\n8. Click on 'Create' button to launch your new ElastiCache Redis cluster.\n\nEnabling at-rest encryption on an existing ElastiCache Redis cluster:\n1. Create a manual backup of the cluster:\n 1.1. Sign in to the AWS Management Console and open the Amazon ElastiCache console at https://console.aws.amazon.com/elasticache/.\n 1.2. From the list in the upper-right corner, choose the AWS Region that you want to launch this cluster in.\n 1.3. Choose 'Redis clusters' from the navigation pane.\n 1.4. Choose the cluster and choose 'Action', and then 'Backup'.\n 1.5. Make sure the cluster name is right and enter backup name. \n 1.6. Select 'Encryption key'. \n 1.7. Choose 'Create Backup'.\n2. Create a new cluster by restoring from the backup:\n 2.1. Sign in to the AWS Management Console and open the ElastiCache console at https://console.aws.amazon.com/elasticache/.\n 2.2. Choose 'Redis clusters' from the navigation pane.\n 2.3. Select 'Create Redis cluster'.\n 2.4. For 'Choose a cluster creation method', choose 'Restore from backups'.\n 2.5. For 'Source' select 'Amazon ElastiCache backups'. And select backup created on the previous step.\n 2.6. Complete other settings and click 'Next'.\n 2.7. At the 'Security' section, enable 'Encryption at rest'. And select a key that will be used to protect the key used to encrypt data at rest for this cluster.\n 2.8. Complete other settings and click 'Next'.\n 2.9. Review settings and click 'Create'.\n3. Update the endpoints in your application to the new cluster's endpoints.\n4. Delete the old cluster.","multiregional":false,"service":"Amazon ElastiCache"},"ecc-aws-162":{"article":"RDS event notifications use Amazon SNS to make you aware of changes in the availability or configuration of your RDS resources. These notifications allow for a rapid response.\nA notification for 'security group' event type should be enabled for all security groups and all event categories ('configuration change','failure').","impact":"Not enabling RDS security group event notifications can lead to slow or no response to a security group configuration change.","report_fields":["account_id","account_name"],"remediation":"To subscribe to RDS database security group event notifications:\n1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. In the navigation pane, choose Event subscriptions.\n3. Under Event subscriptions, choose Create event subscription.\n4. In the Create event subscription dialog, do the following.\n 4.1 For Name, enter a name for the event notification subscription.\n 4.2 For Send notifications to, choose an existing Amazon SNS ARN for an SNS topic.\n To use a new topic, choose create topic to enter the name of a topic and a list of recipients.\n 4.3 For Source type, choose Security groups.\n 4.4 Under Instances to include, select All security groups.\n 4.5 Under Event categories to include, select Specific event categories. The control also passes if you select All event categories.\n 4.6 Select configuration change and failure.\n 4.7 Choose Create.\n5. Confirm subscription to topic, by clicking or visiting the link in an email that you specified in the topic.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-058":{"article":"AWS provides a support center that can be used for incident notification and response, as well as for technical support and customer services. Create an IAM Role to allow authorized users to manage incidents with AWS Support.","impact":"By default, without AWSSupportAccess role, IAM users cannot access the Support Center. Without this role, they won't be able to access resources and perform actions, such as to open Support Center cases and use the AWS Support API.","report_fields":["account_id","account_name"],"remediation":"1. Create an IAM role for managing incidents with AWS: \n - Create a trust relationship policy document that allows you to manage AWS incidents and save it locally as /tmp/TrustPolicy.json: \n { \n \"Version\": \"2012-10-17\", \n \"Statement\": [ \n { \n \"Effect\": \"Allow\", \n \"Principal\": { \n \"AWS\": \"\" \n }, \n \"Action\": \"sts:AssumeRole\" \n } \n ] \n } \n2. Create an IAM role using the above trust policy: \n aws iam create-role --role-name --assume-role-policy-document file:///tmp/TrustPolicy.json \n3. Attach the 'AWSSupportAccess' managed policy to the created IAM role: \n aws iam attach-role-policy --policy-arn arn:aws:iam::aws:policy/AWSSupportAccess --role-name ","multiregional":true,"service":"AWS Account"},"ecc-aws-317":{"article":"A remote listener is a listener residing on one computer that redirects connections to a database instance on another computer.","impact":"Permitting a remote listener for connections to the database instance can allow for the potential spoofing of connections and that could compromise data confidentiality and integrity.","report_fields":["DBInstanceArn"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. In the navigation pane, choose Parameter groups.\n3. Choose the required parameter group.\n4. Choose Edit parameters.\n5. Under Parameters, in the search bar, type 'remote_listener'.\n6. Delete all values and leave this parameter empty.\n7. Choose Save changes.\n8. Restart instance","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-459":{"article":"Code signing for AWS Lambda helps to ensure that only trusted code runs in your Lambda functions. When you enable code signing for a function, Lambda checks every code deployment and verifies that the code package is signed by a trusted source.","impact":"Without a code signing it is possible to run altered code or code from untrusted publisher.","report_fields":["FunctionArn"],"remediation":"1. Sign in to the AWS Console and access the Lambda at https://console.aws.amazon.com/lambda.\n2. Click on the required lambda function.\n3. Go to 'Configuration' and click on the 'Code signing'.\n4. Click 'Edit'.\n5. Choose existing configuration or create a new one.\n6. Click Save.","multiregional":false,"service":"AWS Lambda"},"ecc-aws-427":{"article":"This policy identifies the RDS DB clusters do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["DBClusterArn"],"remediation":"1. Sign in to the AWS Management Console and open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. In the navigation panel, choose Databases, then choose the DB cluster that you want to modify.\n3. Choose the \"Tags\" tab.\n4. Add tags and save.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-119":{"article":"Use KMS customer-managed keys (CMK ) to protect Kinesis Streams and metadata. Using KMS CMK, you gain full control over who can use the keys to access AWS Kinesis data (including the system metadata). The AWS KMS service allows you to create, rotate, disable and audit CMK encryption keys.","impact":"Without KMS CMK customer-managed keys, you do not have full and granular control over who can access encrypted Kinesis streams data.","report_fields":["StreamARN"],"remediation":"1. Navigate to the AWS KMS console and select the region in which your Kinesis stream is located. \n2. In the Navigation pane, choose Customer Managed Keys and then choose Create Key.\n3. Follow the steps to create a key.\n4. Note the ARN for the key.\n5. Navigate to the Kinesis console, select your stream and enter the ARN for the key in the KMS Key ID field.","multiregional":false,"service":"Amazon Kinesis"},"ecc-aws-325":{"article":"Amazon Database Migration Service (DMS) replication instances are using Multi-AZ deployment configurations to provide High Availability (HA) through automatic failover to standby replicas in the event of a failure such as an Availability Zone (AZ) outage, an internal hardware or network outage, a software failure or in case of a planned maintenance session.","impact":"Disabled multiple Availability Zones for DMS instances threaten the availability of stored data.","report_fields":["ReplicationInstanceArn","ReplicationInstanceIdentifier"],"remediation":"To update your Amazon DMS replication instances configuration in order to enable Multi-AZ deployment, perform the following actions: \n1. Sign in to the AWS Management Console.\n2. Navigate to the Database Migration Service (DMS) dashboard at https://console.aws.amazon.com/dms/.\n3. In the left navigation panel, choose Replication instances.\n4. Select the AWS DMS replication instance that you want to reconfigure.\n5. Click the Modify button from the dashboard top menu to access the resource configuration panel.\n6. On the Modify Replication Instance page, select Yes from the Multi-AZ dropdown menu to enable the feature.\n7. Select Apply changes immediately to apply your changes immediately. With this option any pending modifications will be asynchronously applied, regardless of the maintenance window setting for the selected replication instance. If Apply changes immediately checkbox is not selected, your changes will be applied during the next scheduled maintenance window.\n8. Click Modify to apply the configuration changes.","multiregional":false,"service":"AWS Database Migration Service"},"ecc-aws-514":{"article":"Identify and deactivate any unnecessary IAM access keys as a security best practice. AWS allows you to assign maximum two active access keys.","impact":"Unnecessary AWS IAM access keys generate unnecessary management work in auditing and rotating IAM credentials.","report_fields":["Arn"],"remediation":"Perform the following to delete inactive access keys:\n1. Login to the AWS Management Console.\n2. Click on 'Services'.\n3. Click on 'IAM'.\n4. Click on 'Users'.\n5. Click on required user.\n6. Click on 'Security Credentials'.\n6. Delete inactive access keys.","multiregional":true,"service":"AWS Identity and Access Management"},"ecc-aws-266":{"article":"For any Redis cluster, you can enable automatic backups. When automatic backups are enabled, ElastiCache creates a backup of the cluster on a daily basis. There is no impact on the cluster and the change is immediate. Automatic backups can help guard against data loss. \nIn the event of a failure, you can create a new cluster, restoring your data from the most recent backup. The result is a warm-started cluster, preloaded with your data and ready for use.","impact":"Elasticache redis cluster without automatic backup and retention period not set at least to 7 days can result in data loss and the inability to recover it in the event of failure.","report_fields":["ARN"],"remediation":"Enabling automatic backups on an existing cluster:\n1. Sign in to the AWS Management Console and open the Amazon ElastiCache console at https://console.aws.amazon.com/elasticache/.\n2. From the list in the upper-right corner, choose the AWS Region with cluster.\n3. Choose 'Redis clusters' from the navigation pane.\n4. Choose existing cluster. \n5. Choose 'Actions', 'Modify'.\n6. Choose 'Enable automatic backups' and set 'Backup retention period' to at least 7 days.\n7. Complete the configurations and click 'Preview changes'. \n8. Review all your entries and choices, then go back and make any needed corrections. \n9. If you want to perform the update right away, choose 'Apply immediately'. If Apply immediately is not chosen, the update process is performed during the cluster's next maintenance window.\n10. When you're ready, choose 'Modify'.","multiregional":false,"service":"Amazon ElastiCache"},"ecc-aws-474":{"article":"A Classic Load Balancer can be set up to distribute incoming requests across Amazon EC2 instances in a single Availability Zone or multiple Availability Zones","impact":"A Classic Load Balancer that does not span multiple Availability Zones is unable to redirect traffic to targets in another Availability Zone if the sole configured Availability Zone becomes unavailable.","report_fields":["LoadBalancerArn"],"remediation":"1. Open the Amazon EC2 console at https://console.aws.amazon.com/ec2/.\n2. In the navigation pane, choose Load balancers. \n3. Choose an Classic Load Balancer. \n4. On the Instances tab, choose Edit Availability Zones.\n5. On the Add and Remove Availability Zones page, select the Availability Zone.\n6. Choose Save.","multiregional":false,"service":"Amazon Elastic Load Balancing"},"ecc-aws-182":{"article":"You can create an on-demand mode table instead of provisioned so that you don't have to manage any capacity settings for servers, storage, or throughput. DynamoDB instantly accommodates your workloads as they ramp up or down to any previously reached traffic level. If a workload\u2019s traffic level hits a new peak, DynamoDB adapts rapidly to accommodate the workload. \nOr, you can allow DynamoDB auto scaling to manage your table's throughput capacity. However, you still must provide initial settings for read and write capacity when you create the table. DynamoDB auto scaling uses these initial settings as a starting point, and then adjusts them dynamically in response to your application's requirements.","impact":"With provisioned capacity mode, it is harder to administer the DynamoDB table, this can affect DynamoDB data availability and it can increase DynamoDB costs.","report_fields":["TableArn"],"remediation":"To enable autoscaling:\n1. Open the DynamoDB console at https://console.aws.amazon.com/dynamodb/.\n2. Choose the table that you want to update.\n3. Navigate to the 'Additional settings' tab.\n4. In 'Read/write capacity' section, click 'Edit'.\n5. In the 'Table capacity' settings section, set 'Auto scaling' to 'On' for 'Read capacity' or 'Write capacity', or both. For each of these, set your desired scaling policy for the table and, optionally, all global secondary indexes of the table.\n - Minimum capacity units \u2014 Enter your lower boundary for the auto scaling range.\n - Maximum capacity units \u2014 Enter your upper boundary for the auto scaling range.\n - Target utilization \u2014 Enter your target utilization percentage for the table.\n6. When the settings are as you want them, choose 'Save changes'.\n\nTo enable On-demand capacity mode:\nTo enable autoscaling:\n1. Open the DynamoDB console at https://console.aws.amazon.com/dynamodb/.\n2. Choose the table that you want to update.\n3. Navigate to the 'Additional settings' tab.\n4. In 'Read/write capacity' section, click 'Edit'.\n5. For 'Capacity mode' select 'On-demand'.\n6. Click 'Save changes'.","multiregional":false,"service":"Amazon DynamoDB"},"ecc-aws-175":{"article":"Encrypt Amazon RDS instances at rest by enabling the encryption option for your Amazon RDS DB instance.\nWhen dealing with production databases that hold sensitive and critical data, it is highly recommended to implement encryption in order to protect your data from unauthorized access. With RDS encryption enabled, the data stored on the instance underlying storage, the automated backups, Read Replicas, and snapshots, become all encrypted.","impact":"Disabled encryption allows a user to get unauthorized access to sensitive data in RDS instances, backups, read replicas, and snapshots.","report_fields":["DBInstanceArn"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n \nEncrypting Existing AWS RDS Database:\n1. Login to the AWS Management Console.\n2. Navigate to RDS dashboard at https://console.aws.amazon.com/rds/.\n3. In the navigation panel, under RDS Dashboard, click Instances.\n4. Select the RDS database instance that you want to encrypt.\n5. Click Instance Actions button from the dashboard top menu and select Take Snapshot.\n6. On the Take DB Snapshot page, enter a name for the instance snapshot in the Snapshot Name field and click Take Snapshot (the backup process may take few minutes and depends on your instance storage size).\n7. Select the new created snapshot and click the Copy Snapshot button from the dashboard top menu.\n8. On the Make Copy of DB Snapshot page, perform the following:\n 8.1 In the New DB Snapshot Identifier field, enter a name for the new snapshot (copy).\n 8.2 Check Copy Tags so the new snapshot can have the same tags as the source snapshot.\n 8.3 Select Yes from the Enable Encryption dropdown list to enable encryption. You can choose to use the AWS default encryption key or your custom key (key ARN required) by selecting it from the Master Key dropdown list.\n9. Click Copy Snapshot to create an encrypted copy of the selected instance snapshot.\n10. Select the new snapshot copy (encrypted) and click Restore Snapshot button from the dashboard top menu. This will restore the encrypted snapshot to a new database instance.\n11. On the Restore DB Instance page, enter a unique name for the new database instance in the DB Instance Identifier field.\n12. Review the instance configuration details and click Restore DB Instance.\n13. As soon as the new instance provisioning process is completed (its status becomes available), you can update your application configuration to refer to the endpoint of the new (encrypted) database instance. Once the database endpoint is changed at your application level, you can remove the unencrypted instance.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-061":{"article":"AWS Key Management Service (KMS) allows customers to rotate the backing key which is the key material stored within the KMS. The backing key is tied to the key ID of the customer created Customer Master Key (CMK). It is the backing key that is used to perform cryptographic operations such as encryption and decryption.\nAutomated key rotation currently retains all prior backing keys so that decryption of encrypted data can be transparent. It is recommended that CMK key rotation be enabled for symmetric keys. Key rotation can not be enabled for any asymmetric CMK.","impact":"Disabled rotation of KMS keys increases the risk of key exposure. Data encrypted with a compromised key can be accessed by an unauthorized user.","report_fields":["KeyArn","AWSAccountId"],"remediation":"1. Sign in to the AWS Management Console and open the IAM console at https://console.aws.amazon.com/iam. \n2. In the left navigation pane, choose 'Encryption Keys'. \n3. Select a customer created master key (CMK) where 'Key spec = SYMMETRIC_DEFAULT'.\n4. Underneath the 'General configuration' panel open the tab 'Key rotation'. \n5. Check the 'Automatically rotate this KMS key every year' checkbox.","multiregional":false,"service":"AWS Key Management Service"},"ecc-aws-456":{"article":"Instance metadata is used to configure or manage the running instance. The IMDS provides access to temporary, frequently rotated credentials. These credentials remove the need to hard code or distribute sensitive credentials to instances manually or programmatically.\nVersion 2 of the IMDS adds new protections for the following types of vulnerabilities. These vulnerabilities could be used to try to access the IMDS:\n - Open website application firewalls;\n - Open reverse proxies;\n - Server-side request forgery (SSRF) vulnerabilities;\n - Open Layer 3 firewalls and network address translation (NAT).","impact":"Instances that use IMDSv1 are exposed to the following vulnerabilities:\n- Open website application firewalls;\n- Open reverse proxies;Server-side request forgery (SSRF) vulnerabilities;\n- Open Layer 3 firewalls and network address translation (NAT).","report_fields":["ClusterArn"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\nIn order to disable imdsv1 for existing AWS EMR clusters, you must create an EMR security configuration then clone clusters with the new security configuration. \nTo create a security configuration using the console:\n1. Open the Amazon EMR console at https://console.aws.amazon.com/elasticmapreduce/.\n2. In the navigation pane, choose Security Configurations, Create security configuration.\n3. Type a Name for the security configuration.\n4. Enable 'Configure EC2 instance metadata service'.\n5. Click on the 'Turn off IMDSv1 and only allow IMDSv2 on cluster'.\n6. Create security configuration.\n\nTo clone a cluster using the console:\n1. Open the AWS EMR console at https://console.aws.amazon.com/elasticmapreduce/.\n2. In the navigation pane, under EMR on EC2, choose Clusters.\n3. Select the cluster that you want to clone.\n4. At the top of the Cluster Details page, click Clone.\n5. In the dialog box, choose Yes to include the steps from the original cluster in the cloned cluster. Choose No to clone the original cluster's configuration without including any of the steps.\n6. The Create Cluster page appears with a copy of the original cluster's configuration. Review the configuration, make any necessary changes, and on the 'Step 4: Security' select the security configuration that you created earlier.\n7. Then click Create Cluster.","multiregional":false,"service":"Amazon EMR"},"ecc-aws-489":{"article":"In Amazon EC2, Detailed Monitoring enables a more rapid response to performance changes in underlying infrastructure. These performance changes could result in a lack of availability of the data.","impact":"Without Detailed Monitoring, it is not possible to quickly respond to performance changes in the underlying infrastructure. This can lead to data unavailability.","report_fields":["InstanceId","OwnerId"],"remediation":"To enable detailed monitoring: \n1. Open the Amazon EC2 console at https://console.aws.amazon.com/ec2/. \n2. In the navigation pane, under Instances, choose Instances. \n3. Open the instance, and then choose Monitoring. \n4. Click on the 'Manage detailed monitoring'.\n5. Enable detailed monitoring.\n6. Save changes","multiregional":false,"service":"Amazon EC2"},"ecc-aws-042":{"article":"As a best practice, use KMS key to encrypt the data in your storage bucket and ensure full control over your data.","impact":"Disabled S3 bucket encryption makes it impossible to protect S3 data at the bucket from attackers or unauthorized personnel.\nMoreover, S3-Managed Keys (SSE-S3) have several disadvantages compared to KMS keys (SSE-KMS).\nThere are separate permissions for the use of a KMS key that provides additional protection against unauthorized access to objects in S3. SSE-KMS also provides an audit trail that shows when the KMS key was used and by whom.","report_fields":["Name"],"remediation":"1. Login to the AWS Console.\n2. Go to S3.\n3. Select S3.\n4. Click on the properties.\n5. Click on the Default encryption.\n6. Choose AWS-KMS.","multiregional":true,"service":"Amazon S3"},"ecc-aws-418":{"article":"This policy identifies the Kinesis data stream that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["StreamARN"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon Kinesis at https://console.aws.amazon.com/kinesis.\n2. Click on 'Data streams'.\n3. Click on the required data stream.\n4. Open 'Configuration' and click on the 'Manage tags'.\n5. Add new tag and save.","multiregional":false,"service":"Amazon Kinesis"},"ecc-aws-502":{"article":"The RDS service releases engine version upgrades regularly to introduce new software features, bug fixes, security patches and performance improvements. Auto minor version upgrade option allows to have minor engine upgrades applied automatically to the replication instance during the maintenance window or immediately if you choose the Apply changes immediately option.","impact":"With an automatic update disabled, you are missing updates that may contain new software features, bug fixes, security patches and performance improvements.","report_fields":["DBInstanceArn"],"remediation":"To enable automatic minor version upgrade: \n1. Open the RDS console at https://console.aws.amazon.com/rds.\n2. In the navigation pane, choose Databases. \n3. Choose instance and press modify. \n4. Scroll to the Maintenance section. \n5. Enable 'auto minor version upgrade'.\n6. Save changes.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-560":{"article":"Identifying unused Amazon SNS topics will reduce the costs of your AWS bill. A SNS topic is considered to be unused when it has 0 (zero) subscriptions confirmed or messages have not been published to a topic for 30 days.\nUnless there is a business need to retain unused SNS topics, you should remove them to avoid incurring unexpected charges and to maintain an accurate inventory of system components.","impact":"Keeping unused Amazon SNS topics can result in escalating costs and cluttered AWS accounts.","report_fields":["TopicArn"],"remediation":"Perform the following to create a new subscription:\n 1. Sign in to the Amazon SNS console https://console.aws.amazon.com/sns/home.\n 2. On the console, select the specific region. \n 3. Select the desired topic you want to subscribe to.\n 4. Go to 'Subscriptions' section.\n 5. Click 'Create subscription' and configure a new subscription.\n 5. When you have finished, click 'Create subscription'.\n\nPerform the following to delete a topic:\n 1. Sign in to the Amazon SNS console https://console.aws.amazon.com/sns/home.\n 2. On the console, select the specific region. \n 3. On the 'Topics' page, choose a topic you want to delete.\n 4. Choose 'Delete' and confirm deletion.","multiregional":false,"service":"Amazon Simple Notification Service"},"ecc-aws-543":{"article":"By having logs immediately available, an entity can quickly identify and minimize impact of a data breach.","impact":"Without formal processes to detect exceptions and anomalies, the entity may be unaware of unauthorized and potentially malicious activities occurring within their network.","report_fields":["ARN"],"remediation":"To create a real-time log configuration (console):\n1. Sign in to the AWS Management Console and open the Logs page in the CloudFront console.\n2. Choose Real-time log configurations.\n3. Choose Create configuration.\n4. Choose the desired setting for the real-time log configuration.\n5. When finished, choose Create configuration.\nIf successful, the console shows the details of the real-time log configuration that you just created.\n\nTo create a real-time log configuration (CLI with input file):\n1. Use the following command to create a file named rtl-config.yaml that contains all of the input parameters for the create-realtime-log-config command.\n aws cloudfront create-realtime-log-config --generate-cli-skeleton yaml-input > rtl-config.yaml\n2. Open the file named rtl-config.yaml that you just created. Edit the file to specify the real-time log configuration settings that you want, then save the file.\n For more information about the real-time long configuration settings, see Understanding real-time log configurations (https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/real-time-logs.html#understand-real-time-log-config).\n3. Use the following command to create the real-time log configuration using input parameters from the rtl-config.yaml file.\n aws cloudfront create-realtime-log-config --cli-input-yaml file://rtl-config.yaml\nIf successful, the command's output shows the details of the real-time log configuration that you just created.\n\nTo attach a real-time log configuration to an existing distribution (CLI with input file):\n1. Use the following command to save the distribution configuration for the CloudFront distribution that you want to update. Replace distribution_ID with the distribution's ID.\n aws cloudfront get-distribution-config --id distribution_ID --output yaml > dist-config.yaml\n2. Open the file named dist-config.yaml that you just created. Edit the file, making the following changes to each cache behavior that you are updating to use a real-time log configuration.\n - In the cache behavior, add a field named RealtimeLogConfigArn. For the field's value, use the ARN of the real-time log configuration that you want to attach to this cache behavior.\n - Rename the ETag field to IfMatch, but don't change the field's value.\n3. Use the following command to update the distribution to use the real-time log configuration. Replace distribution_ID with the distribution's ID.\n aws cloudfront update-distribution --id distribution_ID --cli-input-yaml file://dist-config.yaml\nIf successful, the command's output shows the details of the distribution that you just updated.","multiregional":true,"service":"Amazon CloudFront"},"ecc-aws-519":{"article":"A VPN tunnel is an encrypted link where data can pass from the customer network to or from AWS within an AWS Site-to-Site VPN connection. Each VPN connection includes two VPN tunnels which you can simultaneously use for high availability. Ensuring that both VPN tunnels are up for a VPN connection is important for confirming a secure and highly available connection between an AWS VPC and your remote network.\nIf there's a device failure within AWS, your VPN connection automatically fails over to the second tunnel so that your access isn't interrupted. From time to time, AWS also performs routine maintenance on your VPN connection, which may briefly disable one of the two tunnels of your VPN connection.","impact":"When both tunnels have state 'DOWN' a VPN connection is not available at all. When one of the tunnels is in the 'DOWN' state, the site-to-site VPN connection is not redundant and if the second tunnel also becomes unavailable, the site-to-site VPN connection is interrupted.","report_fields":["VpnConnectionId"],"remediation":"To remediate this issue, you have to configure a tunnel that has 'DOWN' state, or if there is a problem with a connection that requires troubleshooting, use the following AWS documentation for solving the problem: https://docs.aws.amazon.com/vpn/latest/s2svpn/Troubleshooting.html.","multiregional":false,"service":"Amazon Virtual Private Cloud"},"ecc-aws-288":{"article":"The Amazon WorkSpaces service periodically checks the health of a WorkSpace by sending it a status request. The WorkSpace is marked as unhealthy if a response isn\u2019t received from the WorkSpace in a timely manner.","impact":"If a WorkSpace can not respond and is marked as unhealthy, it can be inoperable and affect the ability of staff to perform their duties.","report_fields":["WorkspaceId"],"remediation":"The following troubleshooting steps can return the WorkSpace to a healthy state:\nFirst, reboot the WorkSpace from the Amazon WorkSpaces console. If rebooting the WorkSpace doesn't resolve the issue, either use RDP, or connect to an Amazon Linux WorkSpace using SSH.\nIf the WorkSpace is unreachable by a different protocol, rebuild the WorkSpace from the Amazon WorkSpaces console.\nIf a WorkSpaces connection cannot be established, verify the following:\n- Verify CPU utilization\n- Verify the computer name of the WorkSpace\n- Confirm that WorkSpaces services are running and responsive\n- Verify Firewall rules\n\nMore information could be found here: https://aws.amazon.com/premiumsupport/knowledge-center/workspaces-unhealthy/.","multiregional":false,"service":"Amazon WorkSpaces Family"},"ecc-aws-350":{"article":"Broker logs enable you to troubleshoot your Apache Kafka applications and to analyze their communications with your MSK cluster. \nYou can configure new or existing MSK cluster to deliver INFO-level broker logs to one or more of the following types of destination resources: a CloudWatch log group, an S3 bucket, a Kinesis Data Firehose delivery stream. Through Kinesis Data Firehose you can then deliver the log data from your delivery stream to OpenSearch Service.","impact":"Without logging it is harder to troubleshoot Apache Kafka applications and to analyze their communications with MSK cluster.","report_fields":["ClusterArn"],"remediation":"Perform the following steps to enable log delivery for MSK:\n1. Login to the MSK console at https://console.aws.amazon.com/msk/.\n2. Click on the Clusters.\n3. Choose required cluster.\n4. Click on the Actions and 'Edit Log Delivery'.\n5. Under the 'Broker log delivery' choose one or multiple delivery methods.\n6. Save changes.","multiregional":false,"service":"Amazon Managed Streaming for Apache Kafka"},"ecc-aws-066":{"article":"The EKS cluster must not be publicly accessible from the Internet. As the cluster endpoint is the entry point for managing the cluster, an attacker may cause damage to the infrastructure or data loss.","impact":"Everyone and everything on the Internet can establish a connection to EKS clusters and this can increase the opportunity for malicious activities and attacks.","report_fields":["arn"],"remediation":"1. Open the Amazon EKS console at https://console.aws.amazon.com/eks/home#/clusters. \n2. Choose the name of the cluster to display your cluster information. \n3. Under Networking, choose Update. \n4. For Private access, choose whether to enable or disable private access to your cluster's Kubernetes API server endpoint. If you enable private access, Kubernetes API requests that originate from within your cluster's VPC use the private VPC endpoint. You must enable private access to disable public access. \n5. For Public access, choose whether to enable or disable public access to your cluster's Kubernetes API server endpoint. If you disable public access, your cluster's Kubernetes API server can only receive requests from within the cluster VPC. \n6. (Optional) If you've enabled Public access, you can specify which addresses from the Internet can communicate with the public endpoint. Select Advanced Settings. Enter a CIDR block, such as 203.0.113.5/32. The block cannot include reserved addresses. You can enter additional blocks by selecting Add Source. There is a maximum number of CIDR blocks that you can specify. \nFor more information, see Amazon EKS service quotas (p. 297). If you specify no blocks, then the public API server endpoint receives requests from all (0.0.0.0/0) IP addresses. \nIf you restrict access to your public endpoint using CIDR blocks, it is recommended that you also enable private endpoint access so that worker nodes and Fargate pods (if you use them) can communicate with the cluster. Without the private endpoint enabled, your public access endpoint CIDR sources must include the egress sources from your VPC. \nFor example, if you have a worker node in a private subnet that communicates to the internet through a NAT Gateway, you will need to add the outbound IP address of the NAT gateway as part of a whitelisted CIDR block on your public endpoint. \n7. Choose Update to finish.","multiregional":false,"service":"Amazon Elastic Kubernetes Service"},"ecc-aws-370":{"article":"It is recommend to maintain WorkSpaces on a regular basis. WorkSpaces schedules default maintenance windows for your WorkSpaces. During the maintenance window, the WorkSpace installs important updates from Amazon WorkSpaces and reboots as necessary. If available, operating system updates are also installed from the OS update server that the WorkSpace is configured to use. During maintenance, your WorkSpaces might be unavailable.","impact":"Without keeping the WorkSpaces up-to-date, it is possible to miss out on bug fixes, security patches, and performance improvements.","report_fields":["DirectoryId"],"remediation":"Perform the following steps to enable maintenance mode From the Console:\n1. Login to the WorkSpaces console at https://console.aws.amazon.com/workspaces/.\n2. In the left pane, click \"Directories\".\n3. Select your directory.\n4. Choose \"Actions\", \"Update Details\".\n5. Expand \"Maintenance Mode\".\n6. To enable automatic updates, choose \"Enabled\".\n7. Click \"Update and Exit\".\nNote: If you prefer to manage updates manually or with another tool document usage of that, and choose Disabled.","multiregional":false,"service":"Amazon WorkSpaces Family"},"ecc-aws-136":{"article":"Services and databases store data that may be sensitive, protected by law, subject to regulatory requirements, or compliance standards. It is highly recommended that access to data be restricted to encrypted protocols. \nThis rule detects network settings that may expose data via unencrypted protocol over the public internet or to an overly wide local scope.","impact":"Unrestricted access increases the opportunity for malicious activity such as unauthorized access, denial-of-service attacks, and loss of data.","report_fields":["LoadBalancerArn"],"remediation":"1. Login to AWS Console at https://console.aws.amazon.com/ec2/.\n2. Go to Load Balancers.\n3. Click on the reported Load Balancer.\n4. In the 'Description' tab, under 'Security' select security group to edit.\n5. In the 'Inbound rules' tab, click 'Edit inbound rules'.\n6. Modify rules.\n7. Click on 'Save rules'.","multiregional":false,"service":"Amazon Elastic Load Balancing"},"ecc-aws-298":{"article":"When you create and use your own KMS CMK customer-managed keys to protect the contents of your SQS queue messages, you obtain full control over who can use the CMK keys and access the data encrypted within queue messages. The AWS KMS service allows you to create, rotate, disable, enable, and audit your Customer Master Keys (CMKs) for Amazon SQS.","impact":"Without a KMS CMK customer-managed key, you do not have full and granular control over who can access key that is used for encryption.","report_fields":["QueueArn"],"remediation":"1. Navigate to https://console.aws.amazon.com/sqs/v2\n2. Choose Queues.\n3. Click on the reported Queue.\n4. Press Edit.\n5. Under Encryption, enable Server-side encryption.\n6. Choose AWS Key Management Service key.\n7. Choose Customer key.","multiregional":false,"service":"Amazon Simple Queue Service"},"ecc-aws-361":{"article":"To help debug issues related to request execution to API Gateway REST API, you can enable Amazon CloudWatch Logs to log API calls. \nExecution logs contain information that you can use to identify and troubleshoot most API errors.","impact":"With disabled monitoring and logging of API Gateway, it may be difficult to troubleshoot any issues that might happen with APIs.","report_fields":["restApiId","stageName"],"remediation":"Create an IAM role for logging to CloudWatch:\n1. In the AWS Identity and Access Management (IAM) console, in the left navigation pane, choose Roles.\n2. On the Roles pane, choose Create role.\n3. On the Create role page, do the following:\n 3.1 For Trusted entity type, choose AWS Service.\n 3.2 For use case, choose API Gateway.\n 3.3 Choose the API Gateway radio button.\n 3.4 Choose Next.\n4. Under Permissions Policies, note that the AWS managed policy AmazonAPIGatewayPushToCloudWatchLogs is selected by default. The policy has all the required permissions.\n5. Choose Next.\n6. Under Name, review and create, do the following:\n 6.1 For Role name, enter a meaningful name for the role.\n 6.2 (Optional) For Role description, edit the description to your preferences.\n 6.3 (Optional) Add tags.\n 6.4 Choose Create role.\n7. On the Roles pane, in the search bar, enter the name of the role that you created. Then, choose the role from the search results.\n8. On the Summary pane, copy the Role ARN. You'll need this Amazon Resource Name (ARN) in the next section.\n\nAdd the IAM role in the API Gateway console:\nNote: If you're developing multiple APIs across different AWS Regions, complete these steps in each Region.\n1. In the API Gateway console, on the APIs pane, choose the name of an API that you created.\n2. In the left navigation pane, at the bottom, below the Client Certificates section, choose Settings.\n3. Under Settings, for CloudWatch log role ARN, paste the IAM role ARN that you copied.\n4. Choose Save.\nNote: The console doesn't confirm that the ARN is saved.\n\nTurn on logging for your API and stage:\n1. In the API Gateway console, find the Stage Editor for your API.\n2. On the Stage Editor pane, choose the Logs/Tracing tab.\n3. On the Logs/Tracing tab, under CloudWatch Settings, do the following to turn on execution logging:\n 3.1 Choose the Enable CloudWatch Logs check box.\n 3.2 For Log level, choose INFO to generate execution logs for all requests. Or, choose ERROR to generate execution logs only for requests to your API that result in an error.\n4. Under Custom Access Logging, do the following to turn on access logging:\n 4.1 Choose the Enable Access Logging check box.\n 4.2 For Access Log Destination ARN, enter the ARN of a CloudWatch log group or an Amazon Kinesis Data Firehose stream.\n 4.3 Enter a Log Format. For guidance, choose CLF, JSON, XML, or CSV to see an example in that format.\n5. Choose Save Changes.\nNote: The console doesn't confirm that settings are saved.","multiregional":false,"service":"Amazon API Gateway"},"ecc-aws-412":{"article":"This policy identifies the FSX backups that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["FileSystemId"],"remediation":"1. Open the Amazon FSx for Lustre console at https://console.aws.amazon.com/fsx/.\n2. Click on the 'Backups'.\n3. CLick on the required backup.\n4. Under the 'Tags' click on the 'Add'.\n5. Add new tag and save.","multiregional":false,"service":"Amazon FSx"},"ecc-aws-025":{"article":"Ensure protection against accidental termination of instances is enabled.","impact":"With numerous instances running in the infrastructure, it is quite possible to delete an EC2 instance by mistake. It can result in the loss of critical resources such as sensitive data or computing resources for production workloads. By default, the volumes associated with EC2 instances are deleted when these are terminated.\nBy enabling the Termination Protection feature, you can ensure safety of EBS data.","report_fields":["InstanceId","OwnerId"],"remediation":"To enable termination protection for an instance at launch, do as follows:\n1. Open the Amazon EC2 console at https://console.aws.amazon.com/ec2/. \n2. On the dashboard, choose Launch Instance and follow the directions in the wizard. \n3. On the Configure Instance Details page, select the Enable termination protection check box. \nTo enable termination protection for a running or stopped instance, do as follows: \n1. Select the instance and choose in sequence: Actions, Instance Settings, Change Termination Protection. \n2. Choose Yes, Enable.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-300":{"article":"The access policy defines the accounts, users, and roles that can access the queue. The access policy also defines the actions (such as SendMessage, ReceiveMessage, or DeleteMessage) that the users can access.\nTo avoid data leakage and unexpected costs on your AWS bill, limit access to your queues by implementing the necessary policies and avoid granting access to all users (anonymous users).","impact":"Publicly-available Amazon SQS queues give unauthorized users access to potentially intercept, delete, or send queue messages, which can lead to data leaks.","report_fields":["QueueArn"],"remediation":"To configure the access policy for an existing queue:\n1. Open the Amazon SQS console at https://console.aws.amazon.com/sqs/.\n2. In the navigation pane, choose Queues.\n3. Choose a queue and choose Edit.\n4. Scroll to the Access policy section.\n5. Edit the access policy statements in the input box. Clear the Everybody (*) checkbox and enter the AWS account ID of the person allowed or denied.\n6. When you finish configuring the access policy, choose Save.","multiregional":false,"service":"Amazon Simple Queue Service"},"ecc-aws-322":{"article":"SQL92_SECURITY specifies whether users must have been granted the SELECT object privilege in order to execute such UPDATE or DELETE statements.","impact":"A user without a SELECT privilege can still infer the value stored in a column by referring to that column in a DELETE or UPDATE statement. This setting prevents inadvertent information disclosure by ensuring that only users who already have the SELECT privilege can execute the statements that would allow them to infer the stored values.","report_fields":["DBInstanceArn"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/\n2. In the navigation pane, choose Parameter groups.\n3. Choose the required parameter group\n4. Choose Edit parameters.\n5. Under Parameters, in the search bar, type 'sql92_security'.\n6. Choose TRUE\n7. Choose Save changes.\n8. Restart instance","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-499":{"article":"Ensure that all the IAM groups within your AWS account are currently used and have at least one user attached. Otherwise, remove any orphaned (unused) IAM groups in order to prevent attaching unauthorized users.","impact":"Removing orphaned and unused IAM groups eliminates the risk that a forgotten group will be used accidentally to allow unauthorized users to access AWS resources.","report_fields":["Arn"],"remediation":"1. Login to the IAM AWS Management Console at https://console.aws.amazon.com/iamv2.\n2. In the left pane, click on 'User Groups'.\n3. Delete all empty groups.","multiregional":false,"service":"AWS Identity and Access Management"},"ecc-aws-431":{"article":"This policy identifies the SNS that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["TopicArn"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon EFS at https://console.aws.amazon.com/sns/v3.\n2. Click on the required sns topic.\n3. Open 'Tags' and click on the 'Manage tags'.\n4. Add new tag and save.","multiregional":false,"service":"Amazon Simple Notification Service"},"ecc-aws-120":{"article":"Enable Server Side Encryption (SSE) of your AWS Kinesis Server data at rest to protect your data and metadata from breach or unauthorized access, and fulfill compliance requirements for data-at-rest encryption within your organization.","impact":"Disabled encryption allows a user to get unauthorized access to sensitive data in Amazon Kinesis Server","report_fields":["StreamARN"],"remediation":"1. Navigate to the Kinesis dashboard at https://console.aws.amazon.com/kinesis/.\n2. In the Navigation pane, under Amazon Kinesis, choose Streams. \n3. Select the relevant Kinesis stream, click on the Actions drop-down menu and select Details to access the stream configuration details. \n4. Choose the Details tab from the top panel and verify the SSE feature status available next to Server-side encryption is set to Enable.","multiregional":false,"service":"Amazon Kinesis"},"ecc-aws-225":{"article":"Control plane logs provide visibility into operation of the EKS Control plane component systems. The API server audit logs record all accepted and rejected requests in the cluster. When enabled via EKS configuration the control plane logs for a cluster are exported to a CloudWatch Log Group for persistence.","impact":"With disabled control plane logging, it will not be possible to detect anomalous configuration activity, track configuration changes conducted manually and programmatically, and trace back unapproved changes.","report_fields":["arn"],"remediation":"1. Sign in to the AWS Management Console and open the EKS console at https://console.aws.amazon.com/eks.\n2. Click on Cluster Name of the cluster you are auditing.\n3. Click 'Logging'.\n4. Select 'Manage Logging' from the button on the right side.\n5. Toggle each selection to the 'Enabled' position.\n6. Click 'Save Changes'.","multiregional":false,"service":"Amazon Elastic Kubernetes Service"},"ecc-aws-151":{"article":"TCP ports 20 is used for data transfer and communication by the File Transfer Protocol (FTP) client-server applications.\nUnrestricted access (0.0.0.0/0) to port 20 increases opportunities for malicious activity, such as unauthorized access, denial-of-service attacks, and loss of data (spoofing attacks and packet capture).","impact":"Unrestricted access increases the opportunity for malicious activity such as unauthorized access, denial-of-service attacks, and loss of data.","report_fields":["GroupId","VpcId","OwnerId"],"remediation":"From Console \n1. Login to the AWS Management Console and open the EC2 console using https://console.aws.amazon.com/ec2/ \n2. In the navigation pane, choose Security Groups. \n3. Select the security group to update, choose Actions, and then choose 'Edit inbound rules' to remove an inbound rule or 'Edit outbound rules' to remove an outbound rule. \n4. Choose the Delete button to the right of the rule to delete. \n5. Choose Preview changes, than choose Confirm.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-109":{"article":"Checks an ACM for Invalid or Failed certificates. An Invalid certificate is the one that has not been validated within 72 hours. A certificate fails 'for these reasons:' \n- the certificate is requested for invalid public domains;\n- the certificate is requested for domains which are not allowed or missing contact information;\n- typographical errors.\nThese certificates cannot be used, and you will have to request new ones. It is recommended to delete Failed or Invalid certificates.","impact":"Invalid or failed certificates can result in deploying an invalid certificate in resources. This may trigger an error in the front end and cause a loss of credibility for the web application/website.","report_fields":["CertificateArn","DomainName"],"remediation":"To delete certificates: \n1. Sign in to the AWS console.\n2. On the console, select the region. \n3. Navigate to the Certificate Manager(ACM) service. \n4. Select the certificate that was reported. \n5. Under the 'Actions' drop-down list, click on 'Delete'.","multiregional":false,"service":"AWS Certificate Manager"},"ecc-aws-415":{"article":"This policy identifies the IAM Users that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["Arn"],"remediation":"1. Login to the AWS Console.\n2. Find the IAM service.\n3. Choose Users without tags.\n4. Click on the \"Tags\".\n5. Click on the \"Add tags\".\n6. Add tag.","multiregional":true,"service":"AWS Identity and Access Management"},"ecc-aws-166":{"article":"Remote Procedure Call (RPC) TCP port 135 is used for client-server communications by Microsoft Message Queuing (MSMQ) as well as other Microsoft Windows/Windows Server software.\nUnrestricted access (0.0.0.0/0) increases opportunities for malicious activity, such as unauthorized access, denial-of-service attacks, and loss of data.","impact":"Unrestricted access increases the opportunity for malicious activity such as unauthorized access, denial-of-service attacks, and loss of data.","report_fields":["GroupId","VpcId","OwnerId"],"remediation":"From Console: \n1. Login to AWS Management Console and open the EC2 console using https://console.aws.amazon.com/ec2/\n2. In the navigation pane, choose Security Groups.\n3. Select the security group to update, choose Actions, and then choose Edit inbound rules to remove an inbound rule or Edit outbound rules to remove an outbound rule.\n4. Choose the Delete button to the right of the rule to delete.\n5. Choose Preview changes, Confirm.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-012":{"article":"Enforce the use of secure ciphers TLS v1.2 in the CloudFront Distribution certificate configuration. This is the best security practice. This signature scans for any deviations from this practice and returns the results.","impact":"Insecure and deprecated ciphers could make an SSL connection between the CloudFront and viewers vulnerable to exploits and result in a breach of confidentiality or integrity.","report_fields":["ARN"],"remediation":"On the AWS CloudFront console, check the details for each distribution that failed the rule and ensure that the Viewer Protocol Policy is HTTPS Only or Redirect HTTP to HTTPS.\nPerform the following for each distribution that failed the rule:\n1. Navigate to the the AWS console CloudFront dashboard at https://console.aws.amazon.com/cloudfront.\n2. Select your distribution ID.\n3. On the 'General' tab.\n4. Click on 'Edit' in the 'Settings' section.\n5. For 'Security policy' choose the latest 'TLSv1.2' protocol version.\n6. Click on Save changes.","multiregional":true,"service":"Amazon CloudFront"},"ecc-aws-134":{"article":"Services and databases store data that may be sensitive, protected by law, subject to regulatory requirements or compliance standards. It is highly recommended that access to data be restricted to encrypted protocols. This rule detects network settings that may expose data via unencrypted protocol over the public internet or to an overly wide local scope.","impact":"Unrestricted access increases the opportunity for malicious activity such as unauthorized access, denial-of-service attacks, and loss of data.","report_fields":["LoadBalancerArn"],"remediation":"1. Login to AWS Console at https://console.aws.amazon.com/ec2/.\n2. Go to Load Balancers.\n3. Click on the reported Load Balancer.\n4. In the 'Description' tab, under 'Security' select security group to edit.\n5. In the 'Inbound rules' tab, click 'Edit inbound rules'.\n6. Modify rules.\n7. Click on 'Save rules'.","multiregional":false,"service":"Amazon Elastic Load Balancing"},"ecc-aws-052":{"article":"AWS CloudTrail is a web service that records AWS API calls for your account and delivers log files to you. The recorded information includes the identity of the API caller, the time of the API call, the source IP address of the API caller, the request parameters, and the response elements returned by the AWS service. \nCloudTrail provides a history of AWS API calls for an account, including API calls made via the Management Console, SDKs, command line tools, and higher-level AWS services (such as CloudFormation).","impact":"Without enabling CloudTrail across different regions, it is hard to monitor the infrastructure and maintain security across all regions.","report_fields":["account_id","account_name"],"remediation":"1. Sign in to the AWS Management Console and open the IAM console at https://console.aws.amazon.com/cloudtrail. \n2. Click on Trails in the left navigation pane. \n3. Click on Get Started Now, if presented: \n- Click on Add new trail;\n- Enter a trail name in the Trail name box;\n- Set the Apply trail to all regions option to Yes;\n- Specify the S3 bucket name in the S3 bucket box;\n- Click on Create. \n4. If one or more trails already exist, select the target trail to enable for global logging. \n5. Click on the edit icon (pencil) next to Apply trail to all regions, click Yes, and click Save. \n6. Click on the edit icon (pencil) next to Management Events, click All for setting Read/Write Events, and click Save.","multiregional":true,"service":"AWS CloudTrail"},"ecc-aws-221":{"article":"Encrypting data at rest reduces the risk of data stored on disk being accessed by a user not authenticated to AWS. It also adds another set of access controls to limit the ability of unauthorized users to access the data. \nFor example, API permissions are required to decrypt the data before it can be read. SNS topics should be encrypted at-rest for an added layer of security.","impact":"Unauthorized users can read confidential information available on SNS topics.","report_fields":["TopicArn"],"remediation":"1. Open the Amazon SNS console at https://console.aws.amazon.com/sns/v3/home.\n2. In the navigation pane, choose Topics. \n3. Choose the name of the topic to encrypt.\n4. Choose Edit. \n5. Under Encryption, choose Enable Encryption. \n6. Choose the KMS key to use to encrypt the topic. \n7. Choose Save changes.","multiregional":false,"service":"Amazon Simple Notification Service"},"ecc-aws-400":{"article":"This policy identifies the DAX that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["ClusterArn"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon DynamoDB at https://console.aws.amazon.com/dynamodbv2.\n2. Click on the Clusters.\n3. Click on the required dax cluster.\n4. Open 'Settings' click on 'Tags' and click on the 'Manage tags'.\n5. Add new tag and save.","multiregional":false,"service":"Amazon DynamoDB Accelerator"},"ecc-aws-087":{"article":"When Amazon Redshift clusters are publicly accessible and have a public IP address, every machine on the Internet can establish a connection to these clusters which increases the opportunity for malicious activity such as SQL injections or Distributed Denial of Service (DDoS) attacks. Unless you intend to make your cluster publicly accessible, it should not be configured with public access.","impact":"When Amazon Redshift clusters are publicly accessible and have a public IP address, every machine on the Internet can establish a connection to these clusters which increases the opportunity for malicious activity such as SQL injections or Distributed Denial of Service (DDoS) attacks. Unless you intend to make your cluster publicly accessible, it should not be configured with public access.","report_fields":["ClusterIdentifier"],"remediation":"1. Open the Amazon Redshift console at https://console.aws.amazon.com/redshift/.\n2. On the navigation pane, choose Clusters and then select your public Amazon Redshift cluster. \n3. From the Cluster drop-down menu, choose Modify cluster. \n4. In Publicly accessible, choose No. \n5. Choose Modify.","multiregional":false,"service":"Amazon Redshift"},"ecc-aws-233":{"article":"The debug_print_parse setting enables printing the resulting parse tree for each executed query. These messages are emitted at the LOG message level. Unless directed otherwise by your organization's logging policy, it is recommended this setting be disabled by setting it to off. Enabling any of the DEBUG printing variables may cause the logging of sensitive information that would otherwise be omitted based on the configuration of the other logging settings.","impact":"Enabling any of the DEBUG printing variables may cause the logging of sensitive information that would otherwise be omitted based on the configuration of the other logging settings.\nUnless directed otherwise by your organization's logging policy, it is recommended this setting be disabled by setting it to 0.","report_fields":["DBInstanceArn"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/\n2. In the navigation pane, choose Parameter groups.\n3. Choose the required parameter group.\n4. Choose Edit parameters.\n5. Under Parameters, in search bar type 'debug_print_parse'.\n6. Choose 0 in value field.\n7. Choose Save changes.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-441":{"article":"Cache encryption comes in the following two flavors. These are similar to the settings that ElastiCache for Redis allows. You can enable the encryption settings only when first enabling caching for your AWS AppSync API.\n- Encryption in transit \u2013 Requests between AWS AppSync, the cache, and data sources (except insecure HTTP data sources) are encrypted at the network level. Because there is some processing needed to encrypt and decrypt the data at the endpoints, in-transit encryption can impact performance.\n- Encryption at rest \u2013 Data saved to disk from memory during swap operations are encrypted at the cache instance. This setting also impacts performance.","impact":"Disabled encryption allows unauthorized users to gain access to AppSync data saved to disk from memory during swap operations. With encryption enabled, this data is protected.","report_fields":["name","arn"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nTo encrypt, cache you should delete the existing cache and recreate it.\n1. Navigate to the https://console.aws.amazon.com/appsync/home\n2. Select the API that for which you want enable encryption.\n3. At the left panel, choose 'Caching'.\n4. Click 'Delete cache'.\n5. Submit deletion.\n6. Select caching behavior:\n - Full request caching: All requests are fully cached.\n - Per-resolver caching: Individual resolvers that you specify are cached.\n7. In 'Cache settings' section, make sure you have selected 'Encryption at rest' option.\n8. Click 'Create cache'.","multiregional":false,"service":"AWS AppSync"},"ecc-aws-419":{"article":"This policy identifies the Kinesis video stream that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["StreamARN"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon Kinesis Video at https://console.aws.amazon.com/kinesisvideo\n2. Click on 'Video streams'.\n3. Click on the required video stream.\n4. Open 'Tags' and click on the 'Manage tags'.\n5. Add new tag and save.","multiregional":false,"service":"Amazon Kinesis"},"ecc-aws-104":{"article":"Geo Restriction has the ability to block IP addresses based on Geo IP by whitelist or blacklist a country in order to allow or restrict users in specific locations from accessing web application content. \nIt is recommended to have geo restriction feature enabled, to restrict or allow users in specific locations accessing web application content. This configuration helps with prevention of DDos Attacks.","impact":"Not enabling CloudFront geo restriction can lead to user access to content from unwanted geographic locations and possible DoS attacks.","report_fields":["ARN"],"remediation":"1. Sign in to the AWS console.\n2. Select the region from the drop-down list in which the issue has occured.\n3. Navigate to CloudFront Distributions Dashboard.\n4. Select relevant Distribution.\n5. On 'Restrictions' tab, click the 'Edit' button.\n6. On 'Edit Geo-Restrictions' page, Set 'Enable Geo-Restriction' to 'Yes' and whitelist/blacklist countries as per your requirement. \n7. Click 'Yes, Edit'.","multiregional":true,"service":"Amazon CloudFront"},"ecc-aws-013":{"article":"Ensure that a SSL policy for a Classic Load balancer is based on TLS 1.3 or TLS 1.2, and not on TLS 1.0,TLS 1.1, SSLv2, SSLv3.","impact":"Insecure and deprecated ciphers for ELB Predefined Security Policy or Custom Security Policy could make an SSL connection between the client and the load balancer vulnerable to exploits and result in a breach of confidentiality or integrity.","report_fields":["LoadBalancerArn"],"remediation":"Login to the AWS Management Console. \n1. Navigate to the EC2 dashboard.\n2. On the navigation panel, under 'Load balancing', click on 'Load Balancers'.\n3. Select your Elastic Load Balancer.\n4. Select the 'Listeners' tab from the bottom panel. In the 'Cipher' column of the HTTPS listener, click 'Change'.\n5. Review the 'SSL Ciphers' section for any insecure / deprecated cipher definitions. \nThe following list defines all the insecure ciphers that should be removed: http://docs.aws.amazon.com/ElasticLoadBalancing/latest/DeveloperGuide/elb-security-policy-table.html","multiregional":false,"service":"Amazon Elastic Load Balancing"},"ecc-aws-547":{"article":"Using the latest generation classes of RDS database instances over their predecessors offers concrete advantages. These encompass enhanced hardware performance, including expanded computing capacity and swifter CPUs, optimized memory utilization, and increased network throughput. Additionally, staying up-to-date with the latest database engine versions, such as MySQL 5.7, is better facilitated. This transition can also lead to cost savings in terms of memory and storage expenditure.","impact":"Not using the latest generation of RDS database you will get less performance for more costs. Additionally, oldest generations not supporting latest database engine versions, such as MySQL 5.7.","report_fields":["DBInstanceArn"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nChanging AWS RDS Database type:\n1. Login to the AWS Management Console.\n2. Navigate to RDS dashboard at https://console.aws.amazon.com/rds/.\n3. In the navigation panel, under RDS Dashboard, click Instances.\n4. Select the RDS database instance that will be changed.\n5. Click Instance 'Modify' button.\n6. Change 'DB instance class'.\n7. Save changes.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-308":{"article":"An Amazon S3 Glacier vault is the primary resource in S3 Glacier. You can add permissions to the policy associated with a S3 Glacier vault. An Amazon S3 Glacier vault access policy is a resource-based policy that you can use to manage permissions to your vault. \nYou can now define and use the policy to grant access to individual users, business groups, and to external business partners. Using a single access policy to control access to a vault can be simpler than using individual user and group IAM policies in many cases.","impact":"An Amazon S3 Glacier with a policy that allows anyone to access your resource (\"Principal\": \"*\") can lead to data leaks.","report_fields":["VaultARN"],"remediation":"To configure the access policy for an existing Amazon S3 Glacier:\n1. Open the Amazon S3 Glacier console at https://console.aws.amazon.com/glacier/home.\n2. Choose Vault that you want to reconfigure.\n3. Choose Permissions tab.\n4. Click 'Edit policy document' button.\n5. Edit 'Principal' value by deleting \"*\" and then enter an allowed AWS account ID or a user.\n6. When you finish configuring the access policy, choose Save.","multiregional":false,"service":"Amazon S3 Glacier"},"ecc-aws-234":{"article":"The debug_print_rewritten setting enables printing the query rewriter output for each executed query. These messages are emitted at the LOG message level. Unless directed otherwise by your organization's logging policy, it is recommended this setting be disabled by setting it to off.","impact":"Enabling any of the DEBUG printing variables may cause the logging of sensitive information that would otherwise be omitted based on the configuration of the other logging settings.","report_fields":["DBInstanceArn"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/\n2. In the navigation pane, choose Parameter groups.\n3. Choose the required parameter group.\n4. Choose Edit parameters.\n5. Under Parameters, in search bar type 'debug_print_rewritten'.\n6. Choose 0 in value field.\n7. Choose Save changes.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-100":{"article":"Real-time monitoring of API calls can be achieved by directing CloudTrail Logs to CloudWatch Logs and establishing corresponding metric filters and alarms. It is recommended that a metric filter and alarm be established for detecting changes made to VPCs.","impact":"Lack of monitoring and logging of VPC changes can result in insufficient response time to detect accidental or intentional modifications that may lead to unauthorized network access or other security breaches.","report_fields":["account_id","account_name"],"remediation":"Perform the following to setup the metric filter, alarm, SNS topic, subscription and trail using the AWS CLI: \n1. Create a log group.\naws logs create-log-group --log-group-name \n2. Create a log stream.\naws logs create-log-stream --log-group-name --log-stream-name \n3. Create a metric filter based on provided filter pattern which checks for unauthorized API calls and the taken from step 1.\naws logs put-metric-filter --log-group-name --filter-name `` --metric-transformations metricName=``,metricNamespace=,metricValue='1' --filter-pattern '{{($.eventName = CreateVpc) || ($.eventName = DeleteVpc) || ($.eventName = ModifyVpcAttribute) || ($.eventName = AcceptVpcPeeringConnection) || ($.eventName = CreateVpcPeeringConnection) || ($.eventName = DeleteVpcPeeringConnection) || ($.eventName = RejectVpcPeeringConnection) || ($.eventName = AttachClassicLinkVpc) || ($.eventName = DetachClassicLinkVpc) || ($.eventName = DisableVpcClassicLink) || ($.eventName = EnableVpcClassicLink)}}'\n4. Create a topic to which notifications will be published.\naws sns create-topic --name \n5. Subscribe an endpoint to an Amazon SNS topic. If the endpoint type is HTTP/S or email, or if the endpoint and the topic are not in the same Amazon Web Services account, the endpoint owner must run the ConfirmSubscription action to confirm the subscription.\naws sns subscribe --topic-arn --protocol email --notification-endpoint \n6. Amazon SNS will send a subscription confirmation message to the endpoint. Confirm subscription to topic, by visiting the link in an email that you specified as notification endpoint.\n7. Create an alarm that is associated with the CloudWatch Logs Metric Filter created in step 3 and an SNS topic created in step 4. \naws cloudwatch put-metric-alarm --alarm-name `` --metric-name `` --statistic Sum --period 300 --threshold 1 --comparison-operator GreaterThanOrEqualToThreshold --evaluation-periods 1 --namespace --alarm-actions \n8. Create an S3 bucket to deliver log files to:\naws s3api create-bucket --bucket \n9. To deliver log files to an S3 bucket, CloudTrail must have the required permissions. The following policy allows CloudTrail to write log files to the bucket from supported regions. Replace myBucketName, [optionalPrefix]/, myAccountID, region, and trailName with the appropriate values for your configuration. \nCreate a file 'policy.json' with the following policy.\n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"AWSCloudTrailAclCheck20150319\",\n \"Effect\": \"Allow\",\n \"Principal\": {\"Service\": \"cloudtrail.amazonaws.com\"},\n \"Action\": \"s3:GetBucketAcl\",\n \"Resource\": \"arn:aws:s3:::\"\n },\n {\n \"Sid\": \"AWSCloudTrailWrite20150319\",\n \"Effect\": \"Allow\",\n \"Principal\": {\"Service\": \"cloudtrail.amazonaws.com\"},\n \"Action\": \"s3:PutObject\",\n \"Resource\": \"arn:aws:s3:::/[optionalPrefix]/AWSLogs//*\",\n \"Condition\": {\n \"StringEquals\": {\n \"s3:x-amz-acl\": \"bucket-owner-full-control\",\n \"aws:SourceArn\": \"arn:aws:cloudtrail:::trail/\"\n }\n }\n }\n ]\n}}\n10. Apply the Amazon S3 bucket policy to the Amazon S3 bucket created in step 8.\naws s3api put-bucket-policy --bucket --policy file://.json\n11. Create a role for CloudTrail that enables it to send events to the CloudWatch Logs log group. To create the JSON file that will contain the policy document, open a text editor and save the following policy contents in a file called 'assume_role_policy_document.json'. \n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Principal\": {\n \"Service\": \"cloudtrail.amazonaws.com\"\n },\n \"Action\": \"sts:AssumeRole\"\n }\n ]\n}}\n12. Create a role.\naws iam create-role --role-name --assume-role-policy-document file://.json\n13. Create the following role policy document for CloudTrail. This document grants CloudTrail the permissions required to create a CloudWatch Logs log stream in the log group you specify and to deliver CloudTrail events to that log stream. Save the policy document in a file called role-policy-document.json. Replace region, accountID, log_group_name, with the appropriate values for your configuration. \n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n\n \"Sid\": \"AWSCloudTrailCreateLogStream2014110\",\n \"Effect\": \"Allow\",\n \"Action\": [\n \"logs:CreateLogStream\"\n ],\n \"Resource\": [\n \"arn:aws:logs:::log-group::log-stream:_CloudTrail_*\"\n ]\n\n },\n {\n \"Sid\": \"AWSCloudTrailPutLogEvents20141101\",\n \"Effect\": \"Allow\",\n \"Action\": [\n \"logs:PutLogEvents\"\n ],\n \"Resource\": [\n \"arn:aws:logs:::log-group::log-stream:_CloudTrail_*\"\n ]\n }\n ]\n}}\n14. Run the following command to apply the policy to the role.\naws iam put-role-policy --role-name --policy-name cloudtrail-policy --policy-document file://.json\n15. Create a trail that specifies the settings for delivery of log data to an Amazon S3 bucket at is associated with the S3 bucket created in step 8, CloudWatch log group created in step 1 and IAM role created in step 12. \naws cloudtrail create-trail --include-global-service-events --is-multi-region-trail --name --s3-bucket-name --cloud-watch-logs-log-group-arn --cloud-watch-logs-role-arn \n16. Start the recording of Amazon Web Services API calls and log file delivery for a trail. For a trail that is enabled in all regions, this operation must be called from the region in which the trail was created.\naws cloudtrail start-logging --name ","multiregional":false,"service":"AWS Account"},"ecc-aws-238":{"article":"PostgreSQL does not maintain the beginning or ending of a connection internally for later review. It is only by enabling the logging of these that one can examine connections for failed attempts, 'over long' duration, or other anomalies. \nNote that enabling this without also enabling log_connections provides little value. Generally, you would enable/disable the pair together.","impact":"PostgreSQL database engine does not log information such as session duration and session end by default. Without the \"log_disconnections\" flag we cannot properly record PostgreSQL activity data that can be useful to identify, troubleshoot, and repair configuration errors and sub-optimal performance for your PostgreSQL database instances.","report_fields":["DBInstanceArn"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/\n2. In the navigation pane, choose Parameter groups.\n3. Choose the required parameter group\n4. Choose Edit parameters.\n5. Under Parameters, in search bar type \"log_disconnections\".\n6. Choose \"1\" in value field.\n7. Choose Save changes.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-044":{"article":"This policy identifies the AWS S3 buckets that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["Name"],"remediation":"1. Login to the AWS Console.\n2. Find the S3 service.\n3. Choose the respective S3 Bucket witout tags.\n4. Click on the Properties tab.\n5. Find Tags, click on it and add a new one.\n6. Save it.","multiregional":true,"service":"Amazon S3"},"ecc-aws-377":{"article":"This policy identifies the AMI images that do not have any tags. Tags can be used for an easy identification and search process.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["ImageId","OwnerId"],"remediation":"1. Sign in to the AWS Management Console and open the EC2 console at https://console.aws.amazon.com/ec2/v2/.\n2. In the navigation pane under 'Images' choose 'AMIs'.\n3. Choose required AMI.\n4. Open 'Tags' tab and click on 'Manage tags'.\n5. Add new tags.\n6. Click 'Save'.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-382":{"article":"This policy identifies the Internet Gateway that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["InternetGatewayId","OwnerId"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon VPC at https://console.aws.amazon.com/vpc/\n2. Click on the 'Internet gateways'.\n3. Click on the required internet gateway.\n4. Open 'Tags' and click on the 'Manage tags'.\n5. Add new tag and save.","multiregional":false,"service":"Amazon Virtual Private Cloud"},"ecc-aws-448":{"article":"Amazon MWAA can send Apache Airflow logs to Amazon CloudWatch. You can view logs for multiple environments from a single location to easily identify Apache Airflow task delays or workflow errors without the need for additional third-party tools.","impact":"If 'Worker logs' is not enabled and the 'log_level' parameter is not set to the correct value, too many details or too few details may be logged.","report_fields":["Arn"],"remediation":"1. Navigate to https://console.aws.amazon.com/mwaa/home.\n2. Open required environment.\n3. Click 'Edit'. \n4. Click \"Next\".\n5. Enable 'Airflow Worker logs'.\n6. Choose required log level.\n7. Save changes","multiregional":false,"service":"Amazon Managed Workflows for Apache Airflow"},"ecc-aws-003":{"article":"VPC Flow Logs is a feature that enables you to capture information about the IP traffic going to and from network interfaces in your VPC. After you've created a flow log, you can view and retrieve its data in Amazon CloudWatch Logs. It is recommended that VPC Flow Logs be enabled for packet 'Rejects' for VPCs.","impact":"Without enabled VPC Flow Logs, you miss out on the opportunity to detect security and access issues like overly permissive security groups or network ACLs. Also, you cannot receive alerts about abnormal activities triggered within your VPC network, such as rejected connection requests or unusual levels of data transfer.","report_fields":["VpcId","OwnerId"],"remediation":"1. Sign into the management console. \n2. Select Services, then select a VPC. \n3. In the left navigation pane, select Your VPCs. \n4. Select the VPC. \n5. In the right pane, select the Flow Logs tab. \n6. If no Flow Log exists, click Create Flow Log. \n7. For Filter, select Reject. \n8. Enter in Role and Destination Log Group. \n9. Click on Create Log Flow. \n10. Click on CloudWatch Logs Group.","multiregional":false,"service":"Amazon Virtual Private Cloud"},"ecc-aws-423":{"article":"This policy identifies the Log group that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["arn"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon CloudWatch at https://console.aws.amazon.com/cloudwatch.\n2. Click on the 'Log groups'.\n3. Click on the required log group.\n4. Open 'Tags' and click on the 'Manage tags'.\n5. Add new tag and save.","multiregional":false,"service":"Amazon CloudWatch"},"ecc-aws-272":{"article":"Engine version management is designed so that you can have as much control as possible over how patching occurs. \nHowever, ElastiCache reserves the right to patch your cluster on your behalf in the unlikely event of a critical security vulnerability in the system or cache software.","impact":"Without keeping the ElastiCache up-to-date, it is possible to miss out on new software features, bug fixes, security patches, and performance improvements.","report_fields":["ARN"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\nAWS ElastiCache Redis cluster engine version can be set only at the time of the cluster creation. To fix this issue, create a new cluster with the latest engine version, migrate all the required ElastiCache Redis cluster data from the old cluster to the new one, and then delete the old cluster:\n1. Sign in to the AWS Management Console and open the Amazon ElastiCache console at https://console.aws.amazon.com/elasticache/.\n2. From the list in the upper-right corner, choose the AWS Region that you want to launch this cluster in.\n3. Choose 'Redis clusters' from the navigation pane.\n5. Click on the 'Create Redis cluster' button.\n6. For 'Engine version' select the latest one.\n7. Complete 'Cluster settings' page and click 'Next'.\n8. Complete 'Advanced settings' page and click 'Next'.\n9. Click on 'Create' button to launch your new ElastiCache Redis cluster.\n\nTo create an ElastiCache for Memcached cluster:\n1. Sign in to the AWS Management Console and open the ElastiCache console at https://console.aws.amazon.com/elasticache/.\n2. From the list in the upper-right corner, choose the AWS Region you want to launch this cluster in.\n3. Choose 'Memcached clusters' from the navigation pane.\n4. Choose 'Create Memcached cluster'.\n5. Under 'Cluster settings', for 'Engine version' select the latest one.\n6. Complete the 'Cluster settings' section. \n7. Click 'Next'.\n8. Complete the 'Advanced settings' section. \n9. Click 'Next'.\n10. Review all your entries and choices, then go back and make any needed corrections. When you're ready, choose 'Create' to launch your cluster.","multiregional":false,"service":"Amazon ElastiCache"},"ecc-aws-244":{"article":"Enabling the 'log_lock_waits' flag for a PostgreSQL instance creates a log for any session waits that take longer than the allotted deadlock_timeout time to acquire a lock.","impact":"Not logging sessions longer than the deadlock_timeout by enabling \"log_lock_waits\" database flag can lead to insufficient response time to identify poor performance due to locking delays.","report_fields":["DBInstanceArn"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/\n2. In the navigation pane, choose Parameter groups.\n3. Choose the required parameter group\n4. Choose Edit parameters.\n5. Under Parameters, in search bar type \"log_lock_waits\".\n6. Choose 1.\n7. Choose Save changes.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-168":{"article":"TCP port 1433 is used by the Microsoft SQL Server which is a relational database management system developed by Microsoft.\nUnrestricted access (0.0.0.0/0) increases opportunities for malicious activity, such as unauthorized access, denial-of-service attacks, and loss of data.","impact":"Unrestricted access increases the opportunity for malicious activity such as unauthorized access, denial-of-service attacks, and loss of data.","report_fields":["GroupId","VpcId","OwnerId"],"remediation":"From Console:\n1. Login to AWS Management Console and open the EC2 console using https://console.aws.amazon.com/ec2/\n2. In the navigation pane, choose Security Groups.\n3. Select the security group to update, choose Actions, and then choose Edit inbound rules to remove an inbound rule or Edit outbound rules to remove an outbound rule.\n4. Choose the Delete button to the right of the rule to delete.\n5. Choose Preview changes, Confirm.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-174":{"article":"If you use a known port to deploy an RDS cluster, an attacker can guess information about the cluster. The attacker can use this information together with other information to connect to an RDS cluster or instance or gain additional information about your application.\nUsing a custom port can protect against potential brute-force and dictionary attacks. When you change the port, you must also update the existing connection strings that were used to connect to the old port.\nChanging the default port number for RDS database clusters represents a basic security measure and does not completely secure the clusters from port scanning and network attacks.","impact":"If you use a known port to deploy an RDS cluster, an attacker can guess information about the cluster. The attacker can use this information together with other information to connect to an RDS cluster or instance or gain additional information about your application.\nUsing default ports also increases the opportunity for malicious activity such as unauthorized access, denial-of-service attacks, and loss of data.","report_fields":["DBClusterArn"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. Choose Databases.\n3. Select the Cluster to modify.\n4. Choose Modify.\n5. Under Database options, change Database port to a non-default value.\n6. Choose Continue.\n7. Under Scheduling of modifications, choose when to apply modifications. You can choose either Apply during the next scheduled maintenance window or Apply immediately.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-149":{"article":"Check whether Amazon Relational Database Service instances are not publicly accessible. The rule is NON_COMPLIANT if the publicly accessible field is true in the instance configuration item.","impact":"Unrestricted access increases the opportunity for malicious activity such as unauthorized access, denial-of-service attacks, and loss of data.","report_fields":["DBInstanceArn"],"remediation":"Modifying an existing DB instance:\n1. Log in to the AWS management console and navigate to the RDS dashboard at https://console.aws.amazon.com/rds/.\n2. Under the navigation panel, on RDS Dashboard, click 'Databases'.\n3. Select the RDS instance that you want to update.\n4. Click 'Modify' from the dashboard top menu.\n5. On the 'Modify DB Instance panel', under the 'Connectivity' section, click on 'Additional connectivity configuration' and update the value for 'Publicly Accessible' to Not publicly accessible to restrict public access. \nFollow the below steps to update subnet configurations:\n 5.1 Select the 'Connectivity and security' tab, and click on the VPC attribute value inside the 'Networking' section.\n 5.2 Select the 'Details' tab from the VPC dashboard bottom panel and click on Route table configuration attribute value.\n 5.3 On the Route table details page, select the 'Routes' tab from the dashboard bottom panel and click on 'Edit routes'.\n 5.4 On the Edit routes page, update the Destination of Target which is set to 'igw-xxxxx' and click on 'Save routes'.\n6. On the 'Modify DB Instance' panel click on 'Continue' and in the 'Scheduling of modifications' section, perform one of the following actions based on your requirements:\n - Select 'Apply during the next scheduled maintenance window' to apply the changes automatically during the next scheduled maintenance window.\n - Select 'Apply immediately to apply the changes right away'. With this option, any pending modifications will be asynchronously applied as soon as possible, regardless of the maintenance window setting for this RDS database instance. \n Note that any changes available in the pending modifications queue are also applied. If any of the pending modifications require downtime, choosing this option can cause unexpected downtime for the application.\n7. Repeat steps 3 to 6 for each RDS instance available in the current region.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-152":{"article":"Enabling connection draining on Classic Load Balancers ensures that the load balancer stops sending requests to instances that are de-registering or unhealthy. It keeps the existing connections open. This is particularly useful for instances in Auto Scaling groups to ensure that connections aren't severed abruptly.","impact":"Without connection draining, Classic Load Balancers cannot send requests to instances that are de-registering or unhealthy.","report_fields":["LoadBalancerArn"],"remediation":"From Console:\n1. Login to the AWS Management Console and open the EC2 console using https://console.aws.amazon.com/ec2/.\n2. On the navigation pane, under LOAD BALANCING, choose Load Balancers. \n3. Select your load balancer. \n4. On the 'Instances' tab, for 'Connection Draining', choose 'Edit'. \n5. On the 'Configure Connection Draining' page, select 'Enable Connection Draining'. \n6. (Optional) For 'Timeout', type a value between 1 and 3,600 seconds. \n7. Choose 'Save'. \n\nFrom AWS CLI:\naws elb modify-load-balancer-attributes --load-balancer-name my-loadbalancer --load-balancer-attributes \"{{\\\"ConnectionDraining\\\"{{\\\"Enabled\\\"true,\\\"Timeout\\\"300}}}}\"","multiregional":false,"service":"Amazon Elastic Load Balancing"},"ecc-aws-115":{"article":"Checks for expired certificates in the AWS Certificate Manager. It is recommended to delete expired certificates.","impact":"Removing expired AWS ACM certificates eliminates the risk of deploying an invalid SSL/TLS certificate in resources which may trigger an error in the front end and cause a loss of credibility for the web application/website.","report_fields":["CertificateArn","DomainName"],"remediation":"1. Open the AWS console. \n2. On the console, select the specific region.\n3. Navigate to the Certificate Manager(ACM) service. \n4. Select the certificate that was reported. \n5. Verify that the 'Status' column shows 'Expired' for the reported certificate. \n6. Under the 'Actions' drop-down list, click on 'Delete'.","multiregional":false,"service":"AWS Certificate Manager"},"ecc-aws-364":{"article":"A public IPv4 address is an IP address that is reachable from the internet. If you create launch configuration with a public IP address, then your EC2 instance is reachable from the internet.\nA private IPv4 address is an IP address that is not reachable from the internet. You can use private IPv4 addresses for communication between EC2 instances in the same VPC or in your connected private network.","impact":"Instances with a public IP address could potentially be compromised, and an attacker could gain anonymous access to the instance and other resources connected to this instance. This can lead to malicious activity that affects sensitive data.","report_fields":["LaunchConfigurationName"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nTo disable automatic ip association, create new launch configuration:\n1. Open the Amazon EC2 Auto Scaling console at https://console.aws.amazon.com/ec2/.\n2. Under Auto Scaling, click on the Launch Configurations.\n3. Click on the Create launch configuration.\n4. Additional configuration, click on the Advanced details.\n5. Under IP address type, choose 'Do not assign a public IP address to any instances'.\n6. Create configuration.","multiregional":false,"service":"Amazon EC2 Auto Scaling"},"ecc-aws-137":{"article":"Services and databases store data that may be sensitive, protected by law, subject to regulatory requirements or compliance standards. It is highly recommended that access to data be restricted to encrypted protocols. \nThis rule detects network settings that may expose data via unencrypted protocol over the public internet or to an overly wide local scope.","impact":"Unrestricted access increases the opportunity for malicious activity such as unauthorized access, denial-of-service attacks, and loss of data.","report_fields":["LoadBalancerArn"],"remediation":"1. Login to AWS Console at https://console.aws.amazon.com/ec2/.\n2. Go to Load Balancers.\n3. Click on the reported Load Balancer.\n4. In the 'Description' tab, under 'Security' select security group to edit.\n5. In the 'Inbound rules' tab, click 'Edit inbound rules'.\n6. Modify rules.\n7. Click on 'Save rules'.","multiregional":false,"service":"Amazon Elastic Load Balancing"},"ecc-aws-253":{"article":"Ensure that your Amazon Glue Data Catalogs are using KMS Customer Master Keys (CMKs) instead of AWS managed-keys in order to have a more granular control over data-at-rest encryption/decryption process and meet compliance requirements.","impact":"Without AWS KMS Customer Master Keys (CMKs), you do not have full and granular control over who can use the encryption keys to access AWS Glue data.","report_fields":["CatalogId"],"remediation":"1. Sign in to the AWS Management Console and open the AWS Glue console at https://console.aws.amazon.com/glue/.\n2. Choose Settings in the navigation pane.\n3. On the Data catalog settings page, select Metadata encryption, and choose an AWS KMS Customer Master Key.","multiregional":false,"service":"AWS Glue"},"ecc-aws-246":{"article":"If default route table association option is enabled, all Transit Gateway attachments are automatically added to default route table.\nTo manage the VPC environment and the transit gateway, it is preferable to manually establish the table association for the transit gateway.","impact":"With the Default Route Table Association enabled, you have no control over which Transit Gateway Route Table you can associate Transit Gateway with, and without it you can not implement custom routing rules and routing requirements.","report_fields":["TransitGatewayArn"],"remediation":"Perform the following steps in order to set 'Default route table association' to disable:\n1. Sign in to the Amazon VPC console at https://console.aws.amazon.com/vpc/.\n2. Choose Transit Gateways.\n3. Choose relevant gateway and click Actions and select Modify.\n4. Uncheck 'Default route table association'.\n5. Update route table with the necessary routes.","multiregional":false,"service":"AWS Transit Gateway"},"ecc-aws-094":{"article":"Real-time monitoring of API calls can be achieved by directing CloudTrail Logs to CloudWatch Logs and establishing corresponding metric filters and alarms. It is recommended that a metric filter and alarm be established for changes to S3 bucket policies.","impact":"Lack of monitoring and logging of S3 Bucket changes can result in insufficient response time to detect accidental or intentional modifications that may lead to unauthorized data access or other security breaches.","report_fields":["account_id","account_name"],"remediation":"Perform the following to setup the metric filter, alarm, SNS topic, subscription and trail using the AWS CLI: \n1. Create a log group.\naws logs create-log-group --log-group-name \n2. Create a log stream.\naws logs create-log-stream --log-group-name --log-stream-name \n3. Create a metric filter based on provided filter pattern which checks for unauthorized API calls and the taken from step 1.\naws logs put-metric-filter --log-group-name --filter-name `` --metric-transformations metricName=``,metricNamespace=,metricValue='1' --filter-pattern '{{($.eventSource = s3.amazonaws.com) && (($.eventName = PutBucketAcl) || ($.eventName = PutBucketPolicy) || ($.eventName = PutBucketCors) || ($.eventName = PutBucketLifecycle) || ($.eventName = PutBucketReplication) || ($.eventName = DeleteBucketPolicy) || ($.eventName = DeleteBucketCors) || ($.eventName = DeleteBucketLifecycle) || ($.eventName = DeleteBucketReplication))}}'\n4. Create a topic to which notifications will be published.\naws sns create-topic --name \n5. Subscribe an endpoint to an Amazon SNS topic. If the endpoint type is HTTP/S or email, or if the endpoint and the topic are not in the same Amazon Web Services account, the endpoint owner must run the ConfirmSubscription action to confirm the subscription.\naws sns subscribe --topic-arn --protocol email --notification-endpoint \n6. Amazon SNS will send a subscription confirmation message to the endpoint. Confirm subscription to topic, by visiting the link in an email that you specified as notification endpoint.\n7. Create an alarm that is associated with the CloudWatch Logs Metric Filter created in step 3 and an SNS topic created in step 4. \naws cloudwatch put-metric-alarm --alarm-name `` --metric-name `` --statistic Sum --period 300 --threshold 1 --comparison-operator GreaterThanOrEqualToThreshold --evaluation-periods 1 --namespace --alarm-actions \n8. Create an S3 bucket to deliver log files to:\naws s3api create-bucket --bucket \n9. To deliver log files to an S3 bucket, CloudTrail must have the required permissions. The following policy allows CloudTrail to write log files to the bucket from supported regions. Replace myBucketName, [optionalPrefix]/, myAccountID, region, and trailName with the appropriate values for your configuration. \nCreate a file 'policy.json' with the following policy.\n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"AWSCloudTrailAclCheck20150319\",\n \"Effect\": \"Allow\",\n \"Principal\": {\"Service\": \"cloudtrail.amazonaws.com\"},\n \"Action\": \"s3:GetBucketAcl\",\n \"Resource\": \"arn:aws:s3:::\"\n },\n {\n \"Sid\": \"AWSCloudTrailWrite20150319\",\n \"Effect\": \"Allow\",\n \"Principal\": {\"Service\": \"cloudtrail.amazonaws.com\"},\n \"Action\": \"s3:PutObject\",\n \"Resource\": \"arn:aws:s3:::/[optionalPrefix]/AWSLogs//*\",\n \"Condition\": {\n \"StringEquals\": {\n \"s3:x-amz-acl\": \"bucket-owner-full-control\",\n \"aws:SourceArn\": \"arn:aws:cloudtrail:::trail/\"\n }\n }\n }\n ]\n}}\n10. Apply the Amazon S3 bucket policy to the Amazon S3 bucket created in step 8.\naws s3api put-bucket-policy --bucket --policy file://.json\n11. Create a role for CloudTrail that enables it to send events to the CloudWatch Logs log group. To create the JSON file that will contain the policy document, open a text editor and save the following policy contents in a file called 'assume_role_policy_document.json'. \n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Principal\": {\n \"Service\": \"cloudtrail.amazonaws.com\"\n },\n \"Action\": \"sts:AssumeRole\"\n }\n ]\n}}\n12. Create a role.\naws iam create-role --role-name --assume-role-policy-document file://.json\n13. Create the following role policy document for CloudTrail. This document grants CloudTrail the permissions required to create a CloudWatch Logs log stream in the log group you specify and to deliver CloudTrail events to that log stream. Save the policy document in a file called role-policy-document.json. Replace region, accountID, log_group_name, with the appropriate values for your configuration. \n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n\n \"Sid\": \"AWSCloudTrailCreateLogStream2014110\",\n \"Effect\": \"Allow\",\n \"Action\": [\n \"logs:CreateLogStream\"\n ],\n \"Resource\": [\n \"arn:aws:logs:::log-group::log-stream:_CloudTrail_*\"\n ]\n\n },\n {\n \"Sid\": \"AWSCloudTrailPutLogEvents20141101\",\n \"Effect\": \"Allow\",\n \"Action\": [\n \"logs:PutLogEvents\"\n ],\n \"Resource\": [\n \"arn:aws:logs:::log-group::log-stream:_CloudTrail_*\"\n ]\n }\n ]\n}}\n14. Run the following command to apply the policy to the role.\naws iam put-role-policy --role-name --policy-name cloudtrail-policy --policy-document file://.json\n15. Create a trail that specifies the settings for delivery of log data to an Amazon S3 bucket at is associated with the S3 bucket created in step 8, CloudWatch log group created in step 1 and IAM role created in step 12. \naws cloudtrail create-trail --include-global-service-events --is-multi-region-trail --name --s3-bucket-name --cloud-watch-logs-log-group-arn --cloud-watch-logs-role-arn \n16. Start the recording of Amazon Web Services API calls and log file delivery for a trail. For a trail that is enabled in all regions, this operation must be called from the region in which the trail was created.\naws cloudtrail start-logging --name ","multiregional":false,"service":"AWS Account"},"ecc-aws-201":{"article":"Enabling instance deletion protection is an additional layer of protection against accidental database deletion or deletion by an unauthorized entity. \nWhile deletion protection is enabled, an RDS DB instance cannot be deleted. Before a deletion request can succeed, deletion protection must be disabled.","impact":"Accidentally deleted or deleted by an unauthorized entity, the RDS instance can result in data loss.","report_fields":["DBInstanceArn"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. In the navigation pane, choose Databases, then choose the DB instance that you want to modify. \n3. Choose Modify. \n4. Under Deletion protection, choose Enable deletion protection.\n5. Choose Continue. \n6. Under Scheduling of modifications, choose when to apply modifications. The options are Apply during the next scheduled maintenance window or Apply immediately. \n7. Choose Modify DB Instance.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-260":{"article":"Ensure that all Amazon EMR cluster log files are periodically archived and uploaded to S3 in order to keep the logging data for historical purposes or to track and analyze the EMR clusters behavior for a long period of time.\nThis ensures that the log files are available after the cluster terminates, whether this is through normal shut down or due to an error. Amazon EMR archives the log files to Amazon S3 at 5 minute intervals.","impact":"Without periodically archiving and uploading cluster log files to S3, there is a possibility to lose logging data. Because these logs exist on the master node, when the node terminates either because the cluster was shut down or because an error occurred, these log files are no longer available.","report_fields":["ClusterArn"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\n1. Login to the AWS Management Console.\n2. Navigate to EMR dashboard at https://console.aws.amazon.com/elasticmapreduce/.\n3. In the navigation panel, under Amazon EMR, click Clusters to access your AWS EMR clusters page.\n4. Select the EMR cluster that you want to relaunch then click on the Clone button from the dashboard top menu.\n5. Inside the Cloning dialog box, choose 'Yes' to include the steps from the original cluster in the cloned cluster or 'No' to clone the original cluster's configuration without including any of the existing steps. Click Clone to start the cloning process.\n6. On the Create Cluster page, select Step 3: General Cluster Settings from the left navigation panel to access the cloned cluster general settings.\n7. On the General Options panel, under Cluster Name, click on the Logging checkbox to enable the feature. Once enabled, the EMR dashboard will display the S3 default folder path where the cluster log files will be uploaded automatically.\n8. Click the Next button without changing any other configuration options.\n9. On the Security Options panel, click Create Cluster to create your new (cloned) AWS EMR cluster.","multiregional":false,"service":"Amazon EMR"},"ecc-aws-462":{"article":"Lambda functions in a single AWS account in one Region share the concurrency limit. If one function exceeds the concurrent limit, this prevents other functions from being invoked by the Lambda service. You can set reserved concurrency for Lambda functions to ensure that they can be invoked even if the overall capacity has been exhausted.","impact":"If reserved concurrency is disabled and if the overall capacity has been exhausted Lambda functions cannot be invoked.","report_fields":["FunctionArn"],"remediation":"To turn on active tracing:\n1. Open the AWS Lambda console at https://console.aws.amazon.com/lambda/\n2. Navigate to Functions and then select your Lambda function.\n3. Choose 'Configuration' and then choose 'Concurrency'.\n4. Under Concurrency, choose Edit.\n5. Choose Reserve concurrency. Enter the amount of concurrency to reserve for the function.\n6. Choose Save.","multiregional":false,"service":"AWS Lambda"},"ecc-aws-045":{"article":"Password policies, in part, enforce password complexity requirements. Use IAM password policies to ensure that passwords use different character sets. We recommend that the password policy require at least one uppercase letter. \nSetting a password complexity policy increases account resiliency against brute force login attempts.","impact":"Not using a password policy with at least one uppercase letter reduces security and account resiliency against brute-force attacks.","report_fields":["account_id","account_name"],"remediation":"1. Login to the AWS Console (with appropriate permissions to View Identity Access Management Account Settings).\n2. Go to IAM Service on the AWS Console.\n3. Click on Account Settings on the Left Pane.\n4. Check 'Requires at least one uppercase letter'.\n5. Click on 'Apply password policy'.","multiregional":true,"service":"AWS Account"},"ecc-aws-316":{"article":"The 'global_names' parameter specifies whether a database link is required to have the same name as the database to which it connects.","impact":"Not requiring database connections to match the domain that is being called remotely could allow unauthorized domain sources to potentially connect via brute-force tactics.","report_fields":["DBInstanceArn"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/\n2. In the navigation pane, choose Parameter groups.\n3. Choose the required parameter group\n4. Choose Edit parameters.\n5. Under Parameters, in the search bar, type \"global_names\".\n6. Choose TRUE\n7. Choose Save changes.\n8. Restart instance","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-133":{"article":"Amazon GuardDuty is a threat detection service that continuously monitors your AWS accounts and workloads for malicious or unauthorized behavior in order to protect them. It monitors accounts for such activites as unusual API calls or potentially unauthorized deployments that indicate a possible account compromise. GuardDuty also detects potentially compromised instances or reconnaissance by attackers.","impact":"With a disabled GuardDuty service, you lose the opportunity to receive intelligent and centralized threat detection without additional security software or infrastructure to deploy and maintain.","report_fields":["account_id","account_name"],"remediation":"For a standalone account environment, perform the following steps: \n1. Open the GuardDuty console at https//console.aws.amazon.com/guardduty/\n2. Choose Get Started. \n3. Choose Enable GuardDuty. \nFor a multi-account environment, perform the following steps:\n1. Log in to the AWS Organizations management account \n2. Open the GuardDuty console at https://console.aws.amazon.com/guardduty/. \nIf GuardDuty is not already enabled, select Get Started and then designate a GuardDuty delegated administrator on the Welcome to GuardDuty page. \nIf GuardDuty is enabled, designate a GuardDuty delegated administrator on the Settings page. \n3. Enter the twelve-digit AWS account ID of the account that you want to designate as the GuardDuty delegated administrator for the organization and choose Delegate. \n4. To add member accounts, choose Settings in the Navigation pane and then choose Accounts. The accounts table displays all the accounts in the organization. \n5. Choose the accounts that you want to add as members by checking the box next to the account ID. Then select Add member in the Action menu.","multiregional":false,"service":"AWS Account"},"ecc-aws-145":{"article":"This control applies only to AWS Organizations management account.\nReal-time monitoring of API calls can be achieved by directing CloudTrail Logs to CloudWatch Logs and establishing corresponding metric filters and alarms. It is recommended that a metric filter and alarm be established for AWS Organizations changes made in the master AWS Account.\nMonitoring AWS Organizations changes can help you prevent any unwanted, accidental or intentional modifications that may lead to unauthorized access or other security breaches. This monitoring technique helps you to ensure that any unexpected changes performed within your AWS Organizations can be investigated and any unwanted changes can be rolled back.","impact":"Lack of monitoring and logging of Organizations changes calls can lead to insufficient response time to accidental or intentional changes. Overlooking this can result in unauthorized access or other security breaches.","report_fields":["account_id","account_name"],"remediation":"Perform the following to setup the metric filter, alarm, SNS topic, subscription and trail using the AWS CLI: \n1. Create a log group.\naws logs create-log-group --log-group-name \n2. Create a log stream.\naws logs create-log-stream --log-group-name --log-stream-name \n3. Create a metric filter based on provided filter pattern which checks for unauthorized API calls and the taken from step 1.\naws logs put-metric-filter --log-group-name --filter-name `` --metric-transformations metricName=``,metricNamespace='CISBenchmark',metricValue=1 --filter-pattern '{ ($.eventSource = organizations.amazonaws.com) && (($.eventName = \"AcceptHandshake\") || ($.eventName = \"AttachPolicy\") || ($.eventName = \"CreateAccount\") || ($.eventName = \"CreateOrganizationalUnit\") || ($.eventName = \"CreatePolicy\") || ($.eventName = \"DeclineHandshake\") || ($.eventName = \"DeleteOrganization\") || ($.eventName = \"DeleteOrganizationalUnit\") || ($.eventName = \"DeletePolicy\") || ($.eventName = \"DetachPolicy\") || ($.eventName = \"DisablePolicyType\") || ($.eventName = \"EnablePolicyType\") || ($.eventName = \"InviteAccountToOrganization\") || ($.eventName = \"LeaveOrganization\") || ($.eventName = \"MoveAccount\") || ($.eventName = \"RemoveAccountFromOrganization\") || ($.eventName = \"UpdatePolicy\") || ($.eventName = \"UpdateOrganizationalUnit\")) }'\n4. Create a topic to which notifications will be published.\naws sns create-topic --name \n5. Subscribe an endpoint to an Amazon SNS topic. If the endpoint type is HTTP/S or email, or if the endpoint and the topic are not in the same Amazon Web Services account, the endpoint owner must run the ConfirmSubscription action to confirm the subscription.\naws sns subscribe --topic-arn --protocol email --notification-endpoint \n6. Amazon SNS will send a subscription confirmation message to the endpoint. Confirm subscription to topic, by visiting the link in an email that you specified as notification endpoint.\n7. Create an alarm that is associated with the CloudWatch Logs Metric Filter created in step 3 and an SNS topic created in step 4. \naws cloudwatch put-metric-alarm --alarm-name `` --metric-name `` --statistic Sum --period 300 --threshold 1 --comparison-operator GreaterThanOrEqualToThreshold --evaluation-periods 1 --namespace 'CISBenchmark' --alarm-actions \n8. Create an S3 bucket to deliver log files to:\naws s3api create-bucket --bucket \n9. To deliver log files to an S3 bucket, CloudTrail must have the required permissions. The following policy allows CloudTrail to write log files to the bucket from supported regions. Replace myBucketName, [optionalPrefix]/, myAccountID, region, and trailName with the appropriate values for your configuration. \nCreate a file 'policy.json' with the following policy.\n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"AWSCloudTrailAclCheck20150319\",\n \"Effect\": \"Allow\",\n \"Principal\": {\"Service\": \"cloudtrail.amazonaws.com\"},\n \"Action\": \"s3:GetBucketAcl\",\n \"Resource\": \"arn:aws:s3:::\"\n },\n {\n \"Sid\": \"AWSCloudTrailWrite20150319\",\n \"Effect\": \"Allow\",\n \"Principal\": {\"Service\": \"cloudtrail.amazonaws.com\"},\n \"Action\": \"s3:PutObject\",\n \"Resource\": \"arn:aws:s3:::/[optionalPrefix]/AWSLogs//*\",\n \"Condition\": {\n \"StringEquals\": {\n \"s3:x-amz-acl\": \"bucket-owner-full-control\",\n \"aws:SourceArn\": \"arn:aws:cloudtrail:::trail/\"\n }\n }\n }\n ]\n}}\n10. Apply the Amazon S3 bucket policy to the Amazon S3 bucket created in step 8.\naws s3api put-bucket-policy --bucket --policy file://.json\n11. Create a role for CloudTrail that enables it to send events to the CloudWatch Logs log group. To create the JSON file that will contain the policy document, open a text editor and save the following policy contents in a file called 'assume_role_policy_document.json'. \n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Principal\": {\n \"Service\": \"cloudtrail.amazonaws.com\"\n },\n \"Action\": \"sts:AssumeRole\"\n }\n ]\n}}\n12. Create a role.\naws iam create-role --role-name --assume-role-policy-document file://.json\n13. Create the following role policy document for CloudTrail. This document grants CloudTrail the permissions required to create a CloudWatch Logs log stream in the log group you specify and to deliver CloudTrail events to that log stream. Save the policy document in a file called role-policy-document.json. Replace region, accountID, log_group_name, with the appropriate values for your configuration. \n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n\n \"Sid\": \"AWSCloudTrailCreateLogStream2014110\",\n \"Effect\": \"Allow\",\n \"Action\": [\n \"logs:CreateLogStream\"\n ],\n \"Resource\": [\n \"arn:aws:logs:::log-group::log-stream:_CloudTrail_*\"\n ]\n\n },\n {\n \"Sid\": \"AWSCloudTrailPutLogEvents20141101\",\n \"Effect\": \"Allow\",\n \"Action\": [\n \"logs:PutLogEvents\"\n ],\n \"Resource\": [\n \"arn:aws:logs:::log-group::log-stream:_CloudTrail_*\"\n ]\n }\n ]\n}}\n14. Run the following command to apply the policy to the role.\naws iam put-role-policy --role-name --policy-name cloudtrail-policy --policy-document file://.json\n15. Create a trail that specifies the settings for delivery of log data to an Amazon S3 bucket at is associated with the S3 bucket created in step 8, CloudWatch log group created in step 1 and IAM role created in step 12. \naws cloudtrail create-trail --include-global-service-events --is-multi-region-trail --name --s3-bucket-name --cloud-watch-logs-log-group-arn --cloud-watch-logs-role-arn \n16. Start the recording of Amazon Web Services API calls and log file delivery for a trail. For a trail that is enabled in all regions, this operation must be called from the region in which the trail was created.\naws cloudtrail start-logging --name ","multiregional":false,"service":"AWS Account"},"ecc-aws-262":{"article":"If you specified that acceptance is required for connection requests, you must manually accept or reject endpoint connection requests to your endpoint service. Otherwise, endpoint connections are automatically accepted.","impact":"When manual acceptance is disabled it is possible that someone accidentally or on purpose connect an unnecessary service to endpoint and expose sensitive information.","report_fields":["ServiceId"],"remediation":"1. Sign in to the AWS Management Console and open the VPC console at https://console.aws.amazon.com/vpc.\n2. In the navigation pane, choose Endpoint Services. \n3. Choose the name of the intended enpoint service. \n4. Choose the Actions and then choose Modify endpoint acceptance setting.\n5. Enable Acceptance required.","multiregional":false,"service":"Amazon Virtual Private Cloud"},"ecc-aws-158":{"article":"Identification and inventory of your IT assets is a crucial aspect of governance and security. You need to have visibility of all your RDS DB instances so that you can assess their security posture and take action on potential areas of weakness. \nSnapshots should be tagged in the same way as their parent RDS database instances. Enabling this setting ensures that snapshots inherit the tags of their parent database instances.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["DBInstanceArn"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. Choose Databases. \n3. Select the DB instance to modify. \n4. Choose Modify. \n5. Under Backup, select Copy tags to snapshots. \n6. Choose Continue. \n7. Under Scheduling of modifications, choose when to apply modifications. You can choose either Apply during the next scheduled maintenance window or Apply immediately.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-092":{"article":"One or more AMIs are exposed to public internet. It is recommended that you do not publicly share your AMI images with other AWS accounts to avoid sensitive data exposure. If required, AMI images should only be shared with relevant AWS accounts without making them public.","impact":"Publicly shared AMIs with the other AWS accounts may lead to sensitive data exposure. If required, AMI images should only be shared with relevant AWS accounts without making them public.","report_fields":["ImageId","OwnerId"],"remediation":"1. Navigate to the EC2 dashboard at https://console.aws.amazon.com/ec2/.\n2. In the left navigation pane, select IMAGES --> AMIs. \n3. Select the relevant image. \n4. Select the Permissions tab (dashboard bottom panel) and check the current AMI permissions. If the selected image is public, the following status will be displayed on the EC2 dashboard 'This image is currently Public.'. \n5. Repeat steps 3-4 to verify the permissions for the rest of the AMIs available in the current region. \n6. Change the AWS region from the navigation bar and repeat steps 1-5 for the all the regions.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-403":{"article":"This policy identifies the ECS clusters that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["clusterArn"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon ECS at https://console.aws.amazon.com/ecs.\n2. Click on the required ECS cluster.\n3. Open 'Tags' and click on the 'Manage tags'.\n4. Add new tag and save.","multiregional":false,"service":"Amazon Elastic Container Service"},"ecc-aws-528":{"article":"Certificate Transparency logging guards against SSL/TLS certificates issued by mistake or by a compromised certificate authority. Most modern browsers require that public certificates issued for your domain be recorded in a certificate transparency log.","impact":"Browsers no longer trust SSL/TLS certificates that are not recorded in a certificate transparency log.","report_fields":["CertificateArn","DomainName"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\nYou can't use the console to enable transparency logging.\n\nUse AWS CLI to enable transparency logging:\n'aws acm request-certificate \\\n --domain-name www.example.com \\\n --validation-method DNS \\\n --options CertificateTransparencyLoggingPreference=ENABLED \\'","multiregional":false,"service":"AWS Certificate Manager"},"ecc-aws-222":{"article":"Systems Manager is an AWS service that you can use to view and control your AWS infrastructure. To help you to maintain security and compliance, Systems Manager scans your stopped and running managed instances. A managed instance is a machine that is configured for use with Systems Manager. Systems Manager then reports or takes corrective action on any policy violations that it detects. \nSystems Manager also helps you to configure and maintain your managed instances.","impact":"If System Manager is disabled, you will not be able to track policy violations it detects through EC2 instances or take corrective action.","report_fields":["InstanceId","OwnerId"],"remediation":"1. Open the AWS Systems Manager console at https://console.aws.amazon.com/systems-manager/.\n2. In the navigation menu, choose Quick setup. \n3. Choose Create. \n4. Under Configuration type, choose Host Management, then choose Next. \n5. On the configuration screen, you can keep the default options. You can optionally make the following changes: \n5.1. If you use CloudWatch to monitor EC2 instances, select Install and configure the CloudWatch agent and Update the CloudWatch agent once every 30 days. \n5.2. Under Targets, choose the management scope to determine the accounts and Regions where this configuration is applied. \n5.3. Under Instance profile options, select Add required IAM policies to existing instance profiles attached to your instances. \n6. Choose Create.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-446":{"article":"Amazon MWAA can send Apache Airflow logs to Amazon CloudWatch. You can view logs for multiple environments from a single location to easily identify Apache Airflow task delays or workflow errors without the need for additional third-party tools.","impact":"If 'Task logs' is not enabled and the 'log_level' parameter is not set to the correct value, too many details or too few details may be logged.","report_fields":["Arn"],"remediation":"1. Navigate to https://console.aws.amazon.com/mwaa/home.\n2. Open required environment.\n3. Click 'Edit'. \n4. Click \"Next\".\n5. Enable 'Airflow Task logs'.\n6. Choose required log level.\n7. Save changes","multiregional":false,"service":"Amazon Managed Workflows for Apache Airflow"},"ecc-aws-209":{"article":"Amazon Aurora-PostgreSQL database engine logs (Postgresql, Upgrade) should be enabled and sent to CloudWatch.\nDatabase logging provides detailed records of requests made to RDS. Database logs can assist with security and access audits and can help to diagnose availability issues.","impact":"With disabled logs for the database, it is harder to analyze statistics, diagnose issues, detect different types of attacks, and retain data for regulatory or legal purposes.","report_fields":["DBInstanceArn"],"remediation":"To create a custom DB parameter group\n1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. In the navigation pane, choose Parameter groups.\n3. Choose Create parameter group. The Create parameter group window appears.\n4. In the Parameter group family list, choose a DB parameter group family.\n5. In the Type list, choose DB Parameter Group.\n6. In Group name, enter the name of the new DB parameter group.\n7. In Description, enter a description for the new DB parameter group.\n8. Choose Create.\n\nTo enable and publish Aurora-PostgreSQL logs to CloudWatch Logs from the AWS Management Console, set the following parameters in a custom DB Parameter Group:\n- log_statement=all\n- log_min_duration_statement=minimum query duration (ms) to log\n\nTo apply a new DB parameter group or DB options group to an RDS DB instance:\n1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. In the navigation pane, choose Databases.\n3. Choose the DB instance that you want to modify.\n4. Choose Modify. The Modify DB Instance page appears.\n5. Under Database options, change the DB parameter group and DB options group as needed.\n6. When you finish you changes, choose Continue. Check the summary of modifications.\n7. (Optional) Choose Apply immediately to apply the changes immediately. Choosing this option can cause an outage in some cases. \n8. Choose Modify DB Instance to save your changes.\n\nTo publish PostgreSQL logs to CloudWatch Logs from the AWS Management Console\n1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. In the navigation pane, choose Databases.\n3. Choose the DB instance that you want to modify.\n4. Choose Modify.\n5. Under Log exports, choose all of the log files to start publishing to CloudWatch Logs.\n6. Log exports is available only for database engine versions that support publishing to CloudWatch Logs.\n7. Choose Continue. Then on the summary page, choose Modify DB Instance.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-223":{"article":"A State Manager association is a configuration that is assigned to your managed instances. The configuration defines the state that you want to maintain on your instances. For example, an association can specify that antivirus software must be installed and running on your instances or that certain ports must be closed. \nAfter you create one or more State Manager associations, compliance status information is immediately available to you. You can view the compliance status in the console or in response to AWS CLI commands or corresponding Systems Manager API actions. For associations, Configuration Compliance shows the compliance status (Compliant or Non-compliant). It also shows the severity level assigned to the association, such as Critical or Medium.","impact":"If AWS Systems Manager association compliance status is \"NON_COMPLIANT\", it means the task has not been completed, and this needs to be fixed.","report_fields":["InstanceId","OwnerId"],"remediation":"1. Open the AWS Systems Manager console at https://console.aws.amazon.com/systems-manager/.\n2. In the navigation pane, under Node Management, choose Fleet Manager.\n3. Choose the instance ID that has an Association status of Failed.\n4. Choose View details.\n5. Choose Associations.\n6. Note the name of the association that has an Association status of Failed. This is the association that you need to investigate. You need to use the association name in the next step.\n7. In the navigation pane, under Node Management, choose State Manager. Search for the association name, then select the association.\n8. After you determine the issue, edit the failed association to correct the problem. For information on how to edit an association, see 'Edit an association': https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-state-assoc-edit.html.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-055":{"article":"AWS CloudTrail is a web service that records AWS API calls for a given AWS account. The recorded information includes the identity of the API caller, the time of the API call, the source IP address of the API caller, the request parameters, and the response elements returned by the AWS service. CloudTrail uses Amazon S3 for log file storage and delivery, so log files are stored durably. \nIn addition to capturing CloudTrail logs within a specified S3 bucket for long term analysis, realtime analysis can be performed by configuring CloudTrail to send logs to CloudWatch Logs. For a trail that is enabled in all regions in an account, CloudTrail sends log files from all those regions to a CloudWatch Logs log group. \nIt is recommended that CloudTrail logs be sent to CloudWatch Logs. Note: The recommendation intends to ensure the AWS account activity is being captured, monitored, and appropriately alarmed on. CloudWatch Logs is a native way to accomplish this using AWS services, though it does not preclude the use of an alternate solution.","impact":"Failure to integrate CloudTrail trails with CloudWatch logs can lead to insufficient response time to detect anomalous, accidental, or intentional account activity. It can result in unrestricted or unauthorized access.","report_fields":["TrailARN"],"remediation":"1. Sign in to the AWS Management Console and open the CloudTrail console at https://console.aws.amazon.com/cloudtrail/. \n2. Under All Buckets, click on the target bucket you wish to evaluate. \n3. Click on Properties on the top right of the console. \n4. Click on Trails in the left menu. \n5. Click on each trail where no CloudWatch Logs are defined. \n6. Go to the CloudWatch Logs section and click on Configure. \n7. Define a new or select an existing log group. \n8. Click Continue.\n9. Configure IAM Role which will deliver CloudTrail events to CloudWatch Logs:\n- Create/Select an IAM Role and Policy Name;\n- Click Allow to continue.","multiregional":false,"service":"AWS CloudTrail"},"ecc-aws-015":{"article":"The root account is the most privileged user in an AWS account. MFA adds an extra layer of protection on top of a username and password. With MFA enabled, when a user signs in to an AWS website, they will be prompted for their username and password as well as for an authentication code from their AWS MFA device. \nNote: When virtual MFA is used for root accounts, it is recommended that the device used is NOT a personal device, but rather a dedicated mobile device (tablet or phone) that is managed to be kept charged and secured independent of any individual personal devices. ('non-personal virtual MFA') This lessens the risks of losing access to MFA due to the device loss, device trade-in or if the individual owning the device is no longer employed by the company.","impact":"One layer of protection (a password) for a root account is not enough to prevent credentials from being compromised. The likelihood of a password being cracked and an account being compromised is much higher without MFA enabled.","report_fields":["account_id","account_name"],"remediation":"Perform the following to establish MFA for the root account: \n1. Sign in to the AWS Management Console and open the IAM console. \n2. Choose Dashboard, and under Security Status, expand Activate MFA on your root account. \n3. Choose Activate MFA. \n4. In the wizard, choose a virtual MFA device and then choose Next Step. \n5. IAM generates and displays configuration information for the virtual MFA device, including a QR code graphic. The graphic is a representation of the 'secret configuration key' that is available for manual entry on devices that do not support QR codes.\n6. Open your virtual MFA application. If the virtual MFA application supports multiple accounts, choose the option to create a new account. \n7. Determine whether the MFA app supports QR codes and then do one of the following: \n- Use the app to scan the QR code. For example, you might choose the camera icon or choose an option similar to Scan code and then use the device's camera to scan the code. \n- In the Manage MFA Device wizard, choose Show secret key for manual configuration and then type the secret configuration key into your MFA application. When finished, the virtual MFA device starts generating one-time passwords. \n7.1. In the Manage MFA Device wizard, in the Authentication Code 1 box, type the one-time password that currently appears in the virtual MFA device. \nWait up to 30 seconds for the device to generate a new one-time password. \nThen type the second one-time password in the Authentication Code 2 box. \nChoose Active Virtual MFA.","multiregional":true,"service":"AWS Account"},"ecc-aws-191":{"article":"Including EFS file systems in the backup plans helps you to protect your data from deletion and data loss.","impact":"Without a backup plan, it is impossible to quickly recover data in the event of failure.","report_fields":["FileSystemArn","OwnerId"],"remediation":"1. Open the Amazon Elastic File System console at https//console.aws.amazon.com/efs/.\n2. On the File systems page, choose the file system for which to enable automatic backups. The File system details page is displayed. \n3. Under General, choose Edit.\n4. To enable automatic backups, select Enable automatic backups. \n5. Choose Save changes.","multiregional":false,"service":"Amazon Elastic File System"},"ecc-aws-059":{"article":"This control is non-compliant if AWS Config recorder does not record all resources, or does not include global resources, or if the recording is disabled or has the status 'FAILURE'.\nAWS Config is a web service that performs configuration management of supported AWS resources within your account and delivers log files to you. The recorded information includes the configuration item (AWS resource), relationships between configuration items (AWS resources), any configuration changes between resources. It is recommended that AWS Config be enabled in all regions.\nTo exercise better governance over your resource configurations and to detect resource misconfigurations, you need fine-grained visibility into what resources exist and how these resources are configured at any time. You can use AWS Config to notify you whenever resources are created, modified, or deleted without having to monitor these changes by polling the calls made to each resource. \nWhen you use multiple AWS resources that depend on one another, a change in the configuration of one resource might have unintended consequences on related resources. With AWS Config, you can view how the resource you intend to modify is related to other resources and assess the impact of your change. You can also use the historical configurations of your resources provided by AWS Config to troubleshoot issues and to access the last known good configuration of a problem resource.","impact":"Failure to audit and evaluate AWS resource configurations using AWS Config can result in a loss of ability to automate the evaluation of recorded configurations against desired configurations. \nAdditionally, this can lead to increased complexity in security analysis, change management, compliance auditing, and operational troubleshooting.","report_fields":["account_id","account_name"],"remediation":"1. Sign in to the AWS Management Console and open the AWS Config console at https://console.aws.amazon.com/config/.\n2. On the top right of the console select target Region.\n3. If presented with 'Setup AWS Config' - follow remediation procedure:\n4. For 'Resource types to record' select 'Record all resources supported in this region'.\n 4.1 Click on 'Get started'.\n 4.2 For 'Resource types to record' select 'Record all resources supported in this region'.\n 4.3 Choose 'Include global resources (IAM resources)'.\n 4.4 Specify an S3 bucket either in the same account or in another managed AWS account.\n 4.5 (Optional) Create a SNS Topic either from the same AWS account or from another managed AWS account.\n 4.6 Click 'Next'. \n 4.7 (Optional) On the 'Rules' page, you can specify the rules that you want AWS Config to use to evaluate compliance information for the recorded resource types.\n 4.8 Click 'Next'. Review your AWS Config setup details. You can go back to edit changes for each section. \n 4.9 Choose 'Confirm' to finish setting up AWS Config.\n5. If AWS Config already enabled, check the following:\n 5.1 Navigate to 'Settings' tab on the left panel and click 'Edit'\n 5.2 On the 'Edit settings' select 'Enable recording' if it is not enabled yet.\n 5.3 Make sure that 'Record all resources supported in this region' selected for 'Resource types to record'.\n 5.3 Make sure that 'Include global resources (IAM resources)' selected.\n 5.4 Click on 'Save'.\n6. Check that AWS Config recorder does not have status 'FAILURE'. You can check it using AWS CLI command: \"aws configservice describe-configuration-recorder-status\". If status is 'lastStatus' is 'FAILURE', look at values of 'lastErrorCode' and 'lastErrorMessage' parameters for information about the type of problem.\nFollow this AWS Guide on how to troubleshoot AWS Config console error messages: https://aws.amazon.com/premiumsupport/knowledge-center/config-console-error/.","multiregional":true,"service":"AWS Config"},"ecc-aws-289":{"article":"Ensure AutoScaling Groups do not have configuration issues which could prevent the ASG from functioning properly or launching instances.\nThe following AutoScaling Group items are checked:\n - invalid subnets\n - invalid security groups\n - invalid key pair name\n - invalid launch config volume snapshots\n - invalid AMIs\n - invalid ELB health check","impact":"If an AutoScaling Group has an invalid configuration, the AutoScaling Group will not function properly or be able to launch instances.\nWhen AutoScaling Groups fail to launch new EC2 instances due to invalid configuration, the scaling mechanism is unable to add compute resources to handle the increased traffic load and this will cause a significant negative impact on performance and can lead to downtime.","report_fields":["AutoScalingGroupARN"],"remediation":"To retrieve an error message from the description of scaling activities, use the 'aws autoscaling describe-scaling-activities' command. \nUse the following link to find a solution for an issue: https://docs.aws.amazon.com/autoscaling/ec2/userguide/CHAP_Troubleshooting.html.","multiregional":false,"service":"Amazon EC2 Auto Scaling"},"ecc-aws-409":{"article":"This policy identifies the EMR clusters that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["ClusterArn"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon EMR at https://console.aws.amazon.com/elasticmapreduce.\n2. Click on the required cluster.\n3. Find Tags and click on the 'Edit'.\n4. Add new tag and save.","multiregional":false,"service":"Amazon EMR"},"ecc-aws-268":{"article":"ElastiCache for Redis supports symmetric customer managed AWS KMS keys (KMS key) for encryption at rest. \nCustomer-managed KMS keys are encryption keys that you create, own and manage in your AWS account.","impact":"Without KMS CMK customer-managed keys, you do not have full and granular control over who can access encrypted Elasticache data.","report_fields":["ARN"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nAWS ElastiCache Redis cluster at-rest encryption can be set only at the time of the cluster creation. To fix this issue, create a new cluster with at-rest encryption, migrate all the required ElastiCache Redis cluster data from the unencrypted cluster to the new one, and then delete the old cluster.\nEnabling at-rest encryption using KMS CMK on an existing ElastiCache Redis cluster:\n1. Create a manual backup of the cluster:\n 1.1. Sign in to the AWS Management Console and open the Amazon ElastiCache console at https://console.aws.amazon.com/elasticache/.\n 1.2. From the list in the upper-right corner, choose the AWS Region that you want to launch this cluster in.\n 1.3. Choose 'Redis clusters' from the navigation pane.\n 1.4. Choose the cluster and choose 'Action', and then 'Backup'.\n 1.5. Make sure the cluster name is right and enter backup name. \n 1.6. Select 'Encryption key'. \n 1.7. Choose 'Create Backup'.\n2. Create a new cluster by restoring from the backup:\n 2.1. Sign in to the AWS Management Console and open the ElastiCache console at https://console.aws.amazon.com/elasticache/.\n 2.2. Choose 'Redis clusters' from the navigation pane.\n 2.3. Select 'Create Redis cluster'.\n 2.4. For 'Choose a cluster creation method', choose 'Restore from backups'.\n 2.5. For 'Source' select 'Amazon ElastiCache backups'. And select backup created on the previous step.\n 2.6. Complete other settings and click 'Next'.\n 2.7. At the 'Security' section, enable 'Encryption at rest'. And select a KMS CMK key that will be used to protect the key used to encrypt data at rest for this cluster.\n 2.8. Complete other settings and click 'Next'.\n 2.9. Review settings and click 'Create'.\n3. Update the endpoints in your application to the new cluster's endpoints.\n4. Delete the old cluster.\n\nTo create a new ElastiCache Redis cluster with at-rest encryption set using KMS CMK, perform the following:\n1. Sign in to the AWS Management Console and open the Amazon ElastiCache console at https://console.aws.amazon.com/elasticache/.\n2. From the list in the upper-right corner, choose the AWS Region that you want to launch this cluster in.\n3. Choose 'Redis clusters' from the navigation pane.\n5. Click on the 'Create Redis cluster' button.\n6. Complete 'Cluster settings' page and click 'Next'.\n7. At the 'Security' section, enable 'Encryption at rest'. And select a KMS CMK key that will be used to protect the key used to encrypt data at rest for this cluster.\n8. Click on 'Create' button to launch your new ElastiCache Redis cluster.","multiregional":false,"service":"Amazon ElastiCache"},"ecc-aws-334":{"article":"Server-Side Encryption (SSE) for Amazon Kinesis Firehose delivery streams helps you meet these security requirements by providing an extra layer of protection for your Kinesis data-at-rest. \nThe data can be encrypted with either AWS KMS default keys or KMS Customer Master Keys (CMKs). Use customer-managed CMKs for your AWS services whenever possible to have full control over all operations related to CMK permissions, management, and lifecycle.\nThis control checks only Amazon Kinesis Firehose delivery streams that have Source type \"Direct PUT\". Because when you configure a Kinesis data stream as the data source of a Kinesis Data Firehose delivery stream, Kinesis Data Firehose no longer stores the data at rest. Instead, the data is stored in the data stream.","impact":"Disabled SSE encryption allows a user to get unauthorized access to sensitive data in Amazon Kinesis Firehose delivery streams.","report_fields":["DeliveryStreamARN"],"remediation":"To enable server-side encryption for Amazon Kinesis Data Firehose service:\n1. Navigate to the Amazon Kinesis Data Firehose dashboard at https://console.aws.amazon.com/firehose/.\n2. Choose the delivery stream that you want to reconfigure, then click on its name to access the resource configuration.\n3. Select the 'Configuration' tab and scroll down to 'Server-side encryption (SSE)' section and click the 'Edit' button.\n4. Tick the box 'Enable server-side encryption for source records in delivery stream'.\n5. Select the 'Encryption type'. Select the default AWS owned CMK key or an AWS KMS Customer Master Key (CMK). It is recommended to use AWS KMS CMK.\n6. Click 'Save changes' to apply the configuration changes.","multiregional":false,"service":"Amazon Kinesis"},"ecc-aws-105":{"article":"Rotate the keys of your Kinesis Streams in order to protect your data and metadata from breach or unauthorized access and fulfill compliance requirements for key management within your organization.","impact":"Not rotated Kinesis Streams Keys can lead to breaches or unauthorized access and not fulfill compliance requirements for key management within your organization.","report_fields":["StreamARN"],"remediation":"1. Navigate to the KMS dashboard at https://console.aws.amazon.com/kms/.\n2. In the left navigation pane, click on the Customer managed keys. \n3. Select the alias of the CMK that you need to check under the Alias column. \n4. In the Key Rotation section, enable the 'Rotate this key every year' checkbox. Note: AWS managed keys are automatically rotated every 3 years.","multiregional":false,"service":"Amazon Kinesis"},"ecc-aws-461":{"article":"When lambda functions using the latest version of the implemented runtime environment, functions benefit from new features and enhancements, better security, performance and reliability","impact":"Without keeping the Lambda functions runtime up-to-date, it is possible to miss out security patches or other updates. And eventually it will be impossible to update the function.","report_fields":["FunctionArn"],"remediation":"Use AWS CLI to update lambda runtime version: \naws lambda update-function-configuration --function-name \"function_name\" --runtime \"runtime_version\"","multiregional":false,"service":"AWS Lambda"},"ecc-aws-157":{"article":"Identification and inventory of your IT assets is a crucial aspect of governance and security. You need to have visibility of all your RDS DB clusters so that you can assess their security posture and take action on potential areas of weakness.\nSnapshots should be tagged in the same way as their parent RDS database clusters. Enabling this setting ensures that snapshots inherit the tags of their parent database clusters.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["DBClusterArn"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. Choose Databases. \n3. Select the DB cluster to modify. \n4. Choose Modify. \n5. Under Backup, select Copy tags to snapshots.\n6. Choose Continue.\n7. Under Scheduling of modifications, choose when to apply modifications. You can choose either Apply during the next scheduled maintenance window or Apply immediately.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-540":{"article":"The AWS Glue version determines the versions of Apache Spark and Python that AWS Glue supports. The Python version indicates the version that's supported for jobs of type Spark","impact":"Without keeping the Glue up-to-date, it is possible to miss out on new features, bug fixes, security patches, and performance improvements.","report_fields":["Name"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon Glue at https://console.aws.amazon.com/glue.\n2. Under the 'AWS Glue Studio' click on the 'Jobs'.\n3. Click on the required job.\n4. Open 'Job details' and click on the 'Glue version'.\n5. Choose the latest version.\n6. Save.","multiregional":false,"service":"AWS Glue"},"ecc-aws-020":{"article":"A good cloud provisioning strategy should include tagging of resources. Tags may be used to associate function, owner, environment, or other attributes with application instances, and a consistent tagging strategy is a recommended best practice.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["InstanceId","OwnerId"],"remediation":"1. Sign in to the AWS Management Console and open the AWS Config console at https://console.aws.amazon.com/config/. \n2. Choose Rules on the left and then, on the Rules page, choose Add Rule to add new rules to the rule list. For existing rules, select the noncompliant rule from the rule list and choose Edit. \n3. On the Rules page, go to Edit Name and, in the Choose remediation action section, choose the appropriate remediation action from the recommended list. Depending on the selected remediation action, you can see either specific parameters or no parameters. \n4. (Optional) If you want to pass the resource ID of noncompliant resources to the remediation action, choose the Resource ID parameter. Once selected, during runtime, this parameter is substituted with the ID of the resource to be remediated. Each parameter has either a static value or a dynamic one. If you do not choose a specific resource ID parameter from the drop-down list, you can enter values for each key. If you choose a resource ID parameter from the drop-down list, you can enter values for all the other keys except the selected resource ID parameter.\n5. Choose Save. The Rules page is displayed.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-028":{"article":"This policy identifies security group rules that allow inbound traffic to the DNS port (53) from the public internet. Allowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Unrestricted DNS access can increase opportunities for malicious activity such as Denial of Service (DoS) attacks or Distributed Denial of Service (DDoS) attacks. \nAlso, DNS can be used by attackers as one of their reconnaissance techniques.","report_fields":["GroupId","VpcId","OwnerId"],"remediation":"1. Login to the AWS Console.\n2. Go to Security Group.\n3. Go to the SG rules.\n4. Click on the reported Firewall rule.\n5. Click on Edit.\n6. Modify the rule.\n7. Click on Save.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-546":{"article":"Retain audit logs across enterprise assets for a minimum of 90 days.\nRetaining historical audit logs is necessary because compromises often go unnoticed for significant lengths of time.\nBy having three months of logs immediately available, an entity can quickly identify and minimize impact of a data breach.","impact":"Without formal processes to detect exceptions and anomalies, the entity may be unaware of unauthorized and potentially malicious activities occurring within their network.\nHaving centrally stored log history allows investigators to better determine the length of time a potential breach was occurring, and the possible system(s) impacted.","report_fields":["StreamARN"],"remediation":"To update a data stream using the console:\n1. Open the Amazon Kinesis console at https://console.aws.amazon.com/kinesis/.\n2. In the navigation bar, expand the Region selector and choose a Region.\n3. Choose the name of your stream in the list and open Configuration tab.\n4. To edit the data retention period, choose Edit in the Data retention period section, and then enter a new data retention period.\n\nTo update a data stream using the CLI:\n1. Use the following command to increase the stream retention period. Replace the --stream-name with the name of the stream and --retention-period-hours according to your organization policy.\n aws kinesis increase-stream-retention-period --stream-name samplestream --retention-period-hours 2160\n\nThis command produces no output. The retention period (the length of time data records are accessible after they are added to the stream) of the specified stream will be changed to 2160 hours.","multiregional":false,"service":"Amazon Kinesis"},"ecc-aws-287":{"article":"To take advantage of the safety and reliability of geographic redundancy, span Auto Scaling group across multiple Availability Zones within a Region.\nWhen one Availability Zone becomes unhealthy or unavailable, Amazon EC2 Auto Scaling launches new instances in an unaffected Availability Zone. When the unhealthy Availability Zone returns to a healthy state, Amazon EC2 Auto Scaling automatically redistributes the application instances evenly across all the Availability Zones for your Auto Scaling group.","impact":"When an Auto Scaling group (ASG) is not configured for multiple Availability Zones, in case of any issue with the Availability Zone, it will not be reachable. This deteriorates the availability and reliability of the ASG.","report_fields":["AutoScalingGroupARN"],"remediation":"Use the following procedure to expand Auto Scaling group to a subnet in an additional Availability Zone. \n1. Open the Amazon EC2 Auto Scaling console at https://console.aws.amazon.com/ec2autoscaling/.\n2. Select the Auto Scaling group that you want to modify.\n3. On the Details tab, choose Network, Edit.\n4. Choose the subnets corresponding to the Availability Zone that you want to add to the Auto Scaling group. Make sure that at least two subnets are selected.\n5. Choose Update.\n6. To update the Availability Zones for your load balancer so that it shares the same Availability Zones as your Auto Scaling group, complete the following steps:\n a. On the navigation pane, under LOAD BALANCING, choose Load Balancers.\n b. Choose your load balancer.\n c. Do one of the following:\n For Application Load Balancers and Network Load Balancers:\n i. On the Description tab, for Availability Zones, choose Edit subnets.\n ii. On the Edit subnets page, for Availability Zones, select the check box for the Availability Zone to add. If there is only one subnet for that zone, it is selected. If there is more than one subnet for that zone, select one of the subnets.\n For Classic Load Balancers in a VPC:\n i. On the Instances tab, choose Edit Availability Zones.\n ii. On the Add and Remove Subnets page, for Available subnets, select the subnet using its add (+) icon. The subnet is moved under Selected subnets.\n For Classic Load Balancers in EC2-Classic:\n i. On the Instances tab, choose Edit Availability Zones.\n ii. On the Add and Remove Availability Zones page, choose the Availability Zone to add.\n d. Choose Save.","multiregional":false,"service":"Amazon EC2 Auto Scaling"},"ecc-aws-056":{"article":"AWS console defaults to no check boxes selected when creating a new IAM user. When creating the IAM User credentials you have to determine what type of access they require. \nProgrammatic access: The IAM user might need to make API calls, use the AWS CLI, or use the Tools for Windows PowerShell. In that case, create an access key (access key ID and a secret access key) for that user. \nAWS Management Console access: If the user needs to access the AWS Management Console, create a password for the user.","impact":"Creating access keys at the same time as the user account, rather than when needed, increases the attack surface of access keys that can remain unused and lead to compromise. Additionally, unnecessary AWS IAM access keys generate unnecessary management work in auditing and rotating IAM credentials.","report_fields":["Arn"],"remediation":"1. Login to the AWS Management Console.\n2. Click on Services.\n3. Click on IAM.\n4. Click on Users.\n5. Click on Security Credentials.\n6. As an Administrator:\n - Click on the X (Delete) for keys that were created at the same time as the user profile but have not been used. \n7. As an IAM User:\n - Click on the X (Delete) for keys that were created at the same time as the user profile but have not been used.","multiregional":true,"service":"AWS Identity and Access Management"},"ecc-aws-469":{"article":"Desync mitigation mode protects your application from issues due to HTTP Desync. The load balancer classifies each request based on its threat level, allows safe requests, and then mitigates risk as specified by the mitigation mode that you specify. The desync mitigation modes are monitor, defensive, and strictest. The default is the defensive mode, which provides durable mitigation against HTTP desync while maintaining the availability of your application.","impact":"HTTP Desync issues can lead to request smuggling and make applications vulnerable to request queue or cache poisoning. In turn, these vulnerabilities can lead to execution of unauthorized commands.","report_fields":["LoadBalancerArn"],"remediation":"1. Open the Amazon EC2 console at https://console.aws.amazon.com/ec2/.\n2. In the navigation pane, choose Load balancers. \n3. Choose an Application Load Balancer. \n4. From Actions, choose Edit attributes. \n5. For Desync mitigation mode choose either Defensive or Strictest.\n6. Choose Save.","multiregional":false,"service":"Amazon Elastic Load Balancing"},"ecc-aws-079":{"article":"Real-time monitoring of API calls can be achieved by directing CloudTrail Logs to CloudWatch Logs and establishing corresponding metric filters and alarms. It is recommended that a metric filter and alarm be established for changes made to Identity and Access Management (IAM) policies.","impact":"Lack of monitoring and logging of IAM policy changes calls can lead to insufficient response time to accidental or intentional changes that may result in unauthorized access.","report_fields":["account_id","account_name"],"remediation":"Perform the following to setup the metric filter, alarm, SNS topic, subscription and trail using the AWS CLI: \n1. Create a log group.\naws logs create-log-group --log-group-name \n2. Create a log stream.\naws logs create-log-stream --log-group-name --log-stream-name \n3. Create a metric filter based on provided filter pattern which checks for unauthorized API calls and the taken from step 1.\naws logs put-metric-filter --log-group-name --filter-name `` --metric-transformations metricName=``,metricNamespace=,metricValue='1' --filter-pattern '{{($.eventName=DeleteGroupPolicy) || ($.eventName=DeleteRolePolicy) || ($.eventName=DeleteUserPolicy) || ($.eventName=PutGroupPolicy) || ($.eventName=PutRolePolicy) || ($.eventName=PutUserPolicy) || ($.eventName=CreatePolicy) || ($.eventName=DeletePolicy) || ($.eventName=CreatePolicyVersion) || ($.eventName=DeletePolicyVersion) || ($.eventName=AttachRolePolicy) || ($.eventName=DetachRolePolicy) || ($.eventName=AttachUserPolicy) || ($.eventName=DetachUserPolicy) || ($.eventName=AttachGroupPolicy) || ($.eventName=DetachGroupPolicy)}}'\n4. Create a topic to which notifications will be published.\naws sns create-topic --name \n5. Subscribe an endpoint to an Amazon SNS topic. If the endpoint type is HTTP/S or email, or if the endpoint and the topic are not in the same Amazon Web Services account, the endpoint owner must run the ConfirmSubscription action to confirm the subscription.\naws sns subscribe --topic-arn --protocol email --notification-endpoint \n6. Amazon SNS will send a subscription confirmation message to the endpoint. Confirm subscription to topic, by visiting the link in an email that you specified as notification endpoint.\n7. Create an alarm that is associated with the CloudWatch Logs Metric Filter created in step 3 and an SNS topic created in step 4. \naws cloudwatch put-metric-alarm --alarm-name `` --metric-name `` --statistic Sum --period 300 --threshold 1 --comparison-operator GreaterThanOrEqualToThreshold --evaluation-periods 1 --namespace --alarm-actions \n8. Create an S3 bucket to deliver log files to:\naws s3api create-bucket --bucket \n9. To deliver log files to an S3 bucket, CloudTrail must have the required permissions. The following policy allows CloudTrail to write log files to the bucket from supported regions. Replace myBucketName, [optionalPrefix]/, myAccountID, region, and trailName with the appropriate values for your configuration. \nCreate a file 'policy.json' with the following policy.\n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"AWSCloudTrailAclCheck20150319\",\n \"Effect\": \"Allow\",\n \"Principal\": {\"Service\": \"cloudtrail.amazonaws.com\"},\n \"Action\": \"s3:GetBucketAcl\",\n \"Resource\": \"arn:aws:s3:::\"\n },\n {\n \"Sid\": \"AWSCloudTrailWrite20150319\",\n \"Effect\": \"Allow\",\n \"Principal\": {\"Service\": \"cloudtrail.amazonaws.com\"},\n \"Action\": \"s3:PutObject\",\n \"Resource\": \"arn:aws:s3:::/[optionalPrefix]/AWSLogs//*\",\n \"Condition\": {\n \"StringEquals\": {\n \"s3:x-amz-acl\": \"bucket-owner-full-control\",\n \"aws:SourceArn\": \"arn:aws:cloudtrail:::trail/\"\n }\n }\n }\n ]\n}}\n10. Apply the Amazon S3 bucket policy to the Amazon S3 bucket created in step 8.\naws s3api put-bucket-policy --bucket --policy file://.json\n11. Create a role for CloudTrail that enables it to send events to the CloudWatch Logs log group. To create the JSON file that will contain the policy document, open a text editor and save the following policy contents in a file called 'assume_role_policy_document.json'. \n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Principal\": {\n \"Service\": \"cloudtrail.amazonaws.com\"\n },\n \"Action\": \"sts:AssumeRole\"\n }\n ]\n}}\n12. Create a role.\naws iam create-role --role-name --assume-role-policy-document file://.json\n13. Create the following role policy document for CloudTrail. This document grants CloudTrail the permissions required to create a CloudWatch Logs log stream in the log group you specify and to deliver CloudTrail events to that log stream. Save the policy document in a file called role-policy-document.json. Replace region, accountID, log_group_name, with the appropriate values for your configuration. \n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n\n \"Sid\": \"AWSCloudTrailCreateLogStream2014110\",\n \"Effect\": \"Allow\",\n \"Action\": [\n \"logs:CreateLogStream\"\n ],\n \"Resource\": [\n \"arn:aws:logs:::log-group::log-stream:_CloudTrail_*\"\n ]\n\n },\n {\n \"Sid\": \"AWSCloudTrailPutLogEvents20141101\",\n \"Effect\": \"Allow\",\n \"Action\": [\n \"logs:PutLogEvents\"\n ],\n \"Resource\": [\n \"arn:aws:logs:::log-group::log-stream:_CloudTrail_*\"\n ]\n }\n ]\n}}\n14. Run the following command to apply the policy to the role.\naws iam put-role-policy --role-name --policy-name cloudtrail-policy --policy-document file://.json\n15. Create a trail that specifies the settings for delivery of log data to an Amazon S3 bucket at is associated with the S3 bucket created in step 8, CloudWatch log group created in step 1 and IAM role created in step 12. \naws cloudtrail create-trail --include-global-service-events --is-multi-region-trail --name --s3-bucket-name --cloud-watch-logs-log-group-arn --cloud-watch-logs-role-arn \n16. Start the recording of Amazon Web Services API calls and log file delivery for a trail. For a trail that is enabled in all regions, this operation must be called from the region in which the trail was created.\naws cloudtrail start-logging --name ","multiregional":false,"service":"AWS Account"},"ecc-aws-281":{"article":"A scaling cooldown helps you prevent your Auto Scaling group from launching or terminating additional instances before the effects of previous activities are visible.\nIf the cooldown period is set to 0, it means that scaling activities initiated by simple scaling policies can start at any moment and cause the initiation of an additional scaling activity.\nIt is recommended to set a cooldown period of at least 300 seconds, this is a value that AWS sets by default.","impact":"If the cooldown period is set to 0, it means that scaling activities initiated by simple scaling policies can start at any moment and cause the initiation of an additional scaling activity and repeatedly trigger unnecessary scaling actions.","report_fields":["AutoScalingGroupARN"],"remediation":"In order to update the default cooldown period, perform the following: \n1. Open the Amazon EC2 Auto Scaling console at https://console.aws.amazon.com/ec2autoscaling/.\n2. Choose auto scaling group that you want to modify.\n3. Under 'Advanced configurations', click 'Edit' button.\n4. Set a new value for 'Default cooldown'.\n5. Choose 'Update'.","multiregional":false,"service":"Amazon EC2 Auto Scaling"},"ecc-aws-460":{"article":"For storing sensitive information, you can encrypt environment variable values prior to sending them to Lambda by using the console's encryption helpers. This adds an additional layer of encryption that obscures secret values in the Lambda console and API output, even for users who have permission to use the key. In your code, you retrieve the encrypted value from the environment and decrypt it by using the AWS KMS API.","impact":"Without encrypting environment variable values in transit, there is a possibility of unauthorized access or accidental exposure (in the Lambda console and API output) of sensitive and critical data stored in variables.","report_fields":["FunctionArn"],"remediation":"1. Use the AWS Key Management Service (AWS KMS) to create any customer managed keys for Lambda to use for server-side and client-side encryption. \n2. Login to the AWS Management Console and open the Amazon Lambda https://console.aws.amazon.com/lambda/.\n3. In the navigation pane click on the 'Functions'.\n4. Click on the required function.\n5. Click on the 'Configuration' and then 'Environment variables'.\n6. Click 'Edit'.\n7. Under 'Encryption in transit', choose 'Enable helpers for encryption in transit'.\n8. For each environment variable that you want to enable console encryption helpers for, choose 'Encrypt' next to the environment variable.\n9. Under 'AWS KMS key to encrypt in transit', choose a customer managed key that you created at the beginning of this procedure.\n10. Choose 'Execution role policy' and copy the policy. This policy grants permission to your function's execution role to decrypt the environment variables.\n11. Save this policy to use in the last step of this procedure.\n12. Add code to your function that decrypts the environment variables. Choose 'Decrypt secrets snippet' to see an example.\n13. Click 'Encrypt'.\n14. Choose 'Save'.\n15. Set up permissions. If you're enabling client-side encryption for security in transit, your function needs permission to call the 'kms:Decrypt' API operation. Add the policy that you saved previously in this procedure to the function's execution role.","multiregional":false,"service":"AWS Lambda"},"ecc-aws-477":{"article":"Configuring an SNS notification with your CloudFormation stack helps immediately notify stakeholders of any events or changes occurring with the stack. The stack must not only have an SNS topic configured, but must also have at least one confirmed subscription to that topic.","impact":"Not enabling SNS notification can lead to slow or no response to a stack changes.","report_fields":["StackId"],"remediation":"1. Open the CloudFormation console at https://console.aws.amazon.com/cloudformation/.\n2. Select stack for which you want to configure notifications.\n3. Click 'Update' on the top bar.\n4. Click 'Next' two times.\n5. On the 'Configure stack options' scroll down to the 'Notification options'.\n6. Select an existing SNS topic or create new.\n7. Click 'Next'.\n8. Click 'Update stack'.\n9. Navigate to the SNS console https://console.aws.amazon.com/sns/v3.\n10. Click 'Topics'.\n11. Select the topic that was configured at the step 6.\n12. On the 'Subscriptions' tab, click 'Create subscription'.\n13. Configure subscription and click 'Create subscription'.\n14. Confirm subscription'.","multiregional":false,"service":"AWS CloudFormation"},"ecc-aws-388":{"article":"This policy identifies the Transit gateway that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["TransitGatewayArn"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon VPC at https://console.aws.amazon.com/vpc/.\n2. Click on the 'Transit gateway'.\n3. Click on the required transit gateway.\n4. Open 'Tags' and click on the 'Manage tags'.\n5. Add new tag and save.","multiregional":false,"service":"AWS Transit Gateway"},"ecc-aws-070":{"article":"A security group should always have attached protected assets. Removing Unused Security Groups is the expected outcome of the firewall and router rule sets review.","impact":"Not cleaning out your security groups pose the risk that a forgotten security group policy will be used to accidentally open an attack surface.","report_fields":["GroupId","VpcId","OwnerId"],"remediation":"1. Login to the AWS Console.\n2. Go to Security Group.\n3. Click on the reported Security group.\n4. Click on Actions.\n5. Click on Delete security group.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-057":{"article":"AWS access from within AWS instances can be performed by either encoding AWS keys into AWS API calls or by assigning the instance to a role which has an appropriate permissions policy for the access required. 'AWS Access' means accessing the APIs of AWS in order to use AWS resources or manage AWS account resources.","impact":"Not using AWS IAM roles for AWS resource access from instances increases the risks of unauthorized access to instances as compromised credentials can be used by the attacker to gain and maintain access to a specific instance to use the privileges associated with it.","report_fields":["InstanceId","OwnerId"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n1. In AWS IAM create a new role. Assign a permissions policy if needed permissions are already known. \n2. From the AWS console, launch a new instance with settings identical to the existing one and ensure that the newly created role is selected. \n3. Shutdown both the existing instance and the new instance. \n4. Detach disks from both instances. \n5. Attach the existing instance disks to the new instance.\n6. Boot the new instance and you will have the same machine, but with the associated role.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-453":{"article":"To manage enterprise caching solution, it's important that you know how your clusters are performing and the resources they're consuming. It's also important that you know the events that are being generated and the costs of your deployment.","impact":"With disabled logs for Redis, it is harder to know how clusters are performing and the resources they're consuming, what events that are being generated and the costs of your deployment.","report_fields":["ARN"],"remediation":"1. Sign in to the AWS Management Console and open the Amazon ElastiCache console at https://console.aws.amazon.com/elasticache/.\n2. Under 'Resources' click on the 'Redis cluster'.\n3. Click on the required cluster.\n4. Click on 'Logs'.\n5. Under the 'Slow logs' and 'Engine logs' click on the enable.\n6. Choose any format and choose any Log destination type.","multiregional":false,"service":"Amazon ElastiCache"},"ecc-aws-307":{"article":"The log_statement setting specifies the types of SQL statements that cause an error condition are recorded in the server log. \nValues are: debug5, debug4, debug3, debug2, debug1, info, notice, warning, error, log, fatal, panic. It is recommended to set on 'error' or stricter for better security.","impact":"If log_statement is not set correctly then few or too many SQL requests will be logged.","report_fields":["DBInstanceArn"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/\n2. In the navigation pane, choose Parameter groups.\n3. Choose the required parameter group.\n4. Choose Edit parameters.\n5. Under Parameters, in search bar type \"log_min_error_statement\".\n6. Choose 'error' or stricter values according to your organization policy.\n7. Choose Save changes.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-473":{"article":"Desync mitigation mode protects your application from issues due to HTTP Desync. The load balancer classifies each request based on its threat level, allows safe requests, and then mitigates risk as specified by the mitigation mode that you specify. The desync mitigation modes are monitor, defensive, and strictest. The default is the defensive mode, which provides durable mitigation against HTTP desync while maintaining the availability of your application.","impact":"HTTP Desync issues can lead to request smuggling and make applications vulnerable to request queue or cache poisoning. In turn, these vulnerabilities can lead to credential stuffing or execution of unauthorized commands.","report_fields":["LoadBalancerArn"],"remediation":"1. Open the Amazon EC2 console at https://console.aws.amazon.com/ec2/.\n2. In the navigation pane, choose Load balancers. \n3. Choose an Classic Load Balancer. \n4. On the Description tab, choose Configure desync mitigation mode. \n5. On the Configure desync mitigation mode page, choose either Defensive or Strictest.\n6. Choose Save.","multiregional":false,"service":"Amazon Elastic Load Balancing"},"ecc-aws-113":{"article":"Inline policies are policies that are attached directly to a single user, group, or role. It is recommend to use managed policies instead of inline policies. Managed policies provide reusability, central change management, versioning, and other capabilities.","impact":"Not using managed policies, you are missing out on such possibilities as reusability, versioning and rollback, automatic updates, larger policy size, and fine-grained control over policies assignment.","report_fields":["Arn"],"remediation":"1. On the IAM console, select Users from the Navigation pane and then select Permissions. \n2. Remove any policies attached directly to the user (these are inline policies), and replace them with the equivalent managed policies (on the Policies page) that are assigned to users, groups or roles.","multiregional":true,"service":"AWS Identity and Access Management"},"ecc-aws-048":{"article":"Password policies are, in part, used to enforce password complexity requirements. IAM password policies can be used to ensure passwords are comprised of different character sets. \nIt is recommended that the password policy require at least one symbol.","impact":"Not using a password policy with at least one symbol reduces security and account resiliency against brute-force attacks.","report_fields":["account_id","account_name"],"remediation":"1. Login to the AWS Console (with appropriate permissions to View Identity Access Management Account Settings). \n2. Go to IAM Service on the AWS Console.\n3. Click on Account Settings in the Left Pane. \n4. Check 'Require at least one non-alphanumeric character'. \n5. Click on 'Apply password policy'.","multiregional":true,"service":"AWS Account"},"ecc-aws-408":{"article":"This policy identifies the ELB that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["LoadBalancerArn"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon ECS at https://console.aws.amazon.com/ec2/v2.\n2. Under the 'Load Balancing' click on the 'Load Balancers'.\n3. Click on the required load balancer.\n4. Open 'Tags' and click on the 'Manage tags'.\n5. Add new tag and save.","multiregional":false,"service":"Amazon Elastic Load Balancing"},"ecc-aws-495":{"article":"By specifying a hard memory limit for your tasks you avoid running out of memory because ECS stops placing tasks on the instance, and docker kills any containers that try to go over the hard limit.","impact":"If not specifying a hard memory limit, tasks can run out of memory because ECS will not stop placing tasks on the instance.","report_fields":["taskDefinitionArn"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nNote that when you update a task definition, it does not update running tasks that were launched from the previous task definition. To update a running task, you must redeploy the task with the new task definition.\n1. Open the Amazon ECS console at https://console.aws.amazon.com/ecs/.\n2. From the navigation bar, choose the Region that contains your task definition.\n3. In the navigation bar, choose task definitions.\n4. Click on the 'Create new revision'.\n5. Under 'Container definitions' click on the 'Add container' or click on the existing container.\n6. Click on the 'Add Hard limit'.\n7. Set required value.\n8. Save.","multiregional":false,"service":"Amazon Elastic Container Service"},"ecc-aws-420":{"article":"This policy identifies the KMS that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["KeyArn","AWSAccountId"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon EFS at https://console.aws.amazon.com/kms.\n2. Click on the required customer managed key.\n3. Open 'Tags' and click on the 'Create tag'.\n4. Add new tag and save.","multiregional":false,"service":"AWS Key Management Service"},"ecc-aws-479":{"article":"If you do not specify a Customer managed key when creating your environment, CloudWatch log groups uses its AWS owned key for data encryption on your environment.\nWhen you create and use your own KMS CMK customer-managed keys to protect the data on your environment, you obtain full control over who can use the CMK keys and access the encrypted data. The AWS KMS service allows you to create, rotate, disable, enable, and audit your Customer Master Keys (CMKs).\nIn addition, if you provide a Customer managed key, you must attach the policy statement for CloudWatch access. You must also create the Customer managed key-specific execution role.","impact":"Without a KMS CMK customer-managed key, you do not have full and granular control over KMS key management. Using AWS-managed KMS key may not meet your company's compliance requirements.","report_fields":["arn"],"remediation":"Use AWS CLI to associate KMS CMK with log group:\naws logs associate-kms-key --log-group-name my-log-group --kms-key-id 'key-arn'","multiregional":false,"service":"Amazon CloudWatch"},"ecc-aws-035":{"article":"This policy identifies security group rules that allow inbound traffic to the Oracle DB port (1521) from the public internet. Allowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Unrestricted Oracle Database access can increase opportunities for malicious activity such as unauthorized access, denial-of-service (DoS) attacks and loss of data. \nAlso, unrestricted Oracle Database access allows remote attackers to execute arbitrary database commands by performing a remote registration of a database instance or service name that already exists, then conducting a man-in-the-middle (MITM) attack to hijack database connections.","report_fields":["GroupId","VpcId","OwnerId"],"remediation":"1. Login to the AWS Console.\n2. Go to Security Group.\n3. Go to the SG rules.\n4. Click on the reported Firewall rule.\n5. Click on Edit.\n6. Modify the rule.\n7. Click on Save.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-577":{"article":"Reserved Instances provide you with significant savings on your Amazon EC2 costs compared to On-Demand Instance pricing. Reserved Instances are not physical instances, but rather a billing discount applied to the use of On-Demand Instances in your account.","impact":"A billing discount is not applied to instances in your account.","report_fields":["ReservedInstancesId"],"remediation":"To retry a failed RI payment, contact AWS Support: https://console.aws.amazon.com/support.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-258":{"article":"With Amazon EMR versions 4.8.0 and later, you can use a security configuration to specify settings for encrypting data at rest, data in transit, or both.\nData encryption helps prevent unauthorized users from reading data on a cluster and associated data storage systems. This includes data saved to persistent media, known as data at rest, and data that may be intercepted as it travels the network, known as data in transit.","impact":"When encryption of data at rest is disabled, it can lead to unauthorized access to sensitive information available on a cluster and associated data storage systems.","report_fields":["ClusterArn"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nIn order to enable in-transit and at-rest encryption for existing AWS EMR clusters, you must create an EMR security configuration then clone clusters with the new security configuration. \nTo create a security configuration using the console:\n1. Open the Amazon EMR console at https://console.aws.amazon.com/elasticmapreduce/.\n2. In the navigation pane, choose Security Configurations, Create security configuration.\n3. Type a Name for the security configuration.\n4. Enable S3 encryption, Local disk encryption and Data in transit encryption.\n5. Choose Create.\nFor more information on the encryption options follow the link: https://docs.aws.amazon.com/emr/latest/ManagementGuide/emr-create-security-configuration.html.\n\nTo clone a cluster using the console:\n1. Open the AWS EMR console at https://console.aws.amazon.com/elasticmapreduce/.\n2. In the navigation pane, under EMR on EC2, choose Clusters.\n3. Select the cluster that you want to clone.\n4. At the top of the Cluster Details page, click Clone.\n5. In the dialog box, choose Yes to include the steps from the original cluster in the cloned cluster. Choose No to clone the original cluster's configuration without including any of the steps.\n6. The Create Cluster page appears with a copy of the original cluster's configuration. Review the configuration, make any necessary changes, and on the 'Step 4: Security' select the security configuration that you created earlier.\n7. Then click Create Cluster.","multiregional":false,"service":"Amazon EMR"},"ecc-aws-138":{"article":"With the creation of an AWS account, a root user is created that cannot be disabled or deleted. This user has unrestricted access to and control over all resources in the AWS account. It is highly recommended that the use of this account be avoided for everyday tasks.","impact":"The 'root user' has unrestricted access to and control over all account resources. Use of it is inconsistent with the principles of least privilege and separation of duties, and can lead to unnecessary harm due to error or account compromise.","report_fields":["account_id","account_name"],"remediation":"If you find out that the root user account is being used for daily activities including administrative tasks that do not require a root user, do the following:\n1. Change the root user password. \n2. Deactivate or delete any access keys associated with the root user.","multiregional":true,"service":"AWS Account"},"ecc-aws-454":{"article":"Monitoring is an essential part of maintaining the availability, reliability, and performance of your Elasticache clusters. When a notable event occurs within your cluster, Elasticache sends a message to the configured email address via Amazon SNS to keep you up-to-date on everything that's going on within your clusters. Notable events include cluster provisioning error, settings changes and more.","impact":"Not enabling Beanstalk notifications can lead to a slow or no response to significant cluster events.","report_fields":["ARN"],"remediation":"1. Sign in to the AWS Management Console and open the ElastiCache console at https://console.aws.amazon.com/elasticache/.\n2. From the list in the upper-right corner, choose the AWS Region you want to launch this cluster in.\n3. Choose 'Memcached clusters' or 'Redis clusters' from the navigation pane.\n4. Choose required cluster.\n5. Click on the 'Modify' button.\n6. Under the 'Topic for Amazon SNS notification' choose existing sns topic or create a new one.\n7. Save changes.","multiregional":false,"service":"Amazon ElastiCache"},"ecc-aws-095":{"article":"Real-time monitoring of API calls can be achieved by directing CloudTrail Logs to CloudWatch Logs and establishing corresponding metric filters and alarms. It is recommended that a metric filter and alarm be established for detecting changes made to Config configurations.","impact":"Lack of monitoring and logging of AWS Config configuration changes can lead to undetected Config items changes.","report_fields":["account_id","account_name"],"remediation":"Perform the following to setup the metric filter, alarm, SNS topic, subscription and trail using the AWS CLI: \n1. Create a log group.\naws logs create-log-group --log-group-name \n2. Create a log stream.\naws logs create-log-stream --log-group-name --log-stream-name \n3. Create a metric filter based on provided filter pattern which checks for unauthorized API calls and the taken from step 1.\naws logs put-metric-filter --log-group-name --filter-name `` --metric-transformations metricName=``,metricNamespace=,metricValue='1' --filter-pattern '{{ ($.eventSource = config.amazonaws.com) && (($.eventName=StopConfigurationRecorder)||($.eventName=DeleteDeliveryChannel)||($.eventName=PutDeliveryChannel)||($.eventName=PutConfigurationRecorder)) }}'\n4. Create a topic to which notifications will be published.\naws sns create-topic --name \n5. Subscribe an endpoint to an Amazon SNS topic. If the endpoint type is HTTP/S or email, or if the endpoint and the topic are not in the same Amazon Web Services account, the endpoint owner must run the ConfirmSubscription action to confirm the subscription.\naws sns subscribe --topic-arn --protocol email --notification-endpoint \n6. Amazon SNS will send a subscription confirmation message to the endpoint. Confirm subscription to topic, by visiting the link in an email that you specified as notification endpoint.\n7. Create an alarm that is associated with the CloudWatch Logs Metric Filter created in step 3 and an SNS topic created in step 4. \naws cloudwatch put-metric-alarm --alarm-name `` --metric-name `` --statistic Sum --period 300 --threshold 1 --comparison-operator GreaterThanOrEqualToThreshold --evaluation-periods 1 --namespace --alarm-actions \n8. Create an S3 bucket to deliver log files to:\naws s3api create-bucket --bucket \n9. To deliver log files to an S3 bucket, CloudTrail must have the required permissions. The following policy allows CloudTrail to write log files to the bucket from supported regions. Replace myBucketName, [optionalPrefix]/, myAccountID, region, and trailName with the appropriate values for your configuration. \nCreate a file 'policy.json' with the following policy.\n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"AWSCloudTrailAclCheck20150319\",\n \"Effect\": \"Allow\",\n \"Principal\": {\"Service\": \"cloudtrail.amazonaws.com\"},\n \"Action\": \"s3:GetBucketAcl\",\n \"Resource\": \"arn:aws:s3:::\"\n },\n {\n \"Sid\": \"AWSCloudTrailWrite20150319\",\n \"Effect\": \"Allow\",\n \"Principal\": {\"Service\": \"cloudtrail.amazonaws.com\"},\n \"Action\": \"s3:PutObject\",\n \"Resource\": \"arn:aws:s3:::/[optionalPrefix]/AWSLogs//*\",\n \"Condition\": {\n \"StringEquals\": {\n \"s3:x-amz-acl\": \"bucket-owner-full-control\",\n \"aws:SourceArn\": \"arn:aws:cloudtrail:::trail/\"\n }\n }\n }\n ]\n}}\n10. Apply the Amazon S3 bucket policy to the Amazon S3 bucket created in step 8.\naws s3api put-bucket-policy --bucket --policy file://.json\n11. Create a role for CloudTrail that enables it to send events to the CloudWatch Logs log group. To create the JSON file that will contain the policy document, open a text editor and save the following policy contents in a file called 'assume_role_policy_document.json'. \n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Principal\": {\n \"Service\": \"cloudtrail.amazonaws.com\"\n },\n \"Action\": \"sts:AssumeRole\"\n }\n ]\n}}\n12. Create a role.\naws iam create-role --role-name --assume-role-policy-document file://.json\n13. Create the following role policy document for CloudTrail. This document grants CloudTrail the permissions required to create a CloudWatch Logs log stream in the log group you specify and to deliver CloudTrail events to that log stream. Save the policy document in a file called role-policy-document.json. Replace region, accountID, log_group_name, with the appropriate values for your configuration. \n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n\n \"Sid\": \"AWSCloudTrailCreateLogStream2014110\",\n \"Effect\": \"Allow\",\n \"Action\": [\n \"logs:CreateLogStream\"\n ],\n \"Resource\": [\n \"arn:aws:logs:::log-group::log-stream:_CloudTrail_*\"\n ]\n\n },\n {\n \"Sid\": \"AWSCloudTrailPutLogEvents20141101\",\n \"Effect\": \"Allow\",\n \"Action\": [\n \"logs:PutLogEvents\"\n ],\n \"Resource\": [\n \"arn:aws:logs:::log-group::log-stream:_CloudTrail_*\"\n ]\n }\n ]\n}}\n14. Run the following command to apply the policy to the role.\naws iam put-role-policy --role-name --policy-name cloudtrail-policy --policy-document file://.json\n15. Create a trail that specifies the settings for delivery of log data to an Amazon S3 bucket at is associated with the S3 bucket created in step 8, CloudWatch log group created in step 1 and IAM role created in step 12. \naws cloudtrail create-trail --include-global-service-events --is-multi-region-trail --name --s3-bucket-name --cloud-watch-logs-log-group-arn --cloud-watch-logs-role-arn \n16. Start the recording of Amazon Web Services API calls and log file delivery for a trail. For a trail that is enabled in all regions, this operation must be called from the region in which the trail was created.\naws cloudtrail start-logging --name ","multiregional":false,"service":"AWS Account"},"ecc-aws-167":{"article":"IMAP is a protocol used for the management of eMail storage on a mail server, it is commonly used by an email user/client to view, access and edit the messages on the remote server, and emulate an experience of the message actually been stored locally on the end user\u2019s computer.\nUnrestricted access (0.0.0.0/0) increases opportunities for malicious activity, such as unauthorized access, denial-of-service attacks, and loss of data.","impact":"Unrestricted access increases the opportunity for malicious activity such as unauthorized access, denial-of-service attacks, and loss of data.","report_fields":["GroupId","VpcId","OwnerId"],"remediation":"From Console:\n1. Login to AWS Management Console and open the EC2 console using https://console.aws.amazon.com/ec2/\n2. In the navigation pane, choose Security Groups.\n3. Select the security group to update, choose Actions, and then choose Edit inbound rules to remove an inbound rule or Edit outbound rules to remove an outbound rule.\n4. Choose the Delete button to the right of the rule to delete.\n5. Choose Preview changes, Confirm.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-429":{"article":"This policy identifies the Amazon Redshift clusters that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["ClusterIdentifier"],"remediation":"1. Sign in to the AWS Management Console and open the Amazon Redshift console at https://console.aws.amazon.com/redshift/.\n2. On the navigation menu, choose Clusters.\n3. Choose the name of the cluster that you want to modify from the list. The cluster details page appears.\n4. Choose the 'Properties' tab, then in the 'Tags' section choose 'Add tags'.\n5. Add tags.\n6. Save.","multiregional":false,"service":"Amazon Redshift"},"ecc-aws-413":{"article":"This policy identifies the Glacier that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["VaultARN"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon Glacier at https://console.aws.amazon.com/glacier.\n2. Click on the 'Vaults'.\n3. Click on the required vault.\n4. Add new tag and save.","multiregional":false,"service":"Amazon S3 Glacier"},"ecc-aws-436":{"article":"You can gain better insight into your AWS Kinesis streams usage (i.e. distribution of data throughput) by enabling shard-level metrics such as IncomingBytes and IncomingRecords \u2013 helpful metrics that identify which shard is receiving more data within a stream, WriteProvisionedThroughputExceeded \u2013 metric that determines if the writes are throttled within a stream shard over a specified period of time, ReadProvisionedThroughputExceeded \u2013 metric that returns the number of GetRecords calls throttled within a shard over a specified time frame, etc.","impact":"Without Shard Level Enhanced Monitoring, it is not possible to quickly respond to performance changes in the underlying infrastructure. This can lead to data unavailability.","report_fields":["StreamARN"],"remediation":"1. Login to the AWS Management Console at https://console.aws.amazon.com/kinesis.\n2. Choose 'Data streams'.\n3. Choose required data stream.\n4. Go to 'Configuration'.\n5. Under 'Enhanced (shard-level) metrics' click on the 'Edit'.\n6. Choose all metrics and save.","multiregional":false,"service":"Amazon Kinesis"},"ecc-aws-108":{"article":"Ensure that your AWS Cloudfront distributions logging is enabled. CloudFront distribution logging is used to track all the requests for the content delivered through the Content Delivery Network (CDN). It is helpful during investigation activities and provides audit trail used for audit purposes.","impact":"Lack of monitoring and logging of Cloudfront can lead to missing suspicious requests made for web content.","report_fields":["ARN"],"remediation":"1. Sign in to the AWS console.\n2. On the console, select the specific region. \n3. Navigate to the CloudFront Distributions dashboard. \n4. Click on the reported distribution.\n5. On the 'General' tab, click on 'Edit'.\n6. On the 'Edit Distribution' page, set 'Logging' to 'On', select 'Bucket for Logs' and 'Log Prefix' as desired.\n7. Click on 'Yes', then click on 'Edit'.","multiregional":true,"service":"Amazon CloudFront"},"ecc-aws-007":{"article":"When an RDS DB instance is enabled with Multi-AZ, the RDS automatically creates a primary DB Instance and synchronously replicates the data to a standby instance in a different availability zone. \nThese Multi-AZ deployments will improve primary node reachability by providing read replica in case of network connectivity loss or loss of availability in the primary availability zone for read/write operations.","impact":"When an RDS is not configured for multiple Availability Zones and in case of any issue with the Availability Zone availability, and during regular RDS maintenance, the data stored in a database will not be reachable. Multi-AZ deployments allow you to automate failover.","report_fields":["DBInstanceArn"],"remediation":"1. Sign in to the AWS console. \n2. On the console, select the specific region. \n3. Navigate to the Amazon RDS console. \n4. Select 'Instances', and then select the reported DB instance. \n5. Select 'Modify' from the 'Instance Actions' drop-down list. \n6. In the 'Instance Specifications' section for the 'Multi-AZ Deployment', select 'Yes'. \n7. Click on 'Continue'. \n8. On the confirmation page, review the changes and click on 'Modify DB Instance' to save your changes.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-074":{"article":"You can launch AWS resources, such as Amazon Elasticsearch Service domains, into a virtual private cloud (VPC). A VPC is a virtual network that's dedicated to your AWS account. It's logically isolated from other virtual networks in the AWS Cloud. Placing an Elasticsearch Service domain within a VPC enables secure communication between Elasticsearch Service and other services within the VPC without the need for an internet gateway, NAT device, or VPN connection. All traffic remains securely within the AWS Cloud.","impact":"Deploying Elasticsearch domains without a VPC reduces the security by allowing all the traffic between Elasticsearch Service and other services over the public Internet.","report_fields":["ARN"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n1. If you create a domain with a public endpoint, you cannot later place it within a VPC. Instead, you must create a new domain and migrate your data.\n2. The reverse is also true. If you create a domain within a VPC, it cannot have a public endpoint. Instead, you must either create another domain or disable this control.","multiregional":false,"service":"Amazon OpenSearch Service"},"ecc-aws-224":{"article":"Instance metadata is used to configure or manage the running instance. The IMDS provides access to temporary, frequently rotated credentials. These credentials remove the need to hard code or distribute sensitive credentials to instances manually or programmatically.\nVersion 2 of the IMDS adds new protections for the following types of vulnerabilities. These vulnerabilities could be used to try to access the IMDS:\n - Open website application firewalls; \n - Open reverse proxies; \n - Server-side request forgery (SSRF) vulnerabilities;\n - Open Layer 3 firewalls and network address translation (NAT).","impact":"Instances that use IMDSv1 are exposed to the following vulnerabilities: \n- Open website application firewalls;\n- Open reverse proxies;\n- Server-side request forgery (SSRF) vulnerabilities;\n- Open Layer 3 firewalls and network address translation (NAT).","report_fields":["InstanceId","OwnerId"],"remediation":"Currently only the AWS SDK or AWS CLI support modifying the instance metadata options on existing instances. You can't use the Amazon EC2 console for modifying instance metadata options.\nUse the modify-instance-metadata-options CLI command and set the http-tokens parameter to required. When you specify a value for http-tokens, you must also set http-endpoint to enabled:\naws ec2 modify-instance-metadata-options --instance-id i-1234567898abcdef0 --http-tokens required --http-endpoint enabled","multiregional":false,"service":"Amazon EC2"},"ecc-aws-226":{"article":"A cluster security group is designed to allow all traffic from the control plane and managed node groups to flow freely between each other. By assigning the cluster security group to the elastic network interfaces created by Amazon EKS that allow the control plane to communicate with the managed node group instances, you don't need to configure complex security group rules to allow this communication. Any instance or network interface that is assigned this security group can freely communicate with other resources with this security group. \nSecurity group should allow only 443 and 10250. Any protocol and ports that you expect your nodes to use for inter-node communication should be included, if required.","impact":"Allowed inbound traffic on all ports enables attackers to use port scanners and other probing techniques to identify applications and services running on your EKS clusters and exploit their vulnerabilities.","report_fields":["arn"],"remediation":"1. Sign in to the AWS Management Console.\n2. Navigate to Amazon EC2 dashboard at https://console.aws.amazon.com/ec2/.\n3. In the navigation panel, under NETWORK & SECURITY section, choose Security Groups.\n4. Select the security group that you want to reconfigure.\n5. Select the Inbound rules tab from the dashboard bottom panel and click the Edit button to update inbound rules configuration.\n6. Inside the Edit inbound rules dialog box, find the inbound rule(s) configured to allow access on ports different than TCP port 443 and 10250, then click on the x button next to each rule to remove it from the security group. Once all non-compliant inbound rules are deleted from the selected security group, click Save to apply the changes.\n7. Repeat steps no. 5 \u2013 6 , but for Outbound rules tab.\n8. Repeat steps no. 4 \u2013 7 to update other security groups with non-compliant access configurations, associated with your Amazon EKS clusters.","multiregional":false,"service":"Amazon Elastic Kubernetes Service"},"ecc-aws-193":{"article":"By default, ALBs are not configured to drop invalid HTTP header values. Removing these header values prevents HTTP desync attacks.","impact":"Not dropping invalid HTTP headers may result in data leakage or the execution of unwanted actions on back-end systems.","report_fields":["LoadBalancerArn"],"remediation":"1. Open the Amazon EC2 console at https://console.aws.amazon.com/ec2/.\n2. In the navigation pane, choose Load balancers. \n3. Choose an Application Load Balancer. \n4. From Actions, choose Edit attributes. \n5. Under Drop Invalid Header Fields, choose Enable. \n6. Choose Save.","multiregional":false,"service":"Amazon Elastic Load Balancing"},"ecc-aws-199":{"article":"In Amazon RDS, Enhanced Monitoring enables a more rapid response to performance changes in underlying infrastructure. These performance changes could result in a lack of availability of the data. \nEnhanced Monitoring provides real-time metrics of the operating system that your RDS DB instance runs on. An agent is installed on the instance. The agent can obtain metrics more accurately than is possible from the hypervisor layer.","impact":"Without Enhanced Monitoring, it is not possible to quickly respond to performance changes in the underlying infrastructure. This can lead to data unavailability.","report_fields":["DBInstanceArn"],"remediation":"1. Open the IAM console at https://console.aws.amazon.com/iamv2 \n2. In the navigation pane, choose Roles. \n3. Choose Create role. \n4. Choose the AWS service tab, and then choose RDS from the list of services. \n5. Choose RDS - Enhanced Monitoring, and then choose Next: Permissions. \n6. Ensure that the Attached permissions policy page shows AmazonRDSEnhancedMonitoringRole, and then choose Next: Tags. \n7. On the Add tags page, choose Next: Review. \n8. For Role Name, enter a name for your role. For example, enter emaccess. The trusted entity for your role is the AWS service monitoring.rds.amazonaws.com. \n9. Choose Create role. \nTo enable enhanced monitoring \n1. Open the RDS console at https://console.aws.amazon.com/rds.\n2. In the navigation pane, choose Databases. \n3. Choose instance and press modify. \n4. Scroll to the Monitoring section. \n5. Choose Enable enhanced monitoring for your DB instance or read replica.\n6. Set the Monitoring Role property to the IAM role that you created to permit Amazon RDS to communicate with Amazon CloudWatch Logs for you, or choose Default to have RDS create a role for you named rds-monitoring-role. \n7. Set the Granularity property to the interval, in seconds, between points when metrics are collected for your DB instance or read replica. The Granularity property can be set to one of the following values: 1, 5, 10, 15, 30, or 60.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-328":{"article":"Removing unattached/orphaned Elastic Block Store volumes will help you avoid unexpected charges on your AWS bill and halt access to any sensitive data available on these volumes.","impact":"Not removing unattached/orphaned Elastic Block Store volumes still get charges on your AWS bill.","report_fields":["VolumeId"],"remediation":"To create snapshot before deleting the volume (optional):\n1. Login to the AWS Management Console and open the Amazon EC2 console using https://console.aws.amazon.com/ec2/ \n2. Under Elastic Block Store, click on Volumes. \n3. Click on the required volume.\n4. Under the Actions, click on Create snapshot.\n5. Click on the Create snapshot.\n\nTo delete EBS Volume \n1. Login to the AWS Management Console and open the Amazon EC2 console using https://console.aws.amazon.com/ec2/ \n2. Under Elastic Block Store, click on Volumes. \n3. Click on the required volume.\n4. Under the Actions, click on Delete volume.","multiregional":false,"service":"Amazon Elastic Block Store"},"ecc-aws-054":{"article":"IAM policies are the means by which privileges are granted to users, groups, or roles. It is recommended and considered a standard security advice to grant least privilege that is, granting only the permissions required to perform a task.\nDetermine what users need to do and then craft policies that allow them to perform only those tasks instead of allowing full administrative privileges.","impact":"Providing full administrative privileges instead of restricted ones to the minimum set of permissions can expose your AWS resources to potentially unwanted actions.","report_fields":["Arn"],"remediation":"1. Sign in to the AWS Management Console and open the IAM console.\n2. In the navigation pane, click Policies and then search for the policy name found in the audit report. \n3. Select the policy that needs to be deleted. \n4. In the policy action menu, select first Detach.\n5. Select all Users, Groups, Roles that have this policy attached 6. Click on Detach Policy.\n7. In the policy action menu, select Detach.","multiregional":true,"service":"AWS Identity and Access Management"},"ecc-aws-476":{"article":"You can use drift detection to identify stack resources to which configuration changes have been made outside of CloudFormation management. You can then take corrective action so that your stack resources are again in sync with their definitions in the stack template, such as updating the drifted resources directly so that they agree with their template definition. Resolving drift helps to ensure configuration consistency and successful stack operations.","impact":"Configuration changes performed outside of CloudFormation can lead to problems and can complicate stack update or deletion operations that affects configuration consistency and successful stack operations.","report_fields":["StackId"],"remediation":"After drift detection identified stack resources to which configuration changes have been made outside of CloudFormation management. You can take corrective action so that your stack resources are again in sync with their definitions in the stack template, such as updating the drifted resources directly so that they agree with their template definition. \nAnother way to resolve drift of resources is to update CloudFormation stack template to match the current configurations of resources.","multiregional":false,"service":"AWS CloudFormation"},"ecc-aws-445":{"article":"Amazon MWAA can send Apache Airflow logs to Amazon CloudWatch. You can view logs for multiple environments from a single location to easily identify Apache Airflow task delays or workflow errors without the need for additional third-party tools.","impact":"If 'Scheduler logs' is not enabled and the 'log_level' parameter is not set to the correct value, too many details or too few details may be logged.","report_fields":["Arn"],"remediation":"1. Navigate to https://console.aws.amazon.com/mwaa/home.\n2. Open required environment.\n3. Click 'Edit'. \n4. Click \"Next\".\n5. Enable 'Airflow scheduler logs'.\n6. Choose required log level.\n7. Save changes","multiregional":false,"service":"Amazon Managed Workflows for Apache Airflow"},"ecc-aws-135":{"article":"Services and databases store data that may be sensitive, protected by law, subject to regulatory requirements or compliance standards. It is highly recommended that access to data be restricted to encrypted protocols. This rule detects network settings that may expose data via unencrypted protocol over the public internet or to an overly wide local scope.","impact":"Unrestricted access increases the opportunity for malicious activity such as unauthorized access, denial-of-service attacks, and loss of data.","report_fields":["LoadBalancerArn"],"remediation":"1. Login to AWS Console at https://console.aws.amazon.com/ec2/.\n2. Go to Load Balancers.\n3. Click on the reported Load Balancer.\n4. In the 'Description' tab, under 'Security' select security group to edit.\n5. In the 'Inbound rules' tab, click 'Edit inbound rules'.\n6. Modify rules.\n7. Click on 'Save rules'.","multiregional":false,"service":"Amazon Elastic Load Balancing"},"ecc-aws-405":{"article":"This policy identifies the EFS that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["FileSystemArn","OwnerId"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon EFS at https://console.aws.amazon.com/efs.\n2. Click on the required efs.\n3. Open 'Tags' and click on the 'Manage tags'.\n4. Add new tag and save.","multiregional":false,"service":"Amazon Elastic File System"},"ecc-aws-107":{"article":"Checks an ACM for unused certificates. It is recommended to delete unused certificates or associate them (use them).","impact":"Unused certificates can be invalid, failed, or expired. Their accidental use can lead to the risk of deploying an invalid certificate in resources which may trigger an error in the front end and cause a loss of credibility for the web application/website.","report_fields":["CertificateArn","DomainName"],"remediation":"To delete unused certificates: \n1. Sign in to the AWS console.\n2. On the console, select the region. \n3. Navigate to the Certificate Manager(ACM) service.\n4. Select the certificate that was reported. \n5. Under the 'Actions' drop-down list, click on 'Delete'. \nAlternatively, you can associate/use the unused certificate with the resource that requires it.","multiregional":false,"service":"AWS Certificate Manager"},"ecc-aws-067":{"article":"Real-time monitoring of API calls can be achieved by directing CloudTrail Logs to CloudWatch Logs and establishing corresponding metric filters and alarms. It is recommended that a metric filter and alarm be established for unauthorized API calls.","impact":"Lack of monitoring and logging of unauthorized API calls can lead to insufficient response time to threats from an attacker. In turn, it can lead to data loss and degradation of the service. Monitoring unauthorized API calls will help reveal application errors and reduce the time to detect malicious activity.","report_fields":["account_id","account_name"],"remediation":"Perform the following to setup the metric filter, alarm, SNS topic, subscription and trail using the AWS CLI: \n1. Create a log group.\naws logs create-log-group --log-group-name \n2. Create a log stream.\naws logs create-log-stream --log-group-name --log-stream-name \n3. Create a metric filter based on provided filter pattern which checks for unauthorized API calls and the taken from step 1.\naws logs put-metric-filter --log-group-name --filter-name `` --metric-transformations metricName=``,metricNamespace=,metricValue='1' --filter-pattern '{{(($.errorCode=\"*UnauthorizedOperation\") || ($.errorCode=\"AccessDenied*\")) && (($.sourceIPAddress!=\"delivery.logs.amazonaws.com\") && ($.eventName!=\"HeadBucket\"))}}'\n4. Create a topic to which notifications will be published.\naws sns create-topic --name \n5. Subscribe an endpoint to an Amazon SNS topic. If the endpoint type is HTTP/S or email, or if the endpoint and the topic are not in the same Amazon Web Services account, the endpoint owner must run the ConfirmSubscription action to confirm the subscription.\naws sns subscribe --topic-arn --protocol email --notification-endpoint \n6. Amazon SNS will send a subscription confirmation message to the endpoint. Confirm subscription to topic, by visiting the link in an email that you specified as notification endpoint.\n7. Create an alarm that is associated with the CloudWatch Logs Metric Filter created in step 3 and an SNS topic created in step 4. \naws cloudwatch put-metric-alarm --alarm-name `` --metric-name `` --statistic Sum --period 300 --threshold 1 --comparison-operator GreaterThanOrEqualToThreshold --evaluation-periods 1 --namespace --alarm-actions \n8. Create an S3 bucket to deliver log files to:\naws s3api create-bucket --bucket \n9. To deliver log files to an S3 bucket, CloudTrail must have the required permissions. The following policy allows CloudTrail to write log files to the bucket from supported regions. Replace myBucketName, [optionalPrefix]/, myAccountID, region, and trailName with the appropriate values for your configuration. \nCreate a file 'policy.json' with the following policy.\n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"AWSCloudTrailAclCheck20150319\",\n \"Effect\": \"Allow\",\n \"Principal\": {\"Service\": \"cloudtrail.amazonaws.com\"},\n \"Action\": \"s3:GetBucketAcl\",\n \"Resource\": \"arn:aws:s3:::\"\n },\n {\n \"Sid\": \"AWSCloudTrailWrite20150319\",\n \"Effect\": \"Allow\",\n \"Principal\": {\"Service\": \"cloudtrail.amazonaws.com\"},\n \"Action\": \"s3:PutObject\",\n \"Resource\": \"arn:aws:s3:::/[optionalPrefix]/AWSLogs//*\",\n \"Condition\": {\n \"StringEquals\": {\n \"s3:x-amz-acl\": \"bucket-owner-full-control\",\n \"aws:SourceArn\": \"arn:aws:cloudtrail:::trail/\"\n }\n }\n }\n ]\n}}\n10. Apply the Amazon S3 bucket policy to the Amazon S3 bucket created in step 8.\naws s3api put-bucket-policy --bucket --policy file://.json\n11. Create a role for CloudTrail that enables it to send events to the CloudWatch Logs log group. To create the JSON file that will contain the policy document, open a text editor and save the following policy contents in a file called 'assume_role_policy_document.json'. \n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Principal\": {\n \"Service\": \"cloudtrail.amazonaws.com\"\n },\n \"Action\": \"sts:AssumeRole\"\n }\n ]\n}}\n12. Create a role.\naws iam create-role --role-name --assume-role-policy-document file://.json\n13. Create the following role policy document for CloudTrail. This document grants CloudTrail the permissions required to create a CloudWatch Logs log stream in the log group you specify and to deliver CloudTrail events to that log stream. Save the policy document in a file called role-policy-document.json. Replace region, accountID, log_group_name, with the appropriate values for your configuration. \n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n\n \"Sid\": \"AWSCloudTrailCreateLogStream2014110\",\n \"Effect\": \"Allow\",\n \"Action\": [\n \"logs:CreateLogStream\"\n ],\n \"Resource\": [\n \"arn:aws:logs:::log-group::log-stream:_CloudTrail_*\"\n ]\n\n },\n {\n \"Sid\": \"AWSCloudTrailPutLogEvents20141101\",\n \"Effect\": \"Allow\",\n \"Action\": [\n \"logs:PutLogEvents\"\n ],\n \"Resource\": [\n \"arn:aws:logs:::log-group::log-stream:_CloudTrail_*\"\n ]\n }\n ]\n}}\n14. Run the following command to apply the policy to the role.\naws iam put-role-policy --role-name --policy-name cloudtrail-policy --policy-document file://.json\n15. Create a trail that specifies the settings for delivery of log data to an Amazon S3 bucket at is associated with the S3 bucket created in step 8, CloudWatch log group created in step 1 and IAM role created in step 12. \naws cloudtrail create-trail --include-global-service-events --is-multi-region-trail --name --s3-bucket-name --cloud-watch-logs-log-group-arn --cloud-watch-logs-role-arn \n16. Start the recording of Amazon Web Services API calls and log file delivery for a trail. For a trail that is enabled in all regions, this operation must be called from the region in which the trail was created.\naws cloudtrail start-logging --name ","multiregional":false,"service":"AWS Account"},"ecc-aws-254":{"article":"Ensure that at-rest encryption is enabled for your AWS Glue job bookmarks in order to encrypt the bookmark data before it is sent to Amazon S3 for storage.","impact":"Disabled encryption allows a user to get unauthorized access to sensitive data.","report_fields":["Name"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nTo create and configure a new AWS Glue security configuration, perform the following actions: \n1. Sign in to AWS Management Console.\n2. Navigate to Glue service dashboard at https://console.aws.amazon.com/glue/.\n3. In the left navigation panel, under Security, select Security configurations.\n4. Click Add security configuration to start the setup process.\n5. On Add security configuration page, perform the following:\n 5.1 In the Security configuration name box, type a unique name for your new Glue security configuration.\n 5.2 Click the Advanced properties tab and select Job bookmark encryption checkbox to enable encryption at-rest for your Amazon Glue job bookmarks, then choose the ARN of the AWS KMS key that you want to use from AWS KMS key dropdown list.\n 5.3 Then click Finish to create your new Amazon Glue security configuration.\n6. Update the configuration of your existing AWS Glue ETL jobs to make use of the new security configuration created at the previous step.","multiregional":false,"service":"AWS Glue"},"ecc-aws-273":{"article":"Amazon DocumentDB engine logs (Audit, Profiler) should be enabled and sent to CloudWatch.\nRDS cluster logging provides detailed records of requests made to DocumentDB. DocumentDB cluster logs can assist with security and access audits and can help to diagnose availability issues.","impact":"With disabled logs for the DocumentDB cluster, it is harder to analyze statistics, diagnose issues, detect different types of attacks, and retain data for regulatory or legal purposes.","report_fields":["DBClusterArn"],"remediation":"To change Cluster settings:\n1. Open the DocumentDB console at https://console.aws.amazon.com/docdb\n2. In the navigation pane, choose Clusters.\n3. Choose the cluster that you want to modify.\n4. Choose Modify.\n5. Under Log exports, choose Audit and Profiler log types to start publishing to CloudWatch Logs.\n6. Log exports is available only for database engine versions that support publishing to CloudWatch Logs.\n7. Choose Continue. Then on the summary page, choose Modify immediately.\n\nTo change Parameter groups settings:\n1. Open the DocumentDB console at https://console.aws.amazon.com/docdb\n2. In the navigation pane, choose Parameter groups.\n3. Choose and open the parameter group that you want to modify.\n4. Change 'audit_logs' and 'profiler' parameters.\n5. Choose Edit.\n6. Change to value to enabled.","multiregional":false,"service":"Amazon DocumentDB"},"ecc-aws-264":{"article":"Default ports are: 6379 for Redis and 11211 for Memcached. Changing default port helps with automated attacks and gives more information if it was a target attack or not.","impact":"Running AWS ElastiCache clusters on the default port is a potential security risk as it provides an attacker path to a service listening on that port and increases the attack vectors your organization is exposed to.","report_fields":["ARN"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nChanging port on an existing ElastiCache Redis cluster:\n1. Create a manual backup of the cluster:\n 1.1. Sign in to the AWS Management Console and open the Amazon ElastiCache console at https://console.aws.amazon.com/elasticache/.\n 1.2. From the list in the upper-right corner, choose the AWS Region that you want to launch this cluster in.\n 1.3. Choose 'Redis clusters' from the navigation pane.\n 1.4. Choose the cluster and choose 'Action', and then 'Backup'.\n 1.5. Make sure the cluster name is right and enter backup name. \n 1.6. Select 'Encryption key'. \n 1.7. Choose 'Create Backup'.\n2. Create a new cluster by restoring from the backup:\n 2.1. Sign in to the AWS Management Console and open the ElastiCache console at https://console.aws.amazon.com/elasticache/.\n 2.2. Choose 'Redis clusters' from the navigation pane.\n 2.3. Select 'Create Redis cluster'.\n 2.4. For 'Choose a cluster creation method', choose 'Restore from backups'.\n 2.5. For 'Source' select 'Amazon ElastiCache backups'. And select backup created on the previous step.\n 2.6. Under 'Redis settings' change 'Port' value from default.\n 2.7. Complete other settings and click 'Next' and 'Next'.\n 2.8. Review settings and click 'Create'.\n3. Update the endpoints in your application to the new cluster's endpoints.\n4. Delete the old cluster.\n\nTo create an ElastiCache for Memcached cluster:\n1. Sign in to the AWS Management Console and open the ElastiCache console at https://console.aws.amazon.com/elasticache/.\n2. From the list in the upper-right corner, choose the AWS Region you want to launch this cluster in.\n3. Choose 'Memcached clusters' from the navigation pane.\n4. Choose 'Create Memcached cluster'.\n5. Under 'Cluster settings' change 'Port' value from default.\n6. Complete the 'Cluster settings' section. \n7. Click 'Next'.\n8. Complete the 'Advanced settings' section. \n9. Click 'Next'.\n10. Review all your entries and choices, then go back and make any needed corrections. When you're ready, choose 'Create' to launch your cluster.","multiregional":false,"service":"Amazon ElastiCache"},"ecc-aws-433":{"article":"An active/standby broker is comprised of two brokers in two different Availability Zones, configured in a redundant pair. These brokers communicate synchronously with your application, and with Amazon EFS.","impact":"When an MQ is not configured with active/standby broker, in case of any issue with the Availability Zone, MQ will not be reachable. \nActive/standby deployments allow you to automate failover.","report_fields":["BrokerArn"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n1. Sign in to the AWS Management Console.\n2. Navigate to the Amazon MQ dashboard at https://console.aws.amazon.com/amazon-mq/.\n3. Under the 'Select deployment and storage type' choose 'Active/standby broker' or 'Cluster deployment'\n4. Create broker.","multiregional":false,"service":"Amazon MQ"},"ecc-aws-203":{"article":"Amazon PostgreSQL database engine logs (Postgresql, Upgrade) should be enabled and sent to CloudWatch.\nDatabase logging provides detailed records of requests made to RDS. Database logs can assist with security and access audits and can help to diagnose availability issues.","impact":"With disabled logs for the database, it is harder to analyze statistics, diagnose issues, detect different types of attacks, and retain data for regulatory or legal purposes.","report_fields":["DBInstanceArn"],"remediation":"To enable and publish PostgreSQL logs to CloudWatch Logs from the AWS Management Console, set the following parameters in a custom DB Parameter Group:\n- log_statement=all\n- log_min_duration_statement=minimum query duration (ms) to log\n\nTo create a custom DB parameter group\n1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. In the navigation pane, choose Parameter groups.\n3. Choose Create parameter group. The Create parameter group window appears.\n4. In the Parameter group family list, choose a DB parameter group family.\n5. In the Type list, choose DB Parameter Group.\n6. In Group name, enter the name of the new DB parameter group.\n7. In Description, enter a description for the new DB parameter group.\n8. Choose Create.\n\nTo apply a new DB parameter group or DB options group to an RDS DB instance:\n1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. In the navigation pane, choose Databases.\n3. Choose the DB instance that you want to modify.\n4. Choose Modify. The Modify DB Instance page appears.\n5. Under Database options, change the DB parameter group and DB options group as needed.\n6. When you finish you changes, choose Continue. Check the summary of modifications.\n7. (Optional) Choose Apply immediately to apply the changes immediately. Choosing this option can cause an outage in some cases. \n8. Choose Modify DB Instance to save your changes.\n\nTo publish PostgreSQL logs to CloudWatch Logs from the AWS Management Console\n1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. In the navigation pane, choose Databases.\n3. Choose the DB instance that you want to modify.\n4. Choose Modify.\n5. Under Log exports, choose all of the log files to start publishing to CloudWatch Logs.\n6. Log exports is available only for database engine versions that support publishing to CloudWatch Logs.\n7. Choose Continue. Then on the summary page, choose Modify DB Instance.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-538":{"article":"An origin is the location where content is stored, and from which CloudFront gets content to serve to viewers.\nWhen a CloudFront distribution in your account is configured to point to a non-existent bucket, a malicious third party can create the referenced bucket and serve their own content through your distribution. It is recommended to check all origins regardless of routing behavior to ensure that your distributions are pointing to appropriate origins.","impact":"When a CloudFront distribution in your account is configured to point to a non-existent bucket, a malicious third party can create the referenced bucket and serve their own content through your distribution.","report_fields":["ARN"],"remediation":"1. Sign in to the AWS Management Console and open the CloudFront console at https://console.aws.amazon.com/cloudfront/v3/home.\n2. Choose the ID of a distribution that has an S3 origin.\n3. Choose the 'Origins' tab.\n4. Choose the check box next to an origin, and then choose 'Edit'.\n5. For 'Origin domain' update value to a valid value (name of S3 bucket that exists or S3 website endpoint).\n8. Click 'Save changes'.","multiregional":true,"service":"Amazon CloudFront"},"ecc-aws-176":{"article":"Encrypt Amazon RDS snapshot at rest by enabling the encryption option for your Amazon RDS DB snapshot.\nThe RDS snapshot encryption and decryption process is handled transparently and does not require any additional action from you or your application. The keys used for AWS RDS database snapshot encryption can be entirely managed and protected by the Amazon Web Services key management infrastructure or fully managed by the AWS customer through Customer Master Keys (CMKs).","impact":"Disabled encryption allows a user to get unauthorized access to sensitive data in snapshots.","report_fields":["DBSnapshotArn","DBInstanceIdentifier"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n \n1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. Choose Snapshots.\n3. Select the Snapshot to modify.\n4. Choose Actions.\n5. Choose Copy Snapshot. \n6. Under Encryption options, check Enable encryption. \n7. Choose Copy snapshot.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-011":{"article":"If you have an HTTPS listener, you deployed an SSL server certificate on your load balancer when you created the listener. Each certificate comes with a validity period. You must ensure that you renew or replace the certificate before its validity period ends.\nCertificates provided by AWS Certificate Manager and deployed on your load balancer can be renewed automatically. ACM attempts to renew certificates before they expire. If you imported a certificate into ACM, you must monitor the expiration date of the certificate and renew it before it expires. After a certificate that is deployed on a load balancer is renewed, new requests use the renewed certificate.","impact":"SSL/TLS certificates not renewed prior to their expiration date become invalid and the communication between a client and an AWS resource that implements the certificates is no longer secure.","report_fields":["LoadBalancerArn"],"remediation":"1. Login to the AWS EC2 console at console.aws.amazon.com/ec2/. \n2. Navigate to the 'Load Balancer' section, and then to the 'Listener' tab. \n3. Select the listener (HTTPS), and the 'View/edit certificates' tab. \n4. Add the new certificate (from IAM) for each instance that failed the rule.","multiregional":false,"service":"Amazon Elastic Load Balancing"},"ecc-aws-482":{"article":"From a security perspective, logging is an important feature to enable for future forensics efforts in the case of any security incidents. Correlating anomalies in CodeBuild projects with threat detections can increase confidence in the accuracy of those threat detections.","impact":"With disabled logs, it is harder to analyze statistics, diagnose issues, detect different types of attacks, and retain data for regulatory or legal purposes.","report_fields":["arn"],"remediation":"1. Open the CodeBuild console at https://console.aws.amazon.com/codebuild/.\n2. Expand Build, choose Build project, and then choose the build project without logging. \n3. From Edit, choose Logs. \n4. Enable either CloudWatch logs or S3 Logs.\n5. Update logs.","multiregional":false,"service":"AWS CodeBuild"},"ecc-aws-544":{"article":"CloudTrail provides a history of API activity within your AWS account, allowing you to track changes and troubleshoot issues. AWS CloudTrail delivery options provide flexibility and customization for logging, monitoring, and analyzing API activity within your AWS infrastructure.","impact":"If CloudTrail logs fail to deliver, you may lose important data related to API activity within your AWS account. This can make it difficult to track changes and troubleshoot issues, it may require additional effort to troubleshoot and resolve the delivery issues, which can increase your operational overhead. If CloudTrail logs are delayed or not delivered in real-time, it can delay your ability to detect and respond to security threats or other issues.","report_fields":["TrailARN"],"remediation":"1. Verify the delivery destination: Check that the S3 bucket, CloudWatch Logs log group, or SNS topic that is configured as the delivery destination for CloudTrail logs is active and accessible. You may need to confirm that the IAM roles and permissions are correctly configured.\n2. Check CloudTrail configuration: Review your CloudTrail configuration to ensure that it is correctly configured to deliver logs to the intended destination(s). Verify that the log file validation feature is enabled, which helps to detect and prevent data tampering.\n3. Check network connectivity: Confirm that there are no network connectivity issues between your AWS account and the delivery destination. Check for any firewall or network security group configurations that may be blocking traffic.\n4. Review delivery failure logs: Check the CloudTrail delivery failure logs to identify any specific errors or issues that may be causing the delivery failure. This may include issues with the AWS service that is delivering the logs, or issues with the delivery destination.\n5. Resend failed logs: Once you have identified the cause of the delivery failure, you can resend any failed logs using the CloudTrail console or the AWS Command Line Interface (CLI). This will help to ensure that you have a complete and accurate record of API activity within your AWS account.\n6. Monitor delivery: After resolving the delivery failure, monitor CloudTrail delivery to ensure that logs are being delivered as expected. Consider setting up alarms or notifications to alert you in the event of future delivery failures.","multiregional":false,"service":"AWS CloudTrail"},"ecc-aws-444":{"article":"Amazon MWAA can send Apache Airflow logs to Amazon CloudWatch. You can view logs for multiple environments from a single location to easily identify Apache Airflow task delays or workflow errors without the need for additional third-party tools.","impact":"If 'Dag Processing logs' is not enabled and the 'log_level' parameter is not set to the correct value, too many details or too few details may be logged.","report_fields":["Arn"],"remediation":"1. Navigate to https://console.aws.amazon.com/mwaa/home.\n2. Open required environment.\n3. Click 'Edit'. \n4. Click \"Next\".\n5. Enable 'Airflow DAG processing logs'.\n6. Choose required log level.\n7. Save changes","multiregional":false,"service":"Amazon Managed Workflows for Apache Airflow"},"ecc-aws-146":{"article":"The Network Access Control List (NACL) function provides stateless filtering of ingress and egress network traffic to AWS resources. It is recommended that no NACL allow unrestricted ingress access to remote server administration ports, such as SSH to port 22 and RDP to port 3389.","impact":"Unrestricted access increases the opportunity for malicious activity such as unauthorized access, denial-of-service attacks, and loss of data.","report_fields":["NetworkAclId","OwnerId"],"remediation":"1. Login to the AWS Management Console at https://console.aws.amazon.com/vpc/home\n2. In the left pane, click on Network ACLs \n3. For each network ACL to remediate, perform the following:\n - Select the network ACL\n - Click on the Inbound Rules tab\n - Click on Edit inbound rules\n - Either \n A) update the Source field to a range other than 0.0.0.0/0, or, \n B) click on Delete to remove the offending inbound rule\n - Click on Save","multiregional":false,"service":"Amazon Virtual Private Cloud"},"ecc-aws-504":{"article":"When creating a RDS instance, you should change the default admin username 'admin' or 'postgres' to a unique value. Default usernames are public knowledge and should be changed upon configuration.","impact":"Well-known database Admin username could lead to unintended access.","report_fields":["DBInstanceArn"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. In the navigation pane, choose Databases.\n3. Choose Create database.\n4. Under the 'Credentials Settings' change 'Master username'.\n5. Create database","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-112":{"article":"Enabling MFA delete for versioning is a good way to add extra protection to sensitive files stored in buckets.","impact":"Users can accidentally delete an S3 bucket, or an attacker/malicious user can do it deliberately to cause disruption.","report_fields":["Name"],"remediation":"Using AWS s3api CLI, enable MFA Delete for the S3 buckets that fail this rule, for example 'aws s3api put-bucket-versioning --bucket bucketname --versioning-configuration Status=Enabled,MFADelete=Enabled --mfa 'your-mfa-serial-number mfa-code''.","multiregional":true,"service":"Amazon S3"},"ecc-aws-159":{"article":"RDS event notifications uses Amazon SNS to make you aware of changes in the availability or configuration of your RDS resources. These notifications allow for rapid response.\n'DBCluster: ['maintenance','failure']' Notification should be enabled for All Clusters.","impact":"Not enabling RDS cluster event notifications can lead to a slow response to a cluster failure.","report_fields":["account_id","account_name"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. In the navigation pane, choose Event subscriptions.\n3. Under Event subscriptions, choose Create event subscription.\n4. In the Create event subscription dialog, do the following:\n4.1. For Name, enter a name for the event notification subscription.\n4.2. For Send notifications to, choose an existing Amazon SNS ARN for an SNS topic.\n To use a new topic, choose Create topic to enter the name of a topic and a list of recipients.\n4.3. For Source type, choose Clusters.\n4.4. Under Instances to include, select All clusters.\n4.5. Under Event categories to include, select Specific event categories. The control also passes if you select All event categories.\n4.5.1 Select maintenance and failure.\n4.6. Choose Create.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-425":{"article":"This policy identifies the MWAA clusters that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["Arn"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon MWAA at https://console.aws.amazon.com/mwaa.\n2. Click on the required environment.\n3. Open 'Tags' and click on the 'Manage tags'.\n4. Add new tag and save.","multiregional":false,"service":"Amazon Managed Workflows for Apache Airflow"},"ecc-aws-185":{"article":"A failed finding indicates that an EC2 instance has not run for a significant period of time. This creates a security risk because the EC2 instance is not being actively maintained (analyzed, patched, updated). If it is later launched, the lack of proper maintenance could result in unexpected issues in your AWS environment. To safely maintain an EC2 instance over time in a nonrunning state, start it periodically for maintenance and then stop it after maintenance. Ideally this is an automated process.","impact":"EC2 instances that have not been running for a significant time pose a security risk because they were not actively maintained (analyzed, patched, updated). If they are launched later, the lack of proper maintenance may result in unexpected issues in the AWS environment.","report_fields":["InstanceId","OwnerId"],"remediation":"You can terminate an EC2 instance using either the console or the command line. \nBefore you terminate the EC2 instance, verify that you won't lose any data:\n - Check that your Amazon EBS volumes will not be deleted on termination. \n - Copy any data that you need from your EC2 instance store volumes to Amazon EBS or Amazon S3. \n\nTo terminate an EC2 instance (console): \n1. Open the Amazon EC2 console at https://console.aws.amazon.com/ec2/. \n2. In the navigation pane, under Instances, choose Instances. \n3. Select the instance, and then choose Actions, Instance State, Terminate. \n4. When prompted for confirmation, choose Yes, Terminate. \n\nTo Terminate an EC2 instance (CLI): aws ec2 terminate-instances --instance-ids ","multiregional":false,"service":"Amazon EC2"},"ecc-aws-417":{"article":"This policy identifies the MSK clusters that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["ClusterArn"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon MSK at https://console.aws.amazon.com/msk\n2. Click on the required cluster.\n3. Open 'Tags' and click on the 'Manage tags'.\n4. Add new tag and save.","multiregional":false,"service":"Amazon Managed Streaming for Apache Kafka"},"ecc-aws-128":{"article":"Route53 conveniently allows you to register and manage domains alongside the rest of your AWS resources. When a domain is registered in Route53, it will automatically have automatic renewal enabled. In the case where automatic renewal is not enabled and domain was not renewed, it becomes expired.","impact":"An expired Amazon Route53 domain can cause website or application downtime or failure. An expired domain could be taken over by a malicious individual or deleted by the domain registrar and it will become available for others to register.","report_fields":["DomainName"],"remediation":"If you don't renew a domain before the end of the late-renewal period or if you accidentally delete the domain, some registries for top-level domains (TLDs) allow you to restore the domain before it becomes available for others to register.\nWhen a domain is deleted or it passes the end of the late-renewal period, it no longer appears in the Amazon Route53 console. \nTo restore any expired domain names registered with AWS Route 53, perform the following:\n1. Determine whether the TLD registry for the domain supports restoring domains and, if so, the period during which restoration is allowed.\n a. Go to 'Domains that you can register with Amazon Route53': https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/registrar-tld-list.html.\n b. Find the TLD for your domain, and review the values in the \"Deadlines for renewing and restoring domains\" section. \n2. Review the price for restoring a domain, which is often higher and sometimes much higher than the price for registering or renewing a domain. In 'Amazon Route53 Pricing for Domain Registration': https://d32ze2gidvkk54.cloudfront.net/Amazon_Route_53_Domain_Registration_Pricing_20140731.pdf, find the TLD for your domain (such as .com) and check the price in the \"Restoration Price\" column. If you still want to restore the domain, make note of the price; you'll need it in a later step.\n3. Using the AWS account that the domain was registered to, sign in to the AWS Support Center: https://console.aws.amazon.com/support/home?region=us-east-1#/case/create?issueType=customer-service&serviceCode=billing&categoryCode=domain-name-registration-issue.\n4. Specify the following values:\n - Regarding: Accept the default value of 'Account and Billing Support'.\n - Service: Accept the default value of 'Billing'.\n - Category: Accept the default value of 'Domain name registration issue'.\n - Subject: Enter 'Restore an expired domain' or 'Restore a deleted domain'.\n - Description: \n Provide the following information:\n - The domain that you want to restore\n - The 12-digit account ID of the AWS account that the domain was registered to\n - Confirmation that you agree to the price for restoring the domain. Use the following text:\n \"I agree to the price of $____ for restoring my domain.\"\n Replace the blank with the price that you found in step 2.\n - Contact method: Specify a contact method and, if you choose 'Phone', enter the applicable values.\n5. Choose 'Submit'.\n6. When AWS learn whether they were able to restore your domain, an AWS Support representative will contact you. In addition, if AWS were able to restore your domain, the domain will reappear in the console. The new expiration date is usually one or two years (depending on the TLD) after the old expiration date.\n Note: The new expiration date is not calculated from the date that the domain was restored.","multiregional":true,"service":"Amazon Route 53"},"ecc-aws-399":{"article":"This policy identifies the Codebuild that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["arn"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon Codebuild at https://console.aws.amazon.com/codesuite/codebuild.\n2. Click on the 'Build', 'Build projects' and choose required project.\n3. Open 'Build details' and click on the 'Edit'.\n4. Under the 'Additional configuration' click on 'Tags'\n5. Add new tag and save.","multiregional":false,"service":"AWS CodeBuild"},"ecc-aws-195":{"article":"Before you start to use your Application Load Balancer, you must add one or more listeners. A listener is a process that uses the configured protocol and port to check for connection requests. Listeners support both HTTP and HTTPS protocols. You can use an HTTPS listener to offload the work of encryption and decryption to your Application Load Balancer. You should use redirect actions with Application Load Balancer to redirect client HTTP request to an HTTPS request on port 443 to enforce encryption in-transit.","impact":"An HTTP protocol is not a secure method of transmitting data. Any person monitoring the Internet traffic can see unencrypted data, which leads to a breach of confidentiality.","report_fields":["LoadBalancerArn"],"remediation":"Create an HTTP listener rule that redirects HTTP requests to HTTPS\n 1. Open the Amazon EC2 console at https://console.aws.amazon.com/ec2/.\n 2. On the navigation pane, under LOAD BALANCING, choose Load Balancers.\n 3. Select a load balancer, and then choose Listeners, Add listener.\n Note: Skip to step 6 if you already have an HTTP listener.\n 4. For Protocol: port, choose HTTP. You can either keep the default port or specify a custom port.\n 5. For Default actions, choose Add action, redirect to, and then enter port 443 (or a different port if you\u2019re not using the default). For more details, see Rule action types. To save, choose the checkmark icon.\n Note: If you created a new HTTP listener following steps 3-5 above, skip to Create an HTTPS listener.\n 6. Select a load balancer, and then choose HTTP Listener.\n 7. Under Rules, choose View/edit rules.\n 8. Choose Edit Rule to modify the existing default rule to redirect all HTTP requests to HTTPS. Or, insert a rule between the existing rules (if appropriate for your use case).\n 9. Under Then, delete the existing condition. Then, add the new condition with the Redirect to action.\n 10. For HTTPS, enter 443 port.\n 11. Keep the default for the remaining options.\n Note: If you want to change the URL or return code, you can modify these options as needed.\n 12. To save, choose the checkmark icon.\n\nCreate an HTTPS listener\nNote: If you already have an HTTPS listener with a rule to forward requests to the respective target group, skip to Verify that the security group of the Application Load Balancer allows traffic on 443.\n 1. Choose Listeners, Add listener.\n 2. For Protocol: port, choose HTTPS. Keep the default port or specify a custom port.\n 3. For Default actions, choose Add action, Forward to.\n 4. Select a target group that hosts application instances.\n 5. Select a predefined security policy that's best suited for your configuration.\n 6. Choose Default Security Certificate. (If you don\u2019t have one, you can create a security certificate.) \n 7. Choose Save.\n\nVerify that the security group of the Application Load Balancer allows traffic on 443\n 1. Choose the load balancer's Description.\n 2. Under Security, choose Security group ID.\n 3. Verify the inbound rules. The security group must have an inbound rule that permits traffic on HTTP and HTTPS. If there are no inbound rules, complete the following steps to add them.\n\nTo add inbound rules (if you don't already have them):\n 1. Choose Actions, Edit Inbound Rules to modify the security group.\n 2. Choose Add rule.\n 3. For Type, choose HTTPS.\n 4. For Source, choose Custom (0.0.0.0/0 or Source CIDR).\n 5. Choose Save.","multiregional":false,"service":"Amazon Elastic Load Balancing"},"ecc-aws-507":{"article":"Logging is an important part of maintaining the reliability, availability, and performance of services. Logging message delivery status helps provide operational insights, such as the following: \n - Knowing whether a message was delivered to the Amazon SNS endpoint. \n - Identifying the response sent from the Amazon SNS endpoint to Amazon SNS.\n - Determining the message dwell time (the time between the publish timestamp and the hand off to an Amazon SNS endpoint).","impact":"With disabled logs, it is harder to analyze operational insights.","report_fields":["TopicArn"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon SNS at https://console.aws.amazon.com/sns/v3.\n2. Click on the required sns topic.\n3. On the Topics page, choose a topic and then choose Edit.\n4. On the Edit MyTopic page, expand the Delivery status logging section.\n5. Choose the protocol for which you want to log delivery status; for example AWS Lambda.\n6. Enter the Success sample rate (the percentage of successful messages for which you want to receive CloudWatch Logs.\n7. In the IAM roles section, do one of the following:\n To choose an existing service role from your account, choose Use existing service role and then specify IAM roles for successful and failed deliveries.\n To create a new service role in your account, choose Create new service role, choose Create new roles to define the IAM roles for successful and failed deliveries in the IAM console.\n To give Amazon SNS write access to use CloudWatch Logs on your behalf, choose Allow.\n8. Choose Save changes.","multiregional":false,"service":"Amazon Simple Notification Service"},"ecc-aws-131":{"article":"Services and databases store data that may be sensitive, protected by law, subject to regulatory requirements or compliance standards. It is highly recommended that access to data be restricted to encrypted protocols. This rule detects network settings that may expose data via unencrypted protocol over the public internet or to an overly wide local scope.","impact":"Unrestricted access increases the opportunity for malicious activity such as unauthorized access, denial-of-service attacks, and loss of data.","report_fields":["InstanceId","OwnerId"],"remediation":"1. Login to AWS Console at https://console.aws.amazon.com/ec2/\n2. Go to Security groups\n3. Click on the reported security group\n3. Go to the SG rules\n4. Click on the reported rule\n5. Click on Edit \n6. Modify the rule\n7. Click on Save","multiregional":false,"service":"Amazon EC2"},"ecc-aws-285":{"article":"AWS X-Ray always encrypts traces and related data at rest. When you need to audit and disable encryption keys for compliance or internal requirements, you can configure X-Ray to use an AWS Key Management Service (AWS KMS) key to encrypt data.\nX-Ray provides an AWS managed key named aws/xray. Use this key when you just want to audit key usage in AWS CloudTrail and don't need to manage the key itself. When you need to manage access to the key or configure key rotation, you can create a customer managed key.","impact":"Without a KMS CMK customer-managed key, you do not have full and granular control over who can access key that is used for encryption.","report_fields":["account_id","account_name"],"remediation":"To configure X-Ray to use a KMS key for encryption\n1. Open the X-Ray console: https://console.aws.amazon.com/xray/home#/service-map.\n2. Choose Encryption.\n3. Choose Use a KMS key.\n4. Choose a Customer Managed Key from the dropdown menu or manually enter a key ARN (usually used for a Customer Managed Key in a different account).\nNote: X-Ray does not support asymmetric KMS keys.\n5. Choose Apply.","multiregional":false,"service":"AWS X-Ray"},"ecc-aws-154":{"article":"An Elasticsearch domain requires at least three data nodes for high availability and fault-tolerance. Deploying an Elasticsearch domain with at least three data nodes ensures cluster operations if a node fails.","impact":"Using only one data node can lead to low availability and fault tolerance. Deploying an Elasticsearch domain with at least three data nodes ensures cluster operations if a node fails.","report_fields":["ARN"],"remediation":"From Console:\n1. Login to the AWS Management Console and open the Amazon Elasticsearch Service console using https://console.aws.amazon.com/esv3. \n2. Under 'My domains', choose the name of the domain to edit. \n3. Choose 'Edit domain'. \n4. Under 'Data nodes', set 'Number of nodes' to a number greater than or equal to three.\nFor three Availability Zone deployments, set to a multiple of three to ensure equal distribution across Availability Zones. \n5. Choose Submit.","multiregional":false,"service":"Amazon OpenSearch Service"},"ecc-aws-255":{"article":"Ensure that CloudWatch logs encryption is enabled for your Amazon Glue security configurations in order to meet regulatory requirements and prevent unauthorized users from getting access to the logging data published to AWS CloudWatch Logs. A security configuration is a set of encryption properties that are used by Amazon Glue service to configure encryption for crawlers, jobs and development endpoints.","impact":"Disabled encryption of CloudWatch logs allows a user to get unauthorized access to sensitive data.","report_fields":["Name"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nTo enable encryption at rest for Amazon Glue logging data published to AWS CloudWatch Logs, you need to re-create the necessary security configurations with the CloudWatch Logs encryption mode enabled. To create and configure a new AWS Glue security configuration, perform the following actions: \n1. Sign in to AWS Management Console.\n2. Navigate to Glue service dashboard at https://console.aws.amazon.com/glue/.\n3. In the left navigation panel, under Security, choose Security configurations.\n4. Click Add security configuration to initiate the setup process.\n5. On Add security configuration page, perform the following:\n 5.1 Enter a unique name for your new configuration within Security configuration name box.\n 5.2 Select CloudWatch logs encryption checkbox to enable at-rest encryption when writing logs to AWS CloudWatch, then choose the ARN of the AWS KMS key that you want to use for encryption from AWS KMS key dropdown list.\n6. Reconfigure (update) your existing Amazon Glue crawlers, jobs and development endpoints to make use of the new security configuration created at the previous step.","multiregional":false,"service":"AWS Glue"},"ecc-aws-496":{"article":"If host is specified, all containers within the tasks that specified the host PID mode on the same container instance share the same process namespace with the host Amazon EC2 instance. If task is specified, all containers within the specified task share the same process namespace. If no value is specified, the default is a private namespace.","impact":"If the host's PID namespace is shared with containers, it would allow containers to see all of the processes on the host system. This reduces the benefit of process level isolation between the host and the containers. These circumstances could lead to unauthorized access to processes on the host itself, including the ability to manipulate and terminate them.","report_fields":["taskDefinitionArn"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nNote that when you update a task definition, it does not update running tasks that were launched from the previous task definition. To update a running task, you must redeploy the task with the new task definition.\n1. Open the Amazon ECS console at https://console.aws.amazon.com/ecs/.\n2. From the navigation bar, choose the Region that contains your task definition.\n3. In the navigation bar, choose task definitions.\n4. Click on the 'Create new revision'.\n5. Under 'Volumes' click on the 'Configure via JSON'.\n6. Set value in 'pidMode' to 'task' or 'null'.\n7. Save changes.\n8. If your task definition is used in a service, update your service with the updated task definition.","multiregional":false,"service":"Amazon Elastic Container Service"},"ecc-aws-179":{"article":"A user might sometimes request the distribution's root URL instead of an object in the distribution. When this happens, specifying a default root object can help you to avoid exposing the contents of your web distribution.","impact":"When the root object for CloudFront distributions is not configured, the content of CloudFront is exposed to the internet.","report_fields":["ARN"],"remediation":"1. Upload the default root object to the origin that your distribution points to. The file can be any type supported by CloudFront.\n2. Confirm that the permissions for the object grant CloudFront at least read access.\n3. Update your distribution to refer to the default root object using the CloudFront console or the CloudFront API. To specify a default root object using the CloudFront console\n3.1. Sign in to the AWS Management Console and open the CloudFront console at https://console.aws.amazon.com/cloudfront/v3/home.\n3.2. In the list of distributions in the top pane, select the distribution to update.\n3.3. In the Distribution Details pane, on the General tab, choose Edit.\n3.4. In the Edit Distribution dialog box, in the Default Root Object field, enter the file name of the default root object.\n3.5. Enter only the object name, for example, index.html. Do not add a / before the object name.\n3.6. To save your changes, choose Yes, Edit. To update your configuration using the CloudFront API, you specify a value for the DefaultRootObject element in your distribution. For information about using the CloudFront API to specify a default root object, see PUT Distribution Config in the Amazon CloudFront API Reference.\n4. Confirm that you have enabled the default root object by requesting your root URL. If your browser doesn't display the default root object, perform the following steps.\n4.1. Confirm that your distribution is fully deployed by viewing the status of your distribution in the CloudFront console.\n4.2. Repeat steps 2 and 3 to verify that you granted the correct permissions and that you correctly updated the configuration of your distribution to specify the default root object.","multiregional":true,"service":"Amazon CloudFront"},"ecc-aws-293":{"article":"In AWS Backup, a Backup vault is a container that stores and organizes your backups. When creating a backup vault, you must specify the AWS Key Management Service (AWS KMS) encryption key that encrypts some of the backups placed in this vault. Encryption for other backups is managed by their source AWS services. By default backup vault is encrypted with AWS managed-key.\nKMS CMK customer-managed keys provide more granular control over your data-at-rest encryption/decryption process than AWS managed-keys.","impact":"Without a KMS CMK customer-managed keys, you do not have full and granular control over who can access encrypted backups in a Backup vault data.","report_fields":["BackupVaultArn"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nTo encrypt backup data using AWS KMS Customer Managed Keys, you have to recreate a Backup vault and enable encryption using KMS CMK.\n1. Open the Amazon Backup console at https://console.aws.amazon.com/backup/.\n2. In the navigation panel, choose Backup vaults.\n3. Click 'Create Backup vault' button from the dashboard top menu.\n4. On the 'Create Backup vault' page, perform the following:\n 4.1 Provide a unique name for a new vault.\n 4.2 Select the ID of the AWS Customer Managed Key (CMK) from the KMS encryption master key dropdown list.\n 4.3 (Optional) Within Backup vault tags section, configure the required tags for the new resource.\n 4.4 Click Create Backup vault.\n5. In the navigation panel select 'Backup plans'.\n6. Choose the backup plan associated with the non-compliant backup vault.\n7. In the Backup rules section, select the rule associated with the non-compliant backup vault and click Edit.\n8. On Edit 'Backup rule' configuration page, select the backup vault created earlier in the process from the Backup vault dropdown list, then click Save to apply the changes. The future backups taken using the selected backup plan will be encrypted with your own AWS KMS CMK configured for the newly created backup vault.","multiregional":false,"service":"AWS Backup"},"ecc-aws-198":{"article":"You should enable error logs for Elasticsearch domains and send those logs to CloudWatch Logs for retention and response. \nDomain error logs can assist with security and access audits, and can help to diagnose availability issues.","impact":"With disabled logs for Elasticsearch, it is harder to analyze statistics, diagnose issues, detect different types of attacks, and retain data for regulatory or legal purposes.","report_fields":["ARN"],"remediation":"1. Open ElasticSearch at https://console.aws.amazon.com/esv3 \n2. Choose Domains. \n3. Select the domain you want to update. \n4. On the Logs tab, select a log type 'Error logs' and choose Enable. \n5. Create a CloudWatch log group, or choose an existing one. \n6. Choose an access policy that contains the appropriate permissions, or create a policy using the JSON that the console provides:\n {{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {{\n \"Effect\": \"Allow\",\n \"Principal\": {{\n \"Service\": \"es.amazonaws.com\"\n }},\n \"Action\": [\n \"logs:PutLogEvents\",\n \"logs:CreateLogStream\"\n ],\n \"Resource\" : cw_log_group_arn\n }}\n ]\n }}","multiregional":false,"service":"Amazon OpenSearch Service"},"ecc-aws-205":{"article":"Amazon MariaDB database engine logs (Audit, Error, General, SlowQuery) should be enabled and sent to CloudWatch.\nDatabase logging provides detailed records of requests made to RDS. Database logs can assist with security and access audits and can help to diagnose availability issues.","impact":"With disabled logs for the database, it is harder to analyze statistics, diagnose issues, detect different types of attacks, and retain data for regulatory or legal purposes.","report_fields":["DBInstanceArn"],"remediation":"To enable and publish MariaDB logs to CloudWatch Logs from the AWS Management Console, set the following parameters in a custom DB Parameter Group:\n- general_log=1\n- slow_query_log=1\n- log_output = FILE\n\nTo create a custom DB parameter group:\n1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. In the navigation pane, choose Parameter groups.\n3. Choose Create parameter group. The Create parameter group window appears.\n4. In the Parameter group family list, choose a DB parameter group family.\n5. In the Type list, choose DB Parameter Group.\n6. In Group name, enter the name of the new DB parameter group.\n7. In Description, enter a description for the new DB parameter group.\n8. Choose Create.\n\nTo apply a new DB parameter group or DB options group to an RDS DB instance:\n1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. In the navigation pane, choose Databases.\n3. Choose the DB instance that you want to modify.\n4. Choose Modify. The Modify DB Instance page appears.\n5. Under Database options, change the DB parameter group and DB options group as needed.\n6. When you finish you changes, choose Continue. Check the summary of modifications.\n7. (Optional) Choose Apply immediately to apply the changes immediately. Choosing this option can cause an outage in some cases. \n8. Choose Modify DB Instance to save your changes.\n\nTo create a new option group for MariaDB logging by using the console:\n1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. In the navigation pane, choose Option groups.\n3. Choose Create group.\n4. In the Create option group window, do the following:\n 4.1. For Name, type a name for the option group that is unique within your AWS account. The name can contain only letters, digits, and hyphens.\n 4.2. For Description, type a brief description of the option group. The description is used for display purposes.\n 4.3. For Engine, choose the mariadb engine.\n 4.4. For Major engine version, choose the major version of the DB engine that you want.\n5. To continue, choose Create.\n6. Choose the name of the option group you just created.\n7. Choose Add option.\n8. Choose MARIADB_AUDIT_PLUGIN from the Option name list.\n9. Set SERVER_AUDIT_EVENTS to CONNECT, QUERY, TABLE, QUERY_DDL, QUERY_DML, QUERY_DCL.\n10. Choose Add option.\n\nTo publish MariaDB logs to CloudWatch Logs from the AWS Management Console\n1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. In the navigation pane, choose Databases.\n3. Choose the DB instance that you want to modify.\n4. Choose Modify.\n5. Under Log exports, choose all of the log files to start publishing to CloudWatch Logs.\n6. Log exports is available only for database engine versions that support publishing to CloudWatch Logs.\n7. Choose Continue. Then on the summary page, choose Modify DB Instance.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-016":{"article":"Ensure HARDWARE MFA is enabled for the 'root' account","impact":"Virtual MFA might not provide the same level of security as hardware MFA devices. Also, one layer of protection (a password) for a root account is not enough to prevent credentials from being compromised. The likelihood of a password being cracked and an account being compromised is much higher without MFA enabled.","report_fields":["account_id","account_name"],"remediation":"Perform the following to establish a hardware MFA for the root account:\n1. Sign in to the AWS Management Console and open the IAM console.\n2. Choose Dashboard, and under Security Status, expand Activate MFA on your root account.\n3. Choose Activate MFA\n4. In the wizard, choose a hardware MFA device and then choose Next Step.\n5. In the Serial Number box, enter the serial number found on the back of the MFA device.\n6. In the Authentication Code 1 box, enter the six-digit number displayed by the MFA device. You might need to press the button on the front of the device to display the number.\n7. Wait 30 seconds for the device to refresh the code and then enter the next six-digit number in the Authentication Code 2 box. You might need to press the button on the front of the device again to display the second number.\n8. Choose Next Step. \nThe MFA device is now associated with the AWS account. The next time you use your AWS account credentials to sign in, you must type a code from the hardware MFA device.","multiregional":true,"service":"AWS Account"},"ecc-aws-030":{"article":"This policy identifies security group rules that allow inbound traffic to the HTTP port (80) from the public internet. Allowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Port 80 is a frequent target for attacks. Allowing unrestricted HTTP access can increase opportunities for malicious activity such as unauthorized access, denial-of-service (DoS) attacks and loss of data.","report_fields":["GroupId","VpcId","OwnerId"],"remediation":"1. Login to the AWS Console.\n2. Go to Security Group. \n3. Go to the SG rules.\n4. Click on the reported Firewall rule.\n5. Click on Edit.\n6. Modify the rule.\n7. Click on Save.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-006":{"article":"RDS instance should have Retention Policies for Backups configured to retain at least 7 days of backups.","impact":"A retention period of fewer than 7 days set for RDS database instances can result in data loss and the inability to recover it in the event of failure.","report_fields":["DBInstanceArn"],"remediation":"Configure your RDS backup retention policy to be at least 7 days. \n1. Navigate to the the AWS console RDS dashboard. \n2. In the navigation pane, select Instances. \n3. Select the database instance you wish to configure. \n4. From the 'Instance actions' menu, select Modify. \n5. Scroll down to 'Backup Retention' options and set the retention period to at least 7 days. \n6. Click on Continue.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-142":{"article":"Amazon S3 provides Block public access (bucket settings) and Block public access (account settings) to help you manage public access to Amazon S3 resources. By default, S3 buckets and objects are created with public access disabled.\nHowever, an IAM principal with sufficient S3 permissions can enable public access at the bucket and/or object level. While enabled, Block public access (bucket settings) prevents an individual bucket and its contained objects from becoming publicly accessible. Similarly, Block public access (account settings) prevents all buckets and their contained objects from becoming publicly accessible across the entire account.","impact":"Everyone and everything on the Internet can connect to publicly accessible S3 buckets, which may increase the opportunity for malicious activities or loss of data confidentiality.","report_fields":["Name"],"remediation":"If utilizing Block Public Access (bucket settings):\nFrom Console:\n1. Login to AWS Management Console and open the Amazon S3 console using https://console.aws.amazon.com/s3/\n2. Select the Check box next to Bucket.\n3. Click on the 'Edit public access settings'.\n4. Click on 'Block all public access'\n5. Repeat for all the buckets in your AWS account that contain sensitive data.\nFrom Command Line:\n1. List all of the S3 Buckets 'aws s3 ls'\n2. Set the Block Public Access to true on that bucket\n'aws s3api put-public-access-block --bucket --public-accessblock-configuration 'BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true''\n\nIf utilizing Block Public Access (account settings):\nFrom console:\nIf the output reads true for separate configuration settings, then it is set on the account.\n1. Login to the AWS Management Console and open the Amazon S3 console using https://console.aws.amazon.com/s3/\n2. Choose Block public access (account settings)\n3. Choose Edit to change the block public access settings for all the buckets in your AWS account\n4. Choose the settings you want to change and then choose Save. For details about each setting, pause on the icons.\n5. When you're asked for confirmation, enter Confirm. Then Click on Confirm to save your changes.\nFrom Command Line:\nTo set Block Public access settings for this account, run the following command:\n'aws s3control put-public-access-block --public-access-block-configuration BlockPublicAcls=true, IgnorePublicAcls=true, BlockPublicPolicy=true, RestrictPublicBuckets=true --account-id '","multiregional":true,"service":"Amazon S3"},"ecc-aws-114":{"article":"This policy identifies Firewall rules attached to the cluster network which allow inbound traffic on all protocols from the public internet. Doing so, may allow a bad actor to bruteforce their way into the system and potentially get access to the entire cluster network.","impact":"Allowed inbound traffic on all protocols from the public internet enables attackers to use port scanners and other probing techniques to identify applications and services running on your EKS clusters and exploit their vulnerabilities.","report_fields":["arn"],"remediation":"1. Login to the AWS Console. \n2. Go to Security group. \n3. Go to the SG rules.\n4. Click on the reported Firewall rule. \n5. Click on Edit. \n6. Modify the rule. \n7. Click on Save.","multiregional":false,"service":"Amazon Elastic Kubernetes Service"},"ecc-aws-318":{"article":"Allowing an unlimited number of login attempts for a user connection can facilitate both brute-force login attacks and the occurrence of denial-of-service.","impact":"Unlimited number of login attempts for a user connection can facilitate brute-force login attacks.","report_fields":["DBInstanceArn"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/\n2. In the navigation pane, choose Parameter groups.\n3. Choose the required parameter group\n4. Choose Edit parameters.\n5. Under Parameters, in the search bar, type \"sec_max_failed_login_attempts\".\n6. Choose '3' or less\n7. Choose Save changes.\n8. Restart instance","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-132":{"article":"Services and databases store data that may be sensitive, protected by law, subject to regulatory requirements or compliance standards. \nIt is highly recommended that access to data will be restricted to encrypted protocols. This rule detects network settings that may expose data via unencrypted protocol over the public internet or to an overly wide local scope.","impact":"Unrestricted access increases the opportunity for malicious activity such as unauthorized access, denial-of-service attacks, and loss of data.","report_fields":["InstanceId","OwnerId"],"remediation":"1. Login to AWS Console at https://console.aws.amazon.com/ec2/.\n2. Go to 'Security groups'.\n3. Click on the reported security group.\n3. Go to the SG rules.\n4. Click on the reported rule.\n5. Click on 'Edit'.\n6. Modify the rule.\n7. Click on 'Save'.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-304":{"article":"You can grant additional permissions to an event bus by attaching a resource-based policy to it. With a resource-based policy, you can allow PutEvents, PutRule, and PutTargets API calls from another account. You can also use IAM conditions in the policy to grant permissions to an organization, apply tags, or filter events to only those from a specific rule or account. You can set a resource-based policy for an event bus when you create it or afterward.","impact":"A CloudWatch event bus with a policy that allows anyone to access your resource (\"Principal\": \"*\") can lead to data leaks and can allow unauthorized AWS users to send their CloudWatch events.","report_fields":["Arn"],"remediation":"Use the following procedure to modify the permissions for an existing event bus:\n1. Open the Amazon EventBridge console at https://console.aws.amazon.com/events/.\n2. In the left navigation pane, choose Event buses.\n3. In Name, choose the name of the event bus to manage permissions for.\n4. If a resource policy is attached to the event bus, the policy displays.\n5. Choose Manage permissions.\n6. Modify the policy that includes the permissions to grant unrestricted access to anyone (\"Principal\": \"*\") for the event bus by removing this permissions and granting new one for a specific AWS account, group, user etc.\n7. Choose Update.","multiregional":false,"service":"Amazon EventBridge"},"ecc-aws-387":{"article":"This policy identifies the Subnet that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["SubnetId","OwnerId","VpcId"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon VPC at https://console.aws.amazon.com/vpc/.\n2. Click on the 'Subnets'.\n3. Click on the required subnet.\n4. Open 'Tags' and click on the 'Manage tags'.\n5. Add new tag and save.","multiregional":false,"service":"Amazon Virtual Private Cloud"},"ecc-aws-368":{"article":"Amazon FSx for Lustre can take an automatic daily backup of your file system. These automatic daily backups occur during the daily backup window that was established when you created the file system. \nAutomatic daily backups are kept for a certain period of time, known as a retention period.\nIt is highly recommended to use automatic daily backups set at least to 7 days for file systems that have any level of critical functionality associated with them.","impact":"A retention period of fewer than 7 days set for FSx file systems can result in data loss and the inability to recover it in the event of failure.","report_fields":["ResourceARN"],"remediation":"To update your Amazon FSx Lustre file systems configuration in order to set up a sufficient backup retention period, perform the following actions: \n1. Open the Amazon FSx for Lustre console at https://console.aws.amazon.com/fsx/.\n2. From the console dashboard, choose the name of the file system that reconfigure.\n3. In the navigation pane, choose Backups.\n4. In the Settings tab, click Update.\n5. Select 'Yes' to enable automatic backups.\n6. For 'Automatic Backup Retention' set number of days to retain a backup at least to 7 days.\n7. Click update.","multiregional":false,"service":"Amazon FSx"},"ecc-aws-576":{"article":"Because dedicated instances are physically isolated at the host hardware level from instances that belong to other AWS accounts, these are more expensive than the ones running on the shared (default) environment. Dedicated EC2 instances must be regularly reviewed for cost optimization.\nUnless there is a business need to have EC2 instances with dedicated tenancy, you should change tenancy to less expensive type to avoid escalating costs. In some cases, Dedicated Hosts or Dedicated Instances can help you address compliance requirements or regulatory requirements and reduce costs by using your existing server-bound software licenses.","impact":"Keeping unnecessary EC2 instances with dedicated tenancy may lead to increased costs.","report_fields":["InstanceId","OwnerId"],"remediation":"If you decided to migrate a dedicated instance to default shared tenancy, follow the next steps:\n\nI. Create an AMI from an Amazon EC2 Instance:\n 1. Open the Amazon EC2 console at https://console.aws.amazon.com/ec2/.\n 2. In the navigation pane, choose 'Instances'.\n 3. Right-click the instance you want to use as the basis for your AMI, and choose 'Create Image' from the context menu.\n 4. In the 'Create Image' dialog box, type a unique name and description, and then choose 5'Create Image'. By default, Amazon EC2 shuts down the instance, takes snapshots of any attached volumes, creates and registers the AMI, and then reboots the instance. Choose 'No reboot' if you don't want your instance to be shut down.\n Warning: If you choose 'No reboot', AWS can't guarantee the file system integrity of the created image.\n 5. It may take a few minutes for the AMI to be created. After it is created, it will appear in the 'AMIs' view in AWS Explorer. \n\nII. Relaunch EC2 instance with the correct tenancy type:\n 1. From the Amazon EC2 the navigation bar, choose 'AMIs'.\n 2. Select 'Owned by me' and find the AMI created at step no. I.\n 3. Select the AMI, and then choose 'Launch instance from AMI'.\n 4. In 'Advanced details' tab, for 'Tenancy' leave the default value 'Shared - Run a shared hardware instance'.\n 5. Make all the other necessary configurations and click 'Launch Instance'.\n 6. Choose 'View Instances' to check the status of your instance.\n\nIII. Terminate the instance with dedicated tenancy in order to stop incurring charges for the resource:\n 1. In the navigation pane, choose 'Instances'.\n 2. Select the instance, and choose 'Instance state', 'Terminate instance'.\n 3. Choose 'Terminate' when prompted for confirmation.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-194":{"article":"Enable deletion protection to protect your Load Balancers from deletion. If you enable deletion protection for your load balancer, you must disable delete protection before you can delete the load balancer.","impact":"It is quite possible to accidentally delete an ELB that was not supposed to be deleted. This can result in the loss of website or application availability.","report_fields":["LoadBalancerArn"],"remediation":"1. Open the Amazon EC2 console at https://console.aws.amazon.com/ec2/.\n2. On the navigation pane, under LOAD BALANCING, choose 'Load Balancers'. \n3. Choose the load balancer. \n4. On the 'Description' tab, choose 'Edit' attributes. \n5. On the 'Edit load balancer attributes' page, select 'Enable for Delete Protection', and then choose 'Save'. \n6. Choose Save.","multiregional":false,"service":"Amazon Elastic Load Balancing"},"ecc-aws-394":{"article":"This policy identifies the AppFlow that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["flowArn"],"remediation":"Use AWS CLI to add tags to appflow:\naws appflow tag-resource --resource-arn arn --tags example=example","multiregional":false,"service":"Amazon AppFlow"},"ecc-aws-218":{"article":"Secrets Manager helps you improve the security posture of your organization. Secrets include database credentials, passwords, and third-party API keys. You can use Secrets Manager to store secrets centrally, encrypt secrets automatically, control access to secrets, and rotate secrets safely and automatically. \nSecrets Manager can rotate secrets. You can use rotation to replace long-term secrets with short-term ones. Rotating your secrets limits how long an unauthorized user can use a compromised secret. For this reason, you should rotate your secrets frequently.","impact":"Compromised secrets can lead to unauthorized access to different AWS resources. Rotating your secrets limits the amount of time an unauthorized user can use a compromised secret.","report_fields":["Name","ARN"],"remediation":"1. Open the Secrets Manager console at https://console.aws.amazon.com/secretsmanager/.\n2. To find the secret that requires rotating, enter the secret name in the search field.\n3. Choose the secret you want to rotate, which displays the secrets details page.\n4. Under Rotation configuration, choose Edit rotation.\n5. From Edit rotation configuration, choose Enable automatic rotation.\n6. For Select Rotation Interval, choose a rotation interval.\n7. Choose a Lambda function for rotation. For information about customizing your Lambda rotation function, see 'Understanding and customizing your Lambda rotation function' in the AWS Secrets Manager User Guide: https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotate-secrets_how.html\n8. To configure the secret for rotation, choose Next.","multiregional":false,"service":"AWS Secrets Manager"},"ecc-aws-082":{"article":"Real-time monitoring of API calls can be achieved by directing CloudTrail Logs to CloudWatch Logs and establishing corresponding metric filters and alarms.\nIt is recommended that a metric filter and alarm be established for customer created CMKs that have changed state to disabled or scheduled deletion. Data encrypted with disabled or deleted keys will no longer be accessible.","impact":"Lack of monitoring and logging of CMK key disabling or deletion can lead to insufficient response time to detect accidental or intentional deletion of a customer key. This may result in a loss of all data encrypted using this key.","report_fields":["account_id","account_name"],"remediation":"Perform the following to setup the metric filter, alarm, SNS topic, subscription and trail using the AWS CLI: \n1. Create a log group.\naws logs create-log-group --log-group-name \n2. Create a log stream.\naws logs create-log-stream --log-group-name --log-stream-name \n3. Create a metric filter based on provided filter pattern which checks for disabled or scheduled for deletion CMK's and the taken from step 1.\naws logs put-metric-filter --log-group-name --filter-name `` --metrictransformations metricName= `` ,metricNamespace='CISBenchmark',metricValue=1 --filter-pattern '{($.eventSource = kms.amazonaws.com) && (($.eventName=DisableKey)||($.eventName=ScheduleKeyDeletion)) }'\n4. Create a topic to which notifications will be published.\naws sns create-topic --name \n5. Subscribe an endpoint to an Amazon SNS topic. If the endpoint type is HTTP/S or email, or if the endpoint and the topic are not in the same Amazon Web Services account, the endpoint owner must run the ConfirmSubscription action to confirm the subscription.\naws sns subscribe --topic-arn --protocol email --notification-endpoint \n6. Amazon SNS will send a subscription confirmation message to the endpoint. Confirm subscription to topic, by visiting the link in an email that you specified as notification endpoint.\n7. Create an alarm that is associated with the CloudWatch Logs Metric Filter created in step 3 and an SNS topic created in step 4. \naws cloudwatch put-metric-alarm --alarm-name `` --metric-name `` --statistic Sum --period 300 --threshold 1 --comparison-operator GreaterThanOrEqualToThreshold --evaluationperiods 1 --namespace 'CISBenchmark' --alarm-actions \n8. Create an S3 bucket to deliver log files to:\naws s3api create-bucket --bucket \n9. To deliver log files to an S3 bucket, CloudTrail must have the required permissions. The following policy allows CloudTrail to write log files to the bucket from supported regions. Replace myBucketName, [optionalPrefix]/, myAccountID, region, and trailName with the appropriate values for your configuration. \nCreate a file 'policy.json' with the following policy.\n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"AWSCloudTrailAclCheck20150319\",\n \"Effect\": \"Allow\",\n \"Principal\": {\"Service\": \"cloudtrail.amazonaws.com\"},\n \"Action\": \"s3:GetBucketAcl\",\n \"Resource\": \"arn:aws:s3:::\"\n },\n {\n \"Sid\": \"AWSCloudTrailWrite20150319\",\n \"Effect\": \"Allow\",\n \"Principal\": {\"Service\": \"cloudtrail.amazonaws.com\"},\n \"Action\": \"s3:PutObject\",\n \"Resource\": \"arn:aws:s3:::/[optionalPrefix]/AWSLogs//*\",\n \"Condition\": {\n \"StringEquals\": {\n \"s3:x-amz-acl\": \"bucket-owner-full-control\",\n \"aws:SourceArn\": \"arn:aws:cloudtrail:::trail/\"\n }\n }\n }\n ]\n}}\n10. Apply the Amazon S3 bucket policy to the Amazon S3 bucket created in step 8.\naws s3api put-bucket-policy --bucket --policy file://.json\n11. Create a role for CloudTrail that enables it to send events to the CloudWatch Logs log group. To create the JSON file that will contain the policy document, open a text editor and save the following policy contents in a file called 'assume_role_policy_document.json'. \n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Principal\": {\n \"Service\": \"cloudtrail.amazonaws.com\"\n },\n \"Action\": \"sts:AssumeRole\"\n }\n ]\n}}\n12. Create a role.\naws iam create-role --role-name --assume-role-policy-document file://.json\n13. Create the following role policy document for CloudTrail. This document grants CloudTrail the permissions required to create a CloudWatch Logs log stream in the log group you specify and to deliver CloudTrail events to that log stream. Save the policy document in a file called role-policy-document.json. Replace region, accountID, log_group_name, with the appropriate values for your configuration. \n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n\n \"Sid\": \"AWSCloudTrailCreateLogStream2014110\",\n \"Effect\": \"Allow\",\n \"Action\": [\n \"logs:CreateLogStream\"\n ],\n \"Resource\": [\n \"arn:aws:logs:::log-group::log-stream:_CloudTrail_*\"\n ]\n\n },\n {\n \"Sid\": \"AWSCloudTrailPutLogEvents20141101\",\n \"Effect\": \"Allow\",\n \"Action\": [\n \"logs:PutLogEvents\"\n ],\n \"Resource\": [\n \"arn:aws:logs:::log-group::log-stream:_CloudTrail_*\"\n ]\n }\n ]\n}}\n14. Run the following command to apply the policy to the role.\naws iam put-role-policy --role-name --policy-name cloudtrail-policy --policy-document file://.json\n15. Create a trail that specifies the settings for delivery of log data to an Amazon S3 bucket at is associated with the S3 bucket created in step 8, CloudWatch log group created in step 1 and IAM role created in step 12. \naws cloudtrail create-trail --include-global-service-events --is-multi-region-trail --name --s3-bucket-name --cloud-watch-logs-log-group-arn --cloud-watch-logs-role-arn \n16. Start the recording of Amazon Web Services API calls and log file delivery for a trail. For a trail that is enabled in all regions, this operation must be called from the region in which the trail was created.\naws cloudtrail start-logging --name ","multiregional":false,"service":"AWS Account"},"ecc-aws-275":{"article":"Amazon Aurora-MySQL database engine logs (Audit, Error, General, SlowQuery) should be enabled and sent to CloudWatch.\nRDS cluster logging provides detailed records of requests made to RDS databases. RDS cluster logs can assist with security and access audits and can help to diagnose availability issues.","impact":"With disabled logs for the RDS DB cluster, it is harder to analyze statistics, diagnose issues, detect different types of attacks, and retain data for regulatory or legal purposes.","report_fields":["DBClusterArn"],"remediation":"Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n1. In the navigation pane, choose Databases.\n2. Choose the DB cluster that you want to modify.\n3. Choose Modify.\n4. Under Log exports, choose Audit, Error, General, SlowQuery log types to start publishing to CloudWatch Logs.\n5. Log exports is available only for database engine versions that support publishing to CloudWatch Logs.\n6. Choose Continue. Then on the summary page, choose Modify.\n\nTo enable and publish Aurora-MySQL logs to CloudWatch Logs from the AWS Management Console, set the following parameters in a custom Cluster Parameter Group:\n- general_log=1\n- slow_query_log=1\n- log_output = FILE\n\nTo create a custom cluster parameter group:\n1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. In the navigation pane, choose Parameter groups.\n3. Choose Create parameter group. The Create parameter group window appears.\n4. In the Parameter group family list, choose a DB parameter group family.\n5. In the Type list, choose DB Cluster Parameter Group.\n6. In Group name, enter the name of the new DB cluster parameter group.\n7. In Description, enter a description for the new DB parameter group.\n8. Choose Create.\n\nTo apply a new DB cluster parameter group to an RDS DB cluster:\n1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. In the navigation pane, choose Databases.\n3. Choose the DB cluster that you want to modify.\n4. Choose Modify. The Modify DB cluster page appears.\n5. Under Database options, change the DB cluster parameter group.\n6. When you finish you changes, choose Continue. Check the summary of modifications.\n7. Choose Modify CLuster to save your changes.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-117":{"article":"API keys are string tokens that you provide to client application developers to grant access to your APIs. You can use API keys together with usage plans or Lambda authorizers to control access to your APIs. API Gateway can generate API keys on your behalf, or you can import them from a CSV file.","impact":"Without API keys, your APIs are vulnerable to unauthorized access.","report_fields":["id","path"],"remediation":"1. Sign in to the AWS Management Console and open the API Gateway console at https://console.aws.amazon.com/apigateway/. \n2. In the API Gateway main navigation pane, choose Resources.\n3. Under Resources, create a new method or choose the existing one.\n4. Choose Method Request.\n5. Under the Authorization Settings section, choose true for API Key Required.\n6. Select the checkmark icon to save the settings.\n7. Deploy or redeploy the API for the requirement to take effect.","multiregional":false,"service":"Amazon API Gateway"},"ecc-aws-005":{"article":"Firewall and router configurations should be used to restrict connections between untrusted networks and any system components in the cloud environment.","impact":"When the VPC security group associated with an RDS instance allows unrestricted access (0.0.0.0/0 or ::/0), everyone and everything on the Internet can establish a connection to your database. This can increase the opportunity for malicious activities such as brute-force attacks, SQL injections, or DoS/DDoS attacks.","report_fields":["DBInstanceArn"],"remediation":"1. Login to the AWS Console at console.aws.amazon.com/vpc/ .\n2. Go to security group.\n3. Click on the reported Firewall rule ID.\n4. Click on Edit inbounds rules.\n5. Modify the rule\n6. Click on Save.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-034":{"article":"This policy identifies security group rules that allow inbound traffic to the NetBIOS-SSN port (139) from the public internet. Allowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Unrestricted NetBIOS access can increase opportunities for malicious activity such as man-in-the-middle attacks (MITM), Denial of Service (DoS) attacks or BadTunnel exploits.","report_fields":["GroupId","VpcId","OwnerId"],"remediation":"1. Login to the AWS Console.\n2. Go to Security Group.\n3. Go to the SG rules.\n4. Click on the reported Firewall rule.\n5. Click on Edit.\n6. Modify the rule.\n7. Click on Save.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-366":{"article":"You can configure the log levels that Amazon FSx logs; that is, whether Amazon FSx will log only error events, only warning events, or both error and warning events. Error and warning events can be logged from the following data repository operations: Automatic export, Data repository tasks. CloudWatch Logs allows you to store, view, and search audit event logs in the Amazon CloudWatch console, run queries on the logs using CloudWatch Logs Insights, and trigger CloudWatch alarms or Lambda functions.","impact":"With disabled monitoring and logging for FSx Lustre, it may be difficult to troubleshoot any issues that might happen with FSx Lustre.","report_fields":["ResourceARN"],"remediation":"To update your Amazon FSx Lustre file systems configuration in order to set up a logging, perform the following actions: \n1. Open the Amazon FSx for Lustre console at https://console.aws.amazon.com/fsx/.\n2. From the console dashboard, choose the name of the file system that you want to reconfigure.\n3. In the navigation pane at the bottom of the page, choose 'Monitoring' tab.\n4. In the 'Logging tab', click 'Update'.\n5. The 'Update logging configuration' window opens, select which types of logs to log.\n6. Choose CloudWatch Logs destination.\n7. Click on 'Update'. It may take a few minutes before changes will apply.","multiregional":false,"service":"Amazon FSx"},"ecc-aws-305":{"article":"The same SQL query can be executed in multiple ways and still produce different results. The PostgreSQL planner/optimizer is responsible for creating an optimal execution plan for each query. The 'log_planner_stats' flag controls the inclusion of PostgreSQL planner performance statistics in the PostgreSQL logs for each query.","impact":"The 'log_planner_stats' flag enables a crude profiling method for logging PostgreSQL planner performance statistics which even though can be useful for troubleshooting, it may increase the amount of logs significantly and have performance overhead.","report_fields":["DBInstanceArn"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/\n2. In the navigation pane, choose Parameter groups.\n3. Choose the required parameter group\n4. Choose Edit parameters.\n5. Under Parameters, in search bar type \"log_planner_stats\".\n6. Choose 0.\n7. Choose Save changes.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-183":{"article":"Backups help you to recover more quickly from a security incident. They also strengthen the resilience of your systems. DynamoDB point-in-time recovery (PITR) automates backups for DynamoDB tables. It reduces the time to recover from accidental delete or write operations. DynamoDB tables that have PITR enabled can be restored to any point in time in the last 35 days.","impact":"Without enabling DynamoDB point-in-time recovery, it is impossible to quickly recover data in the event of failure from accidental delete or write operations.","report_fields":["TableArn"],"remediation":"1. Open the DynamoDB console at https://console.aws.amazon.com/dynamodb/.\n2. Choose the table that you want to work with, and then choose Backups.\n3. In the Point-in-time Recovery section, under Status, choose Enable.\n4. Choose Enable again to confirm the change.","multiregional":false,"service":"Amazon DynamoDB"},"ecc-aws-510":{"article":"Removing EFS without mount target will help you to avoid unexpected charges on your AWS bill and make cloud more organized.","impact":"Having an EFS without any mount targets can lead to wasted costs, unused resources, cluttered cloud environment.","report_fields":["FileSystemArn","OwnerId"],"remediation":"1. Login to the AWS Management Console and Navigate to Elastic File System (EFS) dashboard.\n2. Select File Systems from the left navigation panel.\n3. Click on the required file system.\n4. Click 'Attach' and then click on the 'Manage mount targets'.\n5. Choose 'VPC' and click on the 'Add mount target'.\n6. Choose 'Availability zone', 'Subnet ID', 'IP address', 'Security Groups'.\n7. Save.","multiregional":false,"service":"Amazon Elastic File System"},"ecc-aws-063":{"article":"Security groups provide stateful filtering of ingress and egress network traffic to AWS resources. It is recommended that no security group allows unrestricted ingress access to remote server administration ports, such as SSH to port 22 and RDP to port 3389.","impact":"Exposing port 3389 (RDP) to public access can increase opportunities for malicious activities such as unauthorized access, Man-In-The-Middle attacks (MITM), and brute-force attacks that raise the risk of resource compromising.","report_fields":["GroupId","VpcId","OwnerId"],"remediation":"1. Login to the AWS Management Console at https://console.aws.amazon.com/vpc/home\n2. In the left pane, click on 'Security Groups'.\n3. For each security group, perform the following:\n4. Select the security group.\n5. Click on the 'Inbound Rules' tab.\n6. Click on the 'Edit inbound rules' button.\n7. Identify the rules to be edited or removed.\n8. Either:\n A) Update the Source field to a range other than 0.0.0.0/0 or ::/0 \n B) Click 'Delete' to remove the offending inbound rule.\n9. Click on 'Save rules'.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-292":{"article":"Application Load Balancers should have access logs enabled to capture detailed information about requests sent to load balancer. Each log contains information such as the time the request was received, the client's IP address, latencies, request paths, and server responses. You can use these access logs to analyze traffic patterns and troubleshoot issues.","impact":"Disabled access logs for Application Load Balancers make it harder to analyze statistics, diagnose issues, detect different types of attacks, and retain data for regulatory or legal purposes.","report_fields":["EnvironmentArn"],"remediation":"To enable access logs Elastic Beanstalk need to be configured with Application load balancer:\n1. Open the Elastic Beanstalk console at console.aws.amazon.com/elasticbeanstalk/ and in the Regions list, select your AWS Region. \n2. In the navigation pane, choose Environments, and then choose the name of your environment from the list. \n3. In the navigation pane, choose Configuration. \n4. In the 'Load balancer' category, choose Edit. \n5. Under Access log files, click on the 'Enabled'.\n6. Choose bucket with bucket policy that have permission for Beanstalk to write in bucket.\n7. Choose Apply.","multiregional":false,"service":"AWS Elastic Beanstalk"},"ecc-aws-127":{"article":"Encrypt Amazon RDS Cluster at rest by enabling the encryption option for your Amazon RDS DB cluster.","impact":"Disabled encryption allows a user to get unauthorized access to sensitive data in RDS clusters.","report_fields":["DBClusterArn"],"remediation":"On the AWS RDS console, for each RDS cluster that failed the rule, enable encryption in the Details section. You can choose either to use your default AWS encryption key or supply an AWS KMS key.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-148":{"article":"Server access logging provides detailed records for the requests that are made to an Amazon S3 bucket. Server access logs are useful for many applications. For example, access log information can be useful for security and access audits. It can also help you learn about your customer base and understand your Amazon S3 bill.","impact":"Lack of auditing and logging can lead to insufficient response time to threats from an attacker, which can result in data loss and degradation of the service.","report_fields":["Name"],"remediation":"1. Sign in to the AWS Management Console and open the Amazon S3 console at https://console.aws.amazon.com/s3/. \n2. From the Buckets list, choose the name of the bucket that you want to enable server access logging for. \n3. Choose Properties.\n4. In the Server access logging section, choose Edit. \n5. Under Server access logging, select Enable. \n6. For Target bucket, enter the name of the bucket that you want to receive the log record objects. \n7. The target bucket must be in the same Region as the source bucket and must not have a default retention period configuration.\n8. Choose Save changes.","multiregional":true,"service":"Amazon S3"},"ecc-aws-390":{"article":"This policy identifies the peering connections that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["VpcPeeringConnectionId"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon VPC at https://console.aws.amazon.com/vpc.\n2. Click on the 'Peering connections'.\n3. Click on the required peering connection.\n4. Open 'Tags' and click on the 'Manage tags'.\n5. Add new tag and save.","multiregional":false,"service":"Amazon Virtual Private Cloud"},"ecc-aws-099":{"article":"Real-time monitoring of API calls can be achieved by directing CloudTrail Logs to CloudWatch Logs and establishing corresponding metric filters and alarms. Routing tables are used to route network traffic between subnets and to network gateways. It is recommended that a metric filter and alarm be established for detecting changes made to route tables.","impact":"Lack of monitoring and logging of route table changes can result in insufficient response time to detect accidental or intentional modifications that may lead to uncontrolled network traffic.","report_fields":["account_id","account_name"],"remediation":"Perform the following to setup the metric filter, alarm, SNS topic, subscription and trail using the AWS CLI: \n1. Create a log group.\naws logs create-log-group --log-group-name \n2. Create a log stream.\naws logs create-log-stream --log-group-name --log-stream-name \n3. Create a metric filter based on provided filter pattern which checks for unauthorized API calls and the taken from step 1.\naws logs put-metric-filter --log-group-name --filter-name `` --metric-transformations metricName=``,metricNamespace=,metricValue='1' --filter-pattern '{{($.eventName = CreateRoute) || ($.eventName = CreateRouteTable) || ($.eventName = ReplaceRoute) || ($.eventName = ReplaceRouteTableAssociation) || ($.eventName = DeleteRouteTable) || ($.eventName = DeleteRoute) || ($.eventName = DisassociateRouteTable)}}'\n4. Create a topic to which notifications will be published.\naws sns create-topic --name \n5. Subscribe an endpoint to an Amazon SNS topic. If the endpoint type is HTTP/S or email, or if the endpoint and the topic are not in the same Amazon Web Services account, the endpoint owner must run the ConfirmSubscription action to confirm the subscription.\naws sns subscribe --topic-arn --protocol email --notification-endpoint \n6. Amazon SNS will send a subscription confirmation message to the endpoint. Confirm subscription to topic, by visiting the link in an email that you specified as notification endpoint.\n7. Create an alarm that is associated with the CloudWatch Logs Metric Filter created in step 3 and an SNS topic created in step 4. \naws cloudwatch put-metric-alarm --alarm-name `` --metric-name `` --statistic Sum --period 300 --threshold 1 --comparison-operator GreaterThanOrEqualToThreshold --evaluation-periods 1 --namespace --alarm-actions \n8. Create an S3 bucket to deliver log files to:\naws s3api create-bucket --bucket \n9. To deliver log files to an S3 bucket, CloudTrail must have the required permissions. The following policy allows CloudTrail to write log files to the bucket from supported regions. Replace myBucketName, [optionalPrefix]/, myAccountID, region, and trailName with the appropriate values for your configuration. \nCreate a file 'policy.json' with the following policy.\n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"AWSCloudTrailAclCheck20150319\",\n \"Effect\": \"Allow\",\n \"Principal\": {\"Service\": \"cloudtrail.amazonaws.com\"},\n \"Action\": \"s3:GetBucketAcl\",\n \"Resource\": \"arn:aws:s3:::\"\n },\n {\n \"Sid\": \"AWSCloudTrailWrite20150319\",\n \"Effect\": \"Allow\",\n \"Principal\": {\"Service\": \"cloudtrail.amazonaws.com\"},\n \"Action\": \"s3:PutObject\",\n \"Resource\": \"arn:aws:s3:::/[optionalPrefix]/AWSLogs//*\",\n \"Condition\": {\n \"StringEquals\": {\n \"s3:x-amz-acl\": \"bucket-owner-full-control\",\n \"aws:SourceArn\": \"arn:aws:cloudtrail:::trail/\"\n }\n }\n }\n ]\n}}\n10. Apply the Amazon S3 bucket policy to the Amazon S3 bucket created in step 8.\naws s3api put-bucket-policy --bucket --policy file://.json\n11. Create a role for CloudTrail that enables it to send events to the CloudWatch Logs log group. To create the JSON file that will contain the policy document, open a text editor and save the following policy contents in a file called 'assume_role_policy_document.json'. \n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Principal\": {\n \"Service\": \"cloudtrail.amazonaws.com\"\n },\n \"Action\": \"sts:AssumeRole\"\n }\n ]\n}}\n12. Create a role.\naws iam create-role --role-name --assume-role-policy-document file://.json\n13. Create the following role policy document for CloudTrail. This document grants CloudTrail the permissions required to create a CloudWatch Logs log stream in the log group you specify and to deliver CloudTrail events to that log stream. Save the policy document in a file called role-policy-document.json. Replace region, accountID, log_group_name, with the appropriate values for your configuration. \n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n\n \"Sid\": \"AWSCloudTrailCreateLogStream2014110\",\n \"Effect\": \"Allow\",\n \"Action\": [\n \"logs:CreateLogStream\"\n ],\n \"Resource\": [\n \"arn:aws:logs:::log-group::log-stream:_CloudTrail_*\"\n ]\n\n },\n {\n \"Sid\": \"AWSCloudTrailPutLogEvents20141101\",\n \"Effect\": \"Allow\",\n \"Action\": [\n \"logs:PutLogEvents\"\n ],\n \"Resource\": [\n \"arn:aws:logs:::log-group::log-stream:_CloudTrail_*\"\n ]\n }\n ]\n}}\n14. Run the following command to apply the policy to the role.\naws iam put-role-policy --role-name --policy-name cloudtrail-policy --policy-document file://.json\n15. Create a trail that specifies the settings for delivery of log data to an Amazon S3 bucket at is associated with the S3 bucket created in step 8, CloudWatch log group created in step 1 and IAM role created in step 12. \naws cloudtrail create-trail --include-global-service-events --is-multi-region-trail --name --s3-bucket-name --cloud-watch-logs-log-group-arn --cloud-watch-logs-role-arn \n16. Start the recording of Amazon Web Services API calls and log file delivery for a trail. For a trail that is enabled in all regions, this operation must be called from the region in which the trail was created.\naws cloudtrail start-logging --name ","multiregional":false,"service":"AWS Account"},"ecc-aws-207":{"article":"Amazon Aurora database engine logs (Audit, Error, General, SlowQuery) should be enabled and sent to CloudWatch.\nDatabase logging provides detailed records of requests made to RDS. Database logs can assist with security and access audits and can help to diagnose availability issues.","impact":"With disabled logs for the database, it is harder to analyze statistics, diagnose issues, detect different types of attacks, and retain data for regulatory or legal purposes.","report_fields":["DBInstanceArn"],"remediation":"Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n1. In the navigation pane, choose Databases.\n2. Choose the DB instance that you want to modify.\n3. Choose Modify.\n4. Under Log exports, choose Audit, Error, General, SlowQuery log types to start publishing to CloudWatch Logs.\n5. Log exports is available only for database engine versions that support publishing to CloudWatch Logs.\n6. Choose Continue. Then on the summary page, choose Modify.\n\nTo enable and publish Aurora logs to CloudWatch Logs from the AWS Management Console, set the following parameters in a custom DB Parameter Group:\n- general_log=1\n- slow_query_log=1\n- log_output = FILE\n\nTo create a custom DB parameter group:\n1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. In the navigation pane, choose Parameter groups.\n3. Choose Create parameter group. The Create parameter group window appears.\n4. In the Parameter group family list, choose a DB parameter group family.\n5. In the Type list, choose DB Parameter Group.\n6. In Group name, enter the name of the new DB parameter group.\n7. In Description, enter a description for the new DB parameter group.\n8. Choose Create.\n\nTo apply a new DB parameter group or DB options group to an RDS DB instance:\n1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. In the navigation pane, choose Databases.\n3. Choose the DB instance that you want to modify.\n4. Choose Modify. The Modify DB Instance page appears.\n5. Under Database options, change the DB parameter group and DB options group as needed.\n6. When you finish you changes, choose Continue. Check the summary of modifications.\n7. (Optional) Choose Apply immediately to apply the changes immediately. Choosing this option can cause an outage in some cases. \n8. Choose Modify DB Instance to save your changes.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-488":{"article":"Enabling CloudWatch retention establishes how long log events are kept in AWS CloudWatch Logs. Retention settings are assigned to CloudWatch log groups and the retention period assigned to a log group is applied to their log streams. Any data older than the current retention setting is deleted automatically.","impact":"Log data is stored in CloudWatch Logs indefinitely by default. This may incur high unexpected costs, especially when combined with other forms of logging.","report_fields":["arn"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon CloudWatch at https://console.aws.amazon.com/cloudwatch.\n2. Click on the 'Log groups'.\n3. Click on the required log group.\n4. Click on the 'ACtions' and ' Edit retention setting'.\n5. Choose required retention period.\n6. Save.","multiregional":false,"service":"Amazon CloudWatch"},"ecc-aws-398":{"article":"This policy identifies the Cloudtrail trails that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["TrailARN"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon Cloudtrail at https://console.aws.amazon.com/cloudtrail.\n2. Click on the required trail.\n3. Click on the 'Manage tags'.\n4. Add new tag and save.","multiregional":false,"service":"AWS CloudTrail"},"ecc-aws-217":{"article":"Enhanced VPC routing forces all COPY and UNLOAD traffic between the cluster and data repositories to go through your VPC. You can then use VPC features such as security groups and network access control lists to secure network traffic.","impact":"Without enhanced VPC routing, you cannot be sure that traffic is not allowed from the Internet. Public access increases the opportunity for malicious activity such as hacking, denial-of-service attacks, and data loss.","report_fields":["ClusterIdentifier"],"remediation":"To update a cluster and enable enhanced VPC routing:\n1. Sign in to the AWS Management Console and open the Amazon Redshift console at https://console.aws.amazon.com/redshift/.\n2. On the navigation menu, choose Clusters, then choose a cluster that you want to modify.\n3. Go to 'Properties' tab.\n4. At the 'Network and security settings' section, click 'Edit'.\n5. To enable Enhanced VPC routing for 'Enhanced VPC routing' select 'Turn on'.\n5. Choose 'Save changes'.","multiregional":false,"service":"Amazon Redshift"},"ecc-aws-026":{"article":"Ensure backups on the RDS instance are enabled.","impact":"Failure to enable backups for RDS instances can result in data loss and inability to recover it in the event of a user error on the source database, an unsuccessful major change to the instance database, or other issues.","report_fields":["DBInstanceArn"],"remediation":"1. Login to the AWS Console. \n2. Choose the RDS Service.\n3. Select your RDS DB without backups and click on its name.\n4. Click on the 'Backups' tab and choose retention period of 1 day and more.\n5. Click on 'Save'.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-391":{"article":"This policy identifies the VPC that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["VpcId","OwnerId"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon VPC at https://console.aws.amazon.com/vpc.\n2. Click on the 'Your VPCs'.\n3. Click on the required vpc.\n4. Open 'Tags' and click on the 'Manage tags'.\n5. Add new tag and save.","multiregional":false,"service":"Amazon Virtual Private Cloud"},"ecc-aws-421":{"article":"This policy identifies the Lambda functions that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["FunctionArn"],"remediation":"1. Login to the AWS Management Console and open the Amazon Lambda https://console.aws.amazon.com/lambda/ .\n2. In the navigation pane click on the 'Functions'.\n3. Click on the required function.\n4. Click on the 'Configuration' and then 'Tags'.\n5. Click 'Manage tags'.\n6. Click 'Add new tag'.\n7. Enter values.\n8. Save.","multiregional":false,"service":"AWS Lambda"},"ecc-aws-177":{"article":"You can use API Gateway to generate an SSL certificate and then use its public key in the backend to verify that HTTP requests to your backend system are from API Gateway. This allows your HTTP backend to control and accept only requests that originate from Amazon API Gateway, even if the backend is publicly accessible.","impact":"Without SSL certificates, the HTTP backend will accept all requests, not just those that originate from Amazon API Gateway.","report_fields":["stageName","restApiId"],"remediation":"Step 1. Generate a client certificate using the API Gateway console\n1. Open the API Gateway console at https://console.aws.amazon.com/apigateway/.\n2. Choose a REST API.\n3. In the main navigation pane, choose Client Certificates.\n4. From the Client Certificates pane, choose Generate Client Certificate.\n5. Optionally, for Edit, choose to add a descriptive title for the generated certificate and choose Save to save the description. API Gateway generates a new certificate and returns the new certificate GUID, along with the PEM-encoded public key.\n\nStep 2. Configure an API to use SSL certificates\n1. In the API Gateway console, create or open an API for which you want to use the client certificate. Make sure that the API has been deployed to a stage.\n2. Choose Stages under the selected API and then choose a stage.\n3. In the Stage Editor panel, select a certificate under the Client Certificate section.\n4. To save the settings, choose Save Changes. If the API has been deployed previously in the API Gateway console, you'll need to redeploy it for the changes to take effect.","multiregional":false,"service":"Amazon API Gateway"},"ecc-aws-341":{"article":"SageMaker Model containers are internet-enabled by default. This allows containers to access external services and resources on the public internet as part of your training and inference workloads.","impact":"Disabled network isolation could provide an avenue for unauthorized access to your data. For example, a malicious user or code that you accidentally install on the container (in the form of a publicly available source code library) could access your data and transfer it to a remote host.","report_fields":["ModelArn"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nTo create new model with enabled network isolation:\n1. Open the Sagemaker console at https://console.aws.amazon.com/sagemaker/\n2. In the navigation pane, under Inference choose Models.\n3. Choose create model.\n4. Under Network, choose Enable network isolation\n5. Create model.","multiregional":false,"service":"Amazon SageMaker"},"ecc-aws-228":{"article":"Amazon ECR Tag Immutability enables customers to rely on the descriptive tags of an image as a reliable mechanism to track and uniquely identify images. Prior to this enhancement, tags could be overwritten requiring developers to use the Image SHA to know which image was being deployed. By setting an image tag as immutable, developers can now rely on the tag to correlate the deployed image version with the build that produced the image.","impact":"Disabled tag immutability can result in image tags in the repository being overwritten, for this reason users cannot rely on the descriptive tags of an image as a mechanism to track and uniquely identify images.","report_fields":["repositoryArn"],"remediation":"To change the policy using the AWS Console, follow these steps:\n1. Log in to the AWS Management Console at https://console.aws.amazon.com/.\n2. Open the Amazon ECR console.\n3. Select a repository using the radio button.\n4. Click Edit.\n5. Enable the Tag immutability toggle.","multiregional":false,"service":"Amazon Elastic Container Registry"},"ecc-aws-467":{"article":"Multi-AZ file systems support all the availability and durability features of Single-AZ file systems. In addition, they are designed to provide continuous availability to data, even during file system maintenance, infrastructure component replacement, and when an Availability Zone is unavailable. In a Multi-AZ deployment, Amazon FSx automatically provisions and maintains a standby file server in a different Availability Zone. Any changes written to disk in your file system are synchronously replicated across Availability Zones to the standby. If there is planned file system maintenance or unplanned service disruption, Amazon FSx automatically fails over to the secondary file server, allowing you to continue accessing your data without manual intervention.\nMulti-AZ file systems are recommended for most production workloads that require high availability to shared Windows file data. Single-AZ file systems offer a lower price point for workloads that don\u2019t require the high availability of a Multi-AZ solution and that can recover from the most recent file system backup if data is lost.","impact":"When an FSx file system is not configured to use multiple Availability Zones and Availability Zone in which file system deploy becomes unavailable, your data also can become unavailable. Using Single-AZ does not provide data resiliency and is not recommended for use cases such as business-critical production workloads that require high availability.","report_fields":["ResourceARN"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nTo create FSx system with enabled Multi-AZ, perform the following actions: \n1. Open the Amazon FSx console at https://console.aws.amazon.com/fsx/.\n2. On the dashboard, choose 'Create file system 'to start the file system creation wizard.\n3. On the 'Select file system type' page, choose ' FSx for Windows File Server', and then choose 'Next'. The 'Create file system' page appears.\n4. For 'File system name - optional', enter a name for your file system. \n5. For Deployment type choose 'Multi-AZ'.\n6. Set up all other necessary configurations.\n7. Choose 'Next'.\n8. Review the file system configuration shown on the 'Create file system' page. For your reference, note which file system settings you can modify after the file system is created.\n9. Choose 'Create file system'.","multiregional":false,"service":"Amazon FSx"},"ecc-aws-438":{"article":"'Allow all' a legacy permissions mode that enables access control with API-level granularity for ledgers. 'Standard' a permissions mode that enables access control with finer granularity for ledgers, tables, and PartiQL commands.\nBy default, this mode denies all requests to run any PartiQL commands on any tables in this ledger. To allow PartiQL commands, you must create IAM permissions policies for specific table resources and PartiQL actions, in addition to the SendCommand API permission for the ledger.","impact":"All users with SendCommand API permission can run all PartiQL commands on any table.","report_fields":["EnvironmentArn"],"remediation":"1. Open the AWS QLDB console at https://console.aws.amazon.com/qldb.\n2. Click on the required ledger.\n3. Click on the 'Edit'.\n4. Choose 'Standard access'.","multiregional":false,"service":"Amazon QLDB"},"ecc-aws-440":{"article":"To monitor your AWS AppSync GraphQL API and help debug issues related to requests, you can turn on logging to Amazon CloudWatch Logs.","impact":"With disabled logging of API Gateway, it may be difficult to troubleshoot any issues that might happen with APIs or find who accessed API and how they accessed it.","report_fields":["arn","name"],"remediation":"To turn on automatic logging on a GraphQL API, use the AWS AppSync console.\n1. Sign in to the AWS AppSync console.\n2. On the APIs page, choose the name of a GraphQL API.\n3. On your API's homepage, in the navigation pane, choose 'Settings'.\n4. Under 'Logging', do the following:\n a. Turn on 'Enable Logs'.\n b. (Optional) For detailed request-level logging, select the check box under 'Include verbose content'.\n c. Optional) Under 'Field resolver log level', choose your preferred field-level logging level (None, Error, or All).\n d. Under 'Create or use an existing role', choose 'New role' to create a new AWS Identity and Access Management (IAM) that allows AWS AppSync to write logs to CloudWatch. Or, choose 'Existing role' to select the Amazon Resource Name (ARN) of an existing IAM role in your AWS account.\n5. Choose 'Save'.\nIf you choose to use an existing IAM role, the role must grant AWS AppSync the required permissions to write logs to CloudWatch. To configure this manually, you must provide a service role ARN so that AWS AppSync can assume the role when writing the logs.","multiregional":false,"service":"AWS AppSync"},"ecc-aws-571":{"article":"Identifying unused Amazon RDS instances will lower the cost of your AWS bill. An RDS instance is considered to be unused when it is stopped for more than 3 days. \nUnless there is a business need to retain unused RDS instances, you should remove them to maintain an accurate inventory of system components.\nWhile your DB instance is stopped, you are charged for provisioned storage (including Provisioned IOPS). You're also charged for backup storage, including manual snapshots and automated backups within your specified retention window. However, you're not charged for DB instance hours.\nIf you don't manually start your DB instance after it is stopped for seven consecutive days, RDS automatically starts your DB instance for you. This way, it doesn't fall behind any required maintenance updates.","impact":"Keeping unused RDS instances can result in escalating costs and cluttered AWS accounts.","report_fields":["DBInstanceArn"],"remediation":"To start a DB instance:\n 1. Sign in to the AWS Management Console and open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n 2. In the navigation pane, choose 'Databases', and then choose the DB instance that you want to start.\n 3. For 'Actions', choose 'Start'.\n\nTo delete a DB instance:\n 1. Sign in to the AWS Management Console and open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n 2. In the navigation pane, choose 'Databases', and then choose the DB instance that you want to delete.\n 3. For 'Actions', choose 'Delete'.\n 4. To create a final DB snapshot for the DB instance, choose 'Create final snapshot?'.\n 5. If you chose to create a final snapshot, enter the 'Final snapshot name'.\n 6. To retain automated backups, choose 'Retain automated backups'.\n 7. Enter 'delete me' in the box.\n 8. Choose 'Delete'.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-276":{"article":"Amazon Aurora-PostgreSQL database engine logs (Postgresql) should be enabled and sent to CloudWatch.\nRDS cluster logging provides detailed records of requests made to RDS. This logs can assist with security and access audits and can help to diagnose availability issues.","impact":"With disabled logs for the RDS DB cluster, it is harder to analyze statistics, diagnose issues, detect different types of attacks, and retain data for regulatory or legal purposes.","report_fields":["DBClusterArn"],"remediation":"To publish PostgreSQL logs to CloudWatch Logs from the AWS Management Console\n1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. In the navigation pane, choose Databases.\n3. Choose the DB instance that you want to modify.\n4. Choose Modify.\n5. Under Log exports, choose all of the log files to start publishing to CloudWatch Logs.\n6. Log exports is available only for database engine versions that support publishing to CloudWatch Logs.\n7. Choose Continue. Then on the summary page, choose Modify DB Instance.\n\nTo enable and publish Aurora-PostgreSQL logs to CloudWatch Logs from the AWS Management Console, set the following parameters in a custom DB Cluster Parameter Group:\n- log_statement=all\n- log_min_duration_statement='minimum query duration (ms) to log'\n\nTo create a custom DB cluster parameter group\n1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. In the navigation pane, choose Parameter groups.\n3. Choose Create parameter group. The Create parameter group window appears.\n4. In the Parameter group family list, choose a DB parameter group family.\n5. In the Type list, choose DB Cluster Parameter Group.\n6. In Group name, enter the name of the new DB cluster parameter group.\n7. In Description, enter a description for the new DB cluster parameter group.\n8. Choose Create.\n\nTo apply a new DB cluster parameter group to an RDS DB instance:\n1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. In the navigation pane, choose Databases.\n3. Choose the DB cluster that you want to modify.\n4. Choose Modify. The Modify DB cluster page appears.\n5. Under Database options, change the DB cluster parameter group.\n6. When you finish you changes, choose Continue. Check the summary of modifications.\n7. Choose Modify CLuster to save your changes.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-344":{"article":"Route53 conveniently allows you to register and manage domains alongside the rest of your AWS resources. When a domain is registered in Route53, it will automatically have automatic renewal enabled. In the case where automatic renewal is not enabled, you must renew it manually.\nTo avoid the possibility of a domain expiring accidentally, you can enable automatic renewal on the domain, if it's not done yet. Avoid waiting until the last minute to renew your domain. Some TLDs disallow renewals as much as 25 days before the expiration date, and it can take a day or more to process a renewal.","impact":"If you do not renew your domain name, it will become expired. An expired Amazon Route53 domain can cause website or application downtime or failure. An expired domain could be taken over by a malicious individual or deleted by the domain registrar and it will become available for others to register.","report_fields":["DomainName"],"remediation":"If automatic renewal is enabled, here's what happens:\n - 45 days before expiration\n AWS sends an email to the registrant contact that tells you that automatic renewal is currently enabled and gives instructions about how to disable it. Keep your registrant contact email address current so you don't miss this email.\n - 35 or 30 days before expiration\n For all domains except .com.ar, .com.br, and .jp domains, AWS renews domain registration 35 days before the expiration date so AWS has time to resolve any issues with your renewal before the domain name expires.\n The registries for .com.ar, .com.br, and .jp domains require that AWS renews the domains no more than 30 days before expiration. You'll get a renewal email from Gandi, our registrar associate, 30 days before expiration, which is the same day that AWS renews your domain if you have automatic renewal enabled.\n\nIf automatic renewal is disabled, here's what happens as the expiration date for a domain name approaches:\n - 45 days before expiration\n AWS sends an email to the registrant contact for the domain that tells you that automatic renewal is currently disabled and gives instructions about how to enable it. Keep your registrant contact email address current so you don't miss this email.\n - 30 days and 7 days before expiration\n If automatic renewal is disabled for the domain, ICANN, the governing body for domain registration, requires the registrar to send you an email.\n If you enable automatic renewal less than 30 days before expiration, and the renewal period has not passed, AWS renews the domain within 24 hours.\n\nWhen you want to change whether Amazon Route53 automatically renews registration for a domain shortly before the expiration date, or you want to see the current setting for automatic renewal, perform the following procedure.\nTo enable automatic renewal for a domain:\n1. Sign in to the AWS Management Console and open the Route53 console at https://console.aws.amazon.com/route53/.\n2. In the navigation pane, choose 'Registered Domains'.\n3. Choose the name of the domain that you want to update.\n4. Choose 'Enable' for 'Auto renew'.\n5. If you encounter issues while enabling automatic renewal, you can contact AWS Support for free. \n For more information, see 'Contacting AWS Support about domain registration issues': https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/domain-contact-support.html.","multiregional":true,"service":"Amazon Route 53"},"ecc-aws-319":{"article":"Bad packets received from the client can potentially indicate packet-based attacks on the system, such as \"TCP SYN Flood\" or \"Smurf\" attacks, which could result in a denial-of-service condition.","impact":"Bad packets received from the client can potentially indicate packet-based attacks on the system, such as \"TCP SYN Flood\" or \"Smurf\" attacks, which could result in a denial-of-service condition.","report_fields":["DBInstanceArn"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/\n2. In the navigation pane, choose Parameter groups.\n3. Choose the required parameter group\n4. Choose Edit parameters.\n5. Under Parameters, in the search bar, type \"sec_protocol_error_further_action\".\n6. Type '(DROP,3)'\n7. Choose Save changes.\n8. Restart instance","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-081":{"article":"Real-time monitoring of API calls can be achieved by directing CloudTrail Logs to CloudWatch Logs and establishing corresponding metric filters and alarms. It is recommended that a metric filter and alarm be established for failed console authentication attempts. Monitoring failed console logins may decrease lead time to detect an attempt to bruteforce a credential, which may provide an indicator, such as source IP, that can be used in other event correlations.","impact":"Lack of monitoring and logging of Console authentication failures can lead to insufficient response time to detect an attempt of brute-forcing a user credential or no response at all.","report_fields":["account_id","account_name"],"remediation":"Perform the following to setup the metric filter, alarm, SNS topic, subscription and trail using the AWS CLI: \n1. Create a log group.\naws logs create-log-group --log-group-name \n2. Create a log stream.\naws logs create-log-stream --log-group-name --log-stream-name \n3. Create a metric filter based on provided filter pattern which checks for unauthorized API calls and the taken from step 1.\naws logs put-metric-filter --log-group-name --filter-name `` --metric-transformations metricName=``,metricNamespace=,metricValue='1' --filter-pattern '{{($.eventName = ConsoleLogin) && ($.errorMessage = \"Failed authentication\")}}'\n4. Create a topic to which notifications will be published.\naws sns create-topic --name \n5. Subscribe an endpoint to an Amazon SNS topic. If the endpoint type is HTTP/S or email, or if the endpoint and the topic are not in the same Amazon Web Services account, the endpoint owner must run the ConfirmSubscription action to confirm the subscription.\naws sns subscribe --topic-arn --protocol email --notification-endpoint \n6. Amazon SNS will send a subscription confirmation message to the endpoint. Confirm subscription to topic, by visiting the link in an email that you specified as notification endpoint.\n7. Create an alarm that is associated with the CloudWatch Logs Metric Filter created in step 3 and an SNS topic created in step 4. \naws cloudwatch put-metric-alarm --alarm-name `` --metric-name `` --statistic Sum --period 300 --threshold 1 --comparison-operator GreaterThanOrEqualToThreshold --evaluation-periods 1 --namespace --alarm-actions \n8. Create an S3 bucket to deliver log files to:\naws s3api create-bucket --bucket \n9. To deliver log files to an S3 bucket, CloudTrail must have the required permissions. The following policy allows CloudTrail to write log files to the bucket from supported regions. Replace myBucketName, [optionalPrefix]/, myAccountID, region, and trailName with the appropriate values for your configuration. \nCreate a file 'policy.json' with the following policy.\n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"AWSCloudTrailAclCheck20150319\",\n \"Effect\": \"Allow\",\n \"Principal\": {\"Service\": \"cloudtrail.amazonaws.com\"},\n \"Action\": \"s3:GetBucketAcl\",\n \"Resource\": \"arn:aws:s3:::\"\n },\n {\n \"Sid\": \"AWSCloudTrailWrite20150319\",\n \"Effect\": \"Allow\",\n \"Principal\": {\"Service\": \"cloudtrail.amazonaws.com\"},\n \"Action\": \"s3:PutObject\",\n \"Resource\": \"arn:aws:s3:::/[optionalPrefix]/AWSLogs//*\",\n \"Condition\": {\n \"StringEquals\": {\n \"s3:x-amz-acl\": \"bucket-owner-full-control\",\n \"aws:SourceArn\": \"arn:aws:cloudtrail:::trail/\"\n }\n }\n }\n ]\n}}\n10. Apply the Amazon S3 bucket policy to the Amazon S3 bucket created in step 8.\naws s3api put-bucket-policy --bucket --policy file://.json\n11. Create a role for CloudTrail that enables it to send events to the CloudWatch Logs log group. To create the JSON file that will contain the policy document, open a text editor and save the following policy contents in a file called 'assume_role_policy_document.json'. \n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Principal\": {\n \"Service\": \"cloudtrail.amazonaws.com\"\n },\n \"Action\": \"sts:AssumeRole\"\n }\n ]\n}}\n12. Create a role.\naws iam create-role --role-name --assume-role-policy-document file://.json\n13. Create the following role policy document for CloudTrail. This document grants CloudTrail the permissions required to create a CloudWatch Logs log stream in the log group you specify and to deliver CloudTrail events to that log stream. Save the policy document in a file called role-policy-document.json. Replace region, accountID, log_group_name, with the appropriate values for your configuration. \n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n\n \"Sid\": \"AWSCloudTrailCreateLogStream2014110\",\n \"Effect\": \"Allow\",\n \"Action\": [\n \"logs:CreateLogStream\"\n ],\n \"Resource\": [\n \"arn:aws:logs:::log-group::log-stream:_CloudTrail_*\"\n ]\n\n },\n {\n \"Sid\": \"AWSCloudTrailPutLogEvents20141101\",\n \"Effect\": \"Allow\",\n \"Action\": [\n \"logs:PutLogEvents\"\n ],\n \"Resource\": [\n \"arn:aws:logs:::log-group::log-stream:_CloudTrail_*\"\n ]\n }\n ]\n}}\n14. Run the following command to apply the policy to the role.\naws iam put-role-policy --role-name --policy-name cloudtrail-policy --policy-document file://.json\n15. Create a trail that specifies the settings for delivery of log data to an Amazon S3 bucket at is associated with the S3 bucket created in step 8, CloudWatch log group created in step 1 and IAM role created in step 12. \naws cloudtrail create-trail --include-global-service-events --is-multi-region-trail --name --s3-bucket-name --cloud-watch-logs-log-group-arn --cloud-watch-logs-role-arn \n16. Start the recording of Amazon Web Services API calls and log file delivery for a trail. For a trail that is enabled in all regions, this operation must be called from the region in which the trail was created.\naws cloudtrail start-logging --name ","multiregional":false,"service":"AWS Account"},"ecc-aws-380":{"article":"This policy identifies the EIP that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["AllocationId"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon VPC at https://console.aws.amazon.com/vpc.\n2. Under 'Virtual private cloud' click on the 'Elastic IPs'.\n3. Open required eip.\n4. Click on 'Manage tags'.\n5. Add new tag and save.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-486":{"article":"This rule checks if the deployment group for Lambda Compute Platform is not using the default deployment configuration 'CodeDeployDefault.LambdaAllAtOnce'. This deployment configuration shifts all traffic to the updated Lambda functions at once.","impact":"In some cases, it may be not acceptable to shifts all traffic to the updated Lambda functions at once, as this can lead to interruptions caused by changing application versions.","report_fields":["applicationName","deploymentGroupId","deploymentGroupName"],"remediation":"To remediate this issue you can use the predefined configurations available for AWS Lambda deployments: \n 1. Navigate to CodeDeploy Applications https://console.aws.amazon.com/codesuite/codedeploy/applications.\n 2. Select an application to which you need to update deployment group.\n 3. Select 'Deployment groups' tab.\n 4. Select deployment group that you want to modify.\n 5. Click 'Edit' at top right corner.\n 6. Select one of the predefined configurations available\n - CodeDeployDefault.LambdaCanary10Percent5Minutes - Shifts 10 percent of traffic in the first increment. The remaining 90 percent is deployed five minutes later.\n - CodeDeployDefault.LambdaCanary10Percent10Minutes - Shifts 10 percent of traffic in the first increment. The remaining 90 percent is deployed 10 minutes later.\n - CodeDeployDefault.LambdaCanary10Percent15Minutes - Shifts 10 percent of traffic in the first increment. The remaining 90 percent is deployed 15 minutes later.\n - CodeDeployDefault.LambdaCanary10Percent30Minutes - Shifts 10 percent of traffic in the first increment. The remaining 90 percent is deployed 30 minutes later.\n - CodeDeployDefault.LambdaLinear10PercentEvery1Minute - Shifts 10 percent of traffic every minute until all traffic is shifted.\n - CodeDeployDefault.LambdaLinear10PercentEvery2Minutes - Shifts 10 percent of traffic every two minutes until all traffic is shifted.\n - CodeDeployDefault.LambdaLinear10PercentEvery3Minutes - Shifts 10 percent of traffic every three minutes until all traffic is shifted.\n - CodeDeployDefault.LambdaLinear10PercentEvery10Minutes - Shifts 10 percent of traffic every 10 minutes until all traffic is shifted.\n 7. Click 'Save changes'.\n\nAnother way to remediate this issue is to create a custom configurations:\n 1. Navigate to the CodeDeploy Deployment configurations https://console.aws.amazon.com/codesuite/codedeploy/deployment-configs\n 2. Click at the top right corner on the 'Create deployment configuration' button.\n 3. Enter name for the deployment configuration.\n 4. For 'Compute platform' select 'AWS Lambda'.\n 5. For 'Type' select the one that you need and configure the rest according to the needs of the project.\n 6. After you finished, click 'Create deployment configuration'.\n 7. Navigate to CodeDeploy Applications https://console.aws.amazon.com/codesuite/codedeploy/applications.\n 8. Select an application to which you need to update deployment group.\n 9. Select 'Deployment groups' tab.\n 10. Select deployment group that you want to modify.\n 11. Click 'Edit' at top right corner.\n 12. Select the deployment configurations that was created at the step 6.\n 13. Click 'Save changes'.","multiregional":false,"service":"AWS CodeDeploy"},"ecc-aws-520":{"article":"The Time To Live (TTL) field in the IP packet is reduced by one on every hop. This reduction can be used to ensure that the packet does not travel outside EC2. IMDSv2 protects EC2 instances that may have been misconfigured as open routers, layer 3 firewalls, VPNs, tunnels, or NAT devices, which prevents unauthorized users from retrieving metadata. With IMDSv2, the PUT response that contains the secret token cannot travel outside the instance because the default metadata response hop limit is set to 1.","impact":"If hop limit value is greater than 1, the token can leave the EC2 instance which can lead to unauthorized access to metadata.","report_fields":["LaunchConfigurationName"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nTo limit launch configuration hops, create new launch configuration:\n1. Open the Amazon EC2 Auto Scaling console at https://console.aws.amazon.com/ec2/.\n2. Under Auto Scaling, click on the Launch Configurations.\n3. Click on the Create launch configuration.\n4. Additional configuration, click on the Advanced details.\n5. Under \"Metadata response hop limit\" type \"1\".\n6. Create launch configuration.","multiregional":false,"service":"Amazon EC2 Auto Scaling"},"ecc-aws-271":{"article":"Redis auth enables Redis to require a password before allowing clients to run commands, thereby improving data security. \nBy default redis does not require a password to run commands.","impact":"Without Redis auth, clients can run commands without a password, which makes the cluster more vulnerable.","report_fields":["ARN"],"remediation":"If encryption in transit is already enabled on your cluster follow next steps:\n1. Sign in to the AWS Management Console and open the ElastiCache console at https://console.aws.amazon.com/elasticache/.\n2. From the list in the upper-right corner, choose the AWS Region where the cluster that you want to modify is located.\n3. In the navigation pane, choose 'Redis clusters'.\n4. In the list of clusters, for the cluster that you want to modify, choose its name.\n5. For 'Actions', choose 'Modify'. The Modify Cluster window appears.\n6. If 'Transit encryption mode' is 'Preferred' change it to 'Required' and apply changes before enabling Auth. Then repeat steps 1-5 and move on to the next step.\n If it is already set to 'Required' skip this step.\n7. For 'Access Control' option, choose 'Redis AUTH Default User' and set a new token.\n8. Click 'Preview changes'.\n9. If you want to perform the update right away, choose 'Apply immediately'. If Apply immediately is not chosen, the update process is performed during the cluster's next maintenance window.\n10. Choose 'Modify'.\n\nIf encryption in transit is disabled on your cluster follow next steps:\nStep 1: Set your Transit encryption mode to Preferred\n 1. Sign in to the AWS Management Console and open the Amazon ElastiCache console at https://console.aws.amazon.com/elasticache/.\n 2. Choose 'Redis clusters' from the ElastiCache 'Resources' listed on the navigation pane, present on the left hand.\n 3. Choose the Redis cluster you want to update.\n 4. Choose the 'Actions' dropdown, then choose 'Modify'.\n 5. Choose 'Enable' under 'Encryption in transit' in the 'Security' section.\n 6. Choose 'Preferred' as the 'Transit encryption mode'.\n 7. Choose 'Preview changes' and save your changes.\n\nAfter you migrate all your Redis clients to use encrypted connections:\nStep 2: Set your Transit encryption mode to Required\n 1. Sign in to the AWS Management Console and open the Amazon ElastiCache console at https://console.aws.amazon.com/elasticache/.\n 2. Choose 'Redis clusters' from the ElastiCache 'Resources' listed on the navigation pane, present on the left hand.\n 3. Choose the Redis cluster you want to update.\n 4. Choose the 'Actions' dropdown, then choose 'Modify'.\n 5. Choose 'Required' as the 'Transit encryption mode', in the 'Security' section.\n 6. For 'Access Control' option, choose 'Redis AUTH Default User' and set a new token.\n 7. Choose 'Preview changes' and save your changes.\n\nStep 3: Enable Auth for cluster\n 1. Sign in to the AWS Management Console and open the Amazon ElastiCache console at https://console.aws.amazon.com/elasticache/.\n 2. Choose 'Redis clusters' from the ElastiCache 'Resources' listed on the navigation pane, present on the left hand.\n 3. Choose the Redis cluster you want to update.\n 4. Choose the 'Actions' dropdown, then choose 'Modify'.\n 5. In the 'Security' section for 'Access Control' option, choose 'Redis AUTH Default User' and set a new token.\n 6. Choose 'Preview changes' and save your changes.","multiregional":false,"service":"Amazon ElastiCache"},"ecc-aws-309":{"article":"Ensure that AWS Config does not have status \"FAILURE\". \nThe most common case is when AWS Config recorder fails to deliver logs to S3 bucket, the service is unable to send the recorded information to the designated bucket, therefore you lose the ability to audit the configuration changes made within your AWS account. This usually happens due to misconfigurations of access policies defined for the associated IAM role.\nAWS Config delivers configuration snapshots and configuration history files to S3 bucket that is configured in a delivery channel.","impact":"When AWS Config recorder fails, it means there are some misconfigurations that impact the AWS Config ability to work properly. \nWhen AWS Config recorder fails to deliver logs to S3 bucket, the service is unable to send the recorded information to the designated bucket, therefore you lose the ability to audit the configuration changes made within your AWS account.","report_fields":["name","roleARN"],"remediation":"Follow this AWS Guide on how to troubleshoot AWS Config console error messages: https://aws.amazon.com/premiumsupport/knowledge-center/config-console-error/.","multiregional":false,"service":"AWS Config"},"ecc-aws-084":{"article":"S3 Bucket Access Logging generates a log that contains access records for each request made to your S3 bucket. An access log record contains details about the request, such as the request type, the resources specified in the request worked, and the time and date the request was processed. \nIt is recommended that bucket access logging be enabled on the CloudTrail S3 bucket. By enabling S3 bucket logging on target S3 buckets, it is possible to capture all events which may affect objects within target buckets. Configuring logs to be placed in a separate bucket allows access to log information which can be useful in security and incident response workflows.","impact":"Lack of monitoring and logging of requests to an S3 bucket can lead to undetected changes made to the S3 bucket.","report_fields":["TrailARN"],"remediation":"1. Sign in to the AWS Management Console and open the S3 console at https://console.aws.amazon.com/s3.\n2. Under All Buckets, click on the target S3 bucket.\n3. Click on Properties in the top right of the console.\n4. Under Bucket , click on Logging.\n5. Configure bucket logging: \n 5.1. Click on Enabled checkbox.\n 5.2. Select Target Bucket from the list.\n 5.3. Enter a Target Prefix \n6. Click Save.","multiregional":false,"service":"AWS CloudTrail"},"ecc-aws-023":{"article":"Logging is crucial for detecting intrusion attempts, monitoring access, as well as for helping debug access errors and system failures during and after the fact. Almost every compliance framework will mandate logging to be enabled.","impact":"Disabled access logs for CLB make it harder to analyze statistics, diagnose issues or detect different types of attacks, as well as retain data for regulatory or legal purposes.","report_fields":["LoadBalancerArn"],"remediation":"To enable access logging using the console, do the following:\n1. Open the Amazon EC2 console at https://console.aws.amazon.com/ec2/. \n2. In the navigation pane, choose 'Load Balancers'. \n3. Select your load balancer. \n4. On the 'Description' tab, choose 'Edit attributes'. \n5. On the 'Edit load balancer attributes' page, do the following: \n - For 'Access logs', select 'Enable'. \n - For 'S3 location', type the name of your S3 bucket, including any prefix (for example, my-loadbalancer-logs/my-app). You can specify the name of an existing bucket or a name for a new bucket. If you specify an existing bucket, be sure that you own this bucket and that you configured the required bucket policy. \n - (Optional) If the bucket does not exist, choose 'Create this location for me'. You must specify a name that is unique across all existing bucket names in Amazon S3 and follows the DNS naming conventions. \n - Choose 'Save'.","multiregional":false,"service":"Amazon Elastic Load Balancing"},"ecc-aws-259":{"article":"Ensure that your Amazon Elastic MapReduce (EMR) clusters are provisioned using the AWS EC2-VPC platform instead of EC2-Classic platform (outdated from 2013.12.04) for better flexibility and control over security, better traffic routing and availability.\nWhen you launch an Amazon EMR cluster within a VPC, you can launch it within either a public, private, or shared subnet. There are slight but notable differences in configuration, depending on the subnet type you choose for a cluster.","impact":"Launching and managing AWS EMR clusters using EC2-Classic (no VPC) platform instead of EC2-VPC can bring multiple disadvantages such as worse networking infrastructure (you will not be able to take advantage of network isolation, private subnets and private IP addresses), much less flexible control over access security and no access to newer and powerful EC2 instance types (C4, M4, R4, etc) for your clusters.","report_fields":["ClusterArn"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\n1. Login to the AWS Management Console.\n2. Navigate to EMR dashboard at https://console.aws.amazon.com/elasticmapreduce/.\n3. In the navigation panel, under Amazon EMR, click Clusters to access your AWS EMR clusters page.\n4. Select the EMR cluster that you want to relaunch into a VPC then click on the Clone button from the dashboard top menu.\n5. Inside the Cloning dialog box, choose Yes to include the steps from the original cluster in the cloned cluster or No to clone the original cluster's configuration without including any of the existing steps. Click Clone to start the cloning process.\n6. On the Create Cluster page, select Step 1: Software and Steps from the left navigation panel and configure the software that will be installed on the new cluster. Click Next to continue the setup process.\n7. On the Hardware Configuration panel, select the VPC network and the EC2 subnet where the new EMR cluster instances will be provisioned, set the EBS volume size for the root device and configure the cluster nodes (instances) as needed. Click the Next button until your reach Step 4: Security page, without changing any other configuration attributes.\n8. Review the security options, then click Create Cluster to provision your new Amazon EMR cluster.\n9. Once you have moved the existing data and verified that your new EMR cluster is working 100% within the selected VPC network, terminate the original cluster in order to stop incurring charges for it.","multiregional":false,"service":"Amazon EMR"},"ecc-aws-392":{"article":"This policy identifies the VPC endpoint that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["VpcEndpointId"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon VPC at https://console.aws.amazon.com/vpc.\n2. Click on the 'Endpoints'.\n3. Click on the required endpoint.\n4. Open 'Tags' and click on the 'Manage tags'.\n5. Add new tag and save.","multiregional":false,"service":"Amazon Virtual Private Cloud"},"ecc-aws-475":{"article":"Enabling Cross-Zone Load Balancing makes it easier to deploy and manage applications that run across multiple subnets in different Availability Zones. This would also guarantee better fault tolerance and more consistent traffic flow. If one of the availability zones registered with the ELB fails (as result of network outage or power loss), the load balancer with the Cross-Zone Load Balancing activated would act as a traffic guard, stopping any request being sent to the unhealthy zone and routing it to the other zone(s).","impact":"When cross-zone load balancing is disabled, each load balancer node distributes traffic only across the registered targets in its Availability Zone. If the number of registered targets is not same across the Availability Zones, traffic wont be distributed evenly and the instances in one zone may end up over utilized compared to the instances in another zone.","report_fields":["LoadBalancerArn"],"remediation":"1. Open the Amazon EC2 console at https://console.aws.amazon.com/ec2/.\n2. In the navigation pane, choose Load balancers. \n3. Choose an Classic Load Balancer. \n4. On the Description tab, choose Change cross-zone load balancing setting. \n5. On the Configure Cross-Zone Load Balancing page, select Enable.\n6. Choose Save.","multiregional":false,"service":"Amazon Elastic Load Balancing"},"ecc-aws-278":{"article":"Access Analyzer generates a finding for each instance of a resource-based policy that grants access to a resource within your zone of trust to a principal that is not within your zone of trust. The status of all findings remains Active until you archive them or remove the access that generated the finding. When you remove the access, the finding status is updated to Resolved. You should review all of the findings in your account to determine whether the sharing is expected and approved.","impact":"Do not resolved findings pose a security risk that leads to open unintended access to your resources and data.","report_fields":["account_id","account_name"],"remediation":"Review any findings to determine whether the access identified in the finding is intentional or unintentional.\n\nTo review findings:\n1. Open the IAM console at https://console.aws.amazon.com/iam/.\n2. Choose Access analyzer. \n All Active findings that were generated are displayed for the analyzer.\n\nWhen you get a finding for access to a resource that is intentional, you can archive the finding.\nTo archive findings from the Findings page:\n1. Select the check box next to one or more findings to archive.\n2. Choose Archive. A confirmation is displayed at the top of the screen.\n\nTo resolve findings generated from access that you did not intend to allow, modify the policy statement to remove the permissions that allow access to the identified resource.\nAfter you make a change to resolve a finding, if the resource is no longer shared outside of your zone of trust, the status of the finding is changed to Resolved. The finding is no longer displayed in the Active findings table, and instead is displayed in the Resolved findings table.\nIf the changes you made resulted in the resource being shared outside of your zone of trust, but in a different way, such as with a different principal or for a different permission, Access Analyzer generates a new Active finding.","multiregional":false,"service":"AWS Identity and Access Management Access Analyzer"},"ecc-aws-360":{"article":"Ensure that CloudWatch logs encryption is enabled for your Amazon ECS Exec in order to meet regulatory requirements and prevent unauthorized users from getting access to the logging data published to CloudWatch or S3. Amazon ECS provides a default configuration for logging commands run using ECS Exec by sending logs to CloudWatch Logs using the awslogs log driver that's configured in your task definition.","impact":"Disabled encryption of logs allows a user to get unauthorized access to sensitive data.","report_fields":["clusterArn"],"remediation":"To enable encryption for logging data published to CloudWatch, perform the following actions:\nExecute command :\naws ecs update-cluster \\\n --cluster $CLUSTER_NAME \\\n --region $AWS_REGION \\\n --configuration executeCommandConfiguration=\"{{logging=OVERRIDE,\\\n kmsKeyId=$KMS_KEY_ARN,\\\n logConfiguration={{cloudWatchLogGroupName=\"$CLOUDWATCH_GROUP_NAME\",\\\n cloudWatchEncryptionEnabled=true}}\\\n }}\"\n\nTo enable encryption for logging data published to s3, perform the following actions:\nExecute command:\naws ecs update-cluster \\\n --cluster $CLUSTER_NAME \\\n --region $AWS_REGION \\\n --configuration executeCommandConfiguration=\"{{logging=OVERRIDE,\\\n logConfiguration={{s3EncryptionEnabled=true,\\\n s3BucketName=$ECS_EXEC_BUCKET_NAME,\\\n s3KeyPrefix=$PREFIX}} \\\n }}\"\nIn addition, the task role will need to have IAM permissions to log the output to S3 and/or CloudWatch and decrypt the data with KMS key.\nTo update your task role follow the instructions that are described in the 'IAM permissions required for Amazon CloudWatch Logs or Amazon S3 Logging' and 'IAM permissions required for encryption using your own AWS KMS key (KMS key)' sections in Amazon ECS User Guide: https://docs.aws.amazon.com/AmazonECS/latest/userguide/ecs-exec.html.","multiregional":false,"service":"Amazon Elastic Container Service"},"ecc-aws-294":{"article":"Monitoring is an essential part of maintaining the availability, reliability, and performance of your Amazon Elastic Beanstalk applications. When a notable event occurs within your application environment, Elastic Beanstalk sends a message to the configured email address via Amazon SNS to keep you up-to-date on everything that's going on within your environment. Notable events include environment creation errors, environment changes, and instance health events.","impact":"Not enabling Beanstalk notifications can lead to a slow response to a environment changes, and instance health events.","report_fields":["EnvironmentArn"],"remediation":"To enable notifications perform following steps:\n1. Login to the AWS Management Console and open the Amazon Elastic Beanstalk console using https://console.aws.amazon.com/elasticbeanstalk/\n2. Click on the required environment.\n3. Click on Configuration. \n4. Click Edit on 'Notifications' parameter.\n5. Provide email.\n6. Save changes.\n7. Open email and confirm subscription.","multiregional":false,"service":"AWS Elastic Beanstalk"},"ecc-aws-022":{"article":"Regularly managing and removing outdated snapshots is crucial to maintain efficiency and cost-effectiveness in your backup strategy.","impact":"Keeping snapshots more than 30 days can result in escalating storage costs, cluttered AWS accounts, and prolonged data restoration times.","report_fields":["SnapshotId","OwnerId"],"remediation":"1. Navigate to console.aws.amazon.com/ec2/. \n2. Select Snapshots under Elastic Block Store. \n3. Find snapshots that were created more than 14 days ago.\n4. Update snapshots:\n4.1. Press Create snapshot.\n4.2. Select Volume in the Resource type.\n4.3. Choose the volume with the expired period under the Volume tab.\n4.4. Press Create snapshot.","multiregional":false,"service":"Amazon Elastic Block Store"},"ecc-aws-150":{"article":"Encrypting data at rest reduces the risk of data stored on disk being accessed by a user not authenticated to AWS. It adds another set of access controls to limit unauthorized users ability to access the data. For example, API permissions are required to decrypt the data before it can be read. API Gateway REST API caches should be encrypted at rest to ensure an added layer of security.","impact":"Disabled encryption allows users not authenticated in AWS or unauthorized users to gain access to API Gateway containing sensitive data.","report_fields":["stageName","restApiId"],"remediation":"From Console:\n1. Login to AWS Management Console and open the API Gateway console using https://console.aws.amazon.com/apigateway/ \n2. Choose the API. \n3. Choose Stages. \n4. In the Stages list for the API, choose the stage to add caching to. \n5. Choose Settings. \n6. Choose Enable API cache. \n7. Update the desired settings, then select Encrypt cache data. \n8. Choose Save Changes.","multiregional":false,"service":"Amazon API Gateway"},"ecc-aws-121":{"article":"Security groups provide stateful filtering of ingress/egress network traffic to AWS resources. It is recommended that no security group allow unrestricted egress access.","impact":"Allowing unrestricted outbound access can increase opportunities for malicious activity such as unauthorized access.","report_fields":["GroupId","VpcId","OwnerId"],"remediation":"Reduce the scope of the outbound rules to just the necessary scope, protocols, and ports. \n1. Login to the AWS Console. \n2. Go to Security group.\n3. Go to the SG rules. \n4. Click on the reported Firewall rule. \n5. Click on Edit. \n6. Modify the rule.\n7. Click on Save.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-522":{"article":"Using environmental variables to store credentials in container task definition may violate the requirement to use strong cryptography to render authentication credentials unreadable. AWS Systems Manager Parameter Store can help you improve the security posture of your organization","impact":"Using environmental variables to store credentials in container task definition may violate the requirement to use strong cryptography to render authentication credentials unreadable. This could lead to unintended data exposure and unauthorized access.","report_fields":["taskDefinitionArn"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nTo pass secrets securely to containers use Parameter Store or Secrets Manager:\nUsing Parameter Store: \n1. aws ssm put-parameter --type SecureString --name awsExampleParameter --value awsExampleValue\nUsing Secrets Manager:\n1.1. aws secretsmanager create-secret --name awsExampleParameter --secret-string awsExampleValue\n2. Open the IAM console, and then create a role with a trust relation for ecs-tasks.amazonaws.com. For example:\n {\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Principal\": {\n \"Service\": \"ecs-tasks.amazonaws.com\"\n },\n \"Action\": \"sts:AssumeRole\"\n }\n ]\n }\n3. To create an inline policy for your role in the IAM console, choose Roles, select the role that you created in step 2, and then choose Add inline policy on the Permissions tab. Choose the JSON tab, and then create a policy with the following code. Replace us-east-1 and awsExampleAccountID with the AWS Region and account where your parameters are stored. Replace awsExampleParameter with the name of the parameters that you created in step 1:\n {\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Effect\": \"Allow\",\n \"Action\": [\n \"ssm:GetParameters\",\n \"secretsmanager:GetSecretValue\"\n ],\n \"Resource\": [\n \"arn:aws:ssm:us-east-1:awsExampleAccountID:parameter/awsExampleParameter\",\n \"arn:aws:secretsmanager:us-east-1:awsExampleAccountID:secret:awsExampleParameter*\"\n ]\n }\n ]\n }\n4. (Optional) Attach the managed policy AmazonECSTaskExecutionRolePolicy to the role that you created in step 2.\n\nNote that when you update a task definition, it does not update running tasks that were launched from the previous task definition. To update a running task, you must redeploy the task with the new task definition.\n1. Open the Amazon ECS console at https://console.aws.amazon.com/ecs/.\n2. In the left navigation pane, choose Task Definitions.\n3. Select the task definition that needs to be updated.\n4. For Task execution role, choose the task execution IAM role that you created earlier.\n5. In the Container Definitions section, choose Add container.\n6. In the Environment variables section under ENVIRONMENT, for Key, enter a key for your environment variable.\n7. On the Value dropdown list, choose ValueFrom.\n8. In the text box for the key, enter the Amazon Resource Name (ARN) of your Parameter Store or Secrets Manager resource.","multiregional":false,"service":"Amazon Elastic Container Service"},"ecc-aws-284":{"article":"To maintain the availability of the compute resources in the event of a failure and provide an evenly distributed application load, ensure that your Amazon Auto Scaling Groups (ASGs) have associated Elastic Load Balancers or Target Groups.\nElastic Load Balancing automatically distributes your incoming application traffic across all the EC2 instances that you are running. Elastic Load Balancing helps to manage inbound requests by optimally routing traffic so that no instance is overloaded. When you attach an Application Load Balancer, Network Load Balancer, or Gateway Load Balancer, you attach a Target Group.","impact":"Auto Scaling Group not attached to Elastic Load Balancer or Target Group may cause problems with availability and performance of instances.","report_fields":["AutoScalingGroupARN"],"remediation":"Use the following procedure to attach a load balancer to an existing Auto Scaling group.\n1. Open the Amazon EC2 Auto Scaling console at https://console.aws.amazon.com/ec2autoscaling/.\n2. Select the check box next to an existing group. A split pane opens up in the bottom part of the Auto Scaling groups page, showing information about the group that is selected.\n3. On the Details tab, choose Load balancing, Edit.\n4. Under Load balancing, do one of the following:\n a. For Application, Network or Gateway Load Balancer target groups, select its check box and choose a target group.\n b. For Classic Load Balancers, select its check box and choose a load balancer.\n5. Choose Update.","multiregional":false,"service":"Amazon EC2 Auto Scaling"},"ecc-aws-303":{"article":"Management events provide visibility into management operations that are performed on resources in your AWS account. These are also known as control plane operations. Example management events include: Configuring security (for example, IAM AttachRolePolicy API operations), Registering devices (for example, Amazon EC2 CreateDefaultVpc API operations), Configuring rules for routing data (for example, Amazon EC2 CreateSubnet API operations), Setting up logging (for example, AWS CloudTrail CreateTrail API operations).\nManagement events can also include non-API events that occur in your account. For example, when a user logs in to your account, CloudTrail logs the ConsoleLogin event.","impact":"If security critical information is not recorded, there will be no trail for forensic analysis, and discovering the cause of problems or the source of attacks may become more difficult or impossible.","report_fields":["TrailARN"],"remediation":"To enable Management events for all CloudTrail trails available within your AWS account, perform the following: \n 1. Open the Amazon CloudTrail console at https://console.aws.amazon.com/cloudtrail/. \n 2. In the navigation pane, choose Trails.\n 3. Choose the trail that you want to reconfigure.\n 4. Click the Edit button, next to the Management events section.\n 5. Choose if you want your trail to log Read events, Write events, or both.\n 6. Click Save to apply the changes and save the trail configuration.","multiregional":false,"service":"AWS CloudTrail"},"ecc-aws-426":{"article":"This policy identifies the QLDB that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["EnvironmentArn"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon QLDB at https://console.aws.amazon.com/qldb.\n2. Click on the required QLDB ledger.\n3. Open 'Tags' and click on the 'Manage tags'.\n4. Add new tag and save.","multiregional":false,"service":"Amazon QLDB"},"ecc-aws-324":{"article":"The Oracle 'resource_limit' parameter determines whether resource limits are enforced in database profiles.","impact":"If 'resource_limit' is set to FALSE, there will be no resource limits in database profiles.","report_fields":["DBInstanceArn"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/\n2. In the navigation pane, choose Parameter groups.\n3. Choose the required parameter group\n4. Choose Edit parameters.\n5. Under Parameters, in the search bar, type 'resource_limit'.\n6. Choose TRUE\n7. Choose Save changes.\n8. Restart instance","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-524":{"article":"A WAF Regional web ACL can contain a collection of rules and rule groups that inspect and control web requests.\nA web access control list (web ACL) gives you fine-grained control over all of the HTTP(S) web requests that your protected resource responds to. You can protect Amazon CloudFront, Amazon API Gateway, Application Load Balancer, AWS AppSync, and Amazon Cognito resources.","impact":"If a web ACL is empty, the web traffic can pass without being detected or acted upon by WAF depending on the default action.","report_fields":["Name","WebACLId"],"remediation":"To add rules or rule groups to an empty web ACL:\n1. Sign in to the AWS Management Console and open the AWS WAF console at https://console.aws.amazon.com/wafv2/.\n If you see 'Switch to AWS WAF Classic' in the navigation pane, select it. \n For 'Filter', choose the 'Region' where the empty web ACL is located.\n2. In the navigation pane, choose 'Web ACLs'.\n3. Choose the name of the web ACL that you want to edit. This opens a page with the web ACL's details in the right pane.\t\n4. On the 'Rules' tab in the right pane, choose 'Edit web ACL'.\n5. To add rules to the web ACL, perform the following steps:\n a. In the 'Rules' list, choose the rule that you want to add.\n b. Choose 'Add rule to web ACL'. \n c. Repeat steps a and b until you've added all the rules that you want.\n6. If you want to change the order of the rules in the web ACL, use the arrows in the 'Order' column. AWS WAF Classic inspects web requests based on the order in which rules appear in the web ACL. \n7. Choose 'Update'.","multiregional":false,"service":"AWS Web Application Firewall"},"ecc-aws-098":{"article":"Real-time monitoring of API calls can be achieved by directing CloudTrail Logs to CloudWatch Logs and establishing corresponding metric filters and alarms. Network gateways are required to send/receive traffic to a destination outside of a VPC. It is recommended that a metric filter and alarm be established for detecting changes made to network gateways.","impact":"Lack of monitoring and logging of Internet Gateway changes can result in insufficient response time to accidental or intentional modifications. This may lead to unrestricted network access, loss of connection between your AWS VPC and Internet, or loss of VPN connection between your VPC and on-premises datacenter(s) linked to it.","report_fields":["account_id","account_name"],"remediation":"Perform the following to setup the metric filter, alarm, SNS topic, subscription and trail using the AWS CLI: \n1. Create a log group.\naws logs create-log-group --log-group-name \n2. Create a log stream.\naws logs create-log-stream --log-group-name --log-stream-name \n3. Create a metric filter based on provided filter pattern which checks for unauthorized API calls and the taken from step 1.\naws logs put-metric-filter --log-group-name --filter-name `` --metric-transformations metricName=``,metricNamespace=,metricValue='1' --filter-pattern '{{ ($.eventName = CreateCustomerGateway) || ($.eventName = DeleteCustomerGateway) || ($.eventName = AttachInternetGateway) || ($.eventName = CreateInternetGateway) || ($.eventName = DeleteInternetGateway) || ($.eventName = DetachInternetGateway) }}'\n4. Create a topic to which notifications will be published.\naws sns create-topic --name \n5. Subscribe an endpoint to an Amazon SNS topic. If the endpoint type is HTTP/S or email, or if the endpoint and the topic are not in the same Amazon Web Services account, the endpoint owner must run the ConfirmSubscription action to confirm the subscription.\naws sns subscribe --topic-arn --protocol email --notification-endpoint \n6. Amazon SNS will send a subscription confirmation message to the endpoint. Confirm subscription to topic, by visiting the link in an email that you specified as notification endpoint.\n7. Create an alarm that is associated with the CloudWatch Logs Metric Filter created in step 3 and an SNS topic created in step 4. \naws cloudwatch put-metric-alarm --alarm-name `` --metric-name `` --statistic Sum --period 300 --threshold 1 --comparison-operator GreaterThanOrEqualToThreshold --evaluation-periods 1 --namespace --alarm-actions \n8. Create an S3 bucket to deliver log files to:\naws s3api create-bucket --bucket \n9. To deliver log files to an S3 bucket, CloudTrail must have the required permissions. The following policy allows CloudTrail to write log files to the bucket from supported regions. Replace myBucketName, [optionalPrefix]/, myAccountID, region, and trailName with the appropriate values for your configuration. \nCreate a file 'policy.json' with the following policy.\n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"AWSCloudTrailAclCheck20150319\",\n \"Effect\": \"Allow\",\n \"Principal\": {\"Service\": \"cloudtrail.amazonaws.com\"},\n \"Action\": \"s3:GetBucketAcl\",\n \"Resource\": \"arn:aws:s3:::\"\n },\n {\n \"Sid\": \"AWSCloudTrailWrite20150319\",\n \"Effect\": \"Allow\",\n \"Principal\": {\"Service\": \"cloudtrail.amazonaws.com\"},\n \"Action\": \"s3:PutObject\",\n \"Resource\": \"arn:aws:s3:::/[optionalPrefix]/AWSLogs//*\",\n \"Condition\": {\n \"StringEquals\": {\n \"s3:x-amz-acl\": \"bucket-owner-full-control\",\n \"aws:SourceArn\": \"arn:aws:cloudtrail:::trail/\"\n }\n }\n }\n ]\n}}\n10. Apply the Amazon S3 bucket policy to the Amazon S3 bucket created in step 8.\naws s3api put-bucket-policy --bucket --policy file://.json\n11. Create a role for CloudTrail that enables it to send events to the CloudWatch Logs log group. To create the JSON file that will contain the policy document, open a text editor and save the following policy contents in a file called 'assume_role_policy_document.json'. \n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Principal\": {\n \"Service\": \"cloudtrail.amazonaws.com\"\n },\n \"Action\": \"sts:AssumeRole\"\n }\n ]\n}}\n12. Create a role.\naws iam create-role --role-name --assume-role-policy-document file://.json\n13. Create the following role policy document for CloudTrail. This document grants CloudTrail the permissions required to create a CloudWatch Logs log stream in the log group you specify and to deliver CloudTrail events to that log stream. Save the policy document in a file called role-policy-document.json. Replace region, accountID, log_group_name, with the appropriate values for your configuration. \n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n\n \"Sid\": \"AWSCloudTrailCreateLogStream2014110\",\n \"Effect\": \"Allow\",\n \"Action\": [\n \"logs:CreateLogStream\"\n ],\n \"Resource\": [\n \"arn:aws:logs:::log-group::log-stream:_CloudTrail_*\"\n ]\n\n },\n {\n \"Sid\": \"AWSCloudTrailPutLogEvents20141101\",\n \"Effect\": \"Allow\",\n \"Action\": [\n \"logs:PutLogEvents\"\n ],\n \"Resource\": [\n \"arn:aws:logs:::log-group::log-stream:_CloudTrail_*\"\n ]\n }\n ]\n}}\n14. Run the following command to apply the policy to the role.\naws iam put-role-policy --role-name --policy-name cloudtrail-policy --policy-document file://.json\n15. Create a trail that specifies the settings for delivery of log data to an Amazon S3 bucket at is associated with the S3 bucket created in step 8, CloudWatch log group created in step 1 and IAM role created in step 12. \naws cloudtrail create-trail --include-global-service-events --is-multi-region-trail --name --s3-bucket-name --cloud-watch-logs-log-group-arn --cloud-watch-logs-role-arn \n16. Start the recording of Amazon Web Services API calls and log file delivery for a trail. For a trail that is enabled in all regions, this operation must be called from the region in which the trail was created.\naws cloudtrail start-logging --name ","multiregional":false,"service":"AWS Account"},"ecc-aws-033":{"article":"This policy identifies security group rules that allow inbound traffic to the MySQL DB port (3306) from public internet. Allowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Unrestricted MySQL access can increase opportunities for malicious activity such as unauthorized access, denial-of-service (DoS) attacks and loss of data.","report_fields":["GroupId","VpcId","OwnerId"],"remediation":"1. Login to the AWS Console.\n2. Go to Security Group.\n3. Go to the SG rules.\n4. Click on the reported Firewall rule.\n5. Click on Edit.\n6. Modify the rule.\n7. Click on Save.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-197":{"article":"HTTPS (TLS) can be used to help prevent potential attackers from eavesdropping on or manipulating network traffic using person-in-the-middle or similar attacks. Only encrypted connections over HTTPS (TLS) should be allowed. \nEnabling node-to-node encryption for Elasticsearch domains ensures that intra-cluster communications are encrypted in transit. There can be a performance penalty associated with this configuration. You should be aware of and test the performance trade-off before enabling this option.","impact":"Without Node-to-node encryption, potential attackers can manipulate or intercept network traffic between ElasticSearch cluster nodes using man-in-the-middle attacks (MITM) or similar attacks.","report_fields":["ARN"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nFor a new Opensearch domain. When creating new domain make sure Node-to-node encryption under Encryption field is checked. \nFor a existing domain, first create a new domain with the Node-to-node encryption check box selected. Then follow this link to migrate your data to the new domain https://docs.aws.amazon.com/opensearch-service/latest/developerguide/version-migration.html#snapshot-based-migration.","multiregional":false,"service":"Amazon OpenSearch Service"},"ecc-aws-069":{"article":"Misconfigured S3 buckets can leak private information to the entire internet or allow unauthorized data tampering / deletion.","impact":"Granted anonymous access to S3 buckets can allow malicious users to view, upload, modify and delete S3 objects, actions that can lead to severe security issues such as data loss and unexpected charges on your AWS bill.","report_fields":["Name"],"remediation":"1. Open the Amazon S3 console at https://console.aws.amazon.com/s3/. \n2. Choose the name of the bucket, the Bucket policy of which contains [Effect='Allow' and Principal='*' and Action = '*']. \n3. Change the value of the parameters (Principal or Action ) to the specifically required ones. \n4. Enter Save button.","multiregional":true,"service":"Amazon S3"},"ecc-aws-068":{"article":"CloudTrail logs a record of every API call made in your AWS account. These log files are stored in an S3 bucket. It is recommended that the bucket policy or access control list (ACL) apply to the S3 bucket that CloudTrail logs to prevent public access to the CloudTrail logs.","impact":"Public access to CloudTrail log content can help an attacker in identifying weaknesses in the use or configuration of the affected account.","report_fields":["TrailARN"],"remediation":"Perform the following to remove any public access that has been granted to the bucket via an ACL or S3 bucket policy: \n1. Go to Amazon S3 console at https://console.aws.amazon.com/s3/home.\n2. Right-click on the bucket and then click on Properties.\n3. In the Properties pane, click on the Permissions tab. \n4. The tab shows a list of grants, one row per grant, in the bucket ACL. Each row identifies the grantee and the permissions granted. \n5. Select the row that grants permission to Everyone or Any Authenticated User.\n6. Uncheck all the permissions granted to Everyone or Any Authenticated User (click on x to delete the row). \n7. Click on Save to save the ACL. \n8. If the Edit bucket policy button is present, click on it. \n9. Remove any Statement having an Effect set to Allow and a Principal set to \"*\" or {\"AWS\": \"*\"}.","multiregional":false,"service":"AWS CloudTrail"},"ecc-aws-155":{"article":"An Elasticsearch domain requires at least three dedicated master nodes for high availability and fault-tolerance. Dedicated master node resources can be strained during data node blue/green deployments as there are additional nodes to manage. Deploying an Elasticsearch domain with at least three dedicated master nodes ensures sufficient master node resource capacity and cluster operations if a node fails. Using more than three master nodes might be unnecessary to mitigate the availability risk, and will result in additional cost.","impact":"Using only one master node can lead to low availability and fault tolerance. Deploying an Elasticsearch domain with at least three dedicated master nodes ensures sufficient master node resource capacity and cluster operations if a node fails.","report_fields":["ARN"],"remediation":"From Console:\n1. Login to the AWS Management Console and open the Amazon Elasticsearch Service console using https://console.aws.amazon.com/esv3. \n2. Under 'My domains', choose the name of the domain to edit. \n3. Choose 'Edit domain'. \n4. Under 'Dedicated master nodes', set 'Instance type' to the desired instance type. \n5. Set 'Number of master nodes' equal to three or greater. \n6. Choose Submit.","multiregional":false,"service":"Amazon OpenSearch Service"},"ecc-aws-039":{"article":"This policy identifies security group rules that allow inbound traffic to the Telnet port (23) from the public internet. Allowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Unrestricted Telnet access can increase opportunities for malicious activity such as IP address spoofing, man-in-the-middle attacks (MITM) and brute-force attacks.","report_fields":["GroupId","VpcId","OwnerId"],"remediation":"1. Login to the AWS Console.\n2. Go to Security Group.\n3. Go to the SG rules.\n4. Click on the reported Firewall rule.\n5. Click on Edit.\n6. Modify the rule.\n7. Click on Save.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-430":{"article":"This policy identifies the Sagemaker instances that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["NotebookInstanceArn"],"remediation":"1. Open the Sagemaker console at https://console.aws.amazon.com/sagemaker/\n2. Choose Notebook instances.\n3. Choose required notebook instance.\n4. Under the 'Tags' click on the 'Edit'.\n5. Add tags and save.","multiregional":false,"service":"Amazon SageMaker"},"ecc-aws-527":{"article":"A WAF global web ACL can contain a collection of rules and rule groups that inspect and control web requests.\nA web access control list (web ACL) gives you fine-grained control over all of the HTTP(S) web requests that your protected resource responds to. You can protect Amazon CloudFront, Amazon API Gateway, Application Load Balancer, AWS AppSync, and Amazon Cognito resources.","impact":"If a web ACL is empty, the web traffic can pass without being detected or acted upon by WAF depending on the default action.","report_fields":["WebACLId","Name"],"remediation":"To add rules or rule groups to an empty web ACL:\n1. Open the AWS WAF console at https://console.aws.amazon.com/wafv2/.\n2. In the navigation pane, choose 'Switch to AWS WAF Classic', and then choose 'Web ACLs'.\n3. For 'Filter', choose 'Global (CloudFront)'.\n4. Choose the name of the empty web ACL.\n5. Choose 'Rules', and then choose 'Edit web ACL'.\n6. For 'Rules', choose a rule or rule group, and then choose 'Add rule to web ACL'.\n7. At this point, you can modify the rule order within the web ACL if you are adding multiple rules or rule groups to the web ACL.\n8. Choose 'Update'.","multiregional":true,"service":"AWS Web Application Firewall"},"ecc-aws-513":{"article":"In order to adhere to security best practices and protect certificates from cryptographic algorithm hacking attacks using brute-force methods it is highly recommended upgrading 1024-bit server certificates to 2048-bit or 4096-bit RSA certificates which are using stronger encryption algorithms.","impact":"Any server certificate that is using 1024-bit keys can no longer be considered secure. All major web browsers dropped support for 1024-bit RSA certificates at the end of 2013. If your AWS IAM server certificates are still using 1024-bit keys, you should raise their bit length to 2048 or higher in order to increase its security level.","report_fields":["CertificateArn","DomainName"],"remediation":"1. Open the ACM console at https://console.aws.amazon.com/acm/home. If this is your first time using ACM, look for the AWS Certificate Manager heading and choose the Get started button under it.\n2. Choose Import a certificate.\n3. Do the following:\n 3.1. For Certificate body, paste the PEM-encoded certificate to import. It should begin with -----BEGIN CERTIFICATE----- and end with -----END CERTIFICATE-----.\n 3.2. For Certificate private key, paste the certificate's PEM-encoded, unencrypted private key. It should begin with -----BEGIN PRIVATE KEY----- and end with -----END PRIVATE KEY-----.\n 3.3. (Optional) For Certificate chain, paste the PEM-encoded certificate chain.\n4. Choose Review and import.\n5. On the Review and import page, check the displayed metadata about your certificate to ensure that it is what you intended. The fields include:\n 5.1. Domains \u2014 A list of fully qualified domain names (FQDN) authenticated by the certificate\n 5.2. Expires in \u2014 The number of days until the certificate expires\n 5.3. Public key info \u2014 The cryptographic algorithm used to generate the key pair\n 5.4. Signature algorithm \u2014 The cryptographic algorithm used to create the certificate's signature\n 5.5. Can be used with \u2014 A list of ACM integrated services that support the type of certificate you are importing\n6. If everything is correct, choose Import.","multiregional":false,"service":"AWS Certificate Manager"},"ecc-aws-358":{"article":"AWS CloudTrail provides a number of security features to consider as you develop and implement your own security policies. At least one AWS CloudTrail trail in the account should be defined with these security best practices:\n - records global service events\n - is a multi-region trail\n - has Log file validation enabled\n - encrypted with a KMS key\n - records events for reads and writes\n - records management events\n - does not exclude any management events","impact":"If security critical information is not recorded, there will be no trail for forensic analysis, and discovering the cause of problems or the source of attacks may become more difficult or impossible.\nWithout enabling CloudTrail across different regions, it is hard to monitor the infrastructure and maintain security across all regions.\nWith disabled log file validation, you miss out on additional integrity checks of CloudTrail logs. CloudTrail log file validation is used to determine whether a log file was changed, deleted, or unchanged after CloudTrail delivered the log. \nWithout CloudTrail configured to use SSE-KMS, you do not have full control over who can read the log files in your organization. This can lead to unauthorized access to CloudTrail logs.","report_fields":["account_id","account_name"],"remediation":"To create a CloudTrail trail that comply with security best practices, perform the following actions:\nNote: If you have multiple single region trails, consider configuring your trails so that global service events and are delivered in only one of the trails.\n1. Sign in to the AWS Management Console and open the CloudTrail console at https://console.aws.amazon.com/cloudtrail/\n2. On the CloudTrail service home page, the Trails page, or the Trails section of the Dashboard page, choose Create trail.\n3. On the Create Trail page, for Trail name, type a name for your trail.\n4. For Storage location, choose Create new S3 bucket to create a bucket. When you create a bucket, CloudTrail creates and applies the required bucket policies. To make it easier to find your logs, create a new folder (also known as a prefix) in an existing bucket to store your CloudTrail logs. Enter the prefix in Prefix.\nNote: If you chose 'Use existing S3 bucket', specify a bucket in Trail log bucket name, or choose Browse to choose a bucket. The bucket policy must grant CloudTrail permission to write to it.\n5. For Log file SSE-KMS encryption, choose Enabled. Choose 'New' AWS KMS key. In AWS KMS Alias, specify an alias, in the format alias/MyAliasName. The key policy must allow CloudTrail to use the key to encrypt your log files, and allow the users you specify to read log files in unencrypted form. \n6. In Additional settings, configure the following. For 'Log file validation', choose 'Enabled' to have log digests delivered to your S3 bucket.\n7. Optionally, configure CloudTrail to send log files to CloudWatch Logs by choosing 'Enabled' in 'CloudWatch Logs'.\n8. For Tags, add one or more custom tags (key-value pairs) to your trail. \n9. On the 'Choose log events' page, 'Management events' already selected by default, do not change it.\n10. For 'API activity', leave both boxes 'Read events' and 'Write events' selected. The default setting is to include all AWS KMS events.\n11. Do not exclude AWS events.\n12. Choose 'Next'.\n13. After a new trail was created, you should go to the 'AWS Key Management Service (AWS KMS)' console at https://console.aws.amazon.com/kms and give decrypt permissions to all users who require them. Users who have encrypt permissions but no decrypt permissions will not be able to read encrypted logs. \n\nDuring this configuration process, there was no need to enable 'Global service events' and a 'Multi-region trail'. Because 'Global service events' are delivered by default to trails that are created using the CloudTrail console. Also, in the console, by default, you create a trail that logs events in all AWS Regions.","multiregional":false,"service":"AWS CloudTrail"},"ecc-aws-478":{"article":"Server Name Indication (SNI) is an extension to the TLS protocol that is supported by browsers and clients released after 2010. If you configure CloudFront to serve HTTPS requests using SNI, CloudFront associates your alternate domain name with an IP address for each edge location. When a viewer submits an HTTPS request for your content, DNS routes the request to the IP address for the correct edge location. The IP address to your domain name is determined during the SSL/TLS handshake negotiation; the IP address isn't dedicated to your distribution.\nIf you configured CloudFront to use a custom SSL/TLS certificate with dedicated IP addresses, you can switch to using a custom SSL/TLS certificate with SNI instead and eliminate the charge that is associated with dedicated IP addresses.\nThis control fails if a custom SSL/TLS certificate is associated but the SSL/TLS support method is a dedicated IP address.","impact":"The SSL/TLS negotiation occurs early in the process of establishing an HTTPS connection. If CloudFront can't immediately determine which domain the request is for, it drops the connection if using 'vip' instead of 'sni'.","report_fields":["ARN"],"remediation":"To switch from a custom SSL/TLS certificate with dedicated IP addresses to SNI\n1. Sign in to the Amazon Web Services Management Console and open the CloudFront console at https://console.amazonaws.cn/cloudfront/v3/home.\n2. Choose the ID of the distribution that you want to view or update.\n3. Choose Distribution Settings.\n4. On the General tab, choose Edit.\n5. Change the setting of Custom SSL Client Support to Only Clients that Support Server Name Indication (SNI).\n6. Choose Yes, Edit.","multiregional":true,"service":"Amazon CloudFront"},"ecc-aws-002":{"article":"Access keys consist of an access key ID and secret access key, which are used to sign programmatic requests that you make to AWS. AWS users need their own access keys to make programmatic calls to AWS from the AWS Command Line Interface (AWS CLI), Tools for Windows PowerShell, the AWS SDKs, or to make direct HTTP calls using the APIs for individual AWS services. It is recommended that all access keys be regularly rotated.","impact":"Not rotating access keys periodically increases the probability of access keys being compromised. It can lead to unauthorized access to your AWS cloud resources. Access keys rotation shortens the period an access key is active and reduces the business impact if the key is compromised.","report_fields":["Arn"],"remediation":"1. Go to the Management Console (https://console.aws.amazon.com/iam). \n2. Click on Users. \n3. Click on Security Credentials. \n4. As an Administrator - Click on Make Inactive for keys that have not been rotated in 90 Days. \n5. As an IAM User - Click on Make Inactive or Delete for keys which have not been rotated or used in 90 Days. \n6. Click on Create Access Key. \n7. Update programmatic call with new Access Key credentials.","multiregional":true,"service":"AWS Identity and Access Management"},"ecc-aws-464":{"article":"Amazon ECS provides a default configuration for logging commands run using ECS Exec. You can audit which user accessed the container using AWS CloudTrail and log each command (and their output) to Amazon S3 or Amazon CloudWatch Logs. This configuration only handles the logging of the execute-command session. It doesn't affect logging of your application.","impact":"With disabled logging of ECS Exec, it may be difficult to troubleshoot any issues that might happen with container or find who accessed container and how they accessed it.","report_fields":["clusterArn"],"remediation":"To enable logging and publish data to CloudWatch, perform the following actions:\nExecute command :\naws ecs update-cluster \\\n --cluster $CLUSTER_NAME \\\n --region $AWS_REGION \\\n --configuration executeCommandConfiguration=\"{{logging=OVERRIDE,\\\n kmsKeyId=$KMS_KEY_ARN,\\\n logConfiguration={{cloudWatchLogGroupName=\"$CLOUDWATCH_GROUP_NAME\",\\\n cloudWatchEncryptionEnabled=true}}\\\n }}\"\n\nTo enable logging and publish data to s3, perform the following actions:\nExecute command:\naws ecs update-cluster \\\n --cluster $CLUSTER_NAME \\\n --region $AWS_REGION \\\n --configuration executeCommandConfiguration=\"{{logging=OVERRIDE,\\\n logConfiguration={{s3EncryptionEnabled=true,\\\n s3BucketName=$ECS_EXEC_BUCKET_NAME,\\\n s3KeyPrefix=$PREFIX}} \\\n }}\"\nIn addition, the task role will need to have IAM permissions to log the output to S3 and/or CloudWatch and decrypt the data with KMS key.\nTo update your task role follow the instructions that are described in the 'IAM permissions required for Amazon CloudWatch Logs or Amazon S3 Logging' and 'IAM permissions required for encryption using your own AWS KMS key (KMS key)' sections in Amazon ECS User Guide: https://docs.aws.amazon.com/AmazonECS/latest/userguide/ecs-exec.html.","multiregional":false,"service":"Amazon Elastic Container Service"},"ecc-aws-037":{"article":"This policy identifies security group rules that allow inbound traffic to the PostgreSQL port (5432) from the public internet. Allowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Unrestricted PostgreSQL Database access can increase opportunities for malicious activity such as unauthorized access, denial-of-service (DoS) attacks, and loss of data. \nFor example, if a user account has a weak password, the attacker can execute a brute-force attack or dictionary-based password attack and then perform privilege escalation to root.","report_fields":["GroupId","VpcId","OwnerId"],"remediation":"1. Login to the AWS Console.\n2. Go to Security Group.\n3. Go to the SG rules.\n4. Click on the reported Firewall rule.\n5. Click on Edit.\n6. Modify the rule.\n7. Click on Save.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-500":{"article":"Deploying resources across multiple Availability Zones is an AWS best practice to ensure high availability within your architecture. Availability is a core pillar in the confidentiality, integrity, and availability triad security model. All Lambda functions should have a multi-Availability Zone deployment to ensure that a single zone of failure does not cause a total disruption of operations.\nWhen you create a Lambda function without a VPC configuration, it\u2019s automatically available in all Availability Zones within the Region. When you set up VPC access, you choose which Availability Zones the Lambda function can use. As a result, to provide continued high availability, ensure that the function has access to at least two Availability Zones.","impact":"Deploying Lambda function in only one Availability Zone, can cause a total disruption of operations.","report_fields":["FunctionArn"],"remediation":"To deploy a Lambda function in multiple Availability Zones through console:\n1. Open the AWS Lambda console at https://console.aws.amazon.com/lambda/\n2. From the 'Functions' page on the Lambda console choose a function.\n3. Choose 'Configuration' and then choose 'VPC'.\n4. Choose 'Edit'.\n5. For 'Subnets' attach at least one additional subnet.\n6. Click 'Save'.","multiregional":false,"service":"AWS Lambda"},"ecc-aws-545":{"article":"Logging and monitoring are important for maintaining the reliability, availability, and performance of Step Functions and your AWS solutions. You can choose from OFF, ALL, ERROR, or FATAL. No event types log when set to OFF and all event types do when set to ALL.","impact":"Disabling logging in AWS Step Functions can result in inefficient resource usage, reduced performance, limited scalability, and additional operational overhead.","report_fields":["stateMachineArn"],"remediation":"To enable logging for AWS Step Functions, you can follow these steps:\n1. Open the AWS Management Console and navigate to the Step Functions console.\n2. Click on the state machine for which you want to enable logging.\n3. In the state machine details page, click on the \"Edit\" button.\n4. In the \"Log level\" dialog box choose the log level you want to capture in the log stream. The available log levels are: OFF, ERROR, WARN, INFO, and DEBUG.\n5. Select the desired CloudWatch log group. You can create a new log group or select an existing one.\n6. Click on the \"Save\" button.","multiregional":false,"service":"AWS Step Functions"},"ecc-aws-181":{"article":"A private replication instance has a private IP address that you cannot access outside of the replication network. A replication instance should have a private IP address when the source and target databases are in the same network. The network must also be connected to the replication instance's VPC using a VPN, AWS Direct Connect, or VPC peering.","impact":"When DMS replication instances are publicly accessible and have public IP addresses, any machine outside the VPC can establish a connection to these instances, increasing the attack surface and the opportunity for malicious activity.","report_fields":["ReplicationInstanceIdentifier","ReplicationInstanceArn"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\n1. Open the AWS Database Migration Service console at https://console.aws.amazon.com/dms/.\n2. Navigate to Replication instances, then delete the public instance. Choose the instance, choose Actions, then choose delete.\n3. Choose Create replication instance. Provide the configuration details.\n4. To disable public access, make sure that Publicly accessible is not selected.\n5. Choose Create.","multiregional":false,"service":"AWS Database Migration Service"},"ecc-aws-279":{"article":"Redis authentication tokens, or passwords, enable Redis to require a password before allowing clients to run commands, thereby improving data security.\nAmazon ElastiCache for Redis allows you to modify authentication tokens by setting and rotating new tokens. You can now modify active tokens while in use, or add brand-new tokens to existing encryption-in-transit enabled clusters that were previously setup without authentication tokens. \nWith support for rotating authentication token, ElastiCache for Redis now provides you more control and flexibility to meet your security requirements and password rotation policies.","impact":"Not rotating tokens periodically increases the probability of tokens being compromised. It can lead to unauthorized access to your AWS cloud resources. Tokens rotation shortens the period a token is active and reduces the business impact if it is compromised.","report_fields":["ARN"],"remediation":"To update a Redis server with a new AUTH token:\n1. Sign in to the Console and open the ElastiCache console at https://console.aws.amazon.com/elasticache/.\n2. From the navigation pane, choose 'Redis clusters'.\n3. From the list of clusters, choose the cluster you want to update.\n4. Choose 'Actions' and then choose 'Modify'.\n5. Under 'Redis Auth Token strategy' select 'Rotate token' and set new token.\n6. Click 'Preview changes'.\n7. If you want to perform the update right away, choose 'Apply immediately'. If Apply immediately is not chosen, the update process is performed during the cluster's next maintenance window.\n8. Choose 'Modify'.","multiregional":false,"service":"Amazon ElastiCache"},"ecc-aws-093":{"article":"Ensure that your AWS SageMaker notebook instances are placed to the VPC to access the VPC resources only and 'Direct internet access' is disabled.\nWhen your notebook allows direct internet access, SageMaker provides a network interface that allows the notebook to communicate with the internet through a VPC managed by SageMaker. \nIt is recommended to keep your resources inside a custom VPC whenever possible to ensure secure network protection of your infrastructure. An Amazon VPC is a virtual network dedicated to your AWS account. With an Amazon VPC, you can control the network access and internet connectivity of your SageMaker Studio and notebook instances.","impact":"When AWS SageMaker notebook instances are publicly accessible, any machine outside the VPC can establish a connection to these instances, increasing the attack surface and the opportunity for malicious activity.","report_fields":["NotebookInstanceArn"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\nTo ensure that your AWS SageMaker notebook instances are running inside a VPC, you need to re-create these:\n1. Log in to the AWS Management Console. \n2. Go to the SageMaker service dashboard at https://console.aws.amazon.com/sagemaker/. \n3. Create a notebook Instance.\n4. Under 'Network' section, select 'VPC' and ensure that 'Direct internet access' is set to 'Disable Access the internet through a VPC'.\n5. Configure other settings as required for your particular case.\n6. Click 'Create notebook instance'.","multiregional":false,"service":"Amazon SageMaker"},"ecc-aws-164":{"article":"Amazon Redshift audit logging provides additional information about connections and user activities in your cluster. This data can be stored and secured in Amazon S3 and can be helpful for security audits and investigations.","impact":"If security critical information is not recorded, there will be no trail for forensic analysis, and discovering the cause of problems or the source of attacks may become more difficult or impossible.","report_fields":["ClusterIdentifier"],"remediation":"1. Open the Amazon Redshift console at https://console.aws.amazon.com/redshift/.\n2. In the navigation menu, choose Clusters, then choose the name of the cluster to modify.\n3. Choose Maintenance and monitoring.\n4. Under Audit logging, choose Edit.\n5. Set Enable audit logging to Yes, then enter the log destination bucket details.\n6. Open required cluster and click on 'Properties'.\n7. Click on the 'Parameter group'.\n8. Click on 'Parameters' and choose 'Edit parameters'.\n9. Change 'enable_user_activity_logging' to true.\n10. Save chages\n11. Choose Confirm.","multiregional":false,"service":"Amazon Redshift"},"ecc-aws-354":{"article":"Changing default port (5439) helps with automated attacks and gives more information if it was a target attack or not.","impact":"Running AWS Redshift clusters on the default port is a potential security risk as it provides an attacker path to a service listening on that port and increases the attack vectors your organization is exposed to.","report_fields":["ClusterIdentifier"],"remediation":"To remediate this issue from the AWS CLI, use the Amazon Redshift modify-cluster command to set the --port attribute:\naws redshift modify-cluster --cluster-identifier clustername --port port","multiregional":false,"service":"Amazon Redshift"},"ecc-aws-352":{"article":"Encrypting data at rest reduces the risk of data stored on disk being accessed by a user not authenticated to AWS. It also adds another set of access controls to limit the ability of unauthorized users to access the data. \nA customer master key (CMK) is the primary resource used by AWS Key Management Service (KMS) to create data encryption keys and can be customer managed or AWS managed. SNS topic offers encryption of data at rest, a security feature that helps prevent unauthorized access to AWS SNS topic data. Use a customer managed CMK to encrypt SNS topics so that only users in the account with access to the CMK can view or manage the data.","impact":"Unauthorized users can read confidential information available on SNS topics. Without a KMS CMK customer-managed key, you do not have full and granular control over who can access key that is used for encryption.","report_fields":["TopicArn"],"remediation":"1. Open the Amazon SNS console at https://console.aws.amazon.com/sns/v3/home.\n2. In the navigation pane, choose Topics. \n3. Choose the name of the topic to encrypt.\n4. Choose Edit. \n5. Under Encryption, choose Enable Encryption. \n6. Choose the KMS CMK key to use to encrypt the topic. \n7. Choose Save changes.","multiregional":false,"service":"Amazon Simple Notification Service"},"ecc-aws-085":{"article":"You can configure a Lambda function to connect to private subnets in a virtual private cloud (VPC) in your AWS account. Use Amazon Virtual Private Cloud (Amazon VPC) to create a private network for resources such as databases, cache instances, or internal services. Connect your function to the VPC to access private resources while the function is running.","impact":"Lambda without a VPC is open to the internet. It can increase opportunities for malicious activity such as spamming and Denial-of-Service (DoS) attacks. Also, Lambda without a VPC cannot access AWS resources.","report_fields":["FunctionArn"],"remediation":"1. Open the AWS Lambda console at https://console.aws.amazon.com/lambda/.\n2. Navigate to Functions and then select your Lambda function.\n3. Scroll to Network and then select a VPC with the connectivity requirements of the function.\n4. To run your functions in high availability mode, Security Hub recommends that you choose at least 2 subnets. \n5. Choose at least one security group that has the connectivity requirements of the function \n6. Choose Save.","multiregional":false,"service":"AWS Lambda"},"ecc-aws-021":{"article":"Identifies volumes that have not had a backup or snapshot in the past 14 days. If a snapshot is not recent, it could be missing crucial patches and software updates. It is recommended to keep backups as recent as you can.","impact":"Without a recently taken snapshot, it is impossible to recover data in the event of failure quickly. It has a significant impact on the resilience of your system.","report_fields":["VolumeId"],"remediation":"1. Navigate to console.aws.amazon.com/ec2/. \n2. Select Snapshots under Elastic Block Store. \n3. Find snapshots that were created more than 14 days ago.\n4. Update snapshots:\n4.1. Press Create snapshot.\n4.2. Select Volume in the Resource type.\n4.3. Choose the volume with the expired period under the Volume tab.\n4.4. Press Create snapshot.","multiregional":false,"service":"Amazon Elastic Block Store"},"ecc-aws-215":{"article":"Backups help you to recover more quickly from a security incident. They strengthen the resilience of your systems. Amazon Redshift takes periodic snapshots by default. \nAutomatic snapshots should be enabled and retained for at least seven days.","impact":"A retention period of fewer than 7 days set for Redshift clusters can result in data loss and the inability to recover it in the event of failure.","report_fields":["ClusterIdentifier"],"remediation":"1. Open the Amazon Redshift console at https://console.aws.amazon.com/redshift/.\n2. In the navigation menu, choose Clusters, then choose the name of the cluster to modify. \n3. Choose Edit. \n4. Under Backup, set Snapshot retention to a value of 7 or greater. \n5. Choose Modify Cluster.","multiregional":false,"service":"Amazon Redshift"},"ecc-aws-102":{"article":"When your AWS SageMaker notebook instances are publicly accessible, any machine outside the VPC can establish a connection to these instances, increasing the attack surface and the opportunity for malicious activity. It is recommended that the Amazon SageMaker notebook instances are not publicly accessible.","impact":"Allowing direct public access to your notebook instance might violate the requirement to only allow access to system components that provide authorized publicly accessible services, protocols, and ports. When AWS SageMaker notebook instances are publicly accessible, any machine outside the VPC can establish a connection to these instances, increasing the attack surface and the opportunity for malicious activity.","report_fields":["NotebookInstanceArn"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n1. Log in to the AWS Management Console. \n2. Go to the SageMaker service dashboard at https://console.aws.amazon.com/sagemaker/. \n3. Create a notebook Instance.\n4. Under Network, select VPC and ensure that 'Direct internet access' is set to 'Disable Access the internet through a VPC'.","multiregional":false,"service":"Amazon SageMaker"},"ecc-aws-362":{"article":"If you do not specify a Customer managed key when creating your environment, Amazon MWAA uses its AWS owned key for data encryption on your environment.\nWhen you create and use your own KMS CMK customer-managed keys to protect the data on your environment, you obtain full control over who can use the CMK keys and access the encrypted data. The AWS KMS service allows you to create, rotate, disable, enable, and audit your Customer Master Keys (CMKs).\nIn addition, if you provide a Customer managed key, you must attach the policy statement for CloudWatch access. You must also create the Customer managed key-specific execution role.","impact":"Without a KMS CMK customer-managed key, you do not have full and granular control over who can access key that is used for encryption.","report_fields":["Arn"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nIn order to encrypt a new Airflow environment with KMS CMK: \n1. Navigate to https://console.aws.amazon.com/mwaa/home.\n2. Click the \"Create environment\" button.\n4. On the \"Specify details\" page configure \"Environment details\" and \"DAG code in Amazon S3\". \n5. Click \"Next\".\n6. On the \"Configure advanced settings\" make all the necessary configurations.\n7. Under the \"Encryption\" section, check \"Customize encryption settings (advanced)\".\n8. Select a KMS Customer managed key.\n9. Click \"Next\".\n10. On the \"Review and create\" page, review the configuration and click \"Create environment\".\n\nMake sure that all necessary permissions are configured for the execution role and KMS CMK key policy. Because policies should provide the right permissions for MWAA and the workers to be able to access the keys to decrypt/encrypt messages and logs.\nYou must add permissions to your execution role if your Airflow DAGs require access to any other AWS services.","multiregional":false,"service":"Amazon Managed Workflows for Apache Airflow"},"ecc-aws-365":{"article":"With this feature enabled, you can encrypt AWS Glue Data Catalog connection password. These passwords are stored in the Data Catalog connection and are used when AWS Glue connects to a Java Database Connectivity (JDBC) data store.","impact":"Not encrypted connection password can be compromised. Without AWS KMS Customer Master Keys (CMKs), you do not have full and granular control over who can use the encryption keys to access AWS Glue data.","report_fields":["CatalogId"],"remediation":"1. Sign in to the AWS Management Console and open the AWS Glue console at https://console.aws.amazon.com/glue/.\n2. Choose 'Settings' in the navigation pane.\n3. On the 'Data catalog settings' page, select 'Encrypt connection passwords', and choose an AWS KMS Customer Master Key.","multiregional":false,"service":"AWS Glue"},"ecc-aws-160":{"article":"RDS event notifications use Amazon SNS to make you aware of changes in the availability or configuration of your RDS resources. These notifications allow for rapid response.\nDBInstance:['maintenance','configuration change', 'failure'] Notification should be enabled for All Instances.","impact":"Not enabling RDS instance event notifications can lead to slow or no response to an instance configuration change or a failure.","report_fields":["account_id","account_name"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. In the navigation pane, choose Event subscriptions.\n3. Under Event subscriptions, choose Create event subscription.\n4. In the Create event subscription dialog, do the following.\n4.1. For Name, enter a name for the event notification subscription.\n4.2. For Send notifications to, choose an existing Amazon SNS ARN for an SNS topic.\n To use a new topic, choose Create topic to enter the name of a topic and a list of recipients.\n4.3. For Source type, choose Instances.\n4.4. Under Instances to include, select All Instances.\n4.5. Under Event categories to include, select Specific event categories.\n The control also passes if you select All event categories.\n4.5.1. Select maintenance, configuration change, and failure.\n4.6. Choose Create.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-075":{"article":"The encryption of data at rest is a security feature that helps prevent unauthorized access to your data. When the feature is enabled, it encrypts sensitive information on your Elasticsearch domains and their storage systems such as Indices, Elasticsearch Logs, Swap files, automated snapshots and all other data in the application directory. The ElasticSearch at-rest encryption feature uses AWS KMS service to store and manage the encryption keys.","impact":"When encryption of data at rest is not enabled, it can lead to unauthorized access to sensitive information available on ES domains (clusters) and their storage systems.","report_fields":["ARN"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n1. By default, domains do not encrypt data at rest, and you cannot configure existing domains to use the feature. \n2. To enable the feature, you must create another domain and migrate your data. \n3. Encryption of data at rest requires Amazon ES 5.1 or later.","multiregional":false,"service":"Amazon OpenSearch Service"},"ecc-aws-076":{"article":"EBS snapshots are used to back up the data on your EBS volumes to Amazon S3 at a specific point in time. You can use the snapshots to restore previous states of EBS volumes. It is rarely acceptable to share a snapshot with the public. \nTo avoid accidental exposure of your company's sensitive data, Amazon EBS snapshots should not be publicly restorable by everyone unless you explicitly allow it.","impact":"Publicly restorable AWS Elastic Block Store (EBS) volume snapshots can lead to exposure of personal and sensitive data.","report_fields":["SnapshotId","OwnerId"],"remediation":"1. Open the Amazon EC2 console at https://console.aws.amazon.com/ec2/.\n2. On the navigation pane, choose Snapshots and then choose your public snapshot.\n3. Choose Actions, then choose Modify permissions. \n4. Choose Private. \n5. Optionally, add AWS account numbers for authorized accounts to share your snapshot with. \n6. Choose Save.","multiregional":false,"service":"Amazon Elastic Block Store"},"ecc-aws-029":{"article":"This policy identifies security group rules that allow inbound traffic to the FTP port (21) from the public internet. Allowing access from arbitrary internet IP addresses to this port increases the attack surface of your network.","impact":"Unrestricted FTP access can increase opportunities for malicious activity such as brute-force attacks, FTP bounce attacks, spoofing attacks and packet capture.","report_fields":["GroupId","VpcId","OwnerId"],"remediation":"1. Login to the AWS Console.\n2. Go to Security Group.\n3. Go to the SG rules.\n4. Click on the reported Firewall rule.\n5. Click on Edit.\n6. Modify the rule.\n7. Click on Save.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-338":{"article":"By default, when you create a notebook instance, users that log into that notebook instance have root access.","impact":"Users with root access have administrator privileges, users can access and edit all files on a notebook instance with root access enabled.","report_fields":["NotebookInstanceArn"],"remediation":"To disable root access:\n1. Open the Sagemaker console at https://console.aws.amazon.com/sagemaker/\n2. Choose Notebook instances, and then choose the notebook instance which you want to update.\n3. Choose Stop.\n4. Once the status of the Notebook instance is Stopped, choose Edit.\n5. In the Permissions and encryption settings disable Root access.\n6. Choose Update notebook instance.\n7. Choose Start to start the instance.","multiregional":false,"service":"Amazon SageMaker"},"ecc-aws-089":{"article":"You can use CodeBuild in your PCI DSS environment to compile your source code, run unit tests, or produce artifacts that are ready to deploy. If you do, never store the authentication credentials AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY in clear text. \nUsing environmental variables to store credentials in your CodeBuild project may violate the requirement to use strong cryptography to render authentication credentials unreadable.","impact":"Using environmental variables to store credentials in your CodeBuild project may violate the requirement to use strong cryptography to render authentication credentials unreadable. This could lead to unintended data exposure and unauthorized access.","report_fields":["arn"],"remediation":"1. Open the CodeBuild console at https://console.aws.amazon.com/codebuild/.\n2. Expand Build, choose Build project, and then choose the build project that contains plaintext credentials. \n3. From Edit, choose Environment. \n4. Expand Additional configuration and then scroll to Environment variables. \n5. Choose Remove next to the environment variable. \n6. Choose Update environment.","multiregional":false,"service":"AWS CodeBuild"},"ecc-aws-236":{"article":"Enabling debug_pretty_print indents the messages produced by debug_print_parse, debug_print_rewritten, or debug_print_plan making them significantly easier to read. \nIf this setting is disabled, the \"compact\" format is used instead, significantly reducing readability of the DEBUG statement log messages. \nUnless directed otherwise by your organization's logging policy, it is recommended this setting be disabled by setting it to on.","impact":"If this setting is disabled, the \"compact\" format is used instead, significantly reducing readability of the DEBUG statement log messages.","report_fields":["DBInstanceArn"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/\n2. In the navigation pane, choose Parameter groups.\n3. Choose the required parameter group.\n4. Choose Edit parameters.\n5. Under Parameters, in search bar type 'debug_pretty_print'.\n6. Choose 1 in value field.\n7. Choose Save changes.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-343":{"article":"Check whether Amazon MQ brokers are not publicly accessible. The rule is NON_COMPLIANT if the publicly accessible field is true.","impact":"Unrestricted access increases the opportunity for malicious activity such as unauthorized access, denial-of-service attacks, and loss of data.","report_fields":["BrokerArn"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n1. Open the Amazon MQ console at https://console.aws.amazon.com/amazon-mq/\n2. In the navigation pane, choose Brokers.\n3. Click on the 'Create brokers'\n4. Inside 'Configure settings' under 'Additional settings' click on the 'Private access'.\n5. Create broker.","multiregional":false,"service":"Amazon MQ"},"ecc-aws-116":{"article":"API Gateway private endpoints are made available via AWS PrivateLink interface VPC endpoints. Interface endpoints work by creating elastic network interfaces in subnets that you define inside your VPC. Those network interfaces then provide access to services running in other VPCs, or to AWS services such as API Gateway.\nWhen configuring your interface endpoints, you specify which service traffic should go through them. API Gateway as a fully managed service runs its infrastructure in its own VPCs. When you interface with API Gateway publicly accessible endpoints, it is done through public networks. \nWhen configured as private, the public networks are not made available to route your API. Instead, your API can only be accessed using the interface endpoints that you have configured.","impact":"When a private API endpoint has access from the public internet, it increases the attack surface.","report_fields":["id","name"],"remediation":"1. Sign in to the API Gateway console and choose APIs in the primary navigation pane https://console.aws.amazon.com/apigateway. \n2. Under + Create API, choose the API settings (gear icon).\n3. Under Endpoint Configuration, change the Endpoint Type option from Edge Optimized to Regional or from Regional to Edge Optimized. \n4. Choose Save to start the update.","multiregional":false,"service":"Amazon API Gateway"},"ecc-aws-165":{"article":"A public IP address is an IP address that is reachable from the internet. If you launch your Amazon ECS instances with a public IP address, then your Amazon ECS instances are reachable from the internet. Amazon ECS services should not be publicly accessible, as this may allow unintended access to your container application servers.","impact":"Unrestricted access increases the opportunity for malicious activity such as unauthorized access, denial-of-service attacks, and loss of data.","report_fields":["serviceArn"],"remediation":"To disable automatic public IP assignment, configure VPC and security group settings for your service in the Amazon Elastic Container. \nFrom Console:\n1. Login to the AWS Management Console and open the ECS console using https://console.aws.amazon.com/ecs/\n2. In the navigation pane, choose Clusters and open the needed cluster.\n3. In the navigation pane, choose Services and then choose Create.\n4. Configure network by setting Auto-assign public IP to DISABLED.\n5. Complete service configuration.","multiregional":false,"service":"Amazon Elastic Container Service"},"ecc-aws-270":{"article":"You can enable Multi-AZ only on Redis (cluster mode disabled) clusters that have at least one available read replica. \nClusters without read replicas do not provide high availability or fault tolerance.","impact":"When an ElastiCache is not configured for multiple Availability Zones and in case of any issue with the Availability Zone, elasticache will not be reachable. \nMulti-AZ deployments allow you to automate failover.","report_fields":["ARN"],"remediation":"Enabling Multi-AZ on an existing cluster:\n1. Sign in to the Console and open the ElastiCache console at https://console.aws.amazon.com/elasticache/.\n2. From the navigation pane, choose 'Redis clusters'.\n3. From the list of clusters, choose the cluster you want to update.\n4. Choose 'Actions' and then choose 'Modify'.\n5. Choose 'Enable' for 'Multi-AZ'. And click 'Preview changes'.\n6. If you want to perform the update right away, choose 'Apply immediately'. If Apply immediately is not chosen, the update process is performed during the cluster's next maintenance window.\n7. Choose 'Modify'.","multiregional":false,"service":"Amazon ElastiCache"},"ecc-aws-326":{"article":"When you create and use your own KMS CMK customer-managed keys to protect the contents of your EBS volumes, you obtain full control over who can use the CMK keys and access the data encrypted within Volume. The AWS KMS service allows you to create, rotate, disable, enable, and audit your Customer Master Keys (CMKs) for EBS volumes.","impact":"Without a KMS CMK customer-managed key, you do not have full and granular control over who can access key that is used for encryption.","report_fields":["VolumeId"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nTo encrypt existing EBS Volume with KMS CMK:\n1. Login to the AWS Management Console and open the Amazon EC2 console using https://console.aws.amazon.com/ec2/.\n2. Under Elastic Block Store, click on Volumes.\n3. Click on required volume.\n4. Click on the Actions.\n5. Click on the Create snapshot.\n6. Create snapshot.\n7. Under Elastic Block Store, click on Snapshots\n8. Click on the required snapshot.\n9. Click on the Actions, and Create volume from snapshot.\n10. Under the encryption click on the 'Encrypt this volume'.\n11. Choose existing KMS CMK (not default) key or create new one.\n12. Click on the Create volume.","multiregional":false,"service":"Amazon Elastic Block Store"},"ecc-aws-372":{"article":"For any WorkSpaces API requests setup the connection through an interface endpoint in your VPC. Utilizing a VPC interface endpoint for WorkSpaces API requests keeps the communication within the AWS network.\nThe VPC interface endpoint connects your VPC directly to the Amazon WorkSpaces API endpoint without an internet gateway, NAT device, VPN connection, or AWS Direct Connect connection. The instances in your VPC don't need public IP addresses to communicate with the Amazon WorkSpaces API endpoint.","impact":"Without using VPC Endpoint network connection exposed to the public network, this increases the opportunity for malicious activities and attacks.","report_fields":["DirectoryId"],"remediation":"Perform the following steps to create a VPC interface endpoint:\n1. Log in to the VPC console at https://console.aws.amazon.com/vpc/.\n2. In the left pane, click 'Endpoints'.\n3. Click 'Create Endpoint'.\n4. For 'Service category', ensure that AWS services is selected.\n5. For 'Service Name', choose 'Workspaces'. For Type, ensure that it indicates 'Interface.\n6. Complete the following information:\n - For 'VPC', select a VPC in which to create the endpoint. \n - For 'Subnets', select the subnets (Availability Zones) in which to create the endpoint network interfaces. Not all Availability Zones may be supported for all AWS services. \n - To enable private DNS for the interface endpoint, for 'Enable DNS Name', select the check box. \n - For Security group, select the security groups to associate with the endpoint network interfaces.\n8. Click Create endpoint.\n\nAfter you have created a VPC endpoint, you can use the following example CLI commands that use the endpoint-url parameter to specify interface endpoints to the Amazon WorkSpaces API endpoint:\naws workspaces copy-workspace-image --endpoint-url VPC_Endpoint_ID.workspaces.Region.vpce.amazonaws.com\n\naws workspaces delete-workspace-image --endpoint-url VPC_Endpoint_ID.api.workspaces.Region.vpce.amazonaws.com\n\naws workspaces describe-workspace-bundles --endpoint-url VPC_Endpoint_ID.workspaces.Region.vpce.amazonaws.com \\\n --endpoint-name Endpoint_Name \\\n --body \"Endpoint_Body\" \\\n --content-type \"Content_Type\" \\\n Output_File","multiregional":false,"service":"Amazon WorkSpaces Family"},"ecc-aws-336":{"article":"Using Amazon KMS Customer Master Keys (CMKs) to protect data in SageMaker endpoint configurations gives full control over who can use the encryption keys to access SageMaker data. Amazon KMS service allows to easily create, rotate, disable and audit Customer Master Keys created for SageMaker.","impact":"Without a KMS CMK customer-managed key, you do not have full and granular control over who can access key that is used for encryption.","report_fields":["EndpointConfigArn"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nTo create a new Endpoint configuration, perform the following actions:\n1. Open the Sagemaker console at https://console.aws.amazon.com/sagemaker/.\n2. In the navigation pane, under 'Inference' choose 'Endpoint configurations'.\n3. Choose 'Create Endpoint configurations'.\n4. Under 'Encryption key' choose existing key or create new key.\n5. Choose 'Create Endpoint configurations'.","multiregional":false,"service":"Amazon SageMaker"},"ecc-aws-536":{"article":"This control checks function settings for the following runtimes: nodejs18.x,, nodejs16.x, nodejs14.x, nodejs12.x, python3.9, python3.8, python3.7, ruby2.7, java11, java8, java8.al2, go1.x, dotnetcore3.1, and dotnet6.\nLambda runtimes are built around a combination of operating system, programming language, and software libraries that are subject to maintenance and security updates. When a runtime component is no longer supported for security updates, Lambda deprecates the runtime. Even though you cannot create functions that use the deprecated runtime, the function is still available to process invocation events. Make sure that your Lambda functions are current and do not use out-of-date runtime environments.\nIt is strongly recommended that you migrate functions to a supported runtime version so that you continue to receive security patches and remain eligible for technical support.","impact":"When security updates are no longer available for a component of a runtime, Lambda deprecates the runtime. Because of this, using deprecated Lambda runtimes can pose a security risk. Moreover, functions that use a deprecated runtime are no longer eligible for technical support.","report_fields":["FunctionArn"],"remediation":"To update a function, you need to migrate it to a supported runtime version. \n1. Login to the AWS Management Console and open the Amazon Lambda https://console.aws.amazon.com/lambda/.\n2. In the navigation pane click on the 'Functions'.\n3. Scroll down in the 'Code' tab to the 'Runtime settings' section.\n4. Click 'Edit'.\n5. Select a supported runtime version.\n6. Click 'Save'.","multiregional":false,"service":"AWS Lambda"},"ecc-aws-512":{"article":"Check whether Amazon ELB (Application, Network and Gateway load balancers) are not internet facing. The rule is NON_COMPLIANT if the Scheme field is 'internet-facing' in the configuration.","impact":"Unrestricted access increases the opportunity for malicious activity such as unauthorized access, denial-of-service attacks, and loss of data.","report_fields":["LoadBalancerArn"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n1. Sign in to the AWS Admin Console and access the Amazon EC2 at https://console.aws.amazon.com/ec2/v2.\n2. Under the 'Load Balancing' click on the 'Load Balancers'.\n3. Create Network, Gateway or Application load balancer.\n4. Under the 'Scheme' choose 'Internal'.\n5. Create load balancer.","multiregional":false,"service":"Amazon Elastic Load Balancing"},"ecc-aws-367":{"article":"AWS creates a security group and attaches it to your directory\u2019s domain controller elastic network interfaces. This security group blocks unnecessary traffic to the domain controller and allows traffic that is necessary for Active Directory communications. AWS configures the security group to open only the ports that are required for Active Directory communications. In the default configuration, the security group accepts traffic to these ports from any IP address.\nIf you want to increase the security of your directories\u2019 security groups, you can modify them to accept traffic from a more restrictive list of IP addresses. For example, you could change the accepted addresses from 0.0.0.0/0 to a CIDR range that is specific to a single subnet or computer. Make such changes only if you fully understand how security group filtering works. Improper changes can result in loss of communications to intended computers and instances. AWS recommends that you do not attempt to open additional ports to the domain controller as this decreases the security of your directory.","impact":"Unrestricted access increases the opportunity for malicious activity such as unauthorized access, denial-of-service attacks, and loss of data.","report_fields":["DirectoryId"],"remediation":"To edit a security group of directories: \n1. Go to https://console.aws.amazon.com/directoryservicev2.\n2. Click on a Directory which Security Group you want to edit.\n3. Note the VPC ID.\n4. Go to https://console.aws.amazon.com/ec2/v2/home.\n5. In the left panel, select Security Groups.\n6. Filter security groups based on VPC ID.\n7. Click on a security group that ends on \"_controllers\".\n8. Edit all inbound rules that have Source \"0.0.0.0/0\" or \"::/0\".","multiregional":false,"service":"AWS Directory"},"ecc-aws-282":{"article":"Amazon Elasticsearch Service allows you to configure your domains to require that all traffic be submitted over HTTPS. This ensures communications between your clients and your domain are encrypted. ElasticSearch domains should be configured to enforce HTTPS connections for all clients to ensure encryption of data in transit.","impact":"Without enforcing HTTPS connection, Elasticsearch Service accepts a connection whether it uses SSL or not. This can lead to malicious activity such as man-in-the-middle attacks (MITM), intercepting, or manipulating network traffic.","report_fields":["ARN"],"remediation":"To change the policy using the AWS Console, follow these steps:\n1. Open the Amazon Elasticsearch console at https://console.aws.amazon.com/esv3.\n2. Open a domain tab.\n3. Choose a domain that you want to edit. Select 'Actions' and 'Edit security configuration'.\n4. Select 'Require HTTPS' for all traffic to the domain.\n5. Click Submit.","multiregional":false,"service":"Amazon OpenSearch Service"},"ecc-aws-452":{"article":"Enabling connection draining on Elastic Beanstalk ensures that the load balancer stops sending requests to instances that are de-registering or unhealthy. It keeps the existing connections open. This is particularly useful for instances in Auto Scaling groups to ensure that connections aren't severed abruptly.","impact":"If connection draining is not enabled, Elastic Beanstalk can send requests to instances that are de-registering or unhealthy which results in no connection to Beanstalk.","report_fields":["EnvironmentArn"],"remediation":"1. Login to the AWS Management Console and open the Amazon Elastic Beanstalk console using https://console.aws.amazon.com/elasticbeanstalk/\n2. In the navigation pane, choose Environments, and then choose the name of your environment from the list.\n3. In the navigation pane, choose Configuration.\n4. In the 'Load balancer' category, choose Edit.\n5. Enable 'Connection draining'.\n6. Choose Apply.","multiregional":false,"service":"AWS Elastic Beanstalk"},"ecc-aws-348":{"article":"TLS can be used to help prevent potential attackers from using person-in-the-middle or similar attacks to eavesdrop on or manipulate network traffic. Only encrypted connections over TLS should be allowed. Encrypting data in transit can affect performance. You should test your application with this feature to understand the performance profile and the impact of TLS.","impact":"Without enabled encryption in transit, MSK accept a connection whether it uses TLS or not. This can lead to malicious activity such as man-in-the-middle attacks (MITM), intercepting, or manipulating network traffic.","report_fields":["ClusterArn"],"remediation":"Perform the following steps to enable log delivery for MSK:\n1. Login to the MSK console at https://console.aws.amazon.com/msk/.\n2. Click on the Clusters.\n3. Choose required cluster.\n4. Click on the Actions and 'Edit Security Settings'.\n5. Under the 'Encrypt data in transit' choose only 'TLS encryption'.\n6. Save changes.","multiregional":false,"service":"Amazon Managed Streaming for Apache Kafka"},"ecc-aws-019":{"article":"IAM password policies can prevent the reuse of a given password by the same user. It is recommended that the password policy prevent the reuse of passwords. Preventing the password reuse increases account resiliency against brute force login attempts.","impact":"Using the same password can increase the risk of a brute force attack, dictionary-based password attack, etc.","report_fields":["account_id","account_name"],"remediation":"1. Login to the AWS Console (with appropriate permissions to View Identity Access Management Account Settings). \n2. Go to IAM Service on the AWS Console.\n3. Click on Account Settings in the Left Pane.\n4. Check 'Prevent password reuse'.\n5. Set 'Number of passwords to remember' to 24.","multiregional":true,"service":"AWS Account"},"ecc-aws-301":{"article":"SQS allows users to specify a redrive policy for a particular queue. This policy describes what to do when a message fails to be consumed from a queue, including how many times to try to consume the message. It also allows you to specify a Dead Letter Queue, a queue that the message is sent to after it fails too many times. Specifying a redrive policy allows you to avoid \u201cpoison pills\u201d (messages that cannot be handled and will clog up your consumer) and provides a place for failed messages to be stored for further inspection.","impact":"Messages crucial to your applications pipeline may be discarded, causing loss of data. Bugs in code that produce failing messages may remain undetected.","report_fields":["QueueArn"],"remediation":"To create SQS Queue:\n1. Open the Amazon SQS console at https://console.aws.amazon.com/sqs/.\n2. In the navigation pane, choose Queues.\n3. Create new queue.\n4. Scroll to the \"Redrive allow policy\" and enable it.\n5. Choose required \"Redrive permission\".\n6. Create Queue.\n\nTo configure a dead-letter queue:\n1. Open the Amazon SQS console at https://console.aws.amazon.com/sqs/.\n2. In the navigation pane, choose Queues.\n3. Choose a queue and choose Edit.\n4. Scroll to the Dead-letter queue section and choose Enabled.\n5. Choose the Amazon Resource Name (ARN) of an existing Dead Letter Queue that you want to associate with this source queue.\n6. To configure the number of times that a message can be received before being sent to a dead-letter queue, set Maximum receives to a value between 1 and 1,000.\n7. When you finish configuring the dead-letter queue, choose Save.","multiregional":false,"service":"Amazon Simple Queue Service"},"ecc-aws-450":{"article":"Instance metadata is used to configure or manage the running instance. The IMDS provides access to temporary, frequently rotated credentials. These credentials remove the need to hard code or distribute sensitive credentials to instances manually or programmatically.\nVersion 2 of the IMDS adds new protections for the following types of vulnerabilities. These vulnerabilities could be used to try to access the IMDS:\n - Open website application firewalls;\n - Open reverse proxies;\n - Server-side request forgery (SSRF) vulnerabilities;\n - Open Layer 3 firewalls and network address translation (NAT).","impact":"Instances that use IMDSv1 are exposed to the following vulnerabilities:\n- Open website application firewalls;\n- Open reverse proxies;\n- Server-side request forgery (SSRF) vulnerabilities;\n- Open Layer 3 firewalls and network address translation (NAT).","report_fields":["EnvironmentArn"],"remediation":"1. Login to the AWS Management Console and open the Amazon Elastic Beanstalk console using https://console.aws.amazon.com/elasticbeanstalk/\n2. In the navigation pane, choose Environments, and then choose the name of your environment from the list.\n3. In the navigation pane, choose Configuration.\n4. In the Instances configuration category, choose Edit.\n5. Set Disable IMDSv1 to enforce IMDSv2. Clear Disable IMDSv1 to enable both IMDSv1 and IMDSv2.\n6. Choose Apply.","multiregional":false,"service":"AWS Elastic Beanstalk"},"ecc-aws-024":{"article":"Implement a mechanism to encrypt and decrypt electronic protected health information.","impact":"Disabled encryption allows unauthorized or anonymous users to gain access to messages containing sensitive data.","report_fields":["QueueArn"],"remediation":"1. Navigate to https://console.aws.amazon.com/sqs/. \n2. Choose Queues.\n3. Click on the reported Queue.\n4. Press Edit.\n5. Under Encryption, enable Server-side encryption.","multiregional":false,"service":"Amazon Simple Queue Service"},"ecc-aws-097":{"article":"Real-time monitoring of API calls can be achieved by directing CloudTrail Logs to CloudWatch Logs and establishing corresponding metric filters and alarms. NACLs are used as a stateless packet filter to control ingress and egress traffic for subnets within a VPC. It is recommended that a metric filter and alarm be established for detecting changes made to NACLs.","impact":"Lack of monitoring and logging of Network Access Control Lists (NACL) changes can result in insufficient response time to detect accidental or intentional modifications that may lead to unrestricted or unauthorized access.","report_fields":["account_id","account_name"],"remediation":"Perform the following to setup the metric filter, alarm, SNS topic, subscription and trail using the AWS CLI: \n1. Create a log group.\naws logs create-log-group --log-group-name \n2. Create a log stream.\naws logs create-log-stream --log-group-name --log-stream-name \n3. Create a metric filter based on provided filter pattern which checks for unauthorized API calls and the taken from step 1.\naws logs put-metric-filter --log-group-name --filter-name `` --metric-transformations metricName=``,metricNamespace=,metricValue='1' --filter-pattern '{{ ($.eventName = CreateNetworkAcl) || ($.eventName = CreateNetworkAclEntry) || ($.eventName = DeleteNetworkAcl) || ($.eventName = DeleteNetworkAclEntry) || ($.eventName = ReplaceNetworkAclEntry) || ($.eventName = ReplaceNetworkAclAssociation) }}'\n4. Create a topic to which notifications will be published.\naws sns create-topic --name \n5. Subscribe an endpoint to an Amazon SNS topic. If the endpoint type is HTTP/S or email, or if the endpoint and the topic are not in the same Amazon Web Services account, the endpoint owner must run the ConfirmSubscription action to confirm the subscription.\naws sns subscribe --topic-arn --protocol email --notification-endpoint \n6. Amazon SNS will send a subscription confirmation message to the endpoint. Confirm subscription to topic, by visiting the link in an email that you specified as notification endpoint.\n7. Create an alarm that is associated with the CloudWatch Logs Metric Filter created in step 3 and an SNS topic created in step 4. \naws cloudwatch put-metric-alarm --alarm-name `` --metric-name `` --statistic Sum --period 300 --threshold 1 --comparison-operator GreaterThanOrEqualToThreshold --evaluation-periods 1 --namespace --alarm-actions \n8. Create an S3 bucket to deliver log files to:\naws s3api create-bucket --bucket \n9. To deliver log files to an S3 bucket, CloudTrail must have the required permissions. The following policy allows CloudTrail to write log files to the bucket from supported regions. Replace myBucketName, [optionalPrefix]/, myAccountID, region, and trailName with the appropriate values for your configuration. \nCreate a file 'policy.json' with the following policy.\n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"AWSCloudTrailAclCheck20150319\",\n \"Effect\": \"Allow\",\n \"Principal\": {\"Service\": \"cloudtrail.amazonaws.com\"},\n \"Action\": \"s3:GetBucketAcl\",\n \"Resource\": \"arn:aws:s3:::\"\n },\n {\n \"Sid\": \"AWSCloudTrailWrite20150319\",\n \"Effect\": \"Allow\",\n \"Principal\": {\"Service\": \"cloudtrail.amazonaws.com\"},\n \"Action\": \"s3:PutObject\",\n \"Resource\": \"arn:aws:s3:::/[optionalPrefix]/AWSLogs//*\",\n \"Condition\": {\n \"StringEquals\": {\n \"s3:x-amz-acl\": \"bucket-owner-full-control\",\n \"aws:SourceArn\": \"arn:aws:cloudtrail:::trail/\"\n }\n }\n }\n ]\n}}\n10. Apply the Amazon S3 bucket policy to the Amazon S3 bucket created in step 8.\naws s3api put-bucket-policy --bucket --policy file://.json\n11. Create a role for CloudTrail that enables it to send events to the CloudWatch Logs log group. To create the JSON file that will contain the policy document, open a text editor and save the following policy contents in a file called 'assume_role_policy_document.json'. \n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Principal\": {\n \"Service\": \"cloudtrail.amazonaws.com\"\n },\n \"Action\": \"sts:AssumeRole\"\n }\n ]\n}}\n12. Create a role.\naws iam create-role --role-name --assume-role-policy-document file://.json\n13. Create the following role policy document for CloudTrail. This document grants CloudTrail the permissions required to create a CloudWatch Logs log stream in the log group you specify and to deliver CloudTrail events to that log stream. Save the policy document in a file called role-policy-document.json. Replace region, accountID, log_group_name, with the appropriate values for your configuration. \n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n\n \"Sid\": \"AWSCloudTrailCreateLogStream2014110\",\n \"Effect\": \"Allow\",\n \"Action\": [\n \"logs:CreateLogStream\"\n ],\n \"Resource\": [\n \"arn:aws:logs:::log-group::log-stream:_CloudTrail_*\"\n ]\n\n },\n {\n \"Sid\": \"AWSCloudTrailPutLogEvents20141101\",\n \"Effect\": \"Allow\",\n \"Action\": [\n \"logs:PutLogEvents\"\n ],\n \"Resource\": [\n \"arn:aws:logs:::log-group::log-stream:_CloudTrail_*\"\n ]\n }\n ]\n}}\n14. Run the following command to apply the policy to the role.\naws iam put-role-policy --role-name --policy-name cloudtrail-policy --policy-document file://.json\n15. Create a trail that specifies the settings for delivery of log data to an Amazon S3 bucket at is associated with the S3 bucket created in step 8, CloudWatch log group created in step 1 and IAM role created in step 12. \naws cloudtrail create-trail --include-global-service-events --is-multi-region-trail --name --s3-bucket-name --cloud-watch-logs-log-group-arn --cloud-watch-logs-role-arn \n16. Start the recording of Amazon Web Services API calls and log file delivery for a trail. For a trail that is enabled in all regions, this operation must be called from the region in which the trail was created.\naws cloudtrail start-logging --name ","multiregional":false,"service":"AWS Account"},"ecc-aws-103":{"article":"Custom SSL certificates give you full control over your CloudFront content. Custom certificates allow users to access content by using an alternate domain name. You can store custom certificates in AWS Certificate Manager (ACM) or in IAM. \nIt is recommended to use a custom SSL Certificate to access CloudFront content to have more control over your data.","impact":"Without custom SSL certificates, you will not be able to deliver content over HTTPS using your own domain name, you will be able to use only CloudFront distribution domain name.","report_fields":["ARN"],"remediation":"1. Sign in to the AWS console \n2. Select the region from the drop-down list in which the issue has occured \n3. Navigate to the CloudFront Distributions Dashboard \n4. Click the reported distribution \n5. On the 'General' tab, click on the 'Edit' button \n6. On the 'Edit Distribution' page set 'SSL Certificate' to 'Custom SSL Certificate (example.com)', select a certificate or type your certificate ARN and other parameters as per your requirements in their respective fields.\n7. Click on 'Yes', then click on 'Edit'","multiregional":true,"service":"Amazon CloudFront"},"ecc-aws-424":{"article":"This policy identifies the MQ brokers that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["BrokerArn"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon MQ at https://console.aws.amazon.com/amazon-mq.\n2. Click on the required broker.\n3. Under the 'Tags' click on the 'Create tag'.\n4. Click 'Add new tag' and fill the form.\n5. Save changes.","multiregional":false,"service":"Amazon MQ"},"ecc-aws-043":{"article":"The information lifecycle includes information creation, collection, use, processing, storage, maintenance, dissemination, disclosure, disposition. With S3 Lifecycle configuration rules, you can tell Amazon S3 to move objects to less expensive storage classes or archive or delete them. Defining your data lifecycle policies to perform automated storage class migration and deletion will reduce the overall storage costs during its lifetime.\nSetting up an S3 bucket lifecycle policy, will help to cover the \"COST04-BP05 Enforce data retention policies\" best practice from the 'Cost Optimization Pillar' of the 'AWS Well-Architected Framework'.","impact":"Without the S3 lifecycle configuration, you are missing out on the opportunity to manage S3 objects so that they are stored cost-effectively throughout their lifecycle by moving data to more economical storage classes over time or expiring data based on the object age.","report_fields":["Name"],"remediation":"1. Login to the AWS Console. \n2. Find the S3 service. \n3. Choose your S3 bucket. \n4. Click on the Management tab. \n5. Click on the Lifecycle button and add the lifecycle rule. \n6. Follow instructions and add the rule according to your bucket content and mission. \n7. Save it.","multiregional":true,"service":"Amazon S3"},"ecc-aws-051":{"article":"IAM password policies can require passwords to be rotated or expired after a given number of days. It is recommended that the password policy expire passwords after 90 days or less.","impact":"Not reducing the password lifetime decreases account resiliency against brute-force login attempts.","report_fields":["account_id","account_name"],"remediation":"1. Login to the AWS Console (with appropriate permissions to View Identity Access Management Account Settings). \n2. Go to IAM Service on the AWS Console.\n3. Click on Account Settings in the Left Pane. \n4. Check 'Enable password expiration'.\n5. Set 'Password expiration period (in days)' to 90 or less.","multiregional":true,"service":"AWS Account"},"ecc-aws-080":{"article":"Real-time monitoring of API calls can be achieved by directing CloudTrail Logs to CloudWatch Logs and establishing corresponding metric filters and alarms. It is recommended that a metric filter and alarm be established for detecting changes to CloudTrail's configurations. Monitoring changes to CloudTrail's configuration will help ensure sustained visibility to activities performed in the AWS account.","impact":"Lack of monitoring and logging of CloudTrail changes calls can lead to insufficient response time to accidental or intentional changes. In turn, it may lead to disabling the monitoring of all actions taken by a user, role, or AWS service.","report_fields":["account_id","account_name"],"remediation":"Perform the following to setup the metric filter, alarm, SNS topic, subscription and trail using the AWS CLI: \n1. Create a log group.\naws logs create-log-group --log-group-name \n2. Create a log stream.\naws logs create-log-stream --log-group-name --log-stream-name \n3. Create a metric filter based on provided filter pattern which checks for unauthorized API calls and the taken from step 1.\naws logs put-metric-filter --log-group-name --filter-name `` --metric-transformations metricName=``,metricNamespace=,metricValue='1' --filter-pattern '{{($.eventName = CreateTrail) || ($.eventName = UpdateTrail) || ($.eventName = DeleteTrail) || ($.eventName = StartLogging) || ($.eventName = StopLogging) }}'\n4. Create a topic to which notifications will be published.\naws sns create-topic --name \n5. Subscribe an endpoint to an Amazon SNS topic. If the endpoint type is HTTP/S or email, or if the endpoint and the topic are not in the same Amazon Web Services account, the endpoint owner must run the ConfirmSubscription action to confirm the subscription.\naws sns subscribe --topic-arn --protocol email --notification-endpoint \n6. Amazon SNS will send a subscription confirmation message to the endpoint. Confirm subscription to topic, by visiting the link in an email that you specified as notification endpoint.\n7. Create an alarm that is associated with the CloudWatch Logs Metric Filter created in step 3 and an SNS topic created in step 4. \naws cloudwatch put-metric-alarm --alarm-name `` --metric-name `` --statistic Sum --period 300 --threshold 1 --comparison-operator GreaterThanOrEqualToThreshold --evaluation-periods 1 --namespace --alarm-actions \n8. Create an S3 bucket to deliver log files to:\naws s3api create-bucket --bucket \n9. To deliver log files to an S3 bucket, CloudTrail must have the required permissions. The following policy allows CloudTrail to write log files to the bucket from supported regions. Replace myBucketName, [optionalPrefix]/, myAccountID, region, and trailName with the appropriate values for your configuration. \nCreate a file 'policy.json' with the following policy.\n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"AWSCloudTrailAclCheck20150319\",\n \"Effect\": \"Allow\",\n \"Principal\": {\"Service\": \"cloudtrail.amazonaws.com\"},\n \"Action\": \"s3:GetBucketAcl\",\n \"Resource\": \"arn:aws:s3:::\"\n },\n {\n \"Sid\": \"AWSCloudTrailWrite20150319\",\n \"Effect\": \"Allow\",\n \"Principal\": {\"Service\": \"cloudtrail.amazonaws.com\"},\n \"Action\": \"s3:PutObject\",\n \"Resource\": \"arn:aws:s3:::/[optionalPrefix]/AWSLogs//*\",\n \"Condition\": {\n \"StringEquals\": {\n \"s3:x-amz-acl\": \"bucket-owner-full-control\",\n \"aws:SourceArn\": \"arn:aws:cloudtrail:::trail/\"\n }\n }\n }\n ]\n}}\n10. Apply the Amazon S3 bucket policy to the Amazon S3 bucket created in step 8.\naws s3api put-bucket-policy --bucket --policy file://.json\n11. Create a role for CloudTrail that enables it to send events to the CloudWatch Logs log group. To create the JSON file that will contain the policy document, open a text editor and save the following policy contents in a file called 'assume_role_policy_document.json'. \n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Principal\": {\n \"Service\": \"cloudtrail.amazonaws.com\"\n },\n \"Action\": \"sts:AssumeRole\"\n }\n ]\n}}\n12. Create a role.\naws iam create-role --role-name --assume-role-policy-document file://.json\n13. Create the following role policy document for CloudTrail. This document grants CloudTrail the permissions required to create a CloudWatch Logs log stream in the log group you specify and to deliver CloudTrail events to that log stream. Save the policy document in a file called role-policy-document.json. Replace region, accountID, log_group_name, with the appropriate values for your configuration. \n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n\n \"Sid\": \"AWSCloudTrailCreateLogStream2014110\",\n \"Effect\": \"Allow\",\n \"Action\": [\n \"logs:CreateLogStream\"\n ],\n \"Resource\": [\n \"arn:aws:logs:::log-group::log-stream:_CloudTrail_*\"\n ]\n\n },\n {\n \"Sid\": \"AWSCloudTrailPutLogEvents20141101\",\n \"Effect\": \"Allow\",\n \"Action\": [\n \"logs:PutLogEvents\"\n ],\n \"Resource\": [\n \"arn:aws:logs:::log-group::log-stream:_CloudTrail_*\"\n ]\n }\n ]\n}}\n14. Run the following command to apply the policy to the role.\naws iam put-role-policy --role-name --policy-name cloudtrail-policy --policy-document file://.json\n15. Create a trail that specifies the settings for delivery of log data to an Amazon S3 bucket at is associated with the S3 bucket created in step 8, CloudWatch log group created in step 1 and IAM role created in step 12. \naws cloudtrail create-trail --include-global-service-events --is-multi-region-trail --name --s3-bucket-name --cloud-watch-logs-log-group-arn --cloud-watch-logs-role-arn \n16. Start the recording of Amazon Web Services API calls and log file delivery for a trail. For a trail that is enabled in all regions, this operation must be called from the region in which the trail was created.\naws cloudtrail start-logging --name ","multiregional":false,"service":"AWS Account"},"ecc-aws-468":{"article":"To help you manage your file systems and other Amazon FSx resources, you can assign your own metadata to each resource in the form of tags. Tags enable you to categorize AWS resources in different ways, for example, by purpose, owner, or environment. This is useful when you have many resources of the same type \u2014 you can quickly identify a specific resource based on the tags that you've assigned to it. This topic describes tags and shows you how to create them.\nAmazon FSx can copy tags of the volume to snapshots. If this parameter is set to true, all tags for the volume are copied to snapshots where the user doesn't specify tags. If this value is true and you specify one or more tags, only the specified tags are copied to snapshots. If you specify one or more tags when creating the snapshot, no tags are copied from the volume, regardless of this value.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["ResourceARN"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nYou can create an FSx for OpenZFS file system using the Amazon FSx console:\n1. Open the Amazon FSx console at https://console.aws.amazon.com/fsx/.\n2. On the dashboard, choose 'Create file system' to start the file system creation wizard.\n3. On the 'Select file system type' page, choose 'FSx for OpenZFS', and then choose 'Next'. The 'Create file system' page appears.\n4. For 'Creation method', choose 'Standard create'. Begin your configuration with the File system details section.\n5. For 'File system name - optional', enter a name for your file system.\n6. For 'Storage capacity', enter the storage capacity of your file system, in GiB. Enter any whole number in the range of 64\u2013524288.\n7. For 'Provisioned SSD IOPS', you have two options to provision the number of IOPS for your file system:\n - Choose 'Automatic' (the default) if you want Amazon FSx to automatically provision 3 IOPS per GB of SSD storage.\n - Choose 'User-provisioned' if you want to specify the number of IOPS.\n8. For 'Throughput capacity', you have two options to provide your desired throughput capacity in MB per second (MBps).\n - Choose 'Recommended throughput capacity' (the default) if you want Amazon FSx to automatically choose the throughput capacity. \n - Choose 'Specify throughput capacity' if you want to specify the throughput capacity value. \n9. In the 'Network & security' section, provide networking and security group information:\n For 'Virtual Private Cloud (VPC)', choose the Amazon VPC that you want to associate with your file system.\n For 'VPC Security Groups', the ID for the default security group for your VPC should be already added.\n For 'Subnet', choose any value from the list of available subnets.\n10. In the 'Encryption' section, for 'Encryption key', choose the AWS Key Management Service (AWS KMS) encryption key that protects your file system's data at rest.\n11. In Root volume configuration, you can set the following options for the file system's root volume:\n For 'Data compression type', choose the type of compression to use for your volume, either 'Z-Standard', 'LZ4', or 'No compression'. Z-Standard compression provides more data compression and higher read throughput than LZ4 compression. LZ4 compression provides less compression and higher write throughput performance than Z-Standard compression. \n For 'Copy tags to snapshots', enable the option to copy tags to the volume's snapshot.\n For 'NFS exports', there is a default client configuration setting which you can modify or remove. \n To provide additional client configurations:\n a. In the 'Client addresses' field, specify which clients can access the volume. Enter an asterisk (*) for any client, a specific IP address, or a CIDR range of IP addresses.\n b. In the 'NFS options' field, enter a comma-delimited set of exports options.\n c. Choose 'Add client configuration'.\n d. Repeat the procedure to add another client configuration.\n For 'Record size', choose whether to use the default suggested record size of 128 KiB, or to set a custom suggested record size for the volume. \n For 'User and group quotas', you can set a storage quota for a user or group:\n a. For Quota type, choose USER or GROUP.\n b. For User or group ID, choose a number that is the ID of the user or group.\n c. For Usage quota, choose a number that is the storage quota of the user or group.\n d. Choose Add quota.\n e. Repeat the procedure to add a quota for another user or group.\n12. In 'Backup and maintenance - optional', you can set the following options:\n For 'Daily automatic backup', choose 'Enabled' for automatic daily backups. This option is enabled by default.\n For 'Daily automatic backup window', set the time of the day in Coordinated Universal Time (UTC) that you want the daily automatic backup window to start.\n For 'Automatic backup retention period', set a period from 1\u201390 days that you want to retain automatic backups.\n For 'Weekly maintenance window', you can set the time of the week that you want the maintenance window to start. \n13. For 'Tags - optional', you can enter a key and value to add tags to your file system. A tag is a case-sensitive key-value pair that helps you manage, filter, and search for your file system.\n14. Choose 'Next'.\nReview the file system configuration shown on the 'Create file system' page. For your reference, note which file system settings you can modify after the file system is created.\nChoose 'Create file system'.","multiregional":false,"service":"Amazon FSx"},"ecc-aws-434":{"article":"Using the latest version of MQ broker, you adhere to AWS best practices and receive the newest Elasticsearch features, benefit from better performance and security and get the latest bug fixes.","impact":"Without keeping the MQ up-to-date, it is possible to miss out on new software features, bug fixes, security patches, and performance improvements.","report_fields":["BrokerArn"],"remediation":"1. Sign in to the AWS Management Console.\n2. Navigate to the Amazon MQ dashboard at https://console.aws.amazon.com/amazon-mq/.\n3. Click on the required broker.\n4. Click on the 'Edit'.\n5. Save changes.","multiregional":false,"service":"Amazon MQ"},"ecc-aws-331":{"article":"WorkSpaces images require Operating system patches to be applied and updated and by confirming the creation date is not over 90 days old can help ensure that updates are being applied.","impact":"WorkSpaces images that have been created more than 90 days ago, pose a security vulnerability because they have not been actively maintained (analyzed, patched, updated).","report_fields":["ImageId"],"remediation":"To create a custom image: \n1. If you are still connected to the WorkSpace, disconnect by choosing Amazon Workspaces and Disconnect in the WorkSpaces client application.\n2. Open the WorkSpaces console at https://console.aws.amazon.com/workspaces/.\n3. In the navigation pane, choose \"WorkSpaces\".\n4. Select the WorkSpace and choose \"Actions\", \"Create Image\". If the status of the WorkSpace is STOPPED, you must start it first (choose \"Actions\", \"Start WorkSpaces\") before you can choose \"Actions\", \"Create Image\".\n5. A message displays, prompting you to reboot (restart) your WorkSpace before continuing. Rebooting your WorkSpace updates your Amazon WorkSpaces software to the latest version.\nReboot your WorkSpace and repeat Step 4 of this procedure, but this time choose Next when the reboot message appears. To create an image, the status of the WorkSpace must be AVAILABLE and its modification state must be None.\n6. Enter an image name and a description that will help you identify the image, and then choose \"Create Image\". While the image is being created, the status of the WorkSpace is SUSPENDED and the WorkSpace is unavailable.\n7. In the navigation pane, choose \"Images\". The image is complete when the status of the WorkSpace changes to \"Available\" (this can take up to 45 minutes).","multiregional":false,"service":"Amazon WorkSpaces Family"},"ecc-aws-487":{"article":"If you do not specify a Customer managed key when creating your environment, Amazon CodePipeline uses its AWS owned key for data encryption on your environment.\nWhen you create and use your own KMS CMK customer-managed keys to protect the data on your environment, you obtain full control over who can use the CMK keys and access the encrypted data. The AWS KMS service allows you to create, rotate, disable, enable, and audit your Customer Master Keys (CMKs).\nIn addition, if you provide a Customer managed key, you must attach the policy statement for CloudWatch access. You must also create the Customer managed key-specific execution role.","impact":"Without a KMS CMK customer-managed key, you do not have full and granular control over who can access key that is used for encryption. If you are using the default S3 key, you cannot change or delete this AWS managed key. If you are using a customer managed key in AWS KMS to encrypt or decrypt artifacts in the S3 bucket, you can change or rotate this customer managed key as necessary.","report_fields":["name","roleArn"],"remediation":"Use AWS CLI to update encryption configuration:\naws codepipeline update-pipeline --cli-input-json file://test.json\nInside json file choose S3 as 'artifactStore'.\nInside 'encryptionKey' provide 'id' for KMS CMK key and in 'type' choose 'KMS'.","multiregional":false,"service":"AWS CodePipeline"},"ecc-aws-484":{"article":"You can configure a deployment group to automatically roll back when a deployment fails or when a monitoring threshold you specify is met. In this case, the last known good version of an application revision is deployed. \nYou can create a CloudWatch alarm that watches a single metric over a time period you specify and performs one or more actions based on the value of the metric relative to a given threshold over a number of time periods. For an Amazon EC2 deployment, you can create an alarm for an instance or Amazon EC2 Auto Scaling group that you are using in your CodeDeploy operations. For an AWS Lambda and an Amazon ECS deployment, you can create an alarm for errors in a Lambda function.\nYou can configure a deployment to stop when an Amazon CloudWatch alarm detects that a metric has fallen below or exceeded a defined threshold.","impact":"Without Automatic rollback option enabled, the failed deployment won't roll back to the most recent successfully deployed application revision or it won't roll back when alarm thresholds are met.","report_fields":["applicationName","deploymentGroupName","deploymentGroupId"],"remediation":"Before beginning to edit Deployment groups, you must have already created the alarm in CloudWatch before you can add it to a deployment group.\nTo enable Automatic rollbacks and CloudWatch alarms:\n1. Navigate to CodeDeploy Applications https://console.aws.amazon.com/codesuite/codedeploy/applications\n2. Select application to which you need to configure Automatic rollback and CloudWatch alarms.\n3. Select 'Deployment groups' tab.\n4. Select deployment group that you want to modify.\n5. Click 'Edit' at top right corner.\n6. Click 'Advanced - optional'.\n7. In 'Alarms' section click 'Add alarm'.\n8. In 'Create deployment alarm' window, select necessary alarms that you need to add. \n9. Click 'Add alarm'.\n10. Check that 'Ignore alarm configuration' is unchecked.\n11. (Optional) If you want deployments to proceed in the event that CodeDeploy is unable to retrieve alarm status from Amazon CloudWatch, choose 'Continue deployments even if alarm status is unavailable'.\n12. In 'Rollbacks' section, uncheck 'Disable rollbacks'.\n13. Choose one or both of the following:\n a. 'Roll back when a deployment fails'. CodeDeploy will redeploy the last known good revision as a new deployment.\n b. 'Roll back when alarm thresholds are met'. If you added an alarm to this application in the previous step, CodeDeploy will redeploy the last known good revision when one or more of the specified alarms is activated.\n14. Click 'Save changes'.","multiregional":false,"service":"AWS CodeDeploy"},"ecc-aws-389":{"article":"This policy identifies the Transit gateway attachment that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["TransitGatewayAttachmentId"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon VPC at https://console.aws.amazon.com/vpc/.\n2. Click on the 'Transit gateway attachments'.\n3. Click on the required Transit gateway attachment.\n4. Open 'Tags' and click on the 'Manage tags'.\n5. Add new tag and save.","multiregional":false,"service":"AWS Transit Gateway"},"ecc-aws-071":{"article":"Authentication credentials should never be stored or transmitted in clear text or appear in the repository URL. Instead of personal access tokens or user name and password, you should use OAuth to grant authorization for accessing GitHub or Bitbucket repositories. Using personal access tokens or a user name and password could expose your credentials to unintended data exposure and unauthorized access.","impact":"Using personal access tokens or a user name and password could expose your credentials and lead to unauthorized access to your data.","report_fields":["arn"],"remediation":"1. Open the CodeBuild console at https://console.aws.amazon.com/codebuild/.\n2. Select your Build project that contains personal access tokens or a user name and password. \n3. From Edit, choose Source. \n4. Choose Disconnect from GitHub / Bitbucket. \n5. Choose Connect using OAuth and then choose Connect to GitHub / Bitbucket.\n6. In the message displayed by your source provider, authorize as appropriate.\n7. Reconfigure your Repository URL and additional configuration settings as needed.\n8. Choose Update source.","multiregional":false,"service":"AWS CodeBuild"},"ecc-aws-410":{"article":"This policy identifies the ElasticSearch clusters that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["ARN"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon ElasticSearch at https://console.aws.amazon.com/esv3.\n2. Click on the required domain.\n3. Open 'Tags' and click on the 'Manage tags'.\n4. Add new tag and save.","multiregional":false,"service":"Amazon OpenSearch Service"},"ecc-aws-047":{"article":"Password policies are, in part, used to enforce password complexity requirements. IAM password policies can be used to ensure passwords are comprised of different character sets. It is recommended that the password policy require at least one lowercase letter.","impact":"Not using a password policy with at least one lowercase letter reduces security and account resiliency against brute-force attacks.","report_fields":["account_id","account_name"],"remediation":"1. Login to the AWS Console (with appropriate permissions to View Identity Access Management Account Settings). \n2. Go to IAM Service on the AWS Console.\n3. Click on Account Settings in the Left Pane. \n4. Check 'Requires at least one lowercase letter'.\n5. Click on 'Apply password policy'.","multiregional":true,"service":"AWS Account"},"ecc-aws-532":{"article":"Ensure that certificates stored in AWS ACM are renewed before expiration.\nACM can automatically renew certificates that use DNS validation. For certificates that use email validation, you must respond to a domain validation email. ACM does not automatically renew certificates that you import. You must renew imported certificates manually.","impact":"Certificates that are not renewed prior to their expiration date become invalid, and the communication between a client and an AWS resource that implements the certificates is no longer secure. And it can break applications and reduce customer trust. Furthermore, manually accepting expired certificates as valid can mask other security risks which can lead to phishing, MITM and other identity spoofing attacks.","report_fields":["CertificateArn","DomainName"],"remediation":"For ACM certificates with DNS validation renewal is fully automated, you don't need to take any action.\n\nFor ACM certificates with email validation, you must respond to a domain validation email. \nTo be renewed, email-validated certificates require an action by the domain owner. ACM begins sending renewal notices 45 days before expiration, using the domain's WHOIS mailbox addresses and to five common administrator addresses. The notifications contain a link that the domain owner can click for easy renewal. Once all listed domains are validated, ACM issues a renewed certificate with the same ARN.\n\nFor ACM certificates that you import, ACM does not automatically renew certificates.\nIf you imported a certificate and associated it with other AWS services, you can reimport that certificate before it expires while preserving the AWS service associations of the original certificate. \nThe following conditions apply when you reimport a certificate:\n - You can add or remove domain names.\n - You cannot remove all of the domain names from a certificate.\n - If 'Key Usage' extensions are present in the originally imported certificate, you can add new extension values, but you cannot remove existing values.\n - If 'Extended Key Usage' extensions are present in the originally imported certificate, you can add new extension values, but you cannot remove existing values.\n - The key type and size cannot be changed.\n - You cannot apply resource tags when reimporting a certificate.\n\nThe following example shows how to reimport a certificate using the AWS Management Console.\n1. Open the ACM console at https://console.aws.amazon.com/acm/home.\n2. Select or expand the certificate to reimport.\n3. Open the details pane of the certificate and choose the 'Reimport certificate' button. If you selected the certificate by checking the box beside its name, choose 'Reimport certificate' on the 'Actions' menu.\n4. For 'Certificate body', paste the PEM-encoded end-entity certificate.\n5. For 'Certificate private key', paste the unencrypted PEM-encoded private key associated with the certificate's public key.\n6. (Optional) For 'Certificate chain', paste the PEM-encoded certificate chain. The certificate chain includes one or more certificates for all intermediate issuing certification authorities, and the root certificate. If the certificate to be imported is self-assigned, no certificate chain is necessary.\n7. Choose 'Review and import'.\n8. Review the information about your certificate. If there are no errors, choose 'Reimport'.","multiregional":false,"service":"AWS Certificate Manager"},"ecc-aws-192":{"article":"Elastic Beanstalk enhanced health reporting enables a more rapid response to changes in the health of the underlying infrastructure. These changes could result in a lack of availability of the application. \nElastic Beanstalk enhanced health reporting provides a status descriptor to gauge the severity of the identified issues and identify possible causes to investigate. The Elastic Beanstalk health agent, included in supported Amazon Machine Images (AMIs), evaluates logs and metrics of environment EC2 instances.","impact":"Elastic Beanstalk enhanced health reporting enables a more rapid response to changes in the health of the underlying infrastructure. These changes could result in a lack of the application availability.","report_fields":["EnvironmentArn"],"remediation":"1. Open the Elastic Beanstalk console at console.aws.amazon.com/elasticbeanstalk/ and in the Regions list, select your AWS Region. \n2. In the navigation pane, choose Environments, and then choose the name of your environment from the list. \n3. In the navigation pane, choose Configuration. \n4. In the Monitoring configuration category, choose Edit. \n5. Under Health reporting, for System, choose Enhanced.\n6. Choose Apply.","multiregional":false,"service":"AWS Elastic Beanstalk"},"ecc-aws-220":{"article":"If a secret was not accessed within 90 days, this secret should be removed. Deleting unused secrets is as important as rotating secrets.\nUnused secrets can be abused by their former users, who no longer need access to these secrets. Also, as more users get access to a secret, someone might have mishandled and leaked it to an unauthorized entity, which increases the risk of abuse. Deleting unused secrets helps revoke secret access from users who no longer need it. It also helps to reduce the cost of using Secrets Manager. Therefore, it is essential to routinely delete unused secrets.","impact":"If secrets are unused for more than 90 days, it increases the likelihood of old secrets being used by their former users who no longer need access to these secrets.","report_fields":["Name","ARN"],"remediation":"1. Open the Secrets Manager console at https://console.aws.amazon.com/secretsmanager/. \n2. To locate the secret, enter the secret name in the search box. \n3. Choose the secret to delete. \n4. Under Secret details, from Actions, choose Delete secret. \n5. Under Schedule secret deletion, enter the number of days to wait before the secret is deleted. \n6. Choose Schedule deletion.","multiregional":false,"service":"AWS Secrets Manager"},"ecc-aws-141":{"article":"To enable HTTPS connections to your website or application in AWS, you need an SSL/TLS server certificate. You can use ACM or IAM to store and deploy server certificates. Use IAM as a certificate manager only when you must support HTTPS connections in a region that is not supported by ACM. IAM securely encrypts your private keys and stores the encrypted version in IAM SSL certificate storage.\nIAM supports deploying server certificates in all regions, but you must obtain your certificate from an external provider for using it with AWS. You cannot upload an ACM certificate to IAM. Additionally, you cannot manage your certificates from the IAM Console.","impact":"Keeping expired SSL/TLS certificates that can be deployed accidentally to a resource such as ELB can damage the credibility of the application/website.","report_fields":["Arn"],"remediation":"Removing expired certificates via the AWS Management Console is not currently supported. To delete SSL/TLS certificates stored in IAM via the AWS API, use the Command Line Interface (CLI).\nFrom Command Line:\nTo delete the expired certificate, run the following command replacing with the name of the certificate to delete:\n'aws iam delete-server-certificate --server-certificate-name '\nWhen the preceding command is successful, it does not return any output.","multiregional":true,"service":"AWS Identity and Access Management"},"ecc-aws-533":{"article":"This policy identifies the Key pairs that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["KeyPairId"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon EC2 at https://console.aws.amazon.com/ec2.\n2. Click on the 'Key pairs'.\n3. Click on the required key pair.\n4. Click 'Actions' and 'Manage tags'.\n5. Add new tag and save.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-018":{"article":"IAM users are granted access to services, functions, and data through IAM policies. \nThere are three ways to define policies for a user: \n1) edit the user policy directly (inline); \n2) attach the policy directly to a user; \n3) add the user to an IAM group that has the policy attached.Only the third implementation is recommended.","impact":"Assigning privileges at the IAM user level increases the complexity of access management as the number of users grows. Increased access management complexity might, in turn, increase the opportunity for a principal to inadvertently receive or retain excessive privileges. Also, assigning policies directly to a user can lead to duplication of information.","report_fields":["Arn"],"remediation":"Perform the following to create an IAM group and assign a policy to it:\n1. Sign in to the AWS Management Console and open the IAM console at https://console.aws.amazon.com/iam/.\n2. In the navigation pane, click Groups and then click Create New Group. \n3. In the Group Name box, type the name of the group and then click Next Step. \n4. In the list of policies, select the check box for each policy that you want to apply to all members of the group. Then click Next Step. \n5. Click Create Group \nPerform the following to add a user to a given group: \n1. Sign in to the AWS Management Console and open the IAM console at https://console.aws.amazon.com/iam/. \n2. In the navigation pane, click Groups. \n3. Select the group to add a user to \n4. Click Add Users To Group.\n5. Select the users to be added to the group. \n6. Click Add Users. \nPerform the following to remove the direct association between a user and a policy: \n1. Sign in to the AWS Management Console and open the IAM console at https//console.aws.amazon.com/iam/.\n2. In the left navigation pane, click on Users. \n3. For each user: \n - Select the user;\n - Click on the Permissions tab;\n - Expand Permissions policies;\n - Click X for each policy; \n - Then click Detach or Remove (depending on the policy type).","multiregional":true,"service":"AWS Identity and Access Management"},"ecc-aws-470":{"article":"The following types of API endpoints are supported:.\n - Edge-optimized API endpoint: The default hostname of an API Gateway API that is deployed to the specified Region while using a CloudFront distribution to facilitate client access typically from across AWS Regions.\n - Private API endpoint: An API endpoint that is exposed through interface VPC endpoints and allows a client to securely access private API resources inside a VPC. Private APIs are isolated from the public internet, and they can only be accessed using VPC endpoints for API Gateway that have been granted access.\n - Regional API endpoint: The host name of an API that is deployed to the specified Region and intended to serve clients, such as EC2 instances, in the same AWS Region. API requests are targeted directly to the Region-specific API Gateway API without going through any CloudFront distribution.","impact":"Not choosing right endpoint type can lead to exposing API Gateway to the internet or slow connection time geographically diverse clients.","report_fields":["id","name"],"remediation":"1. Sign in to the API Gateway console and choose APIs in the primary navigation pane https://console.aws.amazon.com/apigateway.\n2. Under + Create API, choose the API settings (gear icon).\n3. Under Endpoint Configuration, change the 'Endpoint Type' option to required type.\n4. Choose Save to start the update.","multiregional":false,"service":"Amazon API Gateway"},"ecc-aws-346":{"article":"Amazon Route 53 health checks monitor the health and performance of your web applications, web servers, and other resources. After you create a health check, you can get the status of the health check, get notifications when the status changes, and configure DNS failover.","impact":"Without health check configured for Route53 resource record, you are missing out on the opportunity to improve monitoring and response time for any service issues.","report_fields":["c7n:parent-id","Name","Type","SetIdentifier"],"remediation":"The following procedure describes how to create health checks using the Route 53 console.\n1. Sign in to the AWS Management Console and open the Route 53 console at https://console.aws.amazon.com/route53/.\n2. In the navigation pane, choose 'Health Checks'.\n3. To create a health check, choose 'Create health check'. For more information about each setting, move the mouse pointer over a label to see its tooltip.\n4. Enter the applicable values. Note that some values can't be changed after you create a health check.\n5. Choose 'Create health check'.\n Note: Route 53 considers a new health check to be healthy until there's enough data to determine the actual status, healthy or unhealthy. If you chose the option to invert the health check status, Route 53 considers a new health check to be unhealthy until there's enough data.\n6. Associate the health check with one or more Route 53 records.","multiregional":true,"service":"Amazon Route 53"},"ecc-aws-239":{"article":"The log_error_verbosity setting specifies the verbosity (amount of detail) of logged messages. Valid values are: TERSE, DEFAULT, VERBOSE, with each containing the fields of the level above it as well as additional fields. TERSE excludes the logging of DETAIL, HINT, QUERY, and CONTEXT error information. VERBOSE output includes the SQLSTATE, error code, and the source code file name, function name, and line number that generated the error. The appropriate value should be set based on your organization's logging policy.","impact":"If the 'log_error_verbosity' parameter is not set to the correct value, too many details or too few details may be logged.","report_fields":["DBInstanceArn"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/\n2. In the navigation pane, choose Parameter groups.\n3. Choose the required parameter group.\n4. Choose Edit parameters.\n5. Under Parameters, in search bar type 'log_error_verbosity'.\n6. Choose value that must be set.\n7. Choose Save changes.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-060":{"article":"AWS CloudTrail is a web service that records AWS API calls for an account and makes those logs available to users and resources in accordance with IAM policies. AWS Key Management Service (KMS) is a managed service that helps create and control the encryption keys used to encrypt account data, and uses Hardware Security Modules (HSMs) to protect the security of encryption keys.\nCloudTrail logs can be configured to leverage server side encryption (SSE) and KMS customer created master keys (CMK) to further protect CloudTrail logs. \nIt is recommended that CloudTrail be configured to use SSE-KMS.","impact":"Without CloudTrail configured to use SSE-KMS, you do not have full control over who can read the log files in your organization. This can lead to unauthorized access to CloudTrail logs.","report_fields":["TrailARN"],"remediation":"1. Sign in to the AWS Management Console and open the CloudTrail console at https://console.aws.amazon.com/cloudtrail.\n2. In the left navigation pane, choose Trails. \n3. Click on a Trail.\n4. Under the S3 section, click on the edit button (pencil icon).\n5. Click on Advanced.\n6. Select an existing CMK from the KMS key ID drop-down menu.\nNote: Ensure the CMK is located in the same region as the S3 bucket.\nNote: You will need to apply a KMS Key policy on the selected CMK in order for CloudTrail as a service to encrypt and decrypt log files using the CMK provided. Steps are provided here for editing the selected CMK Key policy.\n7. Click on Save. \n8. You will see a notification message stating that you need to have decrypt permissions on the specified KMS key to decrypt log files. \n9. Click Yes.","multiregional":false,"service":"AWS CloudTrail"},"ecc-aws-376":{"article":"To help debug issues related to request execution to API Gateway, you can enable Amazon CloudWatch Logs to log API calls. \nExecution logs contain information that you can use to identify and troubleshoot most API errors.","impact":"With disabled monitoring and logging of API Gateway, it may be difficult to troubleshoot any issues that might happen with APIs.","report_fields":["restApiId","StageName"],"remediation":"Create an IAM role for logging to CloudWatch:\n1. In the AWS Identity and Access Management (IAM) console, in the left navigation pane, choose Roles.\n2. On the Roles pane, choose Create role.\n3. On the Create role page, do the following:\n 3.1 For Trusted entity type, choose AWS Service.\n 3.2 For use case, choose API Gateway.\n 3.3 Choose the API Gateway radio button.\n 3.4 Choose Next.\n4. Under Permissions Policies, note that the AWS managed policy AmazonAPIGatewayPushToCloudWatchLogs is selected by default. The policy has all the required permissions.\n5. Choose Next.\n6. Under Name, review and create, do the following:\n 6.1 For Role name, enter a meaningful name for the role.\n 6.2 (Optional) For Role description, edit the description to your preferences.\n 6.3 (Optional) Add tags.\n 6.4 Choose Create role.\n7. On the Roles pane, in the search bar, enter the name of the role that you created. Then, choose the role from the search results.\n8. On the Summary pane, copy the Role ARN. You'll need this Amazon Resource Name (ARN) in the next section.\n\nAdd the IAM role in the API Gateway console:\nNote: If you're developing multiple APIs across different AWS Regions, complete these steps in each Region.\n1. In the API Gateway console, on the APIs pane, choose the name of an API that you created.\n2. In the left navigation pane, at the bottom, below the Client Certificates section, choose Settings.\n3. Under Settings, for CloudWatch log role ARN, paste the IAM role ARN that you copied.\n4. Choose Save.\nNote: The console doesn't confirm that the ARN is saved.\n\nTurn on logging for your API and stage:\n1. In the API Gateway console, find the Stage Editor for your API.\n2. On the Stage Editor pane, choose the 'Logs/Tracing' tab.\n3. On the 'Logs/Tracing' tab, under 'CloudWatch Settings', do the following to turn on execution logging:\n 3.1 Choose the 'Enable CloudWatch Logs' check box.\n 3.2 For 'Log level', choose INFO to generate execution logs for all requests. Or, choose ERROR to generate execution logs only for requests to your API that result in an error.\n4. Under 'Custom Access Logging', do the following to turn on access logging:\n 4.1 Choose the 'Enable Access Logging' check box.\n 4.2 For 'Access Log Destination ARN', enter the ARN of a CloudWatch log group or an Amazon Kinesis Data Firehose stream.\n 4.3 Enter a Log Format. For guidance, choose CLF, JSON, XML, or CSV to see an example in that format.\n5. Choose Save Changes.\nNote: The console doesn't confirm that settings are saved.","multiregional":false,"service":"Amazon API Gateway"},"ecc-aws-232":{"article":"The log_rotation_size setting determines the maximum size of an individual log file. \nOnce the maximum size is reached, automatic log file rotation will occur. Verify that log_rotation_size is set in compliance with the organization's logging policy.","impact":"When this parameter is disabled. This will prevent automatic log file rotation when files become too large, which could put log data at increased risk of loss (unless age-based rotation is configured).","report_fields":["DBInstanceArn"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/\n2. In the navigation pane, choose Parameter groups.\n3. Choose the required parameter group.\n4. Choose Edit parameters.\n5. Under Parameters, in search bar type \"log_rotation_size\".\n6. Type for example 100000 in value field.\n7. Choose Save changes.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-009":{"article":"Ensure that SSL/TLS certificates stored in AWS IAM are renewed one week before expiry.","impact":"SSL/TLS certificates not renewed prior to their expiration date become invalid and the communication between a client and an AWS resource that implements the certificates is no longer secure.","report_fields":["Arn"],"remediation":"From AWS CLI: \naws iam upload-server-certificate --server-certificate-name ExampleCertificate --certificate-body file://Certificate.pem --certificate-chain file://CertificateChain.pem --private-key file://PrivateKey.pem --tags '{\"Key\": \"ExampleKey\", \"Value\": \"ExampleValue\"}'","multiregional":true,"service":"AWS Identity and Access Management"},"ecc-aws-001":{"article":"Multi-Factor Authentication (MFA) adds an extra layer of authentication assurance beyond traditional credentials. With MFA enabled, when a user signs in to the AWS Console, they will be prompted for their user name and password as well as for an authentication code from their physical or virtual MFA token. It is recommended that MFA be enabled for all accounts that have a console password.","impact":"With MFA disabled for all IAM users who have a console password, the likelihood of a password being cracked and an account being compromised is much higher than with MFA enabled. By requiring MFA, you can reduce incidents of compromised accounts and keep sensitive data from being accessed by unauthorized users.","report_fields":["Arn"],"remediation":"1. Sign in to the AWS Management Console and open the IAM console at https://console.aws.amazon.com/iam/. \n2. In the navigation pane, choose Users. \n3. Choose the name of the intended MFA user from the User Name list. \n4. Choose the Security Credentials tab and then choose Manage MFA Device. \n5. In the Manage MFA Device wizard, choose a virtual MFA device and then choose Continue. IAM generates and displays configuration information for the virtual MFA device, including a QR code graphic. The graphic is a representation of the 'secret configuration key' that is available for manual entry on devices that do not support QR codes. \n6. Open your virtual MFA application. (For a list of apps that you can use for hosting virtual MFA devices, see a list of compatible applications: https://aws.amazon.com/iam/features/mfa/?audit=2019q1). If the virtual MFA application supports multiple accounts (multiple virtual MFA devices), choose the option to create a new account (a new virtual MFA device). \n7. Determine whether the MFA app supports QR codes, and then do one of the following: \n- Use the app to scan the QR code. For example, you might choose either a camera icon or an option similar to Scan code and then use the device's camera to scan the code. \n- In the Manage MFA Device wizard, choose Show secret key for manual configuration, and then type the secret configuration key into your MFA application. When finished, the virtual MFA device starts generating one-time passwords.","multiregional":true,"service":"AWS Identity and Access Management"},"ecc-aws-379":{"article":"This policy identifies the EBS snapshots that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["SnapshotId","OwnerId"],"remediation":"1. Sign in to the AWS Management Console and open the EC2 console at https://console.aws.amazon.com/ec2/v2/.\n2. In the navigation pane under 'Elastic Block Store' choose 'Snapshots'.\n3. Choose required snapshot.\n4. Open 'Tags' and click on 'Manage tags'.\n5. Add a tags.\n6. Save.","multiregional":false,"service":"Amazon Elastic Block Store"},"ecc-aws-283":{"article":"Using the latest version of ElasticSearch engine, you adhere to AWS best practices and receive the newest ElasticSearch features, benefit from better performance and security and get the latest bug fixes.","impact":"Without keeping the OpenSearch up-to-date, it is possible to miss out on new software features, bug fixes, security patches, and performance improvements.","report_fields":["ARN"],"remediation":"1. Sign in to the AWS Management Console and open the Amazon OpenSearch console at https://console.aws.amazon.com/esv3\n 1.1. (Optional) Take a manual snapshot of your domain. This snapshot serves as a backup that you can restore on a new domain if you want to return to using the prior OpenSearch version.\n2. In the navigation pane, under Domains, choose the domain you want to upgrade.\n3. Choose Actions and Upgrade.\n4. Choose the version to upgrade to. If you're upgrading to an OpenSearch version, the Enable compatibility mode option appears. If you enable this setting, OpenSearch reports its version as 7.10 to allow Elasticsearch OSS clients and plugins like Logstash to continue working with Amazon OpenSearch Service.\n5. Choose Upgrade.\n6. Check the Status on the domain dashboard to monitor the status of the upgrade.","multiregional":false,"service":"Amazon OpenSearch Service"},"ecc-aws-216":{"article":"Enabling automatic major version upgrades ensures that the latest major version updates to Amazon Redshift clusters are installed during the maintenance window. These updates might include security patches and bug fixes. Keeping up to date with patch installation is an important step in securing systems.","impact":"With an automatic update disabled, you are missing updates that may contain security patches and bug fixes.","report_fields":["ClusterIdentifier"],"remediation":"To remediate this issue from the AWS CLI, use the Amazon Redshift modify-cluster command to set the --allow-version-upgrade attribute:\naws redshift modify-cluster --cluster-identifier clustername --allow-version-upgrade","multiregional":false,"service":"Amazon Redshift"},"ecc-aws-553":{"article":"Identifying unused Classic Load Balancers (CLBs) will reduce the costs of your AWS bill. A CLB is considered to be unused when it does not have registered instances.\nUnless there is a business need to retain unused CLBs, you should remove them to avoid incurring unexpected charges and to maintain an accurate inventory of system components.","impact":"Keeping unused Classic Load Balancers can result in escalating costs and cluttered AWS accounts.","report_fields":["LoadBalancerArn"],"remediation":"To delete Classic Load Balancer:\n 1. Sign in to the Amazon EC2 console and access the Amazon EC2 at https://console.aws.amazon.com/ec2/v2.\n 2. On the console, select the specific region. \n 3. Under the 'Load Balancing' click on the 'Load Balancers'.\n 4. Select a load balancer.\n 4. Click on the 'Actions' button and click on 'Delete load balancer'.","multiregional":false,"service":"Amazon Elastic Load Balancing"},"ecc-aws-541":{"article":"AWS Glue provides real-time, continuous logging for AWS Glue jobs. You can view real-time Apache Spark job logs in Amazon CloudWatch, including driver logs, executor logs, and an Apache Spark job progress bar. Viewing real-time logs provides you with a better perspective on the running job.","impact":"With disabled logging of Glue Job, it may be difficult to troubleshoot any issues that might happen with jobs.","report_fields":["Name"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon Glue at https://console.aws.amazon.com/glue.\n2. Under the 'AWS Glue Studio' click on the 'Jobs'.\n3. Click on the required job.\n4. Open 'Job details' and click on the 'Advanced properties'.\n5. Enable the 'Continuous logging'.\n6. Save changes.","multiregional":false,"service":"AWS Glue"},"ecc-aws-480":{"article":"Codebuild a fully managed build service that compiles source code, run unit tests and produces artifacts.","impact":"Disabled encryption allows unauthorized users to gain access to Codebuild artifacts. With encryption enabled, this data is protected.","report_fields":["arn"],"remediation":"1. Open the CodeBuild console at https://console.aws.amazon.com/codebuild/.\n2. Choose required project.\n3. Open 'Build details'. \n4. Under 'Artifacts' click 'Edit'.\n5. Uncheck 'Disable artifact encryption'.","multiregional":false,"service":"AWS CodeBuild"},"ecc-aws-237":{"article":"PostgreSQL does not maintain an internal record of attempted connections to the database for later auditing. It is only by enabling the logging of these attempts that one can determine if unexpected attempts are being made. Note that enabling this without also enabling log_disconnections provides little value. Generally, you would enable/disable the pair together.","impact":"Without the log_connections logs setting enabled, attempted connections will not be recorded. This makes harder to diagnose issues or detect different types of attacks, as well as retain data for regulatory or legal purposes.","report_fields":["DBInstanceArn"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/\n2. In the navigation pane, choose Parameter groups.\n3. Choose the required parameter group.\n4. Choose Edit parameters.\n5. Under Parameters, in search bar type \"log_connections\".\n6. Choose 1 in value field.\n7. Choose Save changes.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-353":{"article":"Amazon Redshift logs information about connections and user activities in your database can help you to monitor the database for security and troubleshooting purposes.","impact":"If logs information will not record, there will be no opportunity to do any analysis, discover the cause of problems and troubleshoot them.","report_fields":["ClusterIdentifier"],"remediation":"To enable audit logging for a cluster:\n 1. Sign in to the AWS Management Console and open the Amazon Redshift console at https://console.aws.amazon.com/redshift/.\n 2. On the navigation menu, choose Clusters, then choose the cluster that you want to update.\n 3. Choose the Properties tab. On the Database configurations panel, choose Edit, then Edit audit logging.\n 4. On the Edit audit logging page, choose Turn on and select S3 bucket or CloudWatch. Amazon recommends using CloudWatch because administration is easy and it has helpful features for data visualization.\n 5. Choose which logs to export.\n 6. To save your choices, choose Save changes.\n\nTo change default parameter group:\n 1. Open the Amazon Redshift console at https://console.aws.amazon.com/redshift/.\n 2. In the navigation panel, choose Provisioned clusters dashboard.\n 3. Choose the required Cluster.\n 4. Choose Properties.\n 5. Under Database configurations, choose Edit, Edit parameter group.\n 6. Choose existing parameter or create new parameter group.\n\nTo update parameter in existing non default parameter group:\n 1. Open the Amazon Redshift console at https://console.aws.amazon.com/redshift/.\n 2. In the navigation panel, choose Provisioned clusters dashboard.\n 3. Choose the required Cluster. \n 4. Choose Properties.\n 5. Click on Parameter group name.\n 6. Click on Parameters.\n 7. Click Edit Parameters\n 8. Set 'enable_user_activity_logging' to true.\n 9. Choose Save.","multiregional":false,"service":"Amazon Redshift"},"ecc-aws-211":{"article":"IAM database authentication allows for password-free authentication to database instances. The authentication uses an authentication token. Network traffic to and from the database is encrypted using SSL.","impact":"Without IAM Database Authentication, you are missing the opportunity to use in-transit encryption. It may lead to Internet traffic exposure resulting in a breach of confidentiality and centralized access management to all databases.","report_fields":["DBClusterArn"],"remediation":"Open the Amazon RDS console at https://console.aws.amazon.com/rds/. \n1. Choose Databases. \n2. Choose the DB cluster to modify. \n3. Choose Modify. \n4. Under Database options, select Enable IAM DB authentication. \n5. Choose Continue. \n6. Under Scheduling of modifications, choose when to apply modifications: Apply during the next scheduled maintenance window or Apply immediately. \n7. Choose Modify cluster.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-422":{"article":"This policy identifies the Lightsail instance that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["arn"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon Lightsail at https://lightsail.aws.amazon.com/ls/webapp/home/instances.\n2. Click on the required lightsail instance.\n3. Click on three dots and open Tags.\n4. Add key-value tag.","multiregional":false,"service":"Amazon Lightsail"},"ecc-aws-375":{"article":"WorkSpaces is integrated with the AWS Key Management Service (AWS KMS). This enables you to encrypt storage volumes of WorkSpaces using AWS KMS Key. When you launch a WorkSpace, you can encrypt the root volume (for Microsoft Windows, the C drive; for Linux, /) and the user volume (for Windows, the D drive; for Linux, /home). Doing so ensures that the data stored at rest, disk I/O to the volume, and snapshots created from the volumes are all encrypted. You need an AWS KMS Key before you can begin the encryption process. You can select a symmetric customer managed KMS Key that you created using AWS KMS. You can view, use, and manage this KMS Key, including setting its policies.","impact":"Not encrypting data at rest can lead to unauthorized access to sensitive data. Without a KMS CMK customer-managed key, you do not have full and granular control over who can access key that is used for encryption.","report_fields":["WorkspaceId"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nTo encrypt existing AWS WorkSpaces data you must re-create the necessary WorkSpaces instances with the volumes encryption feature enabled as outlined above. Perform the following steps to encrypt WorkSpace volumes:\n1. Login to the WorkSpaces console at https://console.aws.amazon.com/workspaces/.\n2. Click Launch WorkSpaces and complete the first three steps.\n3. For the WorkSpaces Configuration step, do the following: \n - Select the volumes to encrypt: Root Volume, User Volume, or both volumes. \n - For Encryption Key, select an AWS KMS CMK. The CMK that you select must be symmetric.\n4. Click Next Step.\n5. Click Launch WorkSpaces.","multiregional":false,"service":"Amazon WorkSpaces Family"},"ecc-aws-542":{"article":"Auto Scaling is available for your AWS Glue ETL and streaming jobs with AWS Glue version 3.0 or later.\nWith Auto Scaling enabled, you will get the following benefits:\n - AWS Glue automatically adds and removes workers from the cluster depending on the parallelism at each stage or microbatch of the job run.\n - It removes the need for you to experiment and decide on the number of workers to assign for your AWS Glue ETL jobs.\n - If you choose the maximum number of workers, AWS Glue will choose the right size resources for the workload.\n - You can see how the size of the cluster changes during the job run by looking at CloudWatch metrics on the job run details page in AWS Glue Studio.\nAuto Scaling helps with cost and utilization for your applications.","impact":"Disabling autoscaling in AWS Glue can result in inefficient resource usage, reduced performance, limited scalability, and additional operational overhead.","report_fields":["Name"],"remediation":"1. Log in to the AWS Console and navigate to the AWS Glue console.\n2. Click on the \"ETL Jobs\" tab to view your existing jobs.\n3. Select the job for which you want to enable autoscaling.\n4. Navigate to the \"Job details\" to edit the job configuration.\n5. Enable the \"Automatically scale the number of workers\" parameter.\n6. Set up the \"Maximum number of workers\" parameter.\n7. Click on the \"Save\" button to save the changes to your job configuration.\nOnce the changes are saved, AWS Glue will automatically scale up or down the number of workers based on your workload.","multiregional":false,"service":"AWS Glue"},"ecc-aws-163":{"article":"If you use a known port to deploy an RDS instance, an attacker can guess the information about the instance. The attacker can use this information together with other information to connect to the RDS instance or gain additional information about your application.\nWhen you change the port, you must also update the existing connection strings that were used to connect to the old port. You should also check the security group of the DB instance to ensure that it includes an ingress rule that allows connectivity to the new port.","impact":"Unrestricted access increases the opportunity for malicious activity such as unauthorized access, denial-of-service attacks, and loss of data.","report_fields":["DBInstanceArn"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. Choose Databases.\n3. Select the DB instance to modify.\n4. Choose Modify.\n5. Under Database options, change Database port to a non-default value.\n6. Choose Continue.\n7. Under Scheduling of modifications, choose when to apply modifications. You can choose either Apply during the next scheduled maintenance window or Apply immediately.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-349":{"article":"Amazon Route 53 log information about the public DNS queries that Route 53 receives, such as the following: \n - Domain or subdomain that was requested; \n - Date and time of the request; \n - DNS record type (such as A or AAAA);\n - Route 53 edge location that responded to the DNS query;\n - DNS response code, such as NoError or ServFail.","impact":"With disabled logs for the Route53, it is harder to analyze statistics, diagnose issues, detect different types of attacks.","report_fields":["Id","Name"],"remediation":"To enable Route53 query logging:\n1. Open the Amazon RDS console at https://console.aws.amazon.com/route53/v2/.\n2. In the navigation pane, choose 'Hosted zones'.\n3. Click on the Hosted zone that you want to modify.\n4. Click on the 'Configure query logging'.\n5. Under 'Permissions' tab and grant permissions to publish logs. \n6. Choose existing or create a new Log group.\n7. CHoose 'Create'.","multiregional":true,"service":"Amazon Route 53"},"ecc-aws-526":{"article":"A WAF global rule group can contain multiple rules. The rule's conditions allow for traffic inspection and take a defined action (allow, block, or count).","impact":"Without any rules, the traffic passes without inspection. A WAF global rule group with no rules, but with a name or tag suggesting allow, block, or count, could lead to the wrong assumption that one of those actions is occurring.","report_fields":["Name","RuleGroupId"],"remediation":"To delete an empty WAF rule group:\n1. Sign in to the AWS Management Console using the AWS Firewall Manager administrator account that you set up in the prerequisites, and then open the Firewall Manager console at https://console.aws.amazon.com/wafv2/fms.\n2. In the navigation pane, choose 'Switch to AWS WAF Classic'.\n3. In the 'AWS WAF Classic' navigation pane, choose 'Rule groups'.\n4. Choose an empty rule group that you want to delete.\n5. Click 'Delete'\n\nTo add rules to an empty WAF rule group:\n1. Sign in to the AWS Management Console using the AWS Firewall Manager administrator account that you set up in the prerequisites, and then open the Firewall Manager console at https://console.aws.amazon.com/wafv2/fms.\n2. In the navigation pane, choose 'Switch to AWS WAF Classic'.\n3. In the 'AWS WAF Classic' navigation pane, choose 'Rule groups'.\n4. Choose a rule group to which you want to add rules.\n5. Click 'Edit rule group'.\n6. From 'Rules' drop-down list select rules that you want to attach to this rule group. Click 'Add rule to rule group'\n7. Set the right order and 'Action'.\n8. Click 'Update'.","multiregional":true,"service":"AWS Web Application Firewall"},"ecc-aws-248":{"article":"You can use AWS WAF to protect your API Gateway API from common web exploits, such as SQL injection and cross-site scripting (XSS) attacks. These could affect API availability and performance, compromise security, or consume excessive resources. \nFor example, you can create rules to allow or block requests from specified IP address ranges, requests from CIDR blocks, requests that originate from a specific country or region, requests that contain malicious SQL code, or requests that contain malicious script.","impact":"Not enabling Api gateway WAF integration could affect API availability and performance, compromise security, or consume excessive resources.","report_fields":["stageName","restApiId"],"remediation":"1. Sign in to the API Gateway console at https://console.aws.amazon.com/apigateway.\n2. In the APIs navigation pane, choose the API, and then choose Stages.\n3. In the Stages pane, choose the name of the stage.\n4. In the Stage Editor pane, choose the Settings tab.\n5. To associate a Regional web ACL with the API stage:\n5.1. In the AWS WAF web ACL dropdown list, choose the Regional web ACL that you want to associate with this stage.\nNote. If the web ACL you need doesn't exist yet, choose Create WebACL. Then choose Go to AWS WAFto open the AWS WAF console in a new browser tab and create a Regional web ACL. Then return to the API Gateway console to associate the web ACL with the stage.\n6. Choose Save Changes.","multiregional":false,"service":"Amazon API Gateway"},"ecc-aws-345":{"article":"To improve security, you should restrict the connections of unnecessary protocols and ports by properly configuring your Amazon VPC Security Group. For instance, to restrict access to most protocols while allowing access to OpenWire and the web console, you could allow access to only 61617 and 8162.","impact":"Allowed inbound traffic on all ports can increase opportunities for malicious activities such as unauthorized access, Man-In-The-Middle attacks (MITM), and brute-force attacks that raise the risk of resource compromising.","report_fields":["BrokerArn"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon MQ at https://console.aws.amazon.com/amazon-mq.\n2. Click on the required broker.\n3. Click 'Edit'.\n4. Under 'Security and network' choose existing security group restricted to only ports '8162' and '61617' or create a new one.\n5. Save changes.","multiregional":false,"service":"Amazon MQ"},"ecc-aws-330":{"article":"When data changing statements are made (i.e., INSERT, UPDATE), MySQL can handle invalid or missing values differently depending on whether strict SQL mode is enabled.\nWhen strict SQL mode is enabled, data may not be truncated or otherwise \"adjusted\" to make the data changing statement work.","impact":"Without strict mode the server tries to proceed with the action when an error might have been a more secure choice. For example, by default MySQL will truncate data if it does not fit in a field, which can lead to unknown behavior, or be leveraged by an attacker to circumvent data validation.","report_fields":["DBInstanceArn"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. In the navigation pane, choose Parameter groups.\n3. Choose the required parameter group.\n4. Choose Edit parameters.\n5. Under Parameters, in the search bar, type 'sql_mode'.\n6. Type 'STRICT_ALL_TABLES'\n7. Choose Save changes.\n8. Restart instance","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-297":{"article":"AWS Elastic Beanstalk regularly releases platform updates to provide fixes, software updates, and new features. With managed platform updates, you can configure your environment to automatically upgrade to the latest version of a platform during a scheduled maintenance window. Your application remains in service during the update process with no reduction in capacity.","impact":"With an automatic update disabled, you are missing updates that may contain new software features, bug fixes, security patches and performance improvements.","report_fields":["EnvironmentArn"],"remediation":"1. Login to the AWS Management Console and open the Amazon Elastic Beanstalk console using https://console.aws.amazon.com/elasticbeanstalk/\n2. In the navigation pane, choose Environments, and then choose the name of your environment from the list.\n3. In the navigation pane, choose Configuration.\n4. In the Managed updates category, choose Edit.\n5. Disable or enable Managed updates.\n6. If managed updates are enabled, select a maintenance window, and then select an Update level.\n7. Choose Apply.","multiregional":false,"service":"AWS Elastic Beanstalk"},"ecc-aws-213":{"article":"RDS DB clusters should be configured for multiple Availability Zones to ensure availability of the data that is stored. Deployment to multiple Availability Zones allows for automated failover in the event of an Availability Zone availability issue and during regular RDS maintenance events.","impact":"Disabled multiple Availability Zones for RDS DB clusters threaten the availability of stored data.","report_fields":["DBClusterArn"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. In the navigation pane, choose Databases, and then choose the DB instance to modify. \n3. Choose Modify. The Modify DB Instance page appears. \n4. Under Instance Specifications, set Multi-AZ deployment to Yes. \n5. Choose Continue and check the summary of modifications. \n6. (Optional) Choose Apply immediately to apply the changes immediately. Choosing this option can cause an outage in some cases.\n7. On the confirmation page, review your changes. If they are correct, choose Modify DB Instance.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-327":{"article":"Elastic Compute Cloud (EC2) supports encryption at rest when using the Elastic Block Store (EBS) service.","impact":"Disabled encryption allows unauthorized or anonymous users to gain access to EBS containing sensitive data. With Elastic Block Store encryption enabled, the data stored on the snapshots is encrypted.","report_fields":["SnapshotId","OwnerId"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nTo encrypt existing EBS Snapshot:\n1. Login to the AWS Management Console and open the Amazon EC2 console using https://console.aws.amazon.com/ec2/.\n2. Under Elastic Block Store, click on Snapshots.\n3. Click on required snapshot.\n4. Click on the Actions.\n5. Click on the Copy snapshot.\n6. Under Encryption choose 'Encrypt this snapshot'.","multiregional":false,"service":"Amazon Elastic Block Store"},"ecc-aws-072":{"article":"This ensures that the group can determine an instance's health based on additional tests provided by the load balancer. Using Elastic Load Balancing health checks can help support the availability of applications that use EC2 Auto Scaling groups.","impact":"Not using an ELB health check can lead to a longer response time or no response to website availability issues.","report_fields":["AutoScalingGroupARN"],"remediation":"1. Open the Amazon EC2 console at https://console.aws.amazon.com/ec2/.\n2. On the navigation pane, under Auto Scaling, choose Auto Scaling Groups.\n3. To select the group from the list, choose the right box.\n4. From Actions, choose Edit.\n5. For Health Check Type, choose ELB. \n6. For Health Check Grace Period, enter 300.\n7. Choose Save.","multiregional":false,"service":"Amazon EC2 Auto Scaling"},"ecc-aws-202":{"article":"Amazon RDS, Oracle database engine logs (Alert, Audit, Trace, Listener) should be enabled and sent to CloudWatch.\nDatabase logging provides detailed records of requests made to RDS. Database logs can assist with security and access audits and can help to diagnose availability issues.","impact":"With disabled logs for the database, it is harder to analyze statistics, diagnose issues, detect different types of attacks, and retain data for regulatory or legal purposes.","report_fields":["DBInstanceArn"],"remediation":"Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n1. In the navigation pane, choose Databases.\n2. Choose the DB instance that you want to modify.\n3. Choose Modify.\n4. Under Log exports, choose Alert, Audit, Trace and Listener log types to start publishing to CloudWatch Logs.\n5. Log exports is available only for database engine versions that support publishing to CloudWatch Logs.\n6. Choose Continue. Then on the summary page, choose Modify DB Instance.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-010":{"article":"If you have an HTTPS listener, you deployed an SSL server certificate on your load balancer when you created the listener. Each certificate comes with a validity period. You must ensure that you renew or replace the certificate before its validity period ends.\nCertificates provided by AWS Certificate Manager and deployed on your load balancer can be renewed automatically. ACM attempts to renew certificates before they expire. If you imported a certificate into ACM, you must monitor the expiration date of the certificate and renew it before it expires. After a certificate that is deployed on a load balancer is renewed, new requests use the renewed certificate.","impact":"SSL/TLS certificates not renewed prior to their expiration date become invalid and the communication between a client and an AWS resource that implements the certificates (e.g. AWS ELB) is no longer secure.","report_fields":["LoadBalancerArn"],"remediation":"1. Login to the AWS EC2 console at console.aws.amazon.com/ec2/.\n2. Navigate to the 'Load Balancer' section, and then to the 'Listener' tab. \n3. Select the listener (HTTPS) and select the 'View/edit certificates' tab. \n4. Add the new certificate (from IAM) for each instance that failed the rule.","multiregional":false,"service":"Amazon Elastic Load Balancing"},"ecc-aws-299":{"article":"Field-level encryption adds an additional layer of security that lets you protect sensitive data, such as credit card numbers or personally identifiable information (PII) like social security numbers throughout system processing so that only certain applications can see it.\nWhen the HTTPS request with field-level encryption is forwarded to the origin, and the request is routed throughout your origin application or subsystem, the sensitive data is still encrypted, reducing the risk of a data breach or accidental data loss of the sensitive data. Components that need access to the sensitive data for business reasons, such as a payment processing system needing access to a credit number, can use the appropriate private key to decrypt and access the data.","impact":"Without field-level encryption, sensitive information could be exposed to unauthorized processes and pose a threat of data breach or accidental data loss.","report_fields":["ARN"],"remediation":"To enable field-level encryption for your Amazon CloudFront web distributions, perform the following actions: \n\nStep 1: Create an RSA key pair\n1. For example, if you\u2019re using OpenSSL, you can use the following command to generate a key pair with a length of 2048 bits and save it in the file private_key.pem: \n 'openssl genrsa -out private_key.pem 2048'\n2. The resulting file contains both the public and the private key. To extract the public key from that file, run the following command:\n 'openssl rsa -pubout -in private_key.pem -out public_key.pem'\n The public key file (public_key.pem) contains the encoded key value that you paste in the following step.\n\nStep 2: Add your public key to CloudFront\nAfter you get your RSA key pair, add your public key to CloudFront.\nTo add your public key to CloudFront:\n1. Sign in to the AWS Management Console and open the CloudFront console at https://console.aws.amazon.com/cloudfront/v3/home.\n2. In the navigation pane, choose Public key.\n3. Choose Add public key.\n4. For Key name, type a unique name for the key. The name can't have spaces and can include only alphanumeric characters, underscores (_), and hyphens (-). The maximum number of characters is 128.\n5. For Key value, paste the encoded key value for your public key, including the -----BEGIN PUBLIC KEY----- and -----END PUBLIC KEY----- lines.\n6. For Comment, add an optional comment. For example, you could include the expiration date for the public key.\n7. Choose Add.\n\nStep 3: Create a profile for field-level encryption\nAfter you add at least one public key to CloudFront, create a profile that tells CloudFront which fields to encrypt.\nTo create a profile for field-level encryption:\n1. In the navigation pane, choose Field-level encryption.\n2. Choose Create profile.\n3. Fill in the fields.\n4. After you fill in the fields, choose Create profile.\n\nStep 4: Create a configuration\nTo create a configuration for field-level encryption:\n1. On the Field-level encryption page, choose Create configuration.\n2. Fill in the 'Default profile ID' field to specify the profile to use. \n3. If you want to change the CloudFront default behavior for the following options, select the appropriate check box.\n a) Forward request to origin when request\u2019s content type is not configured. Select the check box if you want to allow the request to go to your origin if you have not specified a profile to use for the content type of the request.\n b) Override the profile for a content type with a provided query argument. Select the check box if you want to allow a profile provided in a query argument to override the profile that you\u2019ve specified for a content type.\n4. If you select the check box to allow a query argument to override the default profile, you must complete the additional fields for the configuration. \n\nStep 5: Add a configuration to a cache behavior\n1. Go back to the navigation panel and choose Distributions. \n2. Select the distribution that you want to reconfigure.\n3. Choose the Behaviors tab and select the default behavior for the distribution.\n4. Click the Edit button. \n5. On the Edit Behavior page, select the ID of the field-level encryption configuration. Note that you can set the field-level encryption configuration only when Viewer Protocol Policy and Origin Protocol Policy settings are using HTTPS. \n6. Click 'Save changes'","multiregional":true,"service":"Amazon CloudFront"},"ecc-aws-525":{"article":"A WAF global rule can contain multiple conditions. A rule's conditions allow for traffic inspection and take a defined action (allow, block, or count).","impact":"Without any conditions, the traffic passes without inspection. A WAF global rule with no conditions, but with a name or tag suggesting allow, block, or count, could lead to the wrong assumption that one of those actions is occurring.","report_fields":["Name","RuleId"],"remediation":"To delete an empty WAF rule:\n1. Sign in to the AWS Management Console and open the AWS WAF console at https://console.aws.amazon.com/wafv2/.\n2. If you see 'Switch to AWS WAF Classic' in the navigation pane, select it.\n3. In the navigation pane, choose 'Rules'.\n4. Select an empty rule that you want to delete.\n5. Click 'Delete'.\n\nTo add conditions to an empty WAF rule:\n1. Sign in to the AWS Management Console and open the AWS WAF console at https://console.aws.amazon.com/wafv2/.\n2. If you see 'Switch to AWS WAF Classic' in the navigation pane, select it.\n3. In the navigation pane, choose 'Rules'.\n4. Select an empty rule to which you want to add condition.\n5. Click 'Edit rule'.\n6. To add an existing condition to the rule, specify the following values:\n When a request does/does not\n a) If you want AWS WAF Classic to allow or block requests based on the filters in a condition, choose 'does'. For example, if an IP match condition includes the IP address range 192.0.2.0/24 and you want AWS WAF Classic to allow or block requests that come from those IP addresses, choose does.\n b) If you want AWS WAF Classic to allow or block requests based on the inverse of the filters in a condition, choose 'does not'. For example, if an IP match condition includes the IP address range 192.0.2.0/24 and you want AWS WAF Classic to allow or block requests that do not come from those IP addresses, choose does not.\n \n match/originate from\n Choose the type of condition that you want to add to the rule:\n a) Cross-site scripting match conditions - choose 'match at least one of the filters in the cross-site scripting match condition'\n b) IP match conditions - choose 'originate from an IP address in'\n c) Geo match conditions - choose 'originate from a geographic location in'\n d) Size constraint conditions - choose 'match at least one of the filters in the size constraint condition'\n e) SQL injection match conditions - choose 'match at least one of the filters in the SQL injection match condition'\n f) String match conditions - choose 'match at least one of the filters in the string match condition'\n g) Regular expression match conditions - choose 'match at least one of the filters in the regex match condition'\n7. Click 'Add condition'.\n8. When you're finished adding conditions, choose 'Update'.","multiregional":true,"service":"AWS Web Application Firewall"},"ecc-aws-178":{"article":"X-Ray active tracing enables a more rapid response to performance changes in the underlying infrastructure. Changes in performance could result in a lack of availability of the API. X-Ray active tracing provides real-time metrics of user requests that flow through your API Gateway REST API operations and connected services.","impact":"Disabled AWS X-Ray makes it harder to analyze user requests as they flow through the AWS API Gateway APIs to underlying services. It results in a slower response to performance changes in the underlying infrastructure.","report_fields":["stageName","restApiId"],"remediation":"1. Sign in to the API Gateway console at https://console.aws.amazon.com/apigateway.\n2. In the APIs pane, choose the API, and then choose Stages.\n3. In the Stages pane, choose the name of the stage.\n4. In the Stage Editor pane, choose the Logs/Tracing tab.\n5. To enable active X-Ray tracing, choose Enable X-Ray Tracing under X-Ray Tracing.\n6. If desired, choose Set X-Ray Sampling Rules and go to the X-Ray console to configure sampling rules.\n\nOnce you've enabled X-Ray for your API stage, you can use the X-Ray management console to view the traces and service map.","multiregional":false,"service":"Amazon API Gateway"},"ecc-aws-315":{"article":"Enabling basic auditing features for the Oracle instance permits the collection of data to troubleshoot problems and provides valuable forensic logs in the case of a system breach.\nValues are: DB, OS, NONE, XML, EXTENDED.","impact":"If audit_trail is disabled than there will be no data to troubleshoot problems and provides valuable forensic logs in the case of a system breach.","report_fields":["DBInstanceArn"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/\n2. In the navigation pane, choose Parameter groups.\n3. Choose the required parameter group\n4. Choose Edit parameters.\n5. Under Parameters, in search bar type \"audit_trail\".\n6. Choose XML or other values according to your organization policy\n7. Choose Save changes.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-369":{"article":"Use Cloudwatch to store/archive WorkSpaces login events for future reference, analysis, and action based on the patterns. Utilize the IP address collected to figure out where users are logged in from, and then build policies to allow access only to files or data from those WorkSpaces that meet company access criteria. With this information you can also use policy controls to block access from unauthorized IP addresses.","impact":"Not logging the login events for WorkSpaces makes it harder to analyze statistics, diagnose issues, detect different types of attacks, and retain data for regulatory or legal purposes.","report_fields":["account_id","account_name"],"remediation":"Perform the following steps to create a Rule for CloudWatch WorkSpaces Events From the Console:\n1. Login to the EventBridge console at https://console.aws.amazon.com/events/home.\n2. In the left pane click Rules.\n3. Click Create rule.\n4. For Rule definition, enter a name and description.\n5. For \"Rule type\" select \"Rule with an event pattern\", click \"Next\".\n6. For \"Event source\" select \"AWS events or EventBridge partner events\".\n7. In \"Event pattern\" section, for \"Event source\" select \"AWS services\", for \"AWS service\" select \"WorkSpaces\" and for \"Event type\" select \"WorkSpaces Access\".\n8. Click \"Next\".\n9. For \"Target types\", select \"AWS service\", for target select \"CloudWatch log group\".\n10. Select , click \"Next\".\n11. Configure tags if needed and click \"Next\".\n12. Review changes and click \"Create rule\".","multiregional":false,"service":"Amazon WorkSpaces Family"},"ecc-aws-472":{"article":"Instance metadata is used to configure or manage the running instance. The IMDS provides access to temporary, frequently rotated credentials. These credentials remove the need to hard code or distribute sensitive credentials to instances manually or programmatically.\nVersion 2 of the IMDS adds new protections for the following types of vulnerabilities. These vulnerabilities could be used to try to access the IMDS:\n - Open website application firewalls;\n - Open reverse proxies;\n - Server-side request forgery (SSRF) vulnerabilities;\n - Open Layer 3 firewalls and network address translation (NAT).","impact":"Instances that use IMDSv1 are exposed to the following vulnerabilities:\n- Open website application firewalls;\n- Open reverse proxies;Server-side request forgery (SSRF) vulnerabilities;\n- Open Layer 3 firewalls and network address translation (NAT).","report_fields":["LaunchConfigurationName"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nTo enable imdsv2 only, create new launch configuration:\n1. Open the Amazon EC2 Auto Scaling console at https://console.aws.amazon.com/ec2/.\n2. Under Auto Scaling, click on the Launch Configurations.\n3. Click on the Create launch configuration.\n4. Additional configuration, click on the Advanced details.\n5. Under \"Metadata accessible\" choose enabled.\n6. Under \"Metadata version\" choose \"V2 only\".\n7. Create launch configuration.","multiregional":false,"service":"Amazon EC2 Auto Scaling"},"ecc-aws-210":{"article":"IAM database authentication allows authentication to database instances with an authentication token instead of a password. Network traffic to and from the database is encrypted using SSL.","impact":"Without IAM Database Authentication, you are missing the opportunity to use in-transit encryption. It may lead to Internet traffic exposure resulting in a breach of confidentiality and centralized access management to all databases.","report_fields":["DBInstanceArn"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. Choose Databases. \n3. Select the DB instance to modify.\n4. Choose Modify. \n5. Under Database options, choose Enable IAM DB authentication. \n6. Choose Continue. \n7. Under Scheduling of modifications, choose when to apply modifications. The options are Apply during the next scheduled maintenance window or Apply immediately. \n8. For clusters, choose Modify DB Instance.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-351":{"article":"When you create and use your own KMS CMK customer-managed keys to protect the contents of your RDS instances, you obtain full control over who can use the CMK keys and access the data encrypted within RDS. The AWS KMS service allows you to create, rotate, disable, enable, and audit your Customer Master Keys (CMKs) for Amazon RDS.","impact":"Without a KMS CMK customer-managed key, you do not have full and granular control over who can access key that is used for encryption.","report_fields":["DBInstanceArn"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nEncrypting Existing AWS RDS Database:\n1. Login to the AWS Management Console.\n2. Navigate to RDS dashboard at https://console.aws.amazon.com/rds/.\n3. In the navigation panel, under RDS Dashboard, click Instances.\n4. Select the RDS database instance that you want to encrypt.\n5. Click Instance Actions button from the dashboard top menu and select Take Snapshot.\n6. On the Take DB Snapshot page, enter a name for the instance snapshot in the Snapshot Name field and click Take Snapshot (the backup process may take few minutes and depends on your instance storage size).\n7. Select the new created snapshot and click the Copy Snapshot button from the dashboard top menu.\n8. On the Make Copy of DB Snapshot page, perform the following:\n 8.1 In the New DB Snapshot Identifier field, enter a name for the new snapshot (copy).\n 8.2 Check Copy Tags so the new snapshot can have the same tags as the source snapshot.\n 8.3 Select Yes from the Enable Encryption dropdown list to enable encryption.\n 8.4 Under AWS KMS Key choose required CMK (not default)\n9. Click Copy Snapshot to create an encrypted copy of the selected instance snapshot.\n10. Select the new snapshot copy (encrypted) and click Restore Snapshot button from the dashboard top menu. This will restore the encrypted snapshot to a new database instance.\n11. On the Restore DB Instance page, enter a unique name for the new database instance in the DB Instance Identifier field.\n12. Review the instance configuration details and click Restore DB Instance.\n13. As soon as the new instance provisioning process is completed (its status becomes available), you can update your application configuration to refer to the endpoint of the new (encrypted) database instance. Once the database endpoint is changed at your application level, you can remove the unencrypted instance.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-130":{"article":"Elastic Load Balancing uses a TLS negotiation configuration, known as a security policy, to negotiate TLS connections between a client and the Network load balancer. For TLS listeners, It's recommend using the ELBSecurityPolicy-TLS13-1-2-2021-06 security policy. This is the default policy for listeners created using the AWS Management Console. This security policy includes TLS 1.3, which is optimized for security and performance, and is backward compatible with TLS 1.2. For Forward Secrecy, you can use one of the ELBSecurityPolicy-FS policies or an ELBSecurityPolicy-TLS13 policy.","impact":"Using a deprecated security policy for TLS negotiation configuration within Network Load Balancers exposes the connection between the client and the load balancer to various TLS vulnerabilities. Deprecated security policies may have old versions of TLS or SSL protocols that are known to have fatal security flaws and do not provide protection for data in transit.","report_fields":["LoadBalancerArn"],"remediation":"1. Open the Amazon EC2 console at https://console.aws.amazon.com/ec2/.\n2. In the navigation pane, under 'LOAD BALANCING', choose 'Load Balancers'.\n3. Select the load balancer and choose 'Listeners'.\n4. Select the check box for the TLS listener and choose 'Edit'.\n5. For 'Security policy', choose the security policy.\n6. Choose 'Update'.","multiregional":false,"service":"Amazon Elastic Load Balancing"},"ecc-aws-200":{"article":"Enabling cluster deletion protection is an additional layer of protection against accidental database deletion or deletion by an unauthorized entity. When deletion protection is enabled, an RDS cluster cannot be deleted. Before a deletion request can succeed, deletion protection must be disabled","impact":"Accidentally deleted or deleted by an unauthorized entity, the RDS cluster can result in data loss.","report_fields":["DBClusterArn"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. In the navigation pane, choose Databases, then choose the DB cluster that you want to modify. \n3. Choose Modify. \n4. Under Deletion protection, choose Enable deletion protection. \n5. Choose Continue. \n6. Under Scheduling of modifications, choose when to apply modifications. The options are Apply during the next scheduled maintenance window or Apply immediately. \n7. Choose Modify Cluster.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-252":{"article":"With this feature enabled, you can encrypt AWS Glue Data Catalog objects such as databases, tables, partitions, connections and user-defined functions and also encrypt connection passwords that you provide when you create data connections.","impact":"Not encrypting data at rest can lead to unauthorized access to sensitive or private data and may not meet compliance requirements defined within your organization for data-at-rest encryption.","report_fields":["CatalogId"],"remediation":"1. Sign in to the AWS Management Console and open the AWS Glue console at https://console.aws.amazon.com/glue/.\n2. Choose Settings in the navigation pane.\n3. On the Data catalog settings page, select Metadata encryption, and choose an AWS KMS key.","multiregional":false,"service":"AWS Glue"},"ecc-aws-111":{"article":"Ensure that all your public AWS ALBs are integrated with the Web Application Firewall (AWS WAF) service to protect against application-layer attacks","impact":"By not using a firewall, you deprive yourself of protection against common web-based attacks. This can lead to a loss of data confidentiality, integrity, and availability.","report_fields":["LoadBalancerArn"],"remediation":"1. Go to the AWS WAF service page and select Web ACLs. \n2. Create a new Web ACL or select the existing one. \n3. Select the 'Associated AWS resources' tab.\n4. Click on 'Add AWS resources'. \n5. Under 'Resource Type', select the resource type and then select the resource you want to associate with this web ACL.","multiregional":false,"service":"Amazon Elastic Load Balancing"},"ecc-aws-458":{"article":"CloudWatch Lambda Insights is a monitoring and troubleshooting solution for serverless applications running on AWS Lambda. The solution collects, aggregates, and summarizes system-level metrics including CPU time, memory, disk, and network. It also collects, aggregates, and summarizes diagnostic information such as cold starts and Lambda worker shutdowns to help you isolate issues with your Lambda functions and resolve them quickly.\nLambda Insights uses a new CloudWatch Lambda extension, which is provided as a Lambda layer. When you install this extension on a Lambda function, it collects system-level metrics and emits a single performance log event for every invocation of that Lambda function. CloudWatch uses embedded metric formatting to extract metrics from the log events.","impact":"With Enhanced Monitoring for Lambda functions disabled, you are missing out on the opportunity to use a monitoring and troubleshooting solution that helps you isolate issues with your Lambda functions and resolve them quickly.","report_fields":["FunctionArn"],"remediation":"When you enable Lambda Insights on a function in the Lambda console for a supported runtime, Lambda adds the Lambda Insights extension as a layer to your function, and verifies or attempts to attach the CloudWatchLambdaInsightsExecutionRolePolicy policy to your function\u2019s execution role.\nTo enable Lambda Insights in the Lambda console:\n1. Open the Functions page of the Lambda console https://console.aws.amazon.com/lambda/home#/functions.\n2. Choose your function.\n3. Choose the 'Configuration' tab.\n4. On the 'Monitoring and operations tools' pane, choose 'Edit'.\n5. Under 'CloudWatch Lambda Insights', turn on 'Enhanced monitoring'.\n6. Choose 'Save'.","multiregional":false,"service":"AWS Lambda"},"ecc-aws-321":{"article":"The 'sec_return_server_release_banner' setting return information about the patch/update release number.","impact":"Allowing the database to return information about the patch/update release number could facilitate unauthorized users' attempts to gain access based upon known patch weaknesses.","report_fields":["DBInstanceArn"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/\n2. In the navigation pane, choose Parameter groups.\n3. Choose the required parameter group\n4. Choose Edit parameters.\n5. Under Parameters, in the search bar, type \"sec_return_server_release_banner\".\n6. Choose FALSE\n7. Choose Save changes.\n8. Restart instance","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-123":{"article":"Enable encryption of your EFS file systems in order to protect your data and metadata from breaches or unauthorized access and fulfill compliance requirements for data-at-rest encryption within your organization.","impact":"Disabled encryption allows a user to get unauthorized access to sensitive data in EFS.","report_fields":["FileSystemArn","OwnerId"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\nIn order to encrypt the existing Amazon EFS with your own AWS KMS CMK, you need to create a new Amazon EFS and transfer all the data from the existing EFS to the new one with encryption enabled.\n1. Login to the AWS Management Console and Navigate to Elastic File System (EFS) dashboard.\n2. Select File Systems from the left navigation panel.\n3. Click 'Create File System' button from the dashboard top menu to start the file system setup process.\n4. On the 'Configure file system access' configuration page, perform the following actions.\n 4.1 Choose the right VPC from the VPC dropdown list.\n 4.2 Within 'Create mount targets' section, select the checkboxes for all of the Availability Zones (AZs) within the selected VPC. These will be your mount targets.\n 4.3 Click 'Next step' to continue.\n5. Perform the following on the 'Configure optional settings' page.\n 5.1 Create tags to describe your new file system.\n 5.2 Choose performance mode based on your requirements.\n 5.3 Check 'Enable encryption' checkbox and choose aws/elasticfilesystem from Select KMS master key dropdown list to enable encryption for the new file system using the default master key provided and managed by AWS KMS.\n 5.4 Click 'Next step' to continue.\n6. Review the file system configuration details on the review and create page and then click 'Create File System' to create your new AWS EFS file system.\n7. Copy the data from the old unencrypted EFS file system onto the newly create encrypted file system.\n8. Remove the unencrypted file system as soon as your data migration to the newly create encrypted file system is completed.","multiregional":false,"service":"Amazon Elastic File System"},"ecc-aws-517":{"article":"ACLs are legacy access control mechanisms that predate IAM. Instead of ACLs, it's recommend using IAM policies or S3 bucket policies to more easily manage access to your S3 buckets.\nACLs are suitable for specific scenarios. For example, if a bucket owner allows other AWS accounts to upload objects, permissions to these objects can only be managed using object ACL by the AWS account that owns the object.\nA majority of modern use cases in Amazon S3 no longer require the use of ACLs, and it's recommend that you disable ACLs except in unusual circumstances where you need to control access for each object individually.","impact":"Granting permissions via access control lists (ACLs) has its limits and impairs the ability to control access to objects.\nBecause, by default, when another AWS account uploads an object to your S3 bucket, that account (the object writer) owns the object, has access to it, and can grant other users access to it through ACLs.","report_fields":["Name"],"remediation":"If your bucket ACL grants access outside of your AWS account, before you disable ACLs, you must migrate your bucket ACL permissions to your bucket policy and reset your bucket ACL to the default private ACL. If you don't migrate these bucket ACLs, your request to apply the bucket owner enforced setting to disable ACLs fails and returns the InvalidBucketAclWithObjectOwnership error code. \n\nTo review a bucket's ACL permissions:\n1. Sign in to the AWS Management Console and open the Amazon S3 console at https://console.aws.amazon.com/s3/.\n2. In the 'Buckets list', choose the bucket name.\n3. Choose the 'Permissions' tab.\n4. Under 'Access control list (ACL)', review your bucket ACL permissions.\n\nTo review an object's ACL permissions:\n1. Sign in to the AWS Management Console and open the Amazon S3 console at https://console.aws.amazon.com/s3/.\n2. In the 'Buckets list', choose the bucket name containing your object.\n3. In the 'Objects list', choose your object name.\n4. Choose the 'Permissions' tab.\n5. Under 'Access control list (ACL'), review your object ACL permissions.\n\nTo migrate ACL permissions and update your bucket ACL:\n1. Sign in to the AWS Management Console and open the Amazon S3 console at https://console.aws.amazon.com/s3/.\n2. In the 'Buckets list', choose the bucket name.\n3. On the 'Permissions' tab, under 'Bucket policy', choose 'Edit'.\n4. In the Policy box, add or update your bucket policy.\n5. Choose 'Save changes'.\n6. Update your bucket ACL to remove ACL grants to other groups or AWS accounts.\n 6.1 Under 'Access control list (ACL)', choose 'Edit'.\n 6.2 Remove access from everyone, but 'Bucket owner (your AWS account)'.\n 6.3 Remove access to another AWS account, under 'Access for other AWS accounts', choose 'Remove'.\n 6.3 To save your changes, choose 'Save changes'.\n7. Click 'Edit' in the 'Object Ownership' section.\n8. Select 'ACLs disabled (recommended)' to apply the bucket owner enforced setting for Object Ownership.\n9. Choose 'Save changes'.","multiregional":true,"service":"Amazon S3"},"ecc-aws-126":{"article":"AWS Redshift instances should be encrypted at rest to help protecting sensitive data from breaches.","impact":"Disabled encryption allows a user to get unauthorized access to sensitive data in Redshift clusters.","report_fields":["ClusterIdentifier"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\nTo resolve this issue, create a new instance with encryption enabled, migrate the file data from the reported Redshift instances to this new instance, and then delete the original instances. \n1. Sign in to the AWS Admin Console and Access the Redshift service. \n2. Click on the identified Redshift cluster and take a snapshot of it. \n3. Create a new Redshift cluster (now with 'Encryption' set to 'Yes' during creation time) and use the snapshot to populate (restore) the new cluster. \n4. Once the new cluster is populated, delete the older cluster (without encryption).","multiregional":false,"service":"Amazon Redshift"},"ecc-aws-466":{"article":"Amazon FSx for NetApp ONTAP has two deployment types, Single-AZ and Multi-AZ, that offer you different levels of availability and durability. In a Multi-AZ deployment, the standby file server is deployed in a different Availability Zone from the active file server in the same AWS Region. Any changes written to your file system are synchronously replicated across Availability Zones to the standby.\nMulti-AZ file systems are designed for use cases such as business-critical production workloads that require high availability to shared ONTAP file data and need storage with built-in replication across Availability Zones. Single-AZ file systems are designed for use cases that do not require the data resiliency model of a Multi-AZ file system. They provide a cost-optimized solution for use cases such as development and test environments, or storing secondary copies of data that is already stored on premises or in other AWS Regions, by only replicating data within an Availability Zone.","impact":"When an FSx file system is not configured to use multiple Availability Zones and Availability Zone in which file system deploy becomes unavailable, your data also can become unavailable. Using Single-AZ does not provide data resiliency and is not recommended for use cases such as business-critical production workloads that require high availability to shared ONTAP file data.","report_fields":["ResourceARN"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nTo create FSx system with enabled Multi-AZ, perform the following actions: \n1. Open the Amazon FSx console at https://console.aws.amazon.com/fsx/.\n2. On the dashboard, choose 'Create file system 'to start the file system creation wizard.\n3. On the 'Select file system type' page, choose 'Amazon FSx for NetApp ONTAP', and then choose 'Next'. The 'Create file system' page appears. For 'Creation method', choose 'Standard create'.\n4. For 'File system name - optional', enter a name for your file system. \n5. For Deployment type choose 'Multi-AZ'.\n6. For 'SSD storage capacity', specify the storage capacity of your file system, in gibibytes (GiBs).\n7. For 'Virtual Private Cloud (VPC)'', choose the Amazon VPC that you want to associate with your file system.\n8. For 'Storage efficiency', choose 'Enabled' to turn on the ONTAP storage efficiency features (compression, deduplication, and compaction) or Disabled to turn them off.\n9. Set up all other necessary configurations.\n10. Choose 'Next'.\n11. Review the file system configuration shown on the Create ONTAP file system page. For your reference, note which file system settings you can modify after the file system is created.\n12. Choose 'Create file system'.","multiregional":false,"service":"Amazon FSx"},"ecc-aws-396":{"article":"This policy identifies the cloudformation stacks that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["StackId"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon Cloudfront at https://console.aws.amazon.com/cloudformation/.\n2. Click on 'Stacks'.\n3. CLick on the required stack.\n4. Click 'Update'.\n5. On step 3 click 'Add new tag'.\n6. Add new tag and save.","multiregional":false,"service":"AWS CloudFormation"},"ecc-aws-381":{"article":"This policy identifies the ENI that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["NetworkInterfaceId"],"remediation":"1. Sign in to the AWS Management Console and open the EC2 console at https://console.aws.amazon.com/ec2/v2/.\n2. In the navigation pane under 'Network & Security' choose 'Network interfaces'..\n3. Choose required eni.\n4. Open 'Tags' and click on 'Manage tags'.\n5. Add a tags.\n6. Save.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-241":{"article":"The log_statement setting specifies the types of SQL statements that are logged. \nValues are: none (off), ddl, mod, all (all statements). It is recommended to set on 'all' for better security.","impact":"If log_statement is disabled than sql statements will not be logged.","report_fields":["DBInstanceArn"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/\n2. In the navigation pane, choose Parameter groups.\n3. Choose the required parameter group\n4. Choose Edit parameters.\n5. Under Parameters, in search bar type \"log_statement\".\n6. Choose ddl or other values according to your organization policy\n7. Choose Save changes.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-357":{"article":"The domain registries for all generic TLDs and many geographic TLDs let you lock a domain to prevent someone from transferring the domain to another registrar without your permission.","impact":"While this feature disabled, unauthorized transfer may occur.","report_fields":["DomainName"],"remediation":"If locking is supported and you want to lock your domain, perform the following procedure:\n1. Sign in to the AWS Management Console and open the Route53 console at https://console.aws.amazon.com/route53/.\n2. In the navigation pane, choose 'Registered Domains'.\n3. Choose the name of the domain that you want to update.\n4. Choose 'Enable' for 'Transfer lock'.\n5. If you encounter issues while enabling automatic renewal, you can contact AWS Support for free. \n For more information, see 'Contacting AWS Support about domain registration issues': https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/domain-contact-support.html.","multiregional":true,"service":"Amazon Route 53"},"ecc-aws-088":{"article":"Replication enables automatic, asynchronous copying of objects across Amazon S3 buckets. Buckets that are configured for object replication can be owned by the same AWS account or by different accounts.","impact":"Users can accidentally delete S3 bucket sensitive data.","report_fields":["Name"],"remediation":"1. Open the Amazon S3 console at https://console.aws.amazon.com/s3/. \n2. Choose the S3 bucket that does not have cross-region replication enabled. \n3. Choose Management, then choose Replication. \n4. Choose Add rule. If versioning is not already enabled, you are prompted to enable it. \n5. Choose your source bucket - Entire bucket. \n6. Choose your destination bucket. If versioning is not already enabled on the destination bucket for your account, you are prompted to enable it. \n7. Choose an IAM role. For more information on setting up permissions for replication, see the Amazon Simple Storage Service Developer Guide. \n8. Enter a rule name, choose Enabled for the status, then choose Next. \n9. Choose Save.","multiregional":true,"service":"Amazon S3"},"ecc-aws-242":{"article":"Including csvlog in the log_destination list provides a convenient way to import log files into a database table. This option emits log lines in comma-separated-values (CSV) format, with these columns: time stamp with milliseconds, user name, database name, process ID, client host:port number, session ID, per-session line number, command tag, session start time, virtual transaction ID, regular transaction ID, error severity, SQLSTATE code, error message, error message detail, hint, internal query that led to the error (if any), character count of the error position therein, error context, user query that led to the error (if any and enabled by log_min_error_statement), character count of the error position therein, location of the error in the PostgreSQL source code (if log_error_verbosity is set to verbose), application name, and backend type.","impact":"If log destination is not set to csv it would be harder to analyze reports.","report_fields":["DBInstanceArn"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/\n2. In the navigation pane, choose Parameter groups.\n3. Choose the required parameter group\n4. Choose Edit parameters.\n5. Under Parameters, in search bar type \"log_destination\".\n6. Choose csvlog.\n7. Choose Save changes.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-212":{"article":"Backups help you to recover more quickly from a security incident. They also strengthens the resilience of your systems. Aurora backtracking reduces the time to recover a database to a point in time. It does not require a database restore to do so.","impact":"Without enabling backtracking, it is impossible to quickly recover data in the event of failure. This may have a big impact on the resilience of your systems.","report_fields":["DBClusterArn"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nYou cannot enable backtracking on an existing cluster. Instead, you can create a clone that has backtracking enabled. Open the Amazon RDS console at https://console.aws.amazon.com/rds/. \n1. In the left navigation panel, under Amazon RDS, click Databases. \n2. Select your database.\n3. Choose your Aurora DB cluster from the list, and for Actions, choose Create clone.\n4. On the Create Clone page, perform the following actions:\n4.1 In the Settings section, within DB instance identifier box, provide a name for the primary instance of the clone database cluster. \n4.2 In the Additional configuration section, select Enable Backtrack to activate the feature, then specify the amount of time that you want to be able to backtrack, within the Target Backtrack window box.\n4.3 If required, configure any other settings for the clone database cluster.\n4.4 Choose Create clone to launch the Aurora clone of your chosen Aurora DB cluster. \n5. Once the cluster is created, replace the required endpoints within your application code to switch the source cluster with the new database cluster. \n6. You can now connect to your cloned database to verify that your database is identical to the one you cloned: check the databases, tables, users, and records you created to see they are included in the cloned database.\n7. Now you can remove the source Aurora database cluster from your AWS account in order to avoid further charges. To delete the necessary cluster, perform the following: \n7.1 Select the primary instance for the database cluster that you want to terminate. \n7.2 Click the Instance Actions button from the dashboard top menu and select Delete.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-077":{"article":"Real-time monitoring of API calls can be achieved by directing CloudTrail Logs to CloudWatch Logs and establishing corresponding metric filters and alarms. It is recommended that a metric filter and alarm be established for console logins that are not protected by multi-factor authentication (MFA). Monitoring of single-factor console logins will increase visibility into accounts that are not protected by MFA.","impact":"Lack of monitoring and logging of sign-ins without MFA can lead to insufficient response time to threats from an attacker. This, in turn, may result in data loss and degradation of the service. Monitoring single-factor console logins will increase the visibility of accounts not protected by MFA.","report_fields":["account_id","account_name"],"remediation":"Perform the following to setup the metric filter, alarm, SNS topic, subscription and trail using the AWS CLI: \n1. Create a log group.\naws logs create-log-group --log-group-name \n2. Create a log stream.\naws logs create-log-stream --log-group-name --log-stream-name \n3. Create a metric filter based on provided filter pattern which checks for unauthorized API calls and the taken from step 1.\naws logs put-metric-filter --log-group-name --filter-name `` --metric-transformations metricName=``,metricNamespace=,metricValue='1' --filter-pattern '{{($.eventName = \"ConsoleLogin\") && ($.additionalEventData.MFAUsed != \"Yes\") && ($.userIdentity.type = \"IAMUser\") && ($.responseElements.ConsoleLogin = \"Success\")}}'\n4. Create a topic to which notifications will be published.\naws sns create-topic --name \n5. Subscribe an endpoint to an Amazon SNS topic. If the endpoint type is HTTP/S or email, or if the endpoint and the topic are not in the same Amazon Web Services account, the endpoint owner must run the ConfirmSubscription action to confirm the subscription.\naws sns subscribe --topic-arn --protocol email --notification-endpoint \n6. Amazon SNS will send a subscription confirmation message to the endpoint. Confirm subscription to topic, by visiting the link in an email that you specified as notification endpoint.\n7. Create an alarm that is associated with the CloudWatch Logs Metric Filter created in step 3 and an SNS topic created in step 4. \naws cloudwatch put-metric-alarm --alarm-name `` --metric-name `` --statistic Sum --period 300 --threshold 1 --comparison-operator GreaterThanOrEqualToThreshold --evaluation-periods 1 --namespace --alarm-actions \n8. Create an S3 bucket to deliver log files to:\naws s3api create-bucket --bucket \n9. To deliver log files to an S3 bucket, CloudTrail must have the required permissions. The following policy allows CloudTrail to write log files to the bucket from supported regions. Replace myBucketName, [optionalPrefix]/, myAccountID, region, and trailName with the appropriate values for your configuration. \nCreate a file 'policy.json' with the following policy.\n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"AWSCloudTrailAclCheck20150319\",\n \"Effect\": \"Allow\",\n \"Principal\": {\"Service\": \"cloudtrail.amazonaws.com\"},\n \"Action\": \"s3:GetBucketAcl\",\n \"Resource\": \"arn:aws:s3:::\"\n },\n {\n \"Sid\": \"AWSCloudTrailWrite20150319\",\n \"Effect\": \"Allow\",\n \"Principal\": {\"Service\": \"cloudtrail.amazonaws.com\"},\n \"Action\": \"s3:PutObject\",\n \"Resource\": \"arn:aws:s3:::/[optionalPrefix]/AWSLogs//*\",\n \"Condition\": {\n \"StringEquals\": {\n \"s3:x-amz-acl\": \"bucket-owner-full-control\",\n \"aws:SourceArn\": \"arn:aws:cloudtrail:::trail/\"\n }\n }\n }\n ]\n}}\n10. Apply the Amazon S3 bucket policy to the Amazon S3 bucket created in step 8.\naws s3api put-bucket-policy --bucket --policy file://.json\n11. Create a role for CloudTrail that enables it to send events to the CloudWatch Logs log group. To create the JSON file that will contain the policy document, open a text editor and save the following policy contents in a file called 'assume_role_policy_document.json'. \n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Principal\": {\n \"Service\": \"cloudtrail.amazonaws.com\"\n },\n \"Action\": \"sts:AssumeRole\"\n }\n ]\n}}\n12. Create a role.\naws iam create-role --role-name --assume-role-policy-document file://.json\n13. Create the following role policy document for CloudTrail. This document grants CloudTrail the permissions required to create a CloudWatch Logs log stream in the log group you specify and to deliver CloudTrail events to that log stream. Save the policy document in a file called role-policy-document.json. Replace region, accountID, log_group_name, with the appropriate values for your configuration. \n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n\n \"Sid\": \"AWSCloudTrailCreateLogStream2014110\",\n \"Effect\": \"Allow\",\n \"Action\": [\n \"logs:CreateLogStream\"\n ],\n \"Resource\": [\n \"arn:aws:logs:::log-group::log-stream:_CloudTrail_*\"\n ]\n\n },\n {\n \"Sid\": \"AWSCloudTrailPutLogEvents20141101\",\n \"Effect\": \"Allow\",\n \"Action\": [\n \"logs:PutLogEvents\"\n ],\n \"Resource\": [\n \"arn:aws:logs:::log-group::log-stream:_CloudTrail_*\"\n ]\n }\n ]\n}}\n14. Run the following command to apply the policy to the role.\naws iam put-role-policy --role-name --policy-name cloudtrail-policy --policy-document file://.json\n15. Create a trail that specifies the settings for delivery of log data to an Amazon S3 bucket at is associated with the S3 bucket created in step 8, CloudWatch log group created in step 1 and IAM role created in step 12. \naws cloudtrail create-trail --include-global-service-events --is-multi-region-trail --name --s3-bucket-name --cloud-watch-logs-log-group-arn --cloud-watch-logs-role-arn \n16. Start the recording of Amazon Web Services API calls and log file delivery for a trail. For a trail that is enabled in all regions, this operation must be called from the region in which the trail was created.\naws cloudtrail start-logging --name ","multiregional":false,"service":"AWS Account"},"ecc-aws-339":{"article":"The MQ service releases engine version upgrades regularly to introduce new software features, bug fixes, security patches and performance improvements.\nAuto minor version upgrade option allows to have minor engine upgrades applied automatically to the replication instance during the maintenance window or immediately if you choose the Apply changes immediately option.","impact":"With an automatic update disabled, you are missing updates that may contain new software features, bug fixes, security patches and performance improvements.","report_fields":["BrokerArn"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/amazon-mq/\n2. In the navigation pane, choose Brokers.\n3. Choose the required brokers.\n4. Choose Edit.\n5. Under Maintenance, click on the 'Enable automatic minor version upgrades'.\n6. Choose Save.","multiregional":false,"service":"Amazon MQ"},"ecc-aws-385":{"article":"This policy identifies the Route table that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["RouteTableId"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon VPC at https://console.aws.amazon.com/vpc/.\n2. Click on the 'Route tables'.\n3. Click on the required route table.\n4. Open 'Tags' and click on the 'Manage tags'.\n5. Add new tag and save.","multiregional":false,"service":"Amazon Virtual Private Cloud"},"ecc-aws-515":{"article":"Security Hub collects security data from across AWS accounts, services, and supported third-party partner products and helps you analyze your security trends and identify the highest priority security issues.\nWhen Security Hub is enabled, it starts to collect, organize, and prioritize security findings from other security-oriented AWS cloud services such as intrusion detection findings from Amazon GuardDuty, vulnerability findings from Amazon Inspector, and sensitive data identification findings from Amazon Macie, or from third-party partner security tools.","impact":"With a disabled SecurityHub, it will be harder to organize and analyze findings.","report_fields":["account_id","account_name"],"remediation":"1. Open the Security Hub console at https://console.aws.amazon.com/securityhub/\n2. Click on the 'Go to Security Hub'.\n3. Click 'Enable Security Hub'.","multiregional":false,"service":"AWS Security Hub"},"ecc-aws-173":{"article":"By default, Elasticsearch exposes TCP port 9200 for REST API access and TCP port 9300 for internal cluster communication. Consider adding rules to allow connecting to TCP port 9200 from desired subnets, typically private subnets, and TCP port 9300 from the subnets where Elasticsearch nodes live.\nUnrestricted access (0.0.0.0/0) increases opportunities for malicious activity, such as unauthorized access, denial-of-service attacks, and loss of data.","impact":"Unrestricted access increases the opportunity for malicious activity such as unauthorized access, denial-of-service attacks, and loss of data.","report_fields":["GroupId","VpcId","OwnerId"],"remediation":"From Console: \n1. Login to AWS Management Console and open the EC2 console using https://console.aws.amazon.com/ec2/.\n2. In the navigation pane, choose Security Groups.\n3. Select the security group to update, choose Actions, and then choose Edit inbound rules to remove an inbound rule or Edit outbound rules to remove an outbound rule.\n4. Choose the Delete button to the right of the rule to delete.\n5. Choose Preview changes, Confirm.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-110":{"article":"Ensure that AWS ECS clusters are encrypted. Data encryption at rest prevents unauthorized users from accessing sensitive data on your AWS ECS clusters and associated cache storage systems.","impact":"Not encrypting data at rest can lead to unauthorized access to sensitive data.","report_fields":["clusterArn"],"remediation":"ECS remediation steps to encrypt new EBS volumes: \n1. From the AWS Management Console, select EC2. \n2. Under 'Elastic Block Store', select 'Volumes' \n3. Select 'Create Volume' \n4. Enter the required configuration for your Volume. \n5. Select the checkbox for 'Encrypt this volume' \n6. Select the KMS Customer Master Key (CMK) to be used under 'Master Key' \n7. Select 'Create Volume' There is no option to encrypt existing EBS volumes. \nTo encrypt existing EBS volumes, use the following steps to create a snapshot and encrypt the resulting new volume or snapshot using your default CMK:\n1. Select your unencrypted volume \n2. Select 'Actions' - 'Create Snapshot'\n3. When the snapshot is complete, select 'Snapshots' under 'Elastic Block Store'. Select your newly created snapshot \n4. Select 'Actions' - 'Copy' \n5. Check the box for 'Encryption' \n6. Select the CMK for KMS to use as required \n7. Click on 'Copy'\n8. Select the newly created snapshot \n9. Select 'Actions' - 'Create Volume' \n10. You will notice that the normal 'Encryption' option is set to 'True'. Because the snapshot is itself encrypted, this cannot be modified. The volume currently created from this snapshot will be encrypted","multiregional":false,"service":"Amazon Elastic Container Service"},"ecc-aws-397":{"article":"This policy identifies the CLoudfront distributions that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["ARN"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon EFS at https://console.aws.amazon.com/cloudfront/v3.\n2. Click on the required distribution.\n3. Open 'Tags' and click on the 'Manage tags'.\n4. Add new tag and save.","multiregional":true,"service":"Amazon CloudFront"},"ecc-aws-065":{"article":"Enforce HTTPS-only traffic between a CloudFront distribution and the origin. It is recommended to use HTTPS for secure communications between your CloudFront distribution and end users to guarantee encryption of traffic and prevent malicious actors from intercepting your traffic. \nNote: This rule runs on all the origins except S3 Buckets.","impact":"The unencrypted traffic between the edge servers and the custom origin can be disclosed by malicious users in case they are able to capture packets sent across Cloudfront Content Distribution Network (CDN).","report_fields":["ARN"],"remediation":"Perform the following for each distribution that failed the rule: \nOn the AWS CloudFront console, check the details for the distribution and ensure that the Viewer Protocol Policy is HTTPS Only. \nConfigure CloudFront to require HTTPS between viewers and CloudFront. \n1. Navigate to the the AWS console CloudFront dashboard.\n2. Select your distribution ID.\n3. Select the 'Behaviors' tab.\n4. Check the behavior you want to modify, then select Edit.\n5. Select 'HTTPS Only' or 'Redirect HTTP to HTTPS' for Viewer Protocol Policy.\n6. Select 'Yes', then select Edit.","multiregional":true,"service":"Amazon CloudFront"},"ecc-aws-508":{"article":"Using the latest version of Apache Airflow, you adhere to AWS best practices and receive the newest features, benefit from better performance and security and get the latest bug fixes.","impact":"Without keeping the MWAA up-to-date, it is possible to miss out on new software features, bug fixes, security patches, and performance improvements.","report_fields":["Arn"],"remediation":"1. Navigate to https://console.aws.amazon.com/mwaa/home.\n2. Open required environment.\n3. Click 'Edit'. \n4. Change 'Airflow version'\n5. Save changes","multiregional":false,"service":"Amazon Managed Workflows for Apache Airflow"},"ecc-aws-140":{"article":"Access keys are long-term credentials for an IAM user or the AWS account root user. You can use access keys to sign programmatic requests to the AWS CLI or AWS API (directly or using the AWS SDK)","impact":"Keeping more than one AWS IAM access key increases the risk of unauthorized access to your AWS resources and components.","report_fields":["Arn"],"remediation":"1. Sign in to the AWS Management Console and navigate to IAM dashboard at https://console.aws.amazon.com/iam/.\n2. In the left navigation panel, choose Users.\n3. Click on the IAM user name that you want to examine.\n4. On the IAM user configuration page, select Security Credentials tab.\n5. In Access Keys section, choose one access key that is less than 90 days old. This should be the only active key used by this IAM user to access AWS resources programmatically. Test your application(s) to make sure that the chosen access key is working.\n6. In the same Access Keys section, identify your non-operational access keys (other than the chosen one) and deactivate it by clicking the Make Inactive link.\n7. If you receive the Change Key Status confirmation box, click Deactivate to switch off the selected key.\n8. Repeat steps no. 3 - 7 for each IAM user in your AWS account","multiregional":true,"service":"AWS Identity and Access Management"},"ecc-aws-206":{"article":"Amazon SQL Server database engine logs (Error, Agent) should be enabled and sent to CloudWatch.\nDatabase logging provides detailed records of requests made to RDS. Database logs can assist with security and access audits and can help to diagnose availability issues.","impact":"With disabled logs for the database, it is harder to analyze statistics, diagnose issues, detect different types of attacks, and retain data for regulatory or legal purposes.","report_fields":["DBInstanceArn"],"remediation":"To publish SQL Server DB logs to CloudWatch Logs from the AWS Management Console:\n1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. In the navigation pane, choose Databases.\n3. Choose the DB instance that you want to modify.\n4. Choose Modify.\n5. Under Log exports, choose Error and Agent log types to start publishing to CloudWatch Logs.\n6. Log exports is available only for database engine versions that support publishing to CloudWatch Logs.\n7. Choose Continue. Then on the summary page, choose Modify DB Instance.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-416":{"article":"This policy identifies the IAM Roles that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["Arn"],"remediation":"1. Login to the AWS Console.\n2. Find the IAM service.\n3. Choose Roles without tags.\n4. Click on the \"Tags\".\n5. Click on the \"Manage tags\".\n6. Add tag.","multiregional":true,"service":"AWS Identity and Access Management"},"ecc-aws-463":{"article":"Ensure that your S3 buckets are using DNS-compliant bucket names in order to adhere to AWS best practices and to benefit from new S3 features such as S3 Transfer Acceleration, to benefit from operational improvements and to receive support for virtual-host style access to buckets. A DNS-compliant name is an S3 bucket name that doesn't contain periods (\".\").","impact":"To use virtual hosted\u2013style buckets with SSL or enable S3 Transfer Acceleration feature, the names of these buckets cannot contain periods (\".\").","report_fields":["Name"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n1. Login to the AWS S3 Console at https://s3.console.aws.amazon.com/s3/.\n2. Delete non compliant bucket or create a new one without periods (\".\").","multiregional":true,"service":"Amazon S3"},"ecc-aws-384":{"article":"This policy identifies the NetworkACL that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["NetworkAclId","OwnerId"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon VPC at https://console.aws.amazon.com/vpc/.\n2. Open 'Network ACLs'.\n3. Click on the required NACL.\n4. Open 'Tags' and click on the 'Manage tags'.\n5. Add new tag and save.","multiregional":false,"service":"Amazon Virtual Private Cloud"},"ecc-aws-503":{"article":"When creating a RDS cluster, you should change the default admin username 'admin' or 'postgres' to a unique value. Default usernames are public knowledge and should be changed upon configuration.","impact":"Well-known database Admin username could lead to unintended access.","report_fields":["DBClusterArn"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. In the navigation pane, choose Databases.\n3. Choose Create database.\n4. In Choose a database creation method, choose Standard create.\n5. In Engine options, choose Amazon Aurora.\n6. Under the 'Credentials Settings' change 'Master username'.\n7. Create database","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-516":{"article":"By enabling Event Notifications, you receive alerts on your Amazon S3 buckets when specific events occur. For example, you can be notified of object creation, object removal, and object restoration. These notifications can alert relevant teams to accidental or intentional modifications that may lead to unauthorized data access.","impact":"Not enabling S3 bucket event notifications can lead to slow or no response when specific events occur in your bucket.","report_fields":["Name"],"remediation":"To enable and configure event notifications for an S3 bucket\n 1. Sign in to the AWS Management Console and open the 'Amazon S3 console' at https://console.aws.amazon.com/s3/.\n 2. In the 'Buckets list', choose the name of the bucket that you want to enable events for.\n 3. Choose 'Properties'.\n 4. Navigate to the 'Event Notifications' section and choose 'Create event notification'.\n 5. In the 'General configuration' section, specify descriptive event name for your event notification. \n Optionally, you can also specify a prefix and a suffix to limit the notifications to objects with keys ending in the specified characters.\n a. Enter a description for the 'Event name'.\n b. (Optional) To filter event notifications by prefix, enter a 'Prefix'.\n c. (Optional) To filter event notifications by suffix, enter a 'Suffix'.\n 6. In the 'Event types' section, select one or more event types that you want to receive notifications for.\n 7. In the 'Destination' section, choose the event notification destination.\n Note: Before you can publish event notifications, you must grant the Amazon S3 principal the necessary permissions to call the relevant API. This is so that it can publish notifications to a Lambda function, SNS topic, or SQS queue.\n a. Select the destination type: Lambda Function, SNS Topic, or SQS Queue.\n b. After you choose your destination type, choose a function, topic, or queue from the list.\n c. Or, if you prefer to specify an Amazon Resource Name (ARN), select 'Enter ARN' and enter the ARN.\n 8. Choose 'Save changes', and Amazon S3 sends a test message to the event notification destination.","multiregional":true,"service":"Amazon S3"},"ecc-aws-161":{"article":"RDS event notifications use Amazon SNS to make you aware of changes in the availability or configuration of your RDS resources. These notifications allow for rapid response.\nDBParameterGroup:['configuration change'] Notification should be enabled for All DBParameterGroups.","impact":"Not enabling RDS parameter group event notifications can lead to slow or no response to a parameter group configuration change.","report_fields":["account_id","account_name"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. In the navigation pane, choose Event subscriptions.\n3. Under Event subscriptions, choose Create event subscription.\n4. In the Create event subscription dialog, do the following\n4.1. For Name, enter a name for the event notification subscription.\n4.2. For Send notifications to, choose an existing Amazon SNS ARN for an SNS topic.\n To use a new topic, choose create topic to enter the name of a topic and a list of recipients.\n4.3. For Source type, choose Parameter groups.\n4.4. Under Instances to include, select All parameter groups.\n4.5. Under Event categories to include, select Specific event categories. \n The control also passes if you select All event categories.\n4.5.1 Select configuration change.\n4.6. Choose Create.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-359":{"article":"To help debug issues related to client access to API Gateway REST API, you can enable Amazon CloudWatch Access Logs to log API calls. \nAccess logs contain details about who accessed your API and how they accessed it, which can also be used for troubleshooting API errors.","impact":"With disabled logging of API Gateway, it may be difficult to troubleshoot any issues that might happen with APIs or find who accessed API and how they accessed it.","report_fields":["stageName","restApiId"],"remediation":"Create an IAM role for logging to CloudWatch:\n1. In the AWS Identity and Access Management (IAM) console, in the left navigation pane, choose Roles.\n2. On the Roles pane, choose Create role.\n3. On the Create role page, do the following:\n 3.1 For Trusted entity type, choose AWS Service.\n 3.2 For use case, choose API Gateway.\n 3.3 Choose the API Gateway radio button.\n 3.4 Choose Next.\n4. Under Permissions Policies, note that the AWS managed policy AmazonAPIGatewayPushToCloudWatchLogs is selected by default. The policy has all the required permissions.\n5. Choose Next.\n6. Under Name, review and create, do the following:\n 6.1 For Role name, enter a meaningful name for the role.\n 6.2 (Optional) For Role description, edit the description to your preferences.\n 6.3 (Optional) Add tags.\n 6.4 Choose Create role.\n7. On the Roles pane, in the search bar, enter the name of the role that you created. Then, choose the role from the search results.\n8. On the Summary pane, copy the Role ARN. You'll need this Amazon Resource Name (ARN) in the next section.\n\nAdd the IAM role in the API Gateway console:\nNote: If you're developing multiple APIs across different AWS Regions, complete these steps in each Region.\n1. In the API Gateway console, on the APIs pane, choose the name of an API that you created.\n2. In the left navigation pane, at the bottom, below the Client Certificates section, choose Settings.\n3. Under Settings, for CloudWatch log role ARN, paste the IAM role ARN that you copied.\n4. Choose Save.\nNote: The console doesn't confirm that the ARN is saved.\n\nTurn on logging for your API and stage:\n1. In the API Gateway console, find the Stage Editor for your API.\n2. On the Stage Editor pane, choose the Logs/Tracing tab.\n3. Under Custom Access Logging, do the following to turn on access logging:\n 3.1 Choose the Enable Access Logging check box.\n 3.2 For Access Log Destination ARN, enter the ARN of a CloudWatch log group or an Amazon Kinesis Data Firehose stream.\n 3.3 Enter a Log Format. For guidance, choose CLF, JSON, XML, or CSV to see an example in that format.\n4. Choose Save Changes.\nNote: The console doesn't confirm that settings are saved.","multiregional":false,"service":"Amazon API Gateway"},"ecc-aws-373":{"article":"The authentication protocol between the Microsoft AD DCs and the RADIUS server supported are PAP, CHAP, MS-CHAPv1, and MS-CHAPv2. MS-CHAPv2 provides the strongest security of the options supported.","impact":"Insecure protocols can make the connection vulnerable to attackers and lead to a breach of confidentiality.","report_fields":["DirectoryId"],"remediation":"Perform the steps below to set the protocol to MS-CHAPv2 for multi-factor authentication:\nFor AWS Managed AD based environments:\n1. Log in to the Directory Service console at https://console.aws.amazon.com/directoryservicev2.\n2. In the left pane select Directories.\n3. On the Directory details page, select the Networking & security tab.\n4. In the Multi-factor authentication section, choose Actions, and then choose Edit.\n5. On the Enable multi-factor authentication (MFA) page change the value for 'Protocol' to MS-CHAPv2.\n6. Click Save.\n\nFor directory connector / self-managed AD environments:\n1. Log in to the AWS Workspaces console at https://console.aws.amazon.com/workspaces.\n2. In the left pane select Directories.\n3. Select the directory ID link for your AWS Managed Microsoft AD directory.\n4. On the Directories page, select the Actions > Update Details.\n5. In the Multi-factor authentication section modify the protocol using the dropdown menu to be MS-CHAPv2 from the currently selected option.\n6. Click Update and exit once settings are as desired.","multiregional":false,"service":"Amazon WorkSpaces Family"},"ecc-aws-509":{"article":"DAX encryption in transit increases confidentiality, ensuring that all requests and responses between the application and the cluster are encrypted by transport level security (TLS).","impact":"If encryption in transit is disabled traffic between your application and your DAX cluster is defended only with standard tools like security groups, subnet segmentation with Network ACLs, and VPC flow tracing.","report_fields":["ClusterArn"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nYou cannot enable or disable encryption in transit after a cluster is created. To create a cluster with enabled encryption in transit, perform the following actions: \n1. Sign in to the AWS Management Console and open the DynamoDB console at https://console.aws.amazon.com/dynamodb/.\n2. In the navigation panel on the left side of the console, under DAX, choose Clusters.\n3. Choose Create cluster. \n4. For Cluster name, enter a short name for your cluster. Choose the node type for all of the nodes in the cluster, and for the cluster size, use 3 nodes. \n5. In Encryption, make sure that 'Enable encryption in transit' is selected.\n6. After choosing the IAM role, subnet group, security groups, and cluster settings, choose Launch cluster.","multiregional":false,"service":"Amazon DynamoDB Accelerator"},"ecc-aws-575":{"article":"EBS volumes that are attached to instances continue to retain information and accrue charges, even when an instance is stopped.\nAmazon EBS snapshots are billed at a lower rate than active EBS volumes. You can minimize your Amazon EBS charges but still retain the information that's stored in Amazon EBS for later use. To do this, create a snapshot of the volume as a backup, and then delete the active volume. Later, when you need the information from the snapshot, use the snapshot to replace the EBS volume for use with your infrastructure.\nTo stop Amazon EBS-related charges, delete EBS volumes and snapshots that you don't need.","impact":"EBS volumes that are attached to instances continue to accrue charges, even when an instance is stopped. Keeping unused EBS volumes can result in escalating costs and cluttered AWS accounts.","report_fields":["VolumeId"],"remediation":"To retain the information that's stored in Amazon EBS, you can create a snapshot:\n 1. Open the Amazon EC2 console at https://console.aws.amazon.com/ec2/.\n 2. In the navigation pane, choose 'Snapshots', 'Create snapshot'.\n 3. For 'Resource type', choose 'Volume'.\n 4. For 'Volume ID', select the volume from which to create the snapshot.\n 5. The 'Encryption' field indicates the selected volume's encryption status. If the selected volume is encrypted, the snapshot is automatically encrypted using the same KMS key. If the selected volume is unencrypted, the snapshot is not encrypted.\n 6. (Optional) For 'Description', enter a brief description for the snapshot.\n 7. (Optional) To assign custom tags to the snapshot, in the 'Tags' section, choose 'Add tag', and then enter the key-value pair. You can add up to 50 tags.\n 8. Choose 'Create snapshot'.\n\nTo delete an unused EBS volume:\n 1. Open the Amazon EC2 console at https://console.aws.amazon.com/ec2/.\n 2. In the navigation pane, choose 'Volumes'.\n 3. Select the volume to delete and choose 'Actions', 'Delete volume'.\n Note: If 'Delete volume' is greyed out, the volume is attached to an instance. You must detach the volume from the instance before it can be deleted.\n 4. In the confirmation dialog box, choose 'Delete'.","multiregional":false,"service":"Amazon Elastic Block Store"},"ecc-aws-086":{"article":"Determine the specific permissions needed by your Lambda Functions, and then craft IAM policies for these permissions only, instead of full administrative privileges. There should not be any policies that grant blanket permissions ('*') to resources. \nIt is recommended and considered a standard security best practice to grant least privileges, that is granting only the permissions required to perform a task.","impact":"Providing full administrative privileges instead of restricted ones to the minimum set of permissions can expose your AWS resources to potentially unwanted actions.","report_fields":["FunctionArn"],"remediation":"1. For each Lambda Function that failed this rule, navigate to Policies on the IAM console.\n2. Search for the policy that failed the rule.\n3. Rework the permissions in the policy to grant positive permissions to specific AWS services or actions instead of blanket permissions using '*'.","multiregional":false,"service":"AWS Lambda"},"ecc-aws-329":{"article":"Removing unused Key Pairs will help you to adhere best security practices and protect against unapproved SSH access.","impact":"Not removing unused Key Pairs increase the risk of unauthorized access to AWS EC2 instances as these keys can be reassociated at any time, providing access to the wrong users.","report_fields":["KeyPairId"],"remediation":"To delete Key Pair: \n1. Login to the AWS Management Console and open the Amazon EC2 console using https://console.aws.amazon.com/ec2/ \n2. Under 'Network & Security', click on 'Key Pairs'.\n3. Click on the required key pair.\n4. Under the Actions, click on Delete.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-073":{"article":"If an EIP is not attached to an Amazon EC2 instance, this is an indication that it is no longer in use. Unless there is a business need to retain them, you should remove unused resources to maintain an accurate inventory of system components.","impact":"Keeping unused EIPs will inflict unnecessary monthly costs and eventually can prevent from creating new EIPs because of the limit.","report_fields":["AllocationId"],"remediation":"1. Open the Amazon EC2 console at https://console.aws.amazon.com/ec2/.\n2. In the navigation pane, choose Elastic IPs. \n3. Choose the Elastic IP address, choose Actions, and then choose Release addresses. \n4. When prompted, choose Release.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-523":{"article":"When a KMS key is scheduled for deletion, a mandatory waiting period is enforced to allow time to reverse the deletion, if it was scheduled in error. The default waiting period is 30 days, but it can be reduced to as short as 7 days when the KMS key is scheduled for deletion. During the waiting period, the scheduled deletion can be canceled and the KMS key will not be deleted.","impact":"KMS keys cannot be recovered once deleted. Data encrypted under a KMS key is also permanently unrecoverable if the KMS key is deleted.","report_fields":["KeyArn","AWSAccountId"],"remediation":"To cancel unintentional key deletion:\n1. Sign in to the AWS Management Console and open the KMS console at https://console.aws.amazon.com/iam. \n2. Choose required key. \n3. Click on the 'Key actions'.\n4. Click on the 'Cancel key deletion'.","multiregional":false,"service":"AWS Key Management Service"},"ecc-aws-261":{"article":"It is recommended to delete unused internet gateways or associate them (use them).","impact":"Keeping unused internet gateways eventually can prevent creating new internet gateways because of the limit.","report_fields":["InternetGatewayId","OwnerId"],"remediation":"1. Sign in to the AWS Management Console and open the VPC console at https://console.aws.amazon.com/vpc.\n2. In the navigation pane, choose Internet gateways. \n3. Choose the name of the intended Internet gateway. \n4. Choose the Actions and then choose either \"a\" or \"b\":\n a) Attach to VPC, and select intended VPC.\n b) Delete internet gateway.","multiregional":false,"service":"Amazon Virtual Private Cloud"},"ecc-aws-274":{"article":"Amazon Aurora database engine logs (Audit, Error, General, SlowQuery) should be enabled and sent to CloudWatch.\nRDS cluster logging provides detailed records of requests made to RDS databases. RDS cluster logs can assist with security and access audits and can help to diagnose availability issues.","impact":"With disabled logs for the RDS DB cluster, it is harder to analyze statistics, diagnose issues, detect different types of attacks, and retain data for regulatory or legal purposes.","report_fields":["DBClusterArn"],"remediation":"Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n1. In the navigation pane, choose Databases.\n2. Choose the DB cluster that you want to modify.\n3. Choose Modify.\n4. Under Log exports, choose Audit, Error, General, SlowQuery log types to start publishing to CloudWatch Logs.\n5. Log exports is available only for database engine versions that support publishing to CloudWatch Logs.\n6. Choose Continue. Then on the summary page, choose Modify.\n\nTo enable and publish Aurora logs to CloudWatch Logs from the AWS Management Console, set the following parameters in a custom Cluster Parameter Group:\n- general_log=1\n- slow_query_log=1\n- log_output = FILE\n\nTo create a custom cluster parameter group:\n1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. In the navigation pane, choose Parameter groups.\n3. Choose Create parameter group. The Create parameter group window appears.\n4. In the Parameter group family list, choose a DB parameter group family.\n5. In the Type list, choose DB Cluster Parameter Group.\n6. In Group name, enter the name of the new DB cluster parameter group.\n7. In Description, enter a description for the new DB parameter group.\n8. Choose Create.\n\nTo apply a new DB cluster parameter group to an RDS DB cluster:\n1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/.\n2. In the navigation pane, choose Databases.\n3. Choose the DB cluster that you want to modify.\n4. Choose Modify. The Modify DB cluster page appears.\n5. Under Database options, change the DB cluster parameter group.\n6. When you finish you changes, choose Continue. Check the summary of modifications.\n7. Choose Modify CLuster to save your changes.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-184":{"article":"Encrypting data at rest reduces the risk of data stored on disk being accessed by a user not authenticated to AWS. The encryption adds another set of access controls to limit the ability of unauthorized users to access to the data. For example, API permissions are required to decrypt the data before it can be read.","impact":"Encrypting data at rest reduces the risk of data stored on a disk being accessed by a user not authenticated to AWS. The encryption adds another set of access controls to limit the ability of unauthorized users to access the data.","report_fields":["ClusterArn"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nYou cannot enable or disable encryption at rest after a cluster is created. To create a cluster with enabled encryption at rest, perform the following actions: \n1. Sign in to the AWS Management Console and open the DynamoDB console at https://console.aws.amazon.com/dynamodb/.\n2. In the navigation pane on the left side of the console, under DAX, choose Clusters.\n3. Choose Create cluster. \n4. For Cluster name, enter a short name for your cluster. Choose the node type for all of the nodes in the cluster, and for the cluster size, use 3 nodes. \n5. In Encryption, make sure that Enable encryption is selected.\n6. After choosing the IAM role, subnet group, security groups, and cluster settings, choose Launch cluster.","multiregional":false,"service":"Amazon DynamoDB Accelerator"},"ecc-aws-491":{"article":"Always review cross-account attachment requests to your Transit gateway and approve them only if you trust the source.","impact":"Turning on AutoAcceptSharedAttachments configures a Transit Gateway to automatically accept any cross-account VPC attachment requests without verifying the request or the account the attachment is originating from which can lead to unauthorized access.","report_fields":["TransitGatewayArn"],"remediation":"1. Sign in to the Amazon VPC console at https://console.aws.amazon.com/vpc/.\n2. Choose Transit Gateways.\n3. Choose relevant gateway and click Actions and select Modify.\n4. Uncheck 'Auto accept shared attachments'.\n5. Save changes.","multiregional":false,"service":"AWS Transit Gateway"},"ecc-aws-393":{"article":"This policy identifies the ACM that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["CertificateArn","DomainName"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon ACM at https://console.aws.amazon.com/acm.\n2. Click on the required acm certificate.\n3. Open 'Tags' and click on the 'Manage tags'.\n4. Add new tag and save.","multiregional":false,"service":"AWS Certificate Manager"},"ecc-aws-411":{"article":"This policy identifies the FSX that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["ResourceARN"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon EFS at https://console.aws.amazon.com/sns/v3.\n2. Click on the required sns topic.\n3. Open 'Tags' and click on the 'Manage tags'.\n4. Add new tag and save.","multiregional":false,"service":"Amazon FSx"},"ecc-aws-027":{"article":"Ensure access is restricted from 0-65535 and from all other protocols and ports.","impact":"Unrestricted access increases the opportunity for malicious activity such as unauthorized access, denial-of-service attacks, and loss of data. \nA wide range of open ports inside EC2 security groups is a bad practice. It allows attackers to use port scanners and other probing techniques to identify services used on your instances and exploit their vulnerabilities.","report_fields":["GroupId","VpcId","OwnerId"],"remediation":"1. Login to the AWS Console \n2. Go to Security Group \n3. Go to the SG rules\n4. Click on the reported Firewall rule \n5. Click on Edit \n6. Modify the rule \n7. Click on Save","multiregional":false,"service":"Amazon EC2"},"ecc-aws-374":{"article":"Data events provide visibility into the resource operations performed on or within a resource. These are also known as data plane operations. Data events are often high-volume activities.\nUse the following data event resource types with basic event selectors: Amazon S3 object-level API activity, AWS Lambda function execution activity, Amazon DynamoDB object-level API activity on tables.\nBy default, trails do not log data events. Additional charges apply for data events.","impact":"If security critical information is not recorded, there will be no trail for forensic analysis, and discovering the cause of problems or the source of attacks may become more difficult or impossible.","report_fields":["TrailARN"],"remediation":"To enable Data events for all CloudTrail trails existing in your AWS account, perform the following: \n 1. Open the Amazon CloudTrail console at https://console.aws.amazon.com/cloudtrail/. \n 2. In the navigation pane, choose Trails.\n 3. Choose the trail that you want to reconfigure.\n 4. Click the Edit button, next to the Data events section.\n 5. Select data event type to log and configure according to your requirements.\n 5. To add another data type on which to log data events, choose Add data event type.\n 6. Choose Update trail.","multiregional":false,"service":"AWS CloudTrail"},"ecc-aws-256":{"article":"Ensure that encryption at rest is enabled within your Amazon Glue security configurations to meet regulatory requirements and prevent unauthorized users from getting access to the data written to Amazon S3. A security configuration is a set of security properties that can be used by AWS Glue to configure encryption for processes and resources associated with the security configuration such as jobs, crawlers and development endpoints. \nWith S3 encryption enabled, when you run crawlers, execute ETL jobs or start development endpoints, AWS Key Management Service (KMS) keys are used to encrypt your data at rest.","impact":"When encryption of data at rest is disabled, it can lead to unauthorized access to data.","report_fields":["Name"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nTo create and configure a new AWS Glue security configuration, perform the following actions: \n1. Sign in to AWS Management Console.\n2. Navigate to Glue service dashboard at https://console.aws.amazon.com/glue/.\n3. In the left navigation panel, under Security, choose Security configurations.\n4. Click Add security configuration to create new security configuration.\n5. On Add security configuration page, perform the following actions:\n 5.1 Provide a unique name for your new configuration in the Security configuration name box.\n 5.2 Select S3 encryption checkbox to enable at-rest encryption when writing data to Amazon S3, then choose the ARN of the AWS KMS key that you want to use for encryption.\n6. Reconfigure any existing Amazon Glue ETL jobs, crawlers, and development endpoints to make use of the new security configuration created at the previous step.","multiregional":false,"service":"AWS Glue"},"ecc-aws-291":{"article":"Ensure that a compliant lifecycle configuration is enabled for your Amazon Backup plans in order to meet compliance requirements when it comes to security and cost optimization. \nThe lifecycle defines when a backup is transitioned to cold storage and when it expires. AWS Backup transitions and expires backups automatically according to the lifecycle that you define. \nBackups that are transitioned to cold storage must be stored in cold storage for a minimum of 90 days. Therefore, on the console, the \u201cexpire after days\u201d setting must be 90 days longer than the \u201ctransition to cold after days\u201d setting. \nWhen backups reach the end of their lifecycle and are marked for deletion as part of your lifecycle policy, AWS Backup deletes the backups at a randomly chosen point over the following 8 hours.","impact":"If an AWS Backup lifecycle configuration is not used or has non-compliant values, it could violate security best practices and not meet regulatory compliance within your organization. It could also be economically ineffective.","report_fields":["BackupPlanName","BackupPlanId"],"remediation":"To implement compliant lifecycle configurations for your existing Amazon Backup plans, perform the following actions:\n1. Open the Amazon Backup console at https://console.aws.amazon.com/backup/.\n2. In the left navigation panel, select Backup plans.\n3. Select the backup plan that you want to configure.\n4. Under 'Backup rules' section, select the backup rule that you want to update, then click Edit.\n5. Under 'Transition to cold storage' select 'Days' from dropdown list and enter the number of days for the MoveToColdStorageAfterDays configuration parameter. You can't change this value after a copy has transitioned to cold storage. \n6. Repeat the step 5 for 'Retention period'. Note that Retention period setting must be at least 90 days after your Transition to cold storage setting. \n7. Click Save.","multiregional":false,"service":"AWS Backup"},"ecc-aws-494":{"article":"AWS Fargate platform versions refer to a specific runtime environment for Fargate task infrastructure, which is a combination of kernel and container runtime versions. New platform versions are released as the runtime environment evolves. For example, a new version may be released for kernel or operating system updates, new features, bug fixes, or security updates. Security updates and patches are deployed automatically for your Fargate tasks. If a security issue is found that affects a platform version, AWS patches the platform version.","impact":"With an automatic update disabled, you are missing updates that may contain new software features, bug fixes, security patches and performance improvements.","report_fields":["serviceArn"],"remediation":"To update ECS platform version: \n1. Open the Amazon ECS console at https://console.aws.amazon.com/ecs/\n2. Open required cluster.\n3. Click on the 'service'.\n4. Click on the 'Update'.\n5. Change 'Platform version' to 'LATEST'.\n6. Update service.","multiregional":false,"service":"Amazon Elastic Container Service"},"ecc-aws-497":{"article":"The Kubernetes project is rapidly evolving, introducing new features, design updates, and bug fixes. The community releases new Kubernetes minor versions (1.XX), as generally available approximately every three months. Each minor version is supported for approximately nine months after it is first released. As new Kubernetes versions become available for Amazon EKS, we recommend that you proactively update your clusters to use the latest available version.","impact":"Without keeping the Kubernetes container-orchestration system up-to-date, it is possible to miss out on new software features, bug fixes, security patches, and performance improvements.","report_fields":["arn"],"remediation":"1. Open the Amazon EKS console at https://console.aws.amazon.com/eks/home#/clusters.\n2. Choose the name of the Amazon EKS cluster to update and choose Update cluster version. \n3. For Kubernetes version, select the version to update your cluster to and choose Update. \n4. For Cluster name, type the name of your cluster and choose Confirm. The update takes several minutes to complete.","multiregional":false,"service":"Amazon Elastic Kubernetes Service"},"ecc-aws-064":{"article":"A VPC comes with a default security group the initial settings of which deny all inbound traffic, allow all outbound traffic, and allow all traffic between instances assigned to the security group. If you do not specify the security group when launching an instance, the instance is automatically assigned to this default security group.\nSecurity groups provide stateful filtering of ingress/egress network traffic to AWS resources. It is recommended that the default security group restrict all traffic.","impact":"Not blocking outbound traffic in default security group can increase opportunities for malicious activity such as compromising other resources in the VPC.","report_fields":["GroupId","VpcId","OwnerId"],"remediation":"Perform the following to implement the prescribed state:\n1. Identify AWS resources that exist within the default security group.\n2. Create a set of least privilege security groups for those resources.\n3. Place the resources in those security groups.\n4. Remove the resources noted in #1 from the default security group.\nSecurity Group State \n1. Login to the AWS Management Console at https://console.aws.amazon.com/vpc/home.\n2. Repeat the next steps for all VPCs, including the default VPC in each AWS region:\n3. In the left pane, click Security Groups.\n4. For each default security group, perform the following:\n5. Select the default security group.\n6. Click on the Inbound Rules tab.\n7. Remove any inbound rules.\n8. Click on the Outbound Rules tab.\n9. Remove any inbound rules.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-320":{"article":"'sec_protocol_error_trace_action' specifies the kind of logging the database server does when bad packets are received from a possibly malicious client, apart from the client receiving the error.","impact":"If 'sec_protocol_error_trace_action' is not set to LOG, there will be no trail for forensic analysis and discovering of the bad packets that were received from the client which could result in a denial-of-service condition.","report_fields":["DBInstanceArn"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/\n2. In the navigation pane, choose Parameter groups.\n3. Choose the required parameter group\n4. Choose Edit parameters.\n5. Under Parameters, in the search bar, type \"sec_protocol_error_trace_action\".\n6. Choose LOG\n7. Choose Save changes.\n8. Restart instance","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-449":{"article":"By using relocation in Amazon Redshift, you enable Amazon Redshift to move a cluster to another Availability Zone (AZ) without any loss of data or changes to your applications. With relocation, you can continue operations when there is an interruption of service on your cluster with minimal impact.","impact":"Disabled relocation in Amazon Redshift threaten the availability of stored data.","report_fields":["ClusterIdentifier"],"remediation":"To enable relocation for a new cluster:\n1. Sign in to the AWS Management Console and open the Amazon Redshift console at https://console.aws.amazon.com/redshift/.\n2. On the navigation menu, choose Clusters.\n3. Choose Create cluster to create a new cluster.\n4. Under Backup, for Cluster relocation, choose Enable. Relocation is disabled by default.\n5. Under Network and security, for Publicly accessible, accept the default Disable. If you choose Enable, Amazon Redshift returns an error.\n5. Choose Create cluster.\n\nModifying relocation for an existing cluster:\n1. Sign in to the AWS Management Console and open the Amazon Redshift console at https://console.aws.amazon.com/redshift/.\n2. On the navigation menu, choose Clusters.\n3. Choose the name of the cluster that you want to modify from the list. The cluster details page appears.\n4. Choose the Maintenance tab, then in the Backup details section choose Edit.\n5. Under Backup, choose Enable. Relocation is disabled by default.\n6. Choose the Properties tab, then in the Network and security section make sure to choose Disable for the Publicly accessible option.\n7. In the Network and security section, make sure to choose Disable for the Publicly accessible option.\n8. Choose Modify cluster.","multiregional":false,"service":"Amazon Redshift"},"ecc-aws-172":{"article":"By default, Elastic Beanstalk configures the proxy to forward requests to your application on port 8080. \nUnrestricted access (0.0.0.0/0) increases opportunities for malicious activity, such as unauthorized access, denial-of-service attacks, and loss of data.","impact":"Unrestricted access increases the opportunity for malicious activity such as unauthorized access, denial-of-service attacks, and loss of data.","report_fields":["GroupId","VpcId","OwnerId"],"remediation":"From Console: \n1. Login to AWS Management Console and open the EC2 console using https://console.aws.amazon.com/ec2/.\n2. In the navigation pane, choose Security Groups.\n3. Select the security group to update, choose Actions, and then choose Edit inbound rules to remove an inbound rule or Edit outbound rules to remove an outbound rule.\n4. Choose the Delete button to the right of the rule to delete.\n5. Choose Preview changes, Confirm.","multiregional":false,"service":"Amazon EC2"},"ecc-aws-231":{"article":"When logging_collector is enabled, the log_rotation_age parameter determines the maximum lifetime of an individual log file (depending on the value of log_filename). After this many minutes have elapsed, a new log file will be created via automatic log file rotation. Current best practices advise log rotation at least daily, but your organization's logging policy should dictate your rotation schedule.","impact":"For instance, if the rotation age is 1 day or any long period of time. Then there is a single log file for the entire period, and it will be difficult to look for specific queries when required.","report_fields":["DBInstanceArn"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/\n2. In the navigation pane, choose Parameter groups.\n3. Choose the required parameter group.\n4. Choose Edit parameters.\n5. Under Parameters, in search bar type \"log_rotation_age\".\n6. Type for example 60 in value field.\n7. Choose Save changes.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-535":{"article":"If the Classic Load Balancer uses HTTPS/SSL listeners it should use a certificate provided by AWS Certificate Manager.\nFor certificates in a Region supported by AWS Certificate Manager (ACM), it is recommend that you use ACM to provision, manage, and deploy your server certificates. In unsupported Regions, you must use IAM as a certificate manager. \nACM is the preferred tool to provision, manage, and deploy your server certificates. With ACM you can request a certificate or deploy an existing ACM or external certificate to AWS resources. Certificates provided by ACM are free and automatically renew.\nUse IAM as a certificate manager only when you must support HTTPS connections in a Region that is not supported by ACM. IAM securely encrypts your private keys and stores the encrypted version in IAM SSL certificate storage. IAM supports deploying server certificates in all Regions, but you must obtain your certificate from an external provider for use with AWS. Additionally, you cannot manage your certificates from the IAM Console.","impact":"Managing server certificates in AWS IAM Console is not supported.","report_fields":["LoadBalancerArn"],"remediation":"1. Open the Amazon EC2 console at https://console.aws.amazon.com/ec2/. \n2. On the navigation pane, under 'Load Balancing', choose 'Load Balancers'. \n3. Select the reported ELB. \n4. On the 'Listeners' tab, choose 'Edit'.\n5. For 'SSL Certificate', choose 'Change'.\n6. Select 'Choose an existing certificate from AWS Certificate Manager (ACM)', select the certificate from 'Certificate', and then choose 'Save'.\n7. Choose 'Save' to add the listeners you just configured.","multiregional":false,"service":"Amazon Elastic Load Balancing"},"ecc-aws-240":{"article":"Enabling the log_hostname setting causes the hostname of the connecting host to be logged in addition to the host's IP address for connection log messages. Disabling the setting causes only the connecting host's IP address to be logged, and not the hostname. Unless your organization's logging policy requires hostname logging, it is best to disable this setting so as not to incur the overhead of DNS resolution for each statement that is logged.","impact":"When log_hostname is enabled it can incur the overhead of DNS resolution for each statement that is logged.","report_fields":["DBInstanceArn"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/\n2. In the navigation pane, choose Parameter groups.\n3. Choose the required parameter group\n4. Choose Edit parameters.\n5. Under Parameters, in search bar type \"log_hostname\".\n6. Choose 0.\n7. Choose Save changes.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-249":{"article":"Content encoding allows API clients to request content to be compressed before being sent back in the response to an API request. \nThis reduces the amount of data that is sent from API Gateway to API clients and decreases the time it takes to transfer the data.","impact":"Without compression, API performance and bandwidth utilization will be worse than with the compression enabled.","report_fields":["id","name"],"remediation":"1. Sign in to the API Gateway console.\n2. Choose an existing API.\n3. In the primary navigation pane, choose Settings under the API you chose.\n4. Under the Content Encoding section in the Settings pane, select the Content Encoding enabled option to enable payload compression. Enter a number for the minimum compression size (in bytes) next to Minimum body size required for compression. To disable the compression, clear the Content Encoding enabled option.\n5. Choose Save Changes.","multiregional":false,"service":"Amazon API Gateway"},"ecc-aws-078":{"article":"Real-time monitoring of API calls can be achieved by directing CloudTrail Logs to CloudWatch Logs and establishing corresponding metric filters and alarms.\nIt is recommended that a metric filter and alarm be established for root login attempts. Monitoring for root account logins will provide visibility into the use of a fully privileged account and an opportunity to reduce the use of it.","impact":"Lack of monitoring and logging of root usage can lead to insufficient response or no response to any changes made by root user.","report_fields":["account_id","account_name"],"remediation":"Perform the following to setup the metric filter, alarm, SNS topic, subscription and trail using the AWS CLI: \n1. Create a log group.\naws logs create-log-group --log-group-name \n2. Create a log stream.\naws logs create-log-stream --log-group-name --log-stream-name \n3. Create a metric filter based on provided filter pattern which checks for Root account usage and the taken from step 1.\naaws logs put-metric-filter --log-group-name `` --filter-name `` --metric-transformations metricName=`` ,metricNamespace='CISBenchmark',metricValue=1 --filterpattern '{{ $.userIdentity.type = \"Root\" && $.userIdentity.invokedBy NOT EXISTS && $.eventType != \"AwsServiceEvent\" }}'\n4. Create a topic to which notifications will be published.\naws sns create-topic --name \n5. Subscribe an endpoint to an Amazon SNS topic. If the endpoint type is HTTP/S or email, or if the endpoint and the topic are not in the same Amazon Web Services account, the endpoint owner must run the ConfirmSubscription action to confirm the subscription.\naws sns subscribe --topic-arn --protocol email --notification-endpoint \n6. Amazon SNS will send a subscription confirmation message to the endpoint. Confirm subscription to topic, by visiting the link in an email that you specified as notification endpoint.\n7. Create an alarm that is associated with the CloudWatch Logs Metric Filter created in step 3 and an SNS topic created in step 4. \naws cloudwatch put-metric-alarm --alarm-name `` --metricname `` --statistic Sum --period 300 --threshold 1 --comparison-operator GreaterThanOrEqualToThreshold --evaluation-periods 1 --namespace 'CISBenchmark' --alarm-actions \n8. Create an S3 bucket to deliver log files to:\naws s3api create-bucket --bucket \n9. To deliver log files to an S3 bucket, CloudTrail must have the required permissions. The following policy allows CloudTrail to write log files to the bucket from supported regions. Replace myBucketName, [optionalPrefix]/, myAccountID, region, and trailName with the appropriate values for your configuration. \nCreate a file 'policy.json' with the following policy.\n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"AWSCloudTrailAclCheck20150319\",\n \"Effect\": \"Allow\",\n \"Principal\": {\"Service\": \"cloudtrail.amazonaws.com\"},\n \"Action\": \"s3:GetBucketAcl\",\n \"Resource\": \"arn:aws:s3:::\"\n },\n {\n \"Sid\": \"AWSCloudTrailWrite20150319\",\n \"Effect\": \"Allow\",\n \"Principal\": {\"Service\": \"cloudtrail.amazonaws.com\"},\n \"Action\": \"s3:PutObject\",\n \"Resource\": \"arn:aws:s3:::/[optionalPrefix]/AWSLogs//*\",\n \"Condition\": {\n \"StringEquals\": {\n \"s3:x-amz-acl\": \"bucket-owner-full-control\",\n \"aws:SourceArn\": \"arn:aws:cloudtrail:::trail/\"\n }\n }\n }\n ]\n}}\n10. Apply the Amazon S3 bucket policy to the Amazon S3 bucket created in step 8.\naws s3api put-bucket-policy --bucket --policy file://.json\n11. Create a role for CloudTrail that enables it to send events to the CloudWatch Logs log group. To create the JSON file that will contain the policy document, open a text editor and save the following policy contents in a file called 'assume_role_policy_document.json'. \n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Principal\": {\n \"Service\": \"cloudtrail.amazonaws.com\"\n },\n \"Action\": \"sts:AssumeRole\"\n }\n ]\n}}\n12. Create a role.\naws iam create-role --role-name --assume-role-policy-document file://.json\n13. Create the following role policy document for CloudTrail. This document grants CloudTrail the permissions required to create a CloudWatch Logs log stream in the log group you specify and to deliver CloudTrail events to that log stream. Save the policy document in a file called role-policy-document.json. Replace region, accountID, log_group_name, with the appropriate values for your configuration. \n{{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n\n \"Sid\": \"AWSCloudTrailCreateLogStream2014110\",\n \"Effect\": \"Allow\",\n \"Action\": [\n \"logs:CreateLogStream\"\n ],\n \"Resource\": [\n \"arn:aws:logs:::log-group::log-stream:_CloudTrail_*\"\n ]\n\n },\n {\n \"Sid\": \"AWSCloudTrailPutLogEvents20141101\",\n \"Effect\": \"Allow\",\n \"Action\": [\n \"logs:PutLogEvents\"\n ],\n \"Resource\": [\n \"arn:aws:logs:::log-group::log-stream:_CloudTrail_*\"\n ]\n }\n ]\n}}\n14. Run the following command to apply the policy to the role.\naws iam put-role-policy --role-name --policy-name cloudtrail-policy --policy-document file://.json\n15. Create a trail that specifies the settings for delivery of log data to an Amazon S3 bucket at is associated with the S3 bucket created in step 8, CloudWatch log group created in step 1 and IAM role created in step 12. \naws cloudtrail create-trail --include-global-service-events --is-multi-region-trail --name --s3-bucket-name --cloud-watch-logs-log-group-arn --cloud-watch-logs-role-arn \n16. Start the recording of Amazon Web Services API calls and log file delivery for a trail. For a trail that is enabled in all regions, this operation must be called from the region in which the trail was created.\naws cloudtrail start-logging --name ","multiregional":false,"service":"AWS Account"},"ecc-aws-457":{"article":"Apache Spark web UI makes it possible to monitor and debug AWS Glue ETL jobs running on the AWS Glue job system, and also Spark applications running on AWS Glue development endpoints. The Spark UI enables you to check the following for each job:\n - The event timeline of each Spark stage;\n - A directed acyclic graph (DAG) of the job;\n - Physical and logical plans for SparkSQL queries;\n - The underlying Spark environmental variables for each job.","impact":"With disabled Spark UI of Glue Job, it may be difficult to monitor and debug AWS Glue ETL jobs and Spark applications.","report_fields":["Name"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon Glue at https://console.aws.amazon.com/glue.\n2. Under the 'AWS Glue Studio' click on the 'Jobs'.\n3. Click on the required job.\n4. Open 'Job details' and click on the 'Advanced properties'.\n5. Enable the 'Spark UI' and choose 'Spark UI logs path' bucket.\n6. Save changes.","multiregional":false,"service":"AWS Glue"},"ecc-aws-404":{"article":"This policy identifies the EKS that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["arn"],"remediation":"1. Open the Amazon EKS console at https://console.aws.amazon.com/eks/home#/clusters.\n2. Click on the required eks cluster.\n3. Open 'Tags' and click on the 'Manage tags'.\n4. Add new tag and save.","multiregional":false,"service":"Amazon Elastic Kubernetes Service"},"ecc-aws-243":{"article":"Causes checkpoints and restart points to be logged in the server log. Some statistics are included in the log messages, including the number of buffers written and the time spent writing them.","impact":"In most cases, checkpoints are disrupting your RDS PostgreSQL database performance and can cause connections to stall for up to a few seconds while they occur. By disabling the \"log_checkpoints\" flag, you cannot get verbose logging of the checkpoint process for your PostgreSQL database instances to identify and troubleshoot sub-optimal PostgreSQL database performance.","report_fields":["DBInstanceArn"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/\n2. In the navigation pane, choose Parameter groups.\n3. Choose the required parameter group\n4. Choose Edit parameters.\n5. Under Parameters, in search bar type \"log_checkpoints\".\n6. Choose 1.\n7. Choose Save changes.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-046":{"article":"The root user account is the most privileged user in an AWS account. AWS Access Keys provide programmatic access to a given AWS account. It is recommended that all access keys associated with the root user account be removed.","impact":"Existing access keys can be used to compromise the account and gain unrestricted access to all AWS resources. Anyone who has your root access keys can gain unrestricted access to all the services within your AWS environment, including billing information.","report_fields":["account_id","account_name"],"remediation":"1. Sign in to the AWS Management Console as Root and open the IAM console at https://console.aws.amazon.com/iam/. \n2. Click on at the top right and select My Security Credentials from the drop-down list.\n3. On the pop out screen, click on Continue to Security Credentials.\n4. Click on Access Keys (Access Key ID and Secret Access Key).\n5. Under the Status column, if there are any Keys which are Active: \n- Click on Make Inactive - (Disable the key temporarily - it may be needed again);\n- Click on Delete - (Deleted keys cannot be recovered).","multiregional":true,"service":"AWS Account"},"ecc-aws-505":{"article":"When creating a Redshift cluster, you should change the default admin username 'awsuser' to a unique value. Default usernames are public knowledge and should be changed upon configuration.","impact":"Well-known database Admin username could lead to unintended access.","report_fields":["ClusterIdentifier"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n1. Open the Amazon Redshift console at https://console.aws.amazon.com/redshift/.\n2. Click on the 'Create cluster'.\n3. Under 'Database configuration' change 'Admin user name'.\n4. Create Cluster.","multiregional":false,"service":"Amazon Redshift"},"ecc-aws-247":{"article":"Each Transit Gateway attachment comes with routes that can be installed in one or more transit gateway route tables. When an attachment is propagated to a transit gateway route table, these routes are installed in the route table. \nTo manage the VPC environment and the transit gateway, it is preferable to manually establish the table propagation for the transit gateway.","impact":"With the Default Route Table Propagation enabled, you have less control over which routes of Transit Gateway attachments will be associated with the Transit Gateway Route Table, and with this option enabled it will be harder to implement granular custom routing rules and routing requirements.","report_fields":["TransitGatewayArn"],"remediation":"Perform the following steps in order to set 'Default route table propagation' to disable:\n1. Sign in to the Amazon VPC console at https://console.aws.amazon.com/vpc/.\n2. Choose Transit Gateways.\n3. Choose relevant gateway and click Actions and select Modify.\n4. Uncheck 'Default propagation route table'.\n5. Update route table with the necessary routes.","multiregional":false,"service":"AWS Transit Gateway"},"ecc-aws-306":{"article":"The PostgreSQL executor is responsible for executing the plan handed over by the PostgreSQL planner. The executor processes the plan recursively to extract the required set of rows. The 'log_executor_stats' flag controls the inclusion of PostgreSQL executor performance statistics in the PostgreSQL logs for each query.","impact":"The 'log_executor_stats' flag enables a crude profiling method for logging PostgreSQL executor performance statistics which even though can be useful for troubleshooting, it may increase the amount of logs significantly and have performance overhead.","report_fields":["DBInstanceArn"],"remediation":"1. Open the Amazon RDS console at https://console.aws.amazon.com/rds/\n2. In the navigation pane, choose Parameter groups.\n3. Choose the required parameter group\n4. Choose Edit parameters.\n5. Under Parameters, in search bar type \"log_executor_stats\".\n6. Choose 0.\n7. Choose Save changes.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-aws-402":{"article":"This policy identifies the DMS instances that do not have any Tags. Tags can be used for easy identification and search.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["ReplicationInstanceIdentifier","ReplicationInstanceArn"],"remediation":"1. Sign in to the AWS Admin Console and access the Amazon DMS at https://console.aws.amazon.com/dms/v2.\n2. Click on the required instance.\n3. Open 'Tags' and click on the 'Add'.\n4. Add new tag and save.","multiregional":false,"service":"AWS Database Migration Service"},"ecc-aws-518":{"article":"An S3 Lifecycle configuration is a set of rules that define actions that Amazon S3 applies to a group of objects.\nIt is recommended to configure lifecycle rules on your Amazon S3 bucket as these rules help you define actions that you want Amazon S3 to take during an object's lifetime. Configure their Amazon S3 Lifecycle, to manage your objects so that they are stored cost effectively throughout their lifecycle.","impact":"Without the S3 lifecycle configuration when versioning enabled, you are missing out on the opportunity to manage S3 objects so that they are stored cost-effectively throughout their lifecycle by moving data to more economical storage classes over time or expiring data based on the object age.","report_fields":["Name"],"remediation":"To create a lifecycle rule:\n 1. Sign in to the AWS Management Console and open the Amazon S3 console at https://console.aws.amazon.com/s3/.\n 2. In the 'Buckets' list, choose the name of the bucket that you want to create a lifecycle rule for.\n 3. Choose the 'Management' tab, and choose 'Create lifecycle rule'.\n 4. In 'Lifecycle rule name', enter a name for your rule. The name must be unique within the bucket.\n 5. Choose the scope of the lifecycle rule:\n - To apply this lifecycle rule to all objects with a specific prefix or tag, choose 'Limit the scope to specific prefixes or tags'.\n - To limit the scope by prefix, in 'Prefix', enter the prefix.\n - To limit the scope by tag, choose 'Add tag', and enter the tag key and value.\n - To apply this lifecycle rule to all objects in the bucket, choose 'This rule applies to all objects in the bucket', and choose 'I acknowledge that this rule applies to all objects in the bucket'.\n 6. To filter a rule by object size, you can check 'Specify minimum object size, Specify maximum object size', or both options.\n - When you're specifying a 'minimum object' size or 'maximum object size', the value must be larger than 0 bytes and up to 5TB. You can specify this value in bytes, KB, MB, or GB.\n - When you're specifying both, the maximum object size must be larger than the minimum object size.\n 7. Under Lifecycle rule actions, choose the actions that you want your lifecycle rule to perform:\n - Transition current versions of objects between storage classes\n - Transition previous versions of objects between storage classes\n - Expire current versions of objects\n - Permanently delete previous versions of objects\n - Delete expired delete markers or incomplete multipart uploads\n Depending on the actions that you choose, different options appear.\n 8. To transition current versions of objects between storage classes, under 'Transition current versions of objects between storage classes':\n a. In 'Storage class transitions', choose the storage class to transition to:\n - Standard-IA\n - Intelligent-Tiering\n - One Zone-IA\n - S3 Glacier Flexible Retrieval\n - Glacier Deep Archive\n b. In 'Days after object creation', enter the number of days after creation to transition the object.\n ! Important:\n When you choose the S3 Glacier Flexible Retrieval or Glacier Deep Archive storage class, your objects remain in Amazon S3. You cannot access them directly through the separate Amazon S3 Glacier service.\n 9. To transition non-current versions of objects between storage classes, under 'Transition non-current versions of objects between storage classes':\n a. In 'Storage class transitions', choose the storage class to transition to:\n - Standard-IA\n - Intelligent-Tiering\n - One Zone-IA\n - S3 Glacier Flexible Retrieval\n - Glacier Deep Archive\n b. In Days after object becomes non-current, enter the number of days after creation to transition the object.\n 10. To expire current versions of objects, under 'Expire previous versions of objects', in 'Number of days after object creation', enter the number of days.\n ! Important:\n In a non-versioned bucket the expiration action results in Amazon S3 permanently removing the object.\n 11. To permanently delete previous versions of objects, under 'Permanently delete noncurrent versions of objects', in 'Days after objects become noncurrent', enter the number of days. You can optionally specify the number of newer versions to retain by entering a value under 'Number of newer versions to retain'.\n 12. Under 'Delete expired delete markers or incomplete multipart uploads', choose' Delete expired object delete markers' and 'Delete incomplete multipart uploads'. Then, enter the number of days after the multipart upload initiation that you want to end and clean up incomplete multipart uploads.\n 13. Choose 'Create rule'.\n If the rule does not contain any errors, Amazon S3 enables it, and you can see it on the 'Management' tab under 'Lifecycle rules'.","multiregional":true,"service":"Amazon S3"},"ecc-aws-442":{"article":"Encryption in transit - is when requests between AWS AppSync, the cache, and data sources (except insecure HTTP data sources) are encrypted at the network level. You can enable the encryption in transit only when first enabling caching for your AWS AppSync API. \nBecause there is some processing needed to encrypt and decrypt the data at the endpoints, in-transit encryption can impact performance.","impact":"Without enabled encryption in transit, AppSync accepts a connection whether it uses SSL or not. This can lead to malicious activity such as man-in-the-middle attacks (MITM), intercepting, or manipulating network traffic.","report_fields":["name","arn"],"remediation":"Note: Affected resource must be redeployed to mitigate the issue.\n\nTo encrypt, cache you should delete the existing cache and recreate it.\n1. Navigate to the https://console.aws.amazon.com/appsync/home\n2. Select the API that for which you want enable encryption.\n3. At the left panel, choose 'Caching'.\n4. Click 'Delete cache'.\n5. Submit deletion.\n6. Select caching behavior:\n - Full request caching: All requests are fully cached.\n - Per-resolver caching: Individual resolvers that you specify are cached.\n7. In 'Cache settings' section, make sure you have selected 'Encryption in transit' option.\n8. Click 'Create cache'.","multiregional":false,"service":"AWS AppSync"},"ecc-aws-041":{"article":"This policy identifies the AWS RDS instance that does not have any Tags. Tags can be used for easy identification and searches.","impact":"Without a tagging schema, it is harder to organize resources available within your AWS environment. As your AWS environment is becoming more and more complex, it requires better management strategies.","report_fields":["DBInstanceArn"],"remediation":"1. Login to the AWS Console. \n2. Choose RDS Service.\n3. Select your RDS DB without tags and click on its name.\n4. Click on tab 'Tags' and add a new one.\n5. Click on Save.","multiregional":false,"service":"Amazon Relational Database Service"},"ecc-k8s-016":{"article":"Profiling allows for the identification of specific performance bottlenecks. It generates a significant amount of program data that could potentially be exploited to uncover system and program details. If you are not experiencing any bottlenecks and do not need the profiler for troubleshooting purposes, it is recommended to turn it off to reduce the potential attack surface.\nBy default, profiling is enabled.","impact":"Profiling generates a significant amount of program data that could potentially be exploited to uncover system and program details, increasing the potential attack surface.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Edit the API server pod specification file /etc/kubernetes/manifests/kube-apiserver.yaml on the Control Plane node and set the below parameter:\n--profiling=false","multiregional":true,"service":"Pod"},"ecc-k8s-067":{"article":"Containers run with a default set of capabilities as assigned by the Container Runtime. Capabilities outside this set can be added to containers which could expose them to risks of container breakout attacks.","impact":"Containers with additional capabilities beyond the default set can be used to perform actions that they would otherwise be prevented from doing. This can allow an attacker to gain unauthorized access to the host system, bypass security controls, or compromise other containers running on the same host.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Edit required pod YAML definition file and set the below parameters:\n If you have containers:\n spec:\n containers:\n securityContext:\n capabilities:\n drop:\n - ALL\n If you have initContainers:\n spec:\n initContainers:\n securityContext:\n capabilities:\n drop:\n - ALL\n If you have ephemeralContainers:\n spec:\n ephemeralContainers:\n securityContext:\n capabilities:\n drop:\n - ALL","multiregional":true,"service":"Pod"},"ecc-k8s-081":{"article":"Capabilities permit certain named root actions without giving full root access and are considered a fine-grained permissions model.\nIt's recommended all capabilities should be dropped from a pod, with only those required added back. There are a large number of capabilities, with SYS_ADMIN bounding most. SYS_ADMIN is a highly privileged access level equivalent to root access and should generally be avoided.","impact":"Using the highly privileged access level of SYS_ADMIN can pose a significant security risk, potentially granting root access to unauthorized users and compromising the entire system.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Edit required pod YAML definition file and remove SYS_ADMIN capability from containers.","multiregional":true,"service":"Pod"},"ecc-k8s-062":{"article":"A container running in the host's IPC namespace can use IPC to interact with processes outside the container.\nIf you need to run containers which require hostIPC, this should be defined in a separate policy and you should carefully check to ensure that only limited service accounts and users are given permission to use that policy.","impact":"When the 'host_ipc' flag is set to true, it allows containers to share the host's IPC (inter-process communication) namespace. This means that the containers can use IPC to communicate with processes outside of their own container, including other containers on the same host. It increases the risk of potential security vulnerabilities, as containers can potentially access sensitive data or execute privileged commands on the host.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Edit required pod YAML definition file and set the below parameters:\n spec:\n host_ipc: false","multiregional":true,"service":"Pod"},"ecc-k8s-044":{"article":"Note: This recommendation is applicable only for etcd clusters. If you are using only one etcd server in your environment then this recommendation is not applicable.\nWhen peer client authentication is enabled, etcd peers require clients to present a valid client certificate during TLS negotiation to establish a secure connection. This ensures that only authorized clients with valid client certificates signed by a trusted Certificate Authority (CA) can access the etcd cluster, improving the security of the etcd deployment.","impact":"When peer client authentication is not enabled then etcd peers do not require clients to present a valid client certificate during TLS negotiation, and any client can connect to the etcd cluster without authentication. This can create significant security vulnerabilities, as it allows unauthorized clients to access and modify data stored in etcd. Attackers can potentially compromise the etcd cluster, steal sensitive data, and execute malicious actions, compromising the confidentiality, integrity, and availability of Kubernetes deployments.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Edit the etcd pod specification file /etc/kubernetes/manifests/etcd.yaml on the Control Plane node and set the '--peer-client-cert-auth' parameter to 'true'.","multiregional":true,"service":"Pod"},"ecc-k8s-049":{"article":"Seccomp stands for secure computing mode and has been a feature of the Linux kernel since version 2.6.12. It can be used to sandbox the privileges of a process, restricting the calls it is able to make from userspace into the kernel. Kubernetes lets you automatically apply seccomp profiles loaded onto a node to your Pods and containers.\nSeccomp (secure computing mode) is used to restrict the set of system calls applications can make, allowing cluster administrators greater control over the security of workloads running in the cluster. Kubernetes disables seccomp profiles by default for historical reasons. You should enable it to ensure that the workloads have restricted actions available within the container.\nThe default seccomp profile provides a sane default for running containers with seccomp and disables around 44 system calls out of 300+. It is moderately protective while providing wide application compatibility. If the docker/default seccomp profile is too restrictive for you, you would have to create/manage your own seccomp profiles.\nBy default, seccomp profile is set to unconfined which means that no seccomp profiles are enabled.","impact":"If the seccomp profile is not set to the recommended value, it could allow attackers to bypass security controls and execute malicious code within the container.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Use security context to enable the docker/default seccomp profile in your pod definitions.\nFollow the Kubernetes documentation and set the seccomp profile in a configuration file: https://kubernetes.io/docs/tutorials/security/seccomp/\nEdit the pod specification file and set the below parameters:\n securityContext:\n seccompProfile:\n type: RuntimeDefault","multiregional":true,"service":"Pod"},"ecc-k8s-024":{"article":"etcd is a highly-available key value store used by Kubernetes deployments for persistent storage of all of its REST API objects. These objects are sensitive in nature and should be protected by client authentication. This requires the API server to identify itself to the etcd server using a client certificate and key.","impact":"Broken transport security between apiserver and etcd services (non-HTTPS).","report_fields":["metadata.name","metadata.namespace"],"remediation":"Follow the Kubernetes documentation and set up the TLS connection between the apiserver and etcd. \nThen, edit the API server pod specification file /etc/kubernetes/manifests/kube-apiserver.yaml on the master node and set the etcd certificate and key file parameters:\n- --etcd-certfile=\n- --etcd-keyfile=\nReferences:\nhttps://kubernetes.io/docs/setup/best-practices/certificates/","multiregional":true,"service":"Pod"},"ecc-k8s-032":{"article":"Profiling allows for the identification of specific performance bottlenecks. It generates a significant amount of program data that could potentially be exploited to uncover system and program details. If you are not experiencing any bottlenecks and do not need the profiler for troubleshooting purposes, it is recommended to turn it off to reduce the potential attack surface.","impact":"Profiling can generate a significant amount of data that may contain sensitive information about the system and program. This data could potentially be exploited by attackers to gain insights into the system and to launch attacks.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Edit the Controller Manager pod specification file /etc/kubernetes/manifests/kube-controller-manager.yaml on the Control Plane node and set the 'profiling' parameter to 'false'.","multiregional":true,"service":"Pod"},"ecc-k8s-038":{"article":"Profiling allows for the identification of specific performance bottlenecks via web interface 'host:port/debug/pprof/'. It generates a significant amount of program data that could potentially be exploited to uncover system and program details. If you are not experiencing any bottlenecks and do not need the profiler for troubleshooting purposes, it is recommended to turn it off to reduce the potential attack surface.\nCaution: This parameter is ignored if a config file is specified in '--config'. If '--config' set, this rule may return incorrect result, if config file has this parameter 'enableProfiling' set to true.","impact":"Profiling generates a significant amount of program data that could potentially be exploited to uncover system and program details.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Edit the Scheduler pod specification file /etc/kubernetes/manifests/kube-scheduler.yaml file on the Control Plane node and set the below parameter:\n--profiling=false","multiregional":true,"service":"Pod"},"ecc-k8s-002":{"article":"The token-based authentication utilizes static tokens to authenticate requests to the apiserver. The tokens are stored in clear-text in a file on the apiserver, and cannot be revoked or rotated without restarting the apiserver. Hence, do not use static token-based authentication.","impact":"The tokens are stored in clear-text in a file on the apiserver, and cannot be revoked or rotated without restarting the apiserver.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Follow the documentation and configure alternate mechanisms for authentication: https://kubernetes.io/docs/reference/access-authn-authz/authentication/.\nThen, edit the API server pod specification file /etc/kubernetes/manifests/kubeapiserver.yaml on the master node and remove the --token-auth-file= parameter.","multiregional":true,"service":"Pod"},"ecc-k8s-013":{"article":"When you create a pod, if you do not specify a service account, it is automatically assigned the default service account in the same namespace. You should create your own service account and let the API server manage its security tokens.\nBy default, ServiceAccount is set.","impact":"If the ServiceAccount admission control plugin is disabled, your new pod is not automatically assigned a default service, and the pod will not have access to the Kubernetes API server.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Follow the documentation and create ServiceAccount objects as per your environment: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/. Then, edit the API server pod specification file /etc/kubernetes/manifests/kube-apiserver.yaml on the master node and ensure that the --disable-admission-pluginsparameter is set to a value that does not include ServiceAccount.","multiregional":true,"service":"Pod"},"ecc-k8s-070":{"article":"Containers may run as any Linux user. Containers which run as the root user, whilst constrained by Container Runtime security features still have a escalated likelihood of container breakout. Ideally, all containers should run as a defined non-UID 0 user.","impact":"Allowing containers to run as the root user poses security risks such as increased potential for container breakout, unauthorized access to host systems or other containers, and unrestricted privileges that could lead to malicious actions.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Edit required pod YAML definition file and set the below parameters:\nFor pod:\n spec:\n securityContext:\n runAsNonRoot: true\n runAsUser: \nFor containers:\n spec:\n containers:\n securityContext:\n runAsNonRoot: true\n runAsUser: \nFor initContainers:\n spec:\n initContainers:\n securityContext:\n runAsNonRoot: true\n runAsUser: \nFor ephemeralContainers:\n spec:\n ephemeralContainers:\n securityContext:\n runAsNonRoot: true\n runAsUser: ","multiregional":true,"service":"Pod"},"ecc-k8s-092":{"article":"Basic authentication uses plaintext credentials for authentication. Currently, the basic authentication credentials last indefinitely, and the password cannot be changed without restarting the API server. The basic authentication is currently supported for convenience. Hence, basic authentication should not be used.\nBy default, --basic-auth-file argument is not set and OAuth authentication is configured.\nOpenShift provides it's own fully integrated authentication and authorization mechanism. The APIserver is protected by either requiring an OAuth token issued by the platform's integrated OAuth server or signed certificates.","impact":"The basic authentication credentials last indefinitely, and the password cannot be changed without restarting the API server.","report_fields":["metadata.name","metadata.namespace"],"remediation":"None required. --basic-auth-file cannot be configured on OpenShift.","multiregional":true,"service":"ConfigMap"},"ecc-k8s-012":{"article":"The SecurityContextDeny admission controller can be used to deny pods which make use of some SecurityContext fields which could allow for privilege escalation in the cluster. This should be used where PodSecurityPolicy is not in place within the cluster.\nSecurityContextDeny can be used to provide a layer of security for clusters which do not have PodSecurityPolicy enabled.\nBy default, SecurityContextDeny is not set.","impact":"Without the SecurityContextDeny admission control plugin, pods that use some SecurityContext fields can lead to privilege escalation in the cluster.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Edit the API server pod specification file /etc/kubernetes/manifests/kube-apiserver.yaml on the Control Plane node and set the --enable-admission-plugins parameter to include SecurityContextDeny, unless PodSecurityPolicy is already in place.\n--enable-admission-plugins=...,SecurityContextDeny,...","multiregional":true,"service":"Pod"},"ecc-k8s-061":{"article":"A Windows container making use of the 'hostProcess' flag can interact with the underlying Windows cluster node. As per the Kubernetes documentation, this provides \"privileged access\" to the Windows node.\nThese containers operate as normal processes but have access to the host network namespace, storage, and devices when given the appropriate user privileges. HostProcess containers can be used to deploy network plugins, storage configurations, device plugins, kube-proxy, and other components to Windows nodes without the need for dedicated proxies or the direct installation of host services.","impact":"Enabling HostProcess in Windows containers pose a high security risk, and can potentially lead to privilege escalation, malware infections, and increased potential for unauthorized access and data breaches.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Recreate a pod that violates this rule, and before applying it make sure that the setting 'security_context.windows_options.host_process' is removed from the pod definition or is not set to True.","multiregional":true,"service":"Pod"},"ecc-k8s-008":{"article":"RBAC is a powerful and flexible authorization mechanism that allows cluster administrators to define granular access policies that control who can access and perform actions on Kubernetes resources.","impact":"Without RBAC, any authenticated user or service account in the cluster will have unrestricted access to all resources and actions available in the API server. This means that an attacker who gains access to a user's or service account's credentials can potentially access and modify any resource in the cluster, including sensitive data such as secrets and configuration files. It is also difficult to enforce the principle of least privilege, which is a fundamental security concept that requires that users and applications are granted only the minimum privileges necessary to perform their tasks. This can lead to overprivileged accounts and applications that can access and modify resources that they should not be able to access, increasing the attack surface of the cluster.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Edit the etcd pod specification file /etc/kubernetes/manifests/kube-apiserver.yaml on the Control Plane node and set the '--authorization-mode' parameter to 'RBAC'.","multiregional":true,"service":"Pod"},"ecc-k8s-030":{"article":"TLS ciphers have had a number of known vulnerabilities and weaknesses, which can reduce the protection provided by them. By default Kubernetes supports a number of TLS ciphersuites including some that have security concerns, weakening the protection provided.","impact":"The use of weak TLS ciphers in Kubernetes can compromise the security of the system, leaving it vulnerable to a range of attacks. It's essential to use strong TLS ciphersuits to prevent potential data breaches and unauthorized access.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Edit the API server pod specification file /etc/kubernetes/manifests/kube-apiserver.yaml on the Control Plane node and set the '--tls-cipher-suites' parameter to 'TLS_AES_128_GCM_SHA256,TLS_AES_256_GCM_SHA384,TLS_CHACHA20_POLY1305_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, TLS_RSA_WITH_3DES_EDE_CBC_SHA,TLS_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_128_GCM_SHA256,TLS_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_256_GCM_SHA384'.","multiregional":true,"service":"Pod"},"ecc-k8s-005":{"article":"The connections from the API server to the kubelet are used for:\n - Fetching logs for pods.\n - Attaching (usually through kubectl) to running pods.\n - Providing the kubelet's port-forwarding functionality.\nThese connections terminate at the kubelet's HTTPS endpoint. By default, the API server does not verify the kubelet's serving certificate, which makes the connection subject to man-in-the-middle attacks and unsafe to run over untrusted and/or public networks.","impact":"By default, the API server does not verify the kubelet's serving certificate, which makes the connection subject to man-in-the-middle attacks and unsafe to run over untrusted and/or public networks.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Follow the Kubernetes documentation and setup the TLS connection between the apiserver and kubelets: https://v1-25.docs.kubernetes.io/docs/reference/access-authn-authz/kubelet-authn-authz/\nThen, edit the API server pod specification file /etc/kubernetes/manifests/kube-apiserver.yaml on the Control Plane node and set the --kubelet-certificate-authority parameter to the path to the cert file for the certificate authority.\n--kubelet-certificate-authority=","multiregional":true,"service":"Pod"},"ecc-k8s-072":{"article":"Using a read-only root file system for Kubernetes pods provides several benefits. It prevents any process from writing to the file system, which can help mitigate certain types of security attacks.","impact":"If a read-only root file system is not used it can make the system more vulnerable to security attacks, as attackers may be able to modify the file system to gain escalated privileges or execute malicious code. Additionally, a writable file system can make the system less stable, as changes to the file system can introduce instability and increase the likelihood of failures. Finally, a writable file system can make the system less predictable, as unexpected changes can occur that can lead to errors and failures.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Edit required pod YAML definition file and set the below parameters:\n If you have containers:\n spec:\n containers:\n securityContext:\n readOnlyRootFilesystem: true\n If you have initContainers:\n spec:\n initContainers:\n securityContext:\n readOnlyRootFilesystem: true\n If you have ephemeralContainers:\n spec:\n ephemeralContainers:\n securityContext:\n readOnlyRootFilesystem: true","multiregional":true,"service":"Pod"},"ecc-k8s-007":{"article":"The Node authorization mode only allows kubelets to read Secret, ConfigMap, PersistentVolume, and PersistentVolumeClaim objects associated with their nodes.","impact":"The Node authorization mode provides a minimum necessary permission on a need-to-know basis for kubelet to do its work. \nWithout enabling this mode, kubelet may use a more permissive mode that grants excessive access to cluster resources.\nThis lack of restricted permissions can result in several security risks, including privilege escalation, unauthorized access to sensitive data, unauthorized modification or deletion of Kubernetes resources, and potential compromise of the entire cluster.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Edit the API server pod specification file /etc/kubernetes/manifests/kube-apiserver.yaml on the Control Plane node and set the --authorization-mode parameter to a value that includes Node. One such example could be as below:\n--authorization-mode=Node,RBAC","multiregional":true,"service":"Pod"},"ecc-k8s-035":{"article":"Processes running within pods that need to contact the API server must verify the API server's serving certificate. Failing to do so could be a subject to man-in-the-middle attacks.\nProviding the root certificate for the API server's serving certificate to the controller manager with the --root-ca-file argument allows the controller manager to inject the trusted bundle into pods so that they can verify TLS connections to the API server.\nBy default, --root-ca-file is not set.","impact":"Without proper certificate verification, there is a risk of falling victim to man-in-the-middle attacks, leading to severe security breaches and unauthorized access to sensitive data.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Edit the Controller Manager pod specification file /etc/kubernetes/manifests/kube-controller-manager.yaml on the Control Plane node and set the --root-ca-file parameter to the certificate bundle file`.","multiregional":true,"service":"Pod"},"ecc-k8s-064":{"article":"A container running in the host's network namespace could access the local loopback device, and could access network traffic to and from other pods.\nIf you need to run containers which require access to the host's network namesapces, this should be defined in a separate policy and you should carefully check to ensure that only limited service accounts and users are given permission to use that policy.","impact":"When the hostNetwork flag is set to true, a container can access the local loopback device and network traffic to and from other pods. This increases the risk of security vulnerabilities and compromises the isolation between containers.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Edit required pod YAML definition file and set the below parameters:\n spec:\n host_network: false","multiregional":true,"service":"Pod"},"ecc-k8s-052":{"article":"Kubernetes provides a default namespace, where objects are placed if no namespace is specified for them. Placing objects in this namespace makes application of RBAC and other controls more difficult. Resources in a Kubernetes cluster should be segregated by namespace, to allow for security controls to be applied at that level and to make it easier to manage resources.","impact":"Placing objects in default namespace makes application of RBAC and other security controls more difficult.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Ensure that namespaces are created to allow for appropriate segregation of Kubernetes resources and that all new resources are created in a specific namespace.\nTo remediate this issue, you should recreate existing deployment resource and attach it to non-default namespace.\n1. If you do not have namespace, create one using command: 'kubectl create namespace '.\n2. Before creating a new resource in different namespace, you should delete it from default namespace. Because you cannot move Kubernetes resources between namespaces. They must be recreated.\nUse the following command to delete a desired deployment from default namespace: 'kubectl delete -f .yaml'.\nWhere '.yaml' is a file that describes the deployment you want to recreate.\n3. Then in a file .yaml, change parameter 'metadata.namespace' to non-default namespace. For example, to the namespace name that was created at step 1.\n4. Apply changes with command: 'kubectl apply -f .yaml'.","multiregional":true,"service":"Deployment"},"ecc-k8s-066":{"article":"Containers run with a default set of capabilities as assigned by the Container Runtime. By default this can include potentially dangerous capabilities. With Docker as the container runtime the NET_RAW capability is enabled which may be misused by malicious containers. Ideally, all containers should drop this capability.","impact":"Allowing containers with the NET_RAW capability enabled can pose significant security risks, allowing for potential exploitation of networking exploits within the cluster, leading to data breaches, unauthorized access, and other security incidents.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Edit required pod YAML definition file and set the below parameters:\nFor containers\n spec:\n containers:\n securityContext:\n capabilities:\n drop:\n - NET_RAW\nfor initContainers:\n spec:\n initContainers:\n securityContext:\n capabilities:\n drop:\n - NET_RAW\nfor ephemeralContainers:\n spec:\n ephemeralContainers:\n securityContext:\n capabilities:\n drop:\n - NET_RAW","multiregional":true,"service":"Pod"},"ecc-k8s-031":{"article":"Garbage collection is important to ensure sufficient resource availability and avoiding degraded performance and availability. In the worst case, the system might crash or just be unusable for a long period of time. The current setting for garbage collection is 12,500 terminated pods which might be too high for your system to sustain. Based on your system resources and tests, choose an appropriate threshold value to activate garbage collection.","impact":"Failing to configure appropriate garbage collection thresholds may result in an unresponsive system or long periods of unusability.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Edit the Contoller Manager pod specification file /etc/kubernetes/manifests/kube-controller-manager.yaml on the Control Plane node and set the ' --terminated-pod-gc-threshold' parameter to '10' or an appropriate value.","multiregional":true,"service":"Pod"},"ecc-k8s-026":{"article":"API server communication contains sensitive parameters that should remain encrypted in transit. Configure the API server to serve only HTTPS traffic. If --client-ca-file argument is set, any request presenting a client certificate signed by one of the authorities in the client-ca-file is authenticated with an identity corresponding to the CommonName of the client certificate.","impact":"Without proper client certificate validation, an attacker may be able to connect to the Kubernetes API server and access resources or perform unauthorized actions. If an attacker is able to intercept traffic between the client and the Kubernetes API server, he may be able to inject his own certificate or spoof the client's certificate to gain unauthorized access.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Follow the Kubernetes documentation and set up the TLS connection on the apiserver.\nThen, edit the API server pod specification file /etc/kubernetes/manifests/kubeapiserver.yaml on the master node and set the client certificate authority file:\n--client-ca-file=\nReferences:\nhttps://kubernetes.io/docs/setup/best-practices/certificates/","multiregional":true,"service":"Pod"},"ecc-k8s-017":{"article":"Auditing the Kubernetes API Server provides a security-relevant chronological set of records documenting the sequence of activities that have affected system by individual users, administrators or other components of the system. Even though currently, Kubernetes provides only basic audit capabilities, it should be enabled. You can enable it by setting an appropriate audit log path.\nBy default, auditing is not enabled.","impact":"Not setting the audit log path and enabling auditing can leave organizations vulnerable to security threats and hinder their ability to detect and respond to security incidents.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Edit the API server pod specification file /etc/kubernetes/manifests/kube-apiserver.yaml on the Control Plane node and set the --audit-log-path parameter to a suitable path and file where you would like audit logs to be written, for example:\n--audit-log-path=/var/log/apiserver/audit.log\nFollow the Kubernetes documentation and configure Auditing: https://v1-25.docs.kubernetes.io/docs/tasks/debug/debug-cluster/audit/.","multiregional":true,"service":"Pod"},"ecc-k8s-071":{"article":"Do not generally permit containers with capabilities.\nContainers run with a default set of capabilities as assigned by the Container Runtime. Capabilities are parts of the rights generally granted on a Linux system to the root user. In many cases applications running in containers do not require any capabilities to operate, so from the perspective of the principal of least privilege use of capabilities should be minimized.\nBy default, there are no restrictions on the creation of containers with additional capabilities.","impact":"If containers with capabilities are generally permitted in Kubernetes Pods, it may introduce unnecessary security risks, as containers could potentially have excessive privileges that could be exploited by attackers, leading to potential privilege escalation attacks and increased vulnerability to unauthorized actions.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Review the use of capabilities in applications running on your cluster. Where applications do not require any Linux capabilities to operate consider editing the required pod YAML definition file and set the below parameters:\nFor containers:\n spec:\n containers:\n securityContext:\n capabilities:\n drop:\n - ALL\nFor initContainers:\n spec:\n initContainers:\n securityContext:\n capabilities:\n drop:\n - ALL\nFor ephemeralContainers:\n spec:\n ephemeralContainers:\n securityContext:\n capabilities:\n drop:\n - ALL","multiregional":true,"service":"Pod"},"ecc-k8s-087":{"article":"The Kubernetes API stores secrets, which may be service account tokens for the Kubernetes API or credentials used by workloads in the cluster. Access to these secrets should be restricted to the smallest possible group of users to reduce the risk of privilege escalation. Inappropriate access to secrets stored within the Kubernetes cluster can allow for an attacker to gain additional access to the Kubernetes cluster or external resources whose credentials are stored as secrets.","impact":"Inappropriate access to secrets stored within the Kubernetes cluster can allow for an attacker to gain additional access to the Kubernetes cluster or external resources whose credentials are stored as secrets.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Where possible, remove get, list and watch access to secret objects in the cluster.","multiregional":true,"service":"Role"},"ecc-k8s-059":{"article":"Mounting service account tokens inside pods can provide an avenue for privilege escalation attacks where an attacker is able to compromise a single pod in the cluster. Avoiding mounting these tokens removes this attack avenue.","impact":"If 'automountServiceAccountToken' is true, it's increases the attack surface of the cluster. If an attacker gains control of the pod, they can access the service account token and use it to escalate their privileges, potentially compromising other resources in the cluster. This can lead to serious security breaches and data loss.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Edit required pod YAML definition file and set the below parameters:\n spec:\n automountServiceAccountToken: false","multiregional":true,"service":"Pod"},"ecc-k8s-023":{"article":"By default, if no --service-account-key-file is specified to the apiserver, it uses the private key from the TLS serving certificate to verify service account tokens. To ensure that the keys for service account tokens could be rotated as needed, a separate public/private key pair should be used for signing service account tokens. Hence, the public key should be specified to the apiserver with --service-account-key-file.","impact":"Service account tokens may have permissions that are beyond what the pod itself is authorized to perform. Without proper signing of service account tokens, an attacker may be able to use compromised pods to escalate privileges and gain access to sensitive resources.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Edit the API server pod specification file /etc/kubernetes/manifests/kubeapiserver. yaml on the Control Plane node and set the --service-account-key-file parameter to the public key file for service accounts:\n- --service-account-key-file=/path/to/\nReferences:\nhttps://github.com/kubernetes/kubernetes/issues/24167","multiregional":true,"service":"Pod"},"ecc-k8s-060":{"article":"A container which mounts a hostPath volume as part of its specification will have access to the filesystem of the underlying cluster node. The use of hostPath volumes may allow containers access to privileged areas of the node filesystem.\nHostPath volumes present many security risks, and it is a best practice to avoid the use of HostPaths when possible. When a HostPath volume must be used, it should be scoped to only the required file or directory, and mounted as ReadOnly. If restricting HostPath access to specific directories through AdmissionPolicy, volumeMounts MUST be required to use readOnly mounts for the policy to be effective.","impact":"The use of hostPath volumes may allow containers access to privileged areas of the node filesystem.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Recreate a pod that violates this rule, and before applying it make sure that the setting spec.volumes[*].hostPath is removed from the pod definition.","multiregional":true,"service":"Pod"},"ecc-k8s-079":{"article":"Memory resources can be defined using values from bytes to petabytes, it is common to use mebibytes. If you configure a memory request that is larger than the amount of memory on your nodes, the pod will never be scheduled. When specifying a memory request for a container, include the resources:requests field in the container`s resource manifest. To specify a memory limit, include resources:limits.\nSetting memory requests enforces a memory limit for a container. A container is guaranteed to have as much memory as it requests, but is not allowed to use more memory than the limit set. This configuration may save resources and prevent an attack on an exploited container.","impact":"Not setting memory requests for Kubernetes containers can result in unpredictable application behavior, as the scheduler and kubelet use this information to allocate resources and enforce resource limits. This may lead to either excessive resource usage or reduced performance, depending on the application's memory requirements.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Add resource memory request to the pod or container specifications to prevent them from using more resources than necessary:\nFor containers:\n spec:\n containers:\n resources:\n requests:\n memory: \nFor initContainers:\n spec:\n initContainers:\n resources:\n requests:\n memory: ","multiregional":true,"service":"Pod"},"ecc-k8s-053":{"article":"Kubernetes provides a default namespace, where objects are placed if no namespace is specified for them. Placing objects in this namespace makes application of RBAC and other controls more difficult. Resources in a Kubernetes cluster should be segregated by namespace, to allow for security controls to be applied at that level and to make it easier to manage resources.","impact":"Placing objects in default namespace makes application of RBAC and other security controls more difficult.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Ensure that namespaces are created to allow for appropriate segregation of Kubernetes resources and that all new resources are created in a specific namespace.\nTo remediate this issue, you should recreate existing role resource and attach it to non-default namespace.\n1. If you do not have namespace, create one using command: 'kubectl create namespace '.\n2. Before creating a new resource in different namespace, you should delete it from default namespace. Because you cannot move Kubernetes resources between namespaces. They must be recreated.\nUse the following command to delete a desired role from default namespace: 'kubectl delete -f .yaml'.\nWhere '.yaml' is a file that describes the role you want to recreate.\n3. Then in a file .yaml, change parameter 'metadata.namespace' to non-default namespace. For example, to the namespace name that was created at step 1.\n4. Apply changes with command: 'kubectl apply -f .yaml'.","multiregional":true,"service":"Role"},"ecc-k8s-074":{"article":"Kubernetes supports mounting secrets as data volumes or as environment variables. Minimize the use of environment variable secrets.\nIt is reasonably common for application code to log out its environment (particularly in the event of an error). This will include any secret values passed in as environment variables, so secrets can easily be exposed to any user or entity who has access to the logs. Benefits for storing secrets as files include: setting file permissions, projects of secret keys to specific paths, consuming secret values from volumes, and secret values can be updated without restarting the pod.","impact":"If you don't use secrets as files in Kubernetes, sensitive information stored in the secrets can be exposed as plain text if secrets are used as environment variables, which can pose a security risk. Additionally, environment variables are often visible to all processes on the system, so there is a risk of accidental exposure or deliberate misuse of the information.","report_fields":["metadata.name","metadata.namespace"],"remediation":"If possible, rewrite application code to read secrets from mounted secret files, rather than from environment variables.\nReferences: \nhttps://v1-25.docs.kubernetes.io/docs/concepts/configuration/secret/#using-secrets-as-files-from-a-pod","multiregional":true,"service":"Pod"},"ecc-k8s-033":{"article":"The controller manager creates a service account per controller in the kube-system namespace, generates a credential for it, and builds a dedicated API client with that service account credential for each controller loop to use. Setting the --use-serviceaccount-credentials to true runs each control loop within the controller manager using a separate service account credential. When used in combination with RBAC, this ensures that the control loops run with the minimum permissions required to perform their intended tasks.","impact":"Not using service account credentials in Kubernetes can have significant security implications. Without service account credentials, controller loops would need to use other forms of authentication to access Kubernetes resources, such as usernames and passwords or API tokens. This could potentially result in security vulnerabilities if these credentials are compromised or not properly secured. In addition, without RBAC, control loops may have more permissions than necessary, which could lead to unintended consequences and increase the risk of unauthorized access to Kubernetes resources. Therefore, not using service account credentials could significantly increase the potential impact of security breaches in Kubernetes environments.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Edit the Controller Manager pod specification file /etc/kubernetes/manifests/kube-controller-manager.yaml on the Control Plane node and set the '--use-service-account-credentials' parameter to 'true'.","multiregional":true,"service":"Pod"},"ecc-k8s-043":{"article":"Note: This recommendation is applicable only for etcd clusters. If you are using only one etcd server in your environment then this recommendation is not applicable.\netcd is a highly-available key value store used by Kubernetes deployments for persistent storage of all of its REST API objects. These objects are sensitive in nature and should be encrypted in transit and also amongst peers in the etcd clusters.","impact":"Broken transport security in peer communications to etcd service (non-HTTPS).","report_fields":["metadata.name","metadata.namespace"],"remediation":"Note: This recommendation is applicable only for etcd clusters. If you are using only one etcd server in your environment then this recommendation is not applicable\nFollow the etcd service documentation and configure peer TLS encryption as appropriate for your etcd cluster.\nThen, edit the etcd pod specification file /etc/kubernetes/manifests/etcd.yaml on the master node and set the below parameters:\n- --peer-client-file=\n- --peer-key-file=\nReferences:\nhttps://etcd.io/docs/v3.5/op-guide/security/","multiregional":true,"service":"Pod"},"ecc-k8s-027":{"article":"etcd is a highly-available key value store used by Kubernetes deployments for persistent storage of all of its REST API objects. These objects are sensitive in nature and should be protected by client authentication. This requires the API server to identify itself to the etcd server using a SSL Certificate Authority file.","impact":"Without proper validation of the etcd server's certificate, an attacker may be able to intercept and read or modify sensitive data being transmitted between the Kubernetes API server and etcd.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Follow the Kubernetes documentation and set up the TLS connection between the apiserver and etcd. \nThen, edit the API server pod specification file /etc/kubernetes/manifests/kube-apiserver.yaml on the master node and set the etcd certificate authority file parameter:\n- --etcd-cafile=\nReferences:\nhttps://kubernetes.io/docs/setup/best-practices/certificates/","multiregional":true,"service":"Pod"},"ecc-k8s-047":{"article":"Kubernetes Roles provide access to resources based on sets of objects and actions that can be taken on those objects. It is possible to set it to be the wildcard \"*\" which matches all items.\nThe principle of least privilege recommends that users are provided only the access required for their role and nothing more. The use of wildcard rights grants is likely to provide excessive rights to the Kubernetes API.","impact":"Use of wildcards is not optimal from a security perspective as it may allow for inadvertent access to be granted when new resources are added to the Kubernetes API.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Where possible replace any use of wildcards in roles with specific objects or actions.\nThe following command can be used to edit roles: 'kubectl edit role/'\nOr you can use a 'kubectl apply -f .yaml' where .yaml contains the yaml definition of the role that needs to be modified with your change included.","multiregional":true,"service":"Role"},"ecc-k8s-041":{"article":"etcd is a highly-available key value store used by Kubernetes deployments for persistent storage of all of its REST API objects. These objects are sensitive in nature and should not be available to unauthenticated clients. You should enable the client authentication via valid certificates to secure the access to the etcd service.","impact":"If --client-cert-auth is not enabled, any client can access the etcd service without any authentication, including unauthorized users and malicious actors. This can lead to unauthorized access to sensitive data stored in the etcd service, such as secrets and configuration data. Without client authentication, the etcd service is vulnerable to attacks such as man-in-the-middle attacks, where an attacker can intercept and modify the communication between the client and the etcd service, potentially leading to data loss, data manipulation, or even complete compromise of the Kubernetes cluster.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Edit the etcd pod specification file /etc/kubernetes/manifests/etcd.yaml on the Control Plane node and set the '--client-cert-auth' parameter to 'true'.","multiregional":true,"service":"Pod"},"ecc-k8s-075":{"article":"Kubernetes provides a default namespace, where objects are placed if no namespace is specified for them. Placing objects in this namespace makes application of RBAC and other controls more difficult. Resources in a Kubernetes cluster should be segregated by namespace, to allow for security controls to be applied at that level and to make it easier to manage resources.","impact":"Placing objects in default namespace makes application of RBAC and other security controls more difficult.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Ensure that namespaces are created to allow for appropriate segregation of Kubernetes resources and that all new resources are created in a specific namespace.\nTo remediate this issue, you should recreate existing secret resource and attach it to non-default namespace.\n1. If you do not have namespace, create one using command: 'kubectl create namespace '.\n2. Before creating a new resource in different namespace, you should delete it from default namespace. Because you cannot move Kubernetes resources between namespaces. They must be recreated. \nUse the following command to delete a desired secret from default namespace: 'kubectl delete -f .yaml'.\nWhere '.yaml' is a file that describes the secret you want to recreate.\n3. Then in a file .yaml, change parameter 'metadata.namespace' to non-default namespace. For example, to the namespace name that was created at step 1.\n4. Apply changes with command: 'kubectl apply -f .yaml'.","multiregional":true,"service":"Secret"},"ecc-k8s-014":{"article":"Setting admission control policy to NamespaceLifecycle ensures that objects cannot be created in non-existent namespaces, and that namespaces undergoing termination are not used for creating the new objects. This is recommended to enforce the integrity of the namespace termination process and also for the availability of the newer objects.\nBy default, NamespaceLifecycle is set.","impact":"If the NamespaceLifecycle admission control plugin is disabled, the integrity of the namespace termination process and the availability of newer objects cannot be ensured.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Edit the API server pod specification file /etc/kubernetes/manifests/kube-apiserver.yaml on the Control Plane node and set the --disable-admission-plugins parameter to ensure it does not include NamespaceLifecycle.","multiregional":true,"service":"Pod"},"ecc-k8s-080":{"article":"The scheduler uses resource request information for containers in a pod to decide which node to place the pod on. The kubelet enforces the resource limits set, so that the running container is not allowed to use more resource than the limit set.\nIf a process in the container tries to consume more than the allowed amount of memory, the system kernel terminates the process that attempted the allocation, with an out of memory (OOM) error. With no limit set, kubectl allocates more and more memory to the container until it runs out.","impact":"Not setting resource limits for Kubernetes containers can lead to inefficient resource usage and potential performance issues. Without this information, Kubernetes may not be able to make informed decisions about resource allocation, potentially resulting in resource overuse or the termination of processes due to out-of-memory errors.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Add resource memory limits to the pod or container specifications to prevent them from using more resources than necessary:\nFor containers:\n spec:\n containers:\n resources:\n limits:\n memory: \nFor initContainers:\n spec:\n initContainers:\n resources:\n limits:\n memory: ","multiregional":true,"service":"Pod"},"ecc-k8s-037":{"article":"Do not bind the Controller Manager service to non-loopback insecure addresses.\nThe Controller Manager API service which runs on port 10252/TCP by default is used for health and metrics information and is available without authentication or encryption. As such it should only be bound to a localhost interface, to minimize the cluster's attack surface.\nBy default, the --bind-address parameter is set to 0.0.0.0.","impact":"If --bind-address parameter is not bound to a localhost interface, unauthorized user can access health and metrics information of a Controller Manager.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Edit the Controller Manager pod specification file /etc/kubernetes/manifests/kube-controller-manager.yaml on the Control Plane node and ensure the correct value for the --bind-address parameter","multiregional":true,"service":"Pod"},"ecc-k8s-028":{"article":"etcd is a highly available key-value store used by Kubernetes deployments for persistent storage of all of its REST API objects. These objects are sensitive in nature and should be encrypted at rest to avoid any disclosures.","impact":"If an attacker gains unauthorized access to the etcd datastore or intercepts network traffic to the Kubernetes API server, he may be able to access sensitive data in a plaintext.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Follow the Kubernetes documentation and configure a EncryptionConfig file. \nThen, edit the API server pod specification file /etc/kubernetes/manifests/kube-apiserver.yaml on the master node and set the --encryption-provider-config parameter to the path of that file:\n- --encryption-provider-config=\nReferences:\nhttps://kubernetes.io/docs/setup/best-practices/certificates/","multiregional":true,"service":"Pod"},"ecc-k8s-069":{"article":"Readiness Probe is a Kubernetes capability that enables teams to make their applications more reliable and robust. This probe regulates under what circumstances the pod should be taken out of the list of service endpoints so that it no longer responds to requests. In defined circumstances the probe can remove the pod from the list of available service endpoints.","impact":"Lack of the readiness probe can make the application less reliable and make it more difficult to prevent failure and apply recovery from unexpected errors.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Configure a readiness probe for all containers (where applicable).","multiregional":true,"service":"Pod"},"ecc-k8s-054":{"article":"Host ports connect containers directly to the host's network. \nDon't specify a 'hostPort' settings in either the 'container' or 'initContainer' sections for a pod unless it is absolutely necessary. When you bind a pod to a 'hostPort', it limits the number of places the pod can be scheduled, because each combination must be unique. If you don't specify the hostIP and protocol explicitly, Kubernetes will use 0.0.0.0 as the default hostIP and TCP as the default protocol.\nIf you only need access to the port for debugging purposes, you can use the API Server proxy or 'kubectl port-forward'.\nIf you explicitly need to expose a pod's port on the node, consider using a NodePort Service before resorting to hostPort.","impact":"Host ports connect containers directly to the host's network. This can bypass controls such as network policy. Also, it limits the number of places the Pod can be scheduled.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Recreate a pod that violates this rule, and before applying it make sure that the setting 'hostPort' is removed from pod definition in the following paths:\n - spec.containers[*].ports[*].hostPort\n - spec.initContainers[*].ports[*].hostPort","multiregional":true,"service":"Pod"},"ecc-k8s-088":{"article":"The Kubernetes API stores secrets, which may be service account tokens for the Kubernetes API or credentials used by workloads in the cluster. Access to these secrets should be restricted to the smallest possible group of users to reduce the risk of privilege escalation. Inappropriate access to secrets stored within the Kubernetes cluster can allow for an attacker to gain additional access to the Kubernetes cluster or external resources whose credentials are stored as secrets.","impact":"Inappropriate access to secrets stored within the Kubernetes cluster can allow for an attacker to gain additional access to the Kubernetes cluster or external resources whose credentials are stored as secrets.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Where possible, remove get, list and watch access to secret objects in the cluster.","multiregional":true,"service":"ClusterRole"},"ecc-k8s-006":{"article":"Any request that is successfully authenticated (including an anonymous request) is then authorized. This means that every authenticated request to the Kubernetes API will be successfully authorized if authorization mode is set to 'AlwaysAllow'. This mode should not be used on any production cluster.","impact":"The 'AlwaysAllow' authorization mode explicitly allow unauthorized requests that allows anonymous users to gain access to your resources.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Edit the API server pod specification file /etc/kubernetes/manifests/kube-apiserver.yaml on the Control Plane node and set the --authorization-mode parameter to values other than AlwaysAllow. One such example could be as below:\n--authorization-mode=RBAC","multiregional":true,"service":"Pod"},"ecc-k8s-003":{"article":"This admission controller rejects all net-new usage of the 'Service' field 'externalIPs' and mitigates a known security vulnerability CVE-2020-8554. This feature is very powerful (allows network traffic interception) and not well controlled by policy. When enabled, users of the cluster may not create new Services which use 'externalIPs' and may not add new values to 'externalIPs' on existing 'Service' objects. Existing uses of 'externalIPs' are not affected, and users may remove values from 'externalIPs' on existing 'Service' objects.\nMost users do not need the ability to set the 'externalIPs' field for a 'Service' at all, and cluster admins should consider disabling this functionality by enabling the 'DenyServiceExternalIPs' admission controller. Clusters that do need to allow this functionality should consider using some custom policy to manage its usage.","impact":"Kubernetes API server allow an attacker who is able to create a ClusterIP service and set the spec.externalIPs field, to intercept traffic to that IP address. Additionally, an attacker who is able to patch the status (which is considered a privileged operation and should not typically be granted to users) of a LoadBalancer service can set the status.loadBalancer.ingress.ip to similar effect.","report_fields":["metadata.name","metadata.namespace"],"remediation":"The best approach for mitigation is to restrict the use of ExternalIPs in a cluster, for this install an admission controller to prevent the use of ExternalIPs.\nEdit the API server pod specification file /etc/kubernetes/manifests/kubeapiserver.yaml on the master node and add 'DenyServiceExternalIPs' to the `--enable-admission-plugins' parameter. Check that 'DenyServiceExternalIPs' not set in '--disable-admission-plugins' parameter.","multiregional":true,"service":"Pod"},"ecc-k8s-042":{"article":"etcd is a highly-available key value store used by Kubernetes deployments for persistent storage of all of its REST API objects. These objects are sensitive in nature and should not be available to unauthenticated clients. You should enable the client authentication via valid certificates to secure the access to the etcd service.","impact":"When the --auto-tls argument is set to true, automatically generated self-signed certificates are used for TLS encryption, which could compromise the security of sensitive objects stored in the etcd key-value store. This misconfiguration could allow unauthenticated clients to access these objects, potentially leading to unauthorized data access or tampering. To avoid this, the --auto-tls argument should be set to false and valid certificates should be used for client authentication to secure the etcd service.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Edit the etcd pod specification file /etc/kubernetes/manifests/etcd.yaml on the Control Plane node and set the '--auto-tls' parameter to 'false'.","multiregional":true,"service":"Pod"},"ecc-k8s-077":{"article":"Roles with the impersonate, bind or escalate permissions should not be granted unless strictly required.","impact":"Each of these permissions allow a particular subject to escalate their privileges beyond those explicitly granted by cluster administrators. The impersonate privilege allows a subject to impersonate other users gaining their rights to the cluster. The bind privilege allows the subject to add a binding to a cluster role or role which escalates their effective permissions in the cluster. The escalate privilege allows a subject to modify cluster roles to which they are bound, increasing their rights to that level. Each of these permissions has the potential to allow for privilege escalation to cluster-admin level.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Where possible, remove the impersonate, bind and escalate rights from subjects.","multiregional":true,"service":"Role"},"ecc-k8s-040":{"article":"etcd is a highly-available key value store used by Kubernetes deployments for persistent storage of all of its REST API objects. These objects are sensitive in nature and should be encrypted in transit.","impact":"Broken transport security in client-to-server communications to etcd service (non-HTTPS).","report_fields":["metadata.name","metadata.namespace"],"remediation":"Follow the etcd service documentation and configure TLS encryption.\nThen, edit the etcd pod specification file /etc/kubernetes/manifests/etcd.yaml on the master node and set the below parameters:\n- --cert-file=\n- --key-file=\nReferences:\nhttps://etcd.io/docs/v3.5/op-guide/security/","multiregional":true,"service":"Pod"},"ecc-k8s-021":{"article":"Setting global request timeout allows extending the API server request timeout limit to a duration appropriate to the user's connection speed. By default, it is set to 60 seconds which might be problematic on slower connections making cluster resources inaccessible once the data volume for requests exceeds what can be transmitted in 60 seconds. But, setting this timeout limit to be too large can exhaust the API server resources making it prone to Denial-of-Service attack. Hence, it is recommended to set this limit as appropriate and change the default limit of 60 seconds only if needed.","impact":"Incorrectly specifying the value for the global request timeout limit can have serious consequences. If the timeout is set too low, it can lead to inaccessible cluster resources for users with slower connection speeds, resulting in a poor user experience. On the other hand, setting the timeout limit too high can exhaust the API server's resources, making it vulnerable to Denial-of-Service attacks.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Edit the API server pod specification file /etc/kubernetes/manifests/kube-apiserver.yaml on the Control Plane node and set the --request-timeout parameter to 300 or as an appropriate value.","multiregional":true,"service":"Pod"},"ecc-k8s-068":{"article":"The kubelet uses liveness probes to know when to schedule restarts for containers. Restarting a container in a deadlock state can help to make the application more available, despite bugs.","impact":"Lack of the liveness probe can lead to a performance degradation and possibly allow to perform denial of service (DoS) attack.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Configure a liveness probe for all containers (where applicable).","multiregional":true,"service":"Pod"},"ecc-k8s-010":{"article":"Setting admission control plugin AlwaysAdmit allows all requests and do not filter any requests. The AlwaysAdmit admission controller was deprecated in Kubernetes v1.13. Its behavior was equivalent to turning off all admission controllers.","impact":"Setting admission control plugin AlwaysAdmit allows all requests without filtering, which can lead to unwanted or malicious activity in the cluster.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Edit the API server pod specification file /etc/kubernetes/manifests/kube-apiserver.yaml on the Control Plane node and either remove the --enable-admission-plugins parameter, or set it to a value that does not include AlwaysAdmit.","multiregional":true,"service":"Pod"},"ecc-k8s-051":{"article":"Kubernetes provides a default namespace, where objects are placed if no namespace is specified for them. Placing objects in this namespace makes application of RBAC and other controls more difficult. Resources in a Kubernetes cluster should be segregated by namespace, to allow for security controls to be applied at that level and to make it easier to manage resources.","impact":"Placing objects in default namespace makes application of RBAC and other security controls more difficult.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Ensure that namespaces are created to allow for appropriate segregation of Kubernetes resources and that all new resources are created in a specific namespace.\nTo remediate this issue, you should recreate existing ConfigMap resource and attach it to non-default namespace.\n1. If you do not have namespace, create one using command: 'kubectl create namespace '.\n2. Before creating a new resource in different namespace, you should delete it from default namespace. Because you cannot move Kubernetes resources between namespaces. They must be recreated.\nUse the following command to delete desired ConfigMap from default namespace: 'kubectl delete -f .yaml'.\nWhere '.yaml' is a file that describes the ConfigMap you want to recreate.\n3. Then in a file .yaml, change parameter 'metadata.namespace' to non-default namespace. For example, to the namespace name that was created at step 1.\n4. Apply changes with command: 'kubectl apply -f .yaml'.","multiregional":true,"service":"ConfigMap"},"ecc-k8s-063":{"article":"Do not generally permit containers to be run with the allowPrivilegeEscalation flag set to true. Allowing this right can lead to a process running a container getting more rights than it started with. It's important to note that these rights are still constrained by the overall container sandbox, and this setting does not relate to the use of privileged containers.\nA container running with the allowPrivilegeEscalation flag set to true may have processes that can gain more privileges than their parent.","impact":"Enabling the allowPrivilegeEscalation flag in container runtime settings can pose a security risk as it may allow containers to gain more privileges than their parent processes, potentially leading to privilege escalation attacks.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Edit required pod YAML definition file and set the below parameters:\n If you have containers:\n spec:\n containers:\n securityContext:\n allowPrivilegeEscalation: false\n If you have initContainers:\n spec:\n initContainers:\n securityContext:\n allowPrivilegeEscalation: false\n If you have ephemeralContainers:\n spec:\n ephemeralContainers:\n securityContext:\n allowPrivilegeEscalation: false","multiregional":true,"service":"Pod"},"ecc-k8s-019":{"article":"Retaining old log files ensures that one would have sufficient log data available for carrying out any investigation or correlation.","impact":"If old log files are not retained, there may not be sufficient log data available for carrying out any investigation or correlation, which can hinder the ability to identify and troubleshoot issues.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Edit the API server pod specification file /etc/kubernetes/manifests/kube-apiserver.yaml on the Control Plane node and set the --audit-log-maxbackup parameter to 10 or as an appropriate value.","multiregional":true,"service":"Pod"},"ecc-k8s-022":{"article":"If Service account lookup is not enabled, the apiserver only verifies that the authentication token is valid, and does not validate that the service account token mentioned in the request is actually present in etcd. This allows using a service account token even after the corresponding service account is deleted.","impact":"Not enabling this option can lead to a security vulnerability known as a time of check to time of use issue, where a service account token can be used even after the corresponding service account has been deleted. This can potentially result in unauthorized access to resources and data, as the token may have been issued to an account with specific permissions and privileges.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Edit the API server pod specification file /etc/kubernetes/manifests/kube-apiserver.yaml on the Control Plane node and set the '--service-account-lookup' parameter to 'true'.","multiregional":true,"service":"Pod"},"ecc-k8s-025":{"article":"API server communication contains sensitive parameters that should remain encrypted in transit. Configure the API server to serve only HTTPS traffic.","impact":"Broken transport security to kube-apiserver service (non-HTTPS).","report_fields":["metadata.name","metadata.namespace"],"remediation":"Follow the Kubernetes documentation and set up the TLS connection on the apiserver. \nThen, edit the API server pod specification file /etc/kubernetes/manifests/kubeapiserver.yaml on the master node and set the TLS certificate and private key file parameters:\n- --tls-cert-file=\n- --tls-private-key-file=\nReferences:\nhttps://kubernetes.io/docs/setup/best-practices/certificates/","multiregional":true,"service":"Pod"},"ecc-k8s-058":{"article":"Mounting service account tokens inside pods can provide an avenue for privilege escalation attacks where an attacker is able to compromise a single pod in the cluster. Avoiding mounting these tokens removes this attack avenue.\nBy default, 'automountServiceAccountToken' field in a Kubernetes service account is 'true'.","impact":"Enabling 'automountServiceAccountToken' in Kubernetes increases the cluster's attack surface, allowing potential unauthorized access to the service account token and escalating privileges.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Edit required service accounts and set the below parameters:\n automountServiceAccountToken: false","multiregional":true,"service":"ServiceAccount"},"ecc-k8s-001":{"article":"When enabled, requests that are not rejected by other configured authentication methods are treated as anonymous requests. These requests are then served by the API server. You should rely on authentication to authorize access and disallow anonymous requests. \nIf you are using RBAC authorization, it is generally considered reasonable to allow anonymous access to the API Server for health checks and discovery purposes. However, you should consider whether anonymous discovery is an acceptable risk for your purposes.\nBy default, anonymous access is enabled, so if you don't see it in the list of parameters passed to the server anonymous access will be enabled.\nOn it's own that won't actually give attackers a lot of access to the cluster, as it only covers one of the three gates a request passes through before it's processed. As shown in the Kubernetes documentation 'Controlling Access to the Kubernetes API', the request then has to pass authorization and admission control. However it does mean that in many configurations the only thing stopping attackers compromising your cluster is RBAC rules, so a single mistake could cause significant issues, especially if your cluster is exposed to the internet.","impact":"When enabled, requests that are not rejected by other configured authentication methods are treated as anonymous requests, and given a username of system:anonymous and a group of system:unauthenticated. This may expose certain information, and capabilities to an attacker with access to the cluster.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Edit the API server pod specification file /etc/kubernetes/manifests/kubeapiserver.yaml on the Control Plane node and set the below parameter:\n--anonymous-auth=false","multiregional":true,"service":"Pod"},"ecc-k8s-065":{"article":"A container running in the host's PID namespace can inspect processes running outside the container. If the container also has access to ptrace capabilities this can be used to escalate privileges outside of the container.\nIf you need to run containers which require 'hostPID', this should be defined in a separate policy and you should carefully check to ensure that only limited service accounts and users are given permission to use that policy.","impact":"When the 'hostPID' flag is set to true, a container can inspect processes running outside of the container, potentially leading to the escalation of privileges outside of the container if the container has access to ptrace capabilities.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Edit required pod YAML definition file and set the below parameters:\n spec:\n host_pid: false","multiregional":true,"service":"Pod"},"ecc-k8s-039":{"article":"Do not bind the scheduler service to non-loopback insecure addresses.\nThe Scheduler API service which runs on port 10259/TCP by default is used for health and metrics information and is available without authentication or encryption. As such it should only be bound to a localhost interface, to minimize the cluster's attack surface.\nBy default, the --bind-address parameter is set to 0.0.0.0 .","impact":"If --bind-address parameter is not bound to a localhost interface, unauthorized user can access health and metrics information of a scheduler.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Edit the Scheduler pod specification file /etc/kubernetes/manifests/kube-scheduler.yaml on the Control Plane node and ensure the correct value for the --bind-address parameter.","multiregional":true,"service":"Pod"},"ecc-k8s-036":{"article":"RotateKubeletServerCertificate causes the kubelet to both request a serving certificate after bootstrapping its client credentials and rotate the certificate as its existing credentials expire. This automated periodic rotation ensures that the there are no downtimes due to expired certificates and thus addressing availability in the CIA security triad.\nNote: This recommendation only applies if you let kubelets get their certificates from the API server. In case your kubelet certificates come from an outside authority/tool (e.g. Vault) then you need to take care of rotation yourself.\nBy default, RotateKubeletServerCertificate is set to \"true\" this recommendation verifies that it has not been disabled.","impact":"If RotateKubeletServerCertificate is set to \"false\" and kubelets get their certificates from the API server, it may impact the availability of the Kubernetes cluster. Expired certificates may also create security vulnerabilities, as they can be exploited by malicious actors to gain unauthorized access or perform other malicious activities.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Edit the Controller Manager pod specification file /etc/kubernetes/manifests/kube-controller-manager.yaml on the Control Plane node and set the --feature-gates parameter to include RotateKubeletServerCertificate=true:\n--feature-gates=...,RotateKubeletServerCertificate=true,...","multiregional":true,"service":"Pod"},"ecc-k8s-048":{"article":"Kubernetes ClusterRoles provide access to resources based on sets of objects and actions that can be taken on those objects. It is possible to set it to be the wildcard \"*\" which matches all items.\nThe principle of least privilege recommends that users are provided only the access required for their role and nothing more. The use of wildcard rights grants is likely to provide excessive rights to the Kubernetes API.","impact":"Use of wildcards is not optimal from a security perspective as it may allow for inadvertent access to be granted when new resources are added to the Kubernetes API.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Caution: API servers create a set of default ClusterRole objects. Many of these are 'system:' prefixed, which indicates that the resource is directly managed by the cluster control plane. Take care when modifying ClusterRoles with names that have a 'system:' prefix. Modifications to these resources can result in non-functional clusters.\n\nWhere possible replace any use of wildcards in ClusterRoles with specific objects or actions.\nThe following command can be used to edit ClusterRoles: 'kubectl edit clusterrole/'\nOr you can use a 'kubectl apply -f .yaml' where .yaml contains the yaml definition of the ClusterRole that needs to be modified with your change included.","multiregional":true,"service":"ClusterRole"},"ecc-k8s-015":{"article":"Using the NodeRestriction plug-in ensures that the kubelet is restricted to the Node and Pod objects that it could modify as defined. Such kubelets will only be allowed to modify their own Node API object, and only modify Pod API objects that are bound to their node.\nBy default, NodeRestriction is not set.","impact":"Kubelets will be allowed to modify API objects of all pods and Nodes. If an attacker gives access to a Node, it can lead to privilege escalation and it will give access not only to it, but also to other Nodes.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Follow the Kubernetes documentation and configure NodeRestriction plug-in on kubelets: https://v1-25.docs.kubernetes.io/docs/reference/access-authn-authz/node/. Then, edit the API server pod specification file /etc/kubernetes/manifests/kube-apiserver.yaml on the master node and set the --enable-admission-plugins parameter to a value that includes NodeRestriction.\n--enable-admission-plugins=...,NodeRestriction,...","multiregional":true,"service":"Pod"},"ecc-k8s-057":{"article":"To control pod security Kubernetes provided Pod Security Standards, they specify a set of security settings that Pods must meet before they can be created or updated in a cluster.\nThe Pod Security Standards define three different policies to broadly cover the security spectrum. These standards let you define how you want to restrict the behavior of pods in a clear, consistent fashion. Policies are cumulative and range from highly-permissive to highly-restrictive: privileged, baseline, and restricted.\nKubernetes defines a set of labels that you can set to define which of the predefined Pod Security Standard levels you want to use for a namespace. The label you select defines what action the control plane takes if a potential violation is detected: enforce, audit, warn.\nNote: If you already have a cluster-wide policies configured in AdmissionConfiguration, you can ignore this finding.","impact":"Securely adopting Kubernetes includes preventing unwanted changes to clusters. Unwanted changes can disrupt cluster operations, workload behaviors, and even compromise the whole environment integrity. Introducing pods that lack correct security configurations is an example of an unwanted cluster change.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Policies are applied to a namespace via labels. These labels are as follows:\nREQUIRED: pod-security.kubernetes.io/: \nOPTIONAL: pod-security.kubernetes.io/-version: (defaults to latest)\n\nIt is helpful to apply the --dry-run flag when initially evaluating security profile changes for namespaces. The Pod Security Standard checks will still be run in dry run mode, giving you information about how the new policy would treat existing pods, without actually updating a policy.\nkubectl label --dry-run=server --overwrite ns --all pod-security.kubernetes.io/enforce=baseline\n\nTo enforce baseline pod security level use the following command:\nkubectl label --overwrite ns pod-security.kubernetes.io/enforce=baseline \n\nReference: https://kubernetes.io/docs/tasks/configure-pod-container/enforce-standards-namespace-labels/","multiregional":true,"service":"Namespace"},"ecc-k8s-086":{"article":"A security context defines the operating system security settings (uid, gid, capabilities, SELinux role, etc.) applied to a container. When designing your containers and pods, make sure that you configure the security context for your pods, containers, and volumes. A security context is a property defined in the deployment yaml. It controls the security parameters that will be assigned to the pod/container/volume. There are two levels of security context: pod level security context, and container level security context.\nBy default, no security contexts are automatically applied to pods.","impact":"Without setting security context for pods, containers, the default security parameters are used, which may not provide adequate protection for sensitive data and services. This can result in potential security vulnerabilities and increased risk of data breaches or unauthorized access to resources.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Follow the Kubernetes documentation and apply security contexts to your pods and containers: https://v1-25.docs.kubernetes.io/docs/tasks/configure-pod-container/security-context/\nFor a suggested list of security contexts, you may refer to the CIS Security Benchmark for Docker Containers.","multiregional":true,"service":"Pod"},"ecc-k8s-020":{"article":"The --audit-log-maxsize flag is a configuration option for the Kubernetes API server that specifies the maximum size in megabytes of each audit log file before it gets rotated. When the audit log file reaches the specified maximum size, it is renamed with a timestamp and a new file is created.","impact":"If the --audit-log-maxsize flag is not specified or is set to an excessively high value, audit log files can grow too large and consume all available disk space. This can cause the Kubernetes API server to crash or become unresponsive, leading to service disruptions and potential security vulnerabilities. In addition, it can be difficult to analyze and extract useful information from very large audit log files, which can hinder the ability to identify and troubleshoot security incidents.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Edit the API server pod specification file /etc/kubernetes/manifests/kube-apiserver.yaml on the Control Plane node and set the --audit-log-maxsize parameter to 100 or as an appropriate value.","multiregional":true,"service":"Pod"},"ecc-k8s-078":{"article":"Kubernetes allows administrators to set CPU quotas in namespaces, as hard limits for resource usage. Containers cannot use more CPU than the configured limit. Provided the system has CPU time free, a container is guaranteed to be allocated as much CPU as it requests.\nCPU quotas are used to ensure adequate utilization of shared resources. A system without managed quotas could eventually collapse due to inadequate resources for the tasks it bares.","impact":"Not setting CPU limits in Kubernetes can result in performance issues and resource waste, as containers may consume more resources than necessary, potentially affecting other applications running on the same node. This can lead to a higher risk of system failure and reduced application performance.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Add resource cpu limits to the pod or container specifications to prevent them from using more resources than necessary:\nFor containers:\n spec:\n containers:\n resources:\n limits:\n cpu: \nFor initContainers:\n spec:\n initContainers:\n resources:\n limits:\n cpu: ","multiregional":true,"service":"Pod"},"ecc-k8s-034":{"article":"To ensure that keys for service account tokens can be rotated as needed, a separate public/private key pair should be used for signing service account tokens. The private key should be specified to the controller manager with --service-account-private-key-file as appropriate.","impact":"Without this parameter service account signing/verification key may not get rotated according to the organization policies which may lead to cluster compromise.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Edit the Controller Manager pod specification file /etc/kubernetes/manifests/kube-controller-manager.yaml on the Control Plane node and set the --service-account-private-key-file parameter to the private key file for service accounts.\n--service-account-private-key-file=","multiregional":true,"service":"Pod"},"ecc-k8s-056":{"article":"Do not generally permit containers to be run with the securityContext.privileged flag set to true. There should be at least one admission control policy defined which does not permit privileged containers.If you need to run privileged containers, this should be defined in a separate policy and you should carefully check to ensure that only limited service accounts and users are given permission to use that policy.","impact":"Privileged containers have access to all Linux Kernel capabilities and devices. A container running with full privileges can do almost everything that the host can do.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Make sure that the securityContext.privileged field is omitted or set to false for each pod.","multiregional":true,"service":"Pod"},"ecc-k8s-045":{"article":"etcd is a highly-available key value store used by Kubernetes deployments for persistent storage of all of its REST API objects. These objects are sensitive in nature and should be accessible only by authenticated etcd peers in the etcd cluster. Hence, do not use automatically generated certificates for TLS connections between peers.\nInstead, you should enable peer client cert authentication that ensures all communication between members in the cluster encrypted and authenticated using the client certificates.\nNote: This recommendation is applicable only for etcd clusters. If you are using only one etcd server in your environment then this recommendation is not applicable.","impact":"Automatically generated certificates do not provide authentication between members of etcd cluster. Without peer authentication, attackers can potentially gain access to sensitive data, including Kubernetes configuration information, API objects, and secrets. This could lead to significant security breaches, impacting the confidentiality, integrity, and availability of the Kubernetes deployment.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Note: This recommendation is applicable only for etcd clusters. If you are using only one etcd server in your environment then this recommendation is not applicable.\nEdit the etcd pod specification file /etc/kubernetes/manifests/etcd.yaml on the and either remove the --peer-auto-tls parameter or set it to false:\n--peer-auto-tls=false\nTo enable peer client cert authentication, refer this documentation https://etcd.io/docs/v3.5/op-guide/security/.","multiregional":true,"service":"Pod"},"ecc-k8s-076":{"article":"When specifying the resource request for containers in a pod, the scheduler uses this information to decide which node to place the pod on. When setting resource limit for a container, the kubelet enforces those limits so that the running container is not allowed to use more of that resource than the limit you set.\nIf a container is created in a namespace that has a default CPU limit, and the container does not specify its own CPU limit, then the container is assigned the default CPU limit.","impact":"Not setting CPU requests for Kubernetes pods and containers can lead to unanticipated application behavior since the scheduler and kubelet rely on this information to allocate resources and enforce resource utilization limits. Furthermore, if a default CPU request is assigned, it may not accurately represent the actual resource demands of the application, resulting in either unnecessary resource consumption or degraded performance.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Add resource cpu request to the pod or container specifications to prevent them from using more resources than necessary:\nFor containers:\n spec:\n containers:\n resources:\n requests:\n cpu: \nFor initContainers:\n spec:\n initContainers:\n resources:\n requests:\n cpu: ","multiregional":true,"service":"Pod"},"ecc-k8s-004":{"article":"The apiserver, by default, does not authenticate itself to the kubelet's HTTPS endpoints. The requests from the apiserver are treated anonymously. You should set up certificate based-kubelet authentication to ensure that the apiserver authenticates itself to kubelets when submitting requests.","impact":"A kubelet's HTTPS endpoint exposes APIs which give access to data of varying sensitivity, and allow you to perform operations with varying levels of power on the node and within containers. Attackers may attempt to use anonymous accounts to gain initial access to the cluster or to avoid attribution of their activities within the cluster.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Follow the Kubernetes documentation and set up the TLS connection between the apiserver and kubelets: https://v1-25.docs.kubernetes.io/docs/reference/access-authn-authz/kubelet-authn-authz/\nThen, edit API server pod specification file /etc/kubernetes/manifests/kube-apiserver.yaml on the Control Plane node and set the kubelet client certificate and key parameters as below:\n--kubelet-client-certificate=\n--kubelet-client-key=","multiregional":true,"service":"Pod"},"ecc-k8s-082":{"article":"Cluster roles with the impersonate, bind or escalate permissions should not be granted unless strictly required.","impact":"Each of these permissions allow a particular subject to escalate their privileges beyond those explicitly granted by cluster administrators. The impersonate privilege allows a subject to impersonate other users gaining their rights to the cluster. The bind privilege allows the subject to add a binding to a cluster role or role which escalates their effective permissions in the cluster. The escalate privilege allows a subject to modify cluster roles to which they are bound, increasing their rights to that level. Each of these permissions has the potential to allow for privilege escalation to cluster-admin level.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Where possible, remove the impersonate, bind and escalate rights from subjects.","multiregional":true,"service":"ClusterRole"},"ecc-k8s-018":{"article":"Retaining logs for at least 30 days ensures that you can go back in time and investigate or correlate any events.","impact":"Failure to retain logs for at least 30 days can make it difficult or impossible to investigate or correlate past events. As a result, identifying the cause of problems or security incidents becomes challenging, and this could potentially lead to prolonged downtime, data breaches, or other critical issues. Furthermore, not retaining logs could result in regulatory noncompliance and potential legal consequences.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Edit the API server pod specification file /etc/kubernetes/manifests/kube-apiserver.yaml on the Control Plane node and set the --audit-log-maxage parameter to 30 or as an appropriate number of days.","multiregional":true,"service":"Pod"},"ecc-k8s-011":{"article":"Setting admission control policy to AlwaysPullImages forces every new pod to pull the required images every time. In a multi-tenant cluster users can be assured that their private images can only be used by those who have the credentials to pull them. Without this admission control policy, once an image has been pulled to a node, any pod from any user can use it simply by knowing the image`s name, without any authorization check against the image ownership. When this plug-in is enabled, images are always pulled prior to starting containers, which means valid credentials are required.\nBy default, AlwaysPullImages is not set.","impact":"Without this access control policy, an image pulled to on a node can be used without any authorization check for image ownership.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Edit the API server pod specification file /etc/kubernetes/manifests/kube-apiserver.yaml on the Control Plane node and set the --enable-admission-plugins parameter to include AlwaysPullImages:\n--enable-admission-plugins=...,AlwaysPullImages,...","multiregional":true,"service":"Pod"},"ecc-k8s-050":{"article":"Kubernetes provides a default namespace, where objects are placed if no namespace is specified for them. Placing objects in this namespace makes application of RBAC and other controls more difficult. Resources in a Kubernetes cluster should be segregated by namespace, to allow for security controls to be applied at that level and to make it easier to manage resources.","impact":"Placing objects in default namespace makes application of RBAC and other security controls more difficult.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Ensure that namespaces are created to allow for appropriate segregation of Kubernetes resources and that all new resources are created in a specific namespace.\nTo remediate this issue, you should recreate existing pod resource and attach it to non-default namespace.\n1. If you do not have namespace, create one using command: 'kubectl create namespace '.\n2. Before creating a new resource in different namespace, you should delete it from default namespace. Because you cannot move Kubernetes resources between namespaces. They must be recreated.\nUse the following command to delete a desired pod from default namespace: 'kubectl delete -f .yaml'.\nWhere '.yaml' is a file that describes the pod you want to recreate.\n3. Then in a file .yaml, change parameter 'metadata.namespace' to non-default namespace. For example, to the namespace name that was created at step 1.\n4. Apply changes with command: 'kubectl apply -f .yaml'.","multiregional":true,"service":"Pod"},"ecc-k8s-009":{"article":"Using 'EventRateLimit' admission control enforces a limit on the number of events that the API Server will accept in a given time slice. A misbehaving workload could overwhelm and DoS the API Server, making it unavailable. This particularly applies to a multi-tenant cluster, where there might be a small percentage of misbehaving tenants which could have a significant impact on the performance of the cluster overall. Hence, it is recommended to limit the rate of events that the API server will accept.\nYou need to carefully tune in limits as per your environment.","impact":"A misbehaving workload could overwhelm and DoS the API Server, making it unavailable.","report_fields":["metadata.name","metadata.namespace"],"remediation":"Follow the Kubernetes documentation and set the desired limits in a configuration file: https://v1-25.docs.kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#eventratelimit\nThen, edit the API server pod specification file /etc/kubernetes/manifests/kubeapiserver.yaml and set the below parameters:\n--enable-admission-plugins=...,EventRateLimit,...\n--admission-control-config-file=","multiregional":true,"service":"Pod"}} \ No newline at end of file diff --git a/tests/tests_metrics/mock_files/rulesets/caas-settings/RULES_TO_CATEGORY.json.gz b/tests/tests_metrics/mock_files/rulesets/caas-settings/RULES_TO_CATEGORY.json.gz new file mode 100644 index 000000000..74b8eef5a --- /dev/null +++ b/tests/tests_metrics/mock_files/rulesets/caas-settings/RULES_TO_CATEGORY.json.gz @@ -0,0 +1 @@ +{"ecc-azure-056":"Type > 5xlyqjvjvsr7fj10fn9derrmv6t","ecc-azure-016":"Type > vj53vsc822xwuyhifescd879qfg","ecc-azure-353":"Type > rvs7dl64y9nt9ciw2fe0d806wow","ecc-azure-057":"Type > d4kp5zvya6ezo0n64arcbc2m6bx","ecc-azure-108":"Type > piqqk1lvwlmcybwj5pxmqu95zvt","ecc-azure-038":"Type > l48gn3vvb5f1f62e68vme5ukwbf","ecc-azure-310":"Type > 95hva12nguv6u6qkmsspcinwf1j","ecc-azure-270":"Type > 26nx38vz38gkmnhphucpu02po81","ecc-azure-367":"Type > 9108iz47qcvy9my9zyzy9zgalem","ecc-azure-314":"Type > 9813amrx5klqd9k2knp75qaot8n","ecc-azure-039":"Type > 12zajsynnykywirszziixu24xax","ecc-azure-044":"Type > 2u7bkxf4lgtm54mrmm9savlp8g7","ecc-azure-042":"Type > pwqx5mdy0cxqjn0f9gg57y4l25t","ecc-azure-068":"Type > pvf8xp9g6w6sbz1qbjd0b5bu8le","ecc-azure-137":"Type > 4077ql8ps9cuwmqtdm73nkoh9fh","ecc-azure-163":"Type > k0jv9rzc7xke2p7lqqdig08qol0","ecc-azure-219":"Type > j92zs3d321iaq8kwcebs6m2v88e","ecc-azure-299":"Type > 05y4e5wwgq67a2dohefdolkwo3x","ecc-azure-043":"Type > d3t7pp1kv42j1igp3fhjcx42t7a","ecc-azure-021":"Type > s29os5rlj37tlpodiiemnkilpct","ecc-azure-117":"Type > 0m66owvdyrj679tu6smnk3u5m5p","ecc-azure-161":"Type > wovu3ekwu7znxh0bqof1lnowq83","ecc-azure-214":"Type > rwtmbekfmsybuuw7j1rpn950q84","ecc-azure-205":"Type > lhvtzmd8f5cjf9xms7on1kudh67","ecc-azure-331":"Type > jusrfv2rbmbljseoiap636pw9ia","ecc-azure-024":"Type > kc233qezmh5k2cpoqlj73ailqro","ecc-azure-224":"Type > wafldfv2ee9kbprmzzwidsw5r28","ecc-azure-123":"Type > m0zucy54dnwtfp7zse4f9vj4ptj","ecc-azure-176":"Type > vuuuq7lrjf6onoko714q32im6ux","ecc-azure-122":"Type > jfpn6eky82e4gzvyty9yl1afwse","ecc-azure-133":"Type > qybitxwrtbg5v0yzretemo7csjy","ecc-azure-027":"Type > 9rzj0frpouki7qazgcuqi608enr","ecc-azure-127":"Type > j0h5zum8cvfndc9y5ctgzqlyevn","ecc-azure-146":"Type > e048ig78vjkt6jquw1zdsk4gijg","ecc-azure-236":"Type > xqi0fa9jyespw1zj1eb9ibwz7ku","ecc-azure-232":"Type > 5pfm978s2r47bp5kxc8pbz0h8o7","ecc-azure-015":"Type > 0a74xmdqa1n238jfbufbfbqezka","ecc-azure-156":"Type > kqz56i6f0wtcmuw1m6cri5q6qxu","ecc-azure-321":"Type > p2w0fx5y9vf7uowzmrz3msc8ddx","ecc-azure-344":"Type > 29vce8gw0m6ier2d3wlo0be0jwh","ecc-azure-119":"Type > vy5t53umcorz7xuszfilcs6l3v4","ecc-azure-234":"Type > 2o212t408guo1gi3rm58r1yo0ec","ecc-azure-053":"Type > sjrikgmn4nla5oqd009z4ylpp7c","ecc-azure-126":"Type > 48ylwpg3kwojiaypyt4isprsunc","ecc-azure-111":"Type > veexoygjylvlvjmaeuw4zzrqzhw","ecc-azure-304":"Type > 4t976kee71mz6udanbd07bp2gdq","ecc-azure-162":"Type > 5qs0tsz08qjrf8izy15q67vmahk","ecc-azure-348":"Type > y4mx8zhou8jsbd6v77upu1wzglm","ecc-azure-220":"Type > lntokwv1ke8e1ecnyc2whraxcip","ecc-azure-284":"Type > 9za430usdxxownvobojhhp61rqm","ecc-azure-069":"Type > vx3tk11a8vbxvpud18z6rv1rm4n","ecc-azure-014":"Type > mwst8gj0nd0xjwqlprvnyduzt0e","ecc-azure-048":"Type > qkdfcsya1e1twdr92o5exdy4nlt","ecc-azure-341":"Type > rpnhz7cc6jpdbt02chuuo3xbumh","ecc-azure-110":"Type > um0fho8u7s4jlzfuth88rvkhch4","ecc-azure-328":"Type > d0o8j10o6em0bsmlxt2xronwm22","ecc-azure-160":"Type > 788d1ty2x1v14rl4z723n8sj1wb","ecc-azure-128":"Type > ujgx2j80bdm9zm0xz52ft1rfr5z","ecc-azure-165":"Type > h0eeiveqpdziuzt431vx2bfc2ie","ecc-azure-096":"Type > nig4t6cz0u1psbhcuyv7isxibqb","ecc-azure-150":"Type > iaiks2wjyzedyzhrdb9elrsgmhl","ecc-azure-020":"Type > 22wmepdym4djyt6ojawp8drdlhc","ecc-azure-215":"Type > isjczfcj4aawntnncz3kj2ogvfs","ecc-azure-159":"Type > hupprmnoat8g5qhybd1uk7pj25n","ecc-azure-364":"Type > 1dm6cpknleixlnw6pjle6zfgiou","ecc-azure-030":"Type > kb8ls29zdht6nqk99tu57p27m0f","ecc-azure-239":"Type > mat6zmj7lpt1fikytnshlvyerve","ecc-azure-106":"Type > jxpo5r4u96nks8j5m6v2v26jcs4","ecc-azure-228":"Type > l7jxxknpoqoz9bxr59wps10d86o","ecc-azure-045":"Type > oczzm2d9mm495zyy9bq7qd5y18q","ecc-azure-343":"Type > oy1xspckwwwki19u6e7afl9l74u","ecc-azure-203":"Type > y2p79856n273oyk0maw7dpmx2h1","ecc-azure-059":"Type > oc6l3yn6idp16eaa30lol34k87t","ecc-azure-290":"Type > e0sqck2i52c54d59zchglhkajxm","ecc-azure-060":"Type > 2nkk6v5lw8pnoobt04vow1sagg9","ecc-azure-009":"Type > tfz3boxru8c0zkm0js73rh4pj9g","ecc-azure-258":"Type > 7zhk0oqz1weg8ymdpes2y75ffyo","ecc-azure-033":"Type > lucl85b7fzf8536twaw11ddfcse","ecc-azure-293":"Type > 8c17t9jwy8wgypogr5qzsb4510a","ecc-azure-333":"Type > 2srsbf8zdbx4yk5fgbkm3zv1qm1","ecc-azure-170":"Type > vw6oqf8y86zl4l5ox9ymkmzx7m5","ecc-azure-011":"Type > qgjiaiamv59ta37d71z233pcras","ecc-azure-148":"Type > 01d9cimlcm5silaprybakg1uh0o","ecc-azure-180":"Type > 90sssf68yil9d779dadkiutxnvx","ecc-azure-158":"Type > n8911q8cnn0omq7vquxkv4cvbdn","ecc-azure-283":"Type > oyn8tx7p24dmchd2jououb7sypw","ecc-azure-216":"Type > 8kva8sshxhfzvc5o2he7m4xcfd7","ecc-azure-144":"Type > 007outb3nref56dywxjhb53q2lk","ecc-azure-256":"Type > oi5o5uiticucbplkd4s601mrfdo","ecc-azure-012":"Type > 6lygktroojlow3bvwdh50a853gm","ecc-azure-025":"Type > z83ut7az7qitoycp04o065vuhro","ecc-azure-121":"Type > ox87akhpo4z811xjfcw04dg7fy3","ecc-azure-197":"Type > m7zjfo9pfhkgd7wt63jdbmcatak","ecc-azure-166":"Type > wu2oluuugg4f0fgw6670olc8kxv","ecc-azure-371":"Type > 70qjwhsrwqxwf5fe8pth65ad5lf","ecc-azure-372":"Type > 5i45bpl9zacsix2o8ke3x4ixqd5","ecc-azure-300":"Type > kx3dstqiplxv03y49bnxsv3l0sn","ecc-azure-207":"Type > hrlrtaixjz22z9qmpnob501eq4i","ecc-azure-376":"Type > a7gf5peg90pc9b45ljmjqm253ye","ecc-azure-098":"Type > 7t7y3wpriqr7qkyexsbw6yt5z9j","ecc-azure-311":"Type > ztr9rfoye2q0l8b7jbwtwrf3l2u","ecc-azure-319":"Type > eq7lt44a0xoqb5541j50hegpky1","ecc-azure-173":"Type > 1e0qy8ee7ovgtxlwxkka0im6szi","ecc-azure-026":"Type > 1apg4guhitkj9fw292j79iep78e","ecc-azure-031":"Type > 9fnegu73qg04gdlvpq2x0tcp4xt","ecc-azure-287":"Type > lvhauq5pwri7yoi9j4lwkcybn3t","ecc-azure-342":"Type > nxclqb2fgdabzywzto2vko5lw28","ecc-azure-177":"Type > 425uwkxci0gmrcwr7u3hm2fy987","ecc-azure-151":"Type > p1h2yr33bbw6zgtf02ebg9bylvo","ecc-azure-149":"Type > ed5odb4s89sfoz0qtwemrlr8vj7","ecc-azure-145":"Type > 1f9jdqnehseqcv9gpjyt67mcg1o","ecc-azure-323":"Type > 2hkl9zzdoofot7kthe07icd6ndf","ecc-azure-227":"Type > 9uo5qrefd2m8530x5hy04dp8pbt","ecc-azure-277":"Type > y53igknddxv8cpzcl0hf9kte7w9","ecc-azure-171":"Type > upgnoxbci7zee7w9omge9626p9l","ecc-azure-240":"Type > zgk1yisddymmiqb0ze4ttcfmll7","ecc-azure-071":"Type > c4lylqv1e25emtwzcejpexzivrq","ecc-azure-288":"Type > h6shgmg9t16q9w4pnamg2847yug","ecc-azure-318":"Type > kfmggrzb3wix495l34ngjvcrvqr","ecc-azure-257":"Type > y51aw2ot59zif4vkk5pq69qrdnn","ecc-azure-054":"Type > v8h4yd9cid3u56630jlpfqhinyt","ecc-azure-182":"Type > 6t8b2yxahxm1z8l3vuzbjfupame","ecc-azure-072":"Type > k599u89vpic8a6vfjq39jgmvb8v","ecc-azure-365":"Type > 9by1ryy97cjvulvi4ikfumj9qr1","ecc-azure-097":"Type > h9gdivpafropez9pb1ma30j07wj","ecc-azure-109":"Type > tn1tvd2ahcssdlvqy9vi1qucud5","ecc-azure-147":"Type > 4b3etwvcvao9wihdym5ttd9gxwp","ecc-azure-302":"Type > 38l8lgulr3q7b140d233hu5zbf5","ecc-azure-010":"Type > 5wda5xkrbbcetafww0lfou8y2wc","ecc-azure-326":"Type > fjge5pm4u4zymnovvw03xxac2l6","ecc-azure-006":"Type > m289p5pl4wsme3ix99mine6kr27","ecc-azure-132":"Type > xq0cjkb9wap56i1rr0y1vui2462","ecc-azure-125":"Type > jno8x2iupbp2cgfklgpiyiclrov","ecc-azure-286":"Type > jb0o1qoicygpjcp19wsw4jolxmq","ecc-azure-052":"Type > vb77n6r5ixmy5r2xqqy25t6r4ow","ecc-azure-282":"Type > sn3b245li4oljt0cr2nonx1453u","ecc-azure-067":"Type > 87w2cs71nkhacno7qhdbkqpu03h","ecc-azure-305":"Type > 2ze2isynjynafurqzmi3mgl8nrx","ecc-azure-168":"Type > xgxf929mba4c0a14a6r8t62verd","ecc-azure-139":"Type > gvmukja6fujrgre63armjlqwrn7","ecc-azure-179":"Type > peqlaqkyed7p7sycyegdwq5z0ga","ecc-azure-370":"Type > 3apr27g9w8tobdxjlylaemtsdwl","ecc-azure-349":"Type > y9jcvprop26brpmawyqu6jmlpvx","ecc-azure-028":"Type > t28cgqlx9ptb50512cddlvyjfqp","ecc-azure-141":"Type > yi11nrp6t5bt1wg5ixeu113y1rh","ecc-azure-007":"Type > fdmhf9si0ox8gi1rzdrjlr9f4q3","ecc-azure-356":"Type > gakg1z2q0735xx9v2l2xgtycr9t","ecc-azure-346":"Type > 66n4m2711ovderyebdpbib82ccv","ecc-azure-267":"Type > rppp7r4i0qk6cgs9yv3iu4qry2v","ecc-azure-327":"Type > p3odfu2ygha83mv1upy9f7gall1","ecc-azure-065":"Type > 1peshxr39lw0vfc3zqklfvr8a7x","ecc-azure-099":"Type > rjot2j3hd6hhenwpbwre6ewcppu","ecc-azure-265":"Type > fa2o0zf58cn89fsfn8eakgzdsxu","ecc-azure-345":"Type > 1gywpusqlhrx9aqmdx4dltiohm3","ecc-azure-237":"Type > z067yiyvi9hyfju4g0de3z4bfyf","ecc-azure-196":"Type > 351dlg9qbdxims8pjya3gn2d368","ecc-azure-094":"Type > ec0wap6xuzbtldmgbqlbnu0sptc","ecc-azure-358":"Type > 6laokxnx20xxuwu9ycgdsjgey74","ecc-azure-295":"Type > vk0fyb5z5lb64zp3e6y0np1incg","ecc-azure-281":"Type > evr6u34l0y96uoxrtujwwpn7m53","ecc-azure-357":"Type > 8rfzngaxahvdgk8t84paz3sr67v","ecc-azure-120":"Type > semaphwhzn6rpjkumh95wxrj88g","ecc-azure-325":"Type > 2jpzs2z76no96tmz1zvsqauoenr","ecc-azure-317":"Type > x9ig853ux0qpyei9famskphxtuh","ecc-azure-112":"Type > qcbe8tr2j3pirlm9da1w90ns0cq","ecc-azure-005":"Type > 77gdu6vwaf51lqreudd4to6viro","ecc-azure-167":"Type > p2zdtw0sczcvlgzfrykoihabp3b","ecc-azure-374":"Type > b7x5d7lqecjp0jn639mjo1tat0e","ecc-azure-272":"Type > lnycdt4cl9wqihf4h6hpagpa9y8","ecc-azure-022":"Type > mtin5a5tvjxzcvoxr7pnhsnklh5","ecc-azure-124":"Type > fnozr1746iq6wilsvepgwscuwka","ecc-azure-275":"Type > 2tuwdzpotaezmmtjdftf4kvd31r","ecc-azure-354":"Type > vx43fbmv791y1oepfcmilbnzndc","ecc-azure-095":"Type > x356jj8q06hp1gaww8gd5rz2608","ecc-azure-055":"Type > y1qorwqdghgig9tj8ffjv8e5l6l","ecc-azure-036":"Type > e1wcf0r93symkbva2emm0zlqnjg","ecc-azure-340":"Type > 943cjtgx2mbovp7s448zhkfzlqe","ecc-azure-301":"Type > 4glgioxukzd2oeyywvawyvfjm7o","ecc-azure-101":"Type > 11k5vo1cdyy8096g3zt6owsz1ah","ecc-azure-201":"Type > 0c938ui2vwyy5hjul83aplm94w6","ecc-azure-064":"Type > x9kp5cp1gcyyob71o719memooan","ecc-azure-339":"Type > n4psc1vk2t2t5mf9xlh86i6etvk","ecc-azure-116":"Type > dg2m14iru742k0ak6d03rlh4fvy","ecc-azure-276":"Type > z04hvckjtd1vlq4t1neu1i522sf","ecc-azure-113":"Type > 0a8wvo7yg8vtqar8k0wzw3pe1jx","ecc-azure-066":"Type > oy9ny0roe19v93gyi9peye6sylc","ecc-azure-157":"Type > j25nn9cq83v2num80lx9lz6qmg9","ecc-azure-102":"Type > xp20h9g7h98xbq8yir0dcgnzkje","ecc-azure-032":"Type > lgaokqd9mj72bgybai9n0bcwmr8","ecc-azure-050":"Type > itynjkzn4nnfsgz1ks6nbl9mre7","ecc-azure-213":"Type > kj57simaptcunpuevhcbi8mh03h","ecc-azure-200":"Type > vomvxdyxeaoql3lory5frzvkjwr","ecc-azure-289":"Type > eqa09u9efwjn2m2xtv7tti9zs22","ecc-azure-225":"Type > xnmofpunji68wdjgidcyse7c678","ecc-azure-241":"Type > b84kli12tl8cyrcigqt38ap9ifd","ecc-azure-206":"Type > h91p74avuk08l2fkkgi56ysqvaf","ecc-azure-152":"Type > pwn41ixpqxcj2jvfm2kwhsxbe3p","ecc-azure-184":"Type > 3lksw7uj262sbn5x8qm0rkg9vzo","ecc-azure-329":"Type > lnanryxq2g5s7d8tjiwiskxmhpp","ecc-azure-023":"Type > qhkahikl196i8sni67w1z5groky","ecc-azure-294":"Type > nsc9ptw4czmpwaadapy8467thyn","ecc-azure-129":"Type > hga4p9yxnekwl0379q5nr6bqxq1","ecc-azure-105":"Type > 3mkbwecks3ofm1syrnoqa7sal9g","ecc-azure-332":"Type > jb97a3k3ydyz90ak1h75mfhao1y","ecc-azure-058":"Type > sjkdub7vvp8jtkp2fm8zei5fzik","ecc-azure-379":"Type > hm2knp67nj5ere4vtg967ktpiwi","ecc-azure-202":"Type > pgm4v2ajys93tp2sscpkmic6egu","ecc-azure-350":"Type > am19mjsdd6e9ooh7rq2kn6crptb","ecc-azure-355":"FinOps > 79qojm2uhkmp1wgqdplfpqx65xs","ecc-azure-278":"Type > y5t4z8tug45vqvqfc5a2dubuvzx","ecc-azure-378":"Type > j8kjn0iu88elgbql519szhz1rad","ecc-azure-362":"Type > 694d29cpew8rprpa160wghjrmxb","ecc-azure-334":"Type > q68qs6ervrfvszmav9au4fuaebc","ecc-azure-142":"Type > ilpc5vh6pf3lwa1xnfy7fi2blh1","ecc-azure-306":"Type > v6xtfnxcgh2qeeanh0gapzikeou","ecc-azure-070":"Type > 784fk76v3lt38h3xzsfezggt2ue","ecc-azure-181":"Type > 74tt70u74qaldqo2woa87763nzh","ecc-azure-359":"Type > ikc1s8pq3kw0w9r9gzrja0alueq","ecc-azure-351":"Type > pyny513nynltduj8b1lar1lw0aa","ecc-azure-231":"Type > ds5jt985t5vnssqs4175w6jtm1y","ecc-azure-337":"Type > zt8897wlx3hbhmp1f2h7uae13vr","ecc-azure-164":"Type > 9pxho0h4fyzijj0eoxq6idmg5x2","ecc-azure-155":"Type > 8v7878dzunlodtm774tnh0l8zqm","ecc-azure-061":"Type > cf9wzlmur3ttt8zq1jy9vvm895j","ecc-azure-004":"Type > 4cwlys78fgraspic2amnlj41adc","ecc-azure-130":"Type > djzw68b6wv5y4uy58yqthsekcfv","ecc-azure-373":"Type > a9431gwvsp4ndol1qa5pwnse40m","ecc-azure-238":"Type > 0q4wfn7mgk9hapk2tu09znb65r3","ecc-azure-002":"Type > 5q0e1bsjebzes6qr1qofnt07uct","ecc-azure-199":"Type > 25gn4xp6hieb2xi5l7dsfzfj8zo","ecc-azure-298":"Type > 3sh4qfuoar8tpfnt38po9fh5ka0","ecc-azure-046":"Type > nais5hxlojk2dcr6z8c5tfj7lqu","ecc-azure-217":"Type > t1toe8gs22gn3uyicm3atiq9lpi","ecc-azure-291":"Type > etgbu8nlby0qsnce4ejy1hxhvff","ecc-azure-049":"Type > c0792ynv5ysp1w6hx4f04hp23rp","ecc-azure-313":"Type > wzor8717qoezhbtrpx824cweczc","ecc-azure-204":"Type > zjzet013cr6dku12suy0im8eaip","ecc-azure-279":"Type > 4hbrvvt5e9vss1ck1fi94e7v2nm","ecc-azure-235":"Type > oc9avw1kpelrd82qw235zasxupf","ecc-azure-178":"Type > lr711oce7tvop0eurvk991wjy8c","ecc-azure-172":"Type > 5i76ydlap5jdm6jpb7ck8conjv1","ecc-azure-226":"Type > hj5nh7e6o9zdsb8r8evb39dzeqm","ecc-azure-324":"Type > rk52xh2llhhb82ra8m1rdzvbjx4","ecc-azure-143":"Type > lsz3hlhc7jxmwx18buqqyo1fm6x","ecc-azure-280":"Type > h8s469f2tcp55ux2a3d4ll4wt9s","ecc-azure-369":"Type > 70lb25qm7yovkafz8zeniua42jq","ecc-azure-100":"Type > jp0xjbd6kh115ws8fn6x1zknn5q","ecc-azure-218":"Type > ktf1gopvhy3ujgxktw104kie3x8","ecc-azure-368":"Type > gvrrzhsx0duxva3y9x6mc749ej4","ecc-azure-103":"Type > ehkks2lihiycxl6p7uzvn3ivzm0","ecc-azure-174":"Type > w42wyr95tkzkgws10f7pyrm9rh2","ecc-azure-347":"Type > nr6mo4y8lrso106z3cavll34nmb","ecc-azure-037":"Type > 9vnn7swwktm09kfzvwfgiuom1iw","ecc-azure-222":"Type > 30i4qvekg584yccift7gdl9os77","ecc-azure-131":"Type > o1a1u9w32mmdjxh4uo3krno84xq","ecc-azure-296":"Type > 4qm8svodr3g64k86ni3myc40fjj","ecc-azure-336":"Type > 9crkz37i0dbag8iuu060qny5yz6","ecc-azure-013":"Type > c3ohlyqxoupaf7t2p7v3vbvib3e","ecc-azure-008":"Type > aewid30syq8m1i1b6fvo10z402x","ecc-gcp-246":"Type > 8qlgebxa4osx1fux9a3z20a8sty","ecc-gcp-401":"Type > xaws7cov7vod49wc3dk153cow2o","ecc-gcp-206":"Type > ro85ah4gw3e9sfxlbv34sik935a","ecc-gcp-065":"Type > e87ps39lu38t9nqx1e9pamse4bc","ecc-gcp-272":"Type > 30f9tu8sabnj3eyvkwn4h6tolcn","ecc-gcp-262":"Type > 17cenwjuc5h62y051cx6wpc8tbe","ecc-gcp-310":"Type > o41qf722kqbl4nj0eiestu6cfzf","ecc-gcp-202":"Type > 6c8ujcgssjnx9xuotcbx7ncwjwi","ecc-gcp-294":"Type > 47zvvtuxxnk9yoaikukzbqccccy","ecc-gcp-076":"Type > r03jxxbj52pty8hnx1tingz3jt4","ecc-gcp-281":"Type > 1zh8vovf0uq0yl3flgm3x8cq8na","ecc-gcp-123":"Type > ouyjq2o6o9dxl0vwusm0600oqlg","ecc-gcp-127":"Type > leqo84p35u8bxrbdviw4b8i9q0k","ecc-gcp-311":"Type > t1o8pncg37espg40emna5slsxey","ecc-gcp-214":"Type > r1sz6y6h618wjqtx4qf94wjnq5u","ecc-gcp-068":"Type > xkjk2uk6l7u838f3h2cwt6td6jk","ecc-gcp-268":"Type > bkygmo3s9cqfdmg4uvuiltw6m2m","ecc-gcp-220":"Type > jb1cpexmwfa2ada0bm9wd4j8bsq","ecc-gcp-415":"Type > bisuxbx2b0iodukvfcunudq882p","ecc-gcp-251":"Type > w3jnifv8rkj9jiu29fw8omy48y8","ecc-gcp-118":"Type > ze3i7x8vuhi39edfvuim0zd1jdd","ecc-gcp-126":"Type > t2c4jd3x6aeioe6a6gtgks0jut7","ecc-gcp-122":"Type > ejwp7sojimphqaokk1a5pi43nzf","ecc-gcp-244":"Type > gjtdyz8n34dl28y4xzab5yyz8i8","ecc-gcp-324":"Type > s0fk7pinq0vlj1iiaod0jj9igpm","ecc-gcp-443":"FinOps > n4w92gb57bydvwas3x0h7g8i9eo","ecc-gcp-409":"Type > rojl07ispmt56pfv93cdx061bya","ecc-gcp-200":"Type > laworymtsjusmospzxudqgifrr9","ecc-gcp-049":"Type > cn9h10mi85zsaixlrf4mbu9y7ef","ecc-gcp-083":"Type > yllahskj3q0xk4jl6drgmldevmr","ecc-gcp-051":"Type > covsrv07baiui4h2f74sz63wt94","ecc-gcp-167":"Type > gaqviicu5pzdrubi97p2ouxteyw","ecc-gcp-022":"Type > 0t0nci704z49tdxnt9xl0e7ep7a","ecc-gcp-203":"Type > rxuvj6ggd66gnqhlppdghg7d8fb","ecc-gcp-136":"Type > opforhmqpezijiqkbbcp060qk98","ecc-gcp-208":"Type > tvibojl95930mntlsdqxb1wek62","ecc-gcp-125":"Type > 7s6vagyguqzs5cfv9ciui1p2yk0","ecc-gcp-347":"Type > z80edtlnsfu582j8iqgcfz312m3","ecc-gcp-306":"Type > 0l1uo1adv5tnpj76lwpn4n9bv2e","ecc-gcp-044":"Type > xbyh6in02s0j88shsvit9f57nsm","ecc-gcp-314":"Type > hki8y5tt4vkrwdxigu6x61b8vvi","ecc-gcp-260":"Type > ljpilhx87ij74x6nw0n1go9xtui","ecc-gcp-342":"Type > 92lsuwi9vv8u34t5rcb0njul2p2","ecc-gcp-237":"Type > gsmv13yx13eap1yiwpld37mxdd8","ecc-gcp-072":"Type > zt79w8b7hv3hrlcb4rvumy30exs","ecc-gcp-256":"Type > ctjy8c5j3p37awq6x3p4w8itjxr","ecc-gcp-054":"Type > 8wexjjbojiouhpaex10t6gxn1wm","ecc-gcp-230":"Type > v7f3bh9sn3pw5jtza3rokwckk5m","ecc-gcp-277":"Type > r24a7d7dl190u3umf1oj14c7dgi","ecc-gcp-008":"Type > 00ugph393ofqz1fhiy11zju4ydw","ecc-gcp-250":"Type > lk54n5wwuoe8stdcvk73nv365iu","ecc-gcp-304":"Type > a4byp7yqmol5nzc736ouj777wfa","ecc-gcp-152":"Type > uw4w6sstvbe9cvvvwlb0ewq708u","ecc-gcp-252":"Type > ksrzhk1voni1q8hzemlwhbym4ar","ecc-gcp-445":"FinOps > 114keb6hjoi9vp6xvcnfqcxbxi7","ecc-gcp-070":"Type > naq0a1etht10ckmn9scc6gdl6c1","ecc-gcp-035":"Type > ahw8fqyvzle6gib887heoq4mnre","ecc-gcp-017":"Type > rqgj7s9qws6nf7er7f8n3i7ahvh","ecc-gcp-141":"Type > vmmyeqorsnylhf8cn3mp1off5dk","ecc-gcp-193":"Type > 1mc0j5ydoit342jy8ooo4dcucev","ecc-gcp-175":"Type > q15g8f4b37jpdi2aaeniv76dhjx","ecc-gcp-199":"Type > ny0cdq3l0tb267vyfii2yypzlby","ecc-gcp-185":"Type > bd9mgxyviygqz695eyk0to2xkzo","ecc-gcp-131":"Type > 2m1nvzofwfpzyiszeq2p4edp7a6","ecc-gcp-207":"Type > tthpw4gdqrt5vsdh4mf38jy9k0c","ecc-gcp-004":"Type > b57p0pxb9lkbfc79xwx9zh8ntrk","ecc-gcp-289":"Type > jt52vqp5rkx5y14arhzeb9tlj57","ecc-gcp-104":"Type > l8406fhf25jhy4d6rho4ecmr0b7","ecc-gcp-273":"Type > atypj07yqsqgv29duij3e9p7pk8","ecc-gcp-234":"Type > o4bisxhnwrq73ed4t6hx4h6v506","ecc-gcp-117":"Type > fhs6kyi3pqyuo5qwmu5pw9bbtll","ecc-gcp-309":"Type > rtfxxz9cgs7kj95nzlpifpqcv2u","ecc-gcp-103":"Type > i9wdcf5rhe0ft840hv08ayo3jzg","ecc-gcp-271":"Type > gt1s77n455o6sglfh0ehka74uok","ecc-gcp-346":"Type > e9o2db4ol9v7qwxhrr44dxpt6ns","ecc-gcp-020":"Type > rghu1ghvsvjnyncw08nkjvw0qv8","ecc-gcp-432":"Type > ptmkhrk6u9d4w6mczznyysgpuc6","ecc-gcp-087":"Type > pbtgw7wbss5a9qqa53l1wmc5shk","ecc-gcp-448":"FinOps > eavgmbnmt9prtwinr3kd7m10fdk","ecc-gcp-032":"Type > b6xa9betqjyjz18au6qz6fl6mtb","ecc-gcp-186":"Type > h0m3surp6jjt8mrgtgjw9sv7lob","ecc-gcp-063":"Type > hecc7ypdjduus60xrudp8dht399","ecc-gcp-258":"Type > z1otirwzh1atskjonsal2jrlvin","ecc-gcp-166":"Type > mkt0pntgi05juooixugn6rvdrqn","ecc-gcp-449":"Type > wmpen2rjfjkwn99rvv25dpvj43q","ecc-gcp-227":"Type > c5x7v50uqw4dt26y0zijl8qm94i","ecc-gcp-124":"Type > 2w633hqf69bwp87uox21wnuev0f","ecc-gcp-021":"Type > 4ho8cfuzmperwvhqe3mmcl86o7c","ecc-gcp-173":"Type > czvkgexp7kxgq5rvdy0x5p5sytf","ecc-gcp-061":"Type > ukuvaq2w6xskpfphq304av1ev7q","ecc-gcp-228":"Type > 5pom32x1kszl51gi01jenc5jr31","ecc-gcp-119":"Type > 43w39af8vdun2oasvpxf21y2tqc","ecc-gcp-218":"Type > 91lyq2guswlrm568nxe3ia4wpff","ecc-gcp-093":"Type > sq9ttqtx0u5vc2ygie43ytiof6n","ecc-gcp-215":"Type > cf2r5aqs14msxygkzwvns40phvc","ecc-gcp-446":"Type > 1glr7mcxn4np619loiwu36bu8of","ecc-gcp-025":"Type > rue1u2rjrph3f6y30n8nfdga7e2","ecc-gcp-001":"Type > wtrvb4heqajd3ld6kwp37enh8i1","ecc-gcp-023":"Type > b94tenur4r25lnk3paxv35ufv0b","ecc-gcp-253":"Type > 4u37g17yg2qlpcxfxqhcuj32q0c","ecc-gcp-288":"Type > c6f3cfpjkru8eeq0atw0niwfhgl","ecc-gcp-184":"Type > px76r36ldillliu5gvsrz3xhz8h","ecc-gcp-172":"Type > 9xrazg2a3pnxdbkujac3o96ul7n","ecc-gcp-092":"Type > 4ehj79tcazkcvus6ucm90wgqskt","ecc-gcp-067":"Type > aph9m4ibgj2e1x0aaufqpv2q0ec","ecc-gcp-101":"Type > ahs9db9gysjn73gnmxbnbfeichu","ecc-gcp-036":"Type > mxz86ltrl9tjmha7qsz14r5fkto","ecc-gcp-153":"Type > kmyos4qiqzkcviqr7490hamrgi7","ecc-gcp-019":"Type > 17t7q806e3s33fm7fu9f6honkmv","ecc-gcp-212":"Type > flfsegzqqyypok584a7imbfum78","ecc-gcp-050":"Type > o45kgc7p7hnkywkcaty892qdui3","ecc-gcp-213":"Type > hvn6tev3aet5vi2voync66a5op9","ecc-gcp-229":"Type > 6fymwwixyhvvredqpoh8c33oo7o","ecc-gcp-130":"Type > wp41grnk5kzcbrb1xwd9fne4do5","ecc-gcp-006":"Type > gl9qbs283qppzz8hgdv0qj6clil","ecc-gcp-012":"Type > 2hntu884j5k3w9064zp20skn5jr","ecc-gcp-335":"Type > nhqxq6tioh3xi979m0d3heksyf1","ecc-gcp-307":"Type > hrp74p1jyxdg3znasqzhvjw1rfd","ecc-gcp-030":"Type > ktyl7vgnuny2kh427owa2sqgjg5","ecc-gcp-057":"Type > h7vl00zbzbpp2urnvkc8hkf9xnr","ecc-gcp-450":"FinOps > wui1g0z1g1kmq0cpvb3vxpge1hu","ecc-gcp-015":"Type > bcowr8z83gh3sqsedkxbwzza3x6","ecc-gcp-266":"Type > viaqu7tv9i9vhb7ggiapsavmpob","ecc-gcp-178":"Type > bm2z8ow5dndvn6g5234z0vdv0v1","ecc-gcp-293":"Type > dtztu48d8wpkinoplnlupj4o096","ecc-gcp-291":"Type > d3688521ca3qmnfgsk5q8kt8ewv","ecc-gcp-043":"Type > rehq08ieiw37bnsjv3bskcmhovv","ecc-gcp-438":"Type > e0lto5ya8reydq3yk949qg17jr0","ecc-gcp-039":"Type > xfi7uo2vtyxyvvcb0c27e6715gn","ecc-gcp-187":"Type > 7hulbymmuqt6fjutjgrijhim7hd","ecc-gcp-340":"Type > tybnvxnuw2txhnzzgr5dgr54uqw","ecc-gcp-177":"Type > s7v5rygtx4llzglfpesv4wt8eix","ecc-gcp-058":"Type > gx2k92ohkr8cq51hgm26fmhx4m9","ecc-gcp-302":"Type > w1wocnik3jopuhjgmaq2d2nsp44","ecc-gcp-181":"Type > sfiybk9zhw90b30m8s27jdrgh5l","ecc-gcp-264":"Type > 4jo8t8f0yobs5xpnrytam6ovpa6","ecc-gcp-082":"Type > 18cw1zykmxocl04qej0h6s7s25f","ecc-gcp-209":"Type > 4h0hi5mz8whguvm9skn97fssbfb","ecc-gcp-162":"FinOps > bbu8whyk9omoetc9lqqks8l74el","ecc-gcp-323":"Type > x6c6wdgscki8cwvoqyl5m0a3ep5","ecc-gcp-013":"Type > 4cgv17gxgf15aert9kmyvhf8elm","ecc-gcp-386":"Type > q81xm865nz8vogco0pxbf77qqkg","ecc-gcp-077":"Type > kdxeafohfrw0ftthdbz38tqoxxn","ecc-gcp-194":"Type > jor1cxs6h2kq5at1aaxmx2l3hhg","ecc-gcp-066":"Type > lu1kyyeyqumbfbe020wppbv5i7t","ecc-gcp-007":"Type > afv8qo09zt67yo0fa9i3xr03dhw","ecc-gcp-143":"Type > bc8x6rln6cdyu1oekutbiwar4a1","ecc-gcp-129":"Type > ds0ibenzmj2xn5zvm8g44wqem5j","ecc-gcp-248":"Type > g0klqb17j3hbcokbs0gco8lplah","ecc-gcp-046":"Type > rbzbqxlhtqiwfdyftio25sgimwl","ecc-gcp-091":"Type > brkyy77wdsaee4oqmkcx99g079a","ecc-gcp-088":"Type > f5t8k0sv01sww1llf50mh1d5jcm","ecc-gcp-205":"Type > tf8e8ykwy549pjrvzb00wkfl9jq","ecc-gcp-027":"Type > 0oejms2eye4slodei9aaq62iyir","ecc-gcp-038":"Type > qjt3p92n5zha5hfpxvulxh463c5","ecc-gcp-138":"Type > roavs31ox02roxu7cxm7194iyw4","ecc-gcp-292":"Type > uzaayrg4nl41y0rqt3ktyn44myk","ecc-gcp-295":"Type > 9mggpigb8wmpymvufffnhhuiab4","ecc-gcp-241":"Type > ati1gf7zag7cr9jp9ntdw2qt9p8","ecc-gcp-010":"Type > wseu7laqkhxdvekkshx97cly5d0","ecc-gcp-257":"Type > pqbfeb1goefisnwugnf13j7d9df","ecc-gcp-221":"Type > 2y3r6i8zye9lx0nhggulcpfycd8","ecc-gcp-005":"Type > 5xqb82eybc4alkykw4sv4tgpeh4","ecc-gcp-120":"Type > gm3pidqw0yjqde7w1nfm63xv3mp","ecc-gcp-300":"Type > lgce659m5ux8952ouk8bxca41ht","ecc-gcp-283":"Type > mons04jahfrcujm2kr1nq83vvpq","ecc-gcp-011":"Type > infjomh7tiwtuxwgqoy7k7xdquv","ecc-gcp-265":"Type > x1ipouvez5u1lzmepd6gfcn2ect","ecc-gcp-121":"Type > bu182rbl66ofs8zr4njvf9xgicz","ecc-gcp-245":"Type > oun93te1fnf5qys7ly72dxt6maz","ecc-gcp-144":"Type > t5l7eggr55styrbk1u8gknesc1f","ecc-gcp-009":"Type > vmptcqdjks6qmgrmbs0uz5xlcbi","ecc-gcp-016":"Type > jkt0ezwmeaxpnleyl5yp7jvzd6r","ecc-gcp-059":"Type > qnfr61pjff4lgkymci42za8rkc6","ecc-gcp-337":"Type > r9pnubkaxcqjbf5ffkc68y3qepp","ecc-gcp-111":"Type > iwcag6k9u08tarsudcws7pvez2d","ecc-gcp-128":"Type > x7161a0eddf45aigc143zfwyjkh","ecc-gcp-263":"Type > qxv3pkcyhz9u460fejdb250f6xi","ecc-gcp-031":"Type > x2txg2j7niraex9qessklbbiscz","ecc-gcp-313":"Type > h0gdy4ga3mdbvjdm5xe8qm4dpvx","ecc-gcp-163":"Type > vojf6n27f6s3pox5j9i1fxqtv1t","ecc-gcp-442":"FinOps > 35e8tsibo2nq6jtdgda3kkqdywn","ecc-gcp-334":"Type > 2fp9pq4y15qwb546umbsy0foc40","ecc-gcp-060":"Type > 4iwke8ew4mjko1ugi849tog2drz","ecc-gcp-191":"Type > fespx2cjkqqxyiqaugmnkfclmkl","ecc-gcp-204":"Type > div9eizsqu71jg1qmgljurw6nqn","ecc-gcp-316":"Type > 0l0bci0hk7x9dx6iy7m6uap6s1u","ecc-gcp-198":"Type > i5ijovdu2hafruz9t9fb6qdj27p","ecc-gcp-029":"Type > rc8gesrdvie1mack3qdjhxnmcqi","ecc-gcp-028":"Type > lgm6simm609y3fdtsal5f5f8gtl","ecc-gcp-047":"Type > jfaoqwlfqac4g2mmz4dsi08vjzm","ecc-gcp-276":"Type > g0uhvh2tmmoj3qeirna74xsp0r6","ecc-gcp-444":"FinOps > ch972supa5jb4i4f0bgxx2agoaa","ecc-gcp-232":"Type > lp2lj2u3vm6uwpu4r5rvzn4ggj7","ecc-gcp-318":"Type > ehtxnk6l8wgdoo9upykceonpz32","ecc-gcp-287":"Type > 7c8dj24q8nvy0cacsi3tmjhe6xo","ecc-gcp-195":"Type > pju92sq961b4pxo8s2646st9kwk","ecc-gcp-183":"Type > 8r9o9qc0li44inqsnv81p69grzw","ecc-gcp-165":"Type > fn69zrdbqpj0926jp6wips17mdf","ecc-gcp-037":"Type > sy5406u24eyv1bnvcok3wgho2mp","ecc-gcp-299":"Type > 3usj8yb3k4dwxjmpqiga86ydsjm","ecc-gcp-387":"FinOps > pxo4s59e65c2hl92fr8v2tv34hr","ecc-gcp-151":"Type > u5ie44un5p4iebdssulqv3scczc","ecc-gcp-451":"Type > aldrrvaq1vka1viwqgp9d63moik","ecc-gcp-201":"Type > x1dmmtc4z768mon1a2mqvzispg6","ecc-gcp-385":"Type > n9t7d2ea7292bu0i3i2xn87p97o","ecc-gcp-142":"Type > 1lucxz916e4okhlcz7ffi7q1v2b","ecc-gcp-113":"Type > 6h9or1dwt3p37rboqoxqldk935b","ecc-gcp-024":"Type > e488vnc0t6n0sjrsfaa2qc2p6we","ecc-gcp-040":"Type > 396nf8ns6kv3wcvh35vl4e8nuxo","ecc-gcp-412":"Type > inlkc3utaccd28zxxnujof69f83","ecc-gcp-107":"Type > jd3lelss040d949v2uy1o4jrw0c","ecc-gcp-225":"Type > t5e0m5fnjbq4tg8blmxwxsld8gf","ecc-gcp-217":"Type > s25mdi5fzd5f05tt52acvjs6lqb","ecc-gcp-169":"Type > 6tnbmg4lhxi5il6h120ai73zo58","ecc-gcp-231":"Type > xskw7qoblhi7yz32qmrjda6h16h","ecc-gcp-192":"Type > lca2n6a2wb5t0vuzgs5vy0d2u9j","ecc-gcp-280":"Type > w198mc9m7eqqlf85m4cg4c1hgw3","ecc-gcp-003":"Type > jp24iowctkoapy6k4ad4dtw8ati","ecc-gcp-315":"Type > pstxjt204x30um3jzi1c6jlbfs9","ecc-gcp-033":"Type > xkldm6rybyudpp8qv5rgogarhvn","ecc-gcp-452":"Type > rhwhitcmqlsykraikjq2cixn5z8","ecc-gcp-278":"Type > 3lcgb5uhfz8vy7haa462twv2u5r","ecc-gcp-014":"Type > md6tzgqfrmkews0vu10kjdzobfn","ecc-gcp-099":"Type > 79lr0dm5wquq4x8l040mevq3uve","ecc-gcp-254":"Type > ir2qbnl7xzvqs5sfsyy1xuwkkkj","ecc-gcp-079":"Type > b4wwvxlxa0e953m7wm84pkao79n","ecc-gcp-434":"Type > epj90m7pdcuhtgidgaw9nl3a2ce","ecc-gcp-240":"Type > 7pixjpap7x4if9e1xuqwwo0gajn","ecc-gcp-089":"Type > af4sks9xshurhpg0ozv1f3fat0a","ecc-gcp-133":"Type > sw0kecpmh0qbglsyqauosx0gugh","ecc-gcp-090":"Type > 0v661tb3cxmn86dopu8a7a8efgh","ecc-gcp-109":"Type > 8s3fz8kq3kr71v5uhab9ttjpb6a","ecc-gcp-436":"Type > r419top5ixo7drrap2uvvezm4rn","ecc-gcp-210":"Type > egfj1ha63vionr4dtx41tixz634","ecc-gcp-170":"Type > jv406zo9gd5lukpxn8iullgtvrd","ecc-gcp-400":"Type > fwxknrmc8b1xqsl7uz2cs4iyzep","ecc-gcp-134":"Type > te6kls9uj2eie1ln73uid014iiz","ecc-gcp-233":"Type > 0c9b0x52ht24q3llwn3rycc957u","ecc-gcp-279":"Type > 8q52vp17zlxg5ywdmlqpzyko4z4","ecc-gcp-114":"Type > ittq8m7o90tic730t5azs8rso6d","ecc-gcp-116":"Type > 93fho4lqwdjyqkywyhhyvgg1f7w","ecc-gcp-305":"Type > e62zvoxctk7i5rzv1ml9nfrd7i3","ecc-gcp-286":"Type > nmbss9u40glm1bpu7hap3ezxplm","ecc-gcp-447":"FinOps > yzpnrmyn00hg2ylj33i9hbl1soo","ecc-gcp-180":"Type > skkp0znpcdja093tfxhbr1vyg91","ecc-gcp-171":"Type > 9c91c6k1vm0p1b9bghr1w424id0","ecc-gcp-062":"Type > yddmgpxk1ambyhqz74jebt4a89g","ecc-gcp-132":"Type > frkmto5m095dnmtipn4sfmmyn0o","ecc-gcp-110":"Type > rwf94iowhkeei8ovxmyrhnmynws","ecc-gcp-223":"Type > 9uhgjcficelbcwc9ajt018a9pd6","ecc-gcp-189":"Type > 1ty4xmujxa9cqvqhdohjtkymc87","ecc-gcp-190":"Type > otci1pdodyt0r17vald50w6t3g4","ecc-gcp-115":"Type > efxfprnv5kmsk4se2r0dtcb39yw","ecc-gcp-176":"Type > o7ikeo25upg5mhmqygysajmdy2i","ecc-gcp-261":"Type > nrwmunmodry95lsdtgt16cfr61w","ecc-gcp-053":"Type > c748n2bquoao02d9rwr6lfmepqz","ecc-gcp-303":"Type > qp2nwloalxurrt6ku9wni22ej4p","ecc-gcp-236":"Type > s9vd3dbh572y7iub23yplqgnc09","ecc-gcp-247":"Type > k6e5dlwa4sh3jnfatvqeslku79d","ecc-gcp-086":"Type > b2n0mhnngmh49fv039yv6t7sgmz","ecc-gcp-222":"Type > nnlsxvc2if9i94xrq9x41cx6xgo","ecc-gcp-197":"Type > vug8e1gh7mdzju91i5i1mv2l73s","ecc-gcp-048":"Type > qu1c38kzjijqmkugs0smx5qjjng","ecc-gcp-274":"Type > 08a4lwghnl15gayenyq2wyf8p7m","ecc-gcp-150":"Type > 4rlkxjve6sr8loova50rbhf7xww","ecc-gcp-243":"Type > plv838vq1uece31occkwgq5yv4g","ecc-gcp-282":"Type > ij9pqhum5eblbpzs4iwrixi8dby","ecc-gcp-140":"Type > 8xvylbfmhjsc31kkrh4uoxohupo","ecc-gcp-055":"Type > w3dmt1e8q81wmyal9p2xjt91v0s","ecc-gcp-239":"Type > r2blf5u8v86gzl7czlo53ah0oda","ecc-gcp-285":"Type > d2vrlvkq2pfmqf2djjcr6jhk18o","ecc-gcp-312":"Type > ut1ax47bivk9mbcx1xmzz7gtjpr","ecc-gcp-249":"Type > 6t1o8vzrtz8xpvpumwpf7ibxzlr","ecc-gcp-219":"Type > 8ey1qiml7wmahcje7tw9jdo8ggp","ecc-gcp-018":"Type > yhj2phge4kuhml667sg4kcq1w1v","ecc-gcp-188":"Type > 7kj7xxz504x222ygwqzk7mly7sh","ecc-gcp-179":"Type > 3cbhmho89lm4iz9jhd8u3xfi9o4","ecc-gcp-042":"Type > 9rgjnnt29ie9jupvuguy4wrekit","ecc-gcp-182":"Type > uw98glcgy8blqkcaqqciduas2x6","ecc-gcp-071":"Type > prujwoh8m7bi143cd7sewrgsyp7","ecc-gcp-137":"Type > z3l2cndxh19i16t8b4qlxpkjx5e","ecc-gcp-298":"Type > lj5coyk1q93vmv96xv45wuifi5q","ecc-gcp-112":"Type > a92by68yk4pol5fcc0mxe6rj8c8","ecc-gcp-317":"Type > t0p8d0okr7l89xev3xgpgvvv3dt","ecc-gcp-242":"Type > 29t06xrhxwudvikny1g14897050","ecc-gcp-034":"Type > ihrswdk38g59qkk3e68hnunh5of","ecc-gcp-216":"Type > 08win4jc8ecmuctswmtmi4oftxg","ecc-gcp-453":"Type > e32cnt7q0tmar6jhtngbrym11a1","ecc-gcp-211":"Type > sh5tnektlwk879qgy1uusysmpyi","ecc-gcp-135":"Type > tsb0vep117rfo4ivm302dh19nui","ecc-aws-235":"Type > mwwfhuyrdmtwf4a89hllweatpl8","ecc-aws-511":"Type > t5f68xn4aki69qxja36nv5wuejc","ecc-aws-190":"Type > x0jyr8ggj19oyless7wyut7e0rc","ecc-aws-091":"Type > 44c858whnodl5opn5ljbwqtn1hd","ecc-aws-295":"Type > hbqqzvpse2jj6f52uwumu1329p3","ecc-aws-286":"FinOps > humxkzg85wj6rbsep3b6cjp666d","ecc-aws-529":"FinOps > pb8dug1vbgms1bsogvkoet6dfmj","ecc-aws-493":"Type > 7g1wz6oh64bczcvtr1go4pojlzx","ecc-aws-483":"Type > mmj2c5t61twmtex65qnxkpduyb9","ecc-aws-383":"Type > 1lmgnic4ixj152rxgice1icqd2f","ecc-aws-118":"Type > 46h2v4fdput1imgox6clhncpwiz","ecc-aws-435":"Type > ug2k403sc9zhq9aw2qec1r8fiod","ecc-aws-144":"Type > 660pn1p1mi7dps8z9kx37xqd1pf","ecc-aws-414":"Type > oi2ysqtfuj1qay3ii35qj7yhp3c","ecc-aws-156":"Type > 5yiiitt2j77fyy573lyo5nl6lcz","ecc-aws-363":"Type > 0jmar1bvp2r7ua9k13kx9fky2ei","ecc-aws-229":"Type > 1xtwxynvxzxxpcvintvdl61mrw0","ecc-aws-296":"Type > zudtiuug6srijwpngfmckse8e51","ecc-aws-313":"Type > jdp8jgp25oazcenkfp5fa0zql9p","ecc-aws-378":"Type > 8hjebo385pry9lu0pgwkiviyds3","ecc-aws-302":"Type > vxv0f7yx3rs9f8gkqytd1y7rc7g","ecc-aws-342":"Type > e07u321ruyf81wy01ipym5onbkh","ecc-aws-187":"Type > mtxqfpt0w1k0neegwuum5dx2rhx","ecc-aws-245":"Type > nkorpyzsdexyvnlv3ov7a5nt08y","ecc-aws-101":"Type > 2g59mtuj1zttuy9sq6c6i9570sl","ecc-aws-189":"Type > yn8eiyyfqt130ljzycbho1y42t2","ecc-aws-465":"Type > 3rv140uujt3q59zn9os0vsebb8a","ecc-aws-572":"FinOps > kzb3qmxhaptk2g3r0jshbqfwmb4","ecc-aws-506":"Type > s7iciotljoh7fqyqvf0hvgaw6dz","ecc-aws-214":"Type > 2g9xbvohsc4mh5zp24w9pcwemoh","ecc-aws-153":"Type > mvn32txwjd4l7668vyprsi7dsu2","ecc-aws-083":"Type > uo229ypgowkf825u83jl1bdrrt1","ecc-aws-122":"Type > hkxry3c80mg9fam204nkleqiis3","ecc-aws-265":"FinOps > ussts4fje7ot4snjfqvofxv41py","ecc-aws-147":"Type > dyqujweir812u0ktuhhunodli0h","ecc-aws-129":"Type > 86sj68y143cik8lmq7knj65c1pl","ecc-aws-401":"Type > xhjwuar18a17g6k78pbdm0mr8a8","ecc-aws-062":"Type > 5s808jfteeitw05m9sx3ljxfztu","ecc-aws-250":"FinOps > 3fv8z82txe37u0wkinnaf5y9bqd","ecc-aws-188":"Type > iq7dl8vkcrkajqczoax2fosvswq","ecc-aws-004":"Type > zypryjp3w0rk6sbw6m4rcn6rju7","ecc-aws-332":"Type > p7r0wbzvv13zw8dwn542fsvds7x","ecc-aws-040":"Type > knfhu1ef5m585vsp3aaugr6tc42","ecc-aws-347":"Type > ul1noq0ji7s3h9nej0xowcbxqqr","ecc-aws-269":"Type > g3nrj54ufk4tetynn3n6cfjm6qk","ecc-aws-280":"Type > 9c2ahkld9pe11jxlbap5kc8xs6h","ecc-aws-534":"Type > xdz2w6624d0m9hsm069s639h1yr","ecc-aws-531":"Type > whjvzgtroi05r5a32u3dk7345gn","ecc-aws-471":"Type > 8922svn37uy75fht0bje85siziv","ecc-aws-498":"Type > v85q4dkk085t8nkqfqgfjxhxf6b","ecc-aws-447":"Type > bz4066u4d650lv7zpfpib6l8j5s","ecc-aws-395":"Type > qkgvgeu9qmmg9wa66hwq97ebkae","ecc-aws-548":"FinOps > kbz6sny25zf479oe32t46h65v6j","ecc-aws-277":"Type > y8b2up4eh8chi5unh7ffrr6rpeb","ecc-aws-204":"Type > 8r2mtg4qmpjz8p8pa7tatck744l","ecc-aws-539":"Type > v2d9ic38v30539vlozwm60hy8x8","ecc-aws-501":"Type > js667dakzg174foqkwa9bckqog7","ecc-aws-573":"FinOps > n7maighin6vzytsssiez2a6fbv0","ecc-aws-186":"Type > n4gqv5fivwpuy9my87uh3rrr9ml","ecc-aws-485":"Type > fnzw6mtmv5eh7s54yrf5mbtb8c6","ecc-aws-263":"Type > ppw93iwn54uhg2k71kkqxgm8kx2","ecc-aws-552":"FinOps > tgpn2kjodf9zt43uozk6owk29ig","ecc-aws-032":"Type > otw8fz4fxwjpt2yix2v09bk8u07","ecc-aws-337":"Type > ci4453au7ptb10j2spyy0ixqfop","ecc-aws-439":"Type > itjauybnycxy2dcn1yhap5wducr","ecc-aws-050":"Type > 8i9gheg7qo5iuef09jkls3m5nrg","ecc-aws-406":"Type > lv5m78h21mfnzh1lbji6pbtglpn","ecc-aws-230":"Type > jnpdj95s6q17092v8mbmiwfk5mz","ecc-aws-171":"Type > rtgqm73gchjmwerneo8jtyd4xmk","ecc-aws-267":"Type > 8narnwo5wx88elhmg95tyien7sp","ecc-aws-031":"Type > h81tw8cgi8wfq3njo1gmsy16bho","ecc-aws-169":"Type > gk4bng3phx03rj9kz89r8ofabqg","ecc-aws-451":"Type > f4ullzp9mdd4gnhv81z0vpj6oq9","ecc-aws-208":"Type > z0zwduwv5zd85mp6rxnbukr0fz8","ecc-aws-038":"Type > 43gdue7srjdq2cjqabynu3lbetr","ecc-aws-371":"Type > j294m6j8ss8eujq57t73pxw1bk3","ecc-aws-049":"Type > 54ml9noetpgtvuxo8gpkg6sb624","ecc-aws-530":"Type > awdhith4ozjndzujd92gylp1buu","ecc-aws-407":"Type > 78biiv8nbd4rw6lbe1n3ymq4nc3","ecc-aws-340":"Type > 36lfqne8vdypy80n8cbso6pmtcr","ecc-aws-310":"Type > 6fembyxf2mqv28nte0ggjt2dis0","ecc-aws-257":"Type > 705gtrw9ep60ivbnnhn3he27p2l","ecc-aws-180":"Type > j99oxfgpyqrse6pnd81rhmbqqdl","ecc-aws-196":"Type > tyiwy4xu00mo3vv9gpru9gt1788","ecc-aws-251":"Type > 8xa4y2v9yan740v7x8mxucxfiu0","ecc-aws-312":"Type > 6ecu4r4a9s4zeisz3kftcxac4ft","ecc-aws-386":"Type > etuxj3lczsy6axb37qvrzocw04z","ecc-aws-492":"FinOps > dy2avk3wx7qob8ihe48krt8mbzy","ecc-aws-124":"Type > jfpuwhw7pfgjnldja0s0il7u3pu","ecc-aws-537":"Type > 0f9309jeo5xx7bsdpdkbxbyqn3j","ecc-aws-333":"Type > homlaz2ci6euds0alx9xuxmai4c","ecc-aws-096":"Type > 8bwaaqf7msou1ou8y6okg88jn7g","ecc-aws-170":"Type > 6ed41iwstha58cgs7itcv2vwm1w","ecc-aws-481":"Type > hd0ylprok0y8j8z5fs78dkjfus9","ecc-aws-428":"Type > xhrku4ksrw46w32v120dx99mt33","ecc-aws-036":"Type > 88vh6l39j54c08ljyrktjh7vxb7","ecc-aws-314":"Type > 0rr8ms8uufidps4qqqghumo091f","ecc-aws-017":"Type > 6vq6uvgx9kcpqzlwwc0tpck2zbl","ecc-aws-008":"Type > 9d9p7ri2zyjvqfg7gfr2manpqs5","ecc-aws-521":"Type > 6i8x81p2ccuyi7v1h10lmet88xy","ecc-aws-335":"Type > rj1u5zzoqyw9o98m5b0tvhlyqxx","ecc-aws-014":"Type > n4cwr8pu3deorscttwtajtemgt9","ecc-aws-139":"Type > pd8l9b6dzeyngxusx0ozsdtpbpa","ecc-aws-090":"Type > p9wyuwvy71k7irrv5x42s3wh07x","ecc-aws-355":"Type > i651ih30b6476fnnmlrduvneahs","ecc-aws-323":"Type > v5hqadqay1871ebmz9sbyv2bw0u","ecc-aws-432":"Type > 6lmck6x33lhed59isvtlveaeuuk","ecc-aws-490":"Type > 89zf5sqjh1y467ze7415602lqjt","ecc-aws-143":"Type > 63etrhv1l524bvzi75tx2e12yag","ecc-aws-437":"Type > 33u4yfvidyzpzazl709n4s7r88y","ecc-aws-455":"Type > qgecfacjfrekyt4pnu83orecnm3","ecc-aws-227":"Type > jwni0s1kz05mjzg25y27l7sa992","ecc-aws-443":"Type > 06881a3h023jfw0mjm08d935n25","ecc-aws-356":"Type > at6j60q8vzq56hghwki2oktcb8y","ecc-aws-219":"Type > 510af9ohvs8yib4qugzjxxjwp27","ecc-aws-311":"Type > 579ilskoj7hmeu9s06iby4871bp","ecc-aws-053":"Type > s8k4i9bpkfa5oxvwav1x4903f4v","ecc-aws-290":"Type > 7z6w1w8v4pedlhb5bisohrbjkut","ecc-aws-106":"Type > lk4dxc3afd6s847pv4b4c2nvyf3","ecc-aws-125":"Type > a6doqw0oji10g6udhzjqxgaluow","ecc-aws-162":"Type > 9ioqjipqwau67wdynuz1iyy4kz3","ecc-aws-058":"Type > qxkxota2a9wje2hin4hn9zhoxkj","ecc-aws-317":"Type > 7ut6wxtzztc4x8oyt5zdygpe6f3","ecc-aws-459":"Type > 2c5gnfke2qakihfyz4w81lv6cdj","ecc-aws-427":"Type > ad55u1qn4st8k8k9qbo18duakge","ecc-aws-119":"Type > o80oimegsqqh4x1ulf28xd290xt","ecc-aws-325":"Type > x6toy5165gy3qa7badgepshb3dw","ecc-aws-514":"Type > wgzh1kd0npocn2l4vcucgn8g3l1","ecc-aws-266":"Type > erevpig5g2erjgtpgkh3e8npxph","ecc-aws-474":"Type > 8qyfcsx7fhccmtfqhk52o1rn44f","ecc-aws-182":"FinOps > oq6d7j8b9jiab1uxaf148wa5i7t","ecc-aws-175":"Type > zh92xb3tunfw6jz8pubqv8ovjvv","ecc-aws-061":"Type > dxhjad2j72raucgwhyhqsovry9p","ecc-aws-456":"Type > 57alkclmdlgiilktsktu3rq48bd","ecc-aws-489":"Type > 6q4hse3liu2aw45079kwesfkwmw","ecc-aws-042":"Type > 9xpyd10maq9fzl1ex2vhnddnh65","ecc-aws-418":"Type > sg3uz208ve5vdql4sclsvxpfv4q","ecc-aws-502":"Type > yl0ezfd1eix1bqrip70fg26969l","ecc-aws-560":"FinOps > s137x5yhtw9f8rzfpivxptwavjx","ecc-aws-543":"Type > 4obuairj4n7aocjcw61ompu0bjp","ecc-aws-519":"Type > a5v49kt3h9uc75ne33i1bdwgu07","ecc-aws-288":"Type > qg7095ld7l4q55dyl3945gojv5g","ecc-aws-350":"Type > zbdwc5bbxlypkvuqgdt122yulek","ecc-aws-066":"Type > u4fcrbqflxq0gmg1f15h72u0jui","ecc-aws-370":"Type > xeu07ef9n0vn1dp4hzil5duxkd6","ecc-aws-136":"Type > sdryiyfl9ojc79h5785uxfqveld","ecc-aws-298":"Type > 043blpdkx1hu77ypsldb0bpob81","ecc-aws-361":"Type > o8ki6d07z6h9yte76i6qtttf88l","ecc-aws-412":"Type > c89zv22mkb3gv870qxpuzpbrjgh","ecc-aws-025":"Type > fthm51ytgimcaar8fbppkw35j6l","ecc-aws-300":"Type > doqiahb763x9y8pv60cuayfs9ij","ecc-aws-322":"Type > 6ck12znd6xnqfjxr4jrfm7v7mfy","ecc-aws-499":"Type > 73purm91jit6rdgx1zbhl2widjm","ecc-aws-431":"Type > viudrew8bdsm8lezbpvo2rz4izi","ecc-aws-120":"Type > g58kczf86668jnhfz9ht0njg0uq","ecc-aws-225":"Type > f9mtoh4xhd6yxtirwcb94yq83on","ecc-aws-151":"Type > clzcymuzbyot2zem0go1wem9wlk","ecc-aws-109":"Type > osap0rle4orthw4yv31bj2dqps8","ecc-aws-415":"Type > p73thyyjtqoaeosjxsyzl51w1r1","ecc-aws-166":"Type > imkn70jycby8bf0yegioho03s7o","ecc-aws-012":"Type > xmap2u8hmva25q8qu8z13p0h8i9","ecc-aws-134":"Type > nyvuugkqiv9lopwl2jvkz0puk41","ecc-aws-052":"Type > ygrii0vpe1q3426tj3f672fj7mm","ecc-aws-221":"Type > wrc3ap89uyh0rsnj7k9ot0zvdfk","ecc-aws-400":"Type > 5vv418w90hf9l9so895w1yew8fv","ecc-aws-087":"Type > o3v4g5frrmi7412u0n1t4idx2gm","ecc-aws-233":"Type > zqzn9etkm7f066c1tm12axvj1ky","ecc-aws-441":"Type > sz9vr2bp9yimcxvmfpfqdhqd5oz","ecc-aws-419":"Type > s1vriyx26ib9ihn9s0b64cg4pfe","ecc-aws-104":"Type > jr5pipezmdd7mxr0b013rd0qqjt","ecc-aws-013":"Type > gq07h1lq0vr5d34x3c2nsjocxye","ecc-aws-547":"FinOps > yu23i7mdjbm8b3gk7hyuifcihqy","ecc-aws-308":"Type > 9v1shunp9ivjet399jh0anlo94d","ecc-aws-234":"Type > o661sqmhky0v3zb86pp937p6tb8","ecc-aws-100":"Type > 1k60kjh1kokiz5qox07sa7tdga7","ecc-aws-238":"Type > 588mtg05bo5z293ool97xwwtxdf","ecc-aws-044":"Type > rxnopm8mo46gu4g5kgccfw1mnwc","ecc-aws-377":"Type > j3onk6atugv8182nfvkl3krx3r7","ecc-aws-382":"Type > ujiautrp8aaoafhxcxwwz7qsl9i","ecc-aws-448":"Type > ip0ep2o9hoo4n7lhevf8n0vkxms","ecc-aws-003":"Type > fbk05eguxv06pjogph3ilfp3uws","ecc-aws-423":"Type > usld6fmj3k4bsw5gnxw51ttmjav","ecc-aws-272":"Type > g3xiz7iv7whcb36zydh482ntjeb","ecc-aws-244":"Type > 0p5ifmusj8p058ly17ssiyw8p6z","ecc-aws-168":"Type > kw191r4wc3mkpbyk4ml40eydti9","ecc-aws-174":"Type > qcsltkgswfxsm8ueypcatlqvwol","ecc-aws-149":"Type > 7n3lxoav87dihlzswxd9hi71mxv","ecc-aws-152":"Type > hkfhyaunehquy2lm7ftv7edvtlz","ecc-aws-115":"Type > nuu3czhyuevxomfvhm1euiixwe5","ecc-aws-364":"Type > g8phb71mmf7eupnnqy6rl0hjmyu","ecc-aws-137":"Type > 4u7iczuuzaiigiaw57jji0rfmhv","ecc-aws-253":"Type > wdatfj4b8lnd0yxd6rjph2eg7v3","ecc-aws-246":"Type > v8pvdjf9ikfepndy1pvbqfvvaoa","ecc-aws-094":"Type > x7smcy9mdrfj47omynwbma4ygf6","ecc-aws-201":"Type > 8dx01upn1b9k7iy9m7dkwdxsj9v","ecc-aws-260":"Type > 2404p9gdglm8p7ljzxu4i1gejbi","ecc-aws-462":"Type > 3thzzc5ljinn9jmpcpl26yqm7ea","ecc-aws-045":"Type > yi6df8c8cy6g4bx6q03onhpj0xs","ecc-aws-316":"Type > u459j5erjmjp7cc21rzkgk15c2w","ecc-aws-133":"Type > q27cv2bgwvwscmhoreafjibw0jp","ecc-aws-145":"Type > cs4jcqi9lt9p4v0n90mi2xcm9f7","ecc-aws-262":"Type > ahjl2d8ofhpj4mfx68g7duwao5m","ecc-aws-158":"Type > y8fvl8fbq3z2lg3g6jklwj9ad25","ecc-aws-092":"Type > t81eu3et838cnsomeedku2uecv7","ecc-aws-403":"Type > n2fugcdp7xwumrcr08zwfgyf4l2","ecc-aws-528":"Type > bl4hig2dws18w8lwxpv4kmppd8t","ecc-aws-222":"Type > yjf07xfyat7pjlqbp0giz6byz26","ecc-aws-446":"Type > qs6aje15s6t6bysrg45lgwvujyo","ecc-aws-209":"Type > 51tffr7n7u9cn7w5xnrvnt0ky15","ecc-aws-223":"Type > fsvdavmgwsc5riltc3udr0tospr","ecc-aws-055":"Type > w6xgy3mhuu2ti3m36fbhn9p0np5","ecc-aws-015":"Type > pebicqe2rsf40mwkcn6gipss3u3","ecc-aws-191":"Type > ye0b6krozlcnsts2h5ywot98lse","ecc-aws-059":"Type > a3vyeko1kksuisury96o7gjh7g3","ecc-aws-289":"Type > ze6c4hwtib3efb2nfa7ivkx8xll","ecc-aws-409":"Type > h4iwcj5kwrmqokgytrm7n4549sl","ecc-aws-268":"Type > fcsui39s11iafcr88oc4syhb75h","ecc-aws-334":"Type > bph8y0o008bw4spra5own32d5ej","ecc-aws-105":"Type > slne1eqi199agao8eiixyvubizh","ecc-aws-461":"Type > qhdhb9b8zobepiyqlluga5mzgzo","ecc-aws-157":"Type > i63aygffpg4bmulu22k8qcemajk","ecc-aws-540":"Type > yhculevoukvoo1lkde7wpbjo6op","ecc-aws-020":"Type > l82lwha1t7q0uzc96eqeoug1a26","ecc-aws-028":"Type > hwpda86sojo9arviyguxnba7kgk","ecc-aws-546":"Type > t7ek4kxxtz5vv4cep3rqfpltflm","ecc-aws-287":"Type > u2zxcmsit6ucegdd52ayu5rjq93","ecc-aws-056":"Type > oqhdvhy28wckbn9aobo1dty14r6","ecc-aws-469":"Type > 3rtnpjlk13hb4itjok8x7pf7z50","ecc-aws-079":"Type > e1f5lr69nr1a7tgjfykmjruz1vk","ecc-aws-281":"Type > hiw5xnzc106bhcu185h04kxeozf","ecc-aws-460":"Type > 2p8hy4gbsjgwyrt939zmit23icq","ecc-aws-477":"Type > wpd19gy2yvdboj5bdh4e074z4zo","ecc-aws-388":"Type > 1vsern9dz1gl9os7i0cno9dq4e9","ecc-aws-070":"Type > wgc9i5f1qxiwngb3hm5rjr0fd4j","ecc-aws-057":"Type > 1bqiy4856pvaa5o21duwtbqxufj","ecc-aws-453":"Type > dhoxqlz5b0qh0nl82xezw8fvkbz","ecc-aws-307":"Type > 8eyykcxy2b5m01bbmtat733qwky","ecc-aws-473":"Type > ue1cpzf0kuqb8bq4i0vcjf61ctj","ecc-aws-113":"Type > nouvwz5nyewdyfwae991wdqjjrd","ecc-aws-048":"Type > rzpq7ii5418cg3nirfc7bn31v7l","ecc-aws-408":"Type > vxq7pe84ygienk6s2apssirxyrl","ecc-aws-495":"Type > vxe06th954eer23nfeay4g3zjq6","ecc-aws-420":"Type > og4iuo9nkty86eozzs8w78ywoa6","ecc-aws-479":"Type > dowjsxpp8ay1ffmf6qud08iszac","ecc-aws-035":"Type > s9c0l2fpjkl7f8qybw33bjs12j8","ecc-aws-577":"FinOps > 5ieq56hx2ya5hvg20uft3ogwu2t","ecc-aws-258":"Type > 039fz4t7yg7d5ccc3shh93f6216","ecc-aws-138":"Type > 6anusvklwo9b1n1h5dkwubbunvv","ecc-aws-454":"Type > bkjfx5ow5lt09lj5tuqfghcfjdm","ecc-aws-095":"Type > jhwmlt80qzf1hbtepfr9d413yc9","ecc-aws-167":"Type > 2vzgkt0qqsuo17zx73zr1fmawxp","ecc-aws-429":"Type > 4nx9g1bbfkqxnzy04ybb5nwxujq","ecc-aws-413":"Type > vrrocnvtnh7c2ceaaqonkdn9fgt","ecc-aws-436":"Type > cjtnb9uhngk7qnsi87uop3lrkww","ecc-aws-108":"Type > roeuh76sa7xxntas6ele8gc0zrp","ecc-aws-007":"Type > kuradgxp4l93kc4c6cn3ox1r48h","ecc-aws-074":"Type > jtqwlmywhtjz03le3wzfyai6f6d","ecc-aws-224":"Type > wume4ot1orr4q1gd21huc26ytga","ecc-aws-226":"Type > 34fldsinsuum0ff8zh85982y5fy","ecc-aws-193":"Type > mdjh3jw26tps1v400pf8g6zblcx","ecc-aws-199":"Type > uueo03j4mb3w0fj5umfy6vawtjb","ecc-aws-328":"FinOps > 6mxbruz13bve7xhpye1rumalgh0","ecc-aws-054":"Type > yygplk0f9cgsqdugfyu999a0bpj","ecc-aws-476":"Type > 2y7khkju0t6cxgymtuijh9p0bk9","ecc-aws-445":"Type > allu8lqgwkynzlpkerpkuonpey8","ecc-aws-135":"Type > 2fv3jjssqrfr54q2ubh5e2jp84f","ecc-aws-405":"Type > m1v7z3123rjnp2gcyjnv0at2hnr","ecc-aws-107":"Type > 55rig89kx2javtdi7tax58yi4sc","ecc-aws-067":"Type > k7hwstnar9mevl9jne2ohvij97z","ecc-aws-254":"Type > 3kbpedio3tmhohq1tn8tttxl2br","ecc-aws-273":"Type > 6zepw98k0vbrw5h1y5s29v9au7w","ecc-aws-264":"Type > kuauu7loctipj7m6brlo1s3p751","ecc-aws-433":"Type > 98u1hypbrtzbiikb1ls7d0ghaux","ecc-aws-203":"Type > d0tyee5l8v27kegnd9aoxc78bw0","ecc-aws-538":"Type > st3hex2kzgbmngr8t3pl2vay9vv","ecc-aws-176":"Type > zozdprzbq04c2rlnxqc9qhkravd","ecc-aws-011":"Type > pxarxurowz7stsogcmrgtxnj0jn","ecc-aws-482":"Type > m9sxrwjhr9b8d5cejblg5s5x1vl","ecc-aws-544":"Type > w3w6v5eh2yv6oqnw10wx8hehi8q","ecc-aws-444":"Type > wuaswh45qs6r5btyvmnolaa3ygy","ecc-aws-146":"Type > gxjv5kgch61t8ykvsejcfymmp4o","ecc-aws-504":"Type > f5smonr8537txo2r87147kl9dwf","ecc-aws-112":"Type > 4vud4taiieg8ceb7zeizjd8uwjp","ecc-aws-159":"Type > a95525salsoxoh2022b4h1n3lns","ecc-aws-425":"Type > mjyq8p0uzfqkgwg2fpc56g2sfor","ecc-aws-185":"FinOps > ws9rlv94kkfbznkhk91xfz5gupg","ecc-aws-417":"Type > ip7kk36n4t393w1c42z5nyq5h6n","ecc-aws-128":"Type > 7tnlmphhy455dioer5e92zgllz3","ecc-aws-399":"Type > um22bch5xuxfslcg6o9hfyvxixw","ecc-aws-195":"Type > gzfxagjw8n5rejthzu777y4pwdi","ecc-aws-507":"Type > f3mmbweyzc5wvo53pcohidyukmm","ecc-aws-131":"Type > itj3fn7gke1isx6s6tjepzk0iqv","ecc-aws-285":"Type > e8s4nkeld2nxgv71b7f8fffe81o","ecc-aws-154":"Type > y4n0npxp4zk926mhpapvmt568v1","ecc-aws-255":"Type > v6mzdrd9h2usy692yqieohy2hk0","ecc-aws-496":"Type > 4y4ojxf6mmxs5edjg3sme4599xz","ecc-aws-179":"Type > gfgp5iv8n6lm3j8urufrdt4228o","ecc-aws-293":"Type > 8pg08ing6qdmemukznxcighre44","ecc-aws-198":"Type > orf4knjejrpwzkujnllczj4acv5","ecc-aws-205":"Type > mzny3r5f2ehvdgr1uj3fhm05c44","ecc-aws-016":"Type > l4p5gjtl2sqx8p4n8x2t37euwpc","ecc-aws-030":"Type > r6umauencttgtzahf8pvoyxg9oz","ecc-aws-006":"Type > xubaq7katuzbhidg305qp1yrrod","ecc-aws-142":"Type > texbeveidp2sm4imk0hdeyh5z5k","ecc-aws-114":"Type > egwedyqh6dx0gzqlrkodcc53xbi","ecc-aws-318":"Type > ktc22eu6pc7fl15nwsu7odm23wy","ecc-aws-132":"Type > 6dp9qkevmlvydmwm58zoqwnmqz7","ecc-aws-304":"Type > n9yazmoes98ywd134td6abplh6t","ecc-aws-387":"Type > 74iplpbx6s6k2mt11k9tjyih9n6","ecc-aws-368":"Type > oh33akyuq8wpmn9usprrdne94g5","ecc-aws-576":"FinOps > nu8cktwaccvr4u2ydnhugw5fj2w","ecc-aws-194":"Type > xdoe5xb922e6ziflz6m4mzpgunf","ecc-aws-394":"Type > kwc5mo99ji7kgw8vh9qz4ydxcp0","ecc-aws-218":"Type > g7n3rsnqwprmm61xz3n5pzg5lhj","ecc-aws-082":"Type > fptuyjpdx1ei61v69rgltnsvg4z","ecc-aws-275":"Type > 65kdnqqawscpulek113hcgdnwf7","ecc-aws-117":"Type > zu1g1xevrvqw7xlg5xinun1wjuo","ecc-aws-005":"Type > ypc0s4iikwhrgwgxgcz0qnqcko8","ecc-aws-034":"Type > 96k9obom1q5joynfiwcr3ru03v9","ecc-aws-366":"Type > lr0rxwyzw60g2ytja8zo4mmyijj","ecc-aws-305":"Type > 5g7l7m6v3yc7uuu60kldi6rac7t","ecc-aws-183":"Type > oddi9d7f0m0dxkd8vwey0g93hmb","ecc-aws-510":"FinOps > tu5ci8uafga0kkoza5zdfswkdjj","ecc-aws-063":"Type > ytucd7431v5zx2isn19loo80wa2","ecc-aws-292":"Type > 4ecbbwq8dosf3c6z956ttr2mypn","ecc-aws-127":"Type > gy85ln4fpq3er13xfmgn0h9vx8l","ecc-aws-148":"Type > qdeg7ewh1bxqdqebhh4jw1gecrs","ecc-aws-390":"Type > e02rplpx2to6aefvy7vvdxheh79","ecc-aws-099":"Type > hxpejhnzp16o4m0bcf9y5shubto","ecc-aws-207":"Type > 1z85a4iygbkx79exv3fn2kg6tsr","ecc-aws-488":"FinOps > fvh7mvwv3ocnh3tooxhx88p9eyb","ecc-aws-398":"Type > rcglwkd4ne4njskei85tqo4j32v","ecc-aws-217":"Type > y7hq8gibztc5bw7su6895gsv6uv","ecc-aws-026":"Type > dxu1mol3s7fiu59m3cu6hq6o7xn","ecc-aws-391":"Type > hmgqk6rh12s92lfrhkfsctkl6tt","ecc-aws-421":"Type > ppesf6hro0fhe6bzx6eysumajp7","ecc-aws-177":"Type > ih358vifewanu1jzskevrh82jlq","ecc-aws-341":"Type > o0p392kf9id1fk1p9l6cbetiq3q","ecc-aws-228":"Type > y7pfr6czzheg251juwne2ck9ver","ecc-aws-467":"Type > qvjfy3dxzy9gz7pg4d803oaham8","ecc-aws-438":"Type > 9hynoyu49l387ncjoixmw6lagzp","ecc-aws-440":"Type > 77dkgkqi7v9x9snq3kask7iodo8","ecc-aws-571":"FinOps > cvuf5pi9my8s3xdlohv5e0wee9s","ecc-aws-276":"Type > phoomkcmxcdu4oj6jujzd67jmhq","ecc-aws-344":"Type > 0ioh7y550kdn0r0pasigas3mwag","ecc-aws-319":"Type > 46sk994s20e5rdcw6kd2ted91t6","ecc-aws-081":"Type > bny0x8o65p35hcllu2gvi9ogfkg","ecc-aws-380":"Type > 5e362oh9s52f32ev7s37c27kcov","ecc-aws-486":"Type > r8fxsnilv6bfzv6wgifwcmgtnwh","ecc-aws-520":"Type > azssdw8lpe4bkp2gn7yfsas9fol","ecc-aws-271":"Type > xco4t0r2fv1mndoka1lfzqfeprg","ecc-aws-309":"Type > 25vr1wiz3170cgvy128dy4mal6d","ecc-aws-084":"Type > 173qr974mebjog9f7xctw999m7s","ecc-aws-023":"Type > 6t7j7najavuqonhisciguoffkfn","ecc-aws-259":"Type > 5o56gn2f7yzew46t2ao2x4bmiwx","ecc-aws-392":"Type > yasg5ybd8e5zl4v5iqwso4e0ju8","ecc-aws-475":"Type > a74j1gaxarofx5s3c5cpsc2ewf7","ecc-aws-278":"Type > y75htj68nd24kmz9v0fqhjetl65","ecc-aws-360":"Type > gzv2wgj4aneiaj7504jmvpv75ie","ecc-aws-294":"Type > xle7ycof8ffcujpe0fnpd4pqtcf","ecc-aws-022":"FinOps > ic9dy1ya7o2fkr558ndpulloqgm","ecc-aws-150":"Type > h304a7ji70er5tv508lsdwhqic3","ecc-aws-121":"Type > r1ixizcukorigvwslxmnwig0y8e","ecc-aws-522":"Type > 97bdzlyxke9404xn2gelkrn2muf","ecc-aws-284":"Type > f9gmgi1pbobufeo53aqrg96atui","ecc-aws-303":"Type > y2pc2tp7xrra5u2vjbp8hd6tfrh","ecc-aws-426":"Type > bzc7jy56tcz0fhykgy6srql8lz9","ecc-aws-324":"Type > c3lz12xgqr0ttt741dxwodtivet","ecc-aws-524":"FinOps > f3j3hm6uy2mwlicmgy4nzfino81","ecc-aws-098":"Type > ffkdci9ocq3bdw5wrkpdztq2hxo","ecc-aws-033":"Type > izf4frc2e9nig5kwq8ud26tm0af","ecc-aws-197":"Type > goiz68wh9aahr9xlg6vtlc8hh4a","ecc-aws-069":"Type > 4auftk6m04z075oqbefufvpop7w","ecc-aws-068":"Type > q8tkixu90jd0ukwzu03y7migbqg","ecc-aws-155":"Type > 12kksbnqgwtq8siz4nucn41jrll","ecc-aws-039":"Type > ex2m4aphctg82vg09e0dn4b4zhw","ecc-aws-430":"Type > na0glxnch91mhr3kgjgjulgjwtc","ecc-aws-527":"FinOps > 38o2xprmm395zaws6ywy3iskgjv","ecc-aws-513":"Type > p7b9x17tfjfhc84i1nrklliqm6r","ecc-aws-358":"Type > kxmiso9cuxbadm5vbe2cq2purxc","ecc-aws-478":"Type > ws7ipo1m3ya0bfuz069rgx92aj3","ecc-aws-002":"Type > z13v0vg5c4vi73i1fl24zdi34dd","ecc-aws-464":"Type > 9sjr17j2d2yg3x7vw8zu7pzt5vq","ecc-aws-037":"Type > 2fvx9ygcx6br4nlsvddi313tcs8","ecc-aws-500":"Type > ibimskhxl8gckvf6yx3393iaytg","ecc-aws-545":"Type > yoi306gmnn2y308dfvbzq1a992l","ecc-aws-181":"Type > bu9jxvju5ry4c9seh6q5dal9brj","ecc-aws-279":"Type > fj99ztmu5qzdtjxernkeety9jde","ecc-aws-093":"Type > u2jimucvnkgyz30aw65fw9uftty","ecc-aws-164":"Type > b3s6tts8vn0tm11fpg62793a3b6","ecc-aws-354":"Type > 57ztc3lr9rmdbx0tzvq5t8dejge","ecc-aws-352":"Type > eqtfjii6ex0fvh7y5eseerc7zne","ecc-aws-085":"Type > tvhh0mvvy1h3xttbs2s6gi71g0l","ecc-aws-021":"Type > y8p0k4l8wxkka8agdexdfv697n8","ecc-aws-215":"Type > tmmz5xbvb5voit0ph2novm17jct","ecc-aws-102":"Type > 114ijt3ocgglwdy0sdh589cod4v","ecc-aws-362":"Type > n2g6endzjuwmbnleeur6swa69ip","ecc-aws-365":"Type > hvxg4y8rrqa11o8iuqxrmi8h99w","ecc-aws-160":"Type > bzxfvsvcfu7p8v36zr3fwp6incj","ecc-aws-075":"Type > xa917fn1ey1sebqk0om3j05o67k","ecc-aws-076":"Type > py0i9xotlyuk9y09byt4ff7gthk","ecc-aws-029":"Type > 4fr2d28b1tac930y36zo9ixckya","ecc-aws-338":"Type > ddlgpglg4szo4dlclstde9qn0kd","ecc-aws-089":"Type > outprx74i9vdgibamwotl1hekj3","ecc-aws-236":"Type > vx22dsm6p500i89mk0fs9a9eini","ecc-aws-343":"Type > 2dno4pjv1gaujtvut01g3nj02q4","ecc-aws-116":"Type > n5knfy601bu2texigkrvmf3k4md","ecc-aws-165":"Type > ishl5vbn6xnyhk9k25baumd7ldn","ecc-aws-270":"Type > gcbjf3fycujj3fucwczj8612lxd","ecc-aws-326":"Type > q1ox8wm2toxt5hf6rqx79kan1ii","ecc-aws-372":"Type > vrke2zxlhg3cmcd8t3p23chu920","ecc-aws-336":"Type > 99v160mu4ldb5igjcay4tn44vmq","ecc-aws-536":"Type > i63sf7p23jn5i3odkfq05jnt757","ecc-aws-512":"Type > zoec78qtdzp8pqcggb08foakdl8","ecc-aws-367":"Type > hxcyizgvxh486oklzjkkpdf2ckz","ecc-aws-282":"Type > mpkuy87bc4itmrfhwijzigbh48s","ecc-aws-452":"Type > l8z3gl8ijx0qeu23irznxq0pisu","ecc-aws-348":"Type > 997d0z5rgk34bzhjj28lrjid6ln","ecc-aws-019":"Type > 8mu6m1ddm5fzh3n1wd3bawb1ydg","ecc-aws-301":"Type > nl62h15lu3pe8fvjhnknrapwech","ecc-aws-450":"Type > mdar61nwerkx30sm6t575spyo3o","ecc-aws-024":"Type > o517o6ex3vir75le2lt84b276hp","ecc-aws-097":"Type > t88o86uqwo5ztd4hwiksmr0xzi1","ecc-aws-103":"Type > zoyrjwr3xc1inb2mu5iqci2xzza","ecc-aws-424":"Type > wteu95vj4z1j2cmizkcf1cekh2k","ecc-aws-043":"FinOps > e9o3h34up0j4gm4z6f3q18j1ob5","ecc-aws-051":"Type > 5xcbnwq7e46caqptuypztnjq0pd","ecc-aws-080":"Type > agf798lq0bvli0lwzluoj2wtbke","ecc-aws-468":"Type > n5jkkkq8mqr9hbqo7giqz7naumw","ecc-aws-434":"Type > fmb2ey5xatjrm5tlo2ec8i2bkgv","ecc-aws-331":"Type > qlewa1my24m3ht82k9c2hiu6bk2","ecc-aws-487":"Type > mmswjf02xrksjc9v6mpmn53k9on","ecc-aws-484":"Type > 6p4n09ibmqr2mjec1rbvxngnpq6","ecc-aws-389":"Type > yi8kqe0i0a2bdas0f7h12lij0p6","ecc-aws-071":"Type > 5v6cc4pmbp8usv5o88mjv0bkbaq","ecc-aws-410":"Type > j4e68dpm3u8cm5yx0044gj6jxkp","ecc-aws-047":"Type > wd8eqgz21egyd91v3id4juzuwd8","ecc-aws-532":"Type > 4o38eji81rfrc5j26xv6l2tbqcy","ecc-aws-192":"Type > hvuwljo9blyx1sji7d4uzw0mci7","ecc-aws-220":"FinOps > w82m04p01furm03has0vtsjvhq5","ecc-aws-141":"Type > zs98m7zx9vq66dsdf1wrahwwx8r","ecc-aws-533":"Type > 70yxiuq48f8gx0pm37jl5wphniy","ecc-aws-018":"Type > f7czorkjy3t8bm9t7ucw8himodu","ecc-aws-470":"Type > vlgg91ltjm626vz8ck50m2mp5dw","ecc-aws-346":"Type > wpcw508d9oldvnezx1clgdn6ws3","ecc-aws-239":"Type > zy6xppkdwtvig6xvohb3tjkar1i","ecc-aws-060":"Type > 8p25uppcjsaxidhmbz283spfif0","ecc-aws-376":"Type > s8h7qmjarssdwy1sr0e6jqzcfyj","ecc-aws-232":"Type > upml4m18kr62j7wcg5b5wn0f44o","ecc-aws-009":"Type > gkuncuvfvlrpwn1j5xsm60p3yx4","ecc-aws-001":"Type > bupbhsjbt71w07j6y5mvn1sfm71","ecc-aws-379":"Type > jvpwl8l0s9xik2vjw86d80cjsb8","ecc-aws-283":"Type > 7xam45q6kpi309t2ag96ftepnd6","ecc-aws-216":"Type > wo8583cmli35uuv12mcx5m1pxe4","ecc-aws-553":"FinOps > ropt7mma5ctrpz7hwbf9zbewyo0","ecc-aws-541":"Type > j6rgmtdbqy0ycwo9r0winreob7u","ecc-aws-480":"Type > ktcr0hjid5sb74bxhg4ibbimqit","ecc-aws-237":"Type > ghzfyj4lprj61p7jhm48rk1ibwo","ecc-aws-353":"Type > sslz45lt7y2pg6cmzkesnur14gw","ecc-aws-211":"Type > 60bj1hr313bw0s68xq3tcym5ivt","ecc-aws-422":"Type > 6gxzylyuzhx2p7qjtig0g33703c","ecc-aws-375":"Type > 0c9riksaex0zqhdcglva0im1ozz","ecc-aws-542":"FinOps > v9yrmdbj0okc3g3llspatx83fs6","ecc-aws-163":"Type > yph8ej0oc5mh2bniczrqlh86ihu","ecc-aws-349":"Type > ti8krb45ohro782c2k5rhvokqpw","ecc-aws-526":"Type > yws9jjaiaxpd1712hf70ep1t5qr","ecc-aws-248":"Type > xug9wy8q0qqlvutdb5vpivx9xsb","ecc-aws-345":"Type > dfijecvcudpkueln8j3gnmqjesk","ecc-aws-330":"Type > 7toqskgnsw0aap1wnicbuw4rtrt","ecc-aws-297":"Type > vjymou3hadx5b80qup9lpkelewe","ecc-aws-213":"Type > 5u3msrkix17zxffkv47alhpgdps","ecc-aws-327":"Type > iw7198oowu0c8yghbwof4k2cpqh","ecc-aws-072":"Type > xernxs0esyv3tbaaqpvr5mr2ikn","ecc-aws-202":"Type > 8e2vmz4x5cv43k56r059xwmov4i","ecc-aws-010":"Type > fw5tulwzlm31hv6xkktsz2c7qq7","ecc-aws-299":"Type > 5ticiwf08x46lwer6x2ucwcgv29","ecc-aws-525":"FinOps > do3zf09onq25snm5fhgbkmjxqvo","ecc-aws-178":"Type > p162jyt8kjrxb71b3js7xlhym6u","ecc-aws-315":"Type > ixd3tcfkg00v51x61nvoismh7en","ecc-aws-369":"Type > et7baguwz3z034gan5h60gpj0n4","ecc-aws-472":"Type > 1yeuj3mjc0zo7nkwerl1xtpgbet","ecc-aws-210":"Type > 3uvy10c2fuvssqn80rnw7srm0mn","ecc-aws-351":"Type > zlqhfie689avdsc5haj63mk3z99","ecc-aws-130":"Type > d862a6x78qfkui76bshv49pohr3","ecc-aws-200":"Type > xyutegu1inhbwgk6wesoa1mlxrx","ecc-aws-252":"Type > 3fjjmtecua0gdk31qixls63ysc5","ecc-aws-111":"Type > bun1kgd7uc3daxw2up9hsz1d3uw","ecc-aws-458":"Type > lrus6e08rtg73jzvqjiyzh2pqyh","ecc-aws-321":"Type > 5vj1apj0mccyrjn6ws43hln8dak","ecc-aws-123":"Type > 34vgxsjad3y29are1ve4c5pkjyo","ecc-aws-517":"Type > hpn6bakiuzdzise24evueqf5v9b","ecc-aws-126":"Type > opg09ixnwrx73w58ri5j9dl9bzb","ecc-aws-466":"Type > l3fwlu2nffm0re0rh49og0ztct0","ecc-aws-396":"Type > 0flnewdlf5y5zs4xypa0bd5veeq","ecc-aws-381":"Type > ewb6k0kpy194pwty10yfzy7qg5s","ecc-aws-241":"Type > qn06i9x2qnhzjbl0txwsv9iax8i","ecc-aws-357":"Type > 1o9hdx08y19r19lvpyjm2i14kq5","ecc-aws-088":"Type > bjn65yb87844uc5dccpal9oiz5z","ecc-aws-242":"Type > s80vboiewk0236ivq3pyre8sbne","ecc-aws-212":"Type > igu52ozc5992lydy2rfrsazidul","ecc-aws-077":"Type > k435llt9lbdy5igy5oq1fsgtq8y","ecc-aws-339":"Type > koty3z06nx7c0tisaznpqcy731f","ecc-aws-385":"Type > 8lrb26wqdwtdo5b0uv1u6dp5tsy","ecc-aws-515":"Type > wg3m9hcvw9s34me90ro4adv660i","ecc-aws-173":"Type > u6agaru5g5tetq3jkmrk18rkomp","ecc-aws-110":"Type > 1laoage7l6s31elyr7b3em7nyoa","ecc-aws-397":"Type > 8zxv48yufgboyywu8iflg1l5s2t","ecc-aws-065":"Type > 3j91iz7f766ckifrz7em3yu1h3a","ecc-aws-508":"Type > wwttza4h0w32vziplcvo2bn3an8","ecc-aws-140":"Type > ufohcgo6jlu4kjgylhe10j2iyxt","ecc-aws-206":"Type > msx7mn60guwwbcrpuq1bclijl5a","ecc-aws-416":"Type > dm9x9e4v0owkarjknig4dhysbza","ecc-aws-463":"Type > 1rkswoyg3xez4zdmchvavqj12qs","ecc-aws-384":"Type > tlbxfzyt2tkonwejabsebf4u9bc","ecc-aws-503":"Type > c5i5w3hlud4y4fvump2w19bw1cn","ecc-aws-516":"Type > ze28eha35vhdzbhvibcld4mptyx","ecc-aws-161":"Type > ab053xl14se6zwwjknpa0o6jskv","ecc-aws-359":"Type > t4yhqyd0auhf6szeujazy8zzmck","ecc-aws-373":"Type > wlchvszhjk9zmlmlhda64bgo5qt","ecc-aws-509":"Type > 0giuiau5e46nexuiflem6a6cdgk","ecc-aws-575":"FinOps > 2stohtgiwar1ftlrf9j4l9s3knf","ecc-aws-086":"Type > bjmwmjfpp2hj0a6mrntoinru57e","ecc-aws-329":"Type > ns3kry0sbtbg2wxrhslfespdrdy","ecc-aws-073":"FinOps > aap8cs308j47q24kkuq4ka6xa4a","ecc-aws-523":"Type > j0z83gxgkjk0r2qrepdwdp00u6v","ecc-aws-261":"Type > wzlxuzgma5z7fjnv8ktkwrex4ew","ecc-aws-274":"Type > 43q0ktvd6bpmt3io490ojm3dpfa","ecc-aws-184":"Type > 5nhnla1xuh9gi15i90j6ycsj56z","ecc-aws-491":"Type > klc8h0nq4c6adf40s1ifvxn62aj","ecc-aws-393":"Type > 0q1wmi01j75mbf82rxtdrbui4cv","ecc-aws-411":"Type > 6vkib6o8f8tlbdve0gb0eehz3di","ecc-aws-027":"Type > 3d5ndkxf8p3afvhhjubh8g7x711","ecc-aws-374":"Type > uhae497m96y2wjhrp65syo6l671","ecc-aws-256":"Type > d9cpmqfbavycqpz2thpi6jht93i","ecc-aws-291":"FinOps > aazwt7bgogn80j89fcbb7wzz3g8","ecc-aws-494":"Type > 6aehvy8viattak9pvnhfnp2ubzx","ecc-aws-497":"Type > xnsvuiiwlq381yni8fjxrdqpwcy","ecc-aws-064":"Type > w5061tvj01t1w0tqwp5ucwpi23a","ecc-aws-320":"Type > 1etrahoy20grcz1rg8g7fqaxlws","ecc-aws-449":"Type > h5tnytyt023tfijtdp4ncu5aocz","ecc-aws-172":"Type > n8ageqgz07lwdk54onoy00nv7sf","ecc-aws-231":"Type > dl0igll8frnaobj6tpyyhls3w15","ecc-aws-535":"Type > hlpixo4wvbqwkydchr56mgkqq64","ecc-aws-240":"Type > k3i1jvgcbo4u97fp87zil2qg0xg","ecc-aws-249":"Type > oq0u3j5hae8pktsxo9kp9ejob8s","ecc-aws-078":"Type > uafxbeld4pscyc443c99ap3vqve","ecc-aws-457":"Type > ewe5zda86t7ucpkz8flyha3patu","ecc-aws-404":"Type > j62m44re300gyauzdks6ubq0tcc","ecc-aws-243":"Type > 6n2wwpucxjneco1dv8uwjbaq8v9","ecc-aws-046":"Type > 13mmfg4k5ih5znv0gsdga904tb5","ecc-aws-505":"Type > ruc9c20vqayi3roknm6bpa0l8nd","ecc-aws-247":"Type > 22v2ufcgk14ewufzumw51xz3j4r","ecc-aws-306":"Type > 5waiyx35b3p73ba3hg13tvvdkfw","ecc-aws-402":"Type > ji9q4tau52m2asiv2q17j67wod0","ecc-aws-518":"FinOps > ww1g038jqh0ing5b1afcadxfh3o","ecc-aws-442":"Type > lefa5jyubi64ygg6scknkgibrtw","ecc-aws-041":"Type > py42ihyxcyin2krxdxi3oddwlk0","ecc-k8s-016":"Type > fgss3bhbsr1hyyvx4jlbcofgk94","ecc-k8s-067":"Type > zhgr3stec2uespjegrvpkbapkxl","ecc-k8s-081":"Type > xfc9icshloqvsm4bmlv7fttp9kx","ecc-k8s-062":"Type > 47npzon8o9cgw56y7m0cglsy711","ecc-k8s-044":"Type > em0pnkl5w9em0sug0wrvfro66cp","ecc-k8s-049":"Type > mj18xr3hcgvja0bnjczi6d0dvfs","ecc-k8s-024":"Type > okihwa6wwd1foht8woubg5vgjzc","ecc-k8s-032":"Type > l12h53c0jwzo6vhl3lvnh3j43sf","ecc-k8s-038":"Type > i0whujqx1rebr38k5r9bpjay7ci","ecc-k8s-002":"Type > 2su47hlrtk8w6ec6omhu73upggz","ecc-k8s-013":"Type > pn8d1kjufv00x8pfglwa01grmbd","ecc-k8s-070":"Type > ow294rdw6nbyxo65kkbilcj6aew","ecc-k8s-092":"Type > sercpcddu3xy6ss8gio494k0gkw","ecc-k8s-012":"Type > 6hltit7nlpeyevm64mthgzidzev","ecc-k8s-061":"Type > zkt31feusru3d0ymw1j2dcalp33","ecc-k8s-008":"Type > 4ftqvbax8ng7c5io6c8um2c6afc","ecc-k8s-030":"Type > 94wanfuqx4dntji3eko7i7bl17h","ecc-k8s-005":"Type > w8hofukfq47hc2kreaqfbscx60o","ecc-k8s-072":"Type > 9a7ir6qr6nqs11i1y179vyp66z9","ecc-k8s-007":"Type > see51fggurxrtyow7k0kbxilc76","ecc-k8s-035":"Type > y8bk6xkanko56a7yfk9pz9f3czk","ecc-k8s-064":"Type > s1dwvisif4sltew5zv3uqwup121","ecc-k8s-052":"Type > kqp9r5u98p8dngz48enqb6rthyi","ecc-k8s-066":"Type > habwkiqwqebed9j4wzfhn2rmrj1","ecc-k8s-031":"Type > nbqynv6kpfp2o30v580ai5dbbap","ecc-k8s-026":"Type > u2z052kjek27g6ljg1o5nh85ewx","ecc-k8s-017":"Type > z346as2z0cqo7uz0x8cjvi7a0vf","ecc-k8s-071":"Type > 2k5dyvuw2zoxpxuk5q31zhbnf6t","ecc-k8s-087":"Type > 6e7bzutzmtnkednvp45ow32bbc0","ecc-k8s-059":"Type > ao9uqi3ohn9vl9oej490x6lkogr","ecc-k8s-023":"Type > x40p9sodg6n3e3ig8vh2ho59ors","ecc-k8s-060":"Type > 9vt67u9ludc0a7fcb07h0xn6cs6","ecc-k8s-079":"Type > uinlip6n1vm162mhquubyvwuf85","ecc-k8s-053":"Type > sli2iyuvgtzjkejlxeemdm8kuqj","ecc-k8s-074":"Type > 2k1lj4gvjm09jrjustud0tvhkz9","ecc-k8s-033":"Type > dqiipp3yskelcx11owpfs59yap8","ecc-k8s-043":"Type > jf7721xm2tktvvcofg1danex6kb","ecc-k8s-027":"Type > 0h382dzpyb4ct9p8dh8toywt30e","ecc-k8s-047":"Type > xi253uktldxopocr0nyh37w7rnt","ecc-k8s-041":"Type > a2pgokgbhhdemphlz6gj8uccvl8","ecc-k8s-075":"Type > hq6h4xafl7njkh2vh621jt0zw6e","ecc-k8s-014":"Type > 7vmsmn95zllhii552s5v451paly","ecc-k8s-080":"Type > fpeg5xasng1gpw73f70drunjaxb","ecc-k8s-037":"Type > hxfrz9veg1w8e2hj7qeegbpea0i","ecc-k8s-028":"Type > xkspyvvqg48s3fa34pqq76z6u63","ecc-k8s-069":"Type > d9mtxolbsukcf7zf5gfidi1rkf9","ecc-k8s-054":"Type > 78sxdqmapfgxzlfgjlkoi27d3na","ecc-k8s-088":"Type > nupzq9cme2arkid2yj4gq1yr4cv","ecc-k8s-006":"Type > kmal2hun44jv6qgnf4dqsnzvrs5","ecc-k8s-003":"Type > mypvf7ya7top4pgx1w1z3v21i1z","ecc-k8s-042":"Type > hzgszts1m89r0j9l3p7b17u0uox","ecc-k8s-077":"Type > r6y79ndgddmv2a0plaf9ncccakr","ecc-k8s-040":"Type > mfojr3dtk144ia2yzpy37atkjzq","ecc-k8s-021":"Type > xlcnkrsnkmc1vaphypmut7kzq06","ecc-k8s-068":"Type > w0m67dwnefyw8c6vet5havdmbvx","ecc-k8s-010":"Type > 5du6v9d2g9ullbotegv3bqaqlho","ecc-k8s-051":"Type > jccxfzjbgkkmynhbtx4t7rgaze1","ecc-k8s-063":"Type > uafa0n9ixls11vigebiardogwuc","ecc-k8s-019":"Type > 6qlvnv9g4u0c0hjeabjn66k6smw","ecc-k8s-022":"Type > up6zq2nfwq3e3ibwrq9to8pvq1m","ecc-k8s-025":"Type > 94ibh9wwx7n6s2ng4kwzh4p1kjs","ecc-k8s-058":"Type > ag4r5q4he0vs1p0toi7op1grhpz","ecc-k8s-001":"Type > mpl6n39i2lhvv3wpu5rkjytf7dm","ecc-k8s-065":"Type > obzqchf3izqzta3x1idgbxae5pc","ecc-k8s-039":"Type > fhmaauwy5bnapm60m77duo29po4","ecc-k8s-036":"Type > wuofvczlfkjg7t3euks4mz42fs0","ecc-k8s-048":"Type > k319lwrp8v82laazrlr4euymw2r","ecc-k8s-015":"Type > j4xvx9an92d8fu2pcv3rmco1iq0","ecc-k8s-057":"Type > gl7o4sjjha0987hfv09ys4xy0xr","ecc-k8s-086":"Type > altaxexrx39ajl6dtvi1c5ny7m7","ecc-k8s-020":"Type > excws3hl69xb1eb9vcv3n5s0uw0","ecc-k8s-078":"Type > yr36op4rdnpf1748s7uycemp80t","ecc-k8s-034":"Type > gsb30ncrs8qzqgx5j9wk2mbuamo","ecc-k8s-056":"Type > 9vh5kth8jsxinhzkskx0gs54yy7","ecc-k8s-045":"Type > syimbafgoufr1qzu6w4epqp7fhg","ecc-k8s-076":"Type > fluc43mb3x1g8ipc5vhhsym2n33","ecc-k8s-004":"Type > rtv2j8ee0a9xaancal3irmdwj0r","ecc-k8s-082":"Type > qu46i6tcufzqengi4b9a5n8t96q","ecc-k8s-018":"Type > fcloaquoz47qs78w637cwd8r3m0","ecc-k8s-011":"Type > xxsy6f5ugynx9grfmwumuvn1ely","ecc-k8s-050":"Type > veo2dsgsa8ve88dtksb25619rxx","ecc-k8s-009":"Type > 14r7rcblzv11147nt6z7c6vy8ap"} \ No newline at end of file diff --git a/tests/tests_metrics/mock_files/rulesets/caas-settings/RULES_TO_MITRE.json.gz b/tests/tests_metrics/mock_files/rulesets/caas-settings/RULES_TO_MITRE.json.gz new file mode 100644 index 000000000..7cc940cd1 --- /dev/null +++ b/tests/tests_metrics/mock_files/rulesets/caas-settings/RULES_TO_MITRE.json.gz @@ -0,0 +1 @@ +{"ecc-azure-056":{"Credential Access":[{"tn_id":"T1552","tn_name":"Unsecured Credentials"}]},"ecc-azure-016":{"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"}]},"ecc-azure-353":{},"ecc-azure-057":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-azure-108":{"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}]},"ecc-azure-038":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-azure-310":{"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"}]},"ecc-azure-270":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}]},"ecc-azure-367":{"Privilege Escalation":[{"tn_id":"T1068","tn_name":"Exploitation for Privilege Escalation"}]},"ecc-azure-314":{},"ecc-azure-039":{},"ecc-azure-044":{},"ecc-azure-042":{},"ecc-azure-068":{},"ecc-azure-137":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-azure-163":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-219":{},"ecc-azure-299":{},"ecc-azure-043":{},"ecc-azure-021":{"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"}]},"ecc-azure-117":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-azure-161":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-214":{"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"}]},"ecc-azure-205":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-azure-331":{},"ecc-azure-024":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}]},"ecc-azure-224":{},"ecc-azure-123":{"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force"}],"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service"},{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-176":{"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service"},{"tn_id":"T1499","tn_name":"Endpoint Denial of Service"}]},"ecc-azure-122":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-133":{},"ecc-azure-027":{},"ecc-azure-127":{"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force"}],"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]},{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-146":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-236":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}]},"ecc-azure-232":{},"ecc-azure-015":{},"ecc-azure-156":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-321":{},"ecc-azure-344":{"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"}]},"ecc-azure-119":{"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}],"Initial Access":[{"tn_id":"T1133","tn_name":"External Remote Services"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services","st":[{"st_id":"T1021.001","st_name":"Remote Desktop Protocol"},{"st_id":"T1021.004","st_name":"SSH"}]},{"tn_id":"T1563","tn_name":"Remote Service Session Hijacking","st":[{"st_id":"T1563.001","st_name":"SSH Hijacking"},{"st_id":"T1563.002","st_name":"RDP Hijacking"}]}],"Persistence":[{"tn_id":"T1133","tn_name":"External Remote Services"}],"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service"},{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-234":{},"ecc-azure-053":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1561","tn_name":"Disk Wipe"}]},"ecc-azure-126":{"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force"}],"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service"},{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-111":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-304":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.003","st_name":"Application Exhaustion Flood"}]}]},"ecc-azure-162":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-348":{},"ecc-azure-220":{},"ecc-azure-284":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1561","tn_name":"Disk Wipe"}]},"ecc-azure-069":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}]},"ecc-azure-014":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-azure-048":{"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}],"Initial Access":[{"tn_id":"T1133","tn_name":"External Remote Services"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services","st":[{"st_id":"T1021.001","st_name":"Remote Desktop Protocol"}]},{"tn_id":"T1563","tn_name":"Remote Service Session Hijacking","st":[{"st_id":"T1563.002","st_name":"RDP Hijacking"}]}],"Persistence":[{"tn_id":"T1133","tn_name":"External Remote Services"}]},"ecc-azure-341":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Privilege Escalation":[{"tn_id":"T1068","tn_name":"Exploitation for Privilege Escalation"}],"Execution":[{"tn_id":"T1059","tn_name":"Command and Scripting Interpreter"}]},"ecc-azure-110":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1491","tn_name":"Defacement"}]},"ecc-azure-328":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation"}]},"ecc-azure-160":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-128":{"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service"},{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-165":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-096":{"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"}]},"ecc-azure-150":{"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"}]}]},"ecc-azure-020":{"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"}]},"ecc-azure-215":{},"ecc-azure-159":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-364":{},"ecc-azure-030":{},"ecc-azure-239":{"Credential Access":[{"tn_id":"T1649","tn_name":"Steal or Forge Authentication Certificates"}]},"ecc-azure-106":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1491","tn_name":"Defacement"}]},"ecc-azure-228":{},"ecc-azure-045":{},"ecc-azure-343":{"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"}]},"ecc-azure-203":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-azure-059":{"Defense Evasion":[{"tn_id":"T1550","tn_name":"Use Alternate Authentication Material","st":[{"st_id":"T1550.001","st_name":"Application Access Token"}]}],"Lateral Movement":[{"tn_id":"T1550","tn_name":"Use Alternate Authentication Material","st":[{"st_id":"T1550.001","st_name":"Application Access Token"}]}]},"ecc-azure-290":{},"ecc-azure-060":{"Defense Evasion":[{"tn_id":"T1550","tn_name":"Use Alternate Authentication Material","st":[{"st_id":"T1550.001","st_name":"Application Access Token"}]}],"Lateral Movement":[{"tn_id":"T1550","tn_name":"Use Alternate Authentication Material","st":[{"st_id":"T1550.001","st_name":"Application Access Token"}]}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.003","st_name":"Application Exhaustion Flood"}]}]},"ecc-azure-009":{"Discovery":[{"tn_id":"T1619","tn_name":"Cloud Storage Object Discovery"}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}],"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}]},"ecc-azure-258":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Execution":[{"tn_id":"T1059","tn_name":"Command and Scripting Interpreter"}]},"ecc-azure-033":{"Impact":[{"tn_id":"T1486","tn_name":"Data Encrypted for Impact"}]},"ecc-azure-293":{},"ecc-azure-333":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-170":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-011":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"}],"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}]},"ecc-azure-148":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-180":{"Credential Access":[{"tn_id":"T1212","tn_name":"Exploitation for Credential Access"}]},"ecc-azure-158":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-283":{},"ecc-azure-216":{},"ecc-azure-144":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service"}]},"ecc-azure-256":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Execution":[{"tn_id":"T1059","tn_name":"Command and Scripting Interpreter"}]},"ecc-azure-012":{"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-azure-025":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}]},"ecc-azure-121":{"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force"}],"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service"},{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-197":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1561","tn_name":"Disk Wipe"}]},"ecc-azure-166":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-371":{},"ecc-azure-372":{},"ecc-azure-300":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}]},"ecc-azure-207":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-azure-376":{"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"}]},"ecc-azure-098":{"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"}]},"ecc-azure-311":{},"ecc-azure-319":{},"ecc-azure-173":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-026":{},"ecc-azure-031":{},"ecc-azure-287":{},"ecc-azure-342":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}]},"ecc-azure-177":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}]},"ecc-azure-151":{"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1049","tn_name":"System Network Connections Discovery"}]},"ecc-azure-149":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-145":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-323":{"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}]},"ecc-azure-227":{},"ecc-azure-277":{},"ecc-azure-171":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-240":{"Credential Access":[{"tn_id":"T1649","tn_name":"Steal or Forge Authentication Certificates"}]},"ecc-azure-071":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}]},"ecc-azure-288":{},"ecc-azure-318":{},"ecc-azure-257":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Execution":[{"tn_id":"T1059","tn_name":"Command and Scripting Interpreter"}]},"ecc-azure-054":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1561","tn_name":"Disk Wipe"}]},"ecc-azure-182":{},"ecc-azure-072":{"Credential Access":[{"tn_id":"T1552","tn_name":"Unsecured Credentials"}]},"ecc-azure-365":{},"ecc-azure-097":{"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"}]},"ecc-azure-109":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1491","tn_name":"Defacement"}]},"ecc-azure-147":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-302":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-010":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}],"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}]},"ecc-azure-326":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation"}]},"ecc-azure-006":{},"ecc-azure-132":{"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}],"Initial Access":[{"tn_id":"T1133","tn_name":"External Remote Services"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}]},"ecc-azure-125":{"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force"}],"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service"},{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-286":{"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service"}]},"ecc-azure-052":{"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]}]},"ecc-azure-282":{"Impact":[{"tn_id":"T1561","tn_name":"Disk Wipe"},{"tn_id":"T1565","tn_name":"Data Manipulation"}]},"ecc-azure-067":{},"ecc-azure-305":{"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}]},"ecc-azure-168":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-139":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-azure-179":{"Credential Access":[{"tn_id":"T1212","tn_name":"Exploitation for Credential Access"}]},"ecc-azure-370":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-349":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service"}]},"ecc-azure-028":{},"ecc-azure-141":{"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}]},"ecc-azure-007":{},"ecc-azure-356":{},"ecc-azure-346":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}]},"ecc-azure-267":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}]},"ecc-azure-327":{},"ecc-azure-065":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}]},"ecc-azure-099":{"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"}]},"ecc-azure-265":{"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"}]},"ecc-azure-345":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-azure-237":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}]},"ecc-azure-196":{"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"}]},"ecc-azure-094":{"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"}]},"ecc-azure-358":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service"}]},"ecc-azure-295":{"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force"}]},"ecc-azure-281":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}]},"ecc-azure-357":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-120":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-325":{"Impact":[{"tn_id":"T1561","tn_name":"Disk Wipe"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-azure-317":{},"ecc-azure-112":{},"ecc-azure-005":{},"ecc-azure-167":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-374":{},"ecc-azure-272":{"Defense Evasion":[{"tn_id":"T1027","tn_name":"Obfuscated Files or Information"}],"Execution":[{"tn_id":"T1059","tn_name":"Command and Scripting Interpreter"}],"Initial Access":[{"tn_id":"T1566","tn_name":"Phishing"}]},"ecc-azure-022":{},"ecc-azure-124":{"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force"}],"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service"},{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-275":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-azure-354":{"Discovery":[{"tn_id":"T1613","tn_name":"Container and Resource Discovery"}],"Collection":[{"tn_id":"T1119","tn_name":"Automated Collection"},{"tn_id":"T1213","tn_name":"Data from Information Repositories","st":[{"st_id":"T1213.003","st_name":"Code Repositories"}]}]},"ecc-azure-095":{"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"}]},"ecc-azure-055":{"Credential Access":[{"tn_id":"T1552","tn_name":"Unsecured Credentials","st":[{"st_id":"T1552.004","st_name":"Private Keys"}]}]},"ecc-azure-036":{},"ecc-azure-340":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Privilege Escalation":[{"tn_id":"T1068","tn_name":"Exploitation for Privilege Escalation"}],"Execution":[{"tn_id":"T1059","tn_name":"Command and Scripting Interpreter"}]},"ecc-azure-301":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-101":{"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"}]},"ecc-azure-201":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-azure-064":{"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}]},"ecc-azure-339":{},"ecc-azure-116":{"Defense Evasion":[{"tn_id":"T1027","tn_name":"Obfuscated Files or Information"}],"Execution":[{"tn_id":"T1059","tn_name":"Command and Scripting Interpreter"}],"Initial Access":[{"tn_id":"T1566","tn_name":"Phishing"}]},"ecc-azure-276":{},"ecc-azure-113":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1561","tn_name":"Disk Wipe"}]},"ecc-azure-066":{},"ecc-azure-157":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-102":{"Defense Evasion":[{"tn_id":"T1562","tn_name":"Impair Defenses"}]},"ecc-azure-032":{"Initial Access":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.003","st_name":"Local Accounts"}]}],"Privilege Escalation":[{"tn_id":"T1068","tn_name":"Exploitation for Privilege Escalation"},{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.003","st_name":"Local Accounts"}]}]},"ecc-azure-050":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-213":{"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"}]},"ecc-azure-200":{"Credential Access":[{"tn_id":"T1552","tn_name":"Unsecured Credentials"}]},"ecc-azure-289":{"Initial Access":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.003","st_name":"Local Accounts"}]}],"Privilege Escalation":[{"tn_id":"T1068","tn_name":"Exploitation for Privilege Escalation"},{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.003","st_name":"Local Accounts"}]}]},"ecc-azure-225":{},"ecc-azure-241":{"Credential Access":[{"tn_id":"T1649","tn_name":"Steal or Forge Authentication Certificates"}]},"ecc-azure-206":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"},{"tn_id":"T1649","tn_name":"Steal or Forge Authentication Certificates"}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}]},"ecc-azure-152":{"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}],"Initial Access":[{"tn_id":"T1133","tn_name":"External Remote Services"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services","st":[{"st_id":"T1021.001","st_name":"Remote Desktop Protocol"},{"st_id":"T1021.004","st_name":"SSH"}]},{"tn_id":"T1563","tn_name":"Remote Service Session Hijacking","st":[{"st_id":"T1563.001","st_name":"SSH Hijacking"},{"st_id":"T1563.002","st_name":"RDP Hijacking"}]}],"Persistence":[{"tn_id":"T1133","tn_name":"External Remote Services"}]},"ecc-azure-184":{"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}]},"ecc-azure-329":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation"}]},"ecc-azure-023":{},"ecc-azure-294":{},"ecc-azure-129":{"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force"}],"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service"},{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-105":{"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}],"Persistence":[{"tn_id":"T1098","tn_name":"Account Manipulation"}]},"ecc-azure-332":{},"ecc-azure-058":{"Initial Access":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.003","st_name":"Local Accounts"},{"st_id":"T1078.004","st_name":"Cloud Accounts"}]}],"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.003","st_name":"Local Accounts"}]}]},"ecc-azure-379":{},"ecc-azure-202":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-azure-350":{},"ecc-azure-355":{},"ecc-azure-278":{},"ecc-azure-378":{},"ecc-azure-362":{},"ecc-azure-334":{"Privilege Escalation":[{"tn_id":"T1068","tn_name":"Exploitation for Privilege Escalation"}]},"ecc-azure-142":{"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]}]},"ecc-azure-306":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-azure-070":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}]},"ecc-azure-181":{"Credential Access":[{"tn_id":"T1212","tn_name":"Exploitation for Credential Access"}]},"ecc-azure-359":{},"ecc-azure-351":{},"ecc-azure-231":{},"ecc-azure-337":{},"ecc-azure-164":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-155":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-061":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}]},"ecc-azure-004":{"Defense Evasion":[{"tn_id":"T1562","tn_name":"Impair Defenses"}]},"ecc-azure-130":{"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service"},{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-373":{},"ecc-azure-238":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}]},"ecc-azure-002":{"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]}]},"ecc-azure-199":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}]},"ecc-azure-298":{},"ecc-azure-046":{},"ecc-azure-217":{},"ecc-azure-291":{},"ecc-azure-049":{"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}],"Initial Access":[{"tn_id":"T1133","tn_name":"External Remote Services"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services","st":[{"st_id":"T1021.004","st_name":"SSH"}]},{"tn_id":"T1563","tn_name":"Remote Service Session Hijacking","st":[{"st_id":"T1563.001","st_name":"SSH Hijacking"}]}],"Persistence":[{"tn_id":"T1133","tn_name":"External Remote Services"}]},"ecc-azure-313":{},"ecc-azure-204":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation"}]},"ecc-azure-279":{"Initial Access":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.003","st_name":"Local Accounts"}]}],"Privilege Escalation":[{"tn_id":"T1068","tn_name":"Exploitation for Privilege Escalation"},{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.003","st_name":"Local Accounts"}]}]},"ecc-azure-235":{},"ecc-azure-178":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}]},"ecc-azure-172":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-226":{},"ecc-azure-324":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-azure-143":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-280":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-369":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-azure-100":{"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"}]},"ecc-azure-218":{},"ecc-azure-368":{"Privilege Escalation":[{"tn_id":"T1068","tn_name":"Exploitation for Privilege Escalation"}]},"ecc-azure-103":{"Defense Evasion":[{"tn_id":"T1562","tn_name":"Impair Defenses"}]},"ecc-azure-174":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-azure-347":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation"}]},"ecc-azure-037":{"Discovery":[{"tn_id":"T1580","tn_name":"Cloud Infrastructure Discovery"}]},"ecc-azure-222":{},"ecc-azure-131":{"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}],"Initial Access":[{"tn_id":"T1133","tn_name":"External Remote Services"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}]},"ecc-azure-296":{"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force"}]},"ecc-azure-336":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation"}]},"ecc-azure-013":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-azure-008":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}],"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}]},"ecc-gcp-246":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}]},"ecc-gcp-401":{"Defense Evasion":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Persistence":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Initial Access":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}]},"ecc-gcp-206":{},"ecc-gcp-065":{},"ecc-gcp-272":{},"ecc-gcp-262":{"Defense Evasion":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]},{"tn_id":"T1578","tn_name":"Modify Cloud Compute Infrastructure","st":[{"st_id":"T1578.001","st_name":"Create Snapshot"},{"st_id":"T1578.002","st_name":"Create Cloud Instance"},{"st_id":"T1578.003","st_name":"Delete Cloud Instance"},{"st_id":"T1578.004","st_name":"Revert Cloud Instance"}]},{"tn_id":"T1562","tn_name":"Impair Defenses","st":[{"st_id":"T1562.001","st_name":"Disable or Modify Tools"},{"st_id":"T1562.007","st_name":"Disable or Modify Cloud Firewall"}]}],"Persistence":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]},{"tn_id":"T1098","tn_name":"Account Manipulation","st":[{"st_id":"T1098.001","st_name":"Additional Cloud Credentials"},{"st_id":"T1098.003","st_name":"Additional Cloud Roles"}]}],"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]}],"Initial Access":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]}],"Discovery":[{"tn_id":"T1619","tn_name":"Cloud Storage Object Discovery"},{"tn_id":"T1580","tn_name":"Cloud Infrastructure Discovery"}],"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-gcp-310":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}],"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Exfiltration":[{"tn_id":"T1020","tn_name":"Automated Exfiltration","st":[{"st_id":"T1020.001","st_name":"Traffic Duplication"}]}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}]},"ecc-gcp-202":{},"ecc-gcp-294":{"Exfiltration":[{"tn_id":"T1537","tn_name":"Transfer Data to Cloud Account"}],"Defense Evasion":[{"tn_id":"T1562","tn_name":"Impair Defenses","st":[{"st_id":"T1562.001","st_name":"Disable or Modify Tools"}]}],"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-gcp-076":{},"ecc-gcp-281":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service"}],"Credential Access":[{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"},{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}]},"ecc-gcp-123":{"Credential Access":[{"tn_id":"T1212","tn_name":"Exploitation for Credential Access"}],"Defense Evasion":[{"tn_id":"T1211","tn_name":"Exploitation for Defense Evasion"}],"Privilege Escalation":[{"tn_id":"T1068","tn_name":"Exploitation for Privilege Escalation"}]},"ecc-gcp-127":{"Execution":[{"tn_id":"T1204","tn_name":"User Execution","st":[{"st_id":"T1204.003","st_name":"Malicious Image"}]}]},"ecc-gcp-311":{},"ecc-gcp-214":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-gcp-068":{},"ecc-gcp-268":{"Defense Evasion":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]},{"tn_id":"T1578","tn_name":"Modify Cloud Compute Infrastructure","st":[{"st_id":"T1578.001","st_name":"Create Snapshot"},{"st_id":"T1578.002","st_name":"Create Cloud Instance"},{"st_id":"T1578.003","st_name":"Delete Cloud Instance"},{"st_id":"T1578.004","st_name":"Revert Cloud Instance"}]},{"tn_id":"T1562","tn_name":"Impair Defenses","st":[{"st_id":"T1562.001","st_name":"Disable or Modify Tools"},{"st_id":"T1562.007","st_name":"Disable or Modify Cloud Firewall"}]}],"Persistence":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]},{"tn_id":"T1098","tn_name":"Account Manipulation","st":[{"st_id":"T1098.001","st_name":"Additional Cloud Credentials"},{"st_id":"T1098.003","st_name":"Additional Cloud Roles"}]}],"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]}],"Initial Access":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]}],"Discovery":[{"tn_id":"T1619","tn_name":"Cloud Storage Object Discovery"},{"tn_id":"T1580","tn_name":"Cloud Infrastructure Discovery"}],"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-gcp-220":{"Initial Access":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]},{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}],"Persistence":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service"}],"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Defense Evasion":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Reconnaissance":[{"tn_id":"T1595","tn_name":"Active Scanning","st":[{"st_id":"T1595.002","st_name":"Vulnerability Scanning"}]}]},"ecc-gcp-415":{"Credential Access":[{"tn_id":"T1212","tn_name":"Exploitation for Credential Access"}],"Defense Evasion":[{"tn_id":"T1211","tn_name":"Exploitation for Defense Evasion"}],"Privilege Escalation":[{"tn_id":"T1068","tn_name":"Exploitation for Privilege Escalation"}]},"ecc-gcp-251":{"Persistence":[{"tn_id":"T1098","tn_name":"Account Manipulation","st":[{"st_id":"T1098.001","st_name":"Additional Cloud Credentials"}]},{"tn_id":"T1136","tn_name":"Create Account","st":[{"st_id":"T1136.003","st_name":"Cloud Account"}]}],"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"}],"Defense Evasion":[{"tn_id":"T1562","tn_name":"Impair Defenses","st":[{"st_id":"T1562.001","st_name":"Disable or Modify Tools"},{"st_id":"T1562.007","st_name":"Disable or Modify Cloud Firewall"}]},{"tn_id":"T1578","tn_name":"Modify Cloud Compute Infrastructure","st":[{"st_id":"T1578.001","st_name":"Create Snapshot"},{"st_id":"T1578.002","st_name":"Create Cloud Instance"},{"st_id":"T1578.003","st_name":"Delete Cloud Instance"},{"st_id":"T1578.004","st_name":"Revert Cloud Instance"}]}],"Discovery":[{"tn_id":"T1580","tn_name":"Cloud Infrastructure Discovery"},{"tn_id":"T1087","tn_name":"Account Discovery","st":[{"st_id":"T1087.004","st_name":"Cloud Account"}]},{"tn_id":"T1619","tn_name":"Cloud Storage Object Discovery"},{"tn_id":"T1069","tn_name":"Permission Groups Discovery","st":[{"st_id":"T1069.003","st_name":"Cloud Groups"}]}],"Exfiltration":[{"tn_id":"T1537","tn_name":"Transfer Data to Cloud Account"}],"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}]},"ecc-gcp-118":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service"}],"Credential Access":[{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"},{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}]},"ecc-gcp-126":{"Credential Access":[{"tn_id":"T1212","tn_name":"Exploitation for Credential Access"}],"Defense Evasion":[{"tn_id":"T1211","tn_name":"Exploitation for Defense Evasion"}],"Privilege Escalation":[{"tn_id":"T1068","tn_name":"Exploitation for Privilege Escalation"}]},"ecc-gcp-122":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.004","st_name":"Application or System Exploitation"},{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}]},"ecc-gcp-244":{"Execution":[{"tn_id":"T1204","tn_name":"User Execution"}],"Defense Evasion":[{"tn_id":"T1562","tn_name":"Impair Defenses","st":[{"st_id":"T1562.001","st_name":"Disable or Modify Tools"}]}]},"ecc-gcp-324":{"Privilege Escalation":[{"tn_id":"T1134","tn_name":"Access Token Manipulation","st":[{"st_id":"T1134.001","st_name":"Token Impersonation/Theft"}]}]},"ecc-gcp-443":{},"ecc-gcp-409":{"Credential Access":[{"tn_id":"T1212","tn_name":"Exploitation for Credential Access"}],"Defense Evasion":[{"tn_id":"T1211","tn_name":"Exploitation for Defense Evasion"}],"Privilege Escalation":[{"tn_id":"T1068","tn_name":"Exploitation for Privilege Escalation"}],"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}]},"ecc-gcp-200":{},"ecc-gcp-049":{"Execution":[{"tn_id":"T1610","tn_name":"Deploy Container"},{"tn_id":"T1053","tn_name":"Scheduled Task/Job","st":[{"st_id":"T1053.007","st_name":"Container Orchestration Job"}]}],"Privilege Escalation":[{"tn_id":"T1053","tn_name":"Scheduled Task/Job","st":[{"st_id":"T1053.007","st_name":"Container Orchestration Job"}]}],"Defense Evasion":[{"tn_id":"T1562","tn_name":"Impair Defenses","st":[{"st_id":"T1562.001","st_name":"Disable or Modify Tools"}]},{"tn_id":"T1610","tn_name":"Deploy Container"}],"Discovery":[{"tn_id":"T1613","tn_name":"Container and Resource Discovery"}]},"ecc-gcp-083":{"Impact":[{"tn_id":"T1489","tn_name":"Service Stop"}]},"ecc-gcp-051":{},"ecc-gcp-167":{"Defense Evasion":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]}],"Persistence":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]},{"tn_id":"T1098","tn_name":"Account Manipulation"}],"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]}],"Initial Access":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]},{"tn_id":"T1199","tn_name":"Trusted Relationship"}]},"ecc-gcp-022":{},"ecc-gcp-203":{},"ecc-gcp-136":{"Impact":[{"tn_id":"T1529","tn_name":"System Shutdown/Reboot"},{"tn_id":"T1489","tn_name":"Service Stop"}]},"ecc-gcp-208":{"Collection":[{"tn_id":"T1119","tn_name":"Automated Collection"}],"Execution":[{"tn_id":"T1059","tn_name":"Command and Scripting Interpreter"}]},"ecc-gcp-125":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}],"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}]},"ecc-gcp-347":{"Impact":[{"tn_id":"T1489","tn_name":"Service Stop"}]},"ecc-gcp-306":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}],"Defense Evasion":[{"tn_id":"T1562","tn_name":"Impair Defenses","st":[{"st_id":"T1562.001","st_name":"Disable or Modify Tools"}]}]},"ecc-gcp-044":{"Initial Access":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]},{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service"}]},"ecc-gcp-314":{},"ecc-gcp-260":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}],"Defense Evasion":[{"tn_id":"T1562","tn_name":"Impair Defenses","st":[{"st_id":"T1562.001","st_name":"Disable or Modify Tools"}]}]},"ecc-gcp-342":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service"}],"Credential Access":[{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"},{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}]},"ecc-gcp-237":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"}],"Execution":[{"tn_id":"T1203","tn_name":"Exploitation for Client Execution"}],"Privilege Escalation":[{"tn_id":"T1068","tn_name":"Exploitation for Privilege Escalation"}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-gcp-072":{},"ecc-gcp-256":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service"}],"Credential Access":[{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"},{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}]},"ecc-gcp-054":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Execution":[{"tn_id":"T1072","tn_name":"Software Deployment Tools"}],"Lateral Movement":[{"tn_id":"T1072","tn_name":"Software Deployment Tools"}],"Credential Access":[{"tn_id":"T1212","tn_name":"Exploitation for Credential Access"}],"Defense Evasion":[{"tn_id":"T1211","tn_name":"Exploitation for Defense Evasion"}],"Privilege Escalation":[{"tn_id":"T1068","tn_name":"Exploitation for Privilege Escalation"}]},"ecc-gcp-230":{"Impact":[{"tn_id":"T1489","tn_name":"Service Stop"}]},"ecc-gcp-277":{"Initial Access":[{"tn_id":"T1566","tn_name":"Phishing"}]},"ecc-gcp-008":{"Credential Access":[{"tn_id":"T1552","tn_name":"Unsecured Credentials"}]},"ecc-gcp-250":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}]},"ecc-gcp-304":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-gcp-152":{},"ecc-gcp-252":{"Credential Access":[{"tn_id":"T1212","tn_name":"Exploitation for Credential Access"}],"Defense Evasion":[{"tn_id":"T1211","tn_name":"Exploitation for Defense Evasion"}],"Privilege Escalation":[{"tn_id":"T1068","tn_name":"Exploitation for Privilege Escalation"}]},"ecc-gcp-445":{},"ecc-gcp-070":{"Reconnaissance":[{"tn_id":"T1595","tn_name":"Active Scanning","st":[{"st_id":"T1595.002","st_name":"Vulnerability Scanning"}]}],"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Command and Control":[{"tn_id":"T1071","tn_name":"Application Layer Protocol","st":[{"st_id":"T1071.001","st_name":"Web Protocols"}]}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service"}]},"ecc-gcp-035":{"Credential Access":[{"tn_id":"T1552","tn_name":"Unsecured Credentials","st":[{"st_id":"T1552.005","st_name":"Cloud Instance Metadata API"}]}]},"ecc-gcp-017":{},"ecc-gcp-141":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-gcp-193":{"Credential Access":[{"tn_id":"T1552","tn_name":"Unsecured Credentials","st":[{"st_id":"T1552.005","st_name":"Cloud Instance Metadata API"}]}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services","st":[{"st_id":"T1021.004","st_name":"SSH"}]}]},"ecc-gcp-175":{"Discovery":[{"tn_id":"T1619","tn_name":"Cloud Storage Object Discovery"}],"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}]},"ecc-gcp-199":{},"ecc-gcp-185":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1486","tn_name":"Data Encrypted for Impact"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-gcp-131":{"Initial Access":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]},{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}],"Persistence":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service"}],"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Defense Evasion":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Reconnaissance":[{"tn_id":"T1595","tn_name":"Active Scanning","st":[{"st_id":"T1595.002","st_name":"Vulnerability Scanning"}]}]},"ecc-gcp-207":{},"ecc-gcp-004":{"Defense Evasion":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]},{"tn_id":"T1578","tn_name":"Modify Cloud Compute Infrastructure","st":[{"st_id":"T1578.001","st_name":"Create Snapshot"},{"st_id":"T1578.002","st_name":"Create Cloud Instance"},{"st_id":"T1578.003","st_name":"Delete Cloud Instance"},{"st_id":"T1578.004","st_name":"Revert Cloud Instance"}]},{"tn_id":"T1562","tn_name":"Impair Defenses","st":[{"st_id":"T1562.001","st_name":"Disable or Modify Tools"},{"st_id":"T1562.007","st_name":"Disable or Modify Cloud Firewall"}]}],"Persistence":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]},{"tn_id":"T1098","tn_name":"Account Manipulation","st":[{"st_id":"T1098.001","st_name":"Additional Cloud Credentials"},{"st_id":"T1098.003","st_name":"Additional Cloud Roles"}]}],"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]}],"Initial Access":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]}],"Discovery":[{"tn_id":"T1619","tn_name":"Cloud Storage Object Discovery"},{"tn_id":"T1580","tn_name":"Cloud Infrastructure Discovery"}],"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1486","tn_name":"Data Encrypted for Impact"}]},"ecc-gcp-289":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service"}],"Credential Access":[{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"},{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}]},"ecc-gcp-104":{"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"}]},{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]}],"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"},{"tn_id":"T1021","tn_name":"Remote Services","st":[{"st_id":"T1021.001","st_name":"Remote Desktop Protocol"},{"st_id":"T1021.004","st_name":"SSH"}]}],"Persistence":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"},{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Defense Evasion":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}]},"ecc-gcp-273":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service"}],"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}]},"ecc-gcp-234":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-gcp-117":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service"}],"Credential Access":[{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"},{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}]},"ecc-gcp-309":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-gcp-103":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-gcp-271":{},"ecc-gcp-346":{},"ecc-gcp-020":{},"ecc-gcp-432":{"Initial Access":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]},{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}],"Persistence":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service"}],"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Defense Evasion":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Reconnaissance":[{"tn_id":"T1595","tn_name":"Active Scanning","st":[{"st_id":"T1595.002","st_name":"Vulnerability Scanning"}]}]},"ecc-gcp-087":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}]},"ecc-gcp-448":{},"ecc-gcp-032":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.003","st_name":"Application Exhaustion Flood"}]}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}]},"ecc-gcp-186":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}]},"ecc-gcp-063":{"Persistence":[{"tn_id":"T1098","tn_name":"Account Manipulation","st":[{"st_id":"T1098.001","st_name":"Additional Cloud Credentials"}]},{"tn_id":"T1136","tn_name":"Create Account","st":[{"st_id":"T1136.003","st_name":"Cloud Account"}]}],"Execution":[{"tn_id":"T1648","tn_name":"Serverless Execution"}],"Defense Evasion":[{"tn_id":"T1562","tn_name":"Impair Defenses","st":[{"st_id":"T1562.001","st_name":"Disable or Modify Tools"},{"st_id":"T1562.007","st_name":"Disable or Modify Cloud Firewall"}]},{"tn_id":"T1578","tn_name":"Modify Cloud Compute Infrastructure","st":[{"st_id":"T1578.001","st_name":"Create Snapshot"},{"st_id":"T1578.002","st_name":"Create Cloud Instance"},{"st_id":"T1578.003","st_name":"Delete Cloud Instance"},{"st_id":"T1578.004","st_name":"Revert Cloud Instance"}]}],"Discovery":[{"tn_id":"T1580","tn_name":"Cloud Infrastructure Discovery"},{"tn_id":"T1087","tn_name":"Account Discovery","st":[{"st_id":"T1087.004","st_name":"Cloud Account"}]},{"tn_id":"T1069","tn_name":"Permission Groups Discovery","st":[{"st_id":"T1069.003","st_name":"Cloud Groups"}]}]},"ecc-gcp-258":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-gcp-166":{"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"}]},{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]}],"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"},{"tn_id":"T1021","tn_name":"Remote Services","st":[{"st_id":"T1021.001","st_name":"Remote Desktop Protocol"},{"st_id":"T1021.004","st_name":"SSH"}]}],"Persistence":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"},{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Defense Evasion":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}]},"ecc-gcp-449":{},"ecc-gcp-227":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-gcp-124":{"Credential Access":[{"tn_id":"T1212","tn_name":"Exploitation for Credential Access"}],"Defense Evasion":[{"tn_id":"T1211","tn_name":"Exploitation for Defense Evasion"}],"Privilege Escalation":[{"tn_id":"T1068","tn_name":"Exploitation for Privilege Escalation"}]},"ecc-gcp-021":{},"ecc-gcp-173":{"Initial Access":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]},{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}],"Persistence":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service"}],"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Defense Evasion":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Reconnaissance":[{"tn_id":"T1595","tn_name":"Active Scanning","st":[{"st_id":"T1595.002","st_name":"Vulnerability Scanning"}]}]},"ecc-gcp-061":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-gcp-228":{},"ecc-gcp-119":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service"}],"Credential Access":[{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"},{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}]},"ecc-gcp-218":{"Credential Access":[{"tn_id":"T1552","tn_name":"Unsecured Credentials","st":[{"st_id":"T1552.005","st_name":"Cloud Instance Metadata API"}]}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services","st":[{"st_id":"T1021.004","st_name":"SSH"}]}]},"ecc-gcp-093":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}],"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Exfiltration":[{"tn_id":"T1048","tn_name":"Exfiltration Over Alternative Protocol","st":[{"st_id":"T1048.003","st_name":"Exfiltration Over Unencrypted Non-C2 Protocol"}]},{"tn_id":"T1020","tn_name":"Automated Exfiltration","st":[{"st_id":"T1020.001","st_name":"Traffic Duplication"}]}]},"ecc-gcp-215":{"Credential Access":[{"tn_id":"T1552","tn_name":"Unsecured Credentials","st":[{"st_id":"T1552.005","st_name":"Cloud Instance Metadata API"}]}]},"ecc-gcp-446":{},"ecc-gcp-025":{"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"}]},{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]}],"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"},{"tn_id":"T1021","tn_name":"Remote Services","st":[{"st_id":"T1021.001","st_name":"Remote Desktop Protocol"},{"st_id":"T1021.004","st_name":"SSH"}]}],"Persistence":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"},{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Defense Evasion":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}]},"ecc-gcp-001":{"Defense Evasion":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]}],"Persistence":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]},{"tn_id":"T1098","tn_name":"Account Manipulation"}],"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]}],"Initial Access":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]},{"tn_id":"T1199","tn_name":"Trusted Relationship"}]},"ecc-gcp-023":{},"ecc-gcp-253":{},"ecc-gcp-288":{"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service"},{"tn_id":"T1499","tn_name":"Endpoint Denial of Service"}]},"ecc-gcp-184":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1486","tn_name":"Data Encrypted for Impact"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}],"Exfiltration":[{"tn_id":"T1537","tn_name":"Transfer Data to Cloud Account"}]},"ecc-gcp-172":{"Persistence":[{"tn_id":"T1525","tn_name":"Implant Internal Image"}],"Execution":[{"tn_id":"T1204","tn_name":"User Execution","st":[{"st_id":"T1204.003","st_name":"Malicious Image"}]}]},"ecc-gcp-092":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}],"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}]},"ecc-gcp-067":{"Credential Access":[{"tn_id":"T1528","tn_name":"Steal Application Access Token"},{"tn_id":"T1555","tn_name":"Credentials from Password Stores"}]},"ecc-gcp-101":{},"ecc-gcp-036":{"Credential Access":[{"tn_id":"T1552","tn_name":"Unsecured Credentials","st":[{"st_id":"T1552.005","st_name":"Cloud Instance Metadata API"}]}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services","st":[{"st_id":"T1021.004","st_name":"SSH"}]}]},"ecc-gcp-153":{},"ecc-gcp-019":{},"ecc-gcp-212":{"Collection":[{"tn_id":"T1119","tn_name":"Automated Collection"},{"tn_id":"T1005","tn_name":"Data from Local System"}]},"ecc-gcp-050":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}]},"ecc-gcp-213":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-gcp-229":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-gcp-130":{},"ecc-gcp-006":{"Initial Access":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]}]},"ecc-gcp-012":{},"ecc-gcp-335":{"Privilege Escalation":[{"tn_id":"T1134","tn_name":"Access Token Manipulation","st":[{"st_id":"T1134.001","st_name":"Token Impersonation/Theft"}]}]},"ecc-gcp-307":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}],"Defense Evasion":[{"tn_id":"T1562","tn_name":"Impair Defenses","st":[{"st_id":"T1562.001","st_name":"Disable or Modify Tools"}]}],"Execution":[{"tn_id":"T1204","tn_name":"User Execution","st":[{"st_id":"T1204.003","st_name":"Malicious Image"}]}],"Persistence":[{"tn_id":"T1525","tn_name":"Implant Internal Image"}]},"ecc-gcp-030":{"Persistence":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service"}],"Initial Access":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]},{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Defense Evasion":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services","st":[{"st_id":"T1021.004","st_name":"SSH"}]}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}]},"ecc-gcp-057":{"Persistence":[{"tn_id":"T1133","tn_name":"External Remote Services"}],"Initial Access":[{"tn_id":"T1133","tn_name":"External Remote Services"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service"}]},"ecc-gcp-450":{},"ecc-gcp-015":{},"ecc-gcp-266":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service"}],"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}]},"ecc-gcp-178":{},"ecc-gcp-293":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-gcp-291":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-gcp-043":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Exfiltration":[{"tn_id":"T1020","tn_name":"Automated Exfiltration","st":[{"st_id":"T1020.001","st_name":"Traffic Duplication"}]}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}]},"ecc-gcp-438":{"Persistence":[{"tn_id":"T1525","tn_name":"Implant Internal Image"}],"Execution":[{"tn_id":"T1204","tn_name":"User Execution","st":[{"st_id":"T1204.003","st_name":"Malicious Image"}]}]},"ecc-gcp-039":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-gcp-187":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1486","tn_name":"Data Encrypted for Impact"},{"tn_id":"T1491","tn_name":"Defacement","st":[{"st_id":"T1491.001","st_name":"Internal Defacement"},{"st_id":"T1491.002","st_name":"External Defacement"}]}]},"ecc-gcp-340":{"Command and Control":[{"tn_id":"T1071","tn_name":"Application Layer Protocol","st":[{"st_id":"T1071.001","st_name":"Web Protocols"}]}]},"ecc-gcp-177":{},"ecc-gcp-058":{},"ecc-gcp-302":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Command and Control":[{"tn_id":"T1071","tn_name":"Application Layer Protocol","st":[{"st_id":"T1071.001","st_name":"Web Protocols"}]}],"Exfiltration":[{"tn_id":"T1041","tn_name":"Exfiltration Over C2 Channel"}]},"ecc-gcp-181":{},"ecc-gcp-264":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}]},"ecc-gcp-082":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-gcp-209":{},"ecc-gcp-162":{},"ecc-gcp-323":{"Privilege Escalation":[{"tn_id":"T1134","tn_name":"Access Token Manipulation","st":[{"st_id":"T1134.001","st_name":"Token Impersonation/Theft"}]}]},"ecc-gcp-013":{"Credential Access":[{"tn_id":"T1552","tn_name":"Unsecured Credentials"}]},"ecc-gcp-386":{"Execution":[{"tn_id":"T1203","tn_name":"Exploitation for Client Execution"}]},"ecc-gcp-077":{"Initial Access":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Defense Evasion":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Persistence":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}]},"ecc-gcp-194":{"Credential Access":[{"tn_id":"T1552","tn_name":"Unsecured Credentials","st":[{"st_id":"T1552.005","st_name":"Cloud Instance Metadata API"}]}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services","st":[{"st_id":"T1021.004","st_name":"SSH"}]}]},"ecc-gcp-066":{},"ecc-gcp-007":{"Persistence":[{"tn_id":"T1098","tn_name":"Account Manipulation","st":[{"st_id":"T1098.001","st_name":"Additional Cloud Credentials"}]},{"tn_id":"T1136","tn_name":"Create Account","st":[{"st_id":"T1136.003","st_name":"Cloud Account"}]}]},"ecc-gcp-143":{"Defense Evasion":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]},{"tn_id":"T1578","tn_name":"Modify Cloud Compute Infrastructure","st":[{"st_id":"T1578.001","st_name":"Create Snapshot"},{"st_id":"T1578.002","st_name":"Create Cloud Instance"},{"st_id":"T1578.003","st_name":"Delete Cloud Instance"},{"st_id":"T1578.004","st_name":"Revert Cloud Instance"}]},{"tn_id":"T1562","tn_name":"Impair Defenses","st":[{"st_id":"T1562.001","st_name":"Disable or Modify Tools"},{"st_id":"T1562.007","st_name":"Disable or Modify Cloud Firewall"}]}],"Persistence":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]},{"tn_id":"T1098","tn_name":"Account Manipulation","st":[{"st_id":"T1098.001","st_name":"Additional Cloud Credentials"},{"st_id":"T1098.003","st_name":"Additional Cloud Roles"}]}],"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]}],"Initial Access":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]}],"Discovery":[{"tn_id":"T1619","tn_name":"Cloud Storage Object Discovery"},{"tn_id":"T1580","tn_name":"Cloud Infrastructure Discovery"}]},"ecc-gcp-129":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service"}],"Credential Access":[{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"},{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}]},"ecc-gcp-248":{},"ecc-gcp-046":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service"}]},"ecc-gcp-091":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}],"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Exfiltration":[{"tn_id":"T1048","tn_name":"Exfiltration Over Alternative Protocol","st":[{"st_id":"T1048.003","st_name":"Exfiltration Over Unencrypted Non-C2 Protocol"}]},{"tn_id":"T1020","tn_name":"Automated Exfiltration","st":[{"st_id":"T1020.001","st_name":"Traffic Duplication"}]}]},"ecc-gcp-088":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}]},"ecc-gcp-205":{},"ecc-gcp-027":{"Resource Development":[{"tn_id":"T1584","tn_name":"Compromise Infrastructure","st":[{"st_id":"T1584.001","st_name":"Domains"},{"st_id":"T1584.002","st_name":"DNS Server"}]},{"tn_id":"T1583","tn_name":"Acquire Infrastructure","st":[{"st_id":"T1583.001","st_name":"Domains"},{"st_id":"T1583.002","st_name":"DNS Server"}]}]},"ecc-gcp-038":{"Command and Control":[{"tn_id":"T1090","tn_name":"Proxy","st":[{"st_id":"T1090.001","st_name":"Internal Proxy"}]}]},"ecc-gcp-138":{},"ecc-gcp-292":{"Exfiltration":[{"tn_id":"T1537","tn_name":"Transfer Data to Cloud Account"}],"Defense Evasion":[{"tn_id":"T1562","tn_name":"Impair Defenses","st":[{"st_id":"T1562.001","st_name":"Disable or Modify Tools"}]}],"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-gcp-295":{"Exfiltration":[{"tn_id":"T1537","tn_name":"Transfer Data to Cloud Account"}],"Defense Evasion":[{"tn_id":"T1562","tn_name":"Impair Defenses","st":[{"st_id":"T1562.001","st_name":"Disable or Modify Tools"}]}],"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-gcp-241":{"Defense Evasion":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]},{"tn_id":"T1578","tn_name":"Modify Cloud Compute Infrastructure","st":[{"st_id":"T1578.001","st_name":"Create Snapshot"},{"st_id":"T1578.002","st_name":"Create Cloud Instance"},{"st_id":"T1578.003","st_name":"Delete Cloud Instance"},{"st_id":"T1578.004","st_name":"Revert Cloud Instance"}]},{"tn_id":"T1562","tn_name":"Impair Defenses","st":[{"st_id":"T1562.001","st_name":"Disable or Modify Tools"},{"st_id":"T1562.007","st_name":"Disable or Modify Cloud Firewall"}]}],"Persistence":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]},{"tn_id":"T1098","tn_name":"Account Manipulation","st":[{"st_id":"T1098.001","st_name":"Additional Cloud Credentials"},{"st_id":"T1098.003","st_name":"Additional Cloud Roles"}]}],"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]}],"Initial Access":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]}],"Discovery":[{"tn_id":"T1619","tn_name":"Cloud Storage Object Discovery"},{"tn_id":"T1580","tn_name":"Cloud Infrastructure Discovery"}],"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-gcp-010":{},"ecc-gcp-257":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}]},"ecc-gcp-221":{"Discovery":[{"tn_id":"T1619","tn_name":"Cloud Storage Object Discovery"}],"Exfiltration":[{"tn_id":"T1537","tn_name":"Transfer Data to Cloud Account"}],"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}]},"ecc-gcp-005":{"Defense Evasion":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]},{"tn_id":"T1134","tn_name":"Access Token Manipulation","st":[{"st_id":"T1134.001","st_name":"Token Impersonation/Theft"}]}],"Persistence":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]},{"tn_id":"T1098","tn_name":"Account Manipulation","st":[{"st_id":"T1098.001","st_name":"Additional Cloud Credentials"}]}],"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]},{"tn_id":"T1134","tn_name":"Access Token Manipulation","st":[{"st_id":"T1134.001","st_name":"Token Impersonation/Theft"}]}],"Initial Access":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]}]},"ecc-gcp-120":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service"}],"Credential Access":[{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"},{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}]},"ecc-gcp-300":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-gcp-283":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service"}],"Credential Access":[{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"},{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}]},"ecc-gcp-011":{},"ecc-gcp-265":{"Defense Evasion":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]},{"tn_id":"T1578","tn_name":"Modify Cloud Compute Infrastructure","st":[{"st_id":"T1578.001","st_name":"Create Snapshot"},{"st_id":"T1578.002","st_name":"Create Cloud Instance"},{"st_id":"T1578.003","st_name":"Delete Cloud Instance"},{"st_id":"T1578.004","st_name":"Revert Cloud Instance"}]},{"tn_id":"T1562","tn_name":"Impair Defenses","st":[{"st_id":"T1562.001","st_name":"Disable or Modify Tools"},{"st_id":"T1562.007","st_name":"Disable or Modify Cloud Firewall"}]}],"Persistence":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]},{"tn_id":"T1098","tn_name":"Account Manipulation","st":[{"st_id":"T1098.001","st_name":"Additional Cloud Credentials"},{"st_id":"T1098.003","st_name":"Additional Cloud Roles"}]}],"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]}],"Initial Access":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]}],"Discovery":[{"tn_id":"T1619","tn_name":"Cloud Storage Object Discovery"},{"tn_id":"T1580","tn_name":"Cloud Infrastructure Discovery"}],"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-gcp-121":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service"}],"Credential Access":[{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"},{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}]},"ecc-gcp-245":{"Persistence":[{"tn_id":"T1098","tn_name":"Account Manipulation","st":[{"st_id":"T1098.001","st_name":"Additional Cloud Credentials"}]},{"tn_id":"T1136","tn_name":"Create Account","st":[{"st_id":"T1136.003","st_name":"Cloud Account"}]}],"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"}],"Defense Evasion":[{"tn_id":"T1562","tn_name":"Impair Defenses","st":[{"st_id":"T1562.001","st_name":"Disable or Modify Tools"},{"st_id":"T1562.007","st_name":"Disable or Modify Cloud Firewall"}]},{"tn_id":"T1578","tn_name":"Modify Cloud Compute Infrastructure","st":[{"st_id":"T1578.001","st_name":"Create Snapshot"},{"st_id":"T1578.002","st_name":"Create Cloud Instance"},{"st_id":"T1578.003","st_name":"Delete Cloud Instance"},{"st_id":"T1578.004","st_name":"Revert Cloud Instance"}]}],"Discovery":[{"tn_id":"T1580","tn_name":"Cloud Infrastructure Discovery"},{"tn_id":"T1087","tn_name":"Account Discovery","st":[{"st_id":"T1087.004","st_name":"Cloud Account"}]},{"tn_id":"T1619","tn_name":"Cloud Storage Object Discovery"},{"tn_id":"T1069","tn_name":"Permission Groups Discovery","st":[{"st_id":"T1069.003","st_name":"Cloud Groups"}]}],"Exfiltration":[{"tn_id":"T1537","tn_name":"Transfer Data to Cloud Account"}],"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}]},"ecc-gcp-144":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-gcp-009":{"Impact":[{"tn_id":"T1486","tn_name":"Data Encrypted for Impact"}]},"ecc-gcp-016":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-gcp-059":{},"ecc-gcp-337":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service"}],"Credential Access":[{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"},{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}]},"ecc-gcp-111":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service"}],"Credential Access":[{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"},{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}]},"ecc-gcp-128":{"Credential Access":[{"tn_id":"T1552","tn_name":"Unsecured Credentials","st":[{"st_id":"T1552.005","st_name":"Cloud Instance Metadata API"}]}]},"ecc-gcp-263":{"Defense Evasion":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]},{"tn_id":"T1578","tn_name":"Modify Cloud Compute Infrastructure","st":[{"st_id":"T1578.001","st_name":"Create Snapshot"},{"st_id":"T1578.002","st_name":"Create Cloud Instance"},{"st_id":"T1578.003","st_name":"Delete Cloud Instance"},{"st_id":"T1578.004","st_name":"Revert Cloud Instance"}]},{"tn_id":"T1562","tn_name":"Impair Defenses","st":[{"st_id":"T1562.001","st_name":"Disable or Modify Tools"},{"st_id":"T1562.007","st_name":"Disable or Modify Cloud Firewall"}]}],"Persistence":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]},{"tn_id":"T1098","tn_name":"Account Manipulation","st":[{"st_id":"T1098.001","st_name":"Additional Cloud Credentials"},{"st_id":"T1098.003","st_name":"Additional Cloud Roles"}]}],"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]}],"Initial Access":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]}],"Discovery":[{"tn_id":"T1619","tn_name":"Cloud Storage Object Discovery"},{"tn_id":"T1580","tn_name":"Cloud Infrastructure Discovery"}],"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-gcp-031":{"Persistence":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service"}],"Initial Access":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]},{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Defense Evasion":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services","st":[{"st_id":"T1021.001","st_name":"Remote Desktop Protocol"}]}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}]},"ecc-gcp-313":{},"ecc-gcp-163":{},"ecc-gcp-442":{},"ecc-gcp-334":{"Privilege Escalation":[{"tn_id":"T1134","tn_name":"Access Token Manipulation","st":[{"st_id":"T1134.001","st_name":"Token Impersonation/Theft"}]}]},"ecc-gcp-060":{"Defense Evasion":[{"tn_id":"T1612","tn_name":"Build Image on Host"}],"Execution":[{"tn_id":"T1609","tn_name":"Container Administration Command"}],"Privilege Escalation":[{"tn_id":"T1611","tn_name":"Escape to Host"}]},"ecc-gcp-191":{},"ecc-gcp-204":{},"ecc-gcp-316":{},"ecc-gcp-198":{},"ecc-gcp-029":{},"ecc-gcp-028":{},"ecc-gcp-047":{},"ecc-gcp-276":{},"ecc-gcp-444":{},"ecc-gcp-232":{"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}]},"ecc-gcp-318":{},"ecc-gcp-287":{},"ecc-gcp-195":{},"ecc-gcp-183":{},"ecc-gcp-165":{"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Credential Access":[{"tn_id":"T1040","tn_name":"Network Sniffing"}]},"ecc-gcp-037":{"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}]},"ecc-gcp-299":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-gcp-387":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]}]},"ecc-gcp-151":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1489","tn_name":"Service Stop"}]},"ecc-gcp-451":{},"ecc-gcp-201":{},"ecc-gcp-385":{"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"}]},{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]}],"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"},{"tn_id":"T1021","tn_name":"Remote Services","st":[{"st_id":"T1021.001","st_name":"Remote Desktop Protocol"},{"st_id":"T1021.004","st_name":"SSH"}]}],"Persistence":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"},{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Defense Evasion":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}]},"ecc-gcp-142":{},"ecc-gcp-113":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service"}],"Credential Access":[{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"},{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}]},"ecc-gcp-024":{},"ecc-gcp-040":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}],"Discovery":[{"tn_id":"T1619","tn_name":"Cloud Storage Object Discovery"}],"Collection":[{"tn_id":"T1119","tn_name":"Automated Collection"},{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}]},"ecc-gcp-412":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-gcp-107":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-gcp-225":{"Initial Access":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]},{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}],"Persistence":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service"}],"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Defense Evasion":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Discovery":[{"tn_id":"T1619","tn_name":"Cloud Storage Object Discovery"}],"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}],"Exfiltration":[{"tn_id":"T1537","tn_name":"Transfer Data to Cloud Account"}]},"ecc-gcp-217":{"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}]},"ecc-gcp-169":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]},{"tn_id":"T1486","tn_name":"Data Encrypted for Impact"}]},"ecc-gcp-231":{"Impact":[{"tn_id":"T1489","tn_name":"Service Stop"}]},"ecc-gcp-192":{"Execution":[{"tn_id":"T1610","tn_name":"Deploy Container"},{"tn_id":"T1204","tn_name":"User Execution","st":[{"st_id":"T1204.003","st_name":"Malicious Image"}]}],"Defense Evasion":[{"tn_id":"T1610","tn_name":"Deploy Container"}]},"ecc-gcp-280":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service"}],"Credential Access":[{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"},{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}]},"ecc-gcp-003":{"Credential Access":[{"tn_id":"T1552","tn_name":"Unsecured Credentials","st":[{"st_id":"T1552.001","st_name":"Credentials In Files"}]}]},"ecc-gcp-315":{},"ecc-gcp-033":{},"ecc-gcp-452":{},"ecc-gcp-278":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service"}],"Credential Access":[{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"},{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}]},"ecc-gcp-014":{},"ecc-gcp-099":{},"ecc-gcp-254":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}]},"ecc-gcp-079":{"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}]},"ecc-gcp-434":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-gcp-240":{},"ecc-gcp-089":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}]},"ecc-gcp-133":{},"ecc-gcp-090":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}],"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}]},"ecc-gcp-109":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service"}],"Credential Access":[{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"},{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}]},"ecc-gcp-436":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}]},"ecc-gcp-210":{},"ecc-gcp-170":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}],"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}]},"ecc-gcp-400":{"Defense Evasion":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Persistence":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Initial Access":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}]},"ecc-gcp-134":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}]},"ecc-gcp-233":{"Persistence":[{"tn_id":"T1525","tn_name":"Implant Internal Image"}],"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"}],"Execution":[{"tn_id":"T1204","tn_name":"User Execution","st":[{"st_id":"T1204.003","st_name":"Malicious Image"}]}]},"ecc-gcp-279":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service"}],"Credential Access":[{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"},{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}]},"ecc-gcp-114":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service"}],"Credential Access":[{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"},{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}]},"ecc-gcp-116":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service"}],"Credential Access":[{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"},{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}]},"ecc-gcp-305":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}],"Defense Evasion":[{"tn_id":"T1562","tn_name":"Impair Defenses","st":[{"st_id":"T1562.001","st_name":"Disable or Modify Tools"}]}]},"ecc-gcp-286":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-gcp-447":{},"ecc-gcp-180":{},"ecc-gcp-171":{"Discovery":[{"tn_id":"T1619","tn_name":"Cloud Storage Object Discovery"}],"Exfiltration":[{"tn_id":"T1537","tn_name":"Transfer Data to Cloud Account"}],"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}]},"ecc-gcp-062":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-gcp-132":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}],"Credential Access":[{"tn_id":"T1552","tn_name":"Unsecured Credentials","st":[{"st_id":"T1552.004","st_name":"Private Keys"}]}]},"ecc-gcp-110":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service"}],"Credential Access":[{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"},{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}]},"ecc-gcp-223":{"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"}]},{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]}],"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"},{"tn_id":"T1021","tn_name":"Remote Services","st":[{"st_id":"T1021.001","st_name":"Remote Desktop Protocol"},{"st_id":"T1021.004","st_name":"SSH"}]}],"Persistence":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"},{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Defense Evasion":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}]},"ecc-gcp-189":{"Impact":[{"tn_id":"T1489","tn_name":"Service Stop"}]},"ecc-gcp-190":{},"ecc-gcp-115":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service"}],"Credential Access":[{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"},{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}]},"ecc-gcp-176":{},"ecc-gcp-261":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-gcp-053":{"Impact":[{"tn_id":"T1489","tn_name":"Service Stop"},{"tn_id":"T1529","tn_name":"System Shutdown/Reboot"}]},"ecc-gcp-303":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-gcp-236":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"}],"Execution":[{"tn_id":"T1203","tn_name":"Exploitation for Client Execution"}],"Privilege Escalation":[{"tn_id":"T1068","tn_name":"Exploitation for Privilege Escalation"}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-gcp-247":{},"ecc-gcp-086":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}]},"ecc-gcp-222":{"Persistence":[{"tn_id":"T1098","tn_name":"Account Manipulation","st":[{"st_id":"T1098.001","st_name":"Additional Cloud Credentials"}]},{"tn_id":"T1136","tn_name":"Create Account","st":[{"st_id":"T1136.003","st_name":"Cloud Account"}]}],"Execution":[{"tn_id":"T1648","tn_name":"Serverless Execution"}],"Defense Evasion":[{"tn_id":"T1562","tn_name":"Impair Defenses","st":[{"st_id":"T1562.001","st_name":"Disable or Modify Tools"},{"st_id":"T1562.007","st_name":"Disable or Modify Cloud Firewall"}]},{"tn_id":"T1578","tn_name":"Modify Cloud Compute Infrastructure","st":[{"st_id":"T1578.001","st_name":"Create Snapshot"},{"st_id":"T1578.002","st_name":"Create Cloud Instance"},{"st_id":"T1578.003","st_name":"Delete Cloud Instance"},{"st_id":"T1578.004","st_name":"Revert Cloud Instance"}]}],"Discovery":[{"tn_id":"T1580","tn_name":"Cloud Infrastructure Discovery"},{"tn_id":"T1087","tn_name":"Account Discovery","st":[{"st_id":"T1087.004","st_name":"Cloud Account"}]},{"tn_id":"T1619","tn_name":"Cloud Storage Object Discovery"},{"tn_id":"T1069","tn_name":"Permission Groups Discovery","st":[{"st_id":"T1069.003","st_name":"Cloud Groups"}]}],"Exfiltration":[{"tn_id":"T1537","tn_name":"Transfer Data to Cloud Account"}],"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}]},"ecc-gcp-197":{"Execution":[{"tn_id":"T1203","tn_name":"Exploitation for Client Execution"}]},"ecc-gcp-048":{},"ecc-gcp-274":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service"}],"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}]},"ecc-gcp-150":{"Discovery":[{"tn_id":"T1619","tn_name":"Cloud Storage Object Discovery"}],"Collection":[{"tn_id":"T1119","tn_name":"Automated Collection"},{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}]},"ecc-gcp-243":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service"}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force"}]},"ecc-gcp-282":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service"}],"Credential Access":[{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"},{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}]},"ecc-gcp-140":{},"ecc-gcp-055":{"Privilege Escalation":[{"tn_id":"T1068","tn_name":"Exploitation for Privilege Escalation"}]},"ecc-gcp-239":{"Impact":[{"tn_id":"T1489","tn_name":"Service Stop"}]},"ecc-gcp-285":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service"}],"Credential Access":[{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"},{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}]},"ecc-gcp-312":{},"ecc-gcp-249":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}],"Credential Access":[{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}]},"ecc-gcp-219":{"Persistence":[{"tn_id":"T1525","tn_name":"Implant Internal Image"}],"Execution":[{"tn_id":"T1204","tn_name":"User Execution","st":[{"st_id":"T1204.003","st_name":"Malicious Image"}]}]},"ecc-gcp-018":{},"ecc-gcp-188":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}],"Defense Evasion":[{"tn_id":"T1562","tn_name":"Impair Defenses","st":[{"st_id":"T1562.001","st_name":"Disable or Modify Tools"}]}],"Collection":[{"tn_id":"T1119","tn_name":"Automated Collection"}]},"ecc-gcp-179":{},"ecc-gcp-042":{},"ecc-gcp-182":{},"ecc-gcp-071":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service"}],"Credential Access":[{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"},{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}]},"ecc-gcp-137":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Exfiltration":[{"tn_id":"T1020","tn_name":"Automated Exfiltration","st":[{"st_id":"T1020.001","st_name":"Traffic Duplication"}]}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}]},"ecc-gcp-298":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-gcp-112":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service"}],"Credential Access":[{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"},{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}]},"ecc-gcp-317":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-gcp-242":{"Defense Evasion":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]},{"tn_id":"T1578","tn_name":"Modify Cloud Compute Infrastructure","st":[{"st_id":"T1578.001","st_name":"Create Snapshot"},{"st_id":"T1578.002","st_name":"Create Cloud Instance"},{"st_id":"T1578.003","st_name":"Delete Cloud Instance"},{"st_id":"T1578.004","st_name":"Revert Cloud Instance"}]},{"tn_id":"T1562","tn_name":"Impair Defenses","st":[{"st_id":"T1562.001","st_name":"Disable or Modify Tools"},{"st_id":"T1562.007","st_name":"Disable or Modify Cloud Firewall"}]}],"Persistence":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]},{"tn_id":"T1098","tn_name":"Account Manipulation","st":[{"st_id":"T1098.001","st_name":"Additional Cloud Credentials"},{"st_id":"T1098.003","st_name":"Additional Cloud Roles"}]}],"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]}],"Initial Access":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]}],"Discovery":[{"tn_id":"T1619","tn_name":"Cloud Storage Object Discovery"},{"tn_id":"T1580","tn_name":"Cloud Infrastructure Discovery"}],"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-gcp-034":{"Persistence":[{"tn_id":"T1098","tn_name":"Account Manipulation","st":[{"st_id":"T1098.001","st_name":"Additional Cloud Credentials"}]},{"tn_id":"T1136","tn_name":"Create Account","st":[{"st_id":"T1136.003","st_name":"Cloud Account"}]}],"Execution":[{"tn_id":"T1648","tn_name":"Serverless Execution"}],"Defense Evasion":[{"tn_id":"T1562","tn_name":"Impair Defenses","st":[{"st_id":"T1562.001","st_name":"Disable or Modify Tools"},{"st_id":"T1562.007","st_name":"Disable or Modify Cloud Firewall"}]},{"tn_id":"T1578","tn_name":"Modify Cloud Compute Infrastructure","st":[{"st_id":"T1578.001","st_name":"Create Snapshot"},{"st_id":"T1578.002","st_name":"Create Cloud Instance"},{"st_id":"T1578.003","st_name":"Delete Cloud Instance"},{"st_id":"T1578.004","st_name":"Revert Cloud Instance"}]}],"Discovery":[{"tn_id":"T1580","tn_name":"Cloud Infrastructure Discovery"},{"tn_id":"T1087","tn_name":"Account Discovery","st":[{"st_id":"T1087.004","st_name":"Cloud Account"}]},{"tn_id":"T1619","tn_name":"Cloud Storage Object Discovery"},{"tn_id":"T1069","tn_name":"Permission Groups Discovery","st":[{"st_id":"T1069.003","st_name":"Cloud Groups"}]}],"Exfiltration":[{"tn_id":"T1537","tn_name":"Transfer Data to Cloud Account"}],"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}]},"ecc-gcp-216":{"Command and Control":[{"tn_id":"T1090","tn_name":"Proxy","st":[{"st_id":"T1090.001","st_name":"Internal Proxy"}]}]},"ecc-gcp-453":{},"ecc-gcp-211":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-gcp-135":{"Impact":[{"tn_id":"T1529","tn_name":"System Shutdown/Reboot"},{"tn_id":"T1489","tn_name":"Service Stop"}]},"ecc-aws-235":{},"ecc-aws-511":{"Reconnaissance":[{"tn_id":"T1595","tn_name":"Active Scanning"}],"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"},{"tn_id":"T1133","tn_name":"External Remote Services"}],"Persistence":[{"tn_id":"T1133","tn_name":"External Remote Services"}],"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.001","st_name":"OS Exhaustion Flood"},{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.003","st_name":"Application Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]}]},"ecc-aws-190":{"Execution":[{"tn_id":"T1609","tn_name":"Container Administration Command"}],"Privilege Escalation":[{"tn_id":"T1068","tn_name":"Exploitation for Privilege Escalation"},{"tn_id":"T1611","tn_name":"Escape to Host"}],"Discovery":[{"tn_id":"T1580","tn_name":"Cloud Infrastructure Discovery"}],"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}],"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-091":{},"ecc-aws-295":{"Defense Evasion":[{"tn_id":"T1600","tn_name":"Weaken Encryption"}],"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Exfiltration":[{"tn_id":"T1020","tn_name":"Automated Exfiltration","st":[{"st_id":"T1020.001","st_name":"Traffic Duplication"}]}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}]},"ecc-aws-286":{},"ecc-aws-529":{},"ecc-aws-493":{},"ecc-aws-483":{"Defense Evasion":[{"tn_id":"T1070","tn_name":"Indicator Removal","st":[{"st_id":"T1070.004","st_name":"File Deletion"}]}],"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}]},"ecc-aws-383":{},"ecc-aws-118":{},"ecc-aws-435":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-aws-144":{},"ecc-aws-414":{},"ecc-aws-156":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Exfiltration":[{"tn_id":"T1020","tn_name":"Automated Exfiltration","st":[{"st_id":"T1020.001","st_name":"Traffic Duplication"}]}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}],"Defense Evasion":[{"tn_id":"T1600","tn_name":"Weaken Encryption"}]},"ecc-aws-363":{"Collection":[{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-229":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}],"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}]},"ecc-aws-296":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"}],"Execution":[{"tn_id":"T1203","tn_name":"Exploitation for Client Execution"}],"Privilege Escalation":[{"tn_id":"T1068","tn_name":"Exploitation for Privilege Escalation"}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]}]},"ecc-aws-313":{"Collection":[{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-378":{},"ecc-aws-302":{},"ecc-aws-342":{"Resource Development":[{"tn_id":"T1583","tn_name":"Acquire Infrastructure","st":[{"st_id":"T1583.001","st_name":"Domains"}]}],"Initial Access":[{"tn_id":"T1189","tn_name":"Drive-by Compromise"},{"tn_id":"T1566","tn_name":"Phishing","st":[{"st_id":"T1566.002","st_name":"Spearphishing Link"}]}]},"ecc-aws-187":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}]},"ecc-aws-245":{},"ecc-aws-101":{"Initial Access":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]},{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1555","tn_name":"Credentials from Password Stores"},{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]},{"tn_id":"T1187","tn_name":"Forced Authentication"}],"Persistence":[{"tn_id":"T1098","tn_name":"Account Manipulation"},{"tn_id":"T1136","tn_name":"Create Account"},{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service"}],"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}]},"ecc-aws-189":{},"ecc-aws-465":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1486","tn_name":"Data Encrypted for Impact"},{"tn_id":"T1490","tn_name":"Inhibit System Recovery"},{"tn_id":"T1561","tn_name":"Disk Wipe","st":[{"st_id":"T1561.001","st_name":"Disk Content Wipe"},{"st_id":"T1561.002","st_name":"Disk Structure Wipe"}]},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]},{"tn_id":"T1491","tn_name":"Defacement","st":[{"st_id":"T1491.001","st_name":"Internal Defacement"},{"st_id":"T1491.002","st_name":"External Defacement"}]}]},"ecc-aws-572":{},"ecc-aws-506":{"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}],"Defense Evasion":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Initial Access":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Persistence":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}]},"ecc-aws-214":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Exfiltration":[{"tn_id":"T1020","tn_name":"Automated Exfiltration","st":[{"st_id":"T1020.001","st_name":"Traffic Duplication"}]}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}]},"ecc-aws-153":{},"ecc-aws-083":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-122":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}],"Collection":[{"tn_id":"T1213","tn_name":"Data from Information Repositories"}]},"ecc-aws-265":{},"ecc-aws-147":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}],"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}]},"ecc-aws-129":{},"ecc-aws-401":{},"ecc-aws-062":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services","st":[{"st_id":"T1021.004","st_name":"SSH"}]},{"tn_id":"T1563","tn_name":"Remote Service Session Hijacking","st":[{"st_id":"T1563.001","st_name":"SSH Hijacking"}]}],"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-250":{},"ecc-aws-188":{},"ecc-aws-004":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Exfiltration":[{"tn_id":"T1020","tn_name":"Automated Exfiltration","st":[{"st_id":"T1020.001","st_name":"Traffic Duplication"}]}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}]},"ecc-aws-332":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"},{"tn_id":"T1133","tn_name":"External Remote Services"}],"Persistence":[{"tn_id":"T1133","tn_name":"External Remote Services"}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]},{"tn_id":"T1606","tn_name":"Forge Web Credentials","st":[{"st_id":"T1606.002","st_name":"SAML Tokens"}]}],"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"}],"Collection":[{"tn_id":"T1185","tn_name":"Browser Session Hijacking"}]},"ecc-aws-040":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Privilege Escalation":[{"tn_id":"T1068","tn_name":"Exploitation for Privilege Escalation"}],"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"}],"Execution":[{"tn_id":"T1203","tn_name":"Exploitation for Client Execution"}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]},{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]}]},"ecc-aws-347":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-aws-269":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Collection":[{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]}]},"ecc-aws-280":{"Collection":[{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-534":{},"ecc-aws-531":{"Collection":[{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]},{"tn_id":"T1491","tn_name":"Defacement","st":[{"st_id":"T1491.001","st_name":"Internal Defacement"},{"st_id":"T1491.002","st_name":"External Defacement"}]}]},"ecc-aws-471":{"Impact":[{"tn_id":"T1529","tn_name":"System Shutdown/Reboot"}]},"ecc-aws-498":{"Impact":[{"tn_id":"T1489","tn_name":"Service Stop"}]},"ecc-aws-447":{},"ecc-aws-395":{},"ecc-aws-548":{},"ecc-aws-277":{},"ecc-aws-204":{},"ecc-aws-539":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Discovery":[{"tn_id":"T1619","tn_name":"Cloud Storage Object Discovery"}],"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}]},"ecc-aws-501":{"Collection":[{"tn_id":"T1213","tn_name":"Data from Information Repositories"}]},"ecc-aws-573":{},"ecc-aws-186":{"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}],"Initial Access":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]},{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1555","tn_name":"Credentials from Password Stores"},{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]},{"tn_id":"T1187","tn_name":"Forced Authentication"}],"Persistence":[{"tn_id":"T1098","tn_name":"Account Manipulation"},{"tn_id":"T1136","tn_name":"Create Account"},{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service"}],"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}]},"ecc-aws-485":{},"ecc-aws-263":{},"ecc-aws-552":{},"ecc-aws-032":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-337":{"Credential Access":[{"tn_id":"T1552","tn_name":"Unsecured Credentials"}]},"ecc-aws-439":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-050":{"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"}]}]},"ecc-aws-406":{},"ecc-aws-230":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}]},"ecc-aws-171":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-267":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Exfiltration":[{"tn_id":"T1020","tn_name":"Automated Exfiltration","st":[{"st_id":"T1020.001","st_name":"Traffic Duplication"}]}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}]},"ecc-aws-031":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]},{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-169":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-451":{},"ecc-aws-208":{},"ecc-aws-038":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-371":{"Reconnaissance":[{"tn_id":"T1595","tn_name":"Active Scanning","st":[{"st_id":"T1595.002","st_name":"Vulnerability Scanning"}]}],"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}],"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.001","st_name":"OS Exhaustion Flood"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]}]},"ecc-aws-049":{"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"}]}]},"ecc-aws-530":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Exfiltration":[{"tn_id":"T1020","tn_name":"Automated Exfiltration","st":[{"st_id":"T1020.001","st_name":"Traffic Duplication"}]}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}]},"ecc-aws-407":{},"ecc-aws-340":{},"ecc-aws-310":{},"ecc-aws-257":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1555","tn_name":"Credentials from Password Stores"},{"tn_id":"T1187","tn_name":"Forced Authentication"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Exfiltration":[{"tn_id":"T1020","tn_name":"Automated Exfiltration","st":[{"st_id":"T1020.001","st_name":"Traffic Duplication"}]}],"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"},{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-aws-180":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Impact":[{"tn_id":"T1489","tn_name":"Service Stop"},{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]}]},"ecc-aws-196":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Impact":[{"tn_id":"T1489","tn_name":"Service Stop"},{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]}]},"ecc-aws-251":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}],"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}]},"ecc-aws-312":{},"ecc-aws-386":{},"ecc-aws-492":{},"ecc-aws-124":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}],"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}]},"ecc-aws-537":{"Execution":[{"tn_id":"T1053","tn_name":"Scheduled Task/Job","st":[{"st_id":"T1053.002","st_name":"At"},{"st_id":"T1053.003","st_name":"Cron"},{"st_id":"T1053.006","st_name":"Systemd Timers"},{"st_id":"T1053.007","st_name":"Container Orchestration Job"}]},{"tn_id":"T1610","tn_name":"Deploy Container"}],"Persistence":[{"tn_id":"T1053","tn_name":"Scheduled Task/Job","st":[{"st_id":"T1053.002","st_name":"At"},{"st_id":"T1053.003","st_name":"Cron"},{"st_id":"T1053.006","st_name":"Systemd Timers"},{"st_id":"T1053.007","st_name":"Container Orchestration Job"}]}],"Privilege Escalation":[{"tn_id":"T1068","tn_name":"Exploitation for Privilege Escalation"},{"tn_id":"T1611","tn_name":"Escape to Host"},{"tn_id":"T1053","tn_name":"Scheduled Task/Job","st":[{"st_id":"T1053.002","st_name":"At"},{"st_id":"T1053.003","st_name":"Cron"},{"st_id":"T1053.006","st_name":"Systemd Timers"},{"st_id":"T1053.007","st_name":"Container Orchestration Job"}]}],"Defense Evasion":[{"tn_id":"T1612","tn_name":"Build Image on Host"},{"tn_id":"T1610","tn_name":"Deploy Container"}]},"ecc-aws-333":{"Collection":[{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-096":{},"ecc-aws-170":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-481":{"Execution":[{"tn_id":"T1053","tn_name":"Scheduled Task/Job","st":[{"st_id":"T1053.002","st_name":"At"},{"st_id":"T1053.003","st_name":"Cron"},{"st_id":"T1053.006","st_name":"Systemd Timers"},{"st_id":"T1053.007","st_name":"Container Orchestration Job"}]},{"tn_id":"T1610","tn_name":"Deploy Container"}],"Persistence":[{"tn_id":"T1053","tn_name":"Scheduled Task/Job","st":[{"st_id":"T1053.002","st_name":"At"},{"st_id":"T1053.003","st_name":"Cron"},{"st_id":"T1053.006","st_name":"Systemd Timers"},{"st_id":"T1053.007","st_name":"Container Orchestration Job"}]}],"Privilege Escalation":[{"tn_id":"T1068","tn_name":"Exploitation for Privilege Escalation"},{"tn_id":"T1611","tn_name":"Escape to Host"},{"tn_id":"T1053","tn_name":"Scheduled Task/Job","st":[{"st_id":"T1053.002","st_name":"At"},{"st_id":"T1053.003","st_name":"Cron"},{"st_id":"T1053.006","st_name":"Systemd Timers"},{"st_id":"T1053.007","st_name":"Container Orchestration Job"}]}],"Defense Evasion":[{"tn_id":"T1612","tn_name":"Build Image on Host"},{"tn_id":"T1610","tn_name":"Deploy Container"}]},"ecc-aws-428":{},"ecc-aws-036":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-314":{},"ecc-aws-017":{},"ecc-aws-008":{},"ecc-aws-521":{"Impact":[{"tn_id":"T1491","tn_name":"Defacement","st":[{"st_id":"T1491.001","st_name":"Internal Defacement"},{"st_id":"T1491.002","st_name":"External Defacement"}]},{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1486","tn_name":"Data Encrypted for Impact"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-aws-335":{},"ecc-aws-014":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Exfiltration":[{"tn_id":"T1020","tn_name":"Automated Exfiltration","st":[{"st_id":"T1020.001","st_name":"Traffic Duplication"}]}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}]},"ecc-aws-139":{},"ecc-aws-090":{"Defense Evasion":[{"tn_id":"T1578","tn_name":"Modify Cloud Compute Infrastructure","st":[{"st_id":"T1578.004","st_name":"Revert Cloud Instance"}]}],"Collection":[{"tn_id":"T1213","tn_name":"Data from Information Repositories"}]},"ecc-aws-355":{"Collection":[{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-323":{},"ecc-aws-432":{},"ecc-aws-490":{"Credential Access":[{"tn_id":"T1552","tn_name":"Unsecured Credentials","st":[{"st_id":"T1552.005","st_name":"Cloud Instance Metadata API"}]}]},"ecc-aws-143":{},"ecc-aws-437":{"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}],"Impact":[{"tn_id":"T1486","tn_name":"Data Encrypted for Impact"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1491","tn_name":"Defacement","st":[{"st_id":"T1491.001","st_name":"Internal Defacement"},{"st_id":"T1491.002","st_name":"External Defacement"}]}]},"ecc-aws-455":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-227":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}],"Credential Access":[{"tn_id":"T1552","tn_name":"Unsecured Credentials","st":[{"st_id":"T1552.001","st_name":"Credentials In Files"}]}]},"ecc-aws-443":{"Reconnaissance":[{"tn_id":"T1595","tn_name":"Active Scanning","st":[{"st_id":"T1595.002","st_name":"Vulnerability Scanning"}]}],"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"},{"tn_id":"T1189","tn_name":"Drive-by Compromise"}],"Execution":[{"tn_id":"T1203","tn_name":"Exploitation for Client Execution"}],"Command and Control":[{"tn_id":"T1071","tn_name":"Application Layer Protocol","st":[{"st_id":"T1071.001","st_name":"Web Protocols"}]}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-aws-356":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Exfiltration":[{"tn_id":"T1020","tn_name":"Automated Exfiltration","st":[{"st_id":"T1020.001","st_name":"Traffic Duplication"}]}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}]},"ecc-aws-219":{"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]},{"tn_id":"T1555","tn_name":"Credentials from Password Stores"}]},"ecc-aws-311":{"Collection":[{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-053":{},"ecc-aws-290":{"Collection":[{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-106":{},"ecc-aws-125":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}],"Collection":[{"tn_id":"T1213","tn_name":"Data from Information Repositories"}]},"ecc-aws-162":{},"ecc-aws-058":{},"ecc-aws-317":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Exfiltration":[{"tn_id":"T1020","tn_name":"Automated Exfiltration","st":[{"st_id":"T1020.001","st_name":"Traffic Duplication"}]}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}]},"ecc-aws-459":{"Persistence":[{"tn_id":"T1554","tn_name":"Compromise Client Software Binary"}],"Defense Evasion":[{"tn_id":"T1036","tn_name":"Masquerading","st":[{"st_id":"T1036.001","st_name":"Invalid Code Signature"}]}]},"ecc-aws-427":{},"ecc-aws-119":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}],"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}]},"ecc-aws-325":{"Impact":[{"tn_id":"T1489","tn_name":"Service Stop"},{"tn_id":"T1529","tn_name":"System Shutdown/Reboot"}]},"ecc-aws-514":{},"ecc-aws-266":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1486","tn_name":"Data Encrypted for Impact"},{"tn_id":"T1490","tn_name":"Inhibit System Recovery"},{"tn_id":"T1561","tn_name":"Disk Wipe","st":[{"st_id":"T1561.001","st_name":"Disk Content Wipe"},{"st_id":"T1561.002","st_name":"Disk Structure Wipe"}]},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]},{"tn_id":"T1491","tn_name":"Defacement","st":[{"st_id":"T1491.001","st_name":"Internal Defacement"},{"st_id":"T1491.002","st_name":"External Defacement"}]}]},"ecc-aws-474":{"Impact":[{"tn_id":"T1529","tn_name":"System Shutdown/Reboot"}]},"ecc-aws-182":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service"}]},"ecc-aws-175":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}],"Collection":[{"tn_id":"T1213","tn_name":"Data from Information Repositories"}]},"ecc-aws-061":{},"ecc-aws-456":{"Credential Access":[{"tn_id":"T1552","tn_name":"Unsecured Credentials","st":[{"st_id":"T1552.005","st_name":"Cloud Instance Metadata API"}]}]},"ecc-aws-489":{"Impact":[{"tn_id":"T1496","tn_name":"Resource Hijacking"}]},"ecc-aws-042":{"Credential Access":[{"tn_id":"T1552","tn_name":"Unsecured Credentials","st":[{"st_id":"T1552.001","st_name":"Credentials In Files"}]}],"Discovery":[{"tn_id":"T1619","tn_name":"Cloud Storage Object Discovery"}],"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-aws-418":{},"ecc-aws-502":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"}],"Execution":[{"tn_id":"T1203","tn_name":"Exploitation for Client Execution"}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]}]},"ecc-aws-560":{},"ecc-aws-543":{},"ecc-aws-519":{},"ecc-aws-288":{"Impact":[{"tn_id":"T1496","tn_name":"Resource Hijacking"}]},"ecc-aws-350":{},"ecc-aws-066":{"Initial Access":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]},{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Persistence":[{"tn_id":"T1098","tn_name":"Account Manipulation"},{"tn_id":"T1136","tn_name":"Create Account"},{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service"}],"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}]},"ecc-aws-370":{"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"}],"Execution":[{"tn_id":"T1203","tn_name":"Exploitation for Client Execution"}]},"ecc-aws-136":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-298":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-aws-361":{},"ecc-aws-412":{},"ecc-aws-025":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-300":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-aws-322":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-499":{},"ecc-aws-431":{},"ecc-aws-120":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}],"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}]},"ecc-aws-225":{},"ecc-aws-151":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-109":{"Impact":[{"tn_id":"T1489","tn_name":"Service Stop"}]},"ecc-aws-415":{},"ecc-aws-166":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]}]},"ecc-aws-012":{},"ecc-aws-134":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-052":{},"ecc-aws-221":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}],"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}]},"ecc-aws-400":{},"ecc-aws-087":{"Initial Access":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]},{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1555","tn_name":"Credentials from Password Stores"},{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]},{"tn_id":"T1187","tn_name":"Forced Authentication"}],"Persistence":[{"tn_id":"T1098","tn_name":"Account Manipulation"},{"tn_id":"T1136","tn_name":"Create Account"},{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Collection":[{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service"}],"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}]},"ecc-aws-233":{},"ecc-aws-441":{"Collection":[{"tn_id":"T1213","tn_name":"Data from Information Repositories"}]},"ecc-aws-419":{},"ecc-aws-104":{},"ecc-aws-013":{},"ecc-aws-547":{},"ecc-aws-308":{"Credential Access":[{"tn_id":"T1552","tn_name":"Unsecured Credentials","st":[{"st_id":"T1552.001","st_name":"Credentials In Files"}]}],"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}],"Exfiltration":[{"tn_id":"T1537","tn_name":"Transfer Data to Cloud Account"}],"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]},{"tn_id":"T1491","tn_name":"Defacement","st":[{"st_id":"T1491.001","st_name":"Internal Defacement"},{"st_id":"T1491.002","st_name":"External Defacement"}]}]},"ecc-aws-234":{},"ecc-aws-100":{},"ecc-aws-238":{},"ecc-aws-044":{},"ecc-aws-377":{},"ecc-aws-382":{},"ecc-aws-448":{},"ecc-aws-003":{},"ecc-aws-423":{},"ecc-aws-272":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"}],"Execution":[{"tn_id":"T1203","tn_name":"Exploitation for Client Execution"}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]},{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]}]},"ecc-aws-244":{},"ecc-aws-168":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-174":{},"ecc-aws-149":{"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}],"Initial Access":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]},{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1555","tn_name":"Credentials from Password Stores"},{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]},{"tn_id":"T1187","tn_name":"Forced Authentication"}],"Persistence":[{"tn_id":"T1098","tn_name":"Account Manipulation"},{"tn_id":"T1136","tn_name":"Create Account"},{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service"}],"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}]},"ecc-aws-152":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service"}]},"ecc-aws-115":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Exfiltration":[{"tn_id":"T1020","tn_name":"Automated Exfiltration","st":[{"st_id":"T1020.001","st_name":"Traffic Duplication"}]}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}]},"ecc-aws-364":{"Reconnaissance":[{"tn_id":"T1595","tn_name":"Active Scanning","st":[{"st_id":"T1595.002","st_name":"Vulnerability Scanning"}]}],"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"},{"tn_id":"T1133","tn_name":"External Remote Services"}],"Persistence":[{"tn_id":"T1133","tn_name":"External Remote Services"}],"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Discovery":[{"tn_id":"T1046","tn_name":"Network Service Discovery"}],"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"}],"Impact":[{"tn_id":"T1496","tn_name":"Resource Hijacking"},{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.001","st_name":"OS Exhaustion Flood"},{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]}]},"ecc-aws-137":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-253":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}],"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}]},"ecc-aws-246":{"Initial Access":[{"tn_id":"T1133","tn_name":"External Remote Services"},{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Persistence":[{"tn_id":"T1133","tn_name":"External Remote Services"}],"Lateral Movement":[{"tn_id":"T1563","tn_name":"Remote Service Session Hijacking"}]},"ecc-aws-094":{},"ecc-aws-201":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-260":{},"ecc-aws-462":{},"ecc-aws-045":{"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"}]}]},"ecc-aws-316":{},"ecc-aws-133":{},"ecc-aws-145":{},"ecc-aws-262":{"Defense Evasion":[{"tn_id":"T1578","tn_name":"Modify Cloud Compute Infrastructure"}]},"ecc-aws-158":{},"ecc-aws-092":{"Defense Evasion":[{"tn_id":"T1578","tn_name":"Modify Cloud Compute Infrastructure","st":[{"st_id":"T1578.004","st_name":"Revert Cloud Instance"}]}],"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}]},"ecc-aws-403":{},"ecc-aws-528":{},"ecc-aws-222":{},"ecc-aws-446":{},"ecc-aws-209":{},"ecc-aws-223":{},"ecc-aws-055":{},"ecc-aws-015":{"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]},{"tn_id":"T1555","tn_name":"Credentials from Password Stores"},{"tn_id":"T1187","tn_name":"Forced Authentication"}],"Persistence":[{"tn_id":"T1098","tn_name":"Account Manipulation"},{"tn_id":"T1136","tn_name":"Create Account"}]},"ecc-aws-191":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1486","tn_name":"Data Encrypted for Impact"},{"tn_id":"T1490","tn_name":"Inhibit System Recovery"},{"tn_id":"T1561","tn_name":"Disk Wipe","st":[{"st_id":"T1561.001","st_name":"Disk Content Wipe"},{"st_id":"T1561.002","st_name":"Disk Structure Wipe"}]},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]},{"tn_id":"T1491","tn_name":"Defacement","st":[{"st_id":"T1491.001","st_name":"Internal Defacement"},{"st_id":"T1491.002","st_name":"External Defacement"}]}]},"ecc-aws-059":{},"ecc-aws-289":{"Impact":[{"tn_id":"T1489","tn_name":"Service Stop"}]},"ecc-aws-409":{},"ecc-aws-268":{"Collection":[{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-334":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-105":{"Credential Access":[{"tn_id":"T1552","tn_name":"Unsecured Credentials"}]},"ecc-aws-461":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Execution":[{"tn_id":"T1203","tn_name":"Exploitation for Client Execution"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]}]},"ecc-aws-157":{},"ecc-aws-540":{"Execution":[{"tn_id":"T1203","tn_name":"Exploitation for Client Execution"}]},"ecc-aws-020":{},"ecc-aws-028":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]},{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-546":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1486","tn_name":"Data Encrypted for Impact"},{"tn_id":"T1490","tn_name":"Inhibit System Recovery"},{"tn_id":"T1561","tn_name":"Disk Wipe","st":[{"st_id":"T1561.001","st_name":"Disk Content Wipe"},{"st_id":"T1561.002","st_name":"Disk Structure Wipe"}]},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]},{"tn_id":"T1491","tn_name":"Defacement","st":[{"st_id":"T1491.001","st_name":"Internal Defacement"},{"st_id":"T1491.002","st_name":"External Defacement"}]}]},"ecc-aws-287":{"Impact":[{"tn_id":"T1489","tn_name":"Service Stop"},{"tn_id":"T1529","tn_name":"System Shutdown/Reboot"}]},"ecc-aws-056":{},"ecc-aws-469":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Execution":[{"tn_id":"T1203","tn_name":"Exploitation for Client Execution"}],"Defense Evasion":[{"tn_id":"T1027","tn_name":"Obfuscated Files or Information","st":[{"st_id":"T1027.006","st_name":"HTML Smuggling"}]}],"Command and Control":[{"tn_id":"T1071","tn_name":"Application Layer Protocol","st":[{"st_id":"T1071.001","st_name":"Web Protocols"}]}]},"ecc-aws-079":{},"ecc-aws-281":{},"ecc-aws-460":{"Credential Access":[{"tn_id":"T1552","tn_name":"Unsecured Credentials"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Exfiltration":[{"tn_id":"T1020","tn_name":"Automated Exfiltration","st":[{"st_id":"T1020.001","st_name":"Traffic Duplication"}]}]},"ecc-aws-477":{},"ecc-aws-388":{},"ecc-aws-070":{},"ecc-aws-057":{},"ecc-aws-453":{},"ecc-aws-307":{},"ecc-aws-473":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Execution":[{"tn_id":"T1203","tn_name":"Exploitation for Client Execution"}],"Defense Evasion":[{"tn_id":"T1027","tn_name":"Obfuscated Files or Information","st":[{"st_id":"T1027.006","st_name":"HTML Smuggling"}]}],"Command and Control":[{"tn_id":"T1071","tn_name":"Application Layer Protocol","st":[{"st_id":"T1071.001","st_name":"Web Protocols"}]}]},"ecc-aws-113":{},"ecc-aws-048":{"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"}]}]},"ecc-aws-408":{},"ecc-aws-495":{},"ecc-aws-420":{},"ecc-aws-479":{},"ecc-aws-035":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-577":{},"ecc-aws-258":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}],"Exfiltration":[{"tn_id":"T1020","tn_name":"Automated Exfiltration","st":[{"st_id":"T1020.001","st_name":"Traffic Duplication"}]}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"},{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}]},"ecc-aws-138":{},"ecc-aws-454":{},"ecc-aws-095":{},"ecc-aws-167":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]}]},"ecc-aws-429":{},"ecc-aws-413":{},"ecc-aws-436":{"Impact":[{"tn_id":"T1496","tn_name":"Resource Hijacking"}]},"ecc-aws-108":{},"ecc-aws-007":{},"ecc-aws-074":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service"}]},"ecc-aws-224":{"Initial Access":[{"tn_id":"T1189","tn_name":"Drive-by Compromise"},{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1212","tn_name":"Exploitation for Credential Access"},{"tn_id":"T1552","tn_name":"Unsecured Credentials","st":[{"st_id":"T1552.005","st_name":"Cloud Instance Metadata API"}]}],"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"}]},"ecc-aws-226":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-193":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"},{"tn_id":"T1195","tn_name":"Supply Chain Compromise"}],"Persistence":[{"tn_id":"T1574","tn_name":"Hijack Execution Flow"}],"Privilege Escalation":[{"tn_id":"T1574","tn_name":"Hijack Execution Flow"}],"Defense Evasion":[{"tn_id":"T1574","tn_name":"Hijack Execution Flow"}]},"ecc-aws-199":{},"ecc-aws-328":{"Credential Access":[{"tn_id":"T1552","tn_name":"Unsecured Credentials","st":[{"st_id":"T1552.001","st_name":"Credentials In Files"},{"st_id":"T1552.002","st_name":"Credentials in Registry"},{"st_id":"T1552.004","st_name":"Private Keys"}]}],"Collection":[{"tn_id":"T1213","tn_name":"Data from Information Repositories"}]},"ecc-aws-054":{"Execution":[{"tn_id":"T1648","tn_name":"Serverless Execution"}],"Persistence":[{"tn_id":"T1098","tn_name":"Account Manipulation","st":[{"st_id":"T1098.001","st_name":"Additional Cloud Credentials"},{"st_id":"T1098.003","st_name":"Additional Cloud Roles"}]},{"tn_id":"T1136","tn_name":"Create Account","st":[{"st_id":"T1136.003","st_name":"Cloud Account"}]}],"Defense Evasion":[{"tn_id":"T1578","tn_name":"Modify Cloud Compute Infrastructure","st":[{"st_id":"T1578.001","st_name":"Create Snapshot"},{"st_id":"T1578.002","st_name":"Create Cloud Instance"},{"st_id":"T1578.003","st_name":"Delete Cloud Instance"},{"st_id":"T1578.004","st_name":"Revert Cloud Instance"}]},{"tn_id":"T1535","tn_name":"Unused/Unsupported Cloud Regions"}],"Discovery":[{"tn_id":"T1201","tn_name":"Password Policy Discovery"},{"tn_id":"T1518","tn_name":"Software Discovery","st":[{"st_id":"T1518.001","st_name":"Security Software Discovery"}]},{"tn_id":"T1580","tn_name":"Cloud Infrastructure Discovery"},{"tn_id":"T1619","tn_name":"Cloud Storage Object Discovery"}],"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1486","tn_name":"Data Encrypted for Impact"},{"tn_id":"T1531","tn_name":"Account Access Removal"}]},"ecc-aws-476":{},"ecc-aws-445":{},"ecc-aws-135":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-405":{},"ecc-aws-107":{},"ecc-aws-067":{},"ecc-aws-254":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}],"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}]},"ecc-aws-273":{},"ecc-aws-264":{},"ecc-aws-433":{"Impact":[{"tn_id":"T1489","tn_name":"Service Stop"},{"tn_id":"T1529","tn_name":"System Shutdown/Reboot"}]},"ecc-aws-203":{},"ecc-aws-538":{},"ecc-aws-176":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}],"Collection":[{"tn_id":"T1213","tn_name":"Data from Information Repositories"}]},"ecc-aws-011":{},"ecc-aws-482":{},"ecc-aws-544":{},"ecc-aws-444":{},"ecc-aws-146":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}],"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-504":{"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}],"Defense Evasion":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Initial Access":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Persistence":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}]},"ecc-aws-112":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-aws-159":{},"ecc-aws-425":{},"ecc-aws-185":{},"ecc-aws-417":{},"ecc-aws-128":{"Resource Development":[{"tn_id":"T1583","tn_name":"Acquire Infrastructure","st":[{"st_id":"T1583.001","st_name":"Domains"}]}],"Initial Access":[{"tn_id":"T1133","tn_name":"External Remote Services"},{"tn_id":"T1566","tn_name":"Phishing"},{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Persistence":[{"tn_id":"T1133","tn_name":"External Remote Services"}]},"ecc-aws-399":{},"ecc-aws-195":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Exfiltration":[{"tn_id":"T1020","tn_name":"Automated Exfiltration","st":[{"st_id":"T1020.001","st_name":"Traffic Duplication"}]}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}]},"ecc-aws-507":{},"ecc-aws-131":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-285":{},"ecc-aws-154":{"Impact":[{"tn_id":"T1489","tn_name":"Service Stop"}]},"ecc-aws-255":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}],"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}]},"ecc-aws-496":{"Privilege Escalation":[{"tn_id":"T1068","tn_name":"Exploitation for Privilege Escalation"},{"tn_id":"T1611","tn_name":"Escape to Host"}],"Discovery":[{"tn_id":"T1057","tn_name":"Process Discovery"}],"Impact":[{"tn_id":"T1489","tn_name":"Service Stop"}]},"ecc-aws-179":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}]},"ecc-aws-293":{"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"},{"tn_id":"T1213","tn_name":"Data from Information Repositories"},{"tn_id":"T1039","tn_name":"Data from Network Shared Drive"}],"Exfiltration":[{"tn_id":"T1537","tn_name":"Transfer Data to Cloud Account"}],"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1486","tn_name":"Data Encrypted for Impact"},{"tn_id":"T1490","tn_name":"Inhibit System Recovery"},{"tn_id":"T1561","tn_name":"Disk Wipe","st":[{"st_id":"T1561.001","st_name":"Disk Content Wipe"},{"st_id":"T1561.002","st_name":"Disk Structure Wipe"}]},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]},{"tn_id":"T1491","tn_name":"Defacement","st":[{"st_id":"T1491.001","st_name":"Internal Defacement"},{"st_id":"T1491.002","st_name":"External Defacement"}]}]},"ecc-aws-198":{},"ecc-aws-205":{},"ecc-aws-016":{"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]},{"tn_id":"T1555","tn_name":"Credentials from Password Stores"},{"tn_id":"T1187","tn_name":"Forced Authentication"}],"Persistence":[{"tn_id":"T1098","tn_name":"Account Manipulation"},{"tn_id":"T1136","tn_name":"Create Account"}]},"ecc-aws-030":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]},{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]}]},"ecc-aws-006":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1486","tn_name":"Data Encrypted for Impact"},{"tn_id":"T1490","tn_name":"Inhibit System Recovery"},{"tn_id":"T1561","tn_name":"Disk Wipe","st":[{"st_id":"T1561.001","st_name":"Disk Content Wipe"},{"st_id":"T1561.002","st_name":"Disk Structure Wipe"}]},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]},{"tn_id":"T1491","tn_name":"Defacement","st":[{"st_id":"T1491.001","st_name":"Internal Defacement"},{"st_id":"T1491.002","st_name":"External Defacement"}]}]},"ecc-aws-142":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}],"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}],"Discovery":[{"tn_id":"T1619","tn_name":"Cloud Storage Object Discovery"}]},"ecc-aws-114":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-318":{"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}],"Collection":[{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}]},"ecc-aws-132":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-304":{},"ecc-aws-387":{},"ecc-aws-368":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1486","tn_name":"Data Encrypted for Impact"},{"tn_id":"T1490","tn_name":"Inhibit System Recovery"},{"tn_id":"T1561","tn_name":"Disk Wipe","st":[{"st_id":"T1561.001","st_name":"Disk Content Wipe"},{"st_id":"T1561.002","st_name":"Disk Structure Wipe"}]},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]},{"tn_id":"T1491","tn_name":"Defacement","st":[{"st_id":"T1491.001","st_name":"Internal Defacement"},{"st_id":"T1491.002","st_name":"External Defacement"}]}]},"ecc-aws-576":{},"ecc-aws-194":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-394":{},"ecc-aws-218":{"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]},{"tn_id":"T1552","tn_name":"Unsecured Credentials"}]},"ecc-aws-082":{},"ecc-aws-275":{},"ecc-aws-117":{},"ecc-aws-005":{"Initial Access":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]},{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1555","tn_name":"Credentials from Password Stores"},{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}],"Persistence":[{"tn_id":"T1098","tn_name":"Account Manipulation"},{"tn_id":"T1136","tn_name":"Create Account"},{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service"}],"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}]},"ecc-aws-034":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-366":{},"ecc-aws-305":{},"ecc-aws-183":{},"ecc-aws-510":{"Collection":[{"tn_id":"T1213","tn_name":"Data from Information Repositories"}]},"ecc-aws-063":{"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]},{"tn_id":"T1565","tn_name":"Data Manipulation"},{"tn_id":"T1485","tn_name":"Data Destruction"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services","st":[{"st_id":"T1021.001","st_name":"Remote Desktop Protocol"}]},{"tn_id":"T1563","tn_name":"Remote Service Session Hijacking","st":[{"st_id":"T1563.002","st_name":"RDP Hijacking"}]}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}]},"ecc-aws-292":{},"ecc-aws-127":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}],"Collection":[{"tn_id":"T1213","tn_name":"Data from Information Repositories"}]},"ecc-aws-148":{},"ecc-aws-390":{},"ecc-aws-099":{},"ecc-aws-207":{},"ecc-aws-488":{},"ecc-aws-398":{},"ecc-aws-217":{},"ecc-aws-026":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1486","tn_name":"Data Encrypted for Impact"},{"tn_id":"T1490","tn_name":"Inhibit System Recovery"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]},{"tn_id":"T1491","tn_name":"Defacement","st":[{"st_id":"T1491.001","st_name":"Internal Defacement"},{"st_id":"T1491.002","st_name":"External Defacement"}]}]},"ecc-aws-391":{},"ecc-aws-421":{},"ecc-aws-177":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Exfiltration":[{"tn_id":"T1020","tn_name":"Automated Exfiltration","st":[{"st_id":"T1020.001","st_name":"Traffic Duplication"}]}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}]},"ecc-aws-341":{"Execution":[{"tn_id":"T1569","tn_name":"System Services"}],"Persistence":[{"tn_id":"T1543","tn_name":"Create or Modify System Process"},{"tn_id":"T1505","tn_name":"Server Software Component"}],"Privilege Escalation":[{"tn_id":"T1543","tn_name":"Create or Modify System Process"}],"Discovery":[{"tn_id":"T1083","tn_name":"File and Directory Discovery"},{"tn_id":"T1057","tn_name":"Process Discovery"},{"tn_id":"T1518","tn_name":"Software Discovery"}],"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-aws-228":{"Defense Evasion":[{"tn_id":"T1562","tn_name":"Impair Defenses","st":[{"st_id":"T1562.001","st_name":"Disable or Modify Tools"}]}]},"ecc-aws-467":{"Impact":[{"tn_id":"T1489","tn_name":"Service Stop"},{"tn_id":"T1529","tn_name":"System Shutdown/Reboot"}]},"ecc-aws-438":{"Collection":[{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-440":{},"ecc-aws-571":{},"ecc-aws-276":{},"ecc-aws-344":{"Resource Development":[{"tn_id":"T1583","tn_name":"Acquire Infrastructure","st":[{"st_id":"T1583.001","st_name":"Domains"}]}],"Initial Access":[{"tn_id":"T1189","tn_name":"Drive-by Compromise"},{"tn_id":"T1566","tn_name":"Phishing","st":[{"st_id":"T1566.002","st_name":"Spearphishing Link"}]}]},"ecc-aws-319":{"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]},{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.001","st_name":"OS Exhaustion Flood"}]}]},"ecc-aws-081":{},"ecc-aws-380":{},"ecc-aws-486":{},"ecc-aws-520":{"Credential Access":[{"tn_id":"T1552","tn_name":"Unsecured Credentials","st":[{"st_id":"T1552.005","st_name":"Cloud Instance Metadata API"}]}]},"ecc-aws-271":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Exfiltration":[{"tn_id":"T1020","tn_name":"Automated Exfiltration","st":[{"st_id":"T1020.001","st_name":"Traffic Duplication"}]}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"},{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-309":{"Defense Evasion":[{"tn_id":"T1562","tn_name":"Impair Defenses","st":[{"st_id":"T1562.001","st_name":"Disable or Modify Tools"},{"st_id":"T1562.008","st_name":"Disable Cloud Logs"}]}]},"ecc-aws-084":{},"ecc-aws-023":{},"ecc-aws-259":{},"ecc-aws-392":{},"ecc-aws-475":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.001","st_name":"OS Exhaustion Flood"},{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.003","st_name":"Application Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]}]},"ecc-aws-278":{"Discovery":[{"tn_id":"T1580","tn_name":"Cloud Infrastructure Discovery"},{"tn_id":"T1538","tn_name":"Cloud Service Dashboard"},{"tn_id":"T1526","tn_name":"Cloud Service Discovery"}]},"ecc-aws-360":{},"ecc-aws-294":{},"ecc-aws-022":{},"ecc-aws-150":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}],"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}]},"ecc-aws-121":{},"ecc-aws-522":{"Credential Access":[{"tn_id":"T1552","tn_name":"Unsecured Credentials"}]},"ecc-aws-284":{"Impact":[{"tn_id":"T1489","tn_name":"Service Stop"},{"tn_id":"T1529","tn_name":"System Shutdown/Reboot"},{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.001","st_name":"OS Exhaustion Flood"},{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.003","st_name":"Application Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]}]},"ecc-aws-303":{},"ecc-aws-426":{},"ecc-aws-324":{},"ecc-aws-524":{},"ecc-aws-098":{},"ecc-aws-033":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-197":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Exfiltration":[{"tn_id":"T1020","tn_name":"Automated Exfiltration","st":[{"st_id":"T1020.001","st_name":"Traffic Duplication"}]}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}]},"ecc-aws-069":{"Credential Access":[{"tn_id":"T1552","tn_name":"Unsecured Credentials","st":[{"st_id":"T1552.001","st_name":"Credentials In Files"}]}],"Discovery":[{"tn_id":"T1619","tn_name":"Cloud Storage Object Discovery"}],"Collection":[{"tn_id":"T1119","tn_name":"Automated Collection"},{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}],"Exfiltration":[{"tn_id":"T1537","tn_name":"Transfer Data to Cloud Account"}],"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-aws-068":{"Discovery":[{"tn_id":"T1619","tn_name":"Cloud Storage Object Discovery"}],"Collection":[{"tn_id":"T1119","tn_name":"Automated Collection"},{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}]},"ecc-aws-155":{"Impact":[{"tn_id":"T1489","tn_name":"Service Stop"}]},"ecc-aws-039":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-430":{},"ecc-aws-527":{},"ecc-aws-513":{},"ecc-aws-358":{},"ecc-aws-478":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Exfiltration":[{"tn_id":"T1020","tn_name":"Automated Exfiltration","st":[{"st_id":"T1020.001","st_name":"Traffic Duplication"}]}]},"ecc-aws-002":{"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}],"Persistence":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]}],"Initial Access":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]}]},"ecc-aws-464":{},"ecc-aws-037":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-500":{"Impact":[{"tn_id":"T1489","tn_name":"Service Stop"}]},"ecc-aws-545":{},"ecc-aws-181":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Defense Evasion":[{"tn_id":"T1578","tn_name":"Modify Cloud Compute Infrastructure","st":[{"st_id":"T1578.004","st_name":"Revert Cloud Instance"}]}],"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service"}]},"ecc-aws-279":{"Initial Access":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]}],"Persistence":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]}],"Defense Evasion":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}],"Collection":[{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-093":{},"ecc-aws-164":{},"ecc-aws-354":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}],"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"}],"Collection":[{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]}]},"ecc-aws-352":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-aws-085":{},"ecc-aws-021":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1486","tn_name":"Data Encrypted for Impact"},{"tn_id":"T1490","tn_name":"Inhibit System Recovery"},{"tn_id":"T1561","tn_name":"Disk Wipe","st":[{"st_id":"T1561.001","st_name":"Disk Content Wipe"},{"st_id":"T1561.002","st_name":"Disk Structure Wipe"}]},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]},{"tn_id":"T1491","tn_name":"Defacement","st":[{"st_id":"T1491.001","st_name":"Internal Defacement"},{"st_id":"T1491.002","st_name":"External Defacement"}]}]},"ecc-aws-215":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1486","tn_name":"Data Encrypted for Impact"},{"tn_id":"T1490","tn_name":"Inhibit System Recovery"},{"tn_id":"T1561","tn_name":"Disk Wipe","st":[{"st_id":"T1561.001","st_name":"Disk Content Wipe"},{"st_id":"T1561.002","st_name":"Disk Structure Wipe"}]},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]},{"tn_id":"T1491","tn_name":"Defacement","st":[{"st_id":"T1491.001","st_name":"Internal Defacement"},{"st_id":"T1491.002","st_name":"External Defacement"}]}]},"ecc-aws-102":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service"}]},"ecc-aws-362":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-aws-365":{"Credential Access":[{"tn_id":"T1552","tn_name":"Unsecured Credentials"}]},"ecc-aws-160":{},"ecc-aws-075":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}],"Collection":[{"tn_id":"T1213","tn_name":"Data from Information Repositories"}]},"ecc-aws-076":{"Defense Evasion":[{"tn_id":"T1578","tn_name":"Modify Cloud Compute Infrastructure","st":[{"st_id":"T1578.004","st_name":"Revert Cloud Instance"}]}],"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}]},"ecc-aws-029":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-338":{"Execution":[{"tn_id":"T1569","tn_name":"System Services"}],"Persistence":[{"tn_id":"T1543","tn_name":"Create or Modify System Process"},{"tn_id":"T1505","tn_name":"Server Software Component"}],"Privilege Escalation":[{"tn_id":"T1543","tn_name":"Create or Modify System Process"}],"Discovery":[{"tn_id":"T1083","tn_name":"File and Directory Discovery"},{"tn_id":"T1057","tn_name":"Process Discovery"},{"tn_id":"T1518","tn_name":"Software Discovery"}],"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-aws-089":{"Credential Access":[{"tn_id":"T1552","tn_name":"Unsecured Credentials","st":[{"st_id":"T1552.001","st_name":"Credentials In Files"}]}],"Collection":[{"tn_id":"T1213","tn_name":"Data from Information Repositories","st":[{"st_id":"T1213.003","st_name":"Code Repositories"}]},{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}],"Exfiltration":[{"tn_id":"T1537","tn_name":"Transfer Data to Cloud Account"}],"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-aws-236":{},"ecc-aws-343":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}],"Collection":[{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-aws-116":{"Initial Access":[{"tn_id":"T1189","tn_name":"Drive-by Compromise"},{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-165":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Impact":[{"tn_id":"T1489","tn_name":"Service Stop"},{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service"}]},"ecc-aws-270":{"Impact":[{"tn_id":"T1489","tn_name":"Service Stop"},{"tn_id":"T1529","tn_name":"System Shutdown/Reboot"}]},"ecc-aws-326":{"Collection":[{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-372":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.001","st_name":"OS Exhaustion Flood"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}]},"ecc-aws-336":{"Collection":[{"tn_id":"T1213","tn_name":"Data from Information Repositories"}]},"ecc-aws-536":{"Execution":[{"tn_id":"T1203","tn_name":"Exploitation for Client Execution"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]}]},"ecc-aws-512":{"Reconnaissance":[{"tn_id":"T1595","tn_name":"Active Scanning"}],"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"},{"tn_id":"T1133","tn_name":"External Remote Services"}],"Persistence":[{"tn_id":"T1133","tn_name":"External Remote Services"}],"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-aws-367":{"Reconnaissance":[{"tn_id":"T1595","tn_name":"Active Scanning","st":[{"st_id":"T1595.002","st_name":"Vulnerability Scanning"}]}],"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}],"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.001","st_name":"OS Exhaustion Flood"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]}]},"ecc-aws-282":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Exfiltration":[{"tn_id":"T1020","tn_name":"Automated Exfiltration","st":[{"st_id":"T1020.001","st_name":"Traffic Duplication"}]}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}]},"ecc-aws-452":{},"ecc-aws-348":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Exfiltration":[{"tn_id":"T1020","tn_name":"Automated Exfiltration","st":[{"st_id":"T1020.001","st_name":"Traffic Duplication"}]}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}]},"ecc-aws-019":{"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]},{"tn_id":"T1555","tn_name":"Credentials from Password Stores"}]},"ecc-aws-301":{},"ecc-aws-450":{"Credential Access":[{"tn_id":"T1552","tn_name":"Unsecured Credentials","st":[{"st_id":"T1552.005","st_name":"Cloud Instance Metadata API"}]}]},"ecc-aws-024":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}],"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}]},"ecc-aws-097":{},"ecc-aws-103":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Exfiltration":[{"tn_id":"T1020","tn_name":"Automated Exfiltration","st":[{"st_id":"T1020.001","st_name":"Traffic Duplication"}]}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}]},"ecc-aws-424":{},"ecc-aws-043":{},"ecc-aws-051":{"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]},{"tn_id":"T1555","tn_name":"Credentials from Password Stores"}]},"ecc-aws-080":{},"ecc-aws-468":{},"ecc-aws-434":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Execution":[{"tn_id":"T1203","tn_name":"Exploitation for Client Execution"}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]},{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]}]},"ecc-aws-331":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Privilege Escalation":[{"tn_id":"T1068","tn_name":"Exploitation for Privilege Escalation"},{"tn_id":"T1548","tn_name":"Abuse Elevation Control Mechanism","st":[{"st_id":"T1548.002","st_name":"Bypass User Account Control"}]}],"Defense Evasion":[{"tn_id":"T1211","tn_name":"Exploitation for Defense Evasion"},{"tn_id":"T1548","tn_name":"Abuse Elevation Control Mechanism","st":[{"st_id":"T1548.002","st_name":"Bypass User Account Control"}]}],"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"}]},"ecc-aws-487":{"Collection":[{"tn_id":"T1119","tn_name":"Automated Collection"},{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}]},"ecc-aws-484":{},"ecc-aws-389":{},"ecc-aws-071":{"Credential Access":[{"tn_id":"T1552","tn_name":"Unsecured Credentials","st":[{"st_id":"T1552.001","st_name":"Credentials In Files"}]}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"},{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-410":{},"ecc-aws-047":{"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"}]}]},"ecc-aws-532":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Exfiltration":[{"tn_id":"T1020","tn_name":"Automated Exfiltration","st":[{"st_id":"T1020.001","st_name":"Traffic Duplication"}]}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}]},"ecc-aws-192":{},"ecc-aws-220":{},"ecc-aws-141":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Exfiltration":[{"tn_id":"T1020","tn_name":"Automated Exfiltration","st":[{"st_id":"T1020.001","st_name":"Traffic Duplication"}]}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}]},"ecc-aws-533":{},"ecc-aws-018":{},"ecc-aws-470":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}]},"ecc-aws-346":{"Impact":[{"tn_id":"T1489","tn_name":"Service Stop"},{"tn_id":"T1529","tn_name":"System Shutdown/Reboot"},{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.001","st_name":"OS Exhaustion Flood"},{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-aws-239":{},"ecc-aws-060":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}],"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}]},"ecc-aws-376":{},"ecc-aws-232":{},"ecc-aws-009":{},"ecc-aws-001":{"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]},{"tn_id":"T1555","tn_name":"Credentials from Password Stores"},{"tn_id":"T1187","tn_name":"Forced Authentication"}]},"ecc-aws-379":{},"ecc-aws-283":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"}],"Execution":[{"tn_id":"T1203","tn_name":"Exploitation for Client Execution"}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]},{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]}]},"ecc-aws-216":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-553":{},"ecc-aws-541":{},"ecc-aws-480":{"Collection":[{"tn_id":"T1119","tn_name":"Automated Collection"},{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}]},"ecc-aws-237":{},"ecc-aws-353":{},"ecc-aws-211":{"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1555","tn_name":"Credentials from Password Stores"},{"tn_id":"T1187","tn_name":"Forced Authentication"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Exfiltration":[{"tn_id":"T1020","tn_name":"Automated Exfiltration","st":[{"st_id":"T1020.001","st_name":"Traffic Duplication"}]}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"},{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-422":{},"ecc-aws-375":{"Credential Access":[{"tn_id":"T1003","tn_name":"OS Credential Dumping","st":[{"st_id":"T1003.001","st_name":"LSASS Memory"},{"st_id":"T1003.002","st_name":"Security Account Manager"},{"st_id":"T1003.003","st_name":"NTDS"},{"st_id":"T1003.004","st_name":"LSA Secrets"},{"st_id":"T1003.005","st_name":"Cached Domain Credentials"},{"st_id":"T1003.006","st_name":"DCSync"},{"st_id":"T1003.007","st_name":"Proc Filesystem"},{"st_id":"T1003.008","st_name":"/etc/passwd and /etc/shadow"}]},{"tn_id":"T1552","tn_name":"Unsecured Credentials","st":[{"st_id":"T1552.001","st_name":"Credentials In Files"},{"st_id":"T1552.002","st_name":"Credentials in Registry"},{"st_id":"T1552.004","st_name":"Private Keys"}]}],"Collection":[{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-542":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]}]},"ecc-aws-163":{},"ecc-aws-349":{},"ecc-aws-526":{},"ecc-aws-248":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-345":{"Reconnaissance":[{"tn_id":"T1595","tn_name":"Active Scanning","st":[{"st_id":"T1595.002","st_name":"Vulnerability Scanning"}]}],"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}]},"ecc-aws-330":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}]},"ecc-aws-297":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"}],"Execution":[{"tn_id":"T1203","tn_name":"Exploitation for Client Execution"}],"Privilege Escalation":[{"tn_id":"T1068","tn_name":"Exploitation for Privilege Escalation"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]}]},"ecc-aws-213":{"Impact":[{"tn_id":"T1489","tn_name":"Service Stop"}]},"ecc-aws-327":{"Collection":[{"tn_id":"T1213","tn_name":"Data from Information Repositories"}]},"ecc-aws-072":{},"ecc-aws-202":{},"ecc-aws-010":{},"ecc-aws-299":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Exfiltration":[{"tn_id":"T1020","tn_name":"Automated Exfiltration","st":[{"st_id":"T1020.001","st_name":"Traffic Duplication"}]}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}]},"ecc-aws-525":{},"ecc-aws-178":{},"ecc-aws-315":{},"ecc-aws-369":{},"ecc-aws-472":{"Credential Access":[{"tn_id":"T1552","tn_name":"Unsecured Credentials","st":[{"st_id":"T1552.005","st_name":"Cloud Instance Metadata API"}]}]},"ecc-aws-210":{"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1555","tn_name":"Credentials from Password Stores"},{"tn_id":"T1187","tn_name":"Forced Authentication"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Exfiltration":[{"tn_id":"T1020","tn_name":"Automated Exfiltration","st":[{"st_id":"T1020.001","st_name":"Traffic Duplication"}]}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"},{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-351":{"Collection":[{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-130":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Exfiltration":[{"tn_id":"T1020","tn_name":"Automated Exfiltration","st":[{"st_id":"T1020.001","st_name":"Traffic Duplication"}]}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}],"Defense Evasion":[{"tn_id":"T1600","tn_name":"Weaken Encryption"}]},"ecc-aws-200":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-252":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}],"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}]},"ecc-aws-111":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-458":{},"ecc-aws-321":{"Reconnaissance":[{"tn_id":"T1592","tn_name":"Gather Victim Host Information","st":[{"st_id":"T1592.002","st_name":"Software"}]}]},"ecc-aws-123":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}],"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}]},"ecc-aws-517":{},"ecc-aws-126":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}],"Collection":[{"tn_id":"T1213","tn_name":"Data from Information Repositories"}]},"ecc-aws-466":{"Impact":[{"tn_id":"T1489","tn_name":"Service Stop"},{"tn_id":"T1529","tn_name":"System Shutdown/Reboot"}]},"ecc-aws-396":{},"ecc-aws-381":{},"ecc-aws-241":{},"ecc-aws-357":{"Resource Development":[{"tn_id":"T1584","tn_name":"Compromise Infrastructure","st":[{"st_id":"T1584.001","st_name":"Domains"}]}]},"ecc-aws-088":{},"ecc-aws-242":{},"ecc-aws-212":{},"ecc-aws-077":{},"ecc-aws-339":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"}],"Execution":[{"tn_id":"T1203","tn_name":"Exploitation for Client Execution"}]},"ecc-aws-385":{},"ecc-aws-515":{},"ecc-aws-173":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-110":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-aws-397":{},"ecc-aws-065":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Exfiltration":[{"tn_id":"T1020","tn_name":"Automated Exfiltration","st":[{"st_id":"T1020.001","st_name":"Traffic Duplication"}]}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}]},"ecc-aws-508":{"Initial Access":[{"tn_id":"T1189","tn_name":"Drive-by Compromise"},{"tn_id":"T1133","tn_name":"External Remote Services"}],"Persistence":[{"tn_id":"T1133","tn_name":"External Remote Services"}],"Privilege Escalation":[{"tn_id":"T1068","tn_name":"Exploitation for Privilege Escalation"}]},"ecc-aws-140":{"Initial Access":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]}],"Persistence":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]}],"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]}],"Defense Evasion":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.004","st_name":"Cloud Accounts"}]}]},"ecc-aws-206":{},"ecc-aws-416":{},"ecc-aws-463":{},"ecc-aws-384":{},"ecc-aws-503":{"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}],"Defense Evasion":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Initial Access":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Persistence":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}]},"ecc-aws-516":{},"ecc-aws-161":{},"ecc-aws-359":{},"ecc-aws-373":{"Defense Evasion":[{"tn_id":"T1600","tn_name":"Weaken Encryption"}],"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}]},"ecc-aws-509":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Exfiltration":[{"tn_id":"T1020","tn_name":"Automated Exfiltration","st":[{"st_id":"T1020.001","st_name":"Traffic Duplication"}]}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}]},"ecc-aws-575":{},"ecc-aws-086":{"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}],"Exfiltration":[{"tn_id":"T1537","tn_name":"Transfer Data to Cloud Account"}],"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-aws-329":{"Credential Access":[{"tn_id":"T1552","tn_name":"Unsecured Credentials","st":[{"st_id":"T1552.004","st_name":"Private Keys"}]}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services","st":[{"st_id":"T1021.004","st_name":"SSH"}]}]},"ecc-aws-073":{},"ecc-aws-523":{},"ecc-aws-261":{},"ecc-aws-274":{},"ecc-aws-184":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}],"Collection":[{"tn_id":"T1213","tn_name":"Data from Information Repositories"}]},"ecc-aws-491":{"Reconnaissance":[{"tn_id":"T1595","tn_name":"Active Scanning"}],"Execution":[{"tn_id":"T1072","tn_name":"Software Deployment Tools"}],"Discovery":[{"tn_id":"T1046","tn_name":"Network Service Discovery"}],"Lateral Movement":[{"tn_id":"T1072","tn_name":"Software Deployment Tools"},{"tn_id":"T1570","tn_name":"Lateral Tool Transfer"}],"Command and Control":[{"tn_id":"T1095","tn_name":"Non-Application Layer Protocol"},{"tn_id":"T1105","tn_name":"Ingress Tool Transfer"}]},"ecc-aws-393":{},"ecc-aws-411":{},"ecc-aws-027":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]},{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]},{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]},{"tn_id":"T1485","tn_name":"Data Destruction"}]},"ecc-aws-374":{},"ecc-aws-256":{"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}],"Collection":[{"tn_id":"T1530","tn_name":"Data from Cloud Storage"}]},"ecc-aws-291":{"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1486","tn_name":"Data Encrypted for Impact"},{"tn_id":"T1490","tn_name":"Inhibit System Recovery"},{"tn_id":"T1561","tn_name":"Disk Wipe","st":[{"st_id":"T1561.001","st_name":"Disk Content Wipe"},{"st_id":"T1561.002","st_name":"Disk Structure Wipe"}]},{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]},{"tn_id":"T1491","tn_name":"Defacement","st":[{"st_id":"T1491.001","st_name":"Internal Defacement"},{"st_id":"T1491.002","st_name":"External Defacement"}]}]},"ecc-aws-494":{"Privilege Escalation":[{"tn_id":"T1068","tn_name":"Exploitation for Privilege Escalation"}]},"ecc-aws-497":{"Initial Access":[{"tn_id":"T1133","tn_name":"External Remote Services"}],"Persistence":[{"tn_id":"T1133","tn_name":"External Remote Services"}],"Privilege Escalation":[{"tn_id":"T1068","tn_name":"Exploitation for Privilege Escalation"}]},"ecc-aws-064":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.002","st_name":"Password Cracking"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]},{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]},{"tn_id":"T1565","tn_name":"Data Manipulation"}]},"ecc-aws-320":{},"ecc-aws-449":{"Impact":[{"tn_id":"T1489","tn_name":"Service Stop"},{"tn_id":"T1529","tn_name":"System Shutdown/Reboot"}]},"ecc-aws-172":{"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Credential Access":[{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1021","tn_name":"Remote Services"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]},{"tn_id":"T1213","tn_name":"Data from Information Repositories"}],"Impact":[{"tn_id":"T1498","tn_name":"Network Denial of Service","st":[{"st_id":"T1498.001","st_name":"Direct Network Flood"},{"st_id":"T1498.002","st_name":"Reflection Amplification"}]},{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"},{"st_id":"T1499.004","st_name":"Application or System Exploitation"}]}]},"ecc-aws-231":{},"ecc-aws-535":{},"ecc-aws-240":{},"ecc-aws-249":{},"ecc-aws-078":{},"ecc-aws-457":{},"ecc-aws-404":{},"ecc-aws-243":{},"ecc-aws-046":{"Execution":[{"tn_id":"T1648","tn_name":"Serverless Execution"}],"Persistence":[{"tn_id":"T1098","tn_name":"Account Manipulation","st":[{"st_id":"T1098.001","st_name":"Additional Cloud Credentials"},{"st_id":"T1098.003","st_name":"Additional Cloud Roles"}]},{"tn_id":"T1136","tn_name":"Create Account","st":[{"st_id":"T1136.003","st_name":"Cloud Account"}]}],"Defense Evasion":[{"tn_id":"T1578","tn_name":"Modify Cloud Compute Infrastructure","st":[{"st_id":"T1578.001","st_name":"Create Snapshot"},{"st_id":"T1578.002","st_name":"Create Cloud Instance"},{"st_id":"T1578.003","st_name":"Delete Cloud Instance"},{"st_id":"T1578.004","st_name":"Revert Cloud Instance"}]},{"tn_id":"T1535","tn_name":"Unused/Unsupported Cloud Regions"}],"Discovery":[{"tn_id":"T1201","tn_name":"Password Policy Discovery"},{"tn_id":"T1518","tn_name":"Software Discovery","st":[{"st_id":"T1518.001","st_name":"Security Software Discovery"}]},{"tn_id":"T1580","tn_name":"Cloud Infrastructure Discovery"},{"tn_id":"T1619","tn_name":"Cloud Storage Object Discovery"}],"Impact":[{"tn_id":"T1485","tn_name":"Data Destruction"},{"tn_id":"T1486","tn_name":"Data Encrypted for Impact"},{"tn_id":"T1531","tn_name":"Account Access Removal"}]},"ecc-aws-505":{"Credential Access":[{"tn_id":"T1110","tn_name":"Brute Force","st":[{"st_id":"T1110.001","st_name":"Password Guessing"},{"st_id":"T1110.003","st_name":"Password Spraying"},{"st_id":"T1110.004","st_name":"Credential Stuffing"}]}],"Defense Evasion":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Initial Access":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Persistence":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}],"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts","st":[{"st_id":"T1078.001","st_name":"Default Accounts"}]}]},"ecc-aws-247":{"Initial Access":[{"tn_id":"T1133","tn_name":"External Remote Services"},{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Persistence":[{"tn_id":"T1133","tn_name":"External Remote Services"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}],"Lateral Movement":[{"tn_id":"T1563","tn_name":"Remote Service Session Hijacking"}]},"ecc-aws-306":{},"ecc-aws-402":{},"ecc-aws-518":{},"ecc-aws-442":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle","st":[{"st_id":"T1557.001","st_name":"LLMNR/NBT-NS Poisoning and SMB Relay"},{"st_id":"T1557.002","st_name":"ARP Cache Poisoning"},{"st_id":"T1557.003","st_name":"DHCP Spoofing"}]}],"Exfiltration":[{"tn_id":"T1020","tn_name":"Automated Exfiltration","st":[{"st_id":"T1020.001","st_name":"Traffic Duplication"}]}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}]},"ecc-aws-041":{},"ecc-k8s-016":{"Collection":[{"tn_id":"T1005","tn_name":"Data from Local System"}]},"ecc-k8s-067":{"Persistence":[{"tn_id":"T1543","tn_name":"Create or Modify System Process"}],"Privilege Escalation":[{"tn_id":"T1611","tn_name":"Escape to Host"},{"tn_id":"T1548","tn_name":"Abuse Elevation Control Mechanism","st":[{"st_id":"T1548.001","st_name":"Setuid and Setgid"}]},{"tn_id":"T1543","tn_name":"Create or Modify System Process"}],"Defense Evasion":[{"tn_id":"T1548","tn_name":"Abuse Elevation Control Mechanism","st":[{"st_id":"T1548.001","st_name":"Setuid and Setgid"}]}]},"ecc-k8s-081":{"Persistence":[{"tn_id":"T1543","tn_name":"Create or Modify System Process"}],"Privilege Escalation":[{"tn_id":"T1611","tn_name":"Escape to Host"},{"tn_id":"T1543","tn_name":"Create or Modify System Process"},{"tn_id":"T1068","tn_name":"Exploitation for Privilege Escalation"}]},"ecc-k8s-062":{"Persistence":[{"tn_id":"T1543","tn_name":"Create or Modify System Process"}],"Privilege Escalation":[{"tn_id":"T1611","tn_name":"Escape to Host"},{"tn_id":"T1543","tn_name":"Create or Modify System Process"}],"Execution":[{"tn_id":"T1559","tn_name":"Inter-Process Communication"}]},"ecc-k8s-044":{"Collection":[{"tn_id":"T1005","tn_name":"Data from Local System"},{"tn_id":"T1602","tn_name":"Data from Configuration Repository"}]},"ecc-k8s-049":{"Execution":[{"tn_id":"T1106","tn_name":"Native API"}]},"ecc-k8s-024":{"Exfiltration":[{"tn_id":"T1048","tn_name":"Exfiltration Over Alternative Protocol","st":[{"st_id":"T1048.003","st_name":"Exfiltration Over Unencrypted Non-C2 Protocol"}]}]},"ecc-k8s-032":{"Collection":[{"tn_id":"T1005","tn_name":"Data from Local System"}],"Discovery":[{"tn_id":"T1082","tn_name":"System Information Discovery"}]},"ecc-k8s-038":{"Collection":[{"tn_id":"T1005","tn_name":"Data from Local System"}],"Discovery":[{"tn_id":"T1082","tn_name":"System Information Discovery"}]},"ecc-k8s-002":{"Credential Access":[{"tn_id":"T1528","tn_name":"Steal Application Access Token"}],"Defense Evasion":[{"tn_id":"T1550","tn_name":"Use Alternate Authentication Material","st":[{"st_id":"T1550.001","st_name":"Application Access Token"}]}],"Lateral Movement":[{"tn_id":"T1550","tn_name":"Use Alternate Authentication Material","st":[{"st_id":"T1550.001","st_name":"Application Access Token"}]}]},"ecc-k8s-013":{},"ecc-k8s-070":{"Persistence":[{"tn_id":"T1543","tn_name":"Create or Modify System Process"}],"Privilege Escalation":[{"tn_id":"T1611","tn_name":"Escape to Host"},{"tn_id":"T1543","tn_name":"Create or Modify System Process"},{"tn_id":"T1548","tn_name":"Abuse Elevation Control Mechanism","st":[{"st_id":"T1548.003","st_name":"Sudo and Sudo Caching"}]},{"tn_id":"T1068","tn_name":"Exploitation for Privilege Escalation"}],"Defense Evasion":[{"tn_id":"T1548","tn_name":"Abuse Elevation Control Mechanism","st":[{"st_id":"T1548.003","st_name":"Sudo and Sudo Caching"}]}]},"ecc-k8s-092":{"Credential Access":[{"tn_id":"T1528","tn_name":"Steal Application Access Token"}],"Defense Evasion":[{"tn_id":"T1550","tn_name":"Use Alternate Authentication Material","st":[{"st_id":"T1550.001","st_name":"Application Access Token"}]}],"Lateral Movement":[{"tn_id":"T1550","tn_name":"Use Alternate Authentication Material","st":[{"st_id":"T1550.001","st_name":"Application Access Token"}]}]},"ecc-k8s-012":{"Persistence":[{"tn_id":"T1543","tn_name":"Create or Modify System Process"}],"Privilege Escalation":[{"tn_id":"T1543","tn_name":"Create or Modify System Process"}]},"ecc-k8s-061":{"Privilege Escalation":[{"tn_id":"T1611","tn_name":"Escape to Host"}],"Discovery":[{"tn_id":"T1057","tn_name":"Process Discovery"}]},"ecc-k8s-008":{"Execution":[{"tn_id":"T1609","tn_name":"Container Administration Command"}],"Discovery":[{"tn_id":"T1613","tn_name":"Container and Resource Discovery"},{"tn_id":"T1069","tn_name":"Permission Groups Discovery"}],"Defense Evasion":[{"tn_id":"T1562","tn_name":"Impair Defenses","st":[{"st_id":"T1562.001","st_name":"Disable or Modify Tools"}]}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-k8s-030":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Initial Access":[{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.002","st_name":"Transmitted Data Manipulation"}]}]},"ecc-k8s-005":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}]},"ecc-k8s-072":{"Persistence":[{"tn_id":"T1543","tn_name":"Create or Modify System Process"}],"Privilege Escalation":[{"tn_id":"T1611","tn_name":"Escape to Host"},{"tn_id":"T1548","tn_name":"Abuse Elevation Control Mechanism","st":[{"st_id":"T1548.003","st_name":"Sudo and Sudo Caching"}]},{"tn_id":"T1543","tn_name":"Create or Modify System Process"}],"Defense Evasion":[{"tn_id":"T1548","tn_name":"Abuse Elevation Control Mechanism","st":[{"st_id":"T1548.003","st_name":"Sudo and Sudo Caching"}]}],"Impact":[{"tn_id":"T1565","tn_name":"Data Manipulation","st":[{"st_id":"T1565.001","st_name":"Stored Data Manipulation"}]}]},"ecc-k8s-007":{"Execution":[{"tn_id":"T1609","tn_name":"Container Administration Command"}]},"ecc-k8s-035":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}]},"ecc-k8s-064":{"Discovery":[{"tn_id":"T1046","tn_name":"Network Service Discovery"},{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1613","tn_name":"Container and Resource Discovery"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}]},"ecc-k8s-052":{"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts"}]},"ecc-k8s-066":{"Discovery":[{"tn_id":"T1046","tn_name":"Network Service Discovery"},{"tn_id":"T1040","tn_name":"Network Sniffing"},{"tn_id":"T1613","tn_name":"Container and Resource Discovery"}],"Collection":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service"}]},"ecc-k8s-031":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]},"ecc-k8s-026":{"Persistence":[{"tn_id":"T1543","tn_name":"Create or Modify System Process"}],"Privilege Escalation":[{"tn_id":"T1611","tn_name":"Escape to Host"},{"tn_id":"T1543","tn_name":"Create or Modify System Process"},{"tn_id":"T1068","tn_name":"Exploitation for Privilege Escalation"}]},"ecc-k8s-017":{},"ecc-k8s-071":{"Persistence":[{"tn_id":"T1543","tn_name":"Create or Modify System Process"}],"Privilege Escalation":[{"tn_id":"T1611","tn_name":"Escape to Host"},{"tn_id":"T1548","tn_name":"Abuse Elevation Control Mechanism","st":[{"st_id":"T1548.001","st_name":"Setuid and Setgid"}]},{"tn_id":"T1543","tn_name":"Create or Modify System Process"}],"Defense Evasion":[{"tn_id":"T1548","tn_name":"Abuse Elevation Control Mechanism","st":[{"st_id":"T1548.001","st_name":"Setuid and Setgid"}]}]},"ecc-k8s-087":{"Credential Access":[{"tn_id":"T1552","tn_name":"Unsecured Credentials"}]},"ecc-k8s-059":{"Credential Access":[{"tn_id":"T1552","tn_name":"Unsecured Credentials"},{"tn_id":"T1528","tn_name":"Steal Application Access Token"}]},"ecc-k8s-023":{"Execution":[{"tn_id":"T1609","tn_name":"Container Administration Command"}]},"ecc-k8s-060":{"Privilege Escalation":[{"tn_id":"T1611","tn_name":"Escape to Host"}]},"ecc-k8s-079":{},"ecc-k8s-053":{"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts"}]},"ecc-k8s-074":{"Credential Access":[{"tn_id":"T1552","tn_name":"Unsecured Credentials"}]},"ecc-k8s-033":{"Persistence":[{"tn_id":"T1078","tn_name":"Valid Accounts"}],"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts"}],"Defense Evasion":[{"tn_id":"T1078","tn_name":"Valid Accounts"}],"Initial Access":[{"tn_id":"T1078","tn_name":"Valid Accounts"}]},"ecc-k8s-043":{"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}]},"ecc-k8s-027":{"Exfiltration":[{"tn_id":"T1048","tn_name":"Exfiltration Over Alternative Protocol","st":[{"st_id":"T1048.003","st_name":"Exfiltration Over Unencrypted Non-C2 Protocol"}]}]},"ecc-k8s-047":{"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts"}]},"ecc-k8s-041":{"Collection":[{"tn_id":"T1005","tn_name":"Data from Local System"},{"tn_id":"T1602","tn_name":"Data from Configuration Repository"}]},"ecc-k8s-075":{},"ecc-k8s-014":{},"ecc-k8s-080":{},"ecc-k8s-037":{"Reconnaissance":[{"tn_id":"T1592","tn_name":"Gather Victim Host Information"}]},"ecc-k8s-028":{"Collection":[{"tn_id":"T1119","tn_name":"Automated Collection"}],"Credential Access":[{"tn_id":"T1552","tn_name":"Unsecured Credentials","st":[{"st_id":"T1552.001","st_name":"Credentials In Files"}]}]},"ecc-k8s-069":{},"ecc-k8s-054":{},"ecc-k8s-088":{"Credential Access":[{"tn_id":"T1552","tn_name":"Unsecured Credentials"}]},"ecc-k8s-006":{"Initial Access":[{"tn_id":"T1133","tn_name":"External Remote Services"},{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Discovery":[{"tn_id":"T1613","tn_name":"Container and Resource Discovery"},{"tn_id":"T1069","tn_name":"Permission Groups Discovery"}],"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"}]},"ecc-k8s-003":{"Discovery":[{"tn_id":"T1613","tn_name":"Container and Resource Discovery"}]},"ecc-k8s-042":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}]},"ecc-k8s-077":{"Privilege Escalation":[{"tn_id":"T1548","tn_name":"Abuse Elevation Control Mechanism"}],"Defense Evasion":[{"tn_id":"T1548","tn_name":"Abuse Elevation Control Mechanism"}]},"ecc-k8s-040":{"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}]},"ecc-k8s-021":{},"ecc-k8s-068":{},"ecc-k8s-010":{"Persistence":[{"tn_id":"T1543","tn_name":"Create or Modify System Process"}],"Privilege Escalation":[{"tn_id":"T1543","tn_name":"Create or Modify System Process"}]},"ecc-k8s-051":{"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts"}]},"ecc-k8s-063":{"Persistence":[{"tn_id":"T1543","tn_name":"Create or Modify System Process"}],"Privilege Escalation":[{"tn_id":"T1611","tn_name":"Escape to Host"},{"tn_id":"T1543","tn_name":"Create or Modify System Process"}]},"ecc-k8s-019":{},"ecc-k8s-022":{},"ecc-k8s-025":{"Exfiltration":[{"tn_id":"T1048","tn_name":"Exfiltration Over Alternative Protocol","st":[{"st_id":"T1048.003","st_name":"Exfiltration Over Unencrypted Non-C2 Protocol"}]}]},"ecc-k8s-058":{"Credential Access":[{"tn_id":"T1552","tn_name":"Unsecured Credentials"},{"tn_id":"T1528","tn_name":"Steal Application Access Token"}]},"ecc-k8s-001":{"Initial Access":[{"tn_id":"T1133","tn_name":"External Remote Services"},{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Discovery":[{"tn_id":"T1613","tn_name":"Container and Resource Discovery"},{"tn_id":"T1069","tn_name":"Permission Groups Discovery"}],"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"}]},"ecc-k8s-065":{"Privilege Escalation":[{"tn_id":"T1611","tn_name":"Escape to Host"}],"Discovery":[{"tn_id":"T1057","tn_name":"Process Discovery"}]},"ecc-k8s-039":{"Reconnaissance":[{"tn_id":"T1592","tn_name":"Gather Victim Host Information"}]},"ecc-k8s-036":{"Impact":[{"tn_id":"T1489","tn_name":"Service Stop"}]},"ecc-k8s-048":{"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts"}]},"ecc-k8s-015":{"Execution":[{"tn_id":"T1609","tn_name":"Container Administration Command"}]},"ecc-k8s-057":{"Discovery":[{"tn_id":"T1057","tn_name":"Process Discovery"}],"Persistence":[{"tn_id":"T1543","tn_name":"Create or Modify System Process"}],"Privilege Escalation":[{"tn_id":"T1611","tn_name":"Escape to Host"},{"tn_id":"T1543","tn_name":"Create or Modify System Process"},{"tn_id":"T1548","tn_name":"Abuse Elevation Control Mechanism","st":[{"st_id":"T1548.001","st_name":"Setuid and Setgid"}]},{"tn_id":"T1068","tn_name":"Exploitation for Privilege Escalation"}],"Execution":[{"tn_id":"T1559","tn_name":"Inter-Process Communication"}],"Defense Evasion":[{"tn_id":"T1548","tn_name":"Abuse Elevation Control Mechanism","st":[{"st_id":"T1548.001","st_name":"Setuid and Setgid"}]}]},"ecc-k8s-086":{"Persistence":[{"tn_id":"T1543","tn_name":"Create or Modify System Process"}],"Privilege Escalation":[{"tn_id":"T1611","tn_name":"Escape to Host"},{"tn_id":"T1548","tn_name":"Abuse Elevation Control Mechanism","st":[{"st_id":"T1548.001","st_name":"Setuid and Setgid"}]},{"tn_id":"T1543","tn_name":"Create or Modify System Process"}],"Defense Evasion":[{"tn_id":"T1548","tn_name":"Abuse Elevation Control Mechanism","st":[{"st_id":"T1548.001","st_name":"Setuid and Setgid"}]}]},"ecc-k8s-020":{},"ecc-k8s-078":{},"ecc-k8s-034":{"Credential Access":[{"tn_id":"T1552","tn_name":"Unsecured Credentials"}]},"ecc-k8s-056":{"Persistence":[{"tn_id":"T1543","tn_name":"Create or Modify System Process"}],"Privilege Escalation":[{"tn_id":"T1611","tn_name":"Escape to Host"},{"tn_id":"T1543","tn_name":"Create or Modify System Process"},{"tn_id":"T1068","tn_name":"Exploitation for Privilege Escalation"}]},"ecc-k8s-045":{"Credential Access":[{"tn_id":"T1557","tn_name":"Adversary-in-the-Middle"}],"Discovery":[{"tn_id":"T1040","tn_name":"Network Sniffing"}]},"ecc-k8s-076":{},"ecc-k8s-004":{"Initial Access":[{"tn_id":"T1133","tn_name":"External Remote Services"},{"tn_id":"T1190","tn_name":"Exploit Public-Facing Application"}],"Discovery":[{"tn_id":"T1613","tn_name":"Container and Resource Discovery"},{"tn_id":"T1069","tn_name":"Permission Groups Discovery"}],"Lateral Movement":[{"tn_id":"T1210","tn_name":"Exploitation of Remote Services"}]},"ecc-k8s-082":{"Privilege Escalation":[{"tn_id":"T1548","tn_name":"Abuse Elevation Control Mechanism"}],"Defense Evasion":[{"tn_id":"T1548","tn_name":"Abuse Elevation Control Mechanism"}]},"ecc-k8s-018":{},"ecc-k8s-011":{"Execution":[{"tn_id":"T1204","tn_name":"User Execution","st":[{"st_id":"T1204.003","st_name":"Malicious Image"}]}]},"ecc-k8s-050":{"Privilege Escalation":[{"tn_id":"T1078","tn_name":"Valid Accounts"}]},"ecc-k8s-009":{"Impact":[{"tn_id":"T1499","tn_name":"Endpoint Denial of Service","st":[{"st_id":"T1499.002","st_name":"Service Exhaustion Flood"}]}]}} \ No newline at end of file diff --git a/tests/tests_metrics/mock_files/rulesets/caas-settings/RULES_TO_SERVICE.json.gz b/tests/tests_metrics/mock_files/rulesets/caas-settings/RULES_TO_SERVICE.json.gz new file mode 100644 index 000000000..6bd460f68 --- /dev/null +++ b/tests/tests_metrics/mock_files/rulesets/caas-settings/RULES_TO_SERVICE.json.gz @@ -0,0 +1 @@ +{"ecc-azure-056":"Key Vault","ecc-azure-016":"Azure SQL Database","ecc-azure-353":"Azure Virtual Machine Scale Sets","ecc-azure-057":"Key Vault","ecc-azure-108":"Azure Storage Accounts","ecc-azure-038":"Key Vault","ecc-azure-310":"Microsoft Defender for Cloud","ecc-azure-270":"App Service","ecc-azure-367":"Virtual Machines","ecc-azure-314":"Azure Database for PostgreSQL","ecc-azure-039":"Azure Subscription","ecc-azure-044":"Azure Subscription","ecc-azure-042":"Azure Subscription","ecc-azure-068":"Azure Subscription","ecc-azure-137":"Azure Storage Accounts","ecc-azure-163":"Event Grid","ecc-azure-219":"Batch","ecc-azure-299":"App Service","ecc-azure-043":"Azure Subscription","ecc-azure-021":"Azure SQL Database","ecc-azure-117":"Virtual Machines","ecc-azure-161":"App Configuration","ecc-azure-214":"Microsoft Defender for Cloud","ecc-azure-205":"Azure Container Registry","ecc-azure-331":"App Service","ecc-azure-024":"Azure Database for PostgreSQL","ecc-azure-224":"Azure Logic Apps","ecc-azure-123":"Network security groups","ecc-azure-176":"Virtual Network","ecc-azure-122":"Network security groups","ecc-azure-133":"Virtual Machines","ecc-azure-027":"Azure Database for PostgreSQL","ecc-azure-127":"Network security groups","ecc-azure-146":"Key Vault","ecc-azure-236":"App Service","ecc-azure-232":"Azure Virtual Machine Scale Sets","ecc-azure-015":"Azure SQL Database","ecc-azure-156":"Azure Database for MariaDB","ecc-azure-321":"Azure Database for PostgreSQL","ecc-azure-344":"Azure Database for MySQL","ecc-azure-119":"Network security groups","ecc-azure-234":"Virtual Machines","ecc-azure-053":"Azure Disk Storage","ecc-azure-126":"Network security groups","ecc-azure-111":"Azure Database for PostgreSQL","ecc-azure-304":"Application Gateway","ecc-azure-162":"Azure Cache for Redis","ecc-azure-348":"Azure Database for MySQL","ecc-azure-220":"Data Lake Analytics","ecc-azure-284":"Azure Kubernetes Service","ecc-azure-069":"App Service","ecc-azure-014":"Azure SQL Database","ecc-azure-048":"Network security groups","ecc-azure-341":"Azure Front Door","ecc-azure-110":"Azure Storage Accounts","ecc-azure-328":"Azure Data Factory","ecc-azure-160":"Virtual Network","ecc-azure-128":"Network security groups","ecc-azure-165":"Azure Machine Learning","ecc-azure-096":"Microsoft Defender for Cloud","ecc-azure-150":"Virtual Network","ecc-azure-020":"Azure SQL Database","ecc-azure-215":"Virtual Machines","ecc-azure-159":"Azure Storage Accounts","ecc-azure-364":"Azure Monitor","ecc-azure-030":"Azure Database for PostgreSQL","ecc-azure-239":"App Service","ecc-azure-106":"Azure Storage Accounts","ecc-azure-228":"Virtual Machines","ecc-azure-045":"Azure Subscription","ecc-azure-343":"Azure Database for PostgreSQL","ecc-azure-203":"Azure Database for PostgreSQL","ecc-azure-059":"App Service","ecc-azure-290":"Azure Container Registry","ecc-azure-060":"App Service","ecc-azure-009":"Azure Storage Accounts","ecc-azure-258":"App Service","ecc-azure-033":"Azure SQL Database","ecc-azure-293":"Azure SQL Database","ecc-azure-333":"Azure IoT Hub","ecc-azure-170":"Key Vault","ecc-azure-011":"Azure Storage Accounts","ecc-azure-148":"Cognitive Services","ecc-azure-180":"App Service","ecc-azure-158":"Azure Database for PostgreSQL","ecc-azure-283":"Azure Kubernetes Service","ecc-azure-216":"Virtual Machines","ecc-azure-144":"Azure Kubernetes Service","ecc-azure-256":"App Service","ecc-azure-012":"Azure Storage Accounts","ecc-azure-025":"Azure Database for MySQL","ecc-azure-121":"Network security groups","ecc-azure-197":"Virtual Machines","ecc-azure-166":"Azure SignalR Service","ecc-azure-371":"Azure Database for MySQL","ecc-azure-372":"Azure Database for MySQL","ecc-azure-300":"Application Gateway","ecc-azure-207":"Azure SQL Managed Instance","ecc-azure-376":"Microsoft Defender for Cloud","ecc-azure-098":"Microsoft Defender for Cloud","ecc-azure-311":"Azure Database for PostgreSQL","ecc-azure-319":"Azure Database for PostgreSQL","ecc-azure-173":"Azure Database for PostgreSQL","ecc-azure-026":"Azure Database for PostgreSQL","ecc-azure-031":"Azure Database for PostgreSQL","ecc-azure-287":"Azure Kubernetes Service","ecc-azure-342":"Azure SQL Database","ecc-azure-177":"Application Gateway","ecc-azure-151":"Virtual Network","ecc-azure-149":"Azure Container Registry","ecc-azure-145":"Azure Cosmos DB","ecc-azure-323":"Azure Virtual Machine Scale Sets","ecc-azure-227":"Azure Virtual Machine Scale Sets","ecc-azure-277":"Azure Database for MySQL","ecc-azure-171":"Azure Database for MariaDB","ecc-azure-240":"App Service","ecc-azure-071":"App Service","ecc-azure-288":"Azure Kubernetes Service","ecc-azure-318":"Azure Database for PostgreSQL","ecc-azure-257":"App Service","ecc-azure-054":"Azure Disk Storage","ecc-azure-182":"Azure Service Fabric","ecc-azure-072":"App Service","ecc-azure-365":"API Management","ecc-azure-097":"Microsoft Defender for Cloud","ecc-azure-109":"Azure Storage Accounts","ecc-azure-147":"Cognitive Services","ecc-azure-302":"Azure Cache for Redis","ecc-azure-010":"Azure Storage Accounts","ecc-azure-326":"Azure Data Explorer","ecc-azure-006":"Microsoft Defender for Cloud","ecc-azure-132":"Virtual Machines","ecc-azure-125":"Network security groups","ecc-azure-286":"Azure Kubernetes Service","ecc-azure-052":"Network security groups","ecc-azure-282":"Azure Kubernetes Service","ecc-azure-067":"Azure Subscription","ecc-azure-305":"Azure Storage Accounts","ecc-azure-168":"Azure Container Registry","ecc-azure-139":"Azure Disk Storage","ecc-azure-179":"App Service","ecc-azure-370":"Azure Cosmos DB","ecc-azure-349":"Azure Database for MySQL","ecc-azure-028":"Azure Database for PostgreSQL","ecc-azure-141":"Virtual Network","ecc-azure-007":"Microsoft Defender for Cloud","ecc-azure-356":"API Management","ecc-azure-346":"Azure Database for MySQL","ecc-azure-267":"App Service","ecc-azure-327":"Azure Data Factory","ecc-azure-065":"App Service","ecc-azure-099":"Microsoft Defender for Cloud","ecc-azure-265":"Azure SQL Managed Instance","ecc-azure-345":"Azure Database for MySQL","ecc-azure-237":"App Service","ecc-azure-196":"Azure SQL Managed Instance","ecc-azure-094":"Microsoft Defender for Cloud","ecc-azure-358":"Azure Synapse Analytics","ecc-azure-295":"Azure SQL Database","ecc-azure-281":"Azure Kubernetes Service","ecc-azure-357":"Azure Databricks","ecc-azure-120":"Network security groups","ecc-azure-325":"Azure Data Explorer","ecc-azure-317":"Azure Database for PostgreSQL","ecc-azure-112":"Azure Subscription","ecc-azure-005":"Microsoft Defender for Cloud","ecc-azure-167":"Azure Spring Apps","ecc-azure-374":"Azure Subscription","ecc-azure-272":"Azure Virtual Machine Scale Sets","ecc-azure-022":"Azure SQL Database","ecc-azure-124":"Network security groups","ecc-azure-275":"Virtual Machines","ecc-azure-354":"Azure Container Registry","ecc-azure-095":"Microsoft Defender for Cloud","ecc-azure-055":"Key Vault","ecc-azure-036":"Azure Storage Accounts","ecc-azure-340":"Application Gateway","ecc-azure-301":"Azure Cache for Redis","ecc-azure-101":"Microsoft Defender for Cloud","ecc-azure-201":"Azure Cosmos DB","ecc-azure-064":"App Service","ecc-azure-339":"Key Vault","ecc-azure-116":"Virtual Machines","ecc-azure-276":"Azure Database for MariaDB","ecc-azure-113":"Virtual Machines","ecc-azure-066":"Azure Subscription","ecc-azure-157":"Azure Database for MySQL","ecc-azure-102":"Microsoft Defender for Cloud","ecc-azure-032":"Azure SQL Database","ecc-azure-050":"Azure SQL Database","ecc-azure-213":"Microsoft Defender for Cloud","ecc-azure-200":"Automation","ecc-azure-289":"Azure Container Registry","ecc-azure-225":"Azure Cognitive Search","ecc-azure-241":"App Service","ecc-azure-206":"Azure Service Fabric","ecc-azure-152":"Virtual Machines","ecc-azure-184":"Virtual Machines","ecc-azure-329":"Batch","ecc-azure-023":"Azure SQL Database","ecc-azure-294":"Virtual Machines","ecc-azure-129":"Network security groups","ecc-azure-105":"Azure Storage Accounts","ecc-azure-332":"App Service","ecc-azure-058":"Azure Kubernetes Service","ecc-azure-379":"App Service","ecc-azure-202":"Azure Machine Learning","ecc-azure-350":"Azure Database for MySQL","ecc-azure-355":"Azure Machine Learning","ecc-azure-278":"Azure Database for PostgreSQL","ecc-azure-378":"Network security groups","ecc-azure-362":"Microsoft Defender for Cloud","ecc-azure-334":"Azure Cosmos DB","ecc-azure-142":"Network security groups","ecc-azure-306":"Azure Database for PostgreSQL","ecc-azure-070":"App Service","ecc-azure-181":"App Service","ecc-azure-359":"Azure Synapse Analytics","ecc-azure-351":"Azure Database for MySQL","ecc-azure-231":"Virtual Machines","ecc-azure-337":"Virtual Machines","ecc-azure-164":"Event Grid","ecc-azure-155":"Azure SQL Database","ecc-azure-061":"App Service","ecc-azure-004":"Microsoft Defender for Cloud","ecc-azure-130":"Network security groups","ecc-azure-373":"Azure Subscription","ecc-azure-238":"App Service","ecc-azure-002":"Azure RBAC","ecc-azure-199":"Azure Cache for Redis","ecc-azure-298":"App Service","ecc-azure-046":"Azure Subscription","ecc-azure-217":"Azure Data Lake Storage","ecc-azure-291":"Azure Storage Accounts","ecc-azure-049":"Network security groups","ecc-azure-313":"Azure Database for PostgreSQL","ecc-azure-204":"Cognitive Services","ecc-azure-279":"Azure Kubernetes Service","ecc-azure-235":"Azure Kubernetes Service","ecc-azure-178":"Azure Front Door","ecc-azure-172":"Azure Database for MySQL","ecc-azure-226":"Service Bus","ecc-azure-324":"Azure Data Explorer","ecc-azure-143":"API Management","ecc-azure-280":"Azure Kubernetes Service","ecc-azure-369":"Azure Storage Accounts","ecc-azure-100":"Microsoft Defender for Cloud","ecc-azure-218":"Azure Stream Analytics","ecc-azure-368":"Azure Virtual Machine Scale Sets","ecc-azure-103":"Microsoft Defender for Cloud","ecc-azure-174":"Azure Storage Accounts","ecc-azure-347":"Azure Database for MySQL","ecc-azure-037":"Azure Storage Accounts","ecc-azure-222":"Azure IoT Hub","ecc-azure-131":"Network security groups","ecc-azure-296":"Azure SQL Database","ecc-azure-336":"Azure Virtual Machine Scale Sets","ecc-azure-013":"Azure SQL Database","ecc-azure-008":"Azure Storage Accounts","ecc-gcp-246":"Cloud Functions","ecc-gcp-401":"Cloud SQL","ecc-gcp-206":"Cloud SQL","ecc-gcp-065":"Cloud KMS","ecc-gcp-272":"Virtual Private Cloud","ecc-gcp-262":"Cloud Run","ecc-gcp-310":"Cloud Memorystore","ecc-gcp-202":"Cloud SQL","ecc-gcp-294":"Cloud Spanner","ecc-gcp-076":"Virtual Private Cloud","ecc-gcp-281":"Virtual Private Cloud","ecc-gcp-123":"Google Kubernetes Engine","ecc-gcp-127":"Google Kubernetes Engine","ecc-gcp-311":"Cloud Data Fusion","ecc-gcp-214":"BigQuery","ecc-gcp-068":"Cloud KMS","ecc-gcp-268":"Cloud Run","ecc-gcp-220":"Compute Engine","ecc-gcp-415":"Google Kubernetes Engine","ecc-gcp-251":"Google Kubernetes Engine","ecc-gcp-118":"Virtual Private Cloud","ecc-gcp-126":"Google Kubernetes Engine","ecc-gcp-122":"Virtual Private Cloud","ecc-gcp-244":"Cloud Functions","ecc-gcp-324":"Cloud IAM","ecc-gcp-443":"Cloud KMS","ecc-gcp-409":"Compute Engine","ecc-gcp-200":"Cloud SQL","ecc-gcp-049":"Google Kubernetes Engine","ecc-gcp-083":"Cloud SQL","ecc-gcp-051":"Google Kubernetes Engine","ecc-gcp-167":"BigQuery","ecc-gcp-022":"Cloud Logging","ecc-gcp-203":"Cloud SQL","ecc-gcp-136":"Google Kubernetes Engine","ecc-gcp-208":"Cloud SQL","ecc-gcp-125":"Cloud Load Balancing","ecc-gcp-347":"Cloud Spanner","ecc-gcp-306":"Pub/Sub","ecc-gcp-044":"Cloud SQL","ecc-gcp-314":"Access Approval","ecc-gcp-260":"Cloud Bigtable","ecc-gcp-342":"Virtual Private Cloud","ecc-gcp-237":"Cloud SQL","ecc-gcp-072":"Virtual Private Cloud","ecc-gcp-256":"App Engine","ecc-gcp-054":"Google Kubernetes Engine","ecc-gcp-230":"Compute Engine","ecc-gcp-277":"Cloud DNS","ecc-gcp-008":"Cloud KMS","ecc-gcp-250":"Google Kubernetes Engine","ecc-gcp-304":"Vertex AI Workbench","ecc-gcp-152":"Compute Engine","ecc-gcp-252":"Google Kubernetes Engine","ecc-gcp-445":"Compute Engine","ecc-gcp-070":"Cloud Load Balancing","ecc-gcp-035":"Compute Engine","ecc-gcp-017":"Cloud Logging","ecc-gcp-141":"Cloud Storage","ecc-gcp-193":"Google Kubernetes Engine","ecc-gcp-175":"Cloud Storage","ecc-gcp-199":"Cloud SQL","ecc-gcp-185":"Cloud SQL","ecc-gcp-131":"Google Kubernetes Engine","ecc-gcp-207":"Cloud SQL","ecc-gcp-004":"Cloud IAM","ecc-gcp-289":"Virtual Private Cloud","ecc-gcp-104":"Virtual Private Cloud","ecc-gcp-273":"Cloud Armor","ecc-gcp-234":"Cloud SQL","ecc-gcp-117":"Virtual Private Cloud","ecc-gcp-309":"Dataproc","ecc-gcp-103":"Compute Engine","ecc-gcp-271":"Virtual Private Cloud","ecc-gcp-346":"Compute Engine","ecc-gcp-020":"Cloud Logging","ecc-gcp-432":"Google Kubernetes Engine","ecc-gcp-087":"Cloud SQL","ecc-gcp-448":"Compute Engine","ecc-gcp-032":"Virtual Private Cloud","ecc-gcp-186":"Cloud SQL","ecc-gcp-063":"Google Kubernetes Engine","ecc-gcp-258":"Cloud Bigtable","ecc-gcp-166":"Google Kubernetes Engine","ecc-gcp-449":"Compute Engine","ecc-gcp-227":"Compute Engine","ecc-gcp-124":"Google Kubernetes Engine","ecc-gcp-021":"Cloud Logging","ecc-gcp-173":"Compute Engine","ecc-gcp-061":"Google Kubernetes Engine","ecc-gcp-228":"Compute Engine","ecc-gcp-119":"Virtual Private Cloud","ecc-gcp-218":"Compute Engine","ecc-gcp-093":"Cloud Load Balancing","ecc-gcp-215":"Compute Engine","ecc-gcp-446":"Compute Engine","ecc-gcp-025":"Virtual Private Cloud","ecc-gcp-001":"Cloud IAM","ecc-gcp-023":"Cloud Logging","ecc-gcp-253":"Google Kubernetes Engine","ecc-gcp-288":"Virtual Private Cloud","ecc-gcp-184":"Cloud SQL","ecc-gcp-172":"Compute Engine","ecc-gcp-092":"Cloud Load Balancing","ecc-gcp-067":"Secret Manager","ecc-gcp-101":"Cloud Load Balancing","ecc-gcp-036":"Compute Engine","ecc-gcp-153":"Compute Engine","ecc-gcp-019":"Cloud Logging","ecc-gcp-212":"Cloud SQL","ecc-gcp-050":"Google Kubernetes Engine","ecc-gcp-213":"BigQuery","ecc-gcp-229":"Compute Engine","ecc-gcp-130":"Google Kubernetes Engine","ecc-gcp-006":"Cloud IAM","ecc-gcp-012":"Cloud APIs","ecc-gcp-335":"Cloud IAM","ecc-gcp-307":"Dataproc","ecc-gcp-030":"Virtual Private Cloud","ecc-gcp-057":"Google Kubernetes Engine","ecc-gcp-450":"Google Kubernetes Engine","ecc-gcp-015":"Cloud Logging","ecc-gcp-266":"Cloud Run","ecc-gcp-178":"Cloud SQL","ecc-gcp-293":"Cloud Spanner","ecc-gcp-291":"Secret Manager","ecc-gcp-043":"Cloud SQL","ecc-gcp-438":"Google Kubernetes Engine","ecc-gcp-039":"Compute Engine","ecc-gcp-187":"Cloud SQL","ecc-gcp-340":"Cloud Armor","ecc-gcp-177":"Cloud SQL","ecc-gcp-058":"Google Kubernetes Engine","ecc-gcp-302":"Cloud Armor","ecc-gcp-181":"Cloud SQL","ecc-gcp-264":"Cloud Run","ecc-gcp-082":"Cloud SQL","ecc-gcp-209":"Cloud SQL","ecc-gcp-162":"Cloud Storage","ecc-gcp-323":"Cloud IAM","ecc-gcp-013":"Cloud APIs","ecc-gcp-386":"Compute Engine","ecc-gcp-077":"Cloud SQL","ecc-gcp-194":"Compute Engine","ecc-gcp-066":"Cloud KMS","ecc-gcp-007":"Cloud IAM","ecc-gcp-143":"Cloud IAM","ecc-gcp-129":"Virtual Private Cloud","ecc-gcp-248":"Cloud Functions","ecc-gcp-046":"Cloud SQL","ecc-gcp-091":"Cloud Load Balancing","ecc-gcp-088":"Cloud Load Balancing","ecc-gcp-205":"Cloud SQL","ecc-gcp-027":"Cloud DNS","ecc-gcp-038":"Compute Engine","ecc-gcp-138":"Cloud Load Balancing","ecc-gcp-292":"Cloud Spanner","ecc-gcp-295":"Cloud Spanner","ecc-gcp-241":"Cloud Functions","ecc-gcp-010":"Cloud APIs","ecc-gcp-257":"App Engine","ecc-gcp-221":"Compute Engine","ecc-gcp-005":"Cloud IAM","ecc-gcp-120":"Virtual Private Cloud","ecc-gcp-300":"Cloud Memorystore","ecc-gcp-283":"Virtual Private Cloud","ecc-gcp-011":"Cloud APIs","ecc-gcp-265":"Cloud Run","ecc-gcp-121":"Virtual Private Cloud","ecc-gcp-245":"Cloud Functions","ecc-gcp-144":"Cloud SQL","ecc-gcp-009":"Cloud IAM","ecc-gcp-016":"Cloud Storage","ecc-gcp-059":"Google Kubernetes Engine","ecc-gcp-337":"Virtual Private Cloud","ecc-gcp-111":"Virtual Private Cloud","ecc-gcp-128":"Google Kubernetes Engine","ecc-gcp-263":"Cloud Run","ecc-gcp-031":"Virtual Private Cloud","ecc-gcp-313":"Access Transparency","ecc-gcp-163":"Cloud Storage","ecc-gcp-442":"Compute Engine","ecc-gcp-334":"Cloud IAM","ecc-gcp-060":"Google Kubernetes Engine","ecc-gcp-191":"Google Kubernetes Engine","ecc-gcp-204":"Cloud SQL","ecc-gcp-316":"Cloud Asset Inventory","ecc-gcp-198":"Cloud SQL","ecc-gcp-029":"Cloud DNS","ecc-gcp-028":"Cloud DNS","ecc-gcp-047":"Google Kubernetes Engine","ecc-gcp-276":"Cloud Armor","ecc-gcp-444":"Compute Engine","ecc-gcp-232":"Compute Engine","ecc-gcp-318":"Cloud Functions","ecc-gcp-287":"Pub/Sub","ecc-gcp-195":"Virtual Private Cloud","ecc-gcp-183":"Cloud SQL","ecc-gcp-165":"Google Kubernetes Engine","ecc-gcp-037":"Compute Engine","ecc-gcp-299":"Cloud Storage","ecc-gcp-387":"Compute Engine","ecc-gcp-151":"Compute Engine","ecc-gcp-451":"Compute Engine","ecc-gcp-201":"Cloud SQL","ecc-gcp-385":"Compute Engine","ecc-gcp-142":"Cloud Storage","ecc-gcp-113":"Virtual Private Cloud","ecc-gcp-024":"Cloud Logging","ecc-gcp-040":"Cloud Storage","ecc-gcp-412":"Cloud Data Fusion","ecc-gcp-107":"Compute Engine","ecc-gcp-225":"Compute Engine","ecc-gcp-217":"Compute Engine","ecc-gcp-169":"Cloud KMS","ecc-gcp-231":"Compute Engine","ecc-gcp-192":"Google Kubernetes Engine","ecc-gcp-280":"Virtual Private Cloud","ecc-gcp-003":"Cloud IAM","ecc-gcp-315":"Cloud SQL","ecc-gcp-033":"Virtual Private Cloud","ecc-gcp-452":"Dataproc","ecc-gcp-278":"Virtual Private Cloud","ecc-gcp-014":"Cloud Logging","ecc-gcp-099":"Compute Engine","ecc-gcp-254":"App Engine","ecc-gcp-079":"Cloud Storage","ecc-gcp-434":"Google Kubernetes Engine","ecc-gcp-240":"Cloud SQL","ecc-gcp-089":"Cloud Load Balancing","ecc-gcp-133":"Google Kubernetes Engine","ecc-gcp-090":"Cloud Load Balancing","ecc-gcp-109":"Virtual Private Cloud","ecc-gcp-436":"Cloud SQL","ecc-gcp-210":"Cloud SQL","ecc-gcp-170":"Cloud Load Balancing","ecc-gcp-400":"Cloud SQL","ecc-gcp-134":"Google Kubernetes Engine","ecc-gcp-233":"Compute Engine","ecc-gcp-279":"Virtual Private Cloud","ecc-gcp-114":"Virtual Private Cloud","ecc-gcp-116":"Virtual Private Cloud","ecc-gcp-305":"Dataproc","ecc-gcp-286":"Pub/Sub","ecc-gcp-447":"Compute Engine","ecc-gcp-180":"Cloud SQL","ecc-gcp-171":"Compute Engine","ecc-gcp-062":"Virtual Private Cloud","ecc-gcp-132":"Google Kubernetes Engine","ecc-gcp-110":"Virtual Private Cloud","ecc-gcp-223":"Compute Engine","ecc-gcp-189":"Cloud Storage","ecc-gcp-190":"Cloud Storage","ecc-gcp-115":"Virtual Private Cloud","ecc-gcp-176":"Cloud SQL","ecc-gcp-261":"Dataproc","ecc-gcp-053":"Google Kubernetes Engine","ecc-gcp-303":"Dataflow","ecc-gcp-236":"Cloud SQL","ecc-gcp-247":"Cloud Functions","ecc-gcp-086":"Cloud SQL","ecc-gcp-222":"Compute Engine","ecc-gcp-197":"Compute Engine","ecc-gcp-048":"Google Kubernetes Engine","ecc-gcp-274":"Cloud Armor","ecc-gcp-150":"Cloud Storage","ecc-gcp-243":"Cloud Functions","ecc-gcp-282":"Virtual Private Cloud","ecc-gcp-140":"Cloud SQL","ecc-gcp-055":"Google Kubernetes Engine","ecc-gcp-239":"Cloud SQL","ecc-gcp-285":"Virtual Private Cloud","ecc-gcp-312":"Cloud Data Fusion","ecc-gcp-249":"Cloud Functions","ecc-gcp-219":"Compute Engine","ecc-gcp-018":"Cloud Logging","ecc-gcp-188":"BigQuery","ecc-gcp-179":"Cloud SQL","ecc-gcp-042":"Cloud Storage","ecc-gcp-182":"Cloud SQL","ecc-gcp-071":"Virtual Private Cloud","ecc-gcp-137":"Cloud Load Balancing","ecc-gcp-298":"Cloud Storage","ecc-gcp-112":"Virtual Private Cloud","ecc-gcp-317":"Cloud Bigtable","ecc-gcp-242":"Cloud Functions","ecc-gcp-034":"Compute Engine","ecc-gcp-216":"Compute Engine","ecc-gcp-453":"Cloud Memorystore","ecc-gcp-211":"Cloud SQL","ecc-gcp-135":"Google Kubernetes Engine","ecc-aws-235":"Amazon Relational Database Service","ecc-aws-511":"Amazon Elastic Load Balancing","ecc-aws-190":"Amazon Elastic Container Service","ecc-aws-091":"Amazon EC2","ecc-aws-295":"Amazon CloudFront","ecc-aws-286":"Amazon WorkSpaces Family","ecc-aws-529":"Amazon EC2","ecc-aws-493":"Amazon Elastic Container Service","ecc-aws-483":"AWS CodeBuild","ecc-aws-383":"Amazon Virtual Private Cloud","ecc-aws-118":"Amazon Elastic Container Service","ecc-aws-435":"Amazon MQ","ecc-aws-144":"AWS CloudTrail","ecc-aws-414":"AWS Glue","ecc-aws-156":"Amazon OpenSearch Service","ecc-aws-363":"Amazon Kinesis","ecc-aws-229":"Amazon Elastic Container Registry","ecc-aws-296":"Amazon Relational Database Service","ecc-aws-313":"AWS Database Migration Service","ecc-aws-378":"Amazon Elastic Block Store","ecc-aws-302":"Amazon Relational Database Service","ecc-aws-342":"Amazon Route 53","ecc-aws-187":"Amazon Virtual Private Cloud","ecc-aws-245":"Amazon Relational Database Service","ecc-aws-101":"Amazon Virtual Private Cloud","ecc-aws-189":"Amazon EC2","ecc-aws-465":"Amazon FSx","ecc-aws-572":"AWS Key Management Service","ecc-aws-506":"Amazon Redshift","ecc-aws-214":"Amazon Redshift","ecc-aws-153":"Amazon OpenSearch Service","ecc-aws-083":"Amazon CloudFront","ecc-aws-122":"Amazon DynamoDB","ecc-aws-265":"Amazon ElastiCache","ecc-aws-147":"Amazon Elastic Block Store","ecc-aws-129":"Amazon Elastic Load Balancing","ecc-aws-401":"Amazon Data Lifecycle Manager","ecc-aws-062":"Amazon EC2","ecc-aws-250":"Amazon API Gateway","ecc-aws-188":"Amazon Virtual Private Cloud","ecc-aws-004":"Amazon S3","ecc-aws-332":"Amazon WorkSpaces Family","ecc-aws-040":"Amazon Elastic Kubernetes Service","ecc-aws-347":"Amazon Managed Streaming for Apache Kafka","ecc-aws-269":"Amazon ElastiCache","ecc-aws-280":"Amazon OpenSearch Service","ecc-aws-534":"Amazon EC2 Auto Scaling","ecc-aws-531":"Amazon Elastic Block Store","ecc-aws-471":"Amazon EC2 Auto Scaling","ecc-aws-498":"Amazon Elastic Load Balancing","ecc-aws-447":"Amazon Managed Workflows for Apache Airflow","ecc-aws-395":"Amazon EC2 Auto Scaling","ecc-aws-548":"Amazon Elastic Block Store","ecc-aws-277":"Amazon OpenSearch Service","ecc-aws-204":"Amazon Relational Database Service","ecc-aws-539":"Amazon CloudFront","ecc-aws-501":"Amazon OpenSearch Service","ecc-aws-573":"Amazon Virtual Private Cloud","ecc-aws-186":"Amazon EC2","ecc-aws-485":"AWS CodeDeploy","ecc-aws-263":"Amazon Virtual Private Cloud","ecc-aws-552":"Amazon DynamoDB","ecc-aws-032":"Amazon EC2","ecc-aws-337":"AWS Lambda","ecc-aws-439":"Amazon QLDB","ecc-aws-050":"AWS Account","ecc-aws-406":"Amazon ElastiCache","ecc-aws-230":"Amazon Elastic Container Registry","ecc-aws-171":"Amazon EC2","ecc-aws-267":"Amazon ElastiCache","ecc-aws-031":"Amazon EC2","ecc-aws-169":"Amazon EC2","ecc-aws-451":"AWS Elastic Beanstalk","ecc-aws-208":"Amazon Relational Database Service","ecc-aws-038":"Amazon EC2","ecc-aws-371":"Amazon WorkSpaces Family","ecc-aws-049":"AWS Account","ecc-aws-530":"Amazon CloudFront","ecc-aws-407":"AWS Elastic Beanstalk","ecc-aws-340":"Amazon MQ","ecc-aws-310":"AWS Database Migration Service","ecc-aws-257":"Amazon EMR","ecc-aws-180":"Amazon CloudFront","ecc-aws-196":"Amazon EMR","ecc-aws-251":"Amazon AppFlow","ecc-aws-312":"AWS Database Migration Service","ecc-aws-386":"Amazon EC2","ecc-aws-492":"Amazon Elastic Container Registry","ecc-aws-124":"Amazon Elastic File System","ecc-aws-537":"Amazon Elastic Container Service","ecc-aws-333":"Amazon FSx","ecc-aws-096":"AWS Account","ecc-aws-170":"Amazon EC2","ecc-aws-481":"AWS CodeBuild","ecc-aws-428":"Amazon Relational Database Service","ecc-aws-036":"Amazon EC2","ecc-aws-314":"Amazon Relational Database Service","ecc-aws-017":"AWS Identity and Access Management","ecc-aws-008":"AWS Identity and Access Management","ecc-aws-521":"Amazon Elastic Container Service","ecc-aws-335":"AWS Lambda","ecc-aws-014":"Amazon Elastic Load Balancing","ecc-aws-139":"AWS Account","ecc-aws-090":"Amazon Relational Database Service","ecc-aws-355":"Amazon Redshift","ecc-aws-323":"Amazon Relational Database Service","ecc-aws-432":"Amazon Simple Queue Service","ecc-aws-490":"Amazon EC2","ecc-aws-143":"AWS CloudTrail","ecc-aws-437":"Amazon S3","ecc-aws-455":"Amazon EMR","ecc-aws-227":"Amazon Elastic Kubernetes Service","ecc-aws-443":"AWS AppSync","ecc-aws-356":"Amazon Redshift","ecc-aws-219":"AWS Secrets Manager","ecc-aws-311":"Amazon SageMaker","ecc-aws-053":"AWS CloudTrail","ecc-aws-290":"Amazon WorkSpaces Family","ecc-aws-106":"AWS Certificate Manager","ecc-aws-125":"Amazon ElastiCache","ecc-aws-162":"Amazon Relational Database Service","ecc-aws-058":"AWS Account","ecc-aws-317":"Amazon Relational Database Service","ecc-aws-459":"AWS Lambda","ecc-aws-427":"Amazon Relational Database Service","ecc-aws-119":"Amazon Kinesis","ecc-aws-325":"AWS Database Migration Service","ecc-aws-514":"AWS Identity and Access Management","ecc-aws-266":"Amazon ElastiCache","ecc-aws-474":"Amazon Elastic Load Balancing","ecc-aws-182":"Amazon DynamoDB","ecc-aws-175":"Amazon Relational Database Service","ecc-aws-061":"AWS Key Management Service","ecc-aws-456":"Amazon EMR","ecc-aws-489":"Amazon EC2","ecc-aws-042":"Amazon S3","ecc-aws-418":"Amazon Kinesis","ecc-aws-502":"Amazon Relational Database Service","ecc-aws-560":"Amazon Simple Notification Service","ecc-aws-543":"Amazon CloudFront","ecc-aws-519":"Amazon Virtual Private Cloud","ecc-aws-288":"Amazon WorkSpaces Family","ecc-aws-350":"Amazon Managed Streaming for Apache Kafka","ecc-aws-066":"Amazon Elastic Kubernetes Service","ecc-aws-370":"Amazon WorkSpaces Family","ecc-aws-136":"Amazon Elastic Load Balancing","ecc-aws-298":"Amazon Simple Queue Service","ecc-aws-361":"Amazon API Gateway","ecc-aws-412":"Amazon FSx","ecc-aws-025":"Amazon EC2","ecc-aws-300":"Amazon Simple Queue Service","ecc-aws-322":"Amazon Relational Database Service","ecc-aws-499":"AWS Identity and Access Management","ecc-aws-431":"Amazon Simple Notification Service","ecc-aws-120":"Amazon Kinesis","ecc-aws-225":"Amazon Elastic Kubernetes Service","ecc-aws-151":"Amazon EC2","ecc-aws-109":"AWS Certificate Manager","ecc-aws-415":"AWS Identity and Access Management","ecc-aws-166":"Amazon EC2","ecc-aws-012":"Amazon CloudFront","ecc-aws-134":"Amazon Elastic Load Balancing","ecc-aws-052":"AWS CloudTrail","ecc-aws-221":"Amazon Simple Notification Service","ecc-aws-400":"Amazon DynamoDB Accelerator","ecc-aws-087":"Amazon Redshift","ecc-aws-233":"Amazon Relational Database Service","ecc-aws-441":"AWS AppSync","ecc-aws-419":"Amazon Kinesis","ecc-aws-104":"Amazon CloudFront","ecc-aws-013":"Amazon Elastic Load Balancing","ecc-aws-547":"Amazon Relational Database Service","ecc-aws-308":"Amazon S3 Glacier","ecc-aws-234":"Amazon Relational Database Service","ecc-aws-100":"AWS Account","ecc-aws-238":"Amazon Relational Database Service","ecc-aws-044":"Amazon S3","ecc-aws-377":"Amazon EC2","ecc-aws-382":"Amazon Virtual Private Cloud","ecc-aws-448":"Amazon Managed Workflows for Apache Airflow","ecc-aws-003":"Amazon Virtual Private Cloud","ecc-aws-423":"Amazon CloudWatch","ecc-aws-272":"Amazon ElastiCache","ecc-aws-244":"Amazon Relational Database Service","ecc-aws-168":"Amazon EC2","ecc-aws-174":"Amazon Relational Database Service","ecc-aws-149":"Amazon Relational Database Service","ecc-aws-152":"Amazon Elastic Load Balancing","ecc-aws-115":"AWS Certificate Manager","ecc-aws-364":"Amazon EC2 Auto Scaling","ecc-aws-137":"Amazon Elastic Load Balancing","ecc-aws-253":"AWS Glue","ecc-aws-246":"AWS Transit Gateway","ecc-aws-094":"AWS Account","ecc-aws-201":"Amazon Relational Database Service","ecc-aws-260":"Amazon EMR","ecc-aws-462":"AWS Lambda","ecc-aws-045":"AWS Account","ecc-aws-316":"Amazon Relational Database Service","ecc-aws-133":"AWS Account","ecc-aws-145":"AWS Account","ecc-aws-262":"Amazon Virtual Private Cloud","ecc-aws-158":"Amazon Relational Database Service","ecc-aws-092":"Amazon EC2","ecc-aws-403":"Amazon Elastic Container Service","ecc-aws-528":"AWS Certificate Manager","ecc-aws-222":"Amazon EC2","ecc-aws-446":"Amazon Managed Workflows for Apache Airflow","ecc-aws-209":"Amazon Relational Database Service","ecc-aws-223":"Amazon EC2","ecc-aws-055":"AWS CloudTrail","ecc-aws-015":"AWS Account","ecc-aws-191":"Amazon Elastic File System","ecc-aws-059":"AWS Config","ecc-aws-289":"Amazon EC2 Auto Scaling","ecc-aws-409":"Amazon EMR","ecc-aws-268":"Amazon ElastiCache","ecc-aws-334":"Amazon Kinesis","ecc-aws-105":"Amazon Kinesis","ecc-aws-461":"AWS Lambda","ecc-aws-157":"Amazon Relational Database Service","ecc-aws-540":"AWS Glue","ecc-aws-020":"Amazon EC2","ecc-aws-028":"Amazon EC2","ecc-aws-546":"Amazon Kinesis","ecc-aws-287":"Amazon EC2 Auto Scaling","ecc-aws-056":"AWS Identity and Access Management","ecc-aws-469":"Amazon Elastic Load Balancing","ecc-aws-079":"AWS Account","ecc-aws-281":"Amazon EC2 Auto Scaling","ecc-aws-460":"AWS Lambda","ecc-aws-477":"AWS CloudFormation","ecc-aws-388":"AWS Transit Gateway","ecc-aws-070":"Amazon EC2","ecc-aws-057":"Amazon EC2","ecc-aws-453":"Amazon ElastiCache","ecc-aws-307":"Amazon Relational Database Service","ecc-aws-473":"Amazon Elastic Load Balancing","ecc-aws-113":"AWS Identity and Access Management","ecc-aws-048":"AWS Account","ecc-aws-408":"Amazon Elastic Load Balancing","ecc-aws-495":"Amazon Elastic Container Service","ecc-aws-420":"AWS Key Management Service","ecc-aws-479":"Amazon CloudWatch","ecc-aws-035":"Amazon EC2","ecc-aws-577":"Amazon EC2","ecc-aws-258":"Amazon EMR","ecc-aws-138":"AWS Account","ecc-aws-454":"Amazon ElastiCache","ecc-aws-095":"AWS Account","ecc-aws-167":"Amazon EC2","ecc-aws-429":"Amazon Redshift","ecc-aws-413":"Amazon S3 Glacier","ecc-aws-436":"Amazon Kinesis","ecc-aws-108":"Amazon CloudFront","ecc-aws-007":"Amazon Relational Database Service","ecc-aws-074":"Amazon OpenSearch Service","ecc-aws-224":"Amazon EC2","ecc-aws-226":"Amazon Elastic Kubernetes Service","ecc-aws-193":"Amazon Elastic Load Balancing","ecc-aws-199":"Amazon Relational Database Service","ecc-aws-328":"Amazon Elastic Block Store","ecc-aws-054":"AWS Identity and Access Management","ecc-aws-476":"AWS CloudFormation","ecc-aws-445":"Amazon Managed Workflows for Apache Airflow","ecc-aws-135":"Amazon Elastic Load Balancing","ecc-aws-405":"Amazon Elastic File System","ecc-aws-107":"AWS Certificate Manager","ecc-aws-067":"AWS Account","ecc-aws-254":"AWS Glue","ecc-aws-273":"Amazon DocumentDB","ecc-aws-264":"Amazon ElastiCache","ecc-aws-433":"Amazon MQ","ecc-aws-203":"Amazon Relational Database Service","ecc-aws-538":"Amazon CloudFront","ecc-aws-176":"Amazon Relational Database Service","ecc-aws-011":"Amazon Elastic Load Balancing","ecc-aws-482":"AWS CodeBuild","ecc-aws-544":"AWS CloudTrail","ecc-aws-444":"Amazon Managed Workflows for Apache Airflow","ecc-aws-146":"Amazon Virtual Private Cloud","ecc-aws-504":"Amazon Relational Database Service","ecc-aws-112":"Amazon S3","ecc-aws-159":"Amazon Relational Database Service","ecc-aws-425":"Amazon Managed Workflows for Apache Airflow","ecc-aws-185":"Amazon EC2","ecc-aws-417":"Amazon Managed Streaming for Apache Kafka","ecc-aws-128":"Amazon Route 53","ecc-aws-399":"AWS CodeBuild","ecc-aws-195":"Amazon Elastic Load Balancing","ecc-aws-507":"Amazon Simple Notification Service","ecc-aws-131":"Amazon EC2","ecc-aws-285":"AWS X-Ray","ecc-aws-154":"Amazon OpenSearch Service","ecc-aws-255":"AWS Glue","ecc-aws-496":"Amazon Elastic Container Service","ecc-aws-179":"Amazon CloudFront","ecc-aws-293":"AWS Backup","ecc-aws-198":"Amazon OpenSearch Service","ecc-aws-205":"Amazon Relational Database Service","ecc-aws-016":"AWS Account","ecc-aws-030":"Amazon EC2","ecc-aws-006":"Amazon Relational Database Service","ecc-aws-142":"Amazon S3","ecc-aws-114":"Amazon Elastic Kubernetes Service","ecc-aws-318":"Amazon Relational Database Service","ecc-aws-132":"Amazon EC2","ecc-aws-304":"Amazon EventBridge","ecc-aws-387":"Amazon Virtual Private Cloud","ecc-aws-368":"Amazon FSx","ecc-aws-576":"Amazon EC2","ecc-aws-194":"Amazon Elastic Load Balancing","ecc-aws-394":"Amazon AppFlow","ecc-aws-218":"AWS Secrets Manager","ecc-aws-082":"AWS Account","ecc-aws-275":"Amazon Relational Database Service","ecc-aws-117":"Amazon API Gateway","ecc-aws-005":"Amazon Relational Database Service","ecc-aws-034":"Amazon EC2","ecc-aws-366":"Amazon FSx","ecc-aws-305":"Amazon Relational Database Service","ecc-aws-183":"Amazon DynamoDB","ecc-aws-510":"Amazon Elastic File System","ecc-aws-063":"Amazon EC2","ecc-aws-292":"AWS Elastic Beanstalk","ecc-aws-127":"Amazon Relational Database Service","ecc-aws-148":"Amazon S3","ecc-aws-390":"Amazon Virtual Private Cloud","ecc-aws-099":"AWS Account","ecc-aws-207":"Amazon Relational Database Service","ecc-aws-488":"Amazon CloudWatch","ecc-aws-398":"AWS CloudTrail","ecc-aws-217":"Amazon Redshift","ecc-aws-026":"Amazon Relational Database Service","ecc-aws-391":"Amazon Virtual Private Cloud","ecc-aws-421":"AWS Lambda","ecc-aws-177":"Amazon API Gateway","ecc-aws-341":"Amazon SageMaker","ecc-aws-228":"Amazon Elastic Container Registry","ecc-aws-467":"Amazon FSx","ecc-aws-438":"Amazon QLDB","ecc-aws-440":"AWS AppSync","ecc-aws-571":"Amazon Relational Database Service","ecc-aws-276":"Amazon Relational Database Service","ecc-aws-344":"Amazon Route 53","ecc-aws-319":"Amazon Relational Database Service","ecc-aws-081":"AWS Account","ecc-aws-380":"Amazon EC2","ecc-aws-486":"AWS CodeDeploy","ecc-aws-520":"Amazon EC2 Auto Scaling","ecc-aws-271":"Amazon ElastiCache","ecc-aws-309":"AWS Config","ecc-aws-084":"AWS CloudTrail","ecc-aws-023":"Amazon Elastic Load Balancing","ecc-aws-259":"Amazon EMR","ecc-aws-392":"Amazon Virtual Private Cloud","ecc-aws-475":"Amazon Elastic Load Balancing","ecc-aws-278":"AWS Identity and Access Management Access Analyzer","ecc-aws-360":"Amazon Elastic Container Service","ecc-aws-294":"AWS Elastic Beanstalk","ecc-aws-022":"Amazon Elastic Block Store","ecc-aws-150":"Amazon API Gateway","ecc-aws-121":"Amazon EC2","ecc-aws-522":"Amazon Elastic Container Service","ecc-aws-284":"Amazon EC2 Auto Scaling","ecc-aws-303":"AWS CloudTrail","ecc-aws-426":"Amazon QLDB","ecc-aws-324":"Amazon Relational Database Service","ecc-aws-524":"AWS Web Application Firewall","ecc-aws-098":"AWS Account","ecc-aws-033":"Amazon EC2","ecc-aws-197":"Amazon OpenSearch Service","ecc-aws-069":"Amazon S3","ecc-aws-068":"AWS CloudTrail","ecc-aws-155":"Amazon OpenSearch Service","ecc-aws-039":"Amazon EC2","ecc-aws-430":"Amazon SageMaker","ecc-aws-527":"AWS Web Application Firewall","ecc-aws-513":"AWS Certificate Manager","ecc-aws-358":"AWS CloudTrail","ecc-aws-478":"Amazon CloudFront","ecc-aws-002":"AWS Identity and Access Management","ecc-aws-464":"Amazon Elastic Container Service","ecc-aws-037":"Amazon EC2","ecc-aws-500":"AWS Lambda","ecc-aws-545":"AWS Step Functions","ecc-aws-181":"AWS Database Migration Service","ecc-aws-279":"Amazon ElastiCache","ecc-aws-093":"Amazon SageMaker","ecc-aws-164":"Amazon Redshift","ecc-aws-354":"Amazon Redshift","ecc-aws-352":"Amazon Simple Notification Service","ecc-aws-085":"AWS Lambda","ecc-aws-021":"Amazon Elastic Block Store","ecc-aws-215":"Amazon Redshift","ecc-aws-102":"Amazon SageMaker","ecc-aws-362":"Amazon Managed Workflows for Apache Airflow","ecc-aws-365":"AWS Glue","ecc-aws-160":"Amazon Relational Database Service","ecc-aws-075":"Amazon OpenSearch Service","ecc-aws-076":"Amazon Elastic Block Store","ecc-aws-029":"Amazon EC2","ecc-aws-338":"Amazon SageMaker","ecc-aws-089":"AWS CodeBuild","ecc-aws-236":"Amazon Relational Database Service","ecc-aws-343":"Amazon MQ","ecc-aws-116":"Amazon API Gateway","ecc-aws-165":"Amazon Elastic Container Service","ecc-aws-270":"Amazon ElastiCache","ecc-aws-326":"Amazon Elastic Block Store","ecc-aws-372":"Amazon WorkSpaces Family","ecc-aws-336":"Amazon SageMaker","ecc-aws-536":"AWS Lambda","ecc-aws-512":"Amazon Elastic Load Balancing","ecc-aws-367":"AWS Directory","ecc-aws-282":"Amazon OpenSearch Service","ecc-aws-452":"AWS Elastic Beanstalk","ecc-aws-348":"Amazon Managed Streaming for Apache Kafka","ecc-aws-019":"AWS Account","ecc-aws-301":"Amazon Simple Queue Service","ecc-aws-450":"AWS Elastic Beanstalk","ecc-aws-024":"Amazon Simple Queue Service","ecc-aws-097":"AWS Account","ecc-aws-103":"Amazon CloudFront","ecc-aws-424":"Amazon MQ","ecc-aws-043":"Amazon S3","ecc-aws-051":"AWS Account","ecc-aws-080":"AWS Account","ecc-aws-468":"Amazon FSx","ecc-aws-434":"Amazon MQ","ecc-aws-331":"Amazon WorkSpaces Family","ecc-aws-487":"AWS CodePipeline","ecc-aws-484":"AWS CodeDeploy","ecc-aws-389":"AWS Transit Gateway","ecc-aws-071":"AWS CodeBuild","ecc-aws-410":"Amazon OpenSearch Service","ecc-aws-047":"AWS Account","ecc-aws-532":"AWS Certificate Manager","ecc-aws-192":"AWS Elastic Beanstalk","ecc-aws-220":"AWS Secrets Manager","ecc-aws-141":"AWS Identity and Access Management","ecc-aws-533":"Amazon EC2","ecc-aws-018":"AWS Identity and Access Management","ecc-aws-470":"Amazon API Gateway","ecc-aws-346":"Amazon Route 53","ecc-aws-239":"Amazon Relational Database Service","ecc-aws-060":"AWS CloudTrail","ecc-aws-376":"Amazon API Gateway","ecc-aws-232":"Amazon Relational Database Service","ecc-aws-009":"AWS Identity and Access Management","ecc-aws-001":"AWS Identity and Access Management","ecc-aws-379":"Amazon Elastic Block Store","ecc-aws-283":"Amazon OpenSearch Service","ecc-aws-216":"Amazon Redshift","ecc-aws-553":"Amazon Elastic Load Balancing","ecc-aws-541":"AWS Glue","ecc-aws-480":"AWS CodeBuild","ecc-aws-237":"Amazon Relational Database Service","ecc-aws-353":"Amazon Redshift","ecc-aws-211":"Amazon Relational Database Service","ecc-aws-422":"Amazon Lightsail","ecc-aws-375":"Amazon WorkSpaces Family","ecc-aws-542":"AWS Glue","ecc-aws-163":"Amazon Relational Database Service","ecc-aws-349":"Amazon Route 53","ecc-aws-526":"AWS Web Application Firewall","ecc-aws-248":"Amazon API Gateway","ecc-aws-345":"Amazon MQ","ecc-aws-330":"Amazon Relational Database Service","ecc-aws-297":"AWS Elastic Beanstalk","ecc-aws-213":"Amazon Relational Database Service","ecc-aws-327":"Amazon Elastic Block Store","ecc-aws-072":"Amazon EC2 Auto Scaling","ecc-aws-202":"Amazon Relational Database Service","ecc-aws-010":"Amazon Elastic Load Balancing","ecc-aws-299":"Amazon CloudFront","ecc-aws-525":"AWS Web Application Firewall","ecc-aws-178":"Amazon API Gateway","ecc-aws-315":"Amazon Relational Database Service","ecc-aws-369":"Amazon WorkSpaces Family","ecc-aws-472":"Amazon EC2 Auto Scaling","ecc-aws-210":"Amazon Relational Database Service","ecc-aws-351":"Amazon Relational Database Service","ecc-aws-130":"Amazon Elastic Load Balancing","ecc-aws-200":"Amazon Relational Database Service","ecc-aws-252":"AWS Glue","ecc-aws-111":"Amazon Elastic Load Balancing","ecc-aws-458":"AWS Lambda","ecc-aws-321":"Amazon Relational Database Service","ecc-aws-123":"Amazon Elastic File System","ecc-aws-517":"Amazon S3","ecc-aws-126":"Amazon Redshift","ecc-aws-466":"Amazon FSx","ecc-aws-396":"AWS CloudFormation","ecc-aws-381":"Amazon EC2","ecc-aws-241":"Amazon Relational Database Service","ecc-aws-357":"Amazon Route 53","ecc-aws-088":"Amazon S3","ecc-aws-242":"Amazon Relational Database Service","ecc-aws-212":"Amazon Relational Database Service","ecc-aws-077":"AWS Account","ecc-aws-339":"Amazon MQ","ecc-aws-385":"Amazon Virtual Private Cloud","ecc-aws-515":"AWS Security Hub","ecc-aws-173":"Amazon EC2","ecc-aws-110":"Amazon Elastic Container Service","ecc-aws-397":"Amazon CloudFront","ecc-aws-065":"Amazon CloudFront","ecc-aws-508":"Amazon Managed Workflows for Apache Airflow","ecc-aws-140":"AWS Identity and Access Management","ecc-aws-206":"Amazon Relational Database Service","ecc-aws-416":"AWS Identity and Access Management","ecc-aws-463":"Amazon S3","ecc-aws-384":"Amazon Virtual Private Cloud","ecc-aws-503":"Amazon Relational Database Service","ecc-aws-516":"Amazon S3","ecc-aws-161":"Amazon Relational Database Service","ecc-aws-359":"Amazon API Gateway","ecc-aws-373":"Amazon WorkSpaces Family","ecc-aws-509":"Amazon DynamoDB Accelerator","ecc-aws-575":"Amazon Elastic Block Store","ecc-aws-086":"AWS Lambda","ecc-aws-329":"Amazon EC2","ecc-aws-073":"Amazon EC2","ecc-aws-523":"AWS Key Management Service","ecc-aws-261":"Amazon Virtual Private Cloud","ecc-aws-274":"Amazon Relational Database Service","ecc-aws-184":"Amazon DynamoDB Accelerator","ecc-aws-491":"AWS Transit Gateway","ecc-aws-393":"AWS Certificate Manager","ecc-aws-411":"Amazon FSx","ecc-aws-027":"Amazon EC2","ecc-aws-374":"AWS CloudTrail","ecc-aws-256":"AWS Glue","ecc-aws-291":"AWS Backup","ecc-aws-494":"Amazon Elastic Container Service","ecc-aws-497":"Amazon Elastic Kubernetes Service","ecc-aws-064":"Amazon EC2","ecc-aws-320":"Amazon Relational Database Service","ecc-aws-449":"Amazon Redshift","ecc-aws-172":"Amazon EC2","ecc-aws-231":"Amazon Relational Database Service","ecc-aws-535":"Amazon Elastic Load Balancing","ecc-aws-240":"Amazon Relational Database Service","ecc-aws-249":"Amazon API Gateway","ecc-aws-078":"AWS Account","ecc-aws-457":"AWS Glue","ecc-aws-404":"Amazon Elastic Kubernetes Service","ecc-aws-243":"Amazon Relational Database Service","ecc-aws-046":"AWS Account","ecc-aws-505":"Amazon Redshift","ecc-aws-247":"AWS Transit Gateway","ecc-aws-306":"Amazon Relational Database Service","ecc-aws-402":"AWS Database Migration Service","ecc-aws-518":"Amazon S3","ecc-aws-442":"AWS AppSync","ecc-aws-041":"Amazon Relational Database Service","ecc-k8s-016":"Pod","ecc-k8s-067":"Pod","ecc-k8s-081":"Pod","ecc-k8s-062":"Pod","ecc-k8s-044":"Pod","ecc-k8s-049":"Pod","ecc-k8s-024":"Pod","ecc-k8s-032":"Pod","ecc-k8s-038":"Pod","ecc-k8s-002":"Pod","ecc-k8s-013":"Pod","ecc-k8s-070":"Pod","ecc-k8s-092":"ConfigMap","ecc-k8s-012":"Pod","ecc-k8s-061":"Pod","ecc-k8s-008":"Pod","ecc-k8s-030":"Pod","ecc-k8s-005":"Pod","ecc-k8s-072":"Pod","ecc-k8s-007":"Pod","ecc-k8s-035":"Pod","ecc-k8s-064":"Pod","ecc-k8s-052":"Deployment","ecc-k8s-066":"Pod","ecc-k8s-031":"Pod","ecc-k8s-026":"Pod","ecc-k8s-017":"Pod","ecc-k8s-071":"Pod","ecc-k8s-087":"Role","ecc-k8s-059":"Pod","ecc-k8s-023":"Pod","ecc-k8s-060":"Pod","ecc-k8s-079":"Pod","ecc-k8s-053":"Role","ecc-k8s-074":"Pod","ecc-k8s-033":"Pod","ecc-k8s-043":"Pod","ecc-k8s-027":"Pod","ecc-k8s-047":"Role","ecc-k8s-041":"Pod","ecc-k8s-075":"Secret","ecc-k8s-014":"Pod","ecc-k8s-080":"Pod","ecc-k8s-037":"Pod","ecc-k8s-028":"Pod","ecc-k8s-069":"Pod","ecc-k8s-054":"Pod","ecc-k8s-088":"ClusterRole","ecc-k8s-006":"Pod","ecc-k8s-003":"Pod","ecc-k8s-042":"Pod","ecc-k8s-077":"Role","ecc-k8s-040":"Pod","ecc-k8s-021":"Pod","ecc-k8s-068":"Pod","ecc-k8s-010":"Pod","ecc-k8s-051":"ConfigMap","ecc-k8s-063":"Pod","ecc-k8s-019":"Pod","ecc-k8s-022":"Pod","ecc-k8s-025":"Pod","ecc-k8s-058":"ServiceAccount","ecc-k8s-001":"Pod","ecc-k8s-065":"Pod","ecc-k8s-039":"Pod","ecc-k8s-036":"Pod","ecc-k8s-048":"ClusterRole","ecc-k8s-015":"Pod","ecc-k8s-057":"Namespace","ecc-k8s-086":"Pod","ecc-k8s-020":"Pod","ecc-k8s-078":"Pod","ecc-k8s-034":"Pod","ecc-k8s-056":"Pod","ecc-k8s-045":"Pod","ecc-k8s-076":"Pod","ecc-k8s-004":"Pod","ecc-k8s-082":"ClusterRole","ecc-k8s-018":"Pod","ecc-k8s-011":"Pod","ecc-k8s-050":"Pod","ecc-k8s-009":"Pod"} \ No newline at end of file diff --git a/tests/tests_metrics/mock_files/rulesets/caas-settings/RULES_TO_SERVICE_SECTION.json.gz b/tests/tests_metrics/mock_files/rulesets/caas-settings/RULES_TO_SERVICE_SECTION.json.gz new file mode 100644 index 000000000..3dd71bdeb --- /dev/null +++ b/tests/tests_metrics/mock_files/rulesets/caas-settings/RULES_TO_SERVICE_SECTION.json.gz @@ -0,0 +1 @@ +{"ecc-azure-056":"jn3hlyjcd47g022ao6y06m4psv2","ecc-azure-016":"7blj1vt7zwiukxpjt18q66tkw66","ecc-azure-353":"ktaprvvakitd8jeogdq17pq2ex5","ecc-azure-057":"jjc85ca933yww3w9p1kocnmtj2o","ecc-azure-108":"09utvmzdsen57a0accsh154od2p","ecc-azure-038":"bamrmmj4t7y9xtgn4tzs8h85jnz","ecc-azure-310":"7lngbtg9gm9knchaw1fyn8nt6d1","ecc-azure-270":"2kibkndzhqgf2rxy40va66cjf8k","ecc-azure-367":"r91i7whkzow7loouhqfa85c8dlt","ecc-azure-314":"2i6e0smgcl9wk62a55vd7ayegkl","ecc-azure-039":"sghbzqqb46fe01pkd40anfsfo5o","ecc-azure-044":"wrh0u5pwuuekc9hw4hx2wzzhoh6","ecc-azure-042":"hs828d3swccyzx3cndscxqxa33v","ecc-azure-068":"0u9wk5ihzo0c7xj98nibnpybbx0","ecc-azure-137":"783pvetlq7a1mbo0l52f3xonfzg","ecc-azure-163":"hcxfkt9oqtjzpkrqu4pebwtdzl8","ecc-azure-219":"hdl1bx700gotx04svwz7qjdgpay","ecc-azure-299":"32vbu84gq13mn47dsdmhkrcta7c","ecc-azure-043":"6gsm65zamus9zu92djcoshvof8p","ecc-azure-021":"10dh5fp6nvx7rbcp8wj5hp1kx8z","ecc-azure-117":"sgf8vk41fsct8n1rqh5drfn0t2e","ecc-azure-161":"grdfmawdy08paptob3dky3x3vmw","ecc-azure-214":"y8lyjc3g07yaaol66vmw6qv796w","ecc-azure-205":"dw9go4ixlg580tlcuzz7v9ubowx","ecc-azure-331":"ssgsan6relc56ydpyb6031nokdv","ecc-azure-024":"lgh5v6worykb4lwrta3t8740zhs","ecc-azure-224":"frjnj6mzn7vvulh6hvmoiwxvshh","ecc-azure-123":"t9k2zj3bivcg9tfvq456fjth9k9","ecc-azure-176":"75knaljayj6er8bzfos7me57n8w","ecc-azure-122":"kj5fzo8srpqadb1a2lpxecn0w76","ecc-azure-133":"hursc6w5suulzec6smobmch840c","ecc-azure-027":"31nxbfj130qtsrn60nicrc7ohgv","ecc-azure-127":"4g0ukzmpkcp3d399h70p5jj2mef","ecc-azure-146":"h54qezoku3kgo68a4xbhdfocjup","ecc-azure-236":"dqtm4v5541kv54fz4ptoa9dfigx","ecc-azure-232":"l4fg62azk7h5a4eh2dfpxwpl09p","ecc-azure-015":"wl11er1b6sxpwqbvoyh6x73fhtx","ecc-azure-156":"y0udkrp3i1s9oqoxzhiiv9wo6lx","ecc-azure-321":"tvj0ive5mru3wy13comz1tqoeo0","ecc-azure-344":"dskmdh0d4fv9fo8ywip4nwp8t01","ecc-azure-119":"n3y7401f5331gskyowv8kav0srl","ecc-azure-234":"4dpp3kwjyy5zbpvym4d1rjesylu","ecc-azure-053":"w1cfapsa40stjdyg7o9boqzziqt","ecc-azure-126":"xeilxo106o9kn3fqgtsmosbdcgx","ecc-azure-111":"k6px51h06pc0er056djwwr8nod9","ecc-azure-304":"j6oysk2ukyul724332h86dejg77","ecc-azure-162":"zb8f0vv7pprx6slhfrynepmpyhm","ecc-azure-348":"if0v2h0daxwzx0nbl516i3859ia","ecc-azure-220":"g3i231kyxoyciquga8p15ivlsv1","ecc-azure-284":"tupm3te38sppyfw53yqvwm4f9ij","ecc-azure-069":"5uc5v3fw7cotfrhzx3h8x708id6","ecc-azure-014":"uridirg8wem8nm06mymxb3c7nm7","ecc-azure-048":"0pblmwrk3utepiceeixh1bbbf26","ecc-azure-341":"fgc1fj4iz19cjrgqn8aa20q8dho","ecc-azure-110":"7dm0wvej21pqdxq63xurqjohsbj","ecc-azure-328":"egx2n6cj4r9d22j93wubgieyye2","ecc-azure-160":"r5js31e9nyxr0456m2l744kne3a","ecc-azure-128":"j2u3omzgme3typ4id12xpslzezu","ecc-azure-165":"dzfaqz5z92dtlyn3o9z8qokpyn8","ecc-azure-096":"9sluktls1kwhkdlkxq61rucogoy","ecc-azure-150":"m9sdfoeeyx3o95mahjfwnf0vkwa","ecc-azure-020":"d76qzqgtwmbrfykvp17w18lpqnn","ecc-azure-215":"xdira6j8n5f5ffyslbfjlax6s2f","ecc-azure-159":"gn2pif7sets9h4yppzwbevrpxzp","ecc-azure-364":"b2si7hw0xlub4nfjmio2xdzvoor","ecc-azure-030":"az0hnwsjhdqhfl6f3iqqxwslzpw","ecc-azure-239":"u4ef0ydv9pofy4qwyig6ie61lnn","ecc-azure-106":"d6q9k61thvusxo5p7bzkte38hoy","ecc-azure-228":"pygrn9g0zr3rts3z4691j47106q","ecc-azure-045":"as3m6y1eu4tpmenszlm9k63bmhu","ecc-azure-343":"gajaebrx7n511bgisuo4u05opia","ecc-azure-203":"9lz9q1no0n8jxf3q6w53q5sgnlw","ecc-azure-059":"305h4a41apmt9rivwl9r50lt4oc","ecc-azure-290":"mo077sj1vfguy13i12z03qrxhdl","ecc-azure-060":"fhxgieozpkshdrl7yndkupk2wi5","ecc-azure-009":"53nndsidlxnc3w9oocw8qy9am62","ecc-azure-258":"ioxnaibjdc3lqddo6atjhhc7muy","ecc-azure-033":"vuzwd7war62mm79afp3va2him20","ecc-azure-293":"2pwoe86iq5gyl1q9flxvxxqkprw","ecc-azure-333":"e198r2xps1jb3gd7z8pdmuy7cd2","ecc-azure-170":"sfou6df1i3zfh5u64ur8j59qvvw","ecc-azure-011":"onn3q820tti50znrtj3ghcvqj2i","ecc-azure-148":"u7foks22nqldm1qnwwcq4uqz0tr","ecc-azure-180":"hz385b2qkx2t7e1fbuk8ngx21x9","ecc-azure-158":"fvtydbmkrwm9521x5x7hu0sot8n","ecc-azure-283":"r4sm8vfy6f55hxo1svyxaq8qztn","ecc-azure-216":"3hapib1kle3z3yj1c73vg6qgi9n","ecc-azure-144":"1kglpqs5oqqdjbtp4wa9ttn40wu","ecc-azure-256":"ndur0cxses3pxjgle9fn15sbv10","ecc-azure-012":"ltcl66gwsk558ufda4g5xrr9elb","ecc-azure-025":"blj7svrlqaswlrwyexnswohe6v5","ecc-azure-121":"59thbdd472ovwslz6ptaq875hga","ecc-azure-197":"q6fw1miye75owwahawcsgg0nbzl","ecc-azure-166":"mbez9b7795ghr21ezkso2jogz5m","ecc-azure-371":"y6xkuyuko38q38ckvioatfhy7q0","ecc-azure-372":"caqhmw8upkj2mfr6sz8vpqn7kmg","ecc-azure-300":"ldbcb7pby9gozfa19dl4d1pzsyk","ecc-azure-207":"ailxkghwvfglhe6ehss7456qnqj","ecc-azure-376":"ns9yc6xibveoqtrdczkpoqg89g6","ecc-azure-098":"hhjwxynx9z34lv71vqj4lky23do","ecc-azure-311":"ncmoirgz21dstwu9tzf4v5dj7lb","ecc-azure-319":"jt5qqbzc6okk464brrcm99r1rpz","ecc-azure-173":"ukov673d1pmkn2antn6kgjig7n0","ecc-azure-026":"awfhxv28o6ji6c3nec8n24dn0fw","ecc-azure-031":"bskcu724e0yceizwhtm9fabldyn","ecc-azure-287":"ia9pt58yqdjvz64sh2mfoa0lhv9","ecc-azure-342":"d6wb9cdz147vh5d9k7u9ib8gfbo","ecc-azure-177":"4mnde3yhqf50h3503l07cclvcsb","ecc-azure-151":"ez0rihrp5ys57n1nqg4af2ypjgx","ecc-azure-149":"gffduhqolj9c4wk0we6cfx7fgdy","ecc-azure-145":"pfzkrznv9mvd8vuva6znljbsezy","ecc-azure-323":"fdv1bv9y7gn6vj6xi64hj3rnpfk","ecc-azure-227":"yrcqci5k1crw63mwz5lrjx8ej8w","ecc-azure-277":"tsdpivw3hs20ag9abc6dbkc7l0w","ecc-azure-171":"9j0wzh77bymxamkkhirj7t49226","ecc-azure-240":"m7oi66ps8jfjne98b9f8ln773ve","ecc-azure-071":"p1p6twu1ph47dnzs4a5gb7ztb75","ecc-azure-288":"5p6x2lusig4uc8hi2bcpzc8l3kr","ecc-azure-318":"omclggt9fvdo49yui1xxficdrco","ecc-azure-257":"k81bpolph9rczyfleqiebbv8pzy","ecc-azure-054":"l2ebqvpjieh7tmypv5iyucv28ie","ecc-azure-182":"hhc1fyopyf0fltr1f692p5904g9","ecc-azure-072":"lg4femascq31gpru6wbb8984h7r","ecc-azure-365":"9nk4dxroxr8oszy8q19gan419y2","ecc-azure-097":"mkct73a7iukmi5z6ugqgnvep34y","ecc-azure-109":"4pw1y8y8tvcp8xkpyg3g0ldseek","ecc-azure-147":"l0xdqjzmkcyyzyp576r6xidqx2p","ecc-azure-302":"k1peqtswhh3fm3proz1kr331pmt","ecc-azure-010":"9oyvum28jc9ogs44vof4gkk2imf","ecc-azure-326":"mb1dojl9oqknj1d51btwo1fx753","ecc-azure-006":"axay76ozronom4fsi07sgper1wn","ecc-azure-132":"mmrqmf1sb87cgi8epbcqh1dy77z","ecc-azure-125":"ynyqw7hjl1yn1d7zcdkbjmfi911","ecc-azure-286":"5m18h7t9upd7xees5qtllh2uusa","ecc-azure-052":"9ro67fqqdo327k6suj7ga8v9agw","ecc-azure-282":"3exbujbs8pllvc74v18nto9yo86","ecc-azure-067":"hzcqpklkg384wgdonp6is5ewp4n","ecc-azure-305":"v7x9lvp6u370shuiesszwfsiiav","ecc-azure-168":"g1n7b6v7cko2tkn8o09bti8dcnb","ecc-azure-139":"tvaloo9lbetcmdtpwzd1632ny5p","ecc-azure-179":"eugwhpe41bm1dsco9qck1tz56eu","ecc-azure-370":"82aw7f9n54pwafmy03s1sssvfqa","ecc-azure-349":"ctxy266x5mcd2s1ju9e3ssnvgtn","ecc-azure-028":"ax2dctmq11j6s3n9hxfnb1zghb5","ecc-azure-141":"sfkl0xjcy9eldnaezy6hhrbepiy","ecc-azure-007":"q266h1hk9vazxsxnkgjp9q3rml7","ecc-azure-356":"4tv5wylv2ject44i86zou31210e","ecc-azure-346":"nrmrjyfscyr5v4f50irgcf8w1h0","ecc-azure-267":"3k8yzyghussaqwt9jm3at06m17d","ecc-azure-327":"so8j62fu5a43o52sny3qlloi4iy","ecc-azure-065":"4py8846m44tblwdsawe3ka9pt7l","ecc-azure-099":"g6b0zkmjka3p7hdngibrmbfj979","ecc-azure-265":"u583k1eqglibhpn658rwclps4kg","ecc-azure-345":"9dxsh5synuefgpsv2wbpec7rskm","ecc-azure-237":"7kjq6ez4j7cllrk4zkdeqwosyk7","ecc-azure-196":"7t45qklo9y2zohzcvxgto255dcz","ecc-azure-094":"u949ptrblsgj11uqtfwaybwiztv","ecc-azure-358":"vgb26vtt0q4p3w2z09cvup4m35s","ecc-azure-295":"mu3wupkhzwnxio05jee2rqwetgl","ecc-azure-281":"ep1rjgeboswke4sd4ji5689hv7r","ecc-azure-357":"5q91rgoyygfjqwxd34sm6uc4p4k","ecc-azure-120":"i1n7lu9i6rzv7a2kwulr4nyn659","ecc-azure-325":"3jajx1mxkepdlvdl4s7omedzvmz","ecc-azure-317":"8o316tewarn5kcvnkl27gjmas0c","ecc-azure-112":"f31vqban0c8ehjqsr1205i4x9he","ecc-azure-005":"rb6h911ztteojh0jzm5tnx1j02q","ecc-azure-167":"6ltfg3lznsgsyuhe90pp257raqg","ecc-azure-374":"c8z1f2di1x6wvqxljwou561qvha","ecc-azure-272":"l8oz9tjyiggmbnvox5o9de426jp","ecc-azure-022":"jnpe4vwwdqg3rr67kf2j01mq6x0","ecc-azure-124":"mvluw09wswruhkl5xmz8fvg5sg0","ecc-azure-275":"t35m23vjkfez1vytb5mpiy9ijsw","ecc-azure-354":"rdr5coy7x2uwoml5hw2ogudg6kf","ecc-azure-095":"uzjgjvlj11b03midy0k4hbwaqra","ecc-azure-055":"fcoqmeqlq0qpqzhimpgjgh4go0g","ecc-azure-036":"ynmtltedk56s6dql61voyzexsnj","ecc-azure-340":"vmpneojvuyokme0bx9h7ifmeh81","ecc-azure-301":"5i1t7jhfwbb9k1s1agxu4hibj6d","ecc-azure-101":"l7jgnq90x4bcqnu14tj0ahzfa71","ecc-azure-201":"4my18plyn9p6800c95vot0igtqt","ecc-azure-064":"p4l9v3wenmyd8ontxevky6kvths","ecc-azure-339":"ebvbwuctv94sx1qhi8xa2jfknjd","ecc-azure-116":"9f9ho6ysy0n1pu4o723shoo36un","ecc-azure-276":"8et86orgdipb1403hrrluzwb0u6","ecc-azure-113":"68n3oos3rhw6ww99dgy1sept8rc","ecc-azure-066":"dlysnl6hgfvkv05zmx7v28jcbgb","ecc-azure-157":"sf3ncvpduvpoa0wzrsno5opl9qj","ecc-azure-102":"25jzypt5qgi70715s6f9jqp7ytu","ecc-azure-032":"q0h2i4deuuqx88vknm4zmhk14n1","ecc-azure-050":"9bs7iwmdgxdc4f5pxlpnopaqiwq","ecc-azure-213":"jpis056pcchsqn3evbs9y6uibv7","ecc-azure-200":"31r46wos8zb23l9eep8ev5lsvq7","ecc-azure-289":"o4gawj1ax928fm9ud9ppb9vunom","ecc-azure-225":"7hjt5tmwvamcs7u7h2u18rx3lma","ecc-azure-241":"nawy641ope601l09o1tgq4ie0xw","ecc-azure-206":"gibt1hdrza2401mbn7iy11r415a","ecc-azure-152":"r45mnblwh2xgr48rqr5yg656r2m","ecc-azure-184":"ybq447hfwkodr6pqzc8p0kc77yz","ecc-azure-329":"tp3wuppi1ajbbqj5n7wiraq1j1c","ecc-azure-023":"eeu0zal2ym9b9r7yin1omi2xfx1","ecc-azure-294":"1tdm33vufzdfac952kvuy4pcdr9","ecc-azure-129":"guao25crv4ep8y1wfr98ocyftcp","ecc-azure-105":"pxyyl9cy36pmfakvcgkwvsbd6i0","ecc-azure-332":"5w6yoredbwx3tkje80pr8mz3o55","ecc-azure-058":"7ealosgm6pdlorb2hjoxqayljua","ecc-azure-379":"ha0qmtiw4qqzm204nsufvdkk18x","ecc-azure-202":"8bziu1z9rbm1w6lkjqqknkvkq9m","ecc-azure-350":"sdlsvgwxw4rs2d1kcyz5lrdbd18","ecc-azure-355":"ipqjoieiwpssdkm9k79jgv9gvrk","ecc-azure-278":"evc0nwbz8wa8kg9f6z3sszmvxi5","ecc-azure-378":"lawpjm4p2iuygiqueecxkofduci","ecc-azure-362":"2mslptz1gh3k6qd3znh86nda3u2","ecc-azure-334":"bfxkda418xp89k0b5abkjogl01y","ecc-azure-142":"xjidu4800sxvn0c6vj1vpb46awq","ecc-azure-306":"nwc51q7sxndqaecliq19ktax7ik","ecc-azure-070":"sb7uaymehbov4mwv4c1333lm3tf","ecc-azure-181":"xv72fg5pz4upg9yv3uph5kafsai","ecc-azure-359":"mctw9kvv4dixj94lmfdyyyflud7","ecc-azure-351":"stepc3s6y85gacxozfxs71nv4u3","ecc-azure-231":"o9w9j98iibe0zekm41dqcstp0mp","ecc-azure-337":"3q7k73vmqljz13v3ahgg47bs14d","ecc-azure-164":"v23yte2dmxo7gzzhzqphgjgmxut","ecc-azure-155":"oi6t2y34rxvhc4vtsg1p94jql2v","ecc-azure-061":"kkwb4g77d5hmqq6vipyo7bkynry","ecc-azure-004":"2hac210btzyrqo2q18jdhst02ia","ecc-azure-130":"9p7mtn48qwge60bjsa05eoydn20","ecc-azure-373":"zqy9snp4g8rjik12u1321hcab92","ecc-azure-238":"mpezsnzj41bd8m0slwby2v5qwqz","ecc-azure-002":"574dm837606eqg6h31ublobzptm","ecc-azure-199":"jt61j1mvl28oh7rubqwaz7ts76k","ecc-azure-298":"qssm8k2u6c4n9glqy2wc1owpes4","ecc-azure-046":"6e4q1j1u8rcycu5nabebthimg4q","ecc-azure-217":"2lhksreg6xb42859p5me32lms0c","ecc-azure-291":"m75joxlj9x01rl5qxhzk7bmldb1","ecc-azure-049":"lub80lmctell32knurix4elcdq7","ecc-azure-313":"odp18z29tar55p8cz9rldcrt1he","ecc-azure-204":"cdfpa11x23swnpktldpcesjtidy","ecc-azure-279":"nxgz984x53kuucb1vdma8t5eucm","ecc-azure-235":"tar5skml5wemto6p187fffqa153","ecc-azure-178":"speg06up5qsfebdt0kjps25dwv5","ecc-azure-172":"cznlonhqr1417juwno91gj8z6lj","ecc-azure-226":"9d110d35ezxghga81ryjupouozc","ecc-azure-324":"5su6whh2vo2od3y35wgavmz682t","ecc-azure-143":"rrhknh0eogxie3aukkhs0469g4r","ecc-azure-280":"cuv5za5iid4mxjuxzhio709qhfs","ecc-azure-369":"syo932flqs5khi41nag971t3reh","ecc-azure-100":"v2xzck58m5i6jddf2gb9lt0wn9a","ecc-azure-218":"v9syl02hgddi7fummnlwyjl3zxk","ecc-azure-368":"ctzaxsfmuxsmeu81oshaa1d0mlv","ecc-azure-103":"pmtafiw2ngf0ewzbb8pc5070bbr","ecc-azure-174":"15ifdyxk87ox3oz1y86e9ed4x6z","ecc-azure-347":"yvl63d71mz0ghmddgpt2j7wfs91","ecc-azure-037":"sprj1fzthg4e3cgc3jx6v74wf2e","ecc-azure-222":"txfansxtweiqwn1xeuyjhpxilm3","ecc-azure-131":"65yv3gb57b9medagu39qh4fo042","ecc-azure-296":"utls4pqe0u054el8bv4wbagzwq3","ecc-azure-336":"c93eqzgjmoq6ebkdcyb9pk3uwrd","ecc-azure-013":"a43dvh4cjwyucf6qnp6d0ien4ho","ecc-azure-008":"isb2gcikpzqoe9x6hvg6rrkv8vz","ecc-gcp-246":"ay6wztxi2ngaxp6a55pqsdly6jo","ecc-gcp-401":"ornbhfj4dncjol346blothzrvuf","ecc-gcp-206":"65fbwagur3itvyfcud7augpbkg2","ecc-gcp-065":"6ovg26t0feujzukpdg8wq92wccv","ecc-gcp-272":"0hqjd60wamnovjch7tuwsb8qtlg","ecc-gcp-262":"z3dn0xgbl0m9mqnh8s88ykaeaql","ecc-gcp-310":"m0a8k6r1r0m7keh72xrm81g5nj5","ecc-gcp-202":"kpykr95y618sk4h17543j7nvjeg","ecc-gcp-294":"6sf55480pd6m009k8s23g5gdgwl","ecc-gcp-076":"dgwafhtge8ur2by7srac2tg9cd7","ecc-gcp-281":"qo5gu0o7qeb6zezz1n8xfuh7g6g","ecc-gcp-123":"u9cyszgmxervkquy0n8mjbj34m8","ecc-gcp-127":"tkym91l74wpz3ecr69n484sq02p","ecc-gcp-311":"ow6wt1ja5yozgk01ccicxrf86da","ecc-gcp-214":"vk7b8pt110bsoyc6gqgdc5no0lb","ecc-gcp-068":"1urthf700kzwwdcr2hhdfuzybs5","ecc-gcp-268":"fogx22hg8x9hobsvvvw2pu87il8","ecc-gcp-220":"znjgt8evhkz333eypjljzmgx6a6","ecc-gcp-415":"0havcrm8r96ypmapis5vij5j0ry","ecc-gcp-251":"wstueizxzzn13kqr7wp1epsd8y5","ecc-gcp-118":"paaimghu5n35epwwr9nducfww29","ecc-gcp-126":"pp78fupgj8lyyavuddhkoz0dpdl","ecc-gcp-122":"1szjphges35q8fqghn1jbh0uc0z","ecc-gcp-244":"zixk77liqvku0ep6nlh1rabi9we","ecc-gcp-324":"opt1cgtkcnepjfggje6hmdq57fv","ecc-gcp-443":"fddc2061nwfp9eij4h3j52puxcx","ecc-gcp-409":"d7b3yk7cfe535o3vgkicuzwizp8","ecc-gcp-200":"zwu7trlrx9hf7nej6dd61jfo6ft","ecc-gcp-049":"cxepj7w64dm1re1to6durgys999","ecc-gcp-083":"jerk90opfgbrq3icic8rs3dwsvo","ecc-gcp-051":"kxkvep5xaa90le69tn7hd57h7tj","ecc-gcp-167":"029nc30pbz8xs4i4l3wbwwu5uuv","ecc-gcp-022":"va3s7qg26b04pa1o7cwfaloo8j8","ecc-gcp-203":"17wem8km2fnodvvoyxw65gin3s3","ecc-gcp-136":"kjhhbovqjxi584qjpj9v3tc7zow","ecc-gcp-208":"q8xr2chpaofvajn49vjhnxsk07l","ecc-gcp-125":"5al4fx5mt7ttlmfpgkhwmj7bpnl","ecc-gcp-347":"xy3xqecgy6yxc0c8wrufhpt27pr","ecc-gcp-306":"j610voghzibp7fdfpjf4ejmvsno","ecc-gcp-044":"vid281otr3gd57tb7588gko6rgr","ecc-gcp-314":"9g87ug5brchir5ua1czyhe98dk5","ecc-gcp-260":"yj8e44o5uroxj6ki9keflw4yqkj","ecc-gcp-342":"l3mq8lhedgvzshqqsozpqm6famd","ecc-gcp-237":"p9jvh9e07f7iqzjo8qg3xhvdozi","ecc-gcp-072":"g1h6aalghlrasmacazf6es5dkx8","ecc-gcp-256":"fhuh53k955fc0gx1z7qpawujqkm","ecc-gcp-054":"psaqjyflq8yr70qg1s3txun7ksa","ecc-gcp-230":"mxv94xa593x85ucx5q3jp41ga7e","ecc-gcp-277":"05oy41uc16f3ilqxb5i3vjyfm6c","ecc-gcp-008":"m2ri671ajuf9li18xflt9l4bvq7","ecc-gcp-250":"r1x9yeklesg8gg7ckjejf6l9hr8","ecc-gcp-304":"6c52wujwy8rjjg0fjqy0kcdshpe","ecc-gcp-152":"ggfccxtsisc7hkj25o8kuvyv5sa","ecc-gcp-252":"k3xth671ofi3k00b1gupt9snx9f","ecc-gcp-445":"bcaoamsqigsux6hoi9fr9ltwvw0","ecc-gcp-070":"rlypdstuq7p318ikynazmeus7ta","ecc-gcp-035":"5ns6j1kt3nozcfemugal3bt4h25","ecc-gcp-017":"krl1lg0eolc4crf89xdsrx5hb2f","ecc-gcp-141":"ag4vgmtyc9s1gxjpi1rpinxn27c","ecc-gcp-193":"o3l82kuxbj6tm3yxgpioeqoapt1","ecc-gcp-175":"000l5x3tw8vubri6i342dv71nz1","ecc-gcp-199":"0ex9sl5kianrl6d9oq6abpy27b8","ecc-gcp-185":"l7n7h7t39mbgxu8kyp233c223fu","ecc-gcp-131":"cqtyll8auw1vr5oik7ctucgvgd4","ecc-gcp-207":"6pn7ejrwc75uxi8mqcgymqmror5","ecc-gcp-004":"rl8gs63u00e7eqle0whcgjvaif0","ecc-gcp-289":"40fevp9jst3mo2in2lk25ljimm4","ecc-gcp-104":"alr21vk5u59ak0eehyiz0553a6o","ecc-gcp-273":"1czsfoupxqt61epmjtatjgqmg56","ecc-gcp-234":"6ans5lrh5031f20k5oln42k96bq","ecc-gcp-117":"bh6jnsi47r0574i8scym5yig10u","ecc-gcp-309":"peclr28e3e3fas02kvqik0uv6zn","ecc-gcp-103":"0a60lpuzcnjxx13hrpf2nf1zkcn","ecc-gcp-271":"i4lkqvwpgj7yp08ublq1a1yjs64","ecc-gcp-346":"8hslmnt1i2yl1m9bropp8uvckhf","ecc-gcp-020":"bej27eg8betbjozz4a9i0f0m99k","ecc-gcp-432":"0elhvo8roefxuhkfcy3qmwsufg0","ecc-gcp-087":"cg3fvui296oa5evvlrg9mgxicvv","ecc-gcp-448":"suagewgv7xbxm6hya55khayti9e","ecc-gcp-032":"nav3dfmc1ah2fu1lpifbj1cdqjm","ecc-gcp-186":"dyhdoipehrhxouofh9kc3l8ysp1","ecc-gcp-063":"q1rxmbxgaysm2slj7w7ujtwiv0h","ecc-gcp-258":"dyoa34tak89gsuz111i4j2p4clw","ecc-gcp-166":"ocy6qynikt3qi48544axlpgvve6","ecc-gcp-449":"80u3pxbprq547mjya8w6yiyf9b9","ecc-gcp-227":"rdz4jqxq7d1liqh96frddfc8uxc","ecc-gcp-124":"3gb174cjel4y67x60iypfx2m983","ecc-gcp-021":"tu9eexlb2j2s3eoe2rdm6k5lsss","ecc-gcp-173":"cw73qosp5alcjb30kn3i09lh417","ecc-gcp-061":"70e6j8y25bmjtsabwbwb6z1bijv","ecc-gcp-228":"en0mcmqmgkg8lill8vw7wiyg5zc","ecc-gcp-119":"89mhyvc6x7wdl6kdtabqyd1zxws","ecc-gcp-218":"rziv7ir0zjcxh6oaf33vqo9v8ch","ecc-gcp-093":"95r6yo5mlijcdmbieku8op7jcys","ecc-gcp-215":"utggbtlge0oh0k8depobbl2dem8","ecc-gcp-446":"49of7k64unws69acai0cdy1o818","ecc-gcp-025":"v7lrnifpi4mk93rfs5mf8smpnof","ecc-gcp-001":"cjhxzwr2zccadzkzpoyfbnixj3n","ecc-gcp-023":"hw01rtxoje4n3q6ob7xccldcl20","ecc-gcp-253":"fzmhkhj6tv5ft81jsix2pyyqjz1","ecc-gcp-288":"szqmjss7r6ma6xsmamkldz1ym8o","ecc-gcp-184":"ritrtyzcxa4z5wn5jly558epytw","ecc-gcp-172":"97j20w7deyyup54hg31roj9k661","ecc-gcp-092":"v05gyxi4x9af1ytp60wziqw03e0","ecc-gcp-067":"ihrhw8q5oy8vsojlc5exypeprzb","ecc-gcp-101":"a2l1cup4qow0e71bh0ai3jequwm","ecc-gcp-036":"y9o3irxpao2q9xu8qv0geic7ptq","ecc-gcp-153":"eylyg0emjwgjupitoqtafhdqhdt","ecc-gcp-019":"o4kygnnb9qs5wqici7wmfi2ib26","ecc-gcp-212":"khgtdmwm60z25m7bjpzssh0pdup","ecc-gcp-050":"nptj5thp1qf91eme2kg1i4awwt0","ecc-gcp-213":"lfzd9cysow34h96yfzw8qtdj7h2","ecc-gcp-229":"3j9mxbfr2mwupkhmwysr9wd08ly","ecc-gcp-130":"5wxj4e3qh94kbv6uc3ov0qn4lo6","ecc-gcp-006":"aanwy9r2zja78xegz1y7jwqxl8f","ecc-gcp-012":"vw3sa1t1mudbhhvez02bj65j0w3","ecc-gcp-335":"res6b1dkuw7lofdqxsup5r24l0s","ecc-gcp-307":"ax5bx5svad931y4qafs876p1ejw","ecc-gcp-030":"niqpmch9d068d5prlov8zqlws05","ecc-gcp-057":"e3tz2w6mtgukvrl26kjj8jtxnp2","ecc-gcp-450":"01nr0jdusu3jya53zkzvncoc29o","ecc-gcp-015":"un8hi6zyxszcul4uee1vckuy4hk","ecc-gcp-266":"sbocx4fghiju8yvot9ckmkuyqtw","ecc-gcp-178":"ow6l9t1eo7m4tykc48kl9vfpizz","ecc-gcp-293":"6xw159r4cr6axjkq2gy6hv9hgem","ecc-gcp-291":"u46i4crh4l3asafdttbm6f1xxtu","ecc-gcp-043":"1y1bplmg6361mmfzu3bygjco91d","ecc-gcp-438":"b13iqgvm564nb88prebeu49ivxy","ecc-gcp-039":"84r1i03zadp45cypokem695jkkh","ecc-gcp-187":"fftyf36wvsgdnaf11hnbqg60uxu","ecc-gcp-340":"mydz84i5nc45aylnu4fer79s2c7","ecc-gcp-177":"173s581lqflizq2spv7yq1u5gtb","ecc-gcp-058":"s4zoawc63za6wr3kzarsl5i4wwu","ecc-gcp-302":"xycrg2nj0ni4n8nr311hra782g1","ecc-gcp-181":"w1ni5tf1jzdla7xznp3vlbzk2i5","ecc-gcp-264":"5vcm50b3gdb3or05cv4z6th60v8","ecc-gcp-082":"yt6msc5hbx5yhtoq5gfwbwkl9uh","ecc-gcp-209":"98so4n1gdqbtu0ix2grrvrhvhxj","ecc-gcp-162":"aypqzp8esncdsun69k98m68hjcq","ecc-gcp-323":"01y6nssu7wwame7hhjzu6ca0di9","ecc-gcp-013":"bfk7kvp69ua8o1ygbluh8x4o36a","ecc-gcp-386":"sibu5w8b4brdjyz425mireqjhdc","ecc-gcp-077":"ouv8r3lntnbvvp7tly6l04r5kxz","ecc-gcp-194":"tww67roygr9wqy1ex05z1i7wtrm","ecc-gcp-066":"xnwsdj2fg8t0f0bocixuwscebi1","ecc-gcp-007":"brvqsxog5nxq00owe0klai0li5w","ecc-gcp-143":"zvcl3bch9iqelsu128gubd391lg","ecc-gcp-129":"z3u7964vhzr5i7yyqvv80jbqhda","ecc-gcp-248":"fe0wa2b0rnm25neri2kz4q6l9mi","ecc-gcp-046":"hbrtquk0jn4t23mtpnctalmo3y6","ecc-gcp-091":"ysd6iv46iymr959ebp1h5oi7krn","ecc-gcp-088":"y8zzle4xfbqw8gr70xvbcykmh5s","ecc-gcp-205":"h4449cuzblqsd6fi3cwssfiq6gi","ecc-gcp-027":"brtofesho36x1g3p8vpo6f43eqb","ecc-gcp-038":"n8qeq37iy1riv6vxz8tdcsx0421","ecc-gcp-138":"jurwjklwx4b7le96fuw1rrlz563","ecc-gcp-292":"zoxpagexf3l2rdfzp5qdoz4pht6","ecc-gcp-295":"eonjasjv72y0smar5gvoe83ja2p","ecc-gcp-241":"2yq4c3mv1n67qau5482gbc3e6w0","ecc-gcp-010":"zfewcp9q36qs4fgcddbw5soka4k","ecc-gcp-257":"bx6vaoss077ssf4rb89uqm4qrur","ecc-gcp-221":"38sfq02tf6mf821lg7skdjjaay1","ecc-gcp-005":"94bgxexbbn9b7iqw8bd5r998kq9","ecc-gcp-120":"5s57aihdol3soy4gna88i2dixun","ecc-gcp-300":"51e5297ezjgmccxp3nep96vcd75","ecc-gcp-283":"gcg7dd5sy18mzb4vhhboptp80jc","ecc-gcp-011":"4ki5b4keg57popfwm3s57syqt65","ecc-gcp-265":"ck4tziu49scge26k5il59dqqldu","ecc-gcp-121":"7iz38t07dvvqgh30uicl9eus5up","ecc-gcp-245":"8d8w2g2d6awq9iy8z697erxlx8u","ecc-gcp-144":"6cv3no8vf5mgsd2zky5k1wwwjtz","ecc-gcp-009":"au587in1hefhctbvtihpnrpmuaj","ecc-gcp-016":"xvp4wbjl6u2cmpaj8kyhdq2ees4","ecc-gcp-059":"sxia7j8x8umd6xgia9z4wtfsr7l","ecc-gcp-337":"5byou8zwcqwc3d8ulmize5so825","ecc-gcp-111":"kvpl00tek98zmige9xwgdihrzcr","ecc-gcp-128":"443x8kgjc9vew6unqtvxavb3ag4","ecc-gcp-263":"j07vrakti0z0twv5fader2xqb6l","ecc-gcp-031":"bbbsu6cn2cde3ejumbb1u7uvrvi","ecc-gcp-313":"gphidmrnqpvmccxyity75l5uf1i","ecc-gcp-163":"4gy6v5gzdhhcp9r9rjqrvnd6ngn","ecc-gcp-442":"2fmdovdgf7s91hi9r86d8w646q0","ecc-gcp-334":"ve7f07g6sw2dxmq92g5dj2w6dix","ecc-gcp-060":"2yxfypew4ld4motvuc50nur3s8m","ecc-gcp-191":"k5rf8vs307ofzsvuxp6fggsqepd","ecc-gcp-204":"c2ys5u9f6rcgyc6kouxxy8r9ied","ecc-gcp-316":"m1jobpenwryx8mbwf3nn3c4b3zr","ecc-gcp-198":"mi3610edz5ibbfth4iqztlm08xz","ecc-gcp-029":"77wyu9ppp46raavepspb0mn3eyq","ecc-gcp-028":"zlysdcel3tdaai78kx04w203e43","ecc-gcp-047":"d6nqj33b0arb45u7ywh2l3qfddt","ecc-gcp-276":"16anwnpvcx1sewd0jb6lv8xk5os","ecc-gcp-444":"ixave6f4c64tex4k49nw1t8db06","ecc-gcp-232":"yugyguly9u7toik6s909f1g9qog","ecc-gcp-318":"66rhtr41fo8hwousn1izjoyupu3","ecc-gcp-287":"9emtve559rfxl6t6crl77dbel4o","ecc-gcp-195":"6wfovw553usb7y1mgzdtki4wfzc","ecc-gcp-183":"jlr2rw4ntqm0a6l06t5ays87lb6","ecc-gcp-165":"itloc5nbknpflyjticxdgynsdzb","ecc-gcp-037":"j0kupi3u8a2bq4iprg9bv0qfy4f","ecc-gcp-299":"78orhaqa7c6onn1jyfhaxrg8rnd","ecc-gcp-387":"zxixhhes748qx51vgw4zixp0gat","ecc-gcp-151":"ito04h9uqlh2fz6vywp5xfpikrq","ecc-gcp-451":"9siedng2z9n9jdr11qsmu5tr7t3","ecc-gcp-201":"ambyv2zqsdjnkrfj2gquweznnmc","ecc-gcp-385":"40ob9vetqtwxpubzqpq82ta8lyu","ecc-gcp-142":"1f84swucfukjur0t9cco1tgqo48","ecc-gcp-113":"xa5hcuyhlfroifey55retie67v9","ecc-gcp-024":"lbrh1s8qbkzepm9mqc00cfkvrvy","ecc-gcp-040":"z6vo26i7c74v2hmzj3sylyitoch","ecc-gcp-412":"2fpsah6exu6a34dfmxh5kx4xuji","ecc-gcp-107":"qdov8nu8hmyjn5pi8fob4fv0lj8","ecc-gcp-225":"lu4ef8fxwqjpzulry0dmmrmqup9","ecc-gcp-217":"y1jj6e3hlrkvffzeyp01yjpg7nb","ecc-gcp-169":"f0u39zfkqmlenvu2typtqrbdt6h","ecc-gcp-231":"vjo6ah8uc3csxbau7mjkhumwylq","ecc-gcp-192":"hyyvufurhvtnl038f6f9rx6rq0w","ecc-gcp-280":"1kx61b3nsg8ntgmmjmwth8p88k4","ecc-gcp-003":"x2vtztj2wvaxp99zejfvflht96f","ecc-gcp-315":"zdeei373kiypx17zo3y6gakoqi2","ecc-gcp-033":"z3f4rwoe8bnlrifuyvm9e69n8lv","ecc-gcp-452":"mitz05h4hvdt2l6jy3thtei1vi5","ecc-gcp-278":"q03tgsntl1pfah1etg9n1vq1ik6","ecc-gcp-014":"0pbbnw1aryzca3hw9lcbe6bd7hf","ecc-gcp-099":"exxrh2sfqoj0ii3fs8lqw4jlvmz","ecc-gcp-254":"er2sd5mio0m1zgfbud35x7l9d07","ecc-gcp-079":"4djij6ppgpz7sve89v50m86rwps","ecc-gcp-434":"ebxgs6vm3tcnssqevzlpbxqffwh","ecc-gcp-240":"f2yetrmke5jz9ugih4138m63lxm","ecc-gcp-089":"ldgzpraw830uw298onveld8dwhq","ecc-gcp-133":"tqcp28a7vu06g2ag9hsc4r1zaso","ecc-gcp-090":"5i6qbc1baqqwxw42mvgwfli6y4c","ecc-gcp-109":"bys02bnht9ka7hia2v1jgar8yja","ecc-gcp-436":"marwa0wlk6nexgemv6z7ag8wr7j","ecc-gcp-210":"rplf1f3fl77swhsajebd7t7opwr","ecc-gcp-170":"zgwie03ld3lraxcnqgcv6un72cp","ecc-gcp-400":"ho0gy81qv6ybdw6if5h7y8vpsw3","ecc-gcp-134":"qn5vu3vsiyfv1q03om11m9pde7x","ecc-gcp-233":"3iqhtnlxjbd36byykgmp3e4thnc","ecc-gcp-279":"uxt76l19ex2ce87rv3401c3sga7","ecc-gcp-114":"q5milifu5j9j46cbhhwwoel7gsq","ecc-gcp-116":"jjt7eocedcf7adnyetw7h8rurmm","ecc-gcp-305":"4b7hmjif7wpklfo0nilmovsqwga","ecc-gcp-286":"sxtrn0cnuvbo009cw9itvd7d48g","ecc-gcp-447":"1qi9r06c8a1c8cxgz1bifa25vwo","ecc-gcp-180":"eoutotcql8ceb3elkky7qcj1sal","ecc-gcp-171":"38fskj2ht85mfo4dup7bqxbtc2k","ecc-gcp-062":"o6r3p4fjld3okiyqbz7ttikr72v","ecc-gcp-132":"car9ksugt0vwfyzvqhpzg5r6yu2","ecc-gcp-110":"78ob1otxqxu1067blnn88oxb5dm","ecc-gcp-223":"3s2pqnlizttuzay5zaxc8x5t2ue","ecc-gcp-189":"t744392p802ioj6yvnuqkber8oo","ecc-gcp-190":"i9xdmfvchxx1xnc727328i5a1uo","ecc-gcp-115":"vq6wxsg00qyki3gi8ivuj57r7h4","ecc-gcp-176":"usm5bp14p8orr160hw7emvell0w","ecc-gcp-261":"tmbj6sopplcr24f9ra64c9k3cgt","ecc-gcp-053":"6jv527rjqf5x9hfsdq92f5wrxhu","ecc-gcp-303":"nbnag062vkga5g1lwpoa6txs7j4","ecc-gcp-236":"hs5i90tyh4zv25tqo79we1pbmi7","ecc-gcp-247":"l0dzo543y66enpui6837ofg2rl7","ecc-gcp-086":"2e0ygvzv0cjbv700qw1msk8mb16","ecc-gcp-222":"qpmuno9umu20d4dntpanhyd59ru","ecc-gcp-197":"7j3j9s04mbxa2wazgu1pnoonqug","ecc-gcp-048":"mnxpntkfrchdtlq73051a04y0dn","ecc-gcp-274":"r16wjznz0ffij92vkd4k3alf92d","ecc-gcp-150":"2kitemvb6vy3xg5wxzaptufjtae","ecc-gcp-243":"u6qlov5w7eksttjemddqzwj9fmo","ecc-gcp-282":"givo8coy50f8dvq3yfo8ct3ujmf","ecc-gcp-140":"5z5a682tcx158vhncf8ayfykvyu","ecc-gcp-055":"bam44yau1axkmtmgk6y9oj4mixi","ecc-gcp-239":"l1i6leaidirnasoeh5slbr578ip","ecc-gcp-285":"otlnbzak1z2w0t2o9b0jfa2s1uj","ecc-gcp-312":"7c7v90zwcwo9oh5oogmpdv88s8d","ecc-gcp-249":"3fqe2a86uutwxa45w7xjr2oxgso","ecc-gcp-219":"pnq5ka1cummf624ei3f4tsv2m9b","ecc-gcp-018":"fl16n1fcq3eo84nahd35qauvip1","ecc-gcp-188":"2li36kzeixyn6zoju1g9n2u9ppn","ecc-gcp-179":"do72cvphjbtxkql09k73gql21l5","ecc-gcp-042":"e75zff5udtnc1kxx7i9hf7do6o4","ecc-gcp-182":"rxf8drlks0nsmv3ndsydfbggpt0","ecc-gcp-071":"wt9b71thtz9f3bjtbzc2chmfbhb","ecc-gcp-137":"6bz4i8ck2ellhpj9he9oby55jz3","ecc-gcp-298":"6swfe2q0gdyrz2ugb90jw5omou0","ecc-gcp-112":"bnh85zzgf2y5jzv224gpff4k259","ecc-gcp-317":"exxv0vo8sfa4is0xkzw47hnmoox","ecc-gcp-242":"693azbz9gonol8asc17de599mx4","ecc-gcp-034":"2znnme907l50k49pi236fr6hvt2","ecc-gcp-216":"a6twhmhlm2b4tmm1hr7dlbrr5sq","ecc-gcp-453":"yklaoybf2ftzpfanrgwh3sj7vas","ecc-gcp-211":"46r633lf2on7xnna0qgfyn462t9","ecc-gcp-135":"r50nxbaadv7v70nvbgh4bg8gb72","ecc-aws-235":"mp8jl4hw4hu21hjpkozlo5lhbk6","ecc-aws-511":"sn3sndbp9ykbfe6tbrvpmcjo5xq","ecc-aws-190":"oiohazvrchxwx5ovs4ev0zstgvr","ecc-aws-091":"7onv2u73i8j3zygrs4wst3g7j45","ecc-aws-295":"aprh67ol63z3xxv22ajvfejs5xw","ecc-aws-286":"76wbaoteragi5x6crz5tsavzzl9","ecc-aws-529":"nzzaavuevjdg15p85vo1cdrn4rh","ecc-aws-493":"40uezrgsbm3ol49u7isrdjnvq8f","ecc-aws-483":"afggqzt2eoi8s6my6fj7esqp1fm","ecc-aws-383":"wnx5lxvhjh69c87dmyyjiclv6ow","ecc-aws-118":"porwil0k3s3guqrrn1hogfoliov","ecc-aws-435":"89e44p6w8qz0skhn21qu31aue24","ecc-aws-144":"hunizzgabifs4daexridxu4r735","ecc-aws-414":"ldtm2cd12bai4bcnu8i21itc0f5","ecc-aws-156":"8ma3adbsjy58m75xtaxvjt5albz","ecc-aws-363":"i1nsjdzaam4rhrfcgusnl24x9b8","ecc-aws-229":"2ra0w4rqv3j0vlh3n1ub669i9d1","ecc-aws-296":"t8w5fb6v26v06pm8arvcjo1050y","ecc-aws-313":"ura63tmba5w6015m4crq9iicxvn","ecc-aws-378":"y6nbxzqwyl7j6cummyt2ab1hm3q","ecc-aws-302":"hyeh9l2h41za3k4ps2wei3bzmoq","ecc-aws-342":"0zgoa8iuv6anxs5hw3ug509lmew","ecc-aws-187":"cgpck60m1mp8f23jhiub5r3r956","ecc-aws-245":"6o2ks1f1tils9mbbuym0g7es0rx","ecc-aws-101":"slk7dcsiatshgnuq8fhhmunle45","ecc-aws-189":"zzib5k7322iojb1e8rl1659rf1i","ecc-aws-465":"qimu8uioyw4f0psvel1db832p20","ecc-aws-572":"c27dnhdewvrq5sb8o32mx8pbed4","ecc-aws-506":"tlbyfwlcmhszhmw6q077dq15qk1","ecc-aws-214":"mfhmtmam5uazs8o4015ayu5y3io","ecc-aws-153":"jnzbo95kmyy9s4jm7k7idxgr3c6","ecc-aws-083":"hzndgwwv0pyagu076dxay2a6bpb","ecc-aws-122":"so2lwi0x3tfbovmo6ioah9g6tby","ecc-aws-265":"2x0cxc7m96usyk1nyh0qijah90j","ecc-aws-147":"43vetdjz0668f8qanvvgcs5dqk4","ecc-aws-129":"rk50ywmybccnsei8uaxto1txki7","ecc-aws-401":"qo9b34i6jsbmwwyipt6f5bmsyn6","ecc-aws-062":"wrvq234gjmcsz575nbhr1yx5jn6","ecc-aws-250":"6vppuhuabccjoidbybm5bi7jydm","ecc-aws-188":"mmcb52g4lviol45uyafadqjgta3","ecc-aws-004":"et5kb0mhoa3t6mwk3phbhdhqgcy","ecc-aws-332":"om0jqd6oc0v58btc6zrs0tpq7qc","ecc-aws-040":"vmoccn365jpnopq9c07sby49hpl","ecc-aws-347":"cru47cgspyc7ldsseiar71vzm13","ecc-aws-269":"q2fezf0qd26qs6u70nolseqsvv8","ecc-aws-280":"3k6231prtlobcjnh3zig9emh4nt","ecc-aws-534":"cduzwdg51dupe29y72zphnpr1vd","ecc-aws-531":"fpi0nyr62x20w37cxfzqrezxxdl","ecc-aws-471":"wi6i8cleca8qa3g7br82gnnwuk3","ecc-aws-498":"b6nfdzaf1elfkhoh2imw0bh7o57","ecc-aws-447":"ql5w7j4u7kjrq1968ow2b30sf9d","ecc-aws-395":"d6jo10eybjbrghap5e7l3ccn7gw","ecc-aws-548":"u5lkplud63wbycn8dai4yp6eule","ecc-aws-277":"dd2tz1zot4govbhiy5yzwxuguhw","ecc-aws-204":"8oyq1p8xoytw285iia22sbabvd5","ecc-aws-539":"rd019wye31qeo6267peuoq6isnc","ecc-aws-501":"h2yeqjqdf0mvnaa48v5dhfzn7c7","ecc-aws-573":"tdhcm3tfuq5y2k5kusrncsf1rd3","ecc-aws-186":"m5de7gm628om7una5hbflz038pn","ecc-aws-485":"wpj7dc9nphh8zcfhldw5xdikn5z","ecc-aws-263":"gho0r0woirrzxxxkuf4lnll959v","ecc-aws-552":"8bc9lg4nulbfvf21qtkohplqwxw","ecc-aws-032":"nva431n0jz85p406zixfhwigyat","ecc-aws-337":"r6a7n6ck1aqk0fafvv8frmela5u","ecc-aws-439":"1t3kssr7kwz2ukmujbche6dw7wc","ecc-aws-050":"zq3u0phyto58bwpccz49eyqy0h4","ecc-aws-406":"05ohaevcft8v7dz6m4g6ynzlunz","ecc-aws-230":"zyqlqt2e60x3vrhj2v0c1eyxo87","ecc-aws-171":"9epvhd5xwgo1cddzwypmbraop5z","ecc-aws-267":"wnhq6qvm5z97zoqtzr1yu9kfb5v","ecc-aws-031":"7wrqplljm6fy7e2zrh1ippmk07d","ecc-aws-169":"ocd717hlitrdgzyd008g2u8fs82","ecc-aws-451":"s29u7egzn2orj5cp47zjzzr42qu","ecc-aws-208":"3tkhvwl5d6kuc987mnkhcv2bl1z","ecc-aws-038":"0m89csohsv2ibbdvdfjx0vi5ijt","ecc-aws-371":"7ce5to5djpl1w1iwunu98uizymi","ecc-aws-049":"enkgp8gxrkf5d3s8m7f76slfv36","ecc-aws-530":"qy3d48v9gz2jrthw3gxkzhuqu3j","ecc-aws-407":"s1kiho66it82zxp5d9acgjmwjxw","ecc-aws-340":"7zr5emw5topna95maup5t8ea4ni","ecc-aws-310":"x6hmycved13xupzee957ro2wl62","ecc-aws-257":"76rl6lkafucazn9tcrubm1dk84l","ecc-aws-180":"41e2n41jpp69royozeq8eku3q67","ecc-aws-196":"towo4tear2zscgw2qr8vo3kt5ps","ecc-aws-251":"64irvuoxsisbpkrpno7jzr6t92e","ecc-aws-312":"93dpic9wvjrblh0ze9mlyv16g5h","ecc-aws-386":"v655qh31ab12y2tnuffw7pnegjg","ecc-aws-492":"hi5cvzdpiby25rpypmr0m425tk5","ecc-aws-124":"fgfhy13v2cfvodggen4yyjihvkb","ecc-aws-537":"xosd6ijhcthk5ikrz502y51q4ws","ecc-aws-333":"fe6ge73f96n29rt2tp4hii8n4cq","ecc-aws-096":"83e5xrgwkor4za0ebkmmtfp4sex","ecc-aws-170":"dy7i3lt3jmszdzh2ytgwuhfr26i","ecc-aws-481":"1nhtxlja9s2tcsmd69efqsbmr6l","ecc-aws-428":"1er6e2mh2vmoxqcu1kk2npv47z7","ecc-aws-036":"w50zczj0y02u7hddjko6xdvsple","ecc-aws-314":"1k3zloz1u9ut9gz6j1mx6ogkc6t","ecc-aws-017":"78yyb6csk6bvw87gh19ysiyz4jr","ecc-aws-008":"t6wv8w17zuh3mh5ozg09vqqlhsq","ecc-aws-521":"jc7onpim4wqydqumpzxmbh8jupd","ecc-aws-335":"1s9zmss6ici4bz0bwukeodlof30","ecc-aws-014":"o32vm9aw1u9lbdjtq0jcfuco57p","ecc-aws-139":"no99kj87h4si9m67tnq28b5jm2u","ecc-aws-090":"v2jd2q7uoimwlu7h8hhxx6xwdji","ecc-aws-355":"rcf13bt8jw7wnqgnzqd1ljbeaad","ecc-aws-323":"difg9e52ztiqlqmzftfhekvpsow","ecc-aws-432":"psrmqscmw62886xfgq3des82rg2","ecc-aws-490":"po9jbu4dc8ickpgn6qnv99e7z4v","ecc-aws-143":"xqci5s0wvwpwfy0om6g3mbgqxv5","ecc-aws-437":"67uq3ikov091lh79qa26jpqzpru","ecc-aws-455":"cvn5tvxshy874dlz3sq6c8g5f3l","ecc-aws-227":"70hkbeu9le9hkcppbzerzwz4vdd","ecc-aws-443":"169lx9x96fl5yenoohvta7vnbn5","ecc-aws-356":"5fakjz1ve11gb8aave08tfaij2p","ecc-aws-219":"ctjvcn2gdvmt6flbeuokzhndsty","ecc-aws-311":"4x6ugcc2vyeg1r9e7nju6evrfnj","ecc-aws-053":"k9pqkwnthvqjwdyeqyxul65ce2h","ecc-aws-290":"3c42c3lvurx3m70rqsqg1z0u2kk","ecc-aws-106":"0x77xh52vbyqtieqkwv3b5fs0an","ecc-aws-125":"btac3y3d9nebc19tyjiairun324","ecc-aws-162":"o8m29n3krk902lcjcq0hdg54mge","ecc-aws-058":"mzocq846vw1qrpn3xknlyrry4yy","ecc-aws-317":"qselsnbdn6fvftxo6zwesg4jaga","ecc-aws-459":"o7uhk0qctnf6x2q3zy04ifcqpp2","ecc-aws-427":"ekweim15bqcmmr8png18kugbulc","ecc-aws-119":"dunxkiazvt2qd65xlyo3jqrz65h","ecc-aws-325":"cvfgseh6do2srypjld9ynpcb3tw","ecc-aws-514":"9l856db9ahrulj9fjqccduh4sjg","ecc-aws-266":"i4sbq03tejd8wuqtckdw3yurwsd","ecc-aws-474":"rkbfk0enoa6a5tq5pskcfibckfx","ecc-aws-182":"w9oom4qfwd24jvt97u5tpowjher","ecc-aws-175":"tggcm31rordwwvlqiepatb6d2xa","ecc-aws-061":"yfxtvagj9hkxmr23nkl9v31x8o0","ecc-aws-456":"xehe2zz5kmk5op0mj4hpjsiigqi","ecc-aws-489":"t5rcp51r4obu74zps6gnhjuan9b","ecc-aws-042":"c379co0xddvpg19qvcr0kfkkead","ecc-aws-418":"eca8wksil03qkodo0o71akhnlrt","ecc-aws-502":"2254cwk3zbbxhep4c1ndys2pqq1","ecc-aws-560":"f52gfjaocsiuns05brogpc403qp","ecc-aws-543":"vbg8tnnwkfv458w6drs5qc1jf8d","ecc-aws-519":"gwkcmgu13imr969ic4mrd2xltjj","ecc-aws-288":"jl3yb3f63ffdluprmlu4l5ajx2v","ecc-aws-350":"r43izciwgcnr80d07ssaxe5rsqe","ecc-aws-066":"xif3s3ltnowu9o8yxuw35qlc7gz","ecc-aws-370":"c1wsw07wxn4rityzzxxzp35yt73","ecc-aws-136":"wnhyke4x4edv1tu60wr4qbuv2pl","ecc-aws-298":"ssirq5uxzyhvuwaye5rszm0cl4m","ecc-aws-361":"p3qr69qwpxhva88k3hmi0wonr4x","ecc-aws-412":"h2xuly8qp2acfoj5pk80wwcuvva","ecc-aws-025":"rf6hiskf1q8ze65l35mjhpypaap","ecc-aws-300":"y6xgc14kmzh15kg7htnkzftyh7k","ecc-aws-322":"ztqw86os5s0t727mj52bbxq6904","ecc-aws-499":"unzj30a8u3lfactffjre9zog1bh","ecc-aws-431":"j5m0akib34wxttcetwrr79hjl99","ecc-aws-120":"smz1jvuak7awxrz2hkyu1ykvr14","ecc-aws-225":"i7nos227om0o3x3txwd1066z474","ecc-aws-151":"cp5zgohwj2wa1nz05mx5qmblrnb","ecc-aws-109":"gzu9p54rhmuwwc3eohs4pau9y3a","ecc-aws-415":"1k90dk4bu9g6ea07wqbg2c9lj9d","ecc-aws-166":"3qtujt0pya8eo42ahx8prn1fnbw","ecc-aws-012":"5y0gut4bltfcwn7sc9yi81juxx3","ecc-aws-134":"voyxqb41dpk51westpw0ihr3fea","ecc-aws-052":"krx7x8bdmfbowaq68ombvlamfgq","ecc-aws-221":"q3qsbf64i1azqy3168lorxj590b","ecc-aws-400":"ffcn2dvp6tbnbxvbb14y4nttkri","ecc-aws-087":"njf3u0r6vm132vkbmwus4953o8y","ecc-aws-233":"8woyo5bpbqiaadl7l73y79v3ni1","ecc-aws-441":"g6p0qiahhkdpv9xcgup4w2bhc0o","ecc-aws-419":"93roqc0hh4ujq1f38jnb5dpnm9h","ecc-aws-104":"xqy8k2pd6l1ccp4xhzj7kz1n3ev","ecc-aws-013":"65oumof0x9deovrdlc4n7ziy346","ecc-aws-547":"qlovl6wv29mmldv30vpkiasrx1e","ecc-aws-308":"5ikujok7qtu1aog6g74qccdm161","ecc-aws-234":"ehw6ht4lentg8rz7lbezv86a896","ecc-aws-100":"xex3r2dbdbp4sapawrqg085xg1k","ecc-aws-238":"h1zz28gj5bsmzprojnqfqcdttzi","ecc-aws-044":"9vpbwj9knk6a6uti783mbcj79sl","ecc-aws-377":"g8sa110o2vmm0wq10ktraloff5i","ecc-aws-382":"axihwv44j4mof04hv0rsger7v5s","ecc-aws-448":"9be1gmr7k2s0q7xbz3cav7sv0gu","ecc-aws-003":"j3bvz2xie04bntg8jwi9ciknwcv","ecc-aws-423":"6ljw7fwyd11g5s73z2cay1h2sdj","ecc-aws-272":"a3gq86b1sbrtqvje0pycy90kpm2","ecc-aws-244":"abitdfcvque6w6d7j9s2ppfody1","ecc-aws-168":"t3gba7owimer8uqe4q0599jqtet","ecc-aws-174":"g7fim0gd7o7vx2uh7rylj1rrwt7","ecc-aws-149":"rxkt3ys39ygyngd72xl7snp9kky","ecc-aws-152":"4k2u7kr4pp6cxb175dfp4wgrnvo","ecc-aws-115":"2kcu840pbogh4jpto6vulem62zj","ecc-aws-364":"eewn6fy3v9mtwu0ku9h6qfcgb3a","ecc-aws-137":"mu4vlu1ibqe1cfu8zm5999hxbyx","ecc-aws-253":"tj4fj0hfj0fqkhp5dfjyrr08dgk","ecc-aws-246":"qm1jtr2jvxh7f0vh0ap3mjno7f1","ecc-aws-094":"2h5xywymug2x7m6zkbh02ard3tv","ecc-aws-201":"jkn58wfyci4p1isunojamqvx8ce","ecc-aws-260":"37f64tzojqj1546csnxh72xp7tm","ecc-aws-462":"13y5dph3evgf73ntn2lsn18u0qh","ecc-aws-045":"rtek0uny8rur26psjd0nimsvwf5","ecc-aws-316":"tytgjbml96oabh6aqabztfwhpsk","ecc-aws-133":"rf4jdm9s9213v2vpde01ulwxvbk","ecc-aws-145":"opbg9miezviytq0f8n1k807mbxn","ecc-aws-262":"6yb2ldl79358x5xvpg3pjmh5n0a","ecc-aws-158":"gp81dbjqkt71gczuoaehih3qayc","ecc-aws-092":"h566kadrq54swexy2lf9zylpvtm","ecc-aws-403":"03lh5obrcp9098o41yex5qem0n7","ecc-aws-528":"qw8e581a0vrhtb1u35v205b0boj","ecc-aws-222":"m38d7kc1emtr463zc44nkkmj6wk","ecc-aws-446":"2r8htyn82h3kyhfx81mtgvci7nm","ecc-aws-209":"wvkjzesiouta91ca1a61fpxnq0k","ecc-aws-223":"3q02zkkqb68g9ezgsjok61trzf7","ecc-aws-055":"znqb5antrwucssxlfme4b4z5u5h","ecc-aws-015":"l1zhx4w7ub4cl70wm9iwv26x8ff","ecc-aws-191":"1ypz1ndfivtl9gpnykw60go0r8u","ecc-aws-059":"igj85tmqi75nztdhc0ug9v32y6a","ecc-aws-289":"gv1v63zg328bxttrs3s4watsz1j","ecc-aws-409":"s3ebpw8u2vusummseqy52la8jnb","ecc-aws-268":"ik485m52yl7gqniks71m476170u","ecc-aws-334":"24jshxco6zln4fennifu1h37o7c","ecc-aws-105":"ycaclq4w1d434hekvf0jdzzs9uv","ecc-aws-461":"yfpa30gjmn37x6trflgwt7wv02o","ecc-aws-157":"caygswzlpylea5di37ypc1zijsh","ecc-aws-540":"tkczykfjfrx1tqr9upzqtdcxrmp","ecc-aws-020":"nnpyphehgkt2nomlkfky0uslgab","ecc-aws-028":"6ndz8unmfp6g48i59cvzsq5q30q","ecc-aws-546":"qw1hb7919n5z01wppqtqt1a4na7","ecc-aws-287":"qqkhz499qb2mae2f27eg96jj7r4","ecc-aws-056":"30fan62tddf7xbfhoxzs1fyd9kf","ecc-aws-469":"nhlk90jppuzjz0jlxmx0srd2k7z","ecc-aws-079":"kzhl9p0ruagg61fckwi4u0xq2i8","ecc-aws-281":"8mi1l5xagw9p5drrnwe205c5qwx","ecc-aws-460":"lu1ozsgmnqmi1khtzuuv4ua3az3","ecc-aws-477":"9942n2h299cljp7pql9qv6s2sxd","ecc-aws-388":"aryi8agm42n68u4hlmf4vf2xi3r","ecc-aws-070":"gxwmk68wexigvsb0shj4o0odxtf","ecc-aws-057":"hwa214he7izb7ad8re80dgmfpos","ecc-aws-453":"gddsqkxoikriot02h5w5ta2xush","ecc-aws-307":"31h67licdhjvhhxi1z3b8qb8axk","ecc-aws-473":"a3em5vsggure1vgw1xv76oiob7x","ecc-aws-113":"ofmok96y9xn83n52nxflwtxjodp","ecc-aws-048":"6lkwwk9aqa949qc1s74rmasku3g","ecc-aws-408":"3ie88kw98m0wp20eimegj3ayjzj","ecc-aws-495":"tqjmd8otvm6w431nvmz7odr6y85","ecc-aws-420":"ftm9jmox1htu7ig9qe9jkg1p9wo","ecc-aws-479":"g82iau6vpqrd6i26r70kqnc7j6z","ecc-aws-035":"1xhmp1h27ylq6dkzygyrr5yadj5","ecc-aws-577":"5k26z7dnlu5fe3gs5qv7ka7ehfq","ecc-aws-258":"3tlc8o7wgbxxdd8r9ggwdfskfqw","ecc-aws-138":"mokd29hkovffoq7lljz5ol0u8bi","ecc-aws-454":"7bh9qg0quhc2ax4kbe4v351dxpp","ecc-aws-095":"ds8npui74btv44vo9avu5r44e6a","ecc-aws-167":"kacej5v0jvs077v4kpvmhg5u7xi","ecc-aws-429":"rzgoyx4q3syadq2lk04yedno4cv","ecc-aws-413":"grf1j2ohm98oxp58tadq8sag9aw","ecc-aws-436":"e57ya7t1979w70zxr5cnquujghe","ecc-aws-108":"7tjkex4f8rsqx6o2m2opu94dnel","ecc-aws-007":"13fm0q2bo0aiqigdo9t21nddkco","ecc-aws-074":"strrcy7fc3e8sb8cc0nczb7if8d","ecc-aws-224":"0moxg3y53y6si8rabn3pt016kmg","ecc-aws-226":"4gptrv8883j07ech6x2zphirh6a","ecc-aws-193":"tb6hcz4n1a188bdsehr334uzs9d","ecc-aws-199":"bm1ipv1trpv0z89iob9x54qhxic","ecc-aws-328":"8nho78g3z4ufc36fri7g92jgbnx","ecc-aws-054":"92bnw15czwsouh1njkzdk01jd59","ecc-aws-476":"i9qi6cw974bvx9c4earbbinpqob","ecc-aws-445":"gvm6m783a2mqup6ghh2bjc2asw5","ecc-aws-135":"jvjzcafaqyj0y27j3pvvuslg2mk","ecc-aws-405":"gm9sc72bwyjpjkzpcuw3j155d92","ecc-aws-107":"gcpwfkhxxvll5gdhlvoeswraxun","ecc-aws-067":"kz2xm6tav8exncdqh4tbfua2kmy","ecc-aws-254":"8li1g59wnvyacyw57rbwt9igd0p","ecc-aws-273":"gr3hstfxjb3hcs7akey8aw8dfef","ecc-aws-264":"crb8b8je3fixl94yq47c9j57t4r","ecc-aws-433":"l1k8o6dwc4r49cq9r5wf13yg9sh","ecc-aws-203":"qt7hp3xrudc0p04oeqwdq9yq2n7","ecc-aws-538":"vqp1n8eiejmfcg4pfocfjdko5sb","ecc-aws-176":"65vjv3vbvbeyg5cmgc5034481v0","ecc-aws-011":"tj6udkofsuk5mzcnlb5zcgm7o2q","ecc-aws-482":"wfse3kht5yvh8zqhxhlkpm1jmtd","ecc-aws-544":"oh5v34vtjmxf1bjtjh2t9gmagn2","ecc-aws-444":"vp8j0w3s9h96iiaf1blxwbchx34","ecc-aws-146":"ru9tp9azj1yelhe83i3tnqyuman","ecc-aws-504":"v5uorqton1pp4xx5rozo01cxoho","ecc-aws-112":"oi4t36ju8udqqr32gusw01qzp8m","ecc-aws-159":"se1psjp7dw5985dy78zu5xhs0x6","ecc-aws-425":"9lq3o8waa0n4zu1h9v9qa9zgwyu","ecc-aws-185":"ben419975ytq69y59nugba0iqur","ecc-aws-417":"thxzont2wqfh0y6jvqbo1nb6ebb","ecc-aws-128":"5zui83p8tppgifdmmqyuwlglqw9","ecc-aws-399":"hdxl40qj2g0thea6mhp2gqva9wo","ecc-aws-195":"6g3wd9lmrrvzl2m2z1b3inx22bs","ecc-aws-507":"1pqbltrg2osuuzqkrympr0wfhwy","ecc-aws-131":"ncz3uu3jqg0tbhc6hhoikt8ubzb","ecc-aws-285":"f5qwv1pwn4bzw2rfd6xrds8oys9","ecc-aws-154":"s2gb4cuympuxe2kozsiuwotixn3","ecc-aws-255":"x0niguz62383q233dptsygm0vcn","ecc-aws-496":"hf8o0j4mysq8f3ffbinrn3unjs9","ecc-aws-179":"fzins2r3jvza4w5p9la8qhlzui7","ecc-aws-293":"wyu8tw9nfg3tfxs438ip12hp26z","ecc-aws-198":"x6p0jyz21m0yyhp7hjb1j6f3kah","ecc-aws-205":"m32nkcflfwx4yy9bascgjnmvlvn","ecc-aws-016":"12tsjmz7xo7ghkzkbtli4l1q1j6","ecc-aws-030":"hj95hgxuc2btiv77imngpuke1e0","ecc-aws-006":"cb8y0ia4bnfzvk6xolvx87cm663","ecc-aws-142":"02db46w2lhzbbevuawmx48y9tu5","ecc-aws-114":"wfoqnoewtqkn9hd7bpn2huitun1","ecc-aws-318":"nvh6cizj2i3exj42c87s8rnzy8k","ecc-aws-132":"oevd527g7m2o4l40cgt1gydcg7b","ecc-aws-304":"47jkcoisak6ma64xsxb1axf4upz","ecc-aws-387":"3f0nm4f0jlju4ztsz4vdq23wrn7","ecc-aws-368":"mrne7ewyud4wwziq67vv4fd5wfg","ecc-aws-576":"dw98gozbwgyoe3koc5r3vekqalg","ecc-aws-194":"dx15jhb98e6fq94261vczrpll9n","ecc-aws-394":"wmmpaeaz9xn4arher14g22nta4v","ecc-aws-218":"wrodin04e5ryo364vpmex7o0j6p","ecc-aws-082":"x8ck4mcyw06nd5du9zqwsjbulm7","ecc-aws-275":"a9ewnx67jd84wmo8fwki105q0oy","ecc-aws-117":"yev4a402jwuu7opkm2fhzakksal","ecc-aws-005":"oxzq0ro7yewdqns70c1j7lb35vw","ecc-aws-034":"9nwhrr0vou29c1mpfnqjid7xkur","ecc-aws-366":"r450ri9bosxebj568mumqo9qb4t","ecc-aws-305":"38klpcfpsbvhbfjlzqcey9k7994","ecc-aws-183":"2leyxwhn43xt1azv0wws6gfz5yh","ecc-aws-510":"vgxztga41r7wf5fprb3g605guoe","ecc-aws-063":"4ojbiqpfnqz7q8d1stl9ikv6r0b","ecc-aws-292":"aa443um7oy1ddws0mkmv2yq0zba","ecc-aws-127":"tf1kb92gmami8x3rp01fwvrrml4","ecc-aws-148":"tofgwga62szhhoz1eahexhl42aa","ecc-aws-390":"f3m1turotmazyhwoyyxzaj4adrk","ecc-aws-099":"zhl4eig39hy4876oxx8t7librm6","ecc-aws-207":"9mzw4lxu64v7kcel86iz9enbfwb","ecc-aws-488":"8wiiuo42ymg8je2yv9gs871lq49","ecc-aws-398":"q927wqo10ki5g8llyn0f6h4p2fl","ecc-aws-217":"6ufikvdng6fcll4819mzzp5wnee","ecc-aws-026":"p8ria2xqkocea9j726945gq01lk","ecc-aws-391":"1crcapfsvgm8enqq4sys2527ocd","ecc-aws-421":"ss39jivinon2z299fzvfohyw5u1","ecc-aws-177":"fshz3d3gnhcy9a5dgy27dy9nt9g","ecc-aws-341":"y9mgbqm16p7o6dkjx1517j3bdwa","ecc-aws-228":"vpo2vsblwxfpp8ns5g5cgfbjbsb","ecc-aws-467":"rbbm42ubxy0jbyzl4xicpur7rpz","ecc-aws-438":"3ozdsdefkxy9kj2k3z2egrobfpz","ecc-aws-440":"qymvj8g081nu7v7x47xeo3jd9sd","ecc-aws-571":"o3stsqzcksm16nwbqcjmvoiyvwl","ecc-aws-276":"pu7a3pw1qohfldx788xr8bzvcce","ecc-aws-344":"ahx8q4thap9tmb475vyk01jootm","ecc-aws-319":"o3ol9w61m44iuatuj5l3jtb3ajd","ecc-aws-081":"7p5yvew20aad0yqxg7yyw9fb97m","ecc-aws-380":"f3mqq8wyotf43mn60y3qum242cn","ecc-aws-486":"saq5rgnvnnoqq66udkd7pm4k1hh","ecc-aws-520":"3zgcl6gh2hbb6gm7v5no6a8jk50","ecc-aws-271":"yzhlhveq3wfauvo7ndt780fymn9","ecc-aws-309":"fiira5klye2cubhsnaxghv5iohn","ecc-aws-084":"p9eocfdcmf5y7lkqoshr4870xxt","ecc-aws-023":"c3k6dg64z1d07wicmzhrlsb73n2","ecc-aws-259":"tas57gi9b8dtxzv8byv5pxr0id5","ecc-aws-392":"5x3z2nbp6cwpr35csmj42tbydtb","ecc-aws-475":"6zsi4j2flvrysqhf510w2xq6o16","ecc-aws-278":"22cv6levfcofh833waq6bspvxoh","ecc-aws-360":"fuggvcjz310mx0rvfato0bbnxhu","ecc-aws-294":"cqhn9fjed7mqdiq048y7b8bko2a","ecc-aws-022":"xzv6g0qaps6q304tj2ka1i56fz6","ecc-aws-150":"i9ep2b3ep2pfnht7dip9tc5pgkj","ecc-aws-121":"vss0bb16xo1luelm6b6fblc8adu","ecc-aws-522":"8x98nmytbz05lqruo8hrnzpz3u2","ecc-aws-284":"cgkgypwjaagv8axzeytow5i66yn","ecc-aws-303":"gv14nrh6mwwmje3orzdy0honjif","ecc-aws-426":"nxcykdnsoul8tqzsrhyijtf5qm7","ecc-aws-324":"ywqhdv6oh4crjxjnftts1sa2cwt","ecc-aws-524":"ssu2my4sijhl8r2drtrnc45vlso","ecc-aws-098":"0619mhukd8gnj8m2jkltt4c8eva","ecc-aws-033":"g8fo13ozfkn4f8bfg830coh695r","ecc-aws-197":"p6jjf5ppirxrbf6n2xwjf08855k","ecc-aws-069":"2wvw3lp10spi8abmddqzccdkqm8","ecc-aws-068":"rejh9408065nd9924kmzud9e668","ecc-aws-155":"8bf9qvntiy7shbhef1gkn8c29cf","ecc-aws-039":"x04mg5qq8ys76rob1vndi7epye4","ecc-aws-430":"96k8v6ve4me8qvw39hh5fm3mg2z","ecc-aws-527":"5tnyrkg0f6mrmdylfra90ycpp59","ecc-aws-513":"1pr4mq0jw4yig865m7rabbjcf8l","ecc-aws-358":"tdqulanr0hnioj9dzi0jt6g2ak2","ecc-aws-478":"t00f60yk3n5gbnrfbxvabkgvjap","ecc-aws-002":"nn01gil3dyri2lpb43kqlcrrzzk","ecc-aws-464":"4szqwaq034s4jacsit1pm7lwljh","ecc-aws-037":"47zm479pgrlb7lo6w5eja7gralt","ecc-aws-500":"9q5zggby7bevke62lgfbt8xinr6","ecc-aws-545":"9hu6us6x1glh6ewr9ba5s74ku1p","ecc-aws-181":"usxg0beldi3h0528q6bhd4r9lu3","ecc-aws-279":"58ma7tvl1gtogd58b9aub6sm18y","ecc-aws-093":"tss6aqd8mk0bj2n30dfqo2y2qwy","ecc-aws-164":"5s28ondvlychv7qdv8zu2q5cedz","ecc-aws-354":"xicjwm24crr4826pm8ato5lf0nw","ecc-aws-352":"fv7nn2nkgngnhgmhlwmn9qae8p1","ecc-aws-085":"h4m0ftlaoba18v8626hcpb71bgl","ecc-aws-021":"vkvcc36j80dntnjm4dc46c39cdp","ecc-aws-215":"uopa4e8du44xnwnbf6sevx2ix4j","ecc-aws-102":"9i0121d9sv5gjlb2mop7ah93yst","ecc-aws-362":"l11ynj7lc71q93l3cl6fi7gjr18","ecc-aws-365":"suzvxb2xe82lye45jsy0vc6816x","ecc-aws-160":"xofzsmyexyh86sri9h4fz7ck1sq","ecc-aws-075":"v3auehrnjmbxeexno4azk3yo7h0","ecc-aws-076":"em1hi4k9wueb00ht7nkgn3cdk7v","ecc-aws-029":"pykscgjn7s41z3gm88oodl50hpp","ecc-aws-338":"3jpz14chzrenizjw15b7rtodwth","ecc-aws-089":"epay1wbuad0ihqxizub8eklbuev","ecc-aws-236":"rot2vcrbup10uvk91qpj4nr68xf","ecc-aws-343":"xxyk8bkftu2iiwwyv5jihakdnfl","ecc-aws-116":"4vf0n1xp14qcz3qtmy15x3aij4m","ecc-aws-165":"8k557lc8wuty4ecfk6g8p8wu2lo","ecc-aws-270":"k1u48r4h1g28lw7x72l64i32j9f","ecc-aws-326":"jacrltd8mq3ywmh84fqn6qapyq3","ecc-aws-372":"60x7kzubzmcg6f3udh105s19uv5","ecc-aws-336":"vcwkf3keaitrpqmriokmeuch0mi","ecc-aws-536":"x065754g8ptmhohfsct53qdsmun","ecc-aws-512":"8s3cxv43gi7fjndorfvefud6vi2","ecc-aws-367":"0qkyfbydplwg5rvb2asmj60rxhi","ecc-aws-282":"disl5ehvnsbcge8eokbao82lqb1","ecc-aws-452":"p4f89ds3rkly0761qaljzxt2orj","ecc-aws-348":"vqe9e7oyn5l093xcrbugj5s4bfm","ecc-aws-019":"d297o3fw5ke8tyie6x2ih9m1411","ecc-aws-301":"xcmzkkvn926mieav4nfbvy31648","ecc-aws-450":"c36jtt2uuhutu75dimzsdajgqcn","ecc-aws-024":"dbvyx8rosutix7i81fc6o0s9ora","ecc-aws-097":"xteyw8ks6fs7cx7tnyw377r4f3h","ecc-aws-103":"rd571iqzfi7mx37wjpf0xzen61h","ecc-aws-424":"558xr56y468antu7zg54nydz4z0","ecc-aws-043":"glmhyqyilr5rezqfw47ssrt6ts5","ecc-aws-051":"5oeqpikqhn6vjq8qj5tv6ku9h7f","ecc-aws-080":"uwfad2dr25fef69pinc1de5k3y2","ecc-aws-468":"byby15ek5jxtvl0kkueosb6p2qm","ecc-aws-434":"jhy8ycvfn7yrjnv9pl7yd90xyvr","ecc-aws-331":"7dmm8d54d17r7byxkvvjzlvnxqb","ecc-aws-487":"w6cmjiu9bt47ty7sk1lwvew36md","ecc-aws-484":"usqn899kocthll8hv041qekd7e6","ecc-aws-389":"qmumknfgxjnf86m49cd9tn4o55n","ecc-aws-071":"f5o2ip2a4h3lwbc7uj9f6pyz0wz","ecc-aws-410":"8k49qdl630z3lqzfy50kgl1clb6","ecc-aws-047":"75pivh8qhp1b0s3izpo1vozbu51","ecc-aws-532":"zjfj8cu2qnqc9079f0dnqhmyyqx","ecc-aws-192":"siqmixvxq18zt06jdap7tl5wogv","ecc-aws-220":"c6ycxa6eujsdo2z5yd7pxa23msc","ecc-aws-141":"l70prct075f7zhtn24rco8u83f7","ecc-aws-533":"zzxha28vv0irgmxpaew97c6l4rb","ecc-aws-018":"1auiza3p536hocder562yy42kjx","ecc-aws-470":"v10f5gpujjw08kqya55tqmu774d","ecc-aws-346":"sgp1ve4c6to7nt4ch7xd7dm568o","ecc-aws-239":"dki6e9wtatgih1fniqik39uvnts","ecc-aws-060":"p6mf48rmvi8yf39vqw1bc15c7p0","ecc-aws-376":"w5bneswi9db6szjamlapqpetd16","ecc-aws-232":"5i61lm0xl8d9174czen77pdf4z5","ecc-aws-009":"webqpxji2wggb0o68ezjrbag1dr","ecc-aws-001":"ju2qj4td7gsn44g4x3eunle7ex0","ecc-aws-379":"a48ef2d1by55h4r32kl2wuxukyz","ecc-aws-283":"2c3guudeudngvldkndibg08vqox","ecc-aws-216":"jnnbqncxdzsheyoe4kjfzss5gck","ecc-aws-553":"lz6kk683ywa4ymq3baipk744lm9","ecc-aws-541":"ypny220k0e1nuj2ist2yzu95slt","ecc-aws-480":"j1ux94kod45uz0uhukciyuw9bbz","ecc-aws-237":"s1knnvehss6vk6m9nnf4ilzgyv4","ecc-aws-353":"8j5yoe21z7rckidxs3fzj94y95s","ecc-aws-211":"7wmmo46ln2n5d650zs6fc02mpoh","ecc-aws-422":"4jjdkpzjmcqpsza1abfor5rf9qb","ecc-aws-375":"9xrqtpeg461wqviwc9ub7kes0ro","ecc-aws-542":"tsybzlpmqqiyp6uoglr24q8m7qw","ecc-aws-163":"s83bg0b77zbm57h5bm7avdqpigx","ecc-aws-349":"z14emk6lfjjc2c007j2cd15yxs1","ecc-aws-526":"zziy6s87ieiyhnfwy2duygs837v","ecc-aws-248":"3mj9her2wh2cu3yyjcgzrjilc6g","ecc-aws-345":"amd6tffiaxkr1kdejy9fhua5x0d","ecc-aws-330":"s9ejfk0pg5yjju86sdvqtptksb0","ecc-aws-297":"5348cgquv5qv09s0qx872g09poz","ecc-aws-213":"j6jyns5km015ji24gszpw6mjj0m","ecc-aws-327":"dx6bs1pyb0pj7w4kxhy8mghklmc","ecc-aws-072":"vgmdwbqfpubm782eqxn0qtq3wu6","ecc-aws-202":"s4e0n7olz9w6ncoqyben7ig39q9","ecc-aws-010":"e465l8qsq9iopm7djzfg0j7qeun","ecc-aws-299":"v1ygaott6oqv7edm6q2inq9w3mr","ecc-aws-525":"mlwc84t36ge5orrsnpi5fxlv5i1","ecc-aws-178":"shslq0q4pfv15k073lys16x2v8w","ecc-aws-315":"kqramwx9j0jekc9g65x1wbda9tg","ecc-aws-369":"hlro3fs5guauyp4b4nbthlceodw","ecc-aws-472":"xcgn56jggkl6m02ir1q6o7wxmcf","ecc-aws-210":"69xrv2oa4l6vn2kzhw1vixnp003","ecc-aws-351":"ue7bpkw3a2i30btehh89knucc9z","ecc-aws-130":"ygnfjqxkr3jps4x2q179sufv0gz","ecc-aws-200":"q23nakk8rd2mjjjunf8fgqozqu1","ecc-aws-252":"ndojbn95b5c3wdrsyuyc4ak81h9","ecc-aws-111":"rli19h4ip4huzprzmgvceiqzuuz","ecc-aws-458":"ruophrpsp9119pskiuizeuea2od","ecc-aws-321":"0bgpqnvptpg9qvmfyvh3qouwikg","ecc-aws-123":"6uv3inainf7467ilj18ej5w83rb","ecc-aws-517":"okn60vqk7kd4w9wfj3sba6bf7jo","ecc-aws-126":"1j9uebwgmyjdjw48sfh0w9i3fjj","ecc-aws-466":"on5qhl2uttpalghhx3tssov4ube","ecc-aws-396":"i487l6co803g8h28cto6fz0rvjq","ecc-aws-381":"m44oee01ov4dnzhgs1w7xp3kgsz","ecc-aws-241":"8b4oi6ql6etc31ihszitaal3hha","ecc-aws-357":"m6grbtmq31y9w4tmxn8utpqiq2e","ecc-aws-088":"r9c6o3yadq5ibxlgv7jqtqr4pek","ecc-aws-242":"xookapd9o8oa7yd5nke4jyhh4ly","ecc-aws-212":"gh1qupkbozdt2nr2pxl39dlx8i8","ecc-aws-077":"o7qi9ytbm24z9pt4ad89xyo2but","ecc-aws-339":"jufjjb40xf5vn1w45f4ussy16s9","ecc-aws-385":"74yovmyjiu1and404kurvjpu11y","ecc-aws-515":"k8o3psum0us2d2tpi2so1wdtqhd","ecc-aws-173":"6jsw1tfb8b6f7pa8m3ksp3llck8","ecc-aws-110":"xyb00ohbazs1afmtpibb2kllbeg","ecc-aws-397":"4unlsw7e5zdckjqvawr4ehcsqv6","ecc-aws-065":"gn6xwdcbghsjlvsn2mgjwofnwxs","ecc-aws-508":"43kf8o2944ktel8fpp31mj9qm9k","ecc-aws-140":"ppqmqmguk6gvtftn1g83bz11rb3","ecc-aws-206":"ulmh8hb3cysbd5cqrfmpd4gu28n","ecc-aws-416":"0r8iq5ca5ucasld11iy5iuguloo","ecc-aws-463":"nbkyyo1xqif53ewh8qxx3rz2hdw","ecc-aws-384":"zc29eg54h28fkj6pmblw6tohdou","ecc-aws-503":"rq9g8gbk06qj2k99kqtr4uqootj","ecc-aws-516":"hvaq2ukprmlt613pkcdlyst0sha","ecc-aws-161":"murqvhudff8x1c9skpil1mhgz1t","ecc-aws-359":"5eash5hxhwxtjz11pkm6uh63mge","ecc-aws-373":"u1ro65iq2frr1lvjnr05sqozxw1","ecc-aws-509":"cupwh06tvwmmz3z063w300rj3s6","ecc-aws-575":"ps812575axbl0am7zd4gqd09lmg","ecc-aws-086":"3lnxd22mouhe5607mkbhspa3xhh","ecc-aws-329":"1r8q9c3bc8961vufxknmy3t48r6","ecc-aws-073":"vr50yclwkwqn4oudk25t3dpiapm","ecc-aws-523":"phhhr3ra6qqgr4swaelz62r2rrw","ecc-aws-261":"u41koosz5b4r11iu1qdhtmute8w","ecc-aws-274":"njcggnseu0751xqirzomh08r8b0","ecc-aws-184":"1ke3bnvv122dpndmsnac864kroi","ecc-aws-491":"8jqn19svr97d6fdckrq6o8o731a","ecc-aws-393":"0lytaiva7ahgie5kvc0iuem2v5a","ecc-aws-411":"9635vkyrpbszdo7aaabhk8yrvx6","ecc-aws-027":"9zd64wsoufd5bsgptw2ucsfwwnc","ecc-aws-374":"8w9hshfxtw7emw62a6g8zy3s28e","ecc-aws-256":"4yelvvtf3ymfdmp6mx97rmpsv3a","ecc-aws-291":"v1d620by2z5gtqr6hyl9mry8434","ecc-aws-494":"a48x45kneg2dl6x9jqjvrixlg3e","ecc-aws-497":"4ey4id631dqb9trscd99fjrw71o","ecc-aws-064":"8a0khq8hjk09koybz1uso7t45g0","ecc-aws-320":"u55zsgrfk56cj4acao6rh0etfw2","ecc-aws-449":"4v1kp0uptwpjfldbg9smhmsbfei","ecc-aws-172":"vtap7ne0qc4stt96zh5ndmm04o8","ecc-aws-231":"3do5pr0d25ykljrq9k1uu6h86bq","ecc-aws-535":"h9myqg5484ff28opxrtsiiafnc2","ecc-aws-240":"i1trcm3ddekfn22rwdaqcobtsro","ecc-aws-249":"krhn6jbrvp1gqimshuajz7kng0v","ecc-aws-078":"vkftuq149elwo2mye02lmo6fdw0","ecc-aws-457":"95oik0e8vgtst4jpoj8q17f3jbz","ecc-aws-404":"c4pmjzk8vujsriwslsknn7dpqjn","ecc-aws-243":"qa6b3nd8gbc229p17224rdza3hx","ecc-aws-046":"sa05vui21t69wc5cnh4ovc3ihri","ecc-aws-505":"e82mwadlneg4dppqund60ui67ax","ecc-aws-247":"hb6uy9qdv9imunvg456jxzwpkvy","ecc-aws-306":"3o64a2ar14deaw7ccz5wb0rciqh","ecc-aws-402":"bk3r8uv27qrts624edcm54i5coh","ecc-aws-518":"wnz1h0q54tsieuoc6qtqtndc13s","ecc-aws-442":"zmckaiypycifozeuutiia6ki8du","ecc-aws-041":"807mtz3eruwu8pc9ha1r1zulaoh","ecc-k8s-016":"iggzdiwcgdzcvp99rt5q0njxpga","ecc-k8s-067":"9ey55er3dtilfnuikgby17bi8yw","ecc-k8s-081":"4t1cz145y1gpljydm15b9dw536m","ecc-k8s-062":"umkxcbm0gotse76d2b3l5tk4ctv","ecc-k8s-044":"1f37es4qcda3c5y0apna0h6rncc","ecc-k8s-049":"nl0ua0w8j5jui3yda2e76lafiyl","ecc-k8s-024":"8rhx7159olhehk9qqfl3qzk0phw","ecc-k8s-032":"4gqq6d7pp0sue2174tgpl32rps1","ecc-k8s-038":"5obxduqwawbrsnrqv2p4ccqinpf","ecc-k8s-002":"5xucxdnck84gi0vuse7w86jp794","ecc-k8s-013":"lr3fepolo5x5sm19e0416nftgll","ecc-k8s-070":"55qi0bwwa0xu3w5s80vqywytmwl","ecc-k8s-092":"02oofqzkqi29ksre3vp8oftc96l","ecc-k8s-012":"n4f4onsrz5itqlxxrqt8zqd6a9t","ecc-k8s-061":"2ztbmeffdw3tmwnqupqmpzmu12e","ecc-k8s-008":"jggsmx0e9kajusshjxyzhijrtyu","ecc-k8s-030":"7wthi4ejceaa9ayp4nme1rdq9xm","ecc-k8s-005":"bib9qdu5bvjapflv8ly5yhfmk3r","ecc-k8s-072":"s2cidirxeqn1h6yofo2l0bdd6u6","ecc-k8s-007":"rmfyh2ltk4e4ndcobff0sgy9r5p","ecc-k8s-035":"118d4i7p8fzpbkg66qm0oj8b43v","ecc-k8s-064":"jcb057qynfmi51qncneglyg3apv","ecc-k8s-052":"murtsctq9hp9nesfjmpxslcen36","ecc-k8s-066":"unnkcrj8vqmv7vmqdirpsdquwej","ecc-k8s-031":"vdqtdaq3bz9xpe5eqdxgxmdwc5l","ecc-k8s-026":"vle7iqhsdeop1fh1vibn19dis7f","ecc-k8s-017":"f3gbm39tdr1k4rkq4sf40ndux0q","ecc-k8s-071":"phfq754k3fnwaoq07jon7thkxp1","ecc-k8s-087":"8dwrudj8a86rhjz8ka42zdyvuqt","ecc-k8s-059":"0w7fke1tmrhpuj6v3u6ynkymsg6","ecc-k8s-023":"ycualpvn5dkkip8emfquvrl7v3p","ecc-k8s-060":"hr7v0ms2l52pvvt6n4i4cbj1qtv","ecc-k8s-079":"xkyrsaoefh39eabyuyc41uho1zj","ecc-k8s-053":"m5jlxtx388r0deoj0kvwp7jgmug","ecc-k8s-074":"8iwoqqbq7t3ggw3wmq3ahb1k354","ecc-k8s-033":"trrbp9m7bhtj5csc4zxjkjucpl0","ecc-k8s-043":"9o2n0gexisxk9rtg5zq2prottxy","ecc-k8s-027":"5unv4pxona1g99w9qae5nf9vr7s","ecc-k8s-047":"5zqh6m83dxey7y0iiig858wfopk","ecc-k8s-041":"8yza3icbaxyfv7jvc79s2b3onqv","ecc-k8s-075":"96iwzvmuq4y974huxcklbsnvryb","ecc-k8s-014":"wntuvfvzwar7sxofapiuzw6f5ip","ecc-k8s-080":"c6jqf5sj9y8dudrzu8iuz4b7p16","ecc-k8s-037":"6l3qsh43qvura3fvqvhskb2yy67","ecc-k8s-028":"9nc3sgz0yot4btjv966o1edx1ue","ecc-k8s-069":"a9rqp1e97qaneej3irn8cnf060u","ecc-k8s-054":"4ij1a2wngqnxvooa2vfgusx4mtb","ecc-k8s-088":"uxsxxx9j7ppbdejd35vchajhcfp","ecc-k8s-006":"klq3uuxqoff4rgt0osbfmhe614u","ecc-k8s-003":"2cuc376sic901zi5da5fd1t356n","ecc-k8s-042":"iqbzibdwao1080dfgprpylmfd2k","ecc-k8s-077":"huw6j3dw0tbchsrfqydpn33e320","ecc-k8s-040":"aq729jrn42bgdphytphxcfwa9sh","ecc-k8s-021":"wxkekf1cs8vzrq1e97404e2ssc3","ecc-k8s-068":"xp8d82qgo09co87sfvsmbgrph8n","ecc-k8s-010":"9vr0k3s4uhgt8rgl5az6d460m04","ecc-k8s-051":"2tzv4tf7qi1l8jnw83e4pewxk79","ecc-k8s-063":"vnm0bwe1ehxzxq5t4rvw5bzmmvp","ecc-k8s-019":"xolal87f9dyhjnjg9s25jxjk3qz","ecc-k8s-022":"32yfuylzix8qdcq7bxr2x0tzw1f","ecc-k8s-025":"sq7rpph3bxhsofmo68s06sc6d1q","ecc-k8s-058":"ju4we2ckzp7lceujt0m0qa9v2d5","ecc-k8s-001":"79vhlcjizpsxfpi17zcyc86ht02","ecc-k8s-065":"4savhrxdt4rsks2mxaoe07cpyy5","ecc-k8s-039":"nqw0yyb992q69isw43ah8tl08ut","ecc-k8s-036":"co1fvlf7s7w8yh1uwu60btudyo4","ecc-k8s-048":"wyq9nyllj19bo5y3wgp7hg7tbna","ecc-k8s-015":"suqhml53axyejf8iefe9yfk4a28","ecc-k8s-057":"bldchns3i8ea7nhx8jc7yubtzft","ecc-k8s-086":"c3eddp57svnc8ai9mrhvoi4id6s","ecc-k8s-020":"fytqgasw9dkq5ycq3tim3hamgwi","ecc-k8s-078":"i7ica5atjjesmrr5sbw2ufmn4un","ecc-k8s-034":"5vvvyuw2o96sdf9wgo4uqijbkvu","ecc-k8s-056":"ktas8di2zrqjdgredc8my6y14pj","ecc-k8s-045":"hju70l4m76w74762afu00vbz4kt","ecc-k8s-076":"adzagtgvbxfkqalbehzpeol8jr4","ecc-k8s-004":"xgt04mc9oi939kb33nyt6zfw29z","ecc-k8s-082":"c2z466kdd0mvzc7ka2e6y1g3x3w","ecc-k8s-018":"775dex2nisy5od5guu9bvunl32x","ecc-k8s-011":"3xrwfpqr1j4wixtstr9rxd0kodc","ecc-k8s-050":"eet94o84ihlgg69uz1wya129hqk","ecc-k8s-009":"jznmlyvblsfmu9y8jefnjc6tt2g"} \ No newline at end of file diff --git a/tests/tests_metrics/mock_files/rulesets/caas-settings/RULES_TO_SEVERITY.json.gz b/tests/tests_metrics/mock_files/rulesets/caas-settings/RULES_TO_SEVERITY.json.gz new file mode 100644 index 000000000..cf47de88a --- /dev/null +++ b/tests/tests_metrics/mock_files/rulesets/caas-settings/RULES_TO_SEVERITY.json.gz @@ -0,0 +1 @@ +{"ecc-azure-056":"Low","ecc-azure-016":"Low","ecc-azure-353":"Info","ecc-azure-057":"Low","ecc-azure-108":"Low","ecc-azure-038":"Low","ecc-azure-310":"Low","ecc-azure-270":"Low","ecc-azure-367":"Low","ecc-azure-314":"High","ecc-azure-039":"High","ecc-azure-044":"Low","ecc-azure-042":"Low","ecc-azure-068":"Low","ecc-azure-137":"Info","ecc-azure-163":"High","ecc-azure-219":"High","ecc-azure-299":"Info","ecc-azure-043":"Low","ecc-azure-021":"Low","ecc-azure-117":"Low","ecc-azure-161":"High","ecc-azure-214":"Low","ecc-azure-205":"Low","ecc-azure-331":"Info","ecc-azure-024":"Low","ecc-azure-224":"High","ecc-azure-123":"Low","ecc-azure-176":"Info","ecc-azure-122":"Low","ecc-azure-133":"Medium","ecc-azure-027":"High","ecc-azure-127":"Low","ecc-azure-146":"Low","ecc-azure-236":"Low","ecc-azure-232":"Low","ecc-azure-015":"Info","ecc-azure-156":"Low","ecc-azure-321":"Info","ecc-azure-344":"Low","ecc-azure-119":"Low","ecc-azure-234":"High","ecc-azure-053":"Low","ecc-azure-126":"Low","ecc-azure-111":"High","ecc-azure-304":"Low","ecc-azure-162":"High","ecc-azure-348":"Info","ecc-azure-220":"High","ecc-azure-284":"High","ecc-azure-069":"Low","ecc-azure-014":"Low","ecc-azure-048":"Low","ecc-azure-341":"Low","ecc-azure-110":"Low","ecc-azure-328":"Low","ecc-azure-160":"Info","ecc-azure-128":"Low","ecc-azure-165":"High","ecc-azure-096":"Low","ecc-azure-150":"Low","ecc-azure-020":"Low","ecc-azure-215":"High","ecc-azure-159":"High","ecc-azure-364":"Medium","ecc-azure-030":"High","ecc-azure-239":"Low","ecc-azure-106":"Low","ecc-azure-228":"High","ecc-azure-045":"Low","ecc-azure-343":"Low","ecc-azure-203":"Low","ecc-azure-059":"Low","ecc-azure-290":"Medium","ecc-azure-060":"Low","ecc-azure-009":"Low","ecc-azure-258":"Low","ecc-azure-033":"Low","ecc-azure-293":"Info","ecc-azure-333":"High","ecc-azure-170":"Low","ecc-azure-011":"Low","ecc-azure-148":"Low","ecc-azure-180":"Low","ecc-azure-158":"Low","ecc-azure-283":"Low","ecc-azure-216":"High","ecc-azure-144":"Low","ecc-azure-256":"Low","ecc-azure-012":"High","ecc-azure-025":"Low","ecc-azure-121":"Low","ecc-azure-197":"Low","ecc-azure-166":"High","ecc-azure-371":"High","ecc-azure-372":"Info","ecc-azure-300":"Low","ecc-azure-207":"Low","ecc-azure-376":"Low","ecc-azure-098":"Low","ecc-azure-311":"Info","ecc-azure-319":"Info","ecc-azure-173":"High","ecc-azure-026":"Info","ecc-azure-031":"Info","ecc-azure-287":"Info","ecc-azure-342":"Low","ecc-azure-177":"Low","ecc-azure-151":"Low","ecc-azure-149":"Low","ecc-azure-145":"Low","ecc-azure-323":"Low","ecc-azure-227":"High","ecc-azure-277":"Info","ecc-azure-171":"High","ecc-azure-240":"Low","ecc-azure-071":"Low","ecc-azure-288":"Info","ecc-azure-318":"Info","ecc-azure-257":"Low","ecc-azure-054":"Low","ecc-azure-182":"Low","ecc-azure-072":"Low","ecc-azure-365":"Medium","ecc-azure-097":"Low","ecc-azure-109":"Low","ecc-azure-147":"Low","ecc-azure-302":"Low","ecc-azure-010":"Low","ecc-azure-326":"High","ecc-azure-006":"Low","ecc-azure-132":"Medium","ecc-azure-125":"Low","ecc-azure-286":"Low","ecc-azure-052":"Low","ecc-azure-282":"Low","ecc-azure-067":"Low","ecc-azure-305":"Low","ecc-azure-168":"High","ecc-azure-139":"High","ecc-azure-179":"Low","ecc-azure-370":"High","ecc-azure-349":"High","ecc-azure-028":"Info","ecc-azure-141":"Low","ecc-azure-007":"Low","ecc-azure-356":"Medium","ecc-azure-346":"Low","ecc-azure-267":"Low","ecc-azure-327":"Info","ecc-azure-065":"Low","ecc-azure-099":"Low","ecc-azure-265":"Low","ecc-azure-345":"Low","ecc-azure-237":"Low","ecc-azure-196":"Low","ecc-azure-094":"Low","ecc-azure-358":"Low","ecc-azure-295":"Info","ecc-azure-281":"Low","ecc-azure-357":"Low","ecc-azure-120":"Low","ecc-azure-325":"High","ecc-azure-317":"High","ecc-azure-112":"Medium","ecc-azure-005":"Info","ecc-azure-167":"High","ecc-azure-374":"Low","ecc-azure-272":"Low","ecc-azure-022":"Medium","ecc-azure-124":"Low","ecc-azure-275":"Low","ecc-azure-354":"Low","ecc-azure-095":"Low","ecc-azure-055":"Low","ecc-azure-036":"Low","ecc-azure-340":"Low","ecc-azure-301":"Low","ecc-azure-101":"High","ecc-azure-201":"Low","ecc-azure-064":"Low","ecc-azure-339":"Medium","ecc-azure-116":"Low","ecc-azure-276":"Info","ecc-azure-113":"Low","ecc-azure-066":"Low","ecc-azure-157":"Low","ecc-azure-102":"High","ecc-azure-032":"Low","ecc-azure-050":"Low","ecc-azure-213":"Low","ecc-azure-200":"Low","ecc-azure-289":"High","ecc-azure-225":"High","ecc-azure-241":"Low","ecc-azure-206":"Low","ecc-azure-152":"High","ecc-azure-184":"Low","ecc-azure-329":"High","ecc-azure-023":"Low","ecc-azure-294":"High","ecc-azure-129":"Low","ecc-azure-105":"Low","ecc-azure-332":"Info","ecc-azure-058":"Low","ecc-azure-379":"High","ecc-azure-202":"High","ecc-azure-350":"Medium","ecc-azure-355":"Medium","ecc-azure-278":"Info","ecc-azure-378":"Info","ecc-azure-362":"Low","ecc-azure-334":"Low","ecc-azure-142":"High","ecc-azure-306":"High","ecc-azure-070":"Low","ecc-azure-181":"Low","ecc-azure-359":"Low","ecc-azure-351":"Info","ecc-azure-231":"Low","ecc-azure-337":"Medium","ecc-azure-164":"High","ecc-azure-155":"Low","ecc-azure-061":"Low","ecc-azure-004":"Medium","ecc-azure-130":"Low","ecc-azure-373":"Low","ecc-azure-238":"Low","ecc-azure-002":"Low","ecc-azure-199":"Low","ecc-azure-298":"Info","ecc-azure-046":"Low","ecc-azure-217":"High","ecc-azure-291":"Low","ecc-azure-049":"Low","ecc-azure-313":"Info","ecc-azure-204":"High","ecc-azure-279":"Low","ecc-azure-235":"Info","ecc-azure-178":"Low","ecc-azure-172":"High","ecc-azure-226":"High","ecc-azure-324":"High","ecc-azure-143":"Low","ecc-azure-280":"Low","ecc-azure-369":"Info","ecc-azure-100":"Low","ecc-azure-218":"High","ecc-azure-368":"Low","ecc-azure-103":"Info","ecc-azure-174":"High","ecc-azure-347":"Low","ecc-azure-037":"Low","ecc-azure-222":"High","ecc-azure-131":"Low","ecc-azure-296":"Info","ecc-azure-336":"Low","ecc-azure-013":"Low","ecc-azure-008":"Low","ecc-gcp-246":"Info","ecc-gcp-401":"High","ecc-gcp-206":"High","ecc-gcp-065":"Medium","ecc-gcp-272":"Info","ecc-gcp-262":"High","ecc-gcp-310":"Low","ecc-gcp-202":"Info","ecc-gcp-294":"High","ecc-gcp-076":"Info","ecc-gcp-281":"High","ecc-gcp-123":"Low","ecc-gcp-127":"Info","ecc-gcp-311":"High","ecc-gcp-214":"Low","ecc-gcp-068":"Low","ecc-gcp-268":"Low","ecc-gcp-220":"High","ecc-gcp-415":"High","ecc-gcp-251":"High","ecc-gcp-118":"Low","ecc-gcp-126":"Low","ecc-gcp-122":"Low","ecc-gcp-244":"High","ecc-gcp-324":"High","ecc-gcp-443":"Medium","ecc-gcp-409":"High","ecc-gcp-200":"Info","ecc-gcp-049":"Low","ecc-gcp-083":"High","ecc-gcp-051":"Medium","ecc-gcp-167":"Low","ecc-gcp-022":"Low","ecc-gcp-203":"High","ecc-gcp-136":"Info","ecc-gcp-208":"Low","ecc-gcp-125":"Low","ecc-gcp-347":"Info","ecc-gcp-306":"High","ecc-gcp-044":"Low","ecc-gcp-314":"High","ecc-gcp-260":"Low","ecc-gcp-342":"Low","ecc-gcp-237":"Info","ecc-gcp-072":"High","ecc-gcp-256":"High","ecc-gcp-054":"High","ecc-gcp-230":"High","ecc-gcp-277":"High","ecc-gcp-008":"Low","ecc-gcp-250":"High","ecc-gcp-304":"Low","ecc-gcp-152":"Medium","ecc-gcp-252":"High","ecc-gcp-445":"Medium","ecc-gcp-070":"Info","ecc-gcp-035":"Low","ecc-gcp-017":"Low","ecc-gcp-141":"Low","ecc-gcp-193":"Low","ecc-gcp-175":"Info","ecc-gcp-199":"Info","ecc-gcp-185":"High","ecc-gcp-131":"High","ecc-gcp-207":"High","ecc-gcp-004":"Low","ecc-gcp-289":"Low","ecc-gcp-104":"Low","ecc-gcp-273":"Low","ecc-gcp-234":"High","ecc-gcp-117":"Low","ecc-gcp-309":"Low","ecc-gcp-103":"Medium","ecc-gcp-271":"High","ecc-gcp-346":"Medium","ecc-gcp-020":"Low","ecc-gcp-432":"Low","ecc-gcp-087":"Low","ecc-gcp-448":"Medium","ecc-gcp-032":"Low","ecc-gcp-186":"Low","ecc-gcp-063":"Low","ecc-gcp-258":"Info","ecc-gcp-166":"Low","ecc-gcp-449":"Medium","ecc-gcp-227":"High","ecc-gcp-124":"High","ecc-gcp-021":"Low","ecc-gcp-173":"Low","ecc-gcp-061":"Low","ecc-gcp-228":"Low","ecc-gcp-119":"Low","ecc-gcp-218":"Info","ecc-gcp-093":"Low","ecc-gcp-215":"Info","ecc-gcp-446":"Medium","ecc-gcp-025":"Low","ecc-gcp-001":"High","ecc-gcp-023":"Low","ecc-gcp-253":"High","ecc-gcp-288":"Low","ecc-gcp-184":"High","ecc-gcp-172":"High","ecc-gcp-092":"Low","ecc-gcp-067":"Low","ecc-gcp-101":"Low","ecc-gcp-036":"Low","ecc-gcp-153":"Medium","ecc-gcp-019":"Low","ecc-gcp-212":"High","ecc-gcp-050":"Low","ecc-gcp-213":"Low","ecc-gcp-229":"High","ecc-gcp-130":"Info","ecc-gcp-006":"Low","ecc-gcp-012":"High","ecc-gcp-335":"High","ecc-gcp-307":"Low","ecc-gcp-030":"Low","ecc-gcp-057":"High","ecc-gcp-450":"Medium","ecc-gcp-015":"High","ecc-gcp-266":"High","ecc-gcp-178":"High","ecc-gcp-293":"Info","ecc-gcp-291":"Info","ecc-gcp-043":"Low","ecc-gcp-438":"Info","ecc-gcp-039":"Low","ecc-gcp-187":"Low","ecc-gcp-340":"High","ecc-gcp-177":"Info","ecc-gcp-058":"High","ecc-gcp-302":"High","ecc-gcp-181":"Info","ecc-gcp-264":"Info","ecc-gcp-082":"High","ecc-gcp-209":"Info","ecc-gcp-162":"Info","ecc-gcp-323":"High","ecc-gcp-013":"High","ecc-gcp-386":"High","ecc-gcp-077":"Low","ecc-gcp-194":"Low","ecc-gcp-066":"Medium","ecc-gcp-007":"Low","ecc-gcp-143":"High","ecc-gcp-129":"Low","ecc-gcp-248":"Info","ecc-gcp-046":"Low","ecc-gcp-091":"Low","ecc-gcp-088":"Info","ecc-gcp-205":"High","ecc-gcp-027":"Low","ecc-gcp-038":"Low","ecc-gcp-138":"High","ecc-gcp-292":"High","ecc-gcp-295":"High","ecc-gcp-241":"High","ecc-gcp-010":"High","ecc-gcp-257":"High","ecc-gcp-221":"High","ecc-gcp-005":"Low","ecc-gcp-120":"High","ecc-gcp-300":"High","ecc-gcp-283":"Low","ecc-gcp-011":"High","ecc-gcp-265":"Low","ecc-gcp-121":"Low","ecc-gcp-245":"Info","ecc-gcp-144":"High","ecc-gcp-009":"Low","ecc-gcp-016":"Low","ecc-gcp-059":"High","ecc-gcp-337":"Low","ecc-gcp-111":"Low","ecc-gcp-128":"Low","ecc-gcp-263":"Info","ecc-gcp-031":"Low","ecc-gcp-313":"High","ecc-gcp-163":"Medium","ecc-gcp-442":"Medium","ecc-gcp-334":"High","ecc-gcp-060":"High","ecc-gcp-191":"Info","ecc-gcp-204":"High","ecc-gcp-316":"Info","ecc-gcp-198":"High","ecc-gcp-029":"Low","ecc-gcp-028":"Low","ecc-gcp-047":"Low","ecc-gcp-276":"High","ecc-gcp-444":"Medium","ecc-gcp-232":"Low","ecc-gcp-318":"High","ecc-gcp-287":"Info","ecc-gcp-195":"Low","ecc-gcp-183":"High","ecc-gcp-165":"High","ecc-gcp-037":"Low","ecc-gcp-299":"High","ecc-gcp-387":"Info","ecc-gcp-151":"Low","ecc-gcp-451":"Medium","ecc-gcp-201":"High","ecc-gcp-385":"High","ecc-gcp-142":"Info","ecc-gcp-113":"Low","ecc-gcp-024":"Low","ecc-gcp-040":"Low","ecc-gcp-412":"High","ecc-gcp-107":"Info","ecc-gcp-225":"High","ecc-gcp-217":"Info","ecc-gcp-169":"Low","ecc-gcp-231":"Low","ecc-gcp-192":"High","ecc-gcp-280":"Low","ecc-gcp-003":"Low","ecc-gcp-315":"High","ecc-gcp-033":"Low","ecc-gcp-452":"Medium","ecc-gcp-278":"Low","ecc-gcp-014":"Low","ecc-gcp-099":"Medium","ecc-gcp-254":"High","ecc-gcp-079":"Low","ecc-gcp-434":"Low","ecc-gcp-240":"High","ecc-gcp-089":"Low","ecc-gcp-133":"High","ecc-gcp-090":"Low","ecc-gcp-109":"Low","ecc-gcp-436":"Low","ecc-gcp-210":"Info","ecc-gcp-170":"Low","ecc-gcp-400":"High","ecc-gcp-134":"High","ecc-gcp-233":"High","ecc-gcp-279":"Low","ecc-gcp-114":"Low","ecc-gcp-116":"Low","ecc-gcp-305":"Low","ecc-gcp-286":"High","ecc-gcp-447":"Medium","ecc-gcp-180":"Info","ecc-gcp-171":"Low","ecc-gcp-062":"Low","ecc-gcp-132":"Low","ecc-gcp-110":"Low","ecc-gcp-223":"High","ecc-gcp-189":"Info","ecc-gcp-190":"High","ecc-gcp-115":"Low","ecc-gcp-176":"High","ecc-gcp-261":"High","ecc-gcp-053":"High","ecc-gcp-303":"Low","ecc-gcp-236":"Info","ecc-gcp-247":"Info","ecc-gcp-086":"Info","ecc-gcp-222":"Info","ecc-gcp-197":"Info","ecc-gcp-048":"High","ecc-gcp-274":"High","ecc-gcp-150":"Low","ecc-gcp-243":"High","ecc-gcp-282":"Low","ecc-gcp-140":"Medium","ecc-gcp-055":"High","ecc-gcp-239":"High","ecc-gcp-285":"Low","ecc-gcp-312":"High","ecc-gcp-249":"High","ecc-gcp-219":"High","ecc-gcp-018":"Low","ecc-gcp-188":"Low","ecc-gcp-179":"Info","ecc-gcp-042":"Low","ecc-gcp-182":"Info","ecc-gcp-071":"Low","ecc-gcp-137":"Info","ecc-gcp-298":"Info","ecc-gcp-112":"Low","ecc-gcp-317":"Low","ecc-gcp-242":"Low","ecc-gcp-034":"Low","ecc-gcp-216":"Info","ecc-gcp-453":"Medium","ecc-gcp-211":"Low","ecc-gcp-135":"High","ecc-aws-235":"Low","ecc-aws-511":"High","ecc-aws-190":"Low","ecc-aws-091":"High","ecc-aws-295":"Low","ecc-aws-286":"Info","ecc-aws-529":"Info","ecc-aws-493":"High","ecc-aws-483":"Info","ecc-aws-383":"Medium","ecc-aws-118":"High","ecc-aws-435":"High","ecc-aws-144":"High","ecc-aws-414":"Medium","ecc-aws-156":"High","ecc-aws-363":"High","ecc-aws-229":"Info","ecc-aws-296":"Low","ecc-aws-313":"High","ecc-aws-378":"Medium","ecc-aws-302":"High","ecc-aws-342":"Low","ecc-aws-187":"High","ecc-aws-245":"High","ecc-aws-101":"High","ecc-aws-189":"Info","ecc-aws-465":"High","ecc-aws-572":"Info","ecc-aws-506":"High","ecc-aws-214":"Low","ecc-aws-153":"High","ecc-aws-083":"Low","ecc-aws-122":"Low","ecc-aws-265":"High","ecc-aws-147":"Low","ecc-aws-129":"Low","ecc-aws-401":"Medium","ecc-aws-062":"Low","ecc-aws-250":"Info","ecc-aws-188":"Medium","ecc-aws-004":"Low","ecc-aws-332":"High","ecc-aws-040":"Low","ecc-aws-347":"High","ecc-aws-269":"Info","ecc-aws-280":"High","ecc-aws-534":"Info","ecc-aws-531":"High","ecc-aws-471":"High","ecc-aws-498":"High","ecc-aws-447":"High","ecc-aws-395":"Medium","ecc-aws-548":"Info","ecc-aws-277":"High","ecc-aws-204":"High","ecc-aws-539":"High","ecc-aws-501":"High","ecc-aws-573":"High","ecc-aws-186":"Low","ecc-aws-485":"High","ecc-aws-263":"Medium","ecc-aws-552":"High","ecc-aws-032":"Low","ecc-aws-337":"High","ecc-aws-439":"High","ecc-aws-050":"High","ecc-aws-406":"Medium","ecc-aws-230":"High","ecc-aws-171":"Low","ecc-aws-267":"Low","ecc-aws-031":"Low","ecc-aws-169":"Low","ecc-aws-451":"Low","ecc-aws-208":"High","ecc-aws-038":"High","ecc-aws-371":"Low","ecc-aws-049":"Info","ecc-aws-530":"Low","ecc-aws-407":"Medium","ecc-aws-340":"Low","ecc-aws-310":"Low","ecc-aws-257":"Info","ecc-aws-180":"High","ecc-aws-196":"Low","ecc-aws-251":"Low","ecc-aws-312":"High","ecc-aws-386":"Medium","ecc-aws-492":"High","ecc-aws-124":"High","ecc-aws-537":"Low","ecc-aws-333":"Low","ecc-aws-096":"Low","ecc-aws-170":"Low","ecc-aws-481":"Low","ecc-aws-428":"Medium","ecc-aws-036":"Low","ecc-aws-314":"Low","ecc-aws-017":"High","ecc-aws-008":"Info","ecc-aws-521":"Low","ecc-aws-335":"Info","ecc-aws-014":"Low","ecc-aws-139":"Info","ecc-aws-090":"Low","ecc-aws-355":"High","ecc-aws-323":"High","ecc-aws-432":"Medium","ecc-aws-490":"High","ecc-aws-143":"High","ecc-aws-437":"High","ecc-aws-455":"High","ecc-aws-227":"Low","ecc-aws-443":"Low","ecc-aws-356":"Low","ecc-aws-219":"Low","ecc-aws-311":"Low","ecc-aws-053":"High","ecc-aws-290":"Low","ecc-aws-106":"Low","ecc-aws-125":"Low","ecc-aws-162":"Medium","ecc-aws-058":"Medium","ecc-aws-317":"High","ecc-aws-459":"High","ecc-aws-427":"Medium","ecc-aws-119":"Low","ecc-aws-325":"High","ecc-aws-514":"Info","ecc-aws-266":"Low","ecc-aws-474":"High","ecc-aws-182":"Low","ecc-aws-175":"Low","ecc-aws-061":"Info","ecc-aws-456":"Low","ecc-aws-489":"Info","ecc-aws-042":"Low","ecc-aws-418":"Medium","ecc-aws-502":"High","ecc-aws-560":"Info","ecc-aws-543":"Low","ecc-aws-519":"High","ecc-aws-288":"Info","ecc-aws-350":"High","ecc-aws-066":"Low","ecc-aws-370":"High","ecc-aws-136":"Low","ecc-aws-298":"High","ecc-aws-361":"High","ecc-aws-412":"Medium","ecc-aws-025":"High","ecc-aws-300":"Low","ecc-aws-322":"High","ecc-aws-499":"Info","ecc-aws-431":"Medium","ecc-aws-120":"Low","ecc-aws-225":"High","ecc-aws-151":"Low","ecc-aws-109":"Medium","ecc-aws-415":"Medium","ecc-aws-166":"Low","ecc-aws-012":"Low","ecc-aws-134":"Low","ecc-aws-052":"Low","ecc-aws-221":"Low","ecc-aws-400":"Medium","ecc-aws-087":"Low","ecc-aws-233":"Low","ecc-aws-441":"High","ecc-aws-419":"Medium","ecc-aws-104":"Info","ecc-aws-013":"Low","ecc-aws-547":"High","ecc-aws-308":"Low","ecc-aws-234":"Low","ecc-aws-100":"Low","ecc-aws-238":"High","ecc-aws-044":"Medium","ecc-aws-377":"Medium","ecc-aws-382":"Medium","ecc-aws-448":"High","ecc-aws-003":"Low","ecc-aws-423":"Medium","ecc-aws-272":"Low","ecc-aws-244":"Info","ecc-aws-168":"Low","ecc-aws-174":"Info","ecc-aws-149":"Low","ecc-aws-152":"High","ecc-aws-115":"Low","ecc-aws-364":"Low","ecc-aws-137":"Low","ecc-aws-253":"Low","ecc-aws-246":"Low","ecc-aws-094":"Low","ecc-aws-201":"High","ecc-aws-260":"High","ecc-aws-462":"High","ecc-aws-045":"Info","ecc-aws-316":"High","ecc-aws-133":"High","ecc-aws-145":"Low","ecc-aws-262":"High","ecc-aws-158":"Medium","ecc-aws-092":"High","ecc-aws-403":"Medium","ecc-aws-528":"High","ecc-aws-222":"High","ecc-aws-446":"High","ecc-aws-209":"High","ecc-aws-223":"Info","ecc-aws-055":"High","ecc-aws-015":"Low","ecc-aws-191":"High","ecc-aws-059":"High","ecc-aws-289":"Low","ecc-aws-409":"Medium","ecc-aws-268":"High","ecc-aws-334":"Low","ecc-aws-105":"High","ecc-aws-461":"High","ecc-aws-157":"Medium","ecc-aws-540":"High","ecc-aws-020":"Medium","ecc-aws-028":"Low","ecc-aws-546":"Low","ecc-aws-287":"High","ecc-aws-056":"High","ecc-aws-469":"High","ecc-aws-079":"Low","ecc-aws-281":"Low","ecc-aws-460":"High","ecc-aws-477":"Info","ecc-aws-388":"Medium","ecc-aws-070":"Info","ecc-aws-057":"High","ecc-aws-453":"Low","ecc-aws-307":"High","ecc-aws-473":"High","ecc-aws-113":"Medium","ecc-aws-048":"Info","ecc-aws-408":"Medium","ecc-aws-495":"High","ecc-aws-420":"Medium","ecc-aws-479":"Info","ecc-aws-035":"Low","ecc-aws-577":"Info","ecc-aws-258":"Low","ecc-aws-138":"Low","ecc-aws-454":"High","ecc-aws-095":"Low","ecc-aws-167":"Low","ecc-aws-429":"Medium","ecc-aws-413":"Medium","ecc-aws-436":"Info","ecc-aws-108":"High","ecc-aws-007":"High","ecc-aws-074":"Low","ecc-aws-224":"Low","ecc-aws-226":"High","ecc-aws-193":"High","ecc-aws-199":"Info","ecc-aws-328":"High","ecc-aws-054":"Low","ecc-aws-476":"Info","ecc-aws-445":"High","ecc-aws-135":"Low","ecc-aws-405":"Medium","ecc-aws-107":"Medium","ecc-aws-067":"Low","ecc-aws-254":"High","ecc-aws-273":"Low","ecc-aws-264":"Info","ecc-aws-433":"High","ecc-aws-203":"High","ecc-aws-538":"Low","ecc-aws-176":"Low","ecc-aws-011":"Info","ecc-aws-482":"High","ecc-aws-544":"High","ecc-aws-444":"High","ecc-aws-146":"Low","ecc-aws-504":"High","ecc-aws-112":"High","ecc-aws-159":"Medium","ecc-aws-425":"Medium","ecc-aws-185":"High","ecc-aws-417":"Medium","ecc-aws-128":"Low","ecc-aws-399":"Medium","ecc-aws-195":"High","ecc-aws-507":"High","ecc-aws-131":"Low","ecc-aws-285":"High","ecc-aws-154":"High","ecc-aws-255":"Low","ecc-aws-496":"Low","ecc-aws-179":"Low","ecc-aws-293":"Low","ecc-aws-198":"High","ecc-aws-205":"High","ecc-aws-016":"High","ecc-aws-030":"Low","ecc-aws-006":"High","ecc-aws-142":"Low","ecc-aws-114":"Low","ecc-aws-318":"Low","ecc-aws-132":"Low","ecc-aws-304":"Low","ecc-aws-387":"Medium","ecc-aws-368":"High","ecc-aws-576":"High","ecc-aws-194":"High","ecc-aws-394":"Medium","ecc-aws-218":"Low","ecc-aws-082":"Low","ecc-aws-275":"High","ecc-aws-117":"High","ecc-aws-005":"Low","ecc-aws-034":"Low","ecc-aws-366":"High","ecc-aws-305":"High","ecc-aws-183":"Low","ecc-aws-510":"Medium","ecc-aws-063":"Low","ecc-aws-292":"Low","ecc-aws-127":"Low","ecc-aws-148":"Low","ecc-aws-390":"Medium","ecc-aws-099":"Low","ecc-aws-207":"High","ecc-aws-488":"Info","ecc-aws-398":"Medium","ecc-aws-217":"High","ecc-aws-026":"Low","ecc-aws-391":"Medium","ecc-aws-421":"Medium","ecc-aws-177":"Low","ecc-aws-341":"High","ecc-aws-228":"Info","ecc-aws-467":"High","ecc-aws-438":"High","ecc-aws-440":"High","ecc-aws-571":"High","ecc-aws-276":"High","ecc-aws-344":"High","ecc-aws-319":"High","ecc-aws-081":"Low","ecc-aws-380":"Medium","ecc-aws-486":"Info","ecc-aws-520":"High","ecc-aws-271":"Low","ecc-aws-309":"High","ecc-aws-084":"Low","ecc-aws-023":"Low","ecc-aws-259":"Low","ecc-aws-392":"Medium","ecc-aws-475":"High","ecc-aws-278":"Low","ecc-aws-360":"Low","ecc-aws-294":"High","ecc-aws-022":"Medium","ecc-aws-150":"Low","ecc-aws-121":"High","ecc-aws-522":"Low","ecc-aws-284":"High","ecc-aws-303":"High","ecc-aws-426":"Medium","ecc-aws-324":"High","ecc-aws-524":"Low","ecc-aws-098":"Low","ecc-aws-033":"Low","ecc-aws-197":"Low","ecc-aws-069":"Low","ecc-aws-068":"Low","ecc-aws-155":"High","ecc-aws-039":"Low","ecc-aws-430":"Medium","ecc-aws-527":"Low","ecc-aws-513":"Low","ecc-aws-358":"Low","ecc-aws-478":"Info","ecc-aws-002":"Low","ecc-aws-464":"Low","ecc-aws-037":"Low","ecc-aws-500":"High","ecc-aws-545":"High","ecc-aws-181":"Low","ecc-aws-279":"Low","ecc-aws-093":"Low","ecc-aws-164":"Low","ecc-aws-354":"Info","ecc-aws-352":"High","ecc-aws-085":"Info","ecc-aws-021":"High","ecc-aws-215":"Low","ecc-aws-102":"Low","ecc-aws-362":"High","ecc-aws-365":"Low","ecc-aws-160":"Medium","ecc-aws-075":"Low","ecc-aws-076":"Low","ecc-aws-029":"Low","ecc-aws-338":"Low","ecc-aws-089":"Low","ecc-aws-236":"Info","ecc-aws-343":"High","ecc-aws-116":"Low","ecc-aws-165":"Low","ecc-aws-270":"High","ecc-aws-326":"High","ecc-aws-372":"High","ecc-aws-336":"Low","ecc-aws-536":"Medium","ecc-aws-512":"High","ecc-aws-367":"Info","ecc-aws-282":"High","ecc-aws-452":"Low","ecc-aws-348":"Low","ecc-aws-019":"Low","ecc-aws-301":"Info","ecc-aws-450":"Low","ecc-aws-024":"Low","ecc-aws-097":"Low","ecc-aws-103":"High","ecc-aws-424":"Medium","ecc-aws-043":"Medium","ecc-aws-051":"Info","ecc-aws-080":"Low","ecc-aws-468":"Medium","ecc-aws-434":"High","ecc-aws-331":"Info","ecc-aws-487":"High","ecc-aws-484":"Info","ecc-aws-389":"Medium","ecc-aws-071":"Low","ecc-aws-410":"Medium","ecc-aws-047":"Info","ecc-aws-532":"High","ecc-aws-192":"High","ecc-aws-220":"Low","ecc-aws-141":"Low","ecc-aws-533":"Medium","ecc-aws-018":"Medium","ecc-aws-470":"High","ecc-aws-346":"High","ecc-aws-239":"High","ecc-aws-060":"Low","ecc-aws-376":"High","ecc-aws-232":"High","ecc-aws-009":"Low","ecc-aws-001":"Low","ecc-aws-379":"Medium","ecc-aws-283":"Low","ecc-aws-216":"High","ecc-aws-553":"Info","ecc-aws-541":"High","ecc-aws-480":"High","ecc-aws-237":"Low","ecc-aws-353":"Low","ecc-aws-211":"High","ecc-aws-422":"Medium","ecc-aws-375":"High","ecc-aws-542":"Info","ecc-aws-163":"Info","ecc-aws-349":"High","ecc-aws-526":"Low","ecc-aws-248":"Low","ecc-aws-345":"Low","ecc-aws-330":"High","ecc-aws-297":"High","ecc-aws-213":"High","ecc-aws-327":"High","ecc-aws-072":"Low","ecc-aws-202":"High","ecc-aws-010":"Low","ecc-aws-299":"High","ecc-aws-525":"Low","ecc-aws-178":"Info","ecc-aws-315":"Low","ecc-aws-369":"High","ecc-aws-472":"Low","ecc-aws-210":"Low","ecc-aws-351":"High","ecc-aws-130":"Low","ecc-aws-200":"High","ecc-aws-252":"Low","ecc-aws-111":"Info","ecc-aws-458":"Info","ecc-aws-321":"High","ecc-aws-123":"Low","ecc-aws-517":"High","ecc-aws-126":"Low","ecc-aws-466":"High","ecc-aws-396":"Medium","ecc-aws-381":"Medium","ecc-aws-241":"High","ecc-aws-357":"High","ecc-aws-088":"High","ecc-aws-242":"Info","ecc-aws-212":"High","ecc-aws-077":"Low","ecc-aws-339":"High","ecc-aws-385":"Medium","ecc-aws-515":"Info","ecc-aws-173":"Low","ecc-aws-110":"Low","ecc-aws-397":"Medium","ecc-aws-065":"Low","ecc-aws-508":"High","ecc-aws-140":"Low","ecc-aws-206":"High","ecc-aws-416":"Medium","ecc-aws-463":"Info","ecc-aws-384":"Medium","ecc-aws-503":"High","ecc-aws-516":"Info","ecc-aws-161":"Medium","ecc-aws-359":"High","ecc-aws-373":"Low","ecc-aws-509":"Low","ecc-aws-575":"Info","ecc-aws-086":"Low","ecc-aws-329":"High","ecc-aws-073":"Medium","ecc-aws-523":"Low","ecc-aws-261":"Medium","ecc-aws-274":"High","ecc-aws-184":"Low","ecc-aws-491":"Low","ecc-aws-393":"Medium","ecc-aws-411":"Medium","ecc-aws-027":"Low","ecc-aws-374":"Info","ecc-aws-256":"Low","ecc-aws-291":"High","ecc-aws-494":"High","ecc-aws-497":"Low","ecc-aws-064":"High","ecc-aws-320":"High","ecc-aws-449":"High","ecc-aws-172":"Low","ecc-aws-231":"High","ecc-aws-535":"Info","ecc-aws-240":"Info","ecc-aws-249":"Info","ecc-aws-078":"Low","ecc-aws-457":"High","ecc-aws-404":"Medium","ecc-aws-243":"High","ecc-aws-046":"Low","ecc-aws-505":"High","ecc-aws-247":"High","ecc-aws-306":"High","ecc-aws-402":"Medium","ecc-aws-518":"Medium","ecc-aws-442":"Low","ecc-aws-041":"Medium","ecc-k8s-016":"High","ecc-k8s-067":"Info","ecc-k8s-081":"Low","ecc-k8s-062":"High","ecc-k8s-044":"Low","ecc-k8s-049":"High","ecc-k8s-024":"Low","ecc-k8s-032":"High","ecc-k8s-038":"High","ecc-k8s-002":"High","ecc-k8s-013":"Info","ecc-k8s-070":"High","ecc-k8s-092":"Low","ecc-k8s-012":"Info","ecc-k8s-061":"Low","ecc-k8s-008":"Low","ecc-k8s-030":"Low","ecc-k8s-005":"Low","ecc-k8s-072":"Info","ecc-k8s-007":"Low","ecc-k8s-035":"Low","ecc-k8s-064":"High","ecc-k8s-052":"Medium","ecc-k8s-066":"Low","ecc-k8s-031":"High","ecc-k8s-026":"Low","ecc-k8s-017":"Info","ecc-k8s-071":"High","ecc-k8s-087":"Info","ecc-k8s-059":"Info","ecc-k8s-023":"High","ecc-k8s-060":"Low","ecc-k8s-079":"Info","ecc-k8s-053":"Medium","ecc-k8s-074":"Info","ecc-k8s-033":"Low","ecc-k8s-043":"Low","ecc-k8s-027":"Low","ecc-k8s-047":"High","ecc-k8s-041":"Low","ecc-k8s-075":"Medium","ecc-k8s-014":"Info","ecc-k8s-080":"Info","ecc-k8s-037":"Low","ecc-k8s-028":"Low","ecc-k8s-069":"Info","ecc-k8s-054":"Low","ecc-k8s-088":"Info","ecc-k8s-006":"Low","ecc-k8s-003":"High","ecc-k8s-042":"Low","ecc-k8s-077":"Low","ecc-k8s-040":"Low","ecc-k8s-021":"High","ecc-k8s-068":"Info","ecc-k8s-010":"High","ecc-k8s-051":"Medium","ecc-k8s-063":"High","ecc-k8s-019":"Info","ecc-k8s-022":"Low","ecc-k8s-025":"Low","ecc-k8s-058":"Info","ecc-k8s-001":"Info","ecc-k8s-065":"High","ecc-k8s-039":"Low","ecc-k8s-036":"Low","ecc-k8s-048":"High","ecc-k8s-015":"Low","ecc-k8s-057":"High","ecc-k8s-086":"Info","ecc-k8s-020":"Info","ecc-k8s-078":"Info","ecc-k8s-034":"High","ecc-k8s-056":"Low","ecc-k8s-045":"Low","ecc-k8s-076":"Info","ecc-k8s-004":"Low","ecc-k8s-082":"Low","ecc-k8s-018":"Low","ecc-k8s-011":"High","ecc-k8s-050":"Medium","ecc-k8s-009":"Info"} \ No newline at end of file diff --git a/tests/tests_metrics/mock_files/rulesets/caas-settings/RULES_TO_STANDARDS.json.gz b/tests/tests_metrics/mock_files/rulesets/caas-settings/RULES_TO_STANDARDS.json.gz new file mode 100644 index 000000000..bf868986e --- /dev/null +++ b/tests/tests_metrics/mock_files/rulesets/caas-settings/RULES_TO_STANDARDS.json.gz @@ -0,0 +1 @@ +{"ecc-azure-056":{"Standard1":["v7 (13)","v8 (3.1)"],"CFB":["v1.4.0 (8.4)","v1.5.0 (8.4)","v2.0.0 (8.4)"],"CJIS":["(5.1.1.1)"],"CMMC":["v2.0 (SC.L2-3.13.10)"],"Standard2":["19 (APO14,DSS06)"],"Standard4":["v4 (DCS-01,DSP-01,DSP-06,DSP-17,GRC-03)"],"DA":["(Article_8.2)"],"Standard10":["(D1.G.IT.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-12,SI-12)"],"Standard6":["2016_679 (Article_5)"],"Standard15":["(00.a)"],"Standard14":["27001_2013 (A.18.1.5,A.6.2.2,A.8.1.3)","27002_2013 (18.1.5,6.2.2,8.1.3)","27002_2022 (5.10,5.9,8.1)","27017_2015 (18.1.5,6.2.2,8.1.3)","27018_2019 (18.1,6.2,8)","27701_2019 (6.15.1.5,6.3.2.2,6.5.1.3)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800_53 Rev5 (CM-12,SI-12)"],"Standard9":["v1.1 (PR.IP-6)"],"Standard7":["(2)"],"Standard8":["v4.0 (9.4.2)"],"s202":["(CC6.1,CC6.7)"]},"ecc-azure-016":{"Standard1":["v7 (3.1)","v8 (7.5,7.6)"],"BSA":["v3 (DS-6,PV-5)"],"CFB":["v1.4.0 (4.2.1)","v1.5.0 (4.2.1)","v2.0.0 (4.2.1)"],"CJIS":["(5.10.1.3)"],"CMMC":["v2.0 (RA.L2-3.11.2,SI.L1-3.14.5)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (TVM-07)"],"DA":["(Article_7.2,Article_8.1,Article_8.2)"],"Standard10":["(D3.DC.Th.B.1)"],"Standard3":["Standard11_800_53_Rev5 (RA-05)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(10.m)"],"Standard14":["27001_2013 (A.12.6.1)","27002_2013 (12.6.1)","27002_2022 (8.8)","27017_2015 (12.6.1)","27018_2019 (12.6)","27701_2019 (6.9.6.1)"],"Standard11":["800-171 Rev2 (3.11.2)","800_53 Rev5 (RA-5)"],"Standard9":["v1.1 (DE.CM-8)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (11.2)","v4.0 (11.3.1,11.3.2.1,6.4.1)"],"s202":["(CC6.6,CC7.1)"]},"ecc-azure-353":{"Standard1":["v7 (1.4,1.5)","v8 (1.1)"],"BSA":["v3 (AM-1,AM-3)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)","v4.0 (11.2,12.5.1,9.5.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-azure-057":{"Standard1":["v7 (10)","v8 (11.1)"],"CFB":["v1.4.0 (8.6)","v1.5.0 (8.5)","v2.0.0 (8.5)"],"Standard2":["19 (APO14)"],"Standard4":["v4 (BCR-08,GRC-03)"],"CE":["v2.2 (Back_up_your_data)"],"DA":["(Article_11.1)"],"Standard10":["(D1.RM.RMP.B.1,D5.IR.Pl.B.5,D5.IR.Pl.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CP-02,CP-10)"],"Standard6":["2016_679 (Article_32.1.c)"],"Standard5":["(164.308.a.7.ii.A)"],"Standard15":["(09.l)"],"Standard14":["27001_2013 (A.12.3.1)","27002_2013 (12.3.1)","27002_2022 (8.13)","27017_2015 (12.3.1)","27018_2019 (12.3.1)","27701_2019 (6.9.3.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-009-6_Requirement_R1_Part_1.3)"],"Standard11":["800_53 Rev5 (CP-10,CP-2)"],"Standard9":["v1.1 (ID.SC-5,PR.IP-9)"],"Standard7":["(6s)"],"s202":["(A1.2)"]},"ecc-azure-108":{"Standard1":["v7 (5)","v8 (4.6)"],"CFB":["v1.4.0 (3.7)","v1.5.0 (3.9)","v2.0.0 (3.9)"],"CJIS":["(5.13.1.1)"],"CMMC":["v2.0 (SC.L2-3.13.15)"],"Standard2":["19 (BAI09)"],"DA":["(Article_8.4.b,Article_8.4.e)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,MA-04)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(07.c)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard11":["800_53 Rev5 (CM-7,MA-4)"],"Standard9":["v1.1 (PR.IP-1,PR.IP-2,PR.PT-4)"],"Standard8":["v4.0 (1.2.5)"],"s202":["(CC5.2)"]},"ecc-azure-038":{"Standard1":["v7 (6.2)","v8 (8.2)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CFB":["v1.4.0 (5.1.5)","v1.5.0 (5.1.5)","v2.0.0 (5.1.5)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.2,10.3)","v4.0 (5.3.4,6.4.1,6.4.2)"],"s202":["(CC7.2)"]},"ecc-azure-310":{"Standard1":["v7 (8.1)","v8 (10.1)"],"BSA":["v3 (ES-2)"],"CFB":["v1.5.0 (2.1.6)","v2.0.0 (2.1.6)"],"CJIS":["(5.10.4.2,5.13.3,5.13.4.2)"],"CMMC":["v2.0 (SI.L1-3.14.2)"],"Standard2":["19 (APO13,DSS05)"],"Standard4":["v4 (TVM-02,UEM-09)"],"CE":["v2.2 (A6.2.2,A8.1,Malware_protection)"],"DA":["(Article_14.e,Article_9.1)"],"Standard10":["(D3.PC.Im.B.4)"],"Standard3":["Standard11_800_53_Rev5 (SI-03)"],"Standard5":["(164.308.a.5.ii.B)"],"Standard15":["(09.j)"],"Standard14":["27001_2013 (A.12.2.1)","27002_2013 (12.2.1)","27002_2022 (8.1,8.7)","27017_2015 (12.2.1)","27018_2019 (12.2)","27701_2019 (6.9.2.1)"],"Standard12":["(CIP-007-6_Requirement_R3_Part_3.1,CIP-007-6_Requirement_R3_Part_3.2)"],"Standard11":["800-171 Rev2 (3.14.2)","800_53 Rev5 (SI-3)"],"Standard9":["v1.1 (DE.CM-4)"],"Standard8":["v3.2.1 (5.1)","v4.0 (5.1.1,5.2.1,5.2.2,5.3.2)"],"s202":["(CC6.8)"]},"ecc-azure-270":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (PV-6)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.e)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.5.1,2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]},"ecc-azure-367":{"Standard1":["v7 (12)","v8 (13.5)"],"BSA":["v3 (IM-7,PA-6)"],"CJIS":["(5.10.1.1,5.4.3,5.5.6,5.6.4)"],"CMMC":["v2.0 (AC.L2-3.1.12,AC.L2-3.1.13,AC.L2-3.1.14,AC.L2-3.1.15,AC.L2-3.1.18,SC.L2-3.13.7)"],"Standard4":["v4 (HRS-04)"],"DA":["(Article_14.d)"],"Standard3":["Standard11_800_53_Rev5 (AC-17,AC-17.01,SC-07,SI-04)"],"Standard5":["(164.308.a.3.ii.B,164.308.a.4.ii.B,164.308.a.4.ii.C)"],"Standard15":["(01.y)"],"Standard14":["27001_2013 (A.12.5.1)","27002_2013 (12.5.1)","27002_2022 (8.19)","27017_2015 (12.5.1)","27018_2019 (12.5)","27701_2019 (6.9.5.1)"],"Standard12":["(CIP-003-8_Requirement_R2,CIP-005-7_Requirement_R1_Part_1.4,CIP-010-4_Requirement_R4)"],"Standard11":["800-171 Rev2 (3.1.12)","800_53 Rev5 (AC-17,AC-17.1,SC-7,SI-4)"],"Standard9":["v1.1 (DE.CM-7,PR.AC-3,PR.AC-7,PR.MA-2)"],"Standard8":["v4.0 (8.4.3)"],"s202":["(CC6.6)"]},"ecc-azure-314":{"Standard1":["v7 (6.3)","v8 (8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"Standard13":["v1.0.0 (3.1.16)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.2,10.2.4,10.2.5,10.3)","v4.0 (10.2.1)"],"s202":["(CC7.2)"]},"ecc-azure-039":{"Standard1":["v7 (6.7)","v8 (8.11)"],"BSA":["v3 (DS-7,LT-1,LT-2,LT-5)"],"CFB":["v1.4.0 (5.2.1)","v1.5.0 (5.2.1)","v2.0.0 (5.2.1)"],"CJIS":["(5.4.1,5.4.3)"],"CMMC":["v2.0 (AU.L2-3.3.5)"],"Standard4":["v4 (LOG-05)"],"DA":["(Article_15.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-06,AU-06.01,AU-06.05,AU-07.01)"],"Standard5":["(164.308.a.1.ii.D,164.312.b)"],"Standard14":["27001_2013 (A.12.4.1,A.16.1.4)","27002_2013 (12.4.1,16.1.4)","27002_2022 (5.25)","27017_2015 (12.4.1,16.1.4)","27018_2019 (12.4.1,6.1.4)","27701_2019 (6.13.1.4,6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.4)"],"Standard11":["800-171 Rev2 (3.3.3,3.3.5)","800_53 Rev5 (AU-6,AU-6.1,AU-6.5,AU-7)"],"Standard9":["v1.1 (DE.AE-2,PR.PT-1,RS.AN-1)"],"Standard8":["v3.2.1 (10.6,10.6.1,10.6.2)","v4.0 (10.4.1.1,10.4.2,10.4.3)"],"s202":["(CC4.1,CC7.3)"]},"ecc-azure-044":{"Standard1":["v7 (6.7)","v8 (8.11)"],"BSA":["v3 (DS-7,LT-1,LT-2,LT-5)"],"CFB":["v1.4.0 (5.2.7)","v1.5.0 (5.2.5)","v2.0.0 (5.2.5)"],"CJIS":["(5.4.1,5.4.3)"],"CMMC":["v2.0 (AU.L2-3.3.5)"],"Standard4":["v4 (LOG-05)"],"DA":["(Article_15.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-06,AU-06.01,AU-06.05,AU-07.01)"],"Standard5":["(164.308.a.1.ii.D,164.312.b)"],"Standard14":["27001_2013 (A.12.4.1,A.16.1.4)","27002_2013 (12.4.1,16.1.4)","27002_2022 (5.25)","27017_2015 (12.4.1,16.1.4)","27018_2019 (12.4.1,6.1.4)","27701_2019 (6.13.1.4,6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.4)"],"Standard11":["800-171 Rev2 (3.3.3,3.3.5)","800_53 Rev5 (AU-6,AU-6.1,AU-6.5,AU-7)"],"Standard9":["v1.1 (DE.AE-2,PR.PT-1,RS.AN-1)"],"Standard8":["v3.2.1 (10.6,10.6.1,10.6.2)","v4.0 (10.4.1.1,10.4.2,10.4.3)"],"s202":["(CC4.1,CC7.3)"]},"ecc-azure-042":{"Standard1":["v7 (6.7)","v8 (8.11)"],"BSA":["v3 (DS-7,LT-1,LT-2,LT-5)"],"CFB":["v1.4.0 (5.2.3)","v1.5.0 (5.2.3)","v2.0.0 (5.2.3)"],"CJIS":["(5.4.1,5.4.3)"],"CMMC":["v2.0 (AU.L2-3.3.5)"],"Standard4":["v4 (LOG-05)"],"DA":["(Article_15.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-06,AU-06.01,AU-06.05,AU-07.01)"],"Standard5":["(164.308.a.1.ii.D,164.312.b)"],"Standard14":["27001_2013 (A.12.4.1,A.16.1.4)","27002_2013 (12.4.1,16.1.4)","27002_2022 (5.25)","27017_2015 (12.4.1,16.1.4)","27018_2019 (12.4.1,6.1.4)","27701_2019 (6.13.1.4,6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.4)"],"Standard11":["800-171 Rev2 (3.3.3,3.3.5)","800_53 Rev5 (AU-6,AU-6.1,AU-6.5,AU-7)"],"Standard9":["v1.1 (DE.AE-2,PR.PT-1,RS.AN-1)"],"Standard8":["v3.2.1 (10.6,10.6.1,10.6.2)","v4.0 (10.4.1.1,10.4.2,10.4.3)"],"s202":["(CC4.1,CC7.3)"]},"ecc-azure-068":{"Standard1":["v7 (6.7)","v8 (8.11)"],"BSA":["v3 (DS-7,LT-1,LT-2,LT-5)"],"CFB":["v1.4.0 (5.2.6)","v1.5.0 (5.2.4)","v2.0.0 (5.2.4)"],"CJIS":["(5.4.1,5.4.3)"],"CMMC":["v2.0 (AU.L2-3.3.5)"],"Standard4":["v4 (LOG-05)"],"DA":["(Article_15.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-06,AU-06.01,AU-06.05,AU-07.01)"],"Standard5":["(164.308.a.1.ii.D,164.312.b)"],"Standard14":["27001_2013 (A.12.4.1,A.16.1.4)","27002_2013 (12.4.1,16.1.4)","27002_2022 (5.25)","27017_2015 (12.4.1,16.1.4)","27018_2019 (12.4.1,6.1.4)","27701_2019 (6.13.1.4,6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.4)"],"Standard11":["800-171 Rev2 (3.3.3,3.3.5)","800_53 Rev5 (AU-6,AU-6.1,AU-6.5,AU-7)"],"Standard9":["v1.1 (DE.AE-2,PR.PT-1,RS.AN-1)"],"Standard8":["v3.2.1 (10.6,10.6.1,10.6.2)","v4.0 (10.4.1.1,10.4.2,10.4.3)"],"s202":["(CC4.1,CC7.3)"]},"ecc-azure-137":{"Standard1":["v7 (10)","v8 (11.1)"],"Standard2":["19 (APO14)"],"Standard4":["v4 (BCR-08,GRC-03)"],"CE":["v2.2 (Back_up_your_data)"],"DA":["(Article_11.1)"],"Standard10":["(D1.RM.RMP.B.1,D5.IR.Pl.B.5,D5.IR.Pl.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CP-02,CP-10)"],"Standard6":["2016_679 (Article_32.1.c)"],"Standard5":["(164.308.a.7.ii.A)"],"Standard15":["(09.l)"],"Standard14":["27001_2013 (A.12.3.1)","27002_2013 (12.3.1)","27002_2022 (8.13)","27017_2015 (12.3.1)","27018_2019 (12.3.1)","27701_2019 (6.9.3.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-009-6_Requirement_R1_Part_1.3)"],"Standard11":["800_53 Rev5 (CP-10,CP-2)"],"Standard9":["v1.1 (ID.SC-5,PR.IP-9)"],"Standard7":["(6s)"],"s202":["(A1.2)"]},"ecc-azure-163":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (NS-2)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-219":{"Standard1":["v7 (6.2)","v8 (8.2)"],"BSA":["v3 (LT-3)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.2,10.3)","v4.0 (5.3.4,6.4.1,6.4.2)"],"s202":["(CC7.2)"]},"ecc-azure-299":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.5.1,2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]},"ecc-azure-043":{"Standard1":["v7 (6.7)","v8 (8.11)"],"BSA":["v3 (DS-7,LT-1,LT-2,LT-5)"],"CFB":["v1.4.0 (5.2.4)","v1.5.0 (5.2.4)","v2.0.0 (5.2.4)"],"CJIS":["(5.4.1,5.4.3)"],"CMMC":["v2.0 (AU.L2-3.3.5)"],"Standard4":["v4 (LOG-05)"],"DA":["(Article_15.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-06,AU-06.01,AU-06.05,AU-07.01)"],"Standard5":["(164.308.a.1.ii.D,164.312.b)"],"Standard14":["27001_2013 (A.12.4.1,A.16.1.4)","27002_2013 (12.4.1,16.1.4)","27002_2022 (5.25)","27017_2015 (12.4.1,16.1.4)","27018_2019 (12.4.1,6.1.4)","27701_2019 (6.13.1.4,6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.4)"],"Standard11":["800-171 Rev2 (3.3.3,3.3.5)","800_53 Rev5 (AU-6,AU-6.1,AU-6.5,AU-7)"],"Standard9":["v1.1 (DE.AE-2,PR.PT-1,RS.AN-1)"],"Standard8":["v3.2.1 (10.6,10.6.1,10.6.2)","v4.0 (10.4.1.1,10.4.2,10.4.3)"],"s202":["(CC4.1,CC7.3)"]},"ecc-azure-021":{"Standard1":["v7 (3.1)","v8 (7.5,7.6)"],"BSA":["v3 (DS-6,PV-5)"],"CFB":["v1.4.0 (4.2.3)","v1.5.0 (4.2.3)","v2.0.0 (4.2.3)"],"CJIS":["(5.10.1.3)"],"CMMC":["v2.0 (RA.L2-3.11.2,SI.L1-3.14.5)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (TVM-07)"],"DA":["(Article_7.2,Article_8.1,Article_8.2)"],"Standard10":["(D3.DC.Th.B.1)"],"Standard3":["Standard11_800_53_Rev5 (RA-05)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(10.m)"],"Standard14":["27001_2013 (A.12.6.1)","27002_2013 (12.6.1)","27002_2022 (8.8)","27017_2015 (12.6.1)","27018_2019 (12.6)","27701_2019 (6.9.6.1)"],"Standard11":["800-171 Rev2 (3.11.2)","800_53 Rev5 (RA-5)"],"Standard9":["v1.1 (DE.CM-8)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (11.2)","v4.0 (11.3.1,11.3.2.1,6.4.1)"],"s202":["(CC6.6,CC7.1)"]},"ecc-azure-117":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (DP-4,DP-5)"],"CFB":["v1.4.0 (7.7)","v1.5.0 (7.6)","v2.0.0 (7.7)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.1.1,3.3.2,3.3.3,3.5.1.2,3.5.1.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-azure-161":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (NS-2)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-214":{"Standard1":["v7 (8.1)","v8 (10.1)"],"BSA":["v3 (LT-1)"],"CFB":["v1.5.0 (2.1.13)","v2.0.0 (2.1.13)"],"CJIS":["(5.10.4.2,5.13.3,5.13.4.2)"],"CMMC":["v2.0 (SI.L1-3.14.2)"],"Standard2":["19 (APO13,DSS05)"],"Standard4":["v4 (TVM-02,UEM-09)"],"CE":["v2.2 (A6.2.2,A8.1,Malware_protection)"],"DA":["(Article_14.e,Article_9.1)"],"Standard10":["(D3.PC.Im.B.4)"],"Standard3":["Standard11_800_53_Rev5 (SI-03)"],"Standard5":["(164.308.a.5.ii.B)"],"Standard15":["(09.j)"],"Standard14":["27001_2013 (A.12.2.1)","27002_2013 (12.2.1)","27002_2022 (8.1,8.7)","27017_2015 (12.2.1)","27018_2019 (12.2)","27701_2019 (6.9.2.1)"],"Standard12":["(CIP-007-6_Requirement_R3_Part_3.1,CIP-007-6_Requirement_R3_Part_3.2)"],"Standard11":["800-171 Rev2 (3.14.2)","800_53 Rev5 (SI-3)"],"Standard9":["v1.1 (DE.CM-4)"],"Standard8":["v3.2.1 (5.1)","v4.0 (5.1.1,5.2.1,5.2.2,5.3.2)"],"s202":["(CC6.8)"]},"ecc-azure-205":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.1.1,3.3.2,3.3.3,3.5.1.2,3.5.1.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-azure-331":{"Standard1":["v7 (5.1,6.3)","v8 (4.1,8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4,PV-1,PV-3)"],"CJIS":["(5.4.1,5.7.1)"],"CMMC":["v2.0 (AU.L2-3.3.1,CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04,LOG-08)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_10.7,Article_8.4.b,Article_8.4.e)"],"Standard10":["(D1.G.SP.B.2,D2.MA.Ma.B.1,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12,CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_30,Article_32)"],"Standard15":["(08.k,09.aa,10.k)"],"Standard14":["27001_2013 (A.12.4.1,A.14.2.5)","27002_2013 (12.4.1,14.2.5)","27002_2022 (5.28,8.15,8.27,8.9)","27017_2015 (12.4.1,14.2.5)","27018_2019 (12.4.1,14)","27701_2019 (6.11.2.5,6.9.4.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (AU-12,AU-3,AU-3.1,CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (DE.AE-3,PR.IP-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.2,10.2.4,10.2.5,10.3,2.2)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.5.1,10.2.1,2.1.1,2.2.1,2.2.6)"],"s202":["(CC7.2,CC8.1)"]},"ecc-azure-024":{"Standard1":["v7 (14.4,14.8,16.4,16.5)","v8 (3.10)"],"BSA":["v3 (DP-3)"],"CFB":["v1.4.0 (4.3.1)","v1.5.0 (4.3.1)","v2.0.0 (4.3.1)"],"CJIS":["(5.10.1.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IVS-03)"],"DA":["(Article_8.2,Article_8.3.a,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12,D3.PC.Am.B.13)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,SC-08)"],"Standard6":["2016_679 (Article_32.1.a,Article_44)"],"Standard5":["(164.312.e.1)"],"Standard15":["(06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1,A.18.1.3)","27002_2013 (10.1.1,13.2.1,18.1.3)","27002_2022 (5.14,5.33,8.24)","27017_2015 (10.1.1,13.2.1,18.1.3)","27018_2019 (10.1.1,13.2.1,18.1.3)","27701_2019 (6.10.2.1,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.8,3.5.10)","800_53 Rev5 (AC-17.2,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-2)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1.1,4.1,4.1.1,8.2.1)","v4.0 (2.2.7,4.1.1,4.2.1.2,4.2.2,8.3.2)"],"s202":["(CC6.1,CC6.7)"]},"ecc-azure-224":{"Standard1":["v7 (6.2)","v8 (8.2)"],"BSA":["v3 (LT-3)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.2,10.3)","v4.0 (5.3.4,6.4.1,6.4.2)"],"s202":["(CC7.2)"]},"ecc-azure-123":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (PV-1)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-176":{"Standard1":["v7 (12.7)","v8 (13.3,13.8)"],"BSA":["v3 (NS-5)"],"CJIS":["(5.10.1.3)"],"CMMC":["v2.0 (SI.L2-3.14.6,SI.L2-3.14.7)"],"Standard4":["v4 (IVS-09)"],"DA":["(Article_14.a,Article_8.2,Article_8.4.b,Article_9.1)"],"Standard10":["(D3.DC.An.B.1,D3.DC.Ev.B.3,D3.PC.Im.B.1)"],"Standard3":["Standard11_800_53_Rev5 (SI-04,SI-04.04)"],"Standard15":["(09.ab,09.m)"],"Standard14":["27001_2013 (A.13.1.1)","27002_2013 (13.1.1)","27002_2022 (8.16,8.21,8.8)","27017_2015 (13.1.1)","27018_2019 (13.1)","27701_2019 (6.10.1.1)"],"Standard12":["(CIP-005-7_Requirement_R1_Part_1.5)"],"Standard11":["800-171 Rev2 (3.14.6)","800_53 Rev5 (SI-4,SI-4.4)"],"Standard9":["v1.1 (DE.CM-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (11.1,11.4)","v4.0 (11.5.1,12.10.5,6.4.2)"],"s202":["(CC6.6,CC7.2)"]},"ecc-azure-122":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (PV-1)"],"CFB":["v1.5.0 (6.4)","v2.0.0 (6.4)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-133":{"Standard1":["v7 (1.4,1.5)","v8 (1.1)"],"BSA":["v3 (AM-1,AM-3)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)","v4.0 (11.2,12.5.1,9.5.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-azure-027":{"Standard1":["v7 (6.3)","v8 (8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CFB":["v1.4.0 (4.3.3)","v1.5.0 (4.3.3)","v2.0.0 (4.3.3)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.2,10.2.4,10.2.5,10.3)","v4.0 (10.2.1)"],"s202":["(CC7.2)"]},"ecc-azure-127":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (PV-1)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-146":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (DP-8)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-236":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (PV-2)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-232":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (PV-4)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.5.1,2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]},"ecc-azure-015":{"Standard1":["v7 (6)","v8 (8.10)"],"BSA":["v3 (LT-6)"],"CFB":["v1.4.0 (4.1.3)","v1.5.0 (4.1.6)","v2.0.0 (4.1.6)"],"CJIS":["(5.4.6,5.4.7)"],"Standard4":["v4 (LOG-02)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-11)"],"Standard6":["2016_679 (Article_5.1.e)"],"Standard15":["(09.ac)"],"Standard14":["27001_2013 (A.12.4.2,A.18.1.3)","27002_2013 (12.4.2,18.1.3)","27002_2022 (5.28)","27017_2015 (12.4.2,18.1.3)","27018_2019 (12.4.2,18.1)","27701_2019 (6.15.1.3,6.9.4.2)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.3)"],"Standard11":["800_53 Rev5 (AU-11)"],"Standard8":["v3.2.1 (10.7)","v4.0 (10.5.1)"],"s202":["(C1.1)"]},"ecc-azure-156":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (NS-2)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-321":{"Standard1":["v7 (6.3)","v8 (8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"Standard13":["v1.0.0 (3.1.23)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.2,10.2.4,10.2.5,10.3)","v4.0 (10.2.1)"],"s202":["(CC7.2)"]},"ecc-azure-344":{"Standard1":["v7 (3.1)","v8 (7.5,7.6)"],"BSA":["v3 (DS-6,PV-5)"],"CJIS":["(5.10.1.3)"],"CMMC":["v2.0 (RA.L2-3.11.2,SI.L1-3.14.5)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (TVM-07)"],"DA":["(Article_7.2,Article_8.1,Article_8.2,Article_8.4.f)"],"Standard10":["(D3.DC.Th.B.1)"],"Standard3":["Standard11_800_53_Rev5 (RA-05)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(10.m)"],"Standard14":["27001_2013 (A.12.6.1)","27002_2013 (12.6.1)","27002_2022 (8.8)","27017_2015 (12.6.1)","27018_2019 (12.6)","27701_2019 (6.9.6.1)"],"Standard11":["800-171 Rev2 (3.11.2)","800_53 Rev5 (RA-5)"],"Standard9":["v1.1 (DE.CM-8)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (11.2)","v4.0 (11.3.1,11.3.2.1,6.4.1)"],"s202":["(CC6.6,CC7.1)"]},"ecc-azure-119":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (PV-1)"],"CFB":["v1.5.0 (6.4)","v2.0.0 (6.4)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-234":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (PV-4)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.5.1,2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]},"ecc-azure-053":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (DP-4,DP-5)"],"CFB":["v1.4.0 (7.2)","v1.5.0 (7.2)","v2.0.0 (7.3)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.1.1,3.3.2,3.3.3,3.5.1.2,3.5.1.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-azure-126":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (PV-1)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-111":{"Standard1":["v7 (12)","v8 (12.3)"],"CFB":["v1.4.0 (4.3.7)","v1.5.0 (4.3.7)","v2.0.0 (4.3.7)"],"CJIS":["(5.10.1)"],"CMMC":["v2.0 (SC.L2-3.13.15)"],"Standard2":["19 (APO01,APO03)"],"DA":["(Article_8.4.b,Article_8.4.e)"],"Standard3":["Standard11_800_53_Rev5 (CM-06,CM-07,SC-23)"],"Standard6":["2016_679 (Article_32)"],"Standard5":["(164.312.e.1)"],"Standard15":["(09.m)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard11":["800_53 Rev5 (CM-6,CM-7,SC-23)"],"Standard9":["v1.1 (PR.AC-7,PR.DS-2)"],"Standard8":["v3.2.1 (8.3)"],"s202":["(CC5.2)"]},"ecc-azure-304":{"Standard1":["v7 (11.1,14.4,14.8,16.4,16.51,5.1)","v8 (3.10,4.2)"],"BSA":["v3 (DP-3,PV-1)"],"CJIS":["(5.10.1,5.10.1.2.1,5.7.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.6,SC.L2-3.13.8)"],"Standard2":["19 (BAI10,DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IVS-03,IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.c,Article_8.4.e)"],"Standard10":["(D3.PC.Am.B.12,D3.PC.Am.B.13,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09,SC-08)"],"Standard6":["2016_679 (Article_32,Article_32.1.a,Article_44)"],"Standard5":["(164.312.e.1)"],"Standard15":["(06.c,06.d,09.m,09.n,09.v,09.y,10.k)"],"Standard14":["27001_2013 (A.10.1.1,A.13.1.2,A.13.2.1,A.18.1.3)","27002_2013 (10.1.1,13.1.2,13.2.1,18.1.3)","27002_2022 (5.14,5.33,8.21,8.24,8.9)","27017_2015 (10.1.1,13.1.2,13.2.1,18.1.3)","27018_2019 (10.1.1,13.1,13.2.1,18.1.3)","27701_2019 (6.10.1.2,6.10.2.1,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-005-7_Requirement_R2_Part_2.2,CIP-010-4_Requirement_R1_Part_1.1,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.8,3.4.2,3.5.10)","800_53 Rev5 (AC-17.2,AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-2,PR.IP-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (1.1.1,2.1.1,4.1,4.1.1,8.2.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1,2.2.7,4.1.1,4.2.1.2,4.2.2,8.3.2)"],"s202":["(CC5.2,CC6.1,CC6.6,CC6.7)"]},"ecc-azure-162":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (NS-2)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-348":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.5.1,2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]},"ecc-azure-220":{"Standard1":["v7 (6.2)","v8 (8.2)"],"BSA":["v3 (LT-3)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.2,10.3)","v4.0 (5.3.4,6.4.1,6.4.2)"],"s202":["(CC7.2)"]},"ecc-azure-284":{"Standard1":["v7 (14.8)","v8 (3.11)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.1.1,3.3.2,3.3.3,3.5.1.2,3.5.1.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-azure-069":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (PV-1,PV-3)"],"CFB":["v1.4.0 (9.8)","v1.5.0 (9.8)","v2.0.0 (9.8)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.e,Article_8.4.f)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.5.1,2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]},"ecc-azure-014":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (DP-4,DP-5)"],"CFB":["v1.4.0 (4.1.2)","v1.5.0 (4.1.5)","v2.0.0 (4.1.5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.1.1,3.3.2,3.3.3,3.5.1.2,3.5.1.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-azure-048":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (PV-1)"],"CFB":["v1.4.0 (6.1)","v1.5.0 (6.1)","v2.0.0 (6.1)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-341":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (PV-1)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_14.a,Article_8.2,Article_8.4.b,Article_9.1)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-110":{"Standard1":["v7 (6)","v8 (3.14)"],"BSA":["v3 (LT-3)"],"CFB":["v1.4.0 (3.11)","v1.5.0 (3.14)","v2.0.0 (3.14)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (DSP-17,LOG-04)"],"DA":["(Article_8.4.e)"],"Standard3":["Standard11_800_53_Rev5 (AC-06.09,AU-02,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800_53 Rev5 (AC-6.9,AU-12,AU-2)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.2.1,11.5)","v4.0 (10.2.1.1)"],"s202":["(CC6.1)"]},"ecc-azure-328":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (DP-4,DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.1.1,3.3.2,3.3.3,3.5.1.2,3.5.1.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-azure-160":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (NS-2)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-128":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (PV-1)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-165":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (NS-2)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-096":{"Standard1":["v7 (8.1)","v8 (10.1)"],"BSA":["v3 (ES-2)"],"CFB":["v1.4.0 (2.3)","v1.5.0 (2.1.4)","v2.0.0 (2.1.4)"],"CJIS":["(5.10.4.2,5.13.3,5.13.4.2)"],"CMMC":["v2.0 (SI.L1-3.14.2)"],"Standard2":["19 (APO13,DSS05)"],"Standard4":["v4 (TVM-02,UEM-09)"],"CE":["v2.2 (A6.2.2,A8.1,Malware_protection)"],"DA":["(Article_14.e,Article_9.1)"],"Standard10":["(D3.PC.Im.B.4)"],"Standard3":["Standard11_800_53_Rev5 (SI-03)"],"Standard5":["(164.308.a.5.ii.B)"],"Standard15":["(09.j)"],"Standard14":["27001_2013 (A.12.2.1)","27002_2013 (12.2.1)","27002_2022 (8.1,8.7)","27017_2015 (12.2.1)","27018_2019 (12.2)","27701_2019 (6.9.2.1)"],"Standard12":["(CIP-007-6_Requirement_R3_Part_3.1,CIP-007-6_Requirement_R3_Part_3.2)"],"Standard11":["800-171 Rev2 (3.14.2)","800_53 Rev5 (SI-3)"],"Standard9":["v1.1 (DE.CM-4)"],"Standard8":["v3.2.1 (5.1)","v4.0 (5.1.1,5.2.1,5.2.2,5.3.2)"],"s202":["(CC6.8)"]},"ecc-azure-150":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (NS-1)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-020":{"Standard1":["v7 (3.1)","v8 (7.5,7.6)"],"BSA":["v3 (DS-6,PV-5)"],"CFB":["v1.4.0 (4.2.2)","v1.5.0 (4.2.2)","v2.0.0 (4.2.2)"],"CJIS":["(5.10.1.3)"],"CMMC":["v2.0 (RA.L2-3.11.2,SI.L1-3.14.5)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (TVM-07)"],"DA":["(Article_7.2,Article_8.1,Article_8.2)"],"Standard10":["(D3.DC.Th.B.1)"],"Standard3":["Standard11_800_53_Rev5 (RA-05)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(10.m)"],"Standard14":["27001_2013 (A.12.6.1)","27002_2013 (12.6.1)","27002_2022 (8.8)","27017_2015 (12.6.1)","27018_2019 (12.6)","27701_2019 (6.9.6.1)"],"Standard11":["800-171 Rev2 (3.11.2)","800_53 Rev5 (RA-5)"],"Standard9":["v1.1 (DE.CM-8)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (11.2)","v4.0 (11.3.1,11.3.2.1,6.4.1)"],"s202":["(CC6.6,CC7.1)"]},"ecc-azure-215":{"Standard1":["v7 (12.5,12.8,5.1)","v8 (13.6,4.1)"],"BSA":["v3 (LT-4)"],"CJIS":["(5.10.1.1,5.10.1.3,5.7.1)"],"CMMC":["v2.0 (AC.L2-3.1.3,CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SI.L2-3.14.3,SI.L2-3.14.6)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-03,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.DC.Ev.B.1,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10,SI-04,SI-04.04)"],"Standard6":["2016_679 (Article_30,Article_32)"],"Standard5":["(164.312.b)"],"Standard15":["(08.k,09.aa,09.m,10.k)"],"Standard14":["27001_2013 (A.12.4.1,A.13.1.1,A.14.2.5)","27002_2013 (12.4.1,13.1.1,14.2.5)","27002_2022 (8.15,8.16,8.20,8.27,8.9)","27017_2015 (12.4.1,13.1.1,14.2.5)","27018_2019 (12.4.1,13.1,14)","27701_2019 (6.10.1.1,6.11.2.5,6.9.4.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-007-6_Requirement_R4_Part_4.1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.13.1,3.14.6,3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8,SI-4,SI-4.4)"],"Standard9":["v1.1 (DE.CM-1,PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.5.1,2.1.1,2.2.1,2.2.6)"],"s202":["(CC7.2,CC8.1)"]},"ecc-azure-159":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (NS-2)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-364":{"Standard1":["v7 (1.4,1.5)","v8 (1.1)"],"BSA":["v3 (AM-1,AM-3)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)","v4.0 (11.2,12.5.1,9.5.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-azure-030":{"Standard1":["v7 (6.3)","v8 (8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CFB":["v1.4.0 (4.3.5)","v1.5.0 (4.3.5)","v2.0.0 (4.3.5)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.2,10.2.4,10.2.5,10.3)","v4.0 (10.2.1)"],"s202":["(CC7.2)"]},"ecc-azure-239":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (PV-2)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b,Article_8.4.d)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.5.1,2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]},"ecc-azure-106":{"Standard1":["v7 (6)","v8 (3.14)"],"BSA":["v3 (LT-3)"],"CFB":["v1.4.0 (3.3)","v1.5.0 (3.5)","v2.0.0 (3.5)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (DSP-17,LOG-04)"],"DA":["(Article_8.4.e)"],"Standard3":["Standard11_800_53_Rev5 (AC-06.09,AU-02,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800_53 Rev5 (AC-6.9,AU-12,AU-2)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.2.1,11.5)","v4.0 (10.2.1.1)"],"s202":["(CC6.1)"]},"ecc-azure-228":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (PV-4)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.5.1,2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]},"ecc-azure-045":{"Standard1":["v7 (6.7)","v8 (8.11)"],"BSA":["v3 (DS-7,LT-1,LT-2,LT-5)"],"CFB":["v1.4.0 (5.2.8)","v1.5.0 (5.2.6)","v2.0.0 (5.2.6)"],"CJIS":["(5.4.1,5.4.3)"],"CMMC":["v2.0 (AU.L2-3.3.5)"],"Standard4":["v4 (LOG-05)"],"DA":["(Article_15.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-06,AU-06.01,AU-06.05,AU-07.01)"],"Standard5":["(164.308.a.1.ii.D,164.312.b)"],"Standard14":["27001_2013 (A.12.4.1,A.16.1.4)","27002_2013 (12.4.1,16.1.4)","27002_2022 (5.25)","27017_2015 (12.4.1,16.1.4)","27018_2019 (12.4.1,6.1.4)","27701_2019 (6.13.1.4,6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.4)"],"Standard11":["800-171 Rev2 (3.3.3,3.3.5)","800_53 Rev5 (AU-6,AU-6.1,AU-6.5,AU-7)"],"Standard9":["v1.1 (DE.AE-2,PR.PT-1,RS.AN-1)"],"Standard8":["v3.2.1 (10.6,10.6.1,10.6.2)","v4.0 (10.4.1.1,10.4.2,10.4.3)"],"s202":["(CC4.1,CC7.3)"]},"ecc-azure-343":{"Standard1":["v7 (3.1)","v8 (7.5,7.6)"],"BSA":["v3 (DS-6,PV-5)"],"CJIS":["(5.10.1.3)"],"CMMC":["v2.0 (RA.L2-3.11.2,SI.L1-3.14.5)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (TVM-07)"],"DA":["(Article_7.2,Article_8.1,Article_8.2,Article_8.4.f)"],"Standard10":["(D3.DC.Th.B.1)"],"Standard3":["Standard11_800_53_Rev5 (RA-05)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(10.m)"],"Standard14":["27001_2013 (A.12.6.1)","27002_2013 (12.6.1)","27002_2022 (8.8)","27017_2015 (12.6.1)","27018_2019 (12.6)","27701_2019 (6.9.6.1)"],"Standard11":["800-171 Rev2 (3.11.2)","800_53 Rev5 (RA-5)"],"Standard9":["v1.1 (DE.CM-8)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (11.2)","v4.0 (11.3.1,11.3.2.1,6.4.1)"],"s202":["(CC6.6,CC7.1)"]},"ecc-azure-203":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.1.1,3.3.2,3.3.3,3.5.1.2,3.5.1.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-azure-059":{"Standard1":["v7 (5)","v8 (4.6)"],"CFB":["v1.4.0 (9.1)","v1.5.0 (9.1)","v2.0.0 (9.1)"],"CJIS":["(5.13.1.1)"],"CMMC":["v2.0 (SC.L2-3.13.15)"],"Standard2":["19 (BAI09)"],"DA":["(Article_8.4.d)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,MA-04)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(07.c)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard11":["800_53 Rev5 (CM-7,MA-4)"],"Standard9":["v1.1 (PR.IP-1,PR.IP-2,PR.PT-4)"],"Standard8":["v4.0 (1.2.5)"],"s202":["(CC5.2)"]},"ecc-azure-290":{"Standard1":["v7 (16)","v8 (6.1)"],"BSA":["v3 (PA-3,PA-8)"],"CJIS":["(5.5.1,5.6.1,5.6.3.1,5.6.3.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,IA.L1-3.5.2)"],"Standard4":["v4 (IAM-01,IAM-06,IAM-07)"],"CE":["v2.2 (A7.1,A7.4,A7.5,uac)"],"DA":["(Article_8.4.c)"],"Standard10":["(D3.PC.Am.B.5,D3.PC.Am.B.6)"],"Standard3":["Standard11_800_53_Rev5 (AC-01,AC-02,AC-02.01,IA-04,IA-05)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.b,01.q)"],"Standard14":["27001_2013 (A.9.2.1,A.9.2.2,A.9.2.3)","27002_2013 (9.2.1,9.2.2,9.2.3)","27002_2022 (5.15,5.16,5.18)","27017_2015 (9.2.1,9.2.2,9.2.3)","27018_2019 (9.2.1,9.2.2,9.2.3)","27701_2019 (6.6.2.1,6.6.2.2,6.6.2.3)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.5.2)","800_53 Rev5 (AC-1,AC-2,AC-2.1,IA-4,IA-5)"],"Standard9":["v1.1 (PR.AC-1,PR.AC-4:)"],"Standard8":["v3.2.1 (7.2)","v4.0 (7.2.1,7.2.3,8.1.1,8.2.1,8.2.4)"],"s202":["(CC6.2)"]},"ecc-azure-060":{"Standard1":["v7 (5)","v8 (4.6)"],"CFB":["v1.4.0 (9.2)","v1.5.0 (9.2)","v2.0.0 (9.2)"],"CJIS":["(5.13.1.1)"],"CMMC":["v2.0 (SC.L2-3.13.15)"],"Standard2":["19 (BAI09)"],"DA":["(Article_8.4.c,Article_8.4.e)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,MA-04)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(07.c)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard11":["800_53 Rev5 (CM-7,MA-4)"],"Standard9":["v1.1 (PR.IP-1,PR.IP-2,PR.PT-4)"],"Standard8":["v4.0 (1.2.5)"],"s202":["(CC5.2)"]},"ecc-azure-009":{"Standard1":["v7 (14.6)","v8 (3.3)"],"BSA":["v3 (AM-4,IM-7,PA-7)"],"CFB":["v1.4.0 (3.5)","v1.5.0 (3.7)","v2.0.0 (3.7)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.3.b)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.9.4.1)","27002_2013 (9.4.1)","27002_2022 (5.15)","27017_2015 (9.4.1)","27018_2019 (9.4.1)","27701_2019 (6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)","v4.0 (1.3.1,7.1)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-azure-258":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (PV-2)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.5.1,2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]},"ecc-azure-033":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (DP-4,DP-5)"],"CFB":["v1.4.0 (4.6)","v1.5.0 (4.1.3)","v2.0.0 (4.1.3)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.1.1,3.3.2,3.3.3,3.5.1.2,3.5.1.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-azure-293":{"Standard1":["v7 (10.4)","v8 (11.3)"],"BSA":["v3 (BR-2,BR-3)"],"CMMC":["v2.0 (MP.L2-3.8.9)"],"Standard4":["v4 (BCR-08,CEK-03)"],"CE":["v2.2 (Back_up_your_data)"],"DA":["(Article_11.5.a,Article_11.5.b)"],"Standard3":["Standard11_800_53_Rev5 (CP-09,CP-09.08,SC-28)"],"Standard6":["2016_679 (Article_32.1.c)"],"Standard15":["(06.c,09.l)"],"Standard14":["27001_2013 (A.18.1.3)","27002_2013 (18.1.3)","27002_2022 (5.33,8.12)","27017_2015 (18.1.3)","27018_2019 (18.1.3)","27701_2019 (6.15.1.3)"],"Standard11":["800-171 Rev2 (3.8.9)","800_53 Rev5 (CP-9,CP-9.8,SC-28)"],"Standard9":["v1.1 (PR.DS-1,PR.IP-4)"],"Standard7":["(6)"],"s202":["(A1.2,CC6.4,CC6.7)"]},"ecc-azure-333":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (PV-1)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-170":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (DP-8)"],"CFB":["v1.5.0 (8.7)","v2.0.0 (8.7)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-011":{"Standard1":["v7 (10)","v8 (11.1)"],"CFB":["v1.4.0 (3.8)","v1.5.0 (3.11)","v2.0.0 (3.11)"],"Standard2":["19 (APO14)"],"Standard4":["v4 (BCR-08,GRC-03)"],"CE":["v2.2 (Back_up_your_data)"],"DA":["(Article_11.1)"],"Standard10":["(D1.RM.RMP.B.1,D5.IR.Pl.B.5,D5.IR.Pl.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CP-02,CP-10)"],"Standard6":["2016_679 (Article_32.1.c)"],"Standard5":["(164.308.a.7.ii.A)"],"Standard15":["(09.l)"],"Standard14":["27001_2013 (A.12.3.1)","27002_2013 (12.3.1)","27002_2022 (8.13)","27017_2015 (12.3.1)","27018_2019 (12.3.1)","27701_2019 (6.9.3.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-009-6_Requirement_R1_Part_1.3)"],"Standard11":["800_53 Rev5 (CP-10,CP-2)"],"Standard9":["v1.1 (ID.SC-5,PR.IP-9)"],"Standard7":["(6s)"],"s202":["(A1.2)"]},"ecc-azure-148":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (NS-2)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-180":{"Standard1":["v7 (16.2)","v8 (5.6)"],"BSA":["v3 (IM-3)"],"Standard10":["(D3.PC.Am.B.6)"],"Standard3":["Standard11_800_53_Rev5 (AC-02.01)"],"Standard15":["(01.q)"],"Standard14":["27001_2013 (A.9)","27002_2013 (9)","27002_2022 (5.15)","27017_2015 (9)","27018_2019 (9)","27701_2019 (6.6)"],"Standard11":["800_53 Rev5 (AC-2.1)"],"Standard9":["v1.1 (PR.AC-7)"],"s202":["(CC6.1)"]},"ecc-azure-158":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (NS-2)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-283":{"Standard1":["v7 (6.2)","v8 (8.2)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.2,10.3)","v4.0 (5.3.4,6.4.1,6.4.2)"],"s202":["(CC7.2)"]},"ecc-azure-216":{"Standard1":["v7 (12.5,12.8,5.1)","v8 (13.6,4.1)"],"BSA":["v3 (LT-4)"],"CJIS":["(5.10.1.1,5.10.1.3,5.7.1)"],"CMMC":["v2.0 (AC.L2-3.1.3,CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SI.L2-3.14.3,SI.L2-3.14.6)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-03,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.DC.Ev.B.1,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10,SI-04,SI-04.04)"],"Standard6":["2016_679 (Article_30,Article_32)"],"Standard5":["(164.312.b)"],"Standard15":["(08.k,09.aa,09.m,10.k)"],"Standard14":["27001_2013 (A.12.4.1,A.13.1.1,A.14.2.5)","27002_2013 (12.4.1,13.1.1,14.2.5)","27002_2022 (8.15,8.16,8.20,8.27,8.9)","27017_2015 (12.4.1,13.1.1,14.2.5)","27018_2019 (12.4.1,13.1,14)","27701_2019 (6.10.1.1,6.11.2.5,6.9.4.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-007-6_Requirement_R4_Part_4.1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.13.1,3.14.6,3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8,SI-4,SI-4.4)"],"Standard9":["v1.1 (DE.CM-1,PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.5.1,2.1.1,2.2.1,2.2.6)"],"s202":["(CC7.2,CC8.1)"]},"ecc-azure-144":{"Standard1":["v7 (11.1,5.1)","v8 (4.2,4.6)"],"BSA":["v3 (NS-2)"],"CJIS":["(5.10.1,5.13.1.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.15,SC.L2-3.13.6)"],"Standard2":["19 (BAI09,BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b,Article_8.4.e)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09,MA-04)"],"Standard6":["2016_679 (Article_32,Article_32.1.b,Article_5.1.f)"],"Standard15":["(07.c,09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2,A.14.2.5)","27002_2013 (13.1.2,14.2.5)","27002_2022 (8.21,8.27,8.9)","27017_2015 (13.1.2,14.2.5)","27018_2019 (13.1,14)","27701_2019 (6.10.1.2,6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9,MA-4)"],"Standard9":["v1.1 (PR.IP-1,PR.IP-2,PR.PT-4)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.5,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-256":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (PV-2)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.5.1,2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]},"ecc-azure-012":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (DP-4,DP-5)"],"CFB":["v1.4.0 (3.9)","v1.5.0 (3.12)","v2.0.0 (3.12)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.18.1.3)","27002_2013 (18.1.3)","27002_2022 (8.24)","27017_2015 (18.1.3)","27018_2019 (18.1.3)","27701_2019 (6.15.1.3)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.1.1,3.3.2,3.3.3,3.5.1.2,3.5.1.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-azure-025":{"Standard1":["v7 (14.4,14.8,16.4,16.5)","v8 (3.10)"],"BSA":["v3 (DP-3)"],"CFB":["v1.4.0 (4.4.1)","v1.5.0 (4.4.1)","v2.0.0 (4.4.1)"],"CJIS":["(5.10.1.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IVS-03)"],"DA":["(Article_8.2,Article_8.3.a,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12,D3.PC.Am.B.13)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,SC-08)"],"Standard6":["2016_679 (Article_32.1.a,Article_44)"],"Standard5":["(164.312.e.1)"],"Standard15":["(06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1,A.18.1.3)","27002_2013 (10.1.1,13.2.1,18.1.3)","27002_2022 (5.14,5.33,8.24)","27017_2015 (10.1.1,13.2.1,18.1.3)","27018_2019 (10.1.1,13.2.1,18.1.3)","27701_2019 (6.10.2.1,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.8,3.5.10)","800_53 Rev5 (AC-17.2,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-2)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1.1,4.1,4.1.1,8.2.1)","v4.0 (2.2.7,4.1.1,4.2.1.2,4.2.2,8.3.2)"],"s202":["(CC6.1,CC6.7)"]},"ecc-azure-121":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (PV-1)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-197":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (DP-4)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.1.1,3.3.2,3.3.3,3.5.1.2,3.5.1.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-azure-166":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (NS-2)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-371":{"Standard1":["v7 (6.3)","v8 (8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CFB":["v1.5.0 (4.4.3)","v2.0.0 (4.4.3)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.2,10.2.4,10.2.5,10.3)","v4.0 (10.2.1)"],"s202":["(CC7.2)"]},"ecc-azure-372":{"Standard1":["v7 (6.3)","v8 (8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CFB":["v1.5.0 (4.4.4)","v2.0.0 (4.4.4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.2,10.2.4,10.2.5,10.3)","v4.0 (10.2.1)"],"s202":["(CC7.2)"]},"ecc-azure-300":{"Standard1":["v7 (11.1,14.4,14.8,16.4,16.5,5.1)","v8 (3.10,4.2)"],"BSA":["v3 (DP-3,PV-1)"],"CJIS":["(5.10.1,5.10.1.2.1,5.7.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.6,SC.L2-3.13.8)"],"Standard2":["19 (BAI10,DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IVS-03,IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.2,Article_8.3.a,Article_8.4.b,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12,D3.PC.Am.B.13,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09,SC-08)"],"Standard6":["2016_679 (Article_32,Article_32.1.a,Article_44)"],"Standard5":["(164.312.e.1)"],"Standard15":["(06.c,06.d,09.m,09.n,09.v,09.y,10.k)"],"Standard14":["27001_2013 (A.10.1.1,A.13.1.2,A.13.2.1,A.18.1.3)","27002_2013 (10.1.1,13.1.2,13.2.1,18.1.3)","27002_2022 (5.14,5.33,8.21,8.24,8.9)","27017_2015 (10.1.1,13.1.2,13.2.1,18.1.3)","27018_2019 (10.1.1,13.1,13.2.1,18.1.3)","27701_2019 (6.10.1.2,6.10.2.1,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-005-7_Requirement_R2_Part_2.2,CIP-010-4_Requirement_R1_Part_1.1,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.8,3.4.2,3.5.10)","800_53 Rev5 (AC-17.2,AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-2,PR.IP-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (1.1.1,2.1.1,4.1,4.1.1,8.2.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1,2.2.7,4.1.1,4.2.1.2,4.2.2,8.3.2)"],"s202":["(CC5.2,CC6.1,CC6.6,CC6.7)"]},"ecc-azure-207":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.1.1,3.3.2,3.3.3,3.5.1.2,3.5.1.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-azure-376":{"Standard1":["v7 (8.1)","v8 (10.1)"],"BSA":["v3 (ES-2)"],"CFB":["v1.5.0 (2.1.9)","v2.0.0 (2.1.9)"],"CJIS":["(5.10.4.2,5.13.3,5.13.4.2)"],"CMMC":["v2.0 (SI.L1-3.14.2)"],"Standard2":["19 (APO13,DSS05)"],"Standard4":["v4 (TVM-02,UEM-09)"],"CE":["v2.2 (A6.2.2,A8.1,Malware_protection)"],"DA":["(Article_14.e,Article_9.1)"],"Standard10":["(D3.PC.Im.B.4)"],"Standard3":["Standard11_800_53_Rev5 (SI-03)"],"Standard5":["(164.308.a.5.ii.B)"],"Standard15":["(09.j)"],"Standard14":["27001_2013 (A.12.2.1)","27002_2013 (12.2.1)","27002_2022 (8.1,8.7)","27017_2015 (12.2.1)","27018_2019 (12.2)","27701_2019 (6.9.2.1)"],"Standard12":["(CIP-007-6_Requirement_R3_Part_3.1,CIP-007-6_Requirement_R3_Part_3.2)"],"Standard11":["800-171 Rev2 (3.14.2)","800_53 Rev5 (SI-3)"],"Standard9":["v1.1 (DE.CM-4)"],"Standard8":["v3.2.1 (5.1)","v4.0 (5.1.1,5.2.1,5.2.2,5.3.2)"],"s202":["(CC6.8)"]},"ecc-azure-098":{"Standard1":["v7 (8.1)","v8 (10.1)"],"BSA":["v3 (ES-2)"],"CFB":["v1.4.0 (2.5)","v1.5.0 (2.1.7)","v2.0.0 (2.1.7)"],"CJIS":["(5.10.4.2,5.13.3,5.13.4.2)"],"CMMC":["v2.0 (SI.L1-3.14.2)"],"Standard2":["19 (APO13,DSS05)"],"Standard4":["v4 (TVM-02,UEM-09)"],"CE":["v2.2 (A6.2.2,A8.1,Malware_protection)"],"DA":["(Article_14.e,Article_9.1)"],"Standard10":["(D3.PC.Im.B.4)"],"Standard3":["Standard11_800_53_Rev5 (SI-03)"],"Standard5":["(164.308.a.5.ii.B)"],"Standard15":["(09.j)"],"Standard14":["27001_2013 (A.12.2.1)","27002_2013 (12.2.1)","27002_2022 (8.1,8.7)","27017_2015 (12.2.1)","27018_2019 (12.2)","27701_2019 (6.9.2.1)"],"Standard12":["(CIP-007-6_Requirement_R3_Part_3.1,CIP-007-6_Requirement_R3_Part_3.2)"],"Standard11":["800-171 Rev2 (3.14.2)","800_53 Rev5 (SI-3)"],"Standard9":["v1.1 (DE.CM-4)"],"Standard8":["v3.2.1 (5.1)","v4.0 (5.1.1,5.2.1,5.2.2,5.3.2)"],"s202":["(CC6.8)"]},"ecc-azure-311":{"Standard1":["v7 (6.3)","v8 (8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"Standard13":["v1.0.0 (3.1.3)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.2,10.2.4,10.2.5,10.3)","v4.0 (10.2.1)"],"s202":["(CC7.2)"]},"ecc-azure-319":{"Standard1":["v7 (6.3)","v8 (8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"Standard13":["v1.0.0 (3.1.13)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.2,10.2.4,10.2.5,10.3)","v4.0 (10.2.1)"],"s202":["(CC7.2)"]},"ecc-azure-173":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (NS-2)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-026":{"Standard1":["v7 (6.3)","v8 (8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CFB":["v1.4.0 (4.3.2)","v1.5.0 (4.3.2)","v2.0.0 (4.3.2)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.2,10.2.4,10.2.5,10.3)","v4.0 (10.2.1)"],"s202":["(CC7.2)"]},"ecc-azure-031":{"Standard1":["v7 (6.3)","v8 (8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CFB":["v1.4.0 (4.3.6)","v1.5.0 (4.3.6)","v2.0.0 (4.3.6)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.2,10.2.4,10.2.5,10.3)","v4.0 (10.2.1)"],"s202":["(CC7.2)"]},"ecc-azure-287":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-342":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1)","27002_2013 (10.1.1,13.2.1)","27002_2022 (5.14,5.33)","27017_2015 (10.1.1,13.2.1)","27018_2019 (10.1.1,13.2.1)","27701_2019 (6.10.2.1,6.15.1.3)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.5.1,2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]},"ecc-azure-177":{"Standard1":["v7 (12.7)","v8 (13.3,13.8)"],"BSA":["v3 (NS-6)"],"CJIS":["(5.10.1.3)"],"CMMC":["v2.0 (SI.L2-3.14.6,SI.L2-3.14.7)"],"Standard4":["v4 (IVS-09)"],"DA":["(Article_14.a,Article_8.2,Article_8.4.b,Article_9.1)"],"Standard10":["(D3.DC.An.B.1,D3.DC.Ev.B.3,D3.PC.Im.B.1)"],"Standard3":["Standard11_800_53_Rev5 (SI-04,SI-04.04)"],"Standard15":["(09.ab,09.m)"],"Standard14":["27001_2013 (A.13.1.1)","27002_2013 (13.1.1)","27002_2022 (8.16,8.21,8.8)","27017_2015 (13.1.1)","27018_2019 (13.1)","27701_2019 (6.10.1.1)"],"Standard12":["(CIP-005-7_Requirement_R1_Part_1.5)"],"Standard11":["800-171 Rev2 (3.14.6)","800_53 Rev5 (SI-4,SI-4.4)"],"Standard9":["v1.1 (DE.CM-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (11.1,11.4)","v4.0 (11.5.1,12.10.5,6.4.2)"],"s202":["(CC6.6,CC7.2)"]},"ecc-azure-151":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (NS-3)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-149":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (NS-2)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-145":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (NS-2)"],"CFB":["v1.5.0 (4.5.1)","v2.0.0 (4.5.1)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-323":{"Standard1":["v7 (5.1)","v8 (4.1,4.6)"],"BSA":["v3 (PV-1,PV-3)"],"CJIS":["(5.13.1.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.15)"],"Standard2":["19 (APO13,BAI06,BAI09,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.d)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-07,CM-09,MA-04,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32,Article_32.1.b,Article_5.1.f)"],"Standard15":["(07.c,08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-7,CM-9,MA-4,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1,PR.IP-2,PR.PT-4)"],"Standard8":["v3.2.1 (2.2)","v4.0 (1.1.1,1.2.1,1.2.5,1.2.6,1.2.7,1.5.1,2.1.1,2.2.1,2.2.6)"],"s202":["(CC5.2,CC8.1)"]},"ecc-azure-227":{"Standard1":["v7 (6.2,6.3)","v8 (8.2,8.5)"],"BSA":["v3 (LT-3)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.2,10.2.4,10.2.5,10.3)","v4.0 (10.2.1,5.3.4,6.4.1,6.4.2)"],"s202":["(CC7.2)"]},"ecc-azure-277":{"Standard1":["v7 (10)","v8 (11.1)"],"BSA":["v3 (BR-1,BR-2)"],"Standard2":["19 (APO14)"],"Standard4":["v4 (BCR-08,GRC-03)"],"CE":["v2.2 (Back_up_your_data)"],"DA":["(Article_11.1)"],"Standard10":["(D1.RM.RMP.B.1,D5.IR.Pl.B.5,D5.IR.Pl.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CP-02,CP-10)"],"Standard6":["2016_679 (Article_32.1.c)"],"Standard5":["(164.308.a.7.ii.A)"],"Standard15":["(09.l)"],"Standard14":["27001_2013 (A.12.3.1)","27002_2013 (12.3.1)","27002_2022 (8.13)","27017_2015 (12.3.1)","27018_2019 (12.3.1)","27701_2019 (6.9.3.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-009-6_Requirement_R1_Part_1.3)"],"Standard11":["800_53 Rev5 (CP-10,CP-2)"],"Standard9":["v1.1 (ID.SC-5,PR.IP-9)"],"Standard7":["(6s)"],"s202":["(A1.2)"]},"ecc-azure-171":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (NS-2)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-240":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (PV-2)"],"CFB":["v1.4.0 (9.4)","v1.5.0 (9.4)","v2.0.0 (9.4)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b,Article_8.4.d)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.5.1,2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]},"ecc-azure-071":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (PV-1,PV-3)"],"CFB":["v1.4.0 (9.6)","v1.5.0 (9.6)","v2.0.0 (9.6)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.e,Article_8.4.f)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.5.1,2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]},"ecc-azure-288":{"Standard1":["v7 (5.1)","v8 (4.1)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.5.1,2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]},"ecc-azure-318":{"Standard1":["v7 (6.3)","v8 (8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"Standard13":["v1.0.0 (3.1.22)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.2,10.2.4,10.2.5,10.3)","v4.0 (10.2.1)"],"s202":["(CC7.2)"]},"ecc-azure-257":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (PV-2)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.5.1,2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]},"ecc-azure-054":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (DP-4,DP-5)"],"CFB":["v1.4.0 (7.3)","v1.5.0 (7.3)","v2.0.0 (7.4)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.1.1,3.3.2,3.3.3,3.5.1.2,3.5.1.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-azure-182":{"Standard1":["v7 (5.1)","v8 (12.5,4.1)"],"BSA":["v3 (IM-1)"],"CJIS":["(5.5.2,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IAM-14,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.d)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Am.B.6,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-02.01,CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(01.q,08.k,10.k)"],"Standard14":["27001_2013 (A.13.1.2,A.14.2.5)","27002_2013 (13.1.2,14.2.5)","27002_2022 (8.21,8.27,8.9)","27017_2015 (13.1.2,14.2.5)","27018_2019 (13.1,14)","27701_2019 (6.10.1.2,6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (AC-2.1,CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.5.1,2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]},"ecc-azure-072":{"Standard1":["v7 (13)","v8 (3.1)"],"CFB":["v1.4.0 (9.11)","v1.5.0 (9.11)","v2.0.0 (9.11)"],"CJIS":["(5.1.1.1)"],"CMMC":["v2.0 (SC.L2-3.13.10)"],"Standard2":["19 (APO14,DSS06)"],"Standard4":["v4 (DCS-01,DSP-01,DSP-06,DSP-17,GRC-03)"],"DA":["(Article_8.2)"],"Standard10":["(D1.G.IT.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-12,SI-12)"],"Standard6":["2016_679 (Article_5)"],"Standard15":["(00.a)"],"Standard14":["27001_2013 (A.14.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800_53 Rev5 (CM-12,SI-12)"],"Standard9":["v1.1 (PR.IP-6)"],"Standard7":["(2)"],"Standard8":["v4.0 (9.4.2)"],"s202":["(CC6.1,CC6.7)"]},"ecc-azure-365":{"Standard1":["v7 (1.4,1.5)","v8 (1.1)"],"BSA":["v3 (AM-1,AM-3)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)","v4.0 (11.2,12.5.1,9.5.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-azure-097":{"Standard1":["v7 (8.1)","v8 (10.1)"],"BSA":["v3 (ES-2)"],"CFB":["v1.4.0 (2.4)","v1.5.0 (2.1.5)","v2.0.0 (2.1.5)"],"CJIS":["(5.10.4.2,5.13.3,5.13.4.2)"],"CMMC":["v2.0 (SI.L1-3.14.2)"],"Standard2":["19 (APO13,DSS05)"],"Standard4":["v4 (TVM-02,UEM-09)"],"CE":["v2.2 (A6.2.2,A8.1,Malware_protection)"],"DA":["(Article_14.e,Article_9.1)"],"Standard10":["(D3.PC.Im.B.4)"],"Standard3":["Standard11_800_53_Rev5 (SI-03)"],"Standard5":["(164.308.a.5.ii.B)"],"Standard15":["(09.j)"],"Standard14":["27001_2013 (A.12.2.1)","27002_2013 (12.2.1)","27002_2022 (8.1,8.7)","27017_2015 (12.2.1)","27018_2019 (12.2)","27701_2019 (6.9.2.1)"],"Standard12":["(CIP-007-6_Requirement_R3_Part_3.1,CIP-007-6_Requirement_R3_Part_3.2)"],"Standard11":["800-171 Rev2 (3.14.2)","800_53 Rev5 (SI-3)"],"Standard9":["v1.1 (DE.CM-4)"],"Standard8":["v3.2.1 (5.1)","v4.0 (5.1.1,5.2.1,5.2.2,5.3.2)"],"s202":["(CC6.8)"]},"ecc-azure-109":{"Standard1":["v7 (6)","v8 (3.14)"],"BSA":["v3 (LT-3)"],"CFB":["v1.4.0 (3.10)","v1.5.0 (3.13)","v2.0.0 (3.13)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (DSP-17,LOG-04)"],"DA":["(Article_8.4.e)"],"Standard3":["Standard11_800_53_Rev5 (AC-06.09,AU-02,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800_53 Rev5 (AC-6.9,AU-12,AU-2)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.2.1,11.5)","v4.0 (10.2.1.1)"],"s202":["(CC6.1)"]},"ecc-azure-147":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (NS-2)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-302":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (PV-1)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-010":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (PV-1)"],"CFB":["v1.4.0 (3.6)","v1.5.0 (3.8)","v2.0.0 (3.8)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-326":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (DP-4,DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.1.1,3.3.2,3.3.3,3.5.1.2,3.5.1.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-azure-006":{"Standard1":["v7 (19.3)","v8 (17.1)"],"BSA":["v3 (IR-2)"],"CFB":["v1.4.0 (2.14)","v1.5.0 (2.3.3)","v2.0.0 (2.1.20)"],"CJIS":["(5.3.2)"],"Standard2":["19 (APO13)"],"Standard4":["v4 (BCR-01,SEF-03)"],"DA":["(Article_15.3.d,Article_9.2)"],"Standard10":["(D5.IR.Pl.B.3)"],"Standard3":["Standard11_800_53_Rev5 (IR-01,IR-07,IR-08)"],"Standard5":["(164.308.a.2)"],"Standard15":["(02.a,05.c,11.c)"],"Standard14":["27001_2013 (A.16.1.1)","27002_2013 (16.1.1)","27002_2022 (5.24)","27017_2015 (16.1.1)","27018_2019 (16.1.1)","27701_2019 (6.13.1.1)"],"Standard12":["(CIP-008-6_Requirement_R1_Part_1.3)"],"Standard11":["800_53 Rev5 (IR-1,IR-7,IR-8)"],"Standard9":["v1.1 (DE.DP-1,PR.IP-9)"],"Standard8":["v3.2.1 (12.10.3,12.10.4,12.5.3)","v4.0 (12.10.3,12.10.4)"],"s202":["(CC7.4)"]},"ecc-azure-132":{"Standard1":["v7 (16)","v8 (6.1)"],"BSA":["v3 (PA-3,PA-8)"],"CJIS":["(5.5.1,5.6.1,5.6.3.1,5.6.3.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,IA.L1-3.5.2)"],"Standard4":["v4 (IAM-01,IAM-06,IAM-07)"],"CE":["v2.2 (A7.1,A7.4,A7.5,uac)"],"DA":["(Article_8.4.c)"],"Standard10":["(D3.PC.Am.B.5,D3.PC.Am.B.6)"],"Standard3":["Standard11_800_53_Rev5 (AC-01,AC-02,AC-02.01,IA-04,IA-05)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.b,01.q)"],"Standard14":["27001_2013 (A.9.2.1,A.9.2.2,A.9.2.3)","27002_2013 (9.2.1,9.2.2,9.2.3)","27002_2022 (5.15,5.16,5.18)","27017_2015 (9.2.1,9.2.2,9.2.3)","27018_2019 (9.2.1,9.2.2,9.2.3)","27701_2019 (6.6.2.1,6.6.2.2,6.6.2.3)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.5.2)","800_53 Rev5 (AC-1,AC-2,AC-2.1,IA-4,IA-5)"],"Standard9":["v1.1 (PR.AC-1,PR.AC-4:)"],"Standard8":["v3.2.1 (7.2)","v4.0 (7.2.1,7.2.3,8.1.1,8.2.1,8.2.4)"],"s202":["(CC6.2)"]},"ecc-azure-125":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (PV-1)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-286":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-052":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (PV-1)"],"CFB":["v1.4.0 (6.6)","v1.5.0 (6.3)","v2.0.0 (6.3)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-282":{"Standard1":["v7 (14.8)","v8 (3.11)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.1.1,3.3.2,3.3.3,3.5.1.2,3.5.1.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-azure-067":{"Standard1":["v7 (6.7)","v8 (8.11)"],"BSA":["v3 (DS-7,LT-1,LT-2,LT-5)"],"CFB":["v1.4.0 (5.2.5)","v1.5.0 (5.2.3)","v2.0.0 (5.2.3)"],"CJIS":["(5.4.1,5.4.3)"],"CMMC":["v2.0 (AU.L2-3.3.5)"],"Standard4":["v4 (LOG-05)"],"DA":["(Article_15.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-06,AU-06.01,AU-06.05,AU-07.01)"],"Standard5":["(164.308.a.1.ii.D,164.312.b)"],"Standard14":["27001_2013 (A.12.4.1,A.16.1.4)","27002_2013 (12.4.1,16.1.4)","27002_2022 (5.25)","27017_2015 (12.4.1,16.1.4)","27018_2019 (12.4.1,6.1.4)","27701_2019 (6.13.1.4,6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.4)"],"Standard11":["800-171 Rev2 (3.3.3,3.3.5)","800_53 Rev5 (AU-6,AU-6.1,AU-6.5,AU-7)"],"Standard9":["v1.1 (DE.AE-2,PR.PT-1,RS.AN-1)"],"Standard8":["v3.2.1 (10.6,10.6.1,10.6.2)","v4.0 (10.4.1.1,10.4.2,10.4.3)"],"s202":["(CC4.1,CC7.3)"]},"ecc-azure-305":{"Standard1":["v7 (14.4)","v8 (3.10)"],"BSA":["v3 (DP-3)"],"CFB":["v1.4.0 (3.12)","v1.5.0 (3.15)","v2.0.0 (3.15)"],"CJIS":["(5.10.1.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IVS-03)"],"DA":["(Article_8.2,Article_8.3.a,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12,D3.PC.Am.B.13)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,SC-08)"],"Standard6":["2016_679 (Article_32.1.a,Article_44)"],"Standard5":["(164.312.e.1)"],"Standard15":["(06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1)","27002_2013 (10.1.1,13.2.1)","27002_2022 (5.14,5.33)","27017_2015 (10.1.1,13.2.1)","27018_2019 (10.1.1,13.2.1)","27701_2019 (6.10.2.1,6.15.1.3)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.8,3.5.10)","800_53 Rev5 (AC-17.2,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-2)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1.1,4.1,4.1.1,8.2.1)","v4.0 (2.2.7,4.1.1,4.2.1.2,4.2.2,8.3.2)"],"s202":["(CC6.1,CC6.7)"]},"ecc-azure-168":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (NS-2)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-139":{"Standard1":["v7 (10)","v8 (11.1)"],"Standard2":["19 (APO14)"],"Standard4":["v4 (BCR-08,GRC-03)"],"CE":["v2.2 (Back_up_your_data)"],"DA":["(Article_11.1)"],"Standard10":["(D1.RM.RMP.B.1,D5.IR.Pl.B.5,D5.IR.Pl.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CP-02,CP-10)"],"Standard6":["2016_679 (Article_32.1.c)"],"Standard5":["(164.308.a.7.ii.A)"],"Standard15":["(09.l)"],"Standard14":["27001_2013 (A.12.3.1)","27002_2013 (12.3.1)","27002_2022 (8.13)","27017_2015 (12.3.1)","27018_2019 (12.3.1)","27701_2019 (6.9.3.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-009-6_Requirement_R1_Part_1.3)"],"Standard11":["800_53 Rev5 (CP-10,CP-2)"],"Standard9":["v1.1 (ID.SC-5,PR.IP-9)"],"Standard7":["(6s)"],"s202":["(A1.2)"]},"ecc-azure-179":{"Standard1":["v7 (16.2)","v8 (5.6)"],"BSA":["v3 (IM-3)"],"Standard10":["(D3.PC.Am.B.6)"],"Standard3":["Standard11_800_53_Rev5 (AC-02.01)"],"Standard15":["(01.q)"],"Standard14":["27001_2013 (A.9)","27002_2013 (9)","27002_2022 (5.15)","27017_2015 (9)","27018_2019 (9)","27701_2019 (6.6)"],"Standard11":["800_53 Rev5 (AC-2.1)"],"Standard9":["v1.1 (PR.AC-7)"],"s202":["(CC6.1)"]},"ecc-azure-370":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (PV-1)"],"CFB":["v1.5.0 (4.5.2)","v2.0.0 (4.5.2)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-349":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.5.1,2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]},"ecc-azure-028":{"Standard1":["v7 (6.3)","v8 (8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CFB":["v1.4.0 (4.3.4)","v1.5.0 (4.3.4)","v2.0.0 (4.3.4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.2,10.2.4,10.2.5,10.3)","v4.0 (10.2.1)"],"s202":["(CC7.2)"]},"ecc-azure-141":{"Standard1":["v7 (11.1,5.1,9.5)","v8 (4.2,4.4)"],"BSA":["v3 (NS-1,NS-2,NS-3,NS-7,NS-8,PV-1)"],"CJIS":["(5.10.1,5.7.1,5.7.1.2)"],"CMMC":["v2.0 (AC.L1-3.1.20,CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.1,SC.L2-3.13.6)"],"Standard2":["19 (APO13,BAI10)"],"Standard4":["v4 (IVS-04,UEM-10)"],"CE":["v2.2 (A4.11,A4.5,A4.7,A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.3.b,Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.2,D3.PC.Im.B.5,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CA-09,CM-02,CM-06,CM-07,CM-07.01,CM-09,SC-07,SC-07.05)"],"Standard6":["2016_679 (Article_32,Article_5.1.f)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.20,8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.13.1,3.4.2)","800_53 Rev5 (AC-18,CA-9,CM-2,CM-6,CM-7,CM-7.1,CM-9,SC-7,SC-7.5)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (1.1.1,1.1.4,1.3.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.1,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-007":{"Standard1":["v7 (19.5)","v8 (17.2)"],"CFB":["v1.4.0 (2.15)","v1.5.0 (2.3.1)","v2.0.0 (2.1.18)"],"CJIS":["(5.3.1,5.3.1.1.1,5.3.1.1.2)"],"CMMC":["v2.0 (IR.L2-3.6.2)"],"Standard4":["v4 (SEF-03,SEF-07,SEF-08)"],"DA":["(Article_15.3.d,Article_9.2)"],"Standard10":["(D2.IS.Is.B.2,D2.IS.Is.B.3,D5.ER.Es.B.2)"],"Standard3":["Standard11_800_53_Rev5 (IR-06,IR-06.03)"],"Standard5":["(164.308.a.6.ii)"],"Standard15":["(05.f,05.k)"],"Standard14":["27001_2013 (A.6.1.3)","27002_2013 (6.1.3)","27002_2022 (5.20,5.24,5.5,5.6)","27017_2015 (6.1.3)","27018_2019 (6.1.3)","27701_2019 (6.3.1.3)"],"Standard12":["(CIP-008-6_Requirement_R4)"],"Standard11":["800-171 Rev2 (3.6.2)","800_53 Rev5 (IR-6,IR-6.3)"],"Standard9":["v1.1 (RS.CO-1)"],"s202":["(CC2.3)"]},"ecc-azure-356":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.5.1,2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]},"ecc-azure-346":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.5.1,2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]},"ecc-azure-267":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (PV-6)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.e)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.5.1,2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]},"ecc-azure-327":{"Standard1":["v7 (13.1)","v8 (3.1)"],"CJIS":["(5.1.1.1)"],"CMMC":["v2.0 (SC.L2-3.13.10)"],"Standard2":["19 (APO14,DSS06)"],"Standard4":["v4 (DCS-01,DSP-01,DSP-06,DSP-17,GRC-03)"],"DA":["(Article_8.2)"],"Standard10":["(D1.G.IT.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-12,SI-12)"],"Standard6":["2016_679 (Article_5)"],"Standard15":["(00.a)"],"Standard14":["27001_2013 (A.18.1.5,A.6.2.2,A.8.1.3)","27002_2013 (18.1.5,6.2.2,8.1.3)","27002_2022 (5.10,5.9,8.1)","27017_2015 (18.1.5,6.2.2,8.1.3)","27018_2019 (18.1,6.2,8)","27701_2019 (6.15.1.5,6.3.2.2,6.5.1.3)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800_53 Rev5 (CM-12,SI-12)"],"Standard9":["v1.1 (PR.IP-6)"],"Standard7":["(2)"],"Standard8":["v4.0 (9.4.2)"],"s202":["(CC6.1,CC6.7)"]},"ecc-azure-065":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (PV-1,PV-3)"],"CFB":["v1.4.0 (9.9)","v1.5.0 (9.9)","v2.0.0 (9.9)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.5.1,2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]},"ecc-azure-099":{"Standard1":["v7 (8.1)","v8 (10.1)"],"BSA":["v3 (ES-2)"],"CFB":["v1.4.0 (2.6)","v1.5.0 (2.1.8)","v2.0.0 (2.1.8)"],"CJIS":["(5.10.4.2,5.13.3,5.13.4.2)"],"CMMC":["v2.0 (SI.L1-3.14.2)"],"Standard2":["19 (APO13,DSS05)"],"Standard4":["v4 (TVM-02,UEM-09)"],"CE":["v2.2 (A6.2.2,A8.1,Malware_protection)"],"DA":["(Article_14.e,Article_9.1)"],"Standard10":["(D3.PC.Im.B.4)"],"Standard3":["Standard11_800_53_Rev5 (SI-03)"],"Standard5":["(164.308.a.5.ii.B)"],"Standard15":["(09.j)"],"Standard14":["27001_2013 (A.12.2.1)","27002_2013 (12.2.1)","27002_2022 (8.1,8.7)","27017_2015 (12.2.1)","27018_2019 (12.2)","27701_2019 (6.9.2.1)"],"Standard12":["(CIP-007-6_Requirement_R3_Part_3.1,CIP-007-6_Requirement_R3_Part_3.2)"],"Standard11":["800-171 Rev2 (3.14.2)","800_53 Rev5 (SI-3)"],"Standard9":["v1.1 (DE.CM-4)"],"Standard8":["v3.2.1 (5.1)","v4.0 (5.1.1,5.2.1,5.2.2,5.3.2)"],"s202":["(CC6.8)"]},"ecc-azure-265":{"Standard1":["v7 (3.1)","v8 (7.5,7.6)"],"BSA":["v3 (PV-5)"],"CJIS":["(5.10.1.3)"],"CMMC":["v2.0 (RA.L2-3.11.2,SI.L1-3.14.5)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (TVM-07)"],"DA":["(Article_7.2,Article_8.1,Article_8.2)"],"Standard10":["(D3.DC.Th.B.1)"],"Standard3":["Standard11_800_53_Rev5 (RA-05)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(10.m)"],"Standard14":["27001_2013 (A.12.6.1)","27002_2013 (12.6.1)","27002_2022 (8.8)","27017_2015 (12.6.1)","27018_2019 (12.6)","27701_2019 (6.9.6.1)"],"Standard11":["800-171 Rev2 (3.11.2)","800_53 Rev5 (RA-5)"],"Standard9":["v1.1 (DE.CM-8)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (11.2)","v4.0 (11.3.1,11.3.2.1,6.4.1)"],"s202":["(CC6.6,CC7.1)"]},"ecc-azure-345":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (DP-4,DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.1.1,3.3.2,3.3.3,3.5.1.2,3.5.1.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-azure-237":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (PV-2)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-196":{"Standard1":["v7 (3.1)","v8 (7.5,7.6)"],"BSA":["v3 (DP-2,IR-3,IR-5,LT-1,LT-2)"],"CJIS":["(5.10.1.3)"],"CMMC":["v2.0 (RA.L2-3.11.2,SI.L1-3.14.5)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (TVM-07)"],"DA":["(Article_7.2,Article_8.1,Article_8.2)"],"Standard10":["(D3.DC.Th.B.1)"],"Standard3":["Standard11_800_53_Rev5 (RA-05)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(10.m)"],"Standard14":["27001_2013 (A.12.6.1)","27002_2013 (12.6.1)","27002_2022 (8.8)","27017_2015 (12.6.1)","27018_2019 (12.6)","27701_2019 (6.9.6.1)"],"Standard11":["800-171 Rev2 (3.11.2)","800_53 Rev5 (RA-5)"],"Standard9":["v1.1 (DE.CM-8)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (11.2)","v4.0 (11.3.1,11.3.2.1,6.4.1)"],"s202":["(CC6.6,CC7.1)"]},"ecc-azure-094":{"Standard1":["v7 (8.1)","v8 (10.1)"],"BSA":["v3 (ES-2)"],"CFB":["v1.4.0 (2.1)","v1.5.0 (2.1.1)","v2.0.0 (2.1.1)"],"CJIS":["(5.10.4.2,5.13.3,5.13.4.2)"],"CMMC":["v2.0 (SI.L1-3.14.2)"],"Standard2":["19 (APO13,DSS05)"],"Standard4":["v4 (TVM-02,UEM-09)"],"CE":["v2.2 (A6.2.2,A8.1,Malware_protection)"],"DA":["(Article_14.e,Article_9.1)"],"Standard10":["(D3.PC.Im.B.4)"],"Standard3":["Standard11_800_53_Rev5 (SI-03)"],"Standard5":["(164.308.a.5.ii.B)"],"Standard15":["(09.j)"],"Standard14":["27001_2013 (A.12.2.1)","27002_2013 (12.2.1)","27002_2022 (8.1,8.7)","27017_2015 (12.2.1)","27018_2019 (12.2)","27701_2019 (6.9.2.1)"],"Standard12":["(CIP-007-6_Requirement_R3_Part_3.1,CIP-007-6_Requirement_R3_Part_3.2)"],"Standard11":["800-171 Rev2 (3.14.2)","800_53 Rev5 (SI-3)"],"Standard9":["v1.1 (DE.CM-4)"],"Standard8":["v3.2.1 (5.1)","v4.0 (5.1.1,5.2.1,5.2.2,5.3.2)"],"s202":["(CC6.8)"]},"ecc-azure-358":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (PV-1)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-295":{"Standard1":["v7 (16.6,4.1)","v8 (5.1)"],"BSA":["v3 (PA-4)"],"CJIS":["(5.4,5.5.1,5.6.1,5.6.3.1)"],"Standard4":["v4 (IAM-03,IAM-08,IAM-10,IAM-16)"],"CE":["v2.2 (A7.8,A7.9,uac)"],"DA":["(Article_8.2)"],"Standard3":["Standard11_800_53_Rev5 (AC-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.16,5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.3)"],"Standard11":["800_53 Rev5 (AC-2)"],"Standard9":["v1.1 (PR.AC-1)"],"Standard8":["v3.2.1 (8.1,8.1.1)","v4.0 (12.5.2)"],"s202":["(CC6.1,CC6.2,CC6.3)"]},"ecc-azure-281":{"Standard1":["v7 (5.1)","v8 (4.1)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.e,Article_8.4.f)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.5.1,2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]},"ecc-azure-357":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (PV-1)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-120":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (PV-1)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-325":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (DP-4,DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.1.1,3.3.2,3.3.3,3.5.1.2,3.5.1.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-azure-317":{"Standard1":["v7 (6.3)","v8 (8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"Standard13":["v1.0.0 (3.1.20)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.2,10.2.4,10.2.5,10.3)","v4.0 (10.2.1)"],"s202":["(CC7.2)"]},"ecc-azure-112":{"Standard1":["v7 (12.1)","v8 (12.4)"],"CFB":["v1.4.0 (6.5)","v1.5.0 (6.6)","v2.0.0 (6.6)"],"CJIS":["(5.7.1.2)"],"CMMC":["v2.0 (AC.L1-3.1.20,CA.L2-3.12.4)"],"Standard2":["19 (APO13)"],"Standard4":["v4 (IVS-08)"],"Standard10":["(D4.C.Co.B.3)"],"Standard3":["Standard11_800_53_Rev5 (PL-08)"],"Standard15":["(09.m)"],"Standard14":["27001_2013 (A.17.1.2)","27002_2013 (17.1.2)","27017_2015 (17.1.2)","27018_2019 (17)","27701_2019 (6.14.1.2)"],"Standard12":["(CIP-005-7_Requirement_R1_Part_1.2,CIP-005-7_Requirement_R2_Part_2.1)"],"Standard11":["800_53 Rev5 (PL-8)"],"Standard9":["v1.1 (ID.AM-4)"],"Standard8":["v3.2.1 (1.1.2,1.1.3,6.4.6)","v4.0 (1.2.3)"],"s202":["(CC6.1)"]},"ecc-azure-005":{"Standard1":["v7 (19.5)","v8 (17.2)"],"CFB":["v1.4.0 (2.13)","v1.5.0 (2.3.2)","v2.0.0 (2.1.19)"],"CJIS":["(5.3.1,5.3.1.1.1,5.3.1.1.2)"],"CMMC":["v2.0 (IR.L2-3.6.2)"],"Standard4":["v4 (SEF-03,SEF-07,SEF-08)"],"DA":["(Article_15.3.d)"],"Standard10":["(D2.IS.Is.B.2,D2.IS.Is.B.3,D5.ER.Es.B.2)"],"Standard3":["Standard11_800_53_Rev5 (IR-06,IR-06.03)"],"Standard5":["(164.308.a.6.ii)"],"Standard15":["(05.f,05.k)"],"Standard14":["27001_2013 (A.6.1.3)","27002_2013 (6.1.3)","27002_2022 (5.20,5.24,5.5,5.6)","27017_2015 (6.1.3)","27018_2019 (6.1.3)","27701_2019 (6.3.1.3)"],"Standard12":["(CIP-008-6_Requirement_R4)"],"Standard11":["800-171 Rev2 (3.6.2)","800_53 Rev5 (IR-6,IR-6.3)"],"Standard9":["v1.1 (RS.CO-1)"],"s202":["(CC2.3)"]},"ecc-azure-167":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (NS-2)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-374":{"Standard1":["v7 (6.7)","v8 (8.11)"],"BSA":["v3 (DS-7,LT-1,LT-2,LT-5)"],"CFB":["v1.5.0 (5.2.10)","v2.0.0 (5.2.10)"],"CJIS":["(5.4.1,5.4.3)"],"CMMC":["v2.0 (AU.L2-3.3.5)"],"Standard4":["v4 (LOG-05)"],"DA":["(Article_15.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-06,AU-06.01,AU-06.05,AU-07.01)"],"Standard5":["(164.308.a.1.ii.D,164.312.b)"],"Standard14":["27001_2013 (A.12.4.1,A.16.1.4)","27002_2013 (12.4.1,16.1.4)","27002_2022 (5.25)","27017_2015 (12.4.1,16.1.4)","27018_2019 (12.4.1,6.1.4)","27701_2019 (6.13.1.4,6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.4)"],"Standard11":["800-171 Rev2 (3.3.3,3.3.5)","800_53 Rev5 (AU-6,AU-6.1,AU-6.5,AU-7)"],"Standard9":["v1.1 (DE.AE-2,PR.PT-1,RS.AN-1)"],"Standard8":["v3.2.1 (10.6,10.6.1,10.6.2)","v4.0 (10.4.1.1,10.4.2,10.4.3)"],"s202":["(CC4.1,CC7.3)"]},"ecc-azure-272":{"Standard1":["v7 (8.1)","v8 (10.1)"],"BSA":["v3 (PV-6)"],"CJIS":["(5.10.4.2,5.13.3,5.13.4.2)"],"CMMC":["v2.0 (SI.L1-3.14.2)"],"Standard2":["19 (APO13,DSS05)"],"Standard4":["v4 (TVM-02,UEM-09)"],"CE":["v2.2 (A6.2.2,A8.1,Malware_protection)"],"DA":["(Article_14.e,Article_9.1)"],"Standard10":["(D3.PC.Im.B.4)"],"Standard3":["Standard11_800_53_Rev5 (SI-03)"],"Standard5":["(164.308.a.5.ii.B)"],"Standard15":["(09.j)"],"Standard14":["27001_2013 (A.12.2.1)","27002_2013 (12.2.1)","27002_2022 (8.1,8.7)","27017_2015 (12.2.1)","27018_2019 (12.2)","27701_2019 (6.9.2.1)"],"Standard12":["(CIP-007-6_Requirement_R3_Part_3.1,CIP-007-6_Requirement_R3_Part_3.2)"],"Standard11":["800-171 Rev2 (3.14.2)","800_53 Rev5 (SI-3)"],"Standard9":["v1.1 (DE.CM-4)"],"Standard8":["v3.2.1 (5.1)","v4.0 (5.1.1,5.2.1,5.2.2,5.3.2)"],"s202":["(CC6.8)"]},"ecc-azure-022":{"Standard1":["v7 (3.1)","v8 (7.5,7.6)"],"BSA":["v3 (DS-6,PV-5)"],"CFB":["v1.4.0 (4.2.4)","v1.5.0 (4.2.4)","v2.0.0 (4.2.4)"],"CJIS":["(5.10.1.3)"],"CMMC":["v2.0 (RA.L2-3.11.2,SI.L1-3.14.5)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (TVM-07)"],"DA":["(Article_7.2,Article_8.1,Article_8.2)"],"Standard10":["(D3.DC.Th.B.1)"],"Standard3":["Standard11_800_53_Rev5 (RA-05)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(10.m)"],"Standard14":["27001_2013 (A.12.6.1)","27002_2013 (12.6.1)","27002_2022 (8.8)","27017_2015 (12.6.1)","27018_2019 (12.6)","27701_2019 (6.9.6.1)"],"Standard11":["800-171 Rev2 (3.11.2)","800_53 Rev5 (RA-5)"],"Standard9":["v1.1 (DE.CM-8)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (11.2)","v4.0 (11.3.1,11.3.2.1,6.4.1)"],"s202":["(CC6.6,CC7.1)"]},"ecc-azure-124":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (PV-1)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-275":{"Standard1":["v7 (10)","v8 (11.1)"],"BSA":["v3 (BR-1,BR-2)"],"Standard2":["19 (APO14)"],"Standard4":["v4 (BCR-08,GRC-03)"],"CE":["v2.2 (Back_up_your_data)"],"DA":["(Article_11.1)"],"Standard10":["(D1.RM.RMP.B.1,D5.IR.Pl.B.5,D5.IR.Pl.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CP-02,CP-10)"],"Standard6":["2016_679 (Article_32.1.c)"],"Standard5":["(164.308.a.7.ii.A)"],"Standard15":["(09.l)"],"Standard14":["27001_2013 (A.12.3.1)","27002_2013 (12.3.1)","27002_2022 (8.13)","27017_2015 (12.3.1)","27018_2019 (12.3.1)","27701_2019 (6.9.3.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-009-6_Requirement_R1_Part_1.3)"],"Standard11":["800_53 Rev5 (CP-10,CP-2)"],"Standard9":["v1.1 (ID.SC-5,PR.IP-9)"],"Standard7":["(6s)"],"s202":["(A1.2)"]},"ecc-azure-354":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.5.1,2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]},"ecc-azure-095":{"Standard1":["v7 (8.1)","v8 (10.1)"],"BSA":["v3 (ES-2)"],"CFB":["v1.4.0 (2.2)","v1.5.0 (2.1.2)","v2.0.0 (2.1.2)"],"CJIS":["(5.10.4.2,5.13.3,5.13.4.2)"],"CMMC":["v2.0 (SI.L1-3.14.2)"],"Standard2":["19 (APO13,DSS05)"],"Standard4":["v4 (TVM-02,UEM-09)"],"CE":["v2.2 (A6.2.2,A8.1,Malware_protection)"],"DA":["(Article_14.e,Article_9.1)"],"Standard10":["(D3.PC.Im.B.4)"],"Standard3":["Standard11_800_53_Rev5 (SI-03)"],"Standard5":["(164.308.a.5.ii.B)"],"Standard15":["(09.j)"],"Standard14":["27001_2013 (A.12.2.1)","27002_2013 (12.2.1)","27002_2022 (8.1,8.7)","27017_2015 (12.2.1)","27018_2019 (12.2)","27701_2019 (6.9.2.1)"],"Standard12":["(CIP-007-6_Requirement_R3_Part_3.1,CIP-007-6_Requirement_R3_Part_3.2)"],"Standard11":["800-171 Rev2 (3.14.2)","800_53 Rev5 (SI-3)"],"Standard9":["v1.1 (DE.CM-4)"],"Standard8":["v3.2.1 (5.1)","v4.0 (5.1.1,5.2.1,5.2.2,5.3.2)"],"s202":["(CC6.8)"]},"ecc-azure-055":{"Standard1":["v7 (13)","v8 (3.1)"],"CFB":["v1.4.0 (8.2)","v1.5.0 (8.2)","v2.0.0 (8.2)"],"CJIS":["(5.1.1.1)"],"CMMC":["v2.0 (SC.L2-3.13.10)"],"Standard2":["19 (APO14,DSS06)"],"Standard4":["v4 (DCS-01,DSP-01,DSP-06,DSP-17,GRC-03)"],"DA":["(Article_8.2)"],"Standard10":["(D1.G.IT.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-12,SI-12)"],"Standard6":["2016_679 (Article_5)"],"Standard15":["(00.a)"],"Standard14":["27001_2013 (A.18.1.5,A.6.2.2,A.8.1.3)","27002_2013 (18.1.5,6.2.2,8.1.3)","27002_2022 (5.10,5.9,8.1)","27017_2015 (18.1.5,6.2.2,8.1.3)","27018_2019 (18.1,6.2,8)","27701_2019 (6.15.1.5,6.3.2.2,6.5.1.3)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800_53 Rev5 (CM-12,SI-12)"],"Standard9":["v1.1 (PR.IP-6)"],"Standard7":["(2)"],"Standard8":["v4.0 (9.4.2)"],"s202":["(CC6.1,CC6.7)"]},"ecc-azure-036":{"Standard1":["v7 (5)","v8 (4.6)"],"CFB":["v1.4.0 (5.1.3)","v1.5.0 (5.1.3)","v2.0.0 (5.1.3)"],"CJIS":["(5.13.1.1)"],"CMMC":["v2.0 (SC.L2-3.13.15)"],"Standard2":["19 (BAI09)"],"DA":["(Article_8.3.b)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,MA-04)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(07.c)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard11":["800_53 Rev5 (CM-7,MA-4)"],"Standard9":["v1.1 (PR.IP-1,PR.IP-2,PR.PT-4)"],"Standard8":["v4.0 (1.2.5)"],"s202":["(CC5.2)"]},"ecc-azure-340":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (PV-1)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_14.a,Article_8.2,Article_8.4.b,Article_9.1)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-301":{"Standard1":["v7 (11.1,5.1,9.5)","v8 (4.2,4.4)"],"BSA":["v3 (NS-1,NS-2,NS-3,NS-7,NS-8,PV-1)"],"CJIS":["(5.10.1,5.7.1,5.7.1.2)"],"CMMC":["v2.0 (AC.L1-3.1.20,CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.1,SC.L2-3.13.6)"],"Standard2":["19 (APO13,BAI10)"],"Standard4":["v4 (IVS-04,UEM-10)"],"CE":["v2.2 (A4.11,A4.5,A4.7,A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.3.b,Article_8.4.b,Article_8.4.c)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.2,D3.PC.Im.B.5,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CA-09,CM-02,CM-06,CM-07,CM-07.01,CM-09,SC-07,SC-07.05)"],"Standard6":["2016_679 (Article_32,Article_5.1.f)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.20,8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.13.1,3.4.2)","800_53 Rev5 (AC-18,CA-9,CM-2,CM-6,CM-7,CM-7.1,CM-9,SC-7,SC-7.5)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (1.1.1,1.1.4,1.3.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.1,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-101":{"Standard1":["v7 (8.1)","v8 (10.1)"],"BSA":["v3 (ES-2)"],"CFB":["v1.4.0 (2.8)","v1.5.0 (2.1.10)","v2.0.0 (2.1.10)"],"CJIS":["(5.10.4.2,5.13.3,5.13.4.2)"],"CMMC":["v2.0 (SI.L1-3.14.2)"],"Standard2":["19 (APO13,DSS05)"],"Standard4":["v4 (TVM-02,UEM-09)"],"CE":["v2.2 (A6.2.2,A8.1,Malware_protection)"],"DA":["(Article_14.e,Article_9.1)"],"Standard10":["(D3.PC.Im.B.4)"],"Standard3":["Standard11_800_53_Rev5 (SI-03)"],"Standard5":["(164.308.a.5.ii.B)"],"Standard15":["(09.j)"],"Standard14":["27001_2013 (A.12.2.1)","27002_2013 (12.2.1)","27002_2022 (8.1,8.7)","27017_2015 (12.2.1)","27018_2019 (12.2)","27701_2019 (6.9.2.1)"],"Standard12":["(CIP-007-6_Requirement_R3_Part_3.1,CIP-007-6_Requirement_R3_Part_3.2)"],"Standard11":["800-171 Rev2 (3.14.2)","800_53 Rev5 (SI-3)"],"Standard9":["v1.1 (DE.CM-4)"],"Standard8":["v3.2.1 (5.1)","v4.0 (5.1.1,5.2.1,5.2.2,5.3.2)"],"s202":["(CC6.8)"]},"ecc-azure-201":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.1.1,3.3.2,3.3.3,3.5.1.2,3.5.1.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-azure-064":{"Standard1":["v7 (14.4,14.8,16.4,16.5)","v8 (3.10)"],"BSA":["v3 (DP-3)"],"CFB":["v1.4.0 (9.10)","v1.5.0 (9.10)","v2.0.0 (9.10)"],"CJIS":["(5.10.1.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IVS-03)"],"DA":["(Article_8.2,Article_8.3.a)"],"Standard10":["(D3.PC.Am.B.12,D3.PC.Am.B.13)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,SC-08)"],"Standard6":["2016_679 (Article_32.1.a,Article_44)"],"Standard5":["(164.312.e.1)"],"Standard15":["(06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.13.2.1)","27002_2013 (13.2.1)","27002_2022 (5.14,5.33)","27017_2015 (13.2.1)","27018_2019 (13.2.1)","27701_2019 (6.10.2.1,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.8,3.5.10)","800_53 Rev5 (AC-17.2,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-2)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1.1,4.1,4.1.1,8.2.1)","v4.0 (2.2.7,4.1.1,4.2.1.2,4.2.2,8.3.2)"],"s202":["(CC6.1,CC6.7)"]},"ecc-azure-339":{"Standard1":["v7 (13.1)","v8 (3.2)"],"BSA":["v3 (DP-1)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (APO14)"],"Standard4":["v4 (DSP-03)"],"DA":["(Article_8.3.d)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-12,RA-02)"],"Standard6":["2016_679 (Article_5)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.1)"],"Standard11":["800_53 Rev5 (CM-12,PM-5,RA-2)"],"Standard9":["v1.1 (ID.AM-5)"],"Standard7":["(2)"],"Standard8":["v3.2.1 (9.6.1)","v4.0 (12.5.2,3.2.1,9.4.2,9.4.5.1)"],"s202":["(C1.1,CC3.2,CC6.1)"]},"ecc-azure-116":{"Standard1":["v7 (8.1)","v8 (10.1)"],"BSA":["v3 (ES-2)"],"CFB":["v1.4.0 (7.6)","v1.5.0 (7.5)","v2.0.0 (7.5)"],"CJIS":["(5.10.4.2,5.13.3,5.13.4.2)"],"CMMC":["v2.0 (SI.L1-3.14.2)"],"Standard2":["19 (APO13,DSS05)"],"Standard4":["v4 (TVM-02,UEM-09)"],"CE":["v2.2 (A6.2.2,A8.1,Malware_protection)"],"DA":["(Article_14.e,Article_9.1)"],"Standard10":["(D3.PC.Im.B.4)"],"Standard3":["Standard11_800_53_Rev5 (SI-03)"],"Standard5":["(164.308.a.5.ii.B)"],"Standard15":["(09.j)"],"Standard14":["27001_2013 (A.12.2.1)","27002_2013 (12.2.1)","27002_2022 (8.1,8.7)","27017_2015 (12.2.1)","27018_2019 (12.2)","27701_2019 (6.9.2.1)"],"Standard12":["(CIP-007-6_Requirement_R3_Part_3.1,CIP-007-6_Requirement_R3_Part_3.2)"],"Standard11":["800-171 Rev2 (3.14.2)","800_53 Rev5 (SI-3)"],"Standard9":["v1.1 (DE.CM-4)"],"Standard8":["v3.2.1 (5.1)","v4.0 (5.1.1,5.2.1,5.2.2,5.3.2)"],"s202":["(CC6.8)"]},"ecc-azure-276":{"Standard1":["v7 (10)","v8 (11.1)"],"BSA":["v3 (BR-1,BR-2)"],"Standard2":["19 (APO14)"],"Standard4":["v4 (BCR-08,GRC-03)"],"CE":["v2.2 (Back_up_your_data)"],"DA":["(Article_11.1)"],"Standard10":["(D1.RM.RMP.B.1,D5.IR.Pl.B.5,D5.IR.Pl.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CP-02,CP-10)"],"Standard6":["2016_679 (Article_32.1.c)"],"Standard5":["(164.308.a.7.ii.A)"],"Standard15":["(09.l)"],"Standard14":["27001_2013 (A.12.3.1)","27002_2013 (12.3.1)","27002_2022 (8.13)","27017_2015 (12.3.1)","27018_2019 (12.3.1)","27701_2019 (6.9.3.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-009-6_Requirement_R1_Part_1.3)"],"Standard11":["800_53 Rev5 (CP-10,CP-2)"],"Standard9":["v1.1 (ID.SC-5,PR.IP-9)"],"Standard7":["(6s)"],"s202":["(A1.2)"]},"ecc-azure-113":{"Standard1":["v7 (13)","v8 (3.1)"],"CFB":["v1.4.0 (7.1)","v1.5.0 (7.1)","v2.0.0 (7.2)"],"CJIS":["(5.1.1.1)"],"CMMC":["v2.0 (SC.L2-3.13.10)"],"Standard2":["19 (APO14,DSS06)"],"Standard4":["v4 (DCS-01,DSP-01,DSP-06,DSP-17,GRC-03)"],"DA":["(Article_8.2)"],"Standard10":["(D1.G.IT.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-12,SI-12)"],"Standard6":["2016_679 (Article_5)"],"Standard15":["(00.a)"],"Standard14":["27001_2013 (A.18.1.5,A.6.2.2,A.8.1.3)","27002_2013 (18.1.5,6.2.2,8.1.3)","27002_2022 (5.10,5.9,8.1)","27017_2015 (18.1.5,6.2.2,8.1.3)","27018_2019 (18.1,6.2,8)","27701_2019 (6.15.1.5,6.3.2.2,6.5.1.3)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800_53 Rev5 (CM-12,SI-12)"],"Standard9":["v1.1 (PR.IP-6)"],"Standard7":["(2)"],"Standard8":["v4.0 (9.4.2)"],"s202":["(CC6.1,CC6.7)"]},"ecc-azure-066":{"Standard1":["v7 (6.7)","v8 (8.11)"],"BSA":["v3 (DS-7,LT-1,LT-2,LT-5)"],"CFB":["v1.4.0 (5.2.2)","v1.5.0 (5.2.2)","v2.0.0 (5.2.2)"],"CJIS":["(5.4.1,5.4.3)"],"CMMC":["v2.0 (AU.L2-3.3.5)"],"Standard4":["v4 (LOG-05)"],"DA":["(Article_15.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-06,AU-06.01,AU-06.05,AU-07.01)"],"Standard5":["(164.308.a.1.ii.D,164.312.b)"],"Standard14":["27001_2013 (A.12.4.1,A.16.1.4)","27002_2013 (12.4.1,16.1.4)","27002_2022 (5.25)","27017_2015 (12.4.1,16.1.4)","27018_2019 (12.4.1,6.1.4)","27701_2019 (6.13.1.4,6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.4)"],"Standard11":["800-171 Rev2 (3.3.3,3.3.5)","800_53 Rev5 (AU-6,AU-6.1,AU-6.5,AU-7)"],"Standard9":["v1.1 (DE.AE-2,PR.PT-1,RS.AN-1)"],"Standard8":["v3.2.1 (10.6,10.6.1,10.6.2)","v4.0 (10.4.1.1,10.4.2,10.4.3)"],"s202":["(CC4.1,CC7.3)"]},"ecc-azure-157":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (NS-2)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-102":{"Standard1":["v7 (8.1)","v8 (10.1)"],"BSA":["v3 (ES-2)"],"CFB":["v1.4.0 (2.9)","v1.5.0 (2.4.2)","v2.0.0 (2.1.22)"],"CJIS":["(5.10.4.2,5.13.3,5.13.4.2)"],"CMMC":["v2.0 (SI.L1-3.14.2)"],"Standard2":["19 (APO13,DSS05)"],"Standard4":["v4 (TVM-02,UEM-09)"],"CE":["v2.2 (A6.2.2,A8.1,Malware_protection)"],"DA":["(Article_14.e,Article_9.1)"],"Standard10":["(D3.PC.Im.B.4)"],"Standard3":["Standard11_800_53_Rev5 (SI-03)"],"Standard5":["(164.308.a.5.ii.B)"],"Standard15":["(09.j)"],"Standard14":["27001_2013 (A.12.2.1)","27002_2013 (12.2.1)","27002_2022 (8.1,8.7)","27017_2015 (12.2.1)","27018_2019 (12.2)","27701_2019 (6.9.2.1)"],"Standard12":["(CIP-007-6_Requirement_R3_Part_3.1,CIP-007-6_Requirement_R3_Part_3.2)"],"Standard11":["800-171 Rev2 (3.14.2)","800_53 Rev5 (SI-3)"],"Standard9":["v1.1 (DE.CM-4)"],"Standard8":["v3.2.1 (5.1)","v4.0 (5.1.1,5.2.1,5.2.2,5.3.2)"],"s202":["(CC6.8)"]},"ecc-azure-032":{"Standard1":["v7 (16.2)","v8 (5.6)"],"CFB":["v1.4.0 (4.5)","v1.5.0 (4.1.4)","v2.0.0 (4.1.4)"],"Standard10":["(D3.PC.Am.B.6)"],"Standard3":["Standard11_800_53_Rev5 (AC-02.01)"],"Standard15":["(01.q)"],"Standard14":["27001_2013 (A.9)","27002_2013 (9)","27002_2022 (5.15)","27017_2015 (9)","27018_2019 (9)","27701_2019 (6.6)"],"Standard11":["800_53 Rev5 (AC-2.1)"],"Standard9":["v1.1 (PR.AC-7)"],"s202":["(CC6.1)"]},"ecc-azure-050":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (PV-1)"],"CFB":["v1.4.0 (6.3)","v1.5.0 (4.1.2)","v2.0.0 (4.1.2)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-213":{"Standard1":["v7 (8.1)","v8 (10.1)"],"BSA":["v3 (NS-10)"],"CFB":["v1.5.0 (2.1.11)","v2.0.0 (2.1.11)"],"CJIS":["(5.10.4.2,5.13.3,5.13.4.2)"],"CMMC":["v2.0 (SI.L1-3.14.2)"],"Standard2":["19 (APO13,DSS05)"],"Standard4":["v4 (TVM-02,UEM-09)"],"CE":["v2.2 (A6.2.2,A8.1,Malware_protection)"],"DA":["(Article_14.e,Article_9.1)"],"Standard10":["(D3.PC.Im.B.4)"],"Standard3":["Standard11_800_53_Rev5 (SI-03)"],"Standard5":["(164.308.a.5.ii.B)"],"Standard15":["(09.j)"],"Standard14":["27001_2013 (A.12.2.1)","27002_2013 (12.2.1)","27002_2022 (8.1,8.7)","27017_2015 (12.2.1)","27018_2019 (12.2)","27701_2019 (6.9.2.1)"],"Standard12":["(CIP-007-6_Requirement_R3_Part_3.1,CIP-007-6_Requirement_R3_Part_3.2)"],"Standard11":["800-171 Rev2 (3.14.2)","800_53 Rev5 (SI-3)"],"Standard9":["v1.1 (DE.CM-4)"],"Standard8":["v3.2.1 (5.1)","v4.0 (5.1.1,5.2.1,5.2.2,5.3.2)"],"s202":["(CC6.8)"]},"ecc-azure-200":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (DP-4)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1)","27002_2013 (10.1.1)","27002_2022 (5.33)","27017_2015 (10.1.1)","27018_2019 (10.1.1)","27701_2019 (6.15.1.3)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.1.1,3.3.2,3.3.3,3.5.1.2,3.5.1.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-azure-289":{"Standard1":["v7 (5)","v8 (4.7)"],"CJIS":["(5.4.3,5.6.3.2)"],"CE":["v2.2 (A5.2,A5.3,Firewalls,Secure_configuration)"],"DA":["(Article_7.4,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.3,D3.PC.Am.B.8)"],"Standard3":["Standard11_800_53_Rev5 (IA-05)"],"Standard15":["(01.b)"],"Standard14":["27001_2013 (A.9.2.2,A.9.2.3)","27002_2013 (9.2.2,9.2.3)","27002_2022 (8.2,8.9)","27017_2015 (9.2.2,9.2.3)","27018_2019 (9.2.2,9.2.3)","27701_2019 (6.6.2.2)"],"Standard12":["(CIP-007-6_Requirement_R5_Part_5.2)"],"Standard11":["800_53 Rev5 (IA-5,IA-5.5)"],"Standard9":["v1.1 (PR.AC-1)"],"Standard8":["v3.2.1 (2.1,2.1.1)","v4.0 (2.2.2,2.3.1)"],"s202":["(CC6.3)"]},"ecc-azure-225":{"Standard1":["v7 (6.2)","v8 (8.2)"],"BSA":["v3 (LT-3)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.2,10.3)","v4.0 (5.3.4,6.4.1,6.4.2)"],"s202":["(CC7.2)"]},"ecc-azure-241":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (PV-2)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b,Article_8.4.d)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.5.1,2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]},"ecc-azure-206":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (DP-4)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.1.1,3.3.2,3.3.3,3.5.1.2,3.5.1.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-azure-152":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (NS-3)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-184":{"Standard1":["v7 (5.1)","v8 (4.1,4.6)"],"BSA":["v3 (IM-3)"],"CJIS":["(5.13.1.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.15)"],"Standard2":["19 (APO13,BAI06,BAI09,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.d)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-07,CM-09,MA-04,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32,Article_32.1.b,Article_5.1.f)"],"Standard15":["(07.c,08.k,10.k)"],"Standard14":["27001_2013 (A.9.2.4)","27002_2013 (9.2.4)","27002_2022 (8.27)","27017_2015 (9.2.14)","27018_2019 (8)","27701_2019 (6.6.2.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-7,CM-9,MA-4,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1,PR.IP-2,PR.PT-4)"],"Standard8":["v3.2.1 (2.2)","v4.0 (1.1.1,1.2.1,1.2.5,1.2.6,1.2.7,1.5.1,2.1.1,2.2.1,2.2.6)"],"s202":["(CC5.2,CC8.1)"]},"ecc-azure-329":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (DP-4,DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.1.1,3.3.2,3.3.3,3.5.1.2,3.5.1.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-azure-023":{"Standard1":["v7 (3.1)","v8 (7.5,7.6)"],"BSA":["v3 (DS-6,PV-5)"],"CFB":["v1.4.0 (4.2.5)","v1.5.0 (4.2.5)","v2.0.0 (4.2.5)"],"CJIS":["(5.10.1.3)"],"CMMC":["v2.0 (RA.L2-3.11.2,SI.L1-3.14.5)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (TVM-07)"],"DA":["(Article_15.3.d,Article_7.2,Article_8.1,Article_8.2)"],"Standard10":["(D3.DC.Th.B.1)"],"Standard3":["Standard11_800_53_Rev5 (RA-05)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(10.m)"],"Standard14":["27001_2013 (A.12.6.1)","27002_2013 (12.6.1)","27002_2022 (8.8)","27017_2015 (12.6.1)","27018_2019 (12.6)","27701_2019 (6.9.6.1)"],"Standard11":["800-171 Rev2 (3.11.2)","800_53 Rev5 (RA-5)"],"Standard9":["v1.1 (DE.CM-8)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (11.2)","v4.0 (11.3.1,11.3.2.1,6.4.1)"],"s202":["(CC6.6,CC7.1)"]},"ecc-azure-294":{"Standard1":["v7 (10.5)","v8 (11.4)"],"CMMC":["v2.0 (MP.L2-3.8.9)"],"CE":["v2.2 (Back_up_your_data)"],"Standard3":["Standard11_800_53_Rev5 (CP-06,CP-06.01)"],"Standard14":["27002_2022 (8.13)"],"Standard11":["800_53 Rev5 (CP-6,CP-6.1)"],"Standard9":["v1.1 (PR.PT-5)"],"Standard8":["v4.0 (9.4.1)"],"s202":["(A1.2)"]},"ecc-azure-129":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (PV-1)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-105":{"Standard1":["v7 (16.7)","v8 (6.1,6.2)"],"BSA":["v3 (PA-3,PA-8)"],"CFB":["v1.4.0 (3.2)","v1.5.0 (3.4)","v2.0.0 (3.4)"],"CJIS":["(5.12.2,5.5.1,5.6.1,5.6.3.1,5.6.3.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,IA.L1-3.5.2)"],"Standard4":["v4 (HRS-06,IAM-01,IAM-06,IAM-07)"],"CE":["v2.2 (A7.1,A7.3,A7.4,A7.5,uac)"],"DA":["(Article_8.3.d,Article_8.4.c)"],"Standard10":["(D3.PC.Am.B.5,D3.PC.Am.B.6)"],"Standard3":["Standard11_800_53_Rev5 (AC-01,AC-02,AC-02.01,IA-04,IA-05)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.3.ii.C,164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.b,01.q)"],"Standard14":["27001_2013 (A.9.2.2)","27002_2013 (9.2.2)","27002_2022 (5.15,5.16)","27017_2015 (9.2.2)","27018_2019 (9.2.2)","27701_2019 (6.6.2.1,6.6.2.2)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-004-6_Requirement_R5_Part_5.1,CIP-004-6_Requirement_R5_Part_5.2,CIP-004-6_Requirement_R5_Part_5.3,CIP-004-6_Requirement_R5_Part_5.4)"],"Standard11":["800-171 Rev2 (3.5.2)","800_53 Rev5 (AC-1,AC-2,AC-2.1,AC-3.8,IA-4,IA-5)"],"Standard9":["v1.1 (PR.AC-1,PR.AC-4:)"],"Standard8":["v3.2.1 (7.2,8.1.3)","v4.0 (7.2.1,7.2.3,8.1.1,8.2.1,8.2.4,8.2.5)"],"s202":["(CC6.2,CC6.3)"]},"ecc-azure-332":{"Standard1":["v7 (5.1,6.3)","v8 (4.1,8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4,PV-1,PV-3)"],"CJIS":["(5.4.1,5.7.1)"],"CMMC":["v2.0 (AU.L2-3.3.1,CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04,LOG-08)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_10.7,Article_8.4.b,Article_8.4.e)"],"Standard10":["(D1.G.SP.B.2,D2.MA.Ma.B.1,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12,CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_30,Article_32)"],"Standard15":["(08.k,09.aa,10.k)"],"Standard14":["27001_2013 (A.12.4.1,A.14.2.5)","27002_2013 (12.4.1,14.2.5)","27002_2022 (5.28,8.15,8.27,8.9)","27017_2015 (12.4.1,14.2.5)","27018_2019 (12.4.1,14)","27701_2019 (6.11.2.5,6.9.4.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (AU-12,AU-3,AU-3.1,CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (DE.AE-3,PR.IP-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.2,10.2.4,10.2.5,10.3,2.2)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.5.1,10.2.1,2.1.1,2.2.1,2.2.6)"],"s202":["(CC7.2,CC8.1)"]},"ecc-azure-058":{"Standard1":["v7 (4)","v8 (6.8)"],"BSA":["v3 (PA-1,PA-7)"],"CFB":["v1.4.0 (8.7)"],"CJIS":["(5.5.1,5.5.2,5.5.2.1,5.5.2.2,5.5.2.3,5.5.2.4)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L2-3.1.4,AC.L2-3.1.5,SC.L2-3.13.3)"],"Standard4":["v4 (IAM-04,IAM-05,IAM-16)"],"CE":["v2.2 (A7.4,A7.8,A7.9)"],"Standard10":["(D1.R.St.B.1,D3.PC.Am.B.1,D3.PC.Am.B.2,D3.PC.Am.B.4)"],"Standard3":["Standard11_800_53_Rev5 (AC-02,AC-02.07,AC-03,AC-05,AC-06,AC-06.01,AC-06.07,AU-09.04)"],"Standard6":["2016_679 (Article_25)"],"Standard5":["(164.308.a.3.i,164.312.a.1)"],"Standard15":["(01.c)"],"Standard14":["27001_2013 (A.6.1.2,A.9,A.9.4.1)","27002_2013 (6.1.2,9,9.4.1)","27002_2022 (5.15,5.3,8.2,8.3)","27017_2015 (6.1.2,9,9.4.1)","27018_2019 (6.1.2,9,9.4.1)","27701_2019 (6.10.1.3,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-004-6_Requirement_R4_Part_4.3,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.4,3.1.5)","800_53 Rev5 (AC-2,AC-2.7,AC-3.7,AC-5,AC-6,AC-6.1,AC-6.7,AU-9.4)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v4.0 (10.3.1,7.1.1,7.2.1,7.2.2,7.2.4,7.2.6,7.3.1,7.3.2)"],"s202":["(CC5.2,CC6.3)"]},"ecc-azure-379":{"Standard1":["v7 (6.2)","v8 (8.2)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CFB":["v1.5.0 (5.1.7)","v2.0.0 (5.1.7)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.2,10.3)","v4.0 (5.3.4,6.4.1,6.4.2)"],"s202":["(CC7.2)"]},"ecc-azure-202":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.1.1,3.3.2,3.3.3,3.5.1.2,3.5.1.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-azure-350":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.5.1,2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]},"ecc-azure-355":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.5.1,2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]},"ecc-azure-278":{"Standard1":["v7 (10)","v8 (11.1)"],"BSA":["v3 (BR-1,BR-2)"],"Standard2":["19 (APO14)"],"Standard4":["v4 (BCR-08,GRC-03)"],"CE":["v2.2 (Back_up_your_data)"],"DA":["(Article_11.1)"],"Standard10":["(D1.RM.RMP.B.1,D5.IR.Pl.B.5,D5.IR.Pl.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CP-02,CP-10)"],"Standard6":["2016_679 (Article_32.1.c)"],"Standard5":["(164.308.a.7.ii.A)"],"Standard15":["(09.l)"],"Standard14":["27001_2013 (A.12.3.1)","27002_2013 (12.3.1)","27002_2022 (8.13)","27017_2015 (12.3.1)","27018_2019 (12.3.1)","27701_2019 (6.9.3.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-009-6_Requirement_R1_Part_1.3)"],"Standard11":["800_53 Rev5 (CP-10,CP-2)"],"Standard9":["v1.1 (ID.SC-5,PR.IP-9)"],"Standard7":["(6s)"],"s202":["(A1.2)"]},"ecc-azure-378":{"Standard1":["v7 (6)","v8 (8.10)"],"BSA":["v3 (LT-6)"],"CFB":["v1.5.0 (5.1.6)","v2.0.0 (5.1.6)"],"CJIS":["(5.4.6,5.4.7)"],"Standard4":["v4 (LOG-02)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-11)"],"Standard6":["2016_679 (Article_5.1.e)"],"Standard15":["(09.ac)"],"Standard14":["27001_2013 (A.12.4.2,A.18.1.3)","27002_2013 (12.4.2,18.1.3)","27002_2022 (5.28)","27017_2015 (12.4.2,18.1.3)","27018_2019 (12.4.2,18.1)","27701_2019 (6.15.1.3,6.9.4.2)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.3)"],"Standard11":["800_53 Rev5 (AU-11)"],"Standard8":["v3.2.1 (10.7)","v4.0 (10.5.1)"],"s202":["(C1.1)"]},"ecc-azure-362":{"Standard1":["v7 (3.1)","v8 (7.5,7.6)"],"BSA":["v3 (DS-6,PV-5)"],"CJIS":["(5.10.1.3)"],"CMMC":["v2.0 (RA.L2-3.11.2,SI.L1-3.14.5)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (TVM-07)"],"DA":["(Article_7.2,Article_8.1,Article_8.2)"],"Standard10":["(D3.DC.Th.B.1)"],"Standard3":["Standard11_800_53_Rev5 (RA-05)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(10.m)"],"Standard14":["27001_2013 (A.12.6.1)","27002_2013 (12.6.1)","27002_2022 (8.8)","27017_2015 (12.6.1)","27018_2019 (12.6)","27701_2019 (6.9.6.1)"],"Standard11":["800-171 Rev2 (3.11.2)","800_53 Rev5 (RA-5)"],"Standard9":["v1.1 (DE.CM-8)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (11.2)","v4.0 (11.3.1,11.3.2.1,6.4.1)"],"s202":["(CC6.6,CC7.1)"]},"ecc-azure-334":{"Standard1":["v7 (14)","v8 (6.1)"],"BSA":["v3 (PA-3,PA-8)"],"CJIS":["(5.5.1,5.6.1,5.6.3.1,5.6.3.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,IA.L1-3.5.2)"],"Standard4":["v4 (IAM-01,IAM-06,IAM-07)"],"CE":["v2.2 (A7.1,A7.4,A7.5,uac)"],"DA":["(Article_14.d,Article_8.3,Article_8.4.c)"],"Standard10":["(D3.PC.Am.B.5,D3.PC.Am.B.6)"],"Standard3":["Standard11_800_53_Rev5 (AC-01,AC-02,AC-02.01,IA-04,IA-05)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.b,01.q)"],"Standard14":["27001_2013 (A.9.2.1,A.9.2.2,A.9.2.3)","27002_2013 (9.2.1,9.2.2,9.2.3)","27002_2022 (5.15,5.16,5.18)","27017_2015 (9.2.1,9.2.2,9.2.3)","27018_2019 (9.2.1,9.2.2,9.2.3)","27701_2019 (6.6.2.1,6.6.2.2,6.6.2.3)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.5.2)","800_53 Rev5 (AC-1,AC-2,AC-2.1,IA-4,IA-5)"],"Standard9":["v1.1 (PR.AC-1,PR.AC-4:)"],"Standard8":["v3.2.1 (7.2)","v4.0 (7.2.1,7.2.3,8.1.1,8.2.1,8.2.4)"],"s202":["(CC6.2)"]},"ecc-azure-142":{"Standard1":["v7 (5)","v8 (4.6)"],"BSA":["v3 (NS-1,NS-2,NS-3)"],"CJIS":["(5.13.1.1)"],"CMMC":["v2.0 (SC.L2-3.13.15)"],"Standard2":["19 (BAI09)"],"DA":["(Article_8.4.b)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,MA-04)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(07.c)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard11":["800_53 Rev5 (CM-7,MA-4)"],"Standard9":["v1.1 (PR.IP-1,PR.IP-2,PR.PT-4)"],"Standard8":["v4.0 (1.2.5)"],"s202":["(CC5.2)"]},"ecc-azure-306":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (DP-4,DP-5)"],"CFB":["v1.4.0 (4.3.8)","v1.5.0 (4.3.8)","v2.0.0 (4.3.8)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.1.1,3.3.2,3.3.3,3.5.1.2,3.5.1.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-azure-070":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (PV-1,PV-3)"],"CFB":["v1.4.0 (9.7)","v1.5.0 (9.7)","v2.0.0 (9.7)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.e,Article_8.4.f)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.5.1,2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]},"ecc-azure-181":{"Standard1":["v7 (16.2)","v8 (5.6)"],"CFB":["v1.4.0 (9.5)","v1.5.0 (9.5)","v2.0.0 (9.5)"],"BSA":["v3 (IM-3)"],"Standard10":["(D3.PC.Am.B.6)"],"Standard3":["Standard11_800_53_Rev5 (AC-02.01)"],"Standard15":["(01.q)"],"Standard14":["27001_2013 (A.9)","27002_2013 (9)","27002_2022 (5.15)","27017_2015 (9)","27018_2019 (9)","27701_2019 (6.6)"],"Standard11":["800_53 Rev5 (AC-2.1)"],"Standard9":["v1.1 (PR.AC-7)"],"s202":["(CC6.1)"]},"ecc-azure-359":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (PV-1)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-351":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.5.1,2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]},"ecc-azure-231":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (PV-4)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.5.1,2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]},"ecc-azure-337":{"Standard1":["v7 (8.2)","v8 (10.2)"],"CJIS":["(5.10.4.2,5.13.4.2)"],"CMMC":["v2.0 (SI.L1-3.14.4)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (TVM-04)"],"CE":["v2.2 (A8.2,Malware_protection,Security_update_management)"],"Standard10":["(D3.PC.Im.B.4)"],"Standard3":["Standard11_800_53_Rev5 (SI-03)"],"Standard14":["27001_2013 (A.12.2.1)","27002_2013 (12.2.1)","27002_2022 (8.7)","27017_2015 (12.2.1)","27018_2019 (12.2)","27701_2019 (6.9.2.1)"],"Standard12":["(CIP-007-6_Requirement_R3_Part_3.3)"],"Standard11":["800-171 Rev2 (3.14.4)","800_53 Rev5 (SI-3)"],"Standard8":["v3.2.1 (11.4,5.1.1,5.2)","v4.0 (5.3.1)"],"s202":["(CC6.8)"]},"ecc-azure-164":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (NS-2)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-155":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (NS-2)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-061":{"Standard1":["v7 (14.4,14.8,16.4,16.5)","v8 (3.10)"],"BSA":["v3 (DP-3)"],"CFB":["v1.4.0 (9.3)","v1.5.0 (9.3)","v2.0.0 (9.3)"],"CJIS":["(5.10.1.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IVS-03)"],"DA":["(Article_8.2,Article_8.3.a,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12,D3.PC.Am.B.13)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,SC-08)"],"Standard6":["2016_679 (Article_32.1.a,Article_44)"],"Standard5":["(164.312.e.1)"],"Standard15":["(06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1,A.18.1.3)","27002_2013 (10.1.1,13.2.1,18.1.3)","27002_2022 (5.14,5.33,8.24)","27017_2015 (10.1.1,13.2.1,18.1.3)","27018_2019 (10.1.1,13.2.1,18.1.3)","27701_2019 (6.10.2.1,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.8,3.5.10)","800_53 Rev5 (AC-17.2,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-2)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1.1,4.1,4.1.1,8.2.1)","v4.0 (2.2.7,4.1.1,4.2.1.2,4.2.2,8.3.2)"],"s202":["(CC6.1,CC6.7)"]},"ecc-azure-004":{"Standard1":["v7 (3.1)","v8 (7.5,7.6)"],"BSA":["v3 (DS-6,PV-5)"],"CFB":["v1.4.0 (2.11)","v1.5.0 (2.2.1,2.2.2)","v2.0.0 (2.1.15,2.1.16)"],"CJIS":["(5.10.1.3)"],"CMMC":["v2.0 (RA.L2-3.11.2,SI.L1-3.14.5)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (TVM-07)"],"DA":["(Article_8.1)"],"Standard10":["(D3.DC.Th.B.1)"],"Standard3":["Standard11_800_53_Rev5 (RA-05)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(10.m)"],"Standard14":["27001_2013 (A.12.6.1)","27002_2013 (12.6.1)","27002_2022 (8.8)","27017_2015 (12.6.1)","27018_2019 (12.6)","27701_2019 (6.9.6.1)"],"Standard11":["800-171 Rev2 (3.11.2)","800_53 Rev5 (RA-5)"],"Standard9":["v1.1 (DE.CM-8)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (11.2)","v4.0 (11.3.1,11.3.2.1)"],"s202":["(CC6.6,CC7.1)"]},"ecc-azure-130":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (PV-1)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-373":{"Standard1":["v7 (6.7)","v8 (8.11)"],"BSA":["v3 (DS-7,LT-1,LT-2,LT-5)"],"CFB":["v1.5.0 (5.2.9)","v2.0.0 (5.2.9)"],"CJIS":["(5.4.1,5.4.3)"],"CMMC":["v2.0 (AU.L2-3.3.5)"],"Standard4":["v4 (LOG-05)"],"DA":["(Article_15.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-06,AU-06.01,AU-06.05,AU-07.01)"],"Standard5":["(164.308.a.1.ii.D,164.312.b)"],"Standard14":["27001_2013 (A.12.4.1,A.16.1.4)","27002_2013 (12.4.1,16.1.4)","27002_2022 (5.25)","27017_2015 (12.4.1,16.1.4)","27018_2019 (12.4.1,6.1.4)","27701_2019 (6.13.1.4,6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.4)"],"Standard11":["800-171 Rev2 (3.3.3,3.3.5)","800_53 Rev5 (AU-6,AU-6.1,AU-6.5,AU-7)"],"Standard9":["v1.1 (DE.AE-2,PR.PT-1,RS.AN-1)"],"Standard8":["v3.2.1 (10.6,10.6.1,10.6.2)","v4.0 (10.4.1.1,10.4.2,10.4.3)"],"s202":["(CC4.1,CC7.3)"]},"ecc-azure-238":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (PV-2)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-002":{"Standard1":["v7 (4.3)","v8 (5.4)"],"BSA":["v3 (IM-2,PA-1)"],"CFB":["v1.4.0 (1.20)","v1.5.0 (1.23)","v2.0.0 (1.23)"],"CJIS":["(5.5.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.5,AC.L2-3.1.6,AC.L2-3.1.7,SC.L2-3.13.3)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-09)"],"CE":["v2.2 (A7.5,A7.6,A7.7,uac)"],"DA":["(Article_8.4.c)"],"Standard10":["(D3.PC.Am.B.3)"],"Standard3":["Standard11_800_53_Rev5 (AC-02.07,AC-06.02,AC-06.05)"],"Standard6":["2016_679 (Article_25)"],"Standard15":["(01.e)"],"Standard14":["27001_2013 (A.9.2.3)","27002_2013 (9.2.3)","27002_2022 (5.15,8.2)","27017_2015 (9.2.3)","27018_2019 (9.2.3)","27701_2019 (6.6.2.3)"],"Standard11":["800-171 Rev2 (3.1.6,3.1.7)","800_53 Rev5 (AC-2.7,AC-6.2,AC-6.5)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)"],"s202":["(CC6.3)"]},"ecc-azure-199":{"Standard1":["v7 (14.4,14.8,16.4,16.5)","v8 (3.10)"],"BSA":["v3 (DP-3)"],"CJIS":["(5.10.1.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IVS-03)"],"DA":["(Article_8.2,Article_8.3.a,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12,D3.PC.Am.B.13)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,SC-08)"],"Standard6":["2016_679 (Article_32.1.a,Article_44)"],"Standard5":["(164.312.e.1)"],"Standard15":["(06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1,A.18.1.3)","27002_2013 (10.1.1,13.2.1,18.1.3)","27002_2022 (5.14,5.33,8.24)","27017_2015 (10.1.1,13.2.1,18.1.3)","27018_2019 (10.1.1,13.2.1,18.1.3)","27701_2019 (6.10.2.1,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.8,3.5.10)","800_53 Rev5 (AC-17.2,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-2)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1.1,4.1,4.1.1,8.2.1)","v4.0 (2.2.7,4.1.1,4.2.1.2,4.2.2,8.3.2)"],"s202":["(CC6.1,CC6.7)"]},"ecc-azure-298":{"Standard1":["v7 (6.3)","v8 (8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.2,10.2.4,10.2.5,10.3)","v4.0 (10.2.1)"],"s202":["(CC7.2)"]},"ecc-azure-046":{"Standard1":["v7 (6.7)","v8 (8.11)"],"BSA":["v3 (DS-7,LT-1,LT-2,LT-5)"],"CFB":["v1.4.0 (5.2.9)","v1.5.0 (5.2.7,5.2.8)","v2.0.0 (5.2.7,5.2.8)"],"CJIS":["(5.4.1,5.4.3)"],"CMMC":["v2.0 (AU.L2-3.3.5)"],"Standard4":["v4 (LOG-05)"],"DA":["(Article_15.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-06,AU-06.01,AU-06.05,AU-07.01)"],"Standard5":["(164.308.a.1.ii.D,164.312.b)"],"Standard14":["27001_2013 (A.12.4.1,A.16.1.4)","27002_2013 (12.4.1,16.1.4)","27002_2022 (5.25)","27017_2015 (12.4.1,16.1.4)","27018_2019 (12.4.1,6.1.4)","27701_2019 (6.13.1.4,6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.4)"],"Standard11":["800-171 Rev2 (3.3.3,3.3.5)","800_53 Rev5 (AU-6,AU-6.1,AU-6.5,AU-7)"],"Standard9":["v1.1 (DE.AE-2,PR.PT-1,RS.AN-1)"],"Standard8":["v3.2.1 (10.6,10.6.1,10.6.2)","v4.0 (10.4.1.1,10.4.2,10.4.3)"],"s202":["(CC4.1,CC7.3)"]},"ecc-azure-217":{"Standard1":["v7 (6.2)","v8 (8.2)"],"BSA":["v3 (LT-3)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.2,10.3)","v4.0 (5.3.4,6.4.1,6.4.2)"],"s202":["(CC7.2)"]},"ecc-azure-291":{"Standard6":["2016_679 (Article_3)"]},"ecc-azure-049":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (PV-1)"],"CFB":["v1.4.0 (6.2)","v1.5.0 (6.2)","v2.0.0 (6.2)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-313":{"Standard1":["v7 (6.3)","v8 (8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"Standard13":["v1.0.0 (3.1.12)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.2,10.2.4,10.2.5,10.3)","v4.0 (10.2.1)"],"s202":["(CC7.2)"]},"ecc-azure-204":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.1.1,3.3.2,3.3.3,3.5.1.2,3.5.1.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-azure-279":{"Standard1":["v7 (5)","v8 (4.7)"],"CJIS":["(5.4.3,5.6.3.2)"],"CE":["v2.2 (A5.2,A5.3,Firewalls,Secure_configuration)"],"DA":["(Article_7.4,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.3,D3.PC.Am.B.8)"],"Standard3":["Standard11_800_53_Rev5 (IA-05)"],"Standard15":["(01.b)"],"Standard14":["27001_2013 (A.9.2.2,A.9.2.3)","27002_2013 (9.2.2,9.2.3)","27002_2022 (8.2,8.9)","27017_2015 (9.2.2,9.2.3)","27018_2019 (9.2.2,9.2.3)","27701_2019 (6.6.2.2)"],"Standard12":["(CIP-007-6_Requirement_R5_Part_5.2)"],"Standard11":["800_53 Rev5 (IA-5,IA-5.5)"],"Standard9":["v1.1 (PR.AC-1)"],"Standard8":["v3.2.1 (2.1,2.1.1)","v4.0 (2.2.2,2.3.1)"],"s202":["(CC6.3)"]},"ecc-azure-235":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (PV-2)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.5.1,2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]},"ecc-azure-178":{"Standard1":["v7 (12.7)","v8 (13.3,13.8)"],"BSA":["v3 (NS-6)"],"CJIS":["(5.10.1.3)"],"CMMC":["v2.0 (SI.L2-3.14.6,SI.L2-3.14.7)"],"Standard4":["v4 (IVS-09)"],"DA":["(Article_14.a,Article_8.2,Article_8.4.b,Article_9.1)"],"Standard10":["(D3.DC.An.B.1,D3.DC.Ev.B.3,D3.PC.Im.B.1)"],"Standard3":["Standard11_800_53_Rev5 (SI-04,SI-04.04)"],"Standard15":["(09.ab,09.m)"],"Standard14":["27001_2013 (A.13.1.1)","27002_2013 (13.1.1)","27002_2022 (8.16,8.21,8.8)","27017_2015 (13.1.1)","27018_2019 (13.1)","27701_2019 (6.10.1.1)"],"Standard12":["(CIP-005-7_Requirement_R1_Part_1.5)"],"Standard11":["800-171 Rev2 (3.14.6)","800_53 Rev5 (SI-4,SI-4.4)"],"Standard9":["v1.1 (DE.CM-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (11.1,11.4)","v4.0 (11.5.1,12.10.5,6.4.2)"],"s202":["(CC6.6,CC7.2)"]},"ecc-azure-172":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (NS-2)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-226":{"Standard1":["v7 (6.2)","v8 (8.2)"],"BSA":["v3 (LT-3)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.2,10.3)","v4.0 (5.3.4,6.4.1,6.4.2)"],"s202":["(CC7.2)"]},"ecc-azure-324":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (DP-4,DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.1.1,3.3.2,3.3.3,3.5.1.2,3.5.1.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-azure-143":{"Standard1":["v7 (11.1,5.1)","v8 (12.3,4.2)"],"BSA":["v3 (NS-2)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.15,SC.L2-3.13.6)"],"Standard2":["19 (APO01,APO03,BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09,SC-23)"],"Standard6":["2016_679 (Article_32)"],"Standard5":["(164.312.e.1)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9,SC-23)"],"Standard9":["v1.1 (PR.AC-7,PR.DS-2,PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1,8.3)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-280":{"Standard1":["v7 (5.1)","v8 (12.3,4.1)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.15)"],"Standard2":["19 (APO01,APO03,APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-07,CM-09,SA-03,SA-08,SA-10,SC-23)"],"Standard6":["2016_679 (Article_32)"],"Standard5":["(164.312.e.1)"],"Standard15":["(08.k,09.m,10.k)"],"Standard14":["27001_2013 (A.13.1.2,A.14.2.5)","27002_2013 (13.1.2,14.2.5)","27002_2022 (8.21,8.27,8.9)","27017_2015 (13.1.2,14.2.5)","27018_2019 (13.1,14)","27701_2019 (6.10.1.2,6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-7,CM-9,SA-10,SA-3,SA-8,SC-23)"],"Standard9":["v1.1 (PR.AC-7,PR.DS-2,PR.IP-1)"],"Standard8":["v3.2.1 (2.2,8.3)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.5.1,2.1.1,2.2.1,2.2.6)"],"s202":["(CC5.2,CC8.1)"]},"ecc-azure-369":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (DP-4,DP-5)"],"CFB":["v1.5.0 (3.2)","v2.0.0 (3.2)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.1.1,3.3.2,3.3.3,3.5.1.2,3.5.1.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-azure-100":{"Standard1":["v7 (8.1)","v8 (10.1)"],"BSA":["v3 (ES-2)"],"CFB":["v1.4.0 (2.7)","v1.5.0 (2.1.8)","v2.0.0 (2.1.8)"],"CJIS":["(5.10.4.2,5.13.3,5.13.4.2)"],"CMMC":["v2.0 (SI.L1-3.14.2)"],"Standard2":["19 (APO13,DSS05)"],"Standard4":["v4 (TVM-02,UEM-09)"],"CE":["v2.2 (A6.2.2,A8.1,Malware_protection)"],"DA":["(Article_14.e,Article_9.1)"],"Standard10":["(D3.PC.Im.B.4)"],"Standard3":["Standard11_800_53_Rev5 (SI-03)"],"Standard5":["(164.308.a.5.ii.B)"],"Standard15":["(09.j)"],"Standard14":["27001_2013 (A.12.2.1)","27002_2013 (12.2.1)","27002_2022 (8.1,8.7)","27017_2015 (12.2.1)","27018_2019 (12.2)","27701_2019 (6.9.2.1)"],"Standard12":["(CIP-007-6_Requirement_R3_Part_3.1,CIP-007-6_Requirement_R3_Part_3.2)"],"Standard11":["800-171 Rev2 (3.14.2)","800_53 Rev5 (SI-3)"],"Standard9":["v1.1 (DE.CM-4)"],"Standard8":["v3.2.1 (5.1)","v4.0 (5.1.1,5.2.1,5.2.2,5.3.2)"],"s202":["(CC6.8)"]},"ecc-azure-218":{"Standard1":["v7 (6.2)","v8 (8.2)"],"BSA":["v3 (LT-3)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.2,10.3)","v4.0 (5.3.4,6.4.1,6.4.2)"],"s202":["(CC7.2)"]},"ecc-azure-368":{"Standard1":["v7 (12)","v8 (13.5)"],"BSA":["v3 (IM-7,PA-6)"],"CJIS":["(5.10.1.1,5.4.3,5.5.6,5.6.4)"],"CMMC":["v2.0 (AC.L2-3.1.12,AC.L2-3.1.13,AC.L2-3.1.14,AC.L2-3.1.15,AC.L2-3.1.18,SC.L2-3.13.7)"],"Standard4":["v4 (HRS-04)"],"DA":["(Article_14.d)"],"Standard3":["Standard11_800_53_Rev5 (AC-17,AC-17.01,SC-07,SI-04)"],"Standard5":["(164.308.a.3.ii.B,164.308.a.4.ii.B,164.308.a.4.ii.C)"],"Standard15":["(01.y)"],"Standard14":["27001_2013 (A.12.5.1)","27002_2013 (12.5.1)","27002_2022 (8.19)","27017_2015 (12.5.1)","27018_2019 (12.5)","27701_2019 (6.9.5.1)"],"Standard12":["(CIP-003-8_Requirement_R2,CIP-005-7_Requirement_R1_Part_1.4,CIP-010-4_Requirement_R4)"],"Standard11":["800-171 Rev2 (3.1.12)","800_53 Rev5 (AC-17,AC-17.1,SC-7,SI-4)"],"Standard9":["v1.1 (DE.CM-7,PR.AC-3,PR.AC-7,PR.MA-2)"],"Standard8":["v4.0 (8.4.3)"],"s202":["(CC6.6)"]},"ecc-azure-103":{"Standard1":["v7 (8.1)","v8 (10.1)"],"BSA":["v3 (ES-2)"],"CFB":["v1.4.0 (2.10)","v1.5.0 (2.4.1)","v2.0.0 (2.1.21)"],"CJIS":["(5.10.4.2,5.13.3,5.13.4.2)"],"CMMC":["v2.0 (SI.L1-3.14.2)"],"Standard2":["19 (APO13,DSS05)"],"Standard4":["v4 (TVM-02,UEM-09)"],"CE":["v2.2 (A6.2.2,A8.1,Malware_protection)"],"DA":["(Article_14.e,Article_9.1)"],"Standard10":["(D3.PC.Im.B.4)"],"Standard3":["Standard11_800_53_Rev5 (SI-03)"],"Standard5":["(164.308.a.5.ii.B)"],"Standard15":["(09.j)"],"Standard14":["27001_2013 (A.12.2.1)","27002_2013 (12.2.1)","27002_2022 (8.1,8.7)","27017_2015 (12.2.1)","27018_2019 (12.2)","27701_2019 (6.9.2.1)"],"Standard12":["(CIP-007-6_Requirement_R3_Part_3.1,CIP-007-6_Requirement_R3_Part_3.2)"],"Standard11":["800-171 Rev2 (3.14.2)","800_53 Rev5 (SI-3)"],"Standard9":["v1.1 (DE.CM-4)"],"Standard8":["v3.2.1 (5.1)","v4.0 (5.1.1,5.2.1,5.2.2,5.3.2)"],"s202":["(CC6.8)"]},"ecc-azure-174":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (NS-2)"],"CFB":["v1.5.0 (3.10)","v2.0.0 (3.10)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-347":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (DP-4,DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.1.1,3.3.2,3.3.3,3.5.1.2,3.5.1.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-azure-037":{"Standard1":["v7 (14.8,5)","v8 (3.11,4.6)"],"BSA":["v3 (DP-4,DP-5)"],"CFB":["v1.4.0 (5.1.4)","v1.5.0 (5.1.4)","v2.0.0 (5.1.4)"],"CJIS":["(5.10.1.2.2,5.13.1.1)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.16)"],"Standard2":["19 (BAI09,DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,IA-05.01,MA-04,SC-28)"],"Standard6":["2016_679 (Article_32.1.a,Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d,07.c)"],"Standard14":["27001_2013 (A.10.1.1,A.14.2.5,A.18.1.3)","27002_2013 (10.1.1,14.2.5,18.1.3)","27002_2022 (5.33,8.24,8.27)","27017_2015 (10.1.1,14.2.5,18.1.3)","27018_2019 (10.1.1,14,18.1.3)","27701_2019 (6.11.2.5,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (CM-7,IA-5.1,MA-4,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1,PR.IP-1,PR.IP-2,PR.PT-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (1.2.5,3.1.1,3.3.2,3.3.3,3.5.1.2,3.5.1.3,8.3.2)"],"s202":["(CC5.2,CC6.1)"]},"ecc-azure-222":{"Standard1":["v7 (6.2)","v8 (8.2)"],"BSA":["v3 (LT-3)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.2,10.3)","v4.0 (5.3.4,6.4.1,6.4.2)"],"s202":["(CC7.2)"]},"ecc-azure-131":{"Standard1":["v7 (11.1,5.1)","v8 (4.2)"],"BSA":["v3 (PV-1)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.1.1,1.2.1,1.2.6,1.2.7,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-azure-296":{"Standard1":["v7 (16.6,4.1)","v8 (5.1)"],"BSA":["v3 (PA-4)"],"CJIS":["(5.4,5.5.1,5.6.1,5.6.3.1)"],"Standard4":["v4 (IAM-03,IAM-08,IAM-10,IAM-16)"],"CE":["v2.2 (A7.8,A7.9,uac)"],"DA":["(Article_8.2)"],"Standard3":["Standard11_800_53_Rev5 (AC-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.16,5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.3)"],"Standard11":["800_53 Rev5 (AC-2)"],"Standard9":["v1.1 (PR.AC-1)"],"Standard8":["v3.2.1 (8.1,8.1.1)","v4.0 (12.5.2)"],"s202":["(CC6.1,CC6.2,CC6.3)"]},"ecc-azure-336":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (DP-4,DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.1.1,3.3.2,3.3.3,3.5.1.2,3.5.1.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-azure-013":{"Standard1":["v7 (6.2)","v8 (8.2)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CFB":["v1.4.0 (4.1.1)","v1.5.0 (4.1.1)","v2.0.0 (4.1.1)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.2,10.3)","v4.0 (5.3.4,6.4.1,6.4.2)"],"s202":["(CC7.2)"]},"ecc-azure-008":{"Standard1":["v7 (12.3,12.4,12.9)","v8 (13.10)"],"BSA":["v3 (NS-3,NS-4,NS-5,NS-6)"],"CFB":["v1.4.0 (3.1)","v1.5.0 (3.1)","v2.0.0 (3.1)"],"CJIS":["(5.10.1.1,5.10.3.2,5.10.4.3,5.4.3)"],"CMMC":["v2.0 (SC.L1-3.13.1,SC.L2-3.13.6)"],"Standard10":["(D3.PC.Im.B.2)"],"Standard3":["Standard11_800_53_Rev5 (SC-07.08)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard15":["(01.n,01.o)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.8)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-005-7_Requirement_R1_Part_1.5)"],"Standard11":["800_53 Rev5 (SC-7.8)"],"Standard9":["v1.1 (PR.PT-3)"],"Standard8":["v3.2.1 (1.1.4,1.2,1.3.2,1.3.4,1.3.5,6.6)","v4.0 (1.2.1,1.2.5,1.2.6,6.4.1)"],"s202":["(CC6.6,CC7.2)"]},"ecc-gcp-246":{"Standard1":["v7 (5.1)","v8 (12.2)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)","v4.0 (1.2.5,1.2.6,1.3.1,1.3.2)"],"s202":["(CC6.1,CC6.6)"]},"ecc-gcp-401":{"Standard1":["v7 (4.4)","v8 (5.2)"],"CJIS":["(5.6.2.1.1)"],"CMMC":["v2.0 (IA.L2-3.5.7)"],"Standard4":["v4 (IAM-02)"],"Standard10":["(D3.PC.Am.B.7)"],"Standard3":["Standard11_800_53_Rev5 (IA-05)"],"Standard5":["(164.308.a.5.ii.D)"],"Standard15":["(01.f)"],"Standard14":["27001_2013 (A.9.4.3)","27002_2013 (9.4.3)","27002_2022 (5.17)","27017_2015 (9.4.3)","27018_2019 (9.4.3)","27701_2019 (6.6.4.3)"],"Standard12":["(CIP-007-6_Requirement_R5_Part_5.5)"],"Standard11":["800-171 Rev2 (3.5.7)","800_53 Rev5 (IA-5)"],"Standard8":["v4.0 (8.3.5)"]},"ecc-gcp-206":{"Standard1":["v7 (6.3)","v8 (8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CIS GCP Foundations Benchmark":["v1.2.0 (6.2.12)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.2,10.2.4,10.2.5,10.3)","v4.0 (10.2.1)"],"s202":["(CC7.2)"]},"ecc-gcp-065":{"Standard1":["v7 (16.4)","v8 (3.11)"],"BSA":["v3 (DP-4,DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.3.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-gcp-272":{"Standard1":["v7 (6.2)","v8 (13.6)"],"BSA":["v3 (LT-4)"],"CJIS":["(5.10.1.1,5.10.1.3)"],"CMMC":["v2.0 (AC.L2-3.1.3,SI.L2-3.14.3,SI.L2-3.14.6)"],"Standard4":["v4 (IVS-03)"],"Standard10":["(D3.DC.Ev.B.1)"],"Standard3":["Standard11_800_53_Rev5 (SI-04,SI-04.04)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa,09.m)"],"Standard14":["27001_2013 (A.12.4.1,A.13.1.1)","27002_2013 (12.4.1,13.1.1)","27002_2022 (8.15,8.16,8.20)","27017_2015 (12.4.1,13.1.1)","27018_2019 (12.4.1,13.1)","27701_2019 (6.10.1.1,6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.13.1,3.14.6)","800_53 Rev5 (SI-4,SI-4.4)"],"Standard9":["v1.1 (DE.CM-1)"],"s202":["(CC7.2)"]},"ecc-gcp-262":{"Standard1":["v7 (14.6)","v8 (3.3)"],"BSA":["v3 (AM-4,IM-7,PA-7)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.3.b)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.3)","27017_2015 (8.1.3,9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)","v4.0 (7.1)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-gcp-310":{"Standard1":["v7 (14.4)","v8 (3.10)"],"BSA":["v3 (DP-3)"],"CJIS":["(5.10.1.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.17,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IVS-03)"],"DA":["(Article_8.2,Article_8.3.a,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.13)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,SC-08)"],"Standard6":["2016_679 (Article_32.1.a,Article_44)"],"Standard5":["(164.312.e.1)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1,A.18.1.3)","27002_2013 (10.1.1,13.2.1,18.1.3)","27002_2022 (5.14,5.33,8.24)","27017_2015 (10.1.1,13.2.1,18.1.3)","27018_2019 (10.1.1,13.2.1,18.1.3)","27701_2019 (6.10.2.1,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.8)","800_53 Rev5 (AC-17.2,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-2)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1.1,4.1,4.1.1,8.2.1)","v4.0 (2.2.7,4.2.2,8.3.2)"],"s202":["(CC6.7)"]},"ecc-gcp-202":{"Standard1":["v7 (6.3)","v8 (8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CIS GCP Foundations Benchmark":["v1.2.0 (6.2.8)","v1.3.0 (6.2.5)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.2,10.2.4,10.2.5,10.3)","v4.0 (10.2.1)"],"s202":["(CC7.2)"]},"ecc-gcp-294":{"Standard1":["v7 (4)","v8 (5.4)"],"BSA":["v3 (IM-2,PA-1)"],"CJIS":["(5.5.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.5,AC.L2-3.1.6,AC.L2-3.1.7,SC.L2-3.13.3)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-09)"],"CE":["v2.2 (A7.5,A7.6,A7.7,uac)"],"DA":["(Article_8.4.c)"],"Standard10":["(D3.PC.Am.B.3)"],"Standard3":["Standard11_800_53_Rev5 (AC-02.07,AC-06.02,AC-06.05)"],"Standard6":["2016_679 (Article_25)"],"Standard15":["(01.e)"],"Standard14":["27001_2013 (A.9.2.3)","27002_2013 (9.2.3)","27002_2022 (5.15,8.2)","27017_2015 (9.2.3)","27018_2019 (9.2.3)","27701_2019 (6.6.2.3)"],"Standard11":["800-171 Rev2 (3.1.6,3.1.7)","800_53 Rev5 (AC-2.7,AC-6.2,AC-6.5)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)"],"s202":["(CC6.3)"]},"ecc-gcp-076":{"Standard1":["v7 (11)","v8 (12.3)"],"CJIS":["(5.10.1)"],"CMMC":["v2.0 (SC.L2-3.13.15)"],"Standard2":["19 (APO01,APO03)"],"DA":["(Article_8.4.b)"],"Standard3":["Standard11_800_53_Rev5 (CM-06,CM-07,SC-23)"],"Standard6":["2016_679 (Article_32)"],"Standard5":["(164.312.e.1)"],"Standard15":["(09.m)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.20,8.21)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard11":["800_53 Rev5 (CM-6,CM-7,SC-23)"],"Standard9":["v1.1 (PR.AC-7)"],"Standard8":["v3.2.1 (8.3)"],"s202":["(CC5.2)"]},"ecc-gcp-281":{"Standard1":["v7 (12.4,9.2)","v8 (12.2)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.3.b,Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)","v4.0 (1.2.5,1.2.6,1.3.1)"],"s202":["(CC6.1,CC6.6)"]},"ecc-gcp-123":{"Standard1":["v7 (5.1)","v8 (16.5)"],"CE":["v2.2 (A6.3)"],"DA":["(Article_8.4.f)"],"Standard3":["Standard11_800_53_Rev5 (SR-11)"],"Standard15":["(10.k)"],"Standard14":["27002_2022 (8.26)"],"Standard12":["(CIP-013-2_Requirement_R2)"],"Standard11":["800_53 Rev5 (SR-11)"],"Standard9":["v1.1 (PR.IP-2)"],"Standard8":["v3.2.1 (6.2)","v4.0 (12.3.4,6.3.3)"]},"ecc-gcp-127":{"Standard1":["v7 (5.2)","v8 (7.6)"],"BSA":["v3 (DS-6,PV-5)"],"CIS GKE Benchmark":["v1.2.0 (5.10.5)","v1.3.0 (5.10.5)","v1.4.0 (5.10.5)"],"CJIS":["(5.10.1.3)"],"CMMC":["v2.0 (RA.L2-3.11.2,SI.L1-3.14.5)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (TVM-07)"],"DA":["(Article_8.1)"],"Standard10":["(D3.DC.Th.B.1)"],"Standard3":["Standard11_800_53_Rev5 (RA-05)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(10.m)"],"Standard14":["27001_2013 (A.12.6.1)","27002_2013 (12.6.1)","27002_2022 (8.8)","27017_2015 (12.6.1)","27018_2019 (12.6)","27701_2019 (6.9.6.1)"],"Standard11":["800-171 Rev2 (3.11.2)","800_53 Rev5 (RA-5)"],"Standard9":["v1.1 (DE.CM-8)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (11.2)","v4.0 (11.3.2.1)"],"s202":["(CC6.6,CC7.1)"]},"ecc-gcp-311":{"Standard1":["v7 (6.2)","v8 (8.2)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.2,10.3)","v4.0 (10.2.1)"],"s202":["(CC7.2)"]},"ecc-gcp-214":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (DP-4,DP-5)"],"CIS GCP Foundations Benchmark":["v1.2.0 (7.3)","v1.3.0 (7.3)","v2.0.0 (7.3)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.3.2,3.3.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-gcp-068":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.2.6)"],"s202":["(CC8.1)"]},"ecc-gcp-268":{"Standard1":["v7 (14.6)","v8 (3.3)"],"BSA":["v3 (AM-4,IM-7,PA-7)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.3.b)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.2,A.9.4.1)","27002_2013 (8.1.3,9.1.2,9.4.1)","27002_2022 (5.10,5.15,8.3)","27017_2015 (8.1.3,9.1.2,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)","v4.0 (1.3.1,7.1)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-gcp-220":{"Standard1":["v7 (13)","v8 (12.2)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.3.b,Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)","v4.0 (1.2.5,1.2.6,1.3.1,1.3.2)"],"s202":["(CC6.1,CC6.6)"]},"ecc-gcp-415":{"Standard1":["v7 (18.9)","v8 (16.8)"],"CIS GKE Benchmark":["v1.2.0 (5.10.4)","v1.3.0 (5.10.4)","v1.4.0 (5.10.4)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (SC-07,SC-07.21)"],"Standard14":["27001_2013 (A.14.2.1)","27002_2013 (14.2.1)","27002_2022 (8.31)","27017_2015 (14.2.1)","27018_2019 (14)","27701_2019 (6.11.2.1)"],"Standard11":["800_53 Rev5 (SC-39)"],"Standard9":["v1.1 (PR.DS-7)"],"Standard8":["v3.2.1 (2.2.4)","v4.0 (2.2.6)"]},"ecc-gcp-251":{"Standard1":["v7 (14.6)","v8 (3.3)"],"BSA":["v3 (AM-4,IM-7,PA-7)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.3.b)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.3)","27017_2015 (8.1.3,9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)","v4.0 (7.1)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-gcp-118":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,12.6,3.3)"],"BSA":["v3 (AM-4,GS-4,IM-7,NS-2,PA-7)"],"CJIS":["(5.10.1,5.13.1.1,5.5.2,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.13,AC.L2-3.1.16,AC.L2-3.1.17,AC.L2-3.1.5,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,IA.L1-3.5.2,MP.L2-3.8.2,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.15,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03,DSS05)"],"Standard4":["v4 (DSP-07,IAM-05,IVS-03)"],"CE":["v2.2 (A4.8,A7.4,Firewalls,Secure_configuration,uac)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Im.B.1,D3.PC.Im.B.10,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,AC-18,CM-07,MP-02,PL-08,SA-08,SC-07,SC-08,SC-23)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.e.1)"],"Standard15":["(01.a,01.c,01.i,01.j,01.l,01.m,01.n,01.o,01.v,09.m,09.s,09.y,10.d)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.2,A.13.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (13.1.1,13.1.2,13.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.20,8.21,8.22,8.27,8.3)","27017_2015 (13.1.1,13.1.2,13.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (13.1,8,9.1,9.4.1)","27701_2019 (6.10.1.1,6.10.1.2,6.10.1.3,6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.1.5,3.13.1,3.13.15,3.13.2,3.13.5,3.4.6,3.8.2)","800_53 Rev5 (AC-18,AC-3,AC-6,CM-7,MP-2,PL-8,PM-7,SA-8,SC-23,SC-7,SC-8)"],"Standard9":["v1.1 (PR.AC-4,PR.AC-5,PR.DS-5,PR.IP-1,PR.PT-3,PR.PT-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5,4.1.1,7.1,7.1.1,7.1.2,7.1.3)","v4.0 (1.2.5,1.2.6,1.3.1,1.4.4,7.1)"],"s202":["(CC5.2,CC6.1,CC6.3,CC6.6)"]},"ecc-gcp-126":{"Standard1":["v7 (18.9)","v8 (16.8)"],"CIS GKE Benchmark":["v1.2.0 (5.10.2)","v1.3.0 (5.10.2)","v1.4.0 (5.10.2)"],"Standard4":["v4 (IVS-05)"],"Standard10":["(D3.PC.Am.B.10)"],"Standard3":["Standard11_800_53_Rev5 (SC-07,SC-07.21)"],"Standard15":["(09.c)"],"Standard14":["27001_2013 (A.14.2.1)","27002_2013 (14.2.1)","27002_2022 (8.31)","27017_2015 (14.2.1)","27018_2019 (14)","27701_2019 (6.11.2.1)"],"Standard11":["800_53 Rev5 (SC-7,SC-7.21)"],"Standard9":["v1.1 (PR.DS-7)"],"Standard8":["v3.2.1 (6.4.1,6.4.2)","v4.0 (6.5.3)"]},"ecc-gcp-122":{"Standard1":["v7 (13.3)","v8 (12.2,12.6,3.3)"],"BSA":["v3 (AM-4,GS-4,IM-7,NS-2,PA-7)"],"CJIS":["(5.10.1,5.13.1.1,5.5.2,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.13,AC.L2-3.1.16,AC.L2-3.1.17,AC.L2-3.1.5,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,IA.L1-3.5.2,MP.L2-3.8.2,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.15,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03,DSS05)"],"Standard4":["v4 (DSP-07,IAM-05,IVS-03)"],"CE":["v2.2 (A4.8,A7.4,Firewalls,Secure_configuration,uac)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Im.B.1,D3.PC.Im.B.10,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,AC-18,CM-07,MP-02,PL-08,SA-08,SC-07,SC-08,SC-23)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.e.1)"],"Standard15":["(01.a,01.c,01.i,01.j,01.l,01.m,01.n,01.o,01.v,09.m,09.s,09.y,10.d)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.2,A.13.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (13.1.1,13.1.2,13.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.20,8.21,8.22,8.27,8.3)","27017_2015 (13.1.1,13.1.2,13.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (13.1,8,9.1,9.4.1)","27701_2019 (6.10.1.1,6.10.1.2,6.10.1.3,6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.1.5,3.13.1,3.13.15,3.13.2,3.13.5,3.4.6,3.8.2)","800_53 Rev5 (AC-18,AC-3,AC-6,CM-7,MP-2,PL-8,PM-7,SA-8,SC-23,SC-7,SC-8)"],"Standard9":["v1.1 (PR.AC-4,PR.AC-5,PR.DS-5,PR.IP-1,PR.PT-3,PR.PT-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5,4.1.1,7.1,7.1.1,7.1.2,7.1.3)","v4.0 (1.2.5,1.2.6,1.3.1,1.4.4,7.1)"],"s202":["(CC5.2,CC6.1,CC6.3,CC6.6)"]},"ecc-gcp-244":{"Standard1":["v7 (4)","v8 (5.4)"],"BSA":["v3 (IM-2,PA-1)"],"CJIS":["(5.5.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.5,AC.L2-3.1.6,AC.L2-3.1.7,SC.L2-3.13.3)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-09)"],"CE":["v2.2 (A7.5,A7.6,A7.7,uac)"],"DA":["(Article_8.4.c)"],"Standard10":["(D3.PC.Am.B.3)"],"Standard3":["Standard11_800_53_Rev5 (AC-02.07,AC-06.02,AC-06.05)"],"Standard6":["2016_679 (Article_25)"],"Standard15":["(01.e)"],"Standard14":["27001_2013 (A.9.2.3)","27002_2013 (9.2.3)","27002_2022 (5.15,8.2)","27017_2015 (9.2.3)","27018_2019 (9.2.3)","27701_2019 (6.6.2.3)"],"Standard11":["800-171 Rev2 (3.1.6,3.1.7)","800_53 Rev5 (AC-2.7,AC-6.2,AC-6.5)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)"],"s202":["(CC6.3)"]},"ecc-gcp-324":{"Standard1":["v7 (14.6)","v8 (3.3)"],"BSA":["v3 (AM-4,PA-7)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L2-3.1.5)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4)"],"DA":["(Article_8.3.b)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c)"],"Standard14":["27001_2013 (A.9.1.1,A.9.4.1)","27002_2013 (9.1.1,9.4.1)","27002_2022 (5.15,8.3)","27017_2015 (9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)","v4.0 (7.1)"],"s202":["(CC5.2,CC6.3)"]},"ecc-gcp-443":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"s202":["(CC8.1)"]},"ecc-gcp-409":{"Standard1":["v7 (2.2,3.4,3.5)","v8 (7.3,7.4)"],"DA":["(Article_8.4.f)"],"Standard3":["Standard11_800_53_Rev4 (SA-4)"],"Standard5":["(164.308.a.5.ii.A)"],"Standard14":["27001_2013 (A.14.2.9)","27002_2013 (14.2.9)","27017_2015 (14.2.9)","27018_2019 (14)","27701_2019 (6.11.2.9)"],"Standard11":["800_53 Rev5 (SA-4)"],"Standard8":["v3.2.1 (2.2.3,2.2.4)"]},"ecc-gcp-200":{"Standard1":["v7 (6.3)","v8 (8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CIS GCP Foundations Benchmark":["v1.2.0 (6.2.5)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.2,10.2.4,10.2.5,10.3)","v4.0 (10.2.1)"],"s202":["(CC7.2)"]},"ecc-gcp-049":{"Standard1":["v7 (16,4)","v8 (6.8)"],"BSA":["v3 (PA-1,PA-7)"],"CIS GKE Benchmark":["v1.2.0 (5.8.4)","v1.3.0 (5.8.4)"],"CJIS":["(5.5.1,5.5.2,5.5.2.1,5.5.2.2,5.5.2.3,5.5.2.4)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L2-3.1.4,AC.L2-3.1.5,SC.L2-3.13.3)"],"Standard4":["v4 (IAM-04,IAM-05,IAM-16)"],"CE":["v2.2 (A7.4,A7.8,A7.9)"],"Standard10":["(D1.R.St.B.1,D3.PC.Am.B.1,D3.PC.Am.B.2)"],"Standard3":["Standard11_800_53_Rev5 (AC-02,AC-02.07,AC-03,AC-05,AC-06,AC-06.01,AC-06.07,AU-9.4)"],"Standard6":["2016_679 (Article_25)"],"Standard5":["(164.308.a.3.i,164.312.a.1)"],"Standard15":["(01.c)"],"Standard14":["27001_2013 (A.6.1.2,A.9,A.9.4.1)","27002_2013 (6.1.2,9,9.4.1)","27002_2022 (5.15,5.3,8.2,8.3)","27017_2015 (6.1.2,9,9.4.1)","27018_2019 (6.1.2,9,9.4.1)","27701_2019 (6.10.1.3,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.4,3.1.5)","800_53 Rev5 (AC-2,AC-2.7,AC-3.7,AC-5,AC-6,AC-6.1,AC-6.7,AU-9.4)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v4.0 (1.2.6)"],"s202":["(CC5.2,CC6.3)"]},"ecc-gcp-083":{"Standard1":["v7 (18.11)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.2.6)"],"s202":["(CC8.1)"]},"ecc-gcp-051":{"Standard1":["v7 (5)","v8 (3.7)"],"BSA":["v3 (DP-1)"],"CIS GCP Foundations Benchmark":["v1.0.0 (7.5)"],"Standard2":["19 (APO14)"],"Standard4":["v4 (DSP-01,DSP-04)"],"Standard10":["(D1.G.IT.B.2)"],"Standard3":["Standard11_800_53_Rev5 (RA-02)"],"Standard15":["(00.a)"],"Standard14":["27001_2013 (A.18.1.3,A.8.1.1,A.8.2.1,A.8.2.2)","27002_2013 (18.1.3,8.1.1,8.2.1,8.2.2)","27002_2022 (5.12,5.13,5.33,5.9,8.12)","27017_2015 (18.1.3,8.1.1,8.2.1,8.2.2)","27018_2019 (18.1,8)","27701_2019 (6.15.1.3,6.5.1.1,6.5.2.1,6.5.2.2)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.1)"],"Standard11":["800_53 Rev5 (RA-2)"],"Standard9":["v1.1 (ID.AM-5,ID.RA-5)"],"Standard7":["(2._Classify_personal_data_by_type)"],"Standard8":["v3.2.1 (9.6.1)","v4.0 (9.4.2)"],"s202":["(CC3.2)"]},"ecc-gcp-167":{"Standard1":["v7 (14.6)","v8 (5.6)"],"BSA":["v3 (GS-6)"],"Standard10":["(D3.PC.Am.B.6)"],"Standard3":["Standard11_800_53_Rev5 (AC-02.01)"],"Standard15":["(01.q)"],"Standard14":["27001_2013 (A.9)","27002_2013 (9)","27002_2022 (5.15)","27017_2015 (9)","27018_2019 (9)","27701_2019 (6.6)"],"Standard11":["800_53 Rev5 (AC-2.1)"],"Standard9":["v1.1 (PR.AC-7)"],"s202":["(CC6.1)"]},"ecc-gcp-022":{"Standard1":["v7 (6.2,6.3)","v8 (8.2,8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CIS GCP Foundations Benchmark":["v1.2.0 (2.9)","v1.3.0 (2.9)","v2.0.0 (2.9)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_15.1,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.2,10.2.4,10.2.5,10.3)","v4.0 (10.2.1,10.2.1.2)"],"s202":["(CC7.2)"]},"ecc-gcp-203":{"Standard1":["v7 (6.3)","v8 (8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CIS GCP Foundations Benchmark":["v1.2.0 (6.2.9)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.2,10.2.4,10.2.5,10.3)","v4.0 (10.2.1)"],"s202":["(CC7.2)"]},"ecc-gcp-136":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.2.6)"],"s202":["(CC8.1)"]},"ecc-gcp-208":{"Standard1":["v7 (2.9)","v8 (2.7)"],"BSA":["v3 (AM-2,AM-5)"],"CIS GCP Foundations Benchmark":["v1.2.0 (6.3.1)","v1.3.0 (6.3.1)","v2.0.0 (6.3.1)"],"CMMC":["v2.0 (CM.L2-3.4.7)"],"DA":["(Article_8.4.e)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,CM-07.01)"],"Standard15":["(10.h)"],"Standard11":["800_53 Rev5 (CM-7,CM-7.1)"],"Standard9":["v1.1 (PR.IP-1,PR.PT-3)"],"Standard8":["v4.0 (2.2.4)"],"s202":["(CC5.2,CC7.1)"]},"ecc-gcp-125":{"Standard1":["v7 (9.2)","v8 (3.10)"],"BSA":["v3 (DP-3)"],"CJIS":["(5.10.1.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IVS-03)"],"DA":["(Article_8.3.a)"],"Standard10":["(D3.PC.Am.B.12,D3.PC.Am.B.13)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,SC-08)"],"Standard6":["2016_679 (Article_32.1.a,Article_44)"],"Standard5":["(164.312.e.1)"],"Standard15":["(06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1,A.18.1.3)","27002_2013 (10.1.1,13.2.1,18.1.3)","27002_2022 (5.14,5.33,8.24)","27017_2015 (10.1.1,13.2.1,18.1.3)","27018_2019 (10.1.1,13.2.1,18.1.3)","27701_2019 (6.10.2.1,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.8,3.5.10)","800_53 Rev5 (AC-17.2,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-2)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1.1,4.1,4.1.1,8.2.1)","v4.0 (2.2.7,4.1.1,4.2.2,8.3.2)"],"s202":["(CC6.1,CC6.7)"]},"ecc-gcp-347":{"Standard1":["v7 (5.1)","v8 (11,4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (IVS-02)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_11.5,Article_8.2,Article_8.4.a)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,CP-06,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32.1.b,Article_32.1.c)"],"Standard15":["(08.k,10.k,12.c)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,CP-6,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1,PR.PT-5)"],"Standard8":["v4.0 (2.2.6)"],"s202":["(CC8.1)"]},"ecc-gcp-306":{"Standard1":["v7 (12.4)","v8 (3.3)"],"BSA":["v3 (AM-4,PA-7)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.3.b)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.9.1.1,A.9.4.1)","27002_2013 (9.1.1,9.4.1)","27002_2022 (5.15,8.3)","27017_2015 (9.1.1,9.4.1)","27018_2019 (9.1,9.4.1)","27701_2019 (6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)","v4.0 (1.3.1,7.1)"],"s202":["(CC5.2,CC6.3)"]},"ecc-gcp-044":{"Standard1":["v7 (13,14.6)","v8 (3.3)"],"BSA":["v3 (AM-4,IM-7,PA-7)"],"CIS GCP Foundations Benchmark":["v1.2.0 (6.5)","v1.3.0 (6.5)","v2.0.0 (6.5)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_14.d,Article_8.2,Article_8.3.b)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.3)","27017_2015 (8.1.3,9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)","v4.0 (7.1)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-gcp-314":{"Standard1":["v7 (14.6)","v8 (3.3)"],"BSA":["v3 (AM-4,IM-7)"],"CIS GCP Foundations Benchmark":["v1.3.0 (2.15)","v2.0.0 (2.15)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L2-3.1.5)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.3.b)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-06)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i)"],"Standard15":["(01.c)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.3)","27017_2015 (8.1.3,9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2)","800_53 Rev5 (AC-06)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)","v4.0 (7.1)"],"s202":["(CC8.1)"]},"ecc-gcp-260":{"Standard1":["v7 (4.1)","v8 (5.4)"],"BSA":["v3 (IM-2,PA-1)"],"CJIS":["(5.5.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.5,AC.L2-3.1.6,AC.L2-3.1.7,SC.L2-3.13.3)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-09)"],"CE":["v2.2 (A7.5,A7.6,A7.7,uac)"],"DA":["(Article_8.4.c)"],"Standard10":["(D3.PC.Am.B.3)"],"Standard3":["Standard11_800_53_Rev5 (AC-02.07,AC-06.02,AC-06.05)"],"Standard6":["2016_679 (Article_25)"],"Standard15":["(01.e)"],"Standard14":["27001_2013 (A.9.2.3)","27002_2013 (9.2.3)","27002_2022 (5.15,8.2)","27017_2015 (9.2.3)","27018_2019 (9.2.3)","27701_2019 (6.6.2.3)"],"Standard11":["800-171 Rev2 (3.1.6,3.1.7)","800_53 Rev5 (AC-2.7,AC-6.2,AC-6.5)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)"],"s202":["(CC6.3)"]},"ecc-gcp-342":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,13.4)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,CM.L2-3.4.7,SC.L1-3.13.1,SC.L2-3.13.2,SC.L2-3.13.6)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.3.b,Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.l,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1)","27002_2013 (13.1.1)","27002_2022 (8.20,8.27)","27017_2015 (13.1.1)","27018_2019 (13.1)","27701_2019 (6.10.1.1)"],"Standard12":["(CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.13.1,3.4.6)","800_53 Rev5 (CA-9,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)","v4.0 (1.2.5,1.2.6,1.3.1)"],"s202":["(CC6.1,CC6.6)"]},"ecc-gcp-237":{"Standard1":["v7 (5.1)","v8 (16.5)"],"CE":["v2.2 (A6.3)"],"DA":["(Article_8.4.f)"],"Standard3":["Standard11_800_53_Rev5 (SR-11)"],"Standard15":["(10.k)"],"Standard14":["27002_2022 (8.26)"],"Standard12":["(CIP-013-2_Requirement_R2)"],"Standard11":["800_53 Rev5 (SR-11)"],"Standard9":["v1.1 (PR.IP-2)"],"Standard8":["v3.2.1 (6.2)","v4.0 (12.3.4,6.3.3)"]},"ecc-gcp-072":{"Standard1":["v7 (11.2,14.2)","v8 (12.2,3.3)"],"BSA":["v3 (AM-4,GS-4,IM-7,NS-2,PA-7)"],"CJIS":["(5.10.1,5.5.2,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,AC.L2-3.1.5,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,MP.L2-3.8.2,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03,DSS05)"],"Standard4":["v4 (DSP-07,IAM-05,IVS-03)"],"CE":["v2.2 (A4.8,A7.4,Firewalls,Secure_configuration,uac)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,CM-07,MP-02,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.i,01.l,01.m,01.n,01.o,01.v,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (13.1.1,13.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.20,8.22,8.27,8.3)","27017_2015 (13.1.1,13.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (13.1,8,9.1,9.4.1)","27701_2019 (6.10.1.1,6.10.1.3,6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.1.5,3.13.2,3.13.5,3.4.6,3.8.2)","800_53 Rev5 (AC-3,AC-6,CM-7,MP-2,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-4,PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5,7.1,7.1.1,7.1.2,7.1.3)","v4.0 (1.2.5,1.2.6,1.3.2,1.4.4,7.1)"],"s202":["(CC5.2,CC6.1,CC6.3,CC6.6)"]},"ecc-gcp-256":{"Standard1":["v7 (12.4,9.2)","v8 (12.2)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.3.b,Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)","v4.0 (1.2.5,1.2.6,1.3.1,1.3.2)"],"s202":["(CC6.1,CC6.6)"]},"ecc-gcp-054":{"Standard1":["v7 (2.2,3.4,3.5)","v8 (7.3,7.4)"],"BSA":["v3 (PV-6)"],"CIS GKE Benchmark":["v1.2.0 (5.5.3)","v1.3.0 (5.5.3)","v1.4.0 (5.5.3)"],"CJIS":["(5.10.4.1,5.13.3,5.13.4.1)"],"CMMC":["v2.0 (SI.L1-3.14.1)"],"CE":["v2.2 (A6.4,A6.4.1,A6.5.1,Security_update_management)"],"DA":["(Article_8.4.f)"],"Standard10":["(D3.CC.Pa.B.1)"],"Standard3":["Standard11_800_53_Rev5 (RA-05,RA-07,SI-02,SI-02.02)"],"Standard5":["(164.308.a.1.ii.B)"],"Standard15":["(10.m)"],"Standard14":["27001_2013 (A.12.6.1)","27002_2013 (12.6.1)","27002_2022 (8.8)","27017_2015 (12.6.1)","27018_2019 (12.6)","27701_2019 (6.9.6.1)"],"Standard12":["(CIP-007-6_Requirement_R2_Part_2.1)"],"Standard11":["800_53 Rev5 (RA-5,RA-7,SI-2,SI-2.2,SI-2.4)"],"Standard8":["v3.2.1 (6.2)"],"s202":["(CC7.1,CC8.1)"]},"ecc-gcp-230":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.2.6)"],"s202":["(CC8.1)"]},"ecc-gcp-277":{"Standard1":["v8 (12.2)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)","v4.0 (1.2.5,1.2.6,1.3.1,1.3.2)"],"s202":["(CC6.1,CC6.6)"]},"ecc-gcp-008":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (DP-4,DP-5)"],"CIS GCP Foundations Benchmark":["v1.2.0 (1.10)","v1.3.0 (1.10)","v2.0.0 (1.10)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2)"],"Standard3":["Standard11_800_53_Rev5 (SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.3.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-gcp-250":{"Standard1":["v8 (7.5)"],"BSA":["v3 (DS-6,PV-5)"],"CIS GKE Benchmark":["v1.2.0 (5.1.1)","v1.3.0 (5.1.1)","v1.4.0 (5.1.1)"],"CJIS":["(5.10.1.3)"],"CMMC":["v2.0 (RA.L2-3.11.2,SI.L1-3.14.5)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (TVM-07)"],"DA":["(Article_8.1,Article_8.4.f)"],"Standard10":["(D3.DC.Th.B.1)"],"Standard3":["Standard11_800_53_Rev5 (RA-05)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(10.m)"],"Standard14":["27001_2013 (A.12.6.1)","27002_2013 (12.6.1)","27002_2022 (8.8)","27017_2015 (12.6.1)","27018_2019 (12.6)","27701_2019 (6.9.6.1)"],"Standard11":["800-171 Rev2 (3.11.2)","800_53 Rev5 (RA-5)"],"Standard9":["v1.1 (DE.CM-8)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (11.2)","v4.0 (11.3.1)"],"s202":["(CC7.1)"]},"ecc-gcp-304":{"Standard1":["v7 (12)","v8 (12.2)"],"BSA":["v3 (NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.20,CM.L2-3.4.6,SC.L2-3.13.2)"],"Standard4":["v4 (IVS-03)"],"DA":["(Article_8.3.b,Article_8.4.b)"],"Standard3":["Standard11_800_53_Rev5 (SC-07)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard15":["(01.n)"],"Standard14":["27001_2013 (A.13.1.1)","27002_2013 (13.1.1)","27002_2022 (8.20,8.27)","27017_2015 (13.1.1)","27018_2019 (13.1)","27701_2019 (6.10.1.1)"],"Standard11":["800-171 Rev2 (3.13.2)","800_53 Rev5 (SC-7)"],"Standard9":["v1.1 (PR.PT-3)"],"Standard8":["v3.2.1 (2.2.2)","v4.0 (1.2.5,1.2.6,1.3.1)"],"s202":["(CC6.6)"]},"ecc-gcp-152":{"Standard1":["v7 (1.4)","v8 (3.7)"],"BSA":["v3 (DP-1)"],"Standard2":["19 (APO14)"],"Standard4":["v4 (DSP-01,DSP-04)"],"Standard10":["(D1.G.IT.B.2)"],"Standard3":["Standard11_800_53_Rev5 (RA-02)"],"Standard15":["(00.a)"],"Standard14":["27001_2013 (A.18.1.3,A.8.1.1,A.8.2.1,A.8.2.2)","27002_2013 (18.1.3,8.1.1,8.2.1,8.2.2)","27002_2022 (5.12,5.13,5.33,5.9,8.12)","27017_2015 (18.1.3,8.1.1,8.2.1,8.2.2)","27018_2019 (18.1,8)","27701_2019 (6.15.1.3,6.5.1.1,6.5.2.1,6.5.2.2)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.1)"],"Standard11":["800_53 Rev5 (RA-2)"],"Standard9":["v1.1 (ID.AM-5,ID.RA-5)"],"Standard7":["(2._Classify_personal_data_by_type)"],"Standard8":["v3.2.1 (9.6.1)","v4.0 (9.4.2)"],"s202":["(CC3.2)"]},"ecc-gcp-252":{"Standard1":["v7 (5.1)","v8 (12.1)"],"CJIS":["(5.10.4.1)"],"CE":["v2.2 (A6.1,A6.2,A6.3,A6.6,Security_update_management)"],"DA":["(Article_8.4.f)"],"Standard10":["(D3.CC.Pa.B.1)"],"Standard3":["Standard11_800_53_Rev5 (SI-02)"],"Standard14":["27001_2013 (A.13.1.1)","27002_2013 (13.1.1)","27002_2022 (8.20)","27017_2015 (13.1.1)","27018_2019 (13.1)","27701_2019 (6.10.1.1)"],"Standard12":["(CIP-007-6_Requirement_R2_Part_2.1)"],"Standard11":["800_53 Rev5 (SI-2)"],"s202":["(CC8.1)"]},"ecc-gcp-445":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"s202":["(CC8.1)"]},"ecc-gcp-070":{"Standard1":["v7 (18.10)","v8 (4.4)"],"BSA":["v3 (GS-9,NS-1,NS-2,NS-3,NS-7,NS-8)"],"CJIS":["(5.10.1,5.7.1.2)"],"CMMC":["v2.0 (AC.L1-3.1.20,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (APO13)"],"Standard4":["v4 (UEM-10)"],"CE":["v2.2 (A4.11,A4.5,A4.7,Firewalls)"],"DA":["(Article_8.3.b,Article_8.4.c)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.2,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,SC-07,SC-07.05)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard15":["(09.m)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.20)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard11":["800-171 Rev2 (3.13.1)","800_53 Rev5 (CA-9,SC-7,SC-7.5)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (1.1.4,1.3.1)","v4.0 (1.2.6,1.3.1)"],"s202":["(CC6.6)"]},"ecc-gcp-035":{"Standard1":["v7 (16.5,4.4)","v8 (3.10,5.2)"],"BSA":["v3 (DP-3)"],"CIS GCP Foundations Benchmark":["v1.2.0 (4.3)","v1.3.0 (4.3)","v2.0.0 (4.3)"],"CJIS":["(5.10.1.2.1,5.13.7.1,5.13.7.2,5.6.2.1.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,IA.L2-3.5.7,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IAM-02,IVS-03)"],"CE":["v2.2 (A4.3,A5.3,A5.5,A7.2,Firewalls,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.a,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12,D3.PC.Am.B.13,D3.PC.Am.B.7)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,IA-05.01,SC-08)"],"Standard6":["2016_679 (Article_32.1.a,Article_44)"],"Standard5":["(164.308.a.5.ii.D,164.312.e.1)"],"Standard15":["(01.f,06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1,A.18.1.3,A.9.4.3)","27002_2013 (10.1.1,13.2.1,18.1.3,9.4.3)","27002_2022 (5.14,5.17,5.33,8.24)","27017_2015 (10.1.1,13.2.1,18.1.3,9.4.3)","27018_2019 (10.1.1,13.2.1,18.1.3,9.4.3)","27701_2019 (6.10.2.1,6.15.1.3,6.6.4.3,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-007-6_Requirement_R5_Part_5.5,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.8,3.5.10,3.5.7)","800_53 Rev5 (AC-17.2,IA-5.1,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-2)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1,2.1.1,4.1,4.1.1,8.2.1,8.2.3,8.2.6)","v4.0 (2.2.7,4.2.2,8.3.2)"],"s202":["(CC6.1,CC6.7)"]},"ecc-gcp-017":{"Standard1":["v7 (6.2)","v8 (8.2)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CIS GCP Foundations Benchmark":["v1.2.0 (2.4)","v1.3.0 (2.4)","v2.0.0 (2.4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_15.1,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.2,10.3)","v4.0 (10.2.1.2)"],"s202":["(CC7.2)"]},"ecc-gcp-141":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (DP-4,DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.3.2,3.3.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-gcp-193":{"Standard1":["v7 (6.3)","v8 (8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CIS GKE Benchmark":["v1.2.0 (5.6.1)","v1.3.0 (5.6.1)","v1.4.0 (5.6.1)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.2,10.2.4,10.2.5,10.3)","v4.0 (10.2.1)"],"s202":["(CC7.2)"]},"ecc-gcp-175":{"Standard1":["v7 (14.6)","v8 (3.3)"],"BSA":["v3 (AM-4,IM-7,PA-7)"],"CIS GCP Foundations Benchmark":["v1.2.0 (5.2)","v1.3.0 (5.2)","v2.0.0 (5.2)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.3.b)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.3)","27017_2015 (8.1.3,9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)","v4.0 (1.3.1,7.1)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-gcp-199":{"Standard1":["v7 (6.3)","v8 (8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CIS GCP Foundations Benchmark":["v1.2.0 (6.2.2)","v1.3.0 (6.2.1)","v2.0.0 (6.2.1)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.2,10.2.4,10.2.5,10.3)","v4.0 (10.2.1)"],"s202":["(CC7.2)"]},"ecc-gcp-185":{"Standard1":["v7 (14.6)","v8 (3.3)"],"BSA":["v3 (AM-4,IM-7,PA-7)"],"CIS GCP Foundations Benchmark":["v1.2.0 (6.3.7)","v1.3.0 (6.3.7)","v2.0.0 (6.3.7)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.3.b)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.3)","27017_2015 (8.1.3,9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)","v4.0 (1.3.1,7.1)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-gcp-131":{"Standard1":["v7 (5.1)","v8 (12.2)"],"BSA":["v3 (GS-4,NS-2)"],"CIS GKE Benchmark":["v1.2.0 (5.6.5)","v1.3.0 (5.6.5)","v1.4.0 (5.6.5)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)","v4.0 (1.2.5,1.2.6,1.3.1,1.3.2)"],"s202":["(CC6.1,CC6.6)"]},"ecc-gcp-207":{"Standard1":["v7 (6.3)","v8 (8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CIS GCP Foundations Benchmark":["v1.2.0 (6.2.14)","v1.3.0 (6.2.7)","v2.0.0 (6.2.6)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.2,10.2.4,10.2.5,10.3)","v4.0 (10.2.1)"],"s202":["(CC7.2)"]},"ecc-gcp-004":{"Standard1":["v7 (4.3)","v8 (5.4)"],"BSA":["v3 (IM-2,PA-1)"],"CIS GCP Foundations Benchmark":["v1.2.0 (1.5)","v1.3.0 (1.5)","v2.0.0 (1.5)"],"CJIS":["(5.5.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.5,AC.L2-3.1.6,AC.L2-3.1.7,SC.L2-3.13.3)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-09)"],"CE":["v2.2 (A7.5,A7.6,A7.7,uac)"],"DA":["(Article_8.4.c)"],"Standard10":["(D3.PC.Am.B.3)"],"Standard3":["Standard11_800_53_Rev5 (AC-02.07,AC-06.02,AC-06.05)"],"Standard6":["2016_679 (Article_25)"],"Standard15":["(01.e)"],"Standard14":["27001_2013 (A.9.2.3)","27002_2013 (9.2.3)","27002_2022 (5.15,8.2)","27017_2015 (9.2.3)","27018_2019 (9.2.3)","27701_2019 (6.6.2.3)"],"Standard11":["800-171 Rev2 (3.1.6,3.1.7)","800_53 Rev5 (AC-2.7,AC-6.2,AC-6.5)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)"],"s202":["(CC6.3)"]},"ecc-gcp-289":{"Standard1":["v7 (12.4,9.2)","v8 (12.2)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.3.b,Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)","v4.0 (1.2.5,1.2.6,1.3.1)"],"s202":["(CC6.1,CC6.6)"]},"ecc-gcp-104":{"Standard1":["v7 (11.2,14.2)","v8 (12.2,12.6)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.13.1.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.13,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,IA.L1-3.5.2,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.15,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.10,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-07,PL-08,SA-08,SC-07,SC-08,SC-23)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.312.e.1)"],"Standard15":["(01.i,01.j,01.l,01.m,01.n,01.o,09.m,09.s,09.y,10.d)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.2,A.13.1.3)","27002_2013 (13.1.1,13.1.2,13.1.3)","27002_2022 (8.20,8.21,8.22,8.27)","27017_2015 (13.1.1,13.1.2,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.2,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.1,3.13.15,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (AC-18,CM-7,PL-8,PM-7,SA-8,SC-23,SC-7,SC-8)"],"Standard9":["v1.1 (PR.AC-5,PR.DS-5,PR.IP-1,PR.PT-3,PR.PT-4)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5,4.1.1)","v4.0 (1.2.5,1.2.6,1.3.1,1.3.2,1.4.4)"],"s202":["(CC5.2,CC6.1,CC6.6)"]},"ecc-gcp-273":{"Standard1":["v7 (18.10)","v8 (4.4)"],"BSA":["v3 (GS-9,NS-1,NS-2,NS-3,NS-7,NS-8)"],"CJIS":["(5.10.1,5.7.1.2)"],"CMMC":["v2.0 (AC.L1-3.1.20,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (APO13)"],"Standard4":["v4 (UEM-10)"],"CE":["v2.2 (A4.11,A4.5,A4.7,Firewalls)"],"DA":["(Article_8.3.b,Article_8.4.c)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.2,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,SC-07,SC-07.05)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard15":["(09.m)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.20)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard11":["800-171 Rev2 (3.13.1)","800_53 Rev5 (CA-9,SC-7,SC-7.5)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (1.1.4,1.3.1)","v4.0 (1.2.6)"],"s202":["(CC6.6)"]},"ecc-gcp-234":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (AM-4,DP-4,DP-5,IM-7,PA-7)"],"CJIS":["(5.10.1.2.2,5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.1,MP.L2-3.8.2,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03,IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,IA-05.01,MP-02,SC-28)"],"Standard6":["2016_679 (Article_32.1.a,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.a.2.iv)"],"Standard15":["(01.a,01.c,01.v,06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,5.33,8.24,8.3)","27017_2015 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (10.1.1,18.1.3,8,9.1,9.4.1)","27701_2019 (6.15.1.3,6.5.1.3,6.6.1.1,6.6.4.1,6.7.1.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.13.16,3.8.1,3.8.2)","800_53 Rev5 (AC-3,AC-6,IA-5.1,MP-2,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.AC-4,PR.DS-1)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (3.4,3.4.1,7.1,7.1.1,7.1.2,7.1.3,8.2.1)","v4.0 (3.3.2,3.3.3,8.3.2)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-gcp-117":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,12.6,3.3)"],"BSA":["v3 (AM-4,GS-4,IM-7,NS-2,PA-7)"],"CJIS":["(5.10.1,5.13.1.1,5.5.2,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.13,AC.L2-3.1.16,AC.L2-3.1.17,AC.L2-3.1.5,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,IA.L1-3.5.2,MP.L2-3.8.2,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.15,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03,DSS05)"],"Standard4":["v4 (DSP-07,IAM-05,IVS-03)"],"CE":["v2.2 (A4.8,A7.4,Firewalls,Secure_configuration,uac)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Im.B.1,D3.PC.Im.B.10,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,AC-18,CM-07,MP-02,PL-08,SA-08,SC-07,SC-08,SC-23)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.e.1)"],"Standard15":["(01.a,01.c,01.i,01.j,01.l,01.m,01.n,01.o,01.v,09.m,09.s,09.y,10.d)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.2,A.13.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (13.1.1,13.1.2,13.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.20,8.21,8.22,8.27,8.3)","27017_2015 (13.1.1,13.1.2,13.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (13.1,8,9.1,9.4.1)","27701_2019 (6.10.1.1,6.10.1.2,6.10.1.3,6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.1.5,3.13.1,3.13.15,3.13.2,3.13.5,3.4.6,3.8.2)","800_53 Rev5 (AC-18,AC-3,AC-6,CM-7,MP-2,PL-8,PM-7,SA-8,SC-23,SC-7,SC-8)"],"Standard9":["v1.1 (PR.AC-4,PR.AC-5,PR.DS-5,PR.IP-1,PR.PT-3,PR.PT-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5,4.1.1,7.1,7.1.1,7.1.2,7.1.3)","v4.0 (1.2.5,1.2.6,1.3.1,1.4.4,7.1)"],"s202":["(CC5.2,CC6.1,CC6.3,CC6.6)"]},"ecc-gcp-309":{"Standard1":["v7 (12)","v8 (12.2)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.20,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"DA":["(Article_8.3.b,Article_8.4.b)"],"Standard3":["Standard11_800_53_Rev5 (PL-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.n,09.m)"],"Standard14":["27001_2013 (A.13.1.1)","27002_2013 (13.1.1)","27002_2022 (8.20,8.27)","27017_2015 (13.1.1)","27018_2019 (13.1)","27701_2019 (6.10.1.1)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2)","800_53 Rev5 (PL-8,SC-7)"],"Standard9":["v1.1 (PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (2.2.2)","v4.0 (1.2.5,1.2.6,1.3.1)"],"s202":["(CC6.6)"]},"ecc-gcp-103":{"Standard1":["v7 (5)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.2.6)"],"s202":["(CC8.1)"]},"ecc-gcp-271":{"Standard1":["v7 (9.4)","v8 (4.4)"],"BSA":["v3 (GS-9,NS-1,NS-2,NS-3,NS-7,NS-8)"],"CJIS":["(5.10.1,5.7.1.2)"],"CMMC":["v2.0 (AC.L1-3.1.20,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (APO13)"],"Standard4":["v4 (UEM-10)"],"CE":["v2.2 (A4.11,A4.5,A4.7,Firewalls)"],"DA":["(Article_8.3.b,Article_8.4.c)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.2,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,SC-07,SC-07.05)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard15":["(09.m)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.20)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard11":["800-171 Rev2 (3.13.1)","800_53 Rev5 (CA-9,SC-7,SC-7.5)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (1.1.4,1.3.1)","v4.0 (1.2.5,1.2.6,1.3.2)"],"s202":["(CC6.6)"]},"ecc-gcp-346":{"Standard1":["v7 (1.4)","v8 (3.7)"],"BSA":["v3 (DP-1)"],"Standard2":["19 (APO14)"],"Standard4":["v4 (DSP-01,DSP-04)"],"Standard10":["(D1.G.IT.B.2)"],"Standard3":["Standard11_800_53_Rev5 (RA-02)"],"Standard15":["(00.a)"],"Standard14":["27001_2013 (A.8.1.1,A.8.2.1,A.8.2.2)","27002_2013 (8.1.1,8.2.1,8.2.2)","27002_2022 (5.12,5.13,5.9,8.12)","27017_2015 (8.1.1,8.2.1,8.2.2)","27018_2019 (8)","27701_2019 (6.5.1.1,6.5.2.1,6.5.2.2)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.1)"],"Standard11":["800_53 Rev5 (RA-2)"],"Standard9":["v1.1 (ID.AM-5,ID.RA-5)"],"Standard7":["(2._Classify_personal_data_by_type)"],"Standard8":["v3.2.1 (9.6.1)","v4.0 (9.4.2)"],"s202":["(CC3.2)"]},"ecc-gcp-020":{"Standard1":["v7 (6.2,6.3)","v8 (8.2,8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CIS GCP Foundations Benchmark":["v1.2.0 (2.7)","v1.3.0 (2.7)","v2.0.0 (2.7)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_15.1,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.2,10.2.4,10.2.5,10.3)","v4.0 (10.2.1,10.2.1.2)"],"s202":["(CC7.2)"]},"ecc-gcp-432":{"Standard1":["v7 (12)","v8 (4.4)"],"BSA":["v3 (GS-4,NS-2)"],"CIS GKE Benchmark":["v1.2.0 (5.6.4)","v1.3.0 (5.6.4)","v1.4.0 (5.6.4)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)","v4.0 (1.2.5,1.2.6,1.3.1,1.3.2)"],"s202":["(CC6.1,CC6.6)"]},"ecc-gcp-087":{"Standard1":["v7 (14.4)","v8 (3.10)"],"BSA":["v3 (DP-3)"],"CJIS":["(5.10.1.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IVS-03)"],"DA":["(Article_8.2,Article_8.3.a)"],"Standard10":["(D3.PC.Am.B.12,D3.PC.Am.B.13)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,SC-08)"],"Standard6":["2016_679 (Article_32.1.a,Article_44)"],"Standard5":["(164.312.e.1)"],"Standard15":["(06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1,A.18.1.3)","27002_2013 (10.1.1,13.2.1,18.1.3)","27002_2022 (5.14,5.33,8.24)","27017_2015 (10.1.1,13.2.1,18.1.3)","27018_2019 (10.1.1,13.2.1,18.1.3)","27701_2019 (6.10.2.1,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.8,3.5.10)","800_53 Rev5 (AC-17.2,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-2)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1.1,4.1,4.1.1,8.2.1)","v4.0 (2.2.7,4.1.1,4.2.2,8.3.2)"],"s202":["(CC6.1,CC6.7)"]},"ecc-gcp-448":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,PV-1)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (APO13,BAI06)"],"Standard4":["v4 (IVS-02)"],"DA":["(Article_8.2)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard5":["(164.308.a.1.ii.B,164.308.a.7.i,164.308.a.7.ii.C,164.312.e.1,164.314.b.2.i)"],"Standard15":["(10.k,12.c)"],"Standard14":["27001_2013 (A.14.2.5,A.17.2.1)","27002_2013 (14.2.5,17.2.1)","27002_2022 (8.14,8.27,8.9)","27017_2015 (14.2.5,17.2.1)","27018_2019 (14,17)","27701_2019 (6.11.2.5,6.14.2.1)"],"Standard12":["(CIP-003-8_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.4.1)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-8)"],"Standard9":["v1.1 (PR.IP-1,PR.PT-5)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-gcp-032":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,3.3)"],"BSA":["v3 (AM-4,GS-4,IM-7,NS-2,PA-7)"],"CIS GCP Foundations Benchmark":["v1.0.0 (3.8)"],"CJIS":["(5.10.1,5.5.2,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,AC.L2-3.1.5,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,MP.L2-3.8.2,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03,DSS05)"],"Standard4":["v4 (DSP-07,IAM-05,IVS-03)"],"CE":["v2.2 (A4.8,A7.4,Firewalls,Secure_configuration,uac)"],"DA":["(Article_14.d,Article_8.3.b,Article_8.4.b)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,CM-07,MP-02,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.i,01.l,01.m,01.n,01.o,01.v,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (13.1.1,13.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.20,8.22,8.27,8.3)","27017_2015 (13.1.1,13.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (13.1,8,9.1,9.4.1)","27701_2019 (6.10.1.1,6.10.1.3,6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.1.5,3.13.2,3.13.5,3.4.6,3.8.2)","800_53 Rev5 (AC-3,AC-6,CM-7,MP-2,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-4,PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5,7.1,7.1.1,7.1.2,7.1.3)","v4.0 (1.2.6,1.3.2,1.4.4,7.1)"],"s202":["(CC5.2,CC6.1,CC6.3,CC6.6)"]},"ecc-gcp-186":{"Standard1":["v7 (12)","v8 (12.2)"],"BSA":["v3 (GS-4,NS-2)"],"CIS GCP Foundations Benchmark":["v1.2.0 (6.6)","v1.3.0 (6.6)","v2.0.0 (6.6)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.3.b,Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)","v4.0 (1.2.5,1.2.6,1.3.1,1.3.2)"],"s202":["(CC6.1,CC6.6)"]},"ecc-gcp-063":{"Standard1":["v7 (4.3)","v8 (5.5)"],"BSA":["v3 (PA-4,PV-5)"],"CIS GKE Benchmark":["v1.2.0 (5.2.1)","v1.3.0 (5.2.1)","v1.4.0 (5.2.1)"],"CJIS":["(5.5.1)"],"Standard4":["v4 (IAM-03)"],"CE":["v2.2 (A7.8,A7.9,uac)"],"Standard10":["(D3.DC.An.B.4)"],"Standard3":["Standard11_800_53_Rev5 (AC-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.15)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard12":["(CIP-007-6_Requirement_R5_Part_5.3)"],"Standard11":["800_53 Rev5 (AC-2)"],"Standard9":["v1.1 (PR.AC-1)"],"Standard8":["v4.0 (7.2.1,7.2.2)"],"s202":["(8.1.1)"]},"ecc-gcp-258":{"Standard1":["v7 (10.1)","v8 (11.2)"],"BSA":["v3 (BR-1)"],"Standard4":["v4 (BCR-08)"],"CE":["v2.2 (Back_up_your_data)"],"DA":["(Article_11.2,Article_11.4)"],"Standard10":["(D5.IR.Pl.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CP-09,CP-10)"],"Standard6":["2016_679 (Article_32.1.c)"],"Standard5":["(164.308.a.7.ii.A)"],"Standard15":["(09.l)"],"Standard14":["27001_2013 (A.12.3.1)","27002_2013 (12.3.1)","27002_2022 (8.13)","27017_2015 (12.3.1)","27018_2019 (12.3.1)","27701_2019 (6.9.3.1)"],"Standard12":["(CIP-009-6_Requirement_R1_Part_1.3)"],"Standard11":["800_53 Rev5 (CP-10,CP-9)"],"Standard9":["v1.1 (PR.IP-4)"],"Standard7":["(6s)"],"Standard8":["v3.2.1 (12.10.1)"],"s202":["(A1.2)"]},"ecc-gcp-166":{"Standard1":["v7 (5.1)","v8 (12.6)"],"CJIS":["(5.10.1,5.13.1.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,IA.L1-3.5.2,SC.L1-3.13.1,SC.L2-3.13.15)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.10)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,SC-08,SC-23)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.312.e.1)"],"Standard15":["(01.j,09.m,09.s,09.y,10.d)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.2)","27002_2013 (13.1.1,13.1.2)","27002_2022 (8.20,8.21)","27017_2015 (13.1.1,13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.2)"],"Standard11":["800-171 Rev2 (3.13.1,3.13.15)","800_53 Rev5 (AC-18,SC-23,SC-8)"],"Standard9":["v1.1 (PR.DS-5,PR.PT-4)"],"Standard8":["v3.2.1 (4.1.1)"],"s202":["(CC5.2,CC6.1)"]},"ecc-gcp-449":{"Standard1":["v7 (1.4)","v8 (3.7)"],"BSA":["v3 (DP-1)"],"Standard2":["19 (APO14)"],"Standard4":["v4 (DSP-01,DSP-04)"],"Standard10":["(D1.G.IT.B.2)"],"Standard3":["Standard11_800_53_Rev5 (RA-02)"],"Standard15":["(00.a)"],"Standard14":["27001_2013 (A.18.1.3,A.8.1.1,A.8.2.1,A.8.2.2)","27002_2013 (18.1.3,8.1.1,8.2.1,8.2.2)","27002_2022 (5.12,5.13,5.33,5.9,8.12)","27017_2015 (18.1.3,8.1.1,8.2.1,8.2.2)","27018_2019 (18.1,8)","27701_2019 (6.15.1.3,6.5.1.1,6.5.2.1,6.5.2.2)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.1)"],"Standard11":["800_53 Rev5 (RA-2)"],"Standard9":["v1.1 (ID.AM-5,ID.RA-5)"],"Standard7":["(2._Classify_personal_data_by_type)"],"Standard8":["v3.2.1 (9.6.1)","v4.0 (9.4.2)"],"s202":["(CC3.2)"]},"ecc-gcp-227":{"Standard1":["v7 (14.4)","v8 (3.11)"],"BSA":["v3 (AM-4,DP-4,DP-5,IM-7,PA-7)"],"CJIS":["(5.10.1.2.2,5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.1,MP.L2-3.8.2,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03,IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,IA-05.01,MP-02,SC-28)"],"Standard6":["2016_679 (Article_32.1.a,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.a.2.iv)"],"Standard15":["(01.a,01.c,01.v,06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,5.33,8.24,8.3)","27017_2015 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (10.1.1,18.1.3,8,9.1,9.4.1)","27701_2019 (6.15.1.3,6.5.1.3,6.6.1.1,6.6.4.1,6.7.1.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.13.16,3.8.1,3.8.2)","800_53 Rev5 (AC-3,AC-6,IA-5.1,MP-2,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.AC-4,PR.DS-1)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (3.4,3.4.1,7.1,7.1.1,7.1.2,7.1.3,8.2.1)","v4.0 (3.3.2,3.3.3,3.5.1.2,3.5.1.3,8.3.2)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-gcp-124":{"Standard1":["v7 (5.1)","v8 (16.5)"],"CE":["v2.2 (A6.3)"],"DA":["(Article_8.4.f)"],"Standard3":["Standard11_800_53_Rev5 (SR-11)"],"Standard15":["(10.k)"],"Standard14":["27002_2022 (8.26)"],"Standard12":["(CIP-013-2_Requirement_R2)"],"Standard11":["800_53 Rev5 (SR-11)"],"Standard9":["v1.1 (PR.IP-2)"],"Standard8":["v3.2.1 (6.2)","v4.0 (12.3.4,6.3.3)"]},"ecc-gcp-021":{"Standard1":["v7 (6.2,6.3)","v8 (8.2,8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CIS GCP Foundations Benchmark":["v1.2.0 (2.8)","v1.3.0 (2.8)","v2.0.0 (2.8)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_15.1,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.2,10.2.4,10.2.5,10.3)","v4.0 (10.2.1,10.2.1.2)"],"s202":["(CC7.2)"]},"ecc-gcp-173":{"Standard1":["v7 (12)","v8 (12.2)"],"BSA":["v3 (GS-4,NS-2)"],"CIS GCP Foundations Benchmark":["v1.2.0 (4.9)","v1.3.0 (4.9)","v2.0.0 (4.9)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)","v4.0 (1.2.5,1.2.6,1.3.1,1.3.2)"],"s202":["(CC6.1,CC6.6)"]},"ecc-gcp-061":{"Standard1":["v7 (11)","v8 (12.3)"],"CIS GCP Foundations Benchmark":["v1.0.0 (7.15)"],"CJIS":["(5.10.1)"],"CMMC":["v2.0 (SC.L2-3.13.15)"],"Standard2":["19 (APO01,APO03)"],"DA":["(Article_8.4.b)"],"Standard3":["Standard11_800_53_Rev5 (CM-06,CM-07,SC-23)"],"Standard6":["2016_679 (Article_32)"],"Standard5":["(164.312.e.1)"],"Standard15":["(09.m)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.20,8.21)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard11":["800_53 Rev5 (CM-6,CM-7,SC-23)"],"Standard9":["v1.1 (PR.AC-7)"],"Standard8":["v3.2.1 (8.3)"],"s202":["(CC5.2)"]},"ecc-gcp-228":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.2.6)"],"s202":["(CC8.1)"]},"ecc-gcp-119":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,12.6,3.3)"],"BSA":["v3 (AM-4,GS-4,IM-7,NS-2,PA-7)"],"CJIS":["(5.10.1,5.13.1.1,5.5.2,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.13,AC.L2-3.1.16,AC.L2-3.1.17,AC.L2-3.1.5,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,IA.L1-3.5.2,MP.L2-3.8.2,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.15,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03,DSS05)"],"Standard4":["v4 (DSP-07,IAM-05,IVS-03)"],"CE":["v2.2 (A4.8,A7.4,Firewalls,Secure_configuration,uac)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Im.B.1,D3.PC.Im.B.10,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,AC-18,CM-07,MP-02,PL-08,SA-08,SC-07,SC-08,SC-23)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.e.1)"],"Standard15":["(01.a,01.c,01.i,01.j,01.l,01.m,01.n,01.o,01.v,09.m,09.s,09.y,10.d)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.2,A.13.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (13.1.1,13.1.2,13.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.20,8.21,8.22,8.27,8.3)","27017_2015 (13.1.1,13.1.2,13.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (13.1,8,9.1,9.4.1)","27701_2019 (6.10.1.1,6.10.1.2,6.10.1.3,6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.1.5,3.13.1,3.13.15,3.13.2,3.13.5,3.4.6,3.8.2)","800_53 Rev5 (AC-18,AC-3,AC-6,CM-7,MP-2,PL-8,PM-7,SA-8,SC-23,SC-7,SC-8)"],"Standard9":["v1.1 (PR.AC-4,PR.AC-5,PR.DS-5,PR.IP-1,PR.PT-3,PR.PT-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5,4.1.1,7.1,7.1.1,7.1.2,7.1.3)","v4.0 (1.2.5,1.2.6,1.3.1,1.4.4,7.1)"],"s202":["(CC5.2,CC6.1,CC6.3,CC6.6)"]},"ecc-gcp-218":{"Standard1":["v7 (16)","v8 (6.7)"],"BSA":["v3 (GS-6,IM-1,IM-9)"],"CJIS":["(5.6.2)"],"Standard3":["Standard11_800_53_Rev5 (AC-02.01,AC-03)"],"Standard15":["(01.q)"],"Standard14":["27001_2013 (A.9)","27002_2013 (9)","27002_2022 (5.15)","27017_2015 (9)","27018_2019 (9)","27701_2019 (6.6)"],"Standard12":["(CIP-007-6_Requirement_R5_Part_5.1)"],"Standard11":["800_53 Rev5 (AC-2.1,AC-3)"],"Standard9":["v1.1 (PR.AC-1)"]},"ecc-gcp-093":{"Standard1":["v7 (14.4)","v8 (3.10)"],"BSA":["v3 (DP-3)"],"CJIS":["(5.10.1.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IVS-03)"],"DA":["(Article_8.2,Article_8.3.a)"],"Standard10":["(D3.PC.Am.B.12,D3.PC.Am.B.13)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,SC-08)"],"Standard6":["2016_679 (Article_32.1.a,Article_44)"],"Standard5":["(164.312.e.1)"],"Standard15":["(06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1,A.18.1.3)","27002_2013 (10.1.1,13.2.1,18.1.3)","27002_2022 (5.14,5.33,8.24)","27017_2015 (10.1.1,13.2.1,18.1.3)","27018_2019 (10.1.1,13.2.1,18.1.3)","27701_2019 (6.10.2.1,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.8,3.5.10)","800_53 Rev5 (AC-17.2,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-2)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1.1,4.1,4.1.1,8.2.1)","v4.0 (2.2.7,4.2.2,8.3.2)"],"s202":["(CC6.1,CC6.7)"]},"ecc-gcp-215":{"Standard1":["v7 (16.5,4.4)","v8 (3.10,5.2)"],"BSA":["v3 (DP-3)"],"CJIS":["(5.10.1.2.1,5.13.7.1,5.13.7.2,5.6.2.1.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,IA.L2-3.5.7,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IAM-02,IVS-03)"],"CE":["v2.2 (A4.3,A5.3,A5.5,A7.2,Firewalls,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.a,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12,D3.PC.Am.B.13,D3.PC.Am.B.7)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,IA-05.01,SC-08)"],"Standard6":["2016_679 (Article_32.1.a,Article_44)"],"Standard5":["(164.308.a.5.ii.D,164.312.e.1)"],"Standard15":["(01.f,06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1,A.18.1.3,A.9.4.3)","27002_2013 (10.1.1,13.2.1,18.1.3,9.4.3)","27002_2022 (5.14,5.17,5.33,8.24)","27017_2015 (10.1.1,13.2.1,18.1.3,9.4.3)","27018_2019 (10.1.1,13.2.1,18.1.3,9.4.3)","27701_2019 (6.10.2.1,6.15.1.3,6.6.4.3,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-007-6_Requirement_R5_Part_5.5,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.8,3.5.10,3.5.7)","800_53 Rev5 (AC-17.2,IA-5.1,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-2)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1,2.1.1,4.1,4.1.1,8.2.1,8.2.3,8.2.6)","v4.0 (2.2.7,4.2.2,8.3.2)"],"s202":["(CC6.1,CC6.7)"]},"ecc-gcp-446":{"Standard1":["v7 (1.4)","v8 (3.7)"],"BSA":["v3 (DP-1)"],"Standard2":["19 (APO14)"],"Standard4":["v4 (DSP-01,DSP-04)"],"Standard10":["(D1.G.IT.B.2)"],"Standard3":["Standard11_800_53_Rev5 (RA-02)"],"Standard15":["(00.a)"],"Standard14":["27001_2013 (A.18.1.3,A.8.1.1,A.8.2.1,A.8.2.2)","27002_2013 (18.1.3,8.1.1,8.2.1,8.2.2)","27002_2022 (5.12,5.13,5.33,5.9,8.12)","27017_2015 (18.1.3,8.1.1,8.2.1,8.2.2)","27018_2019 (18.1,8)","27701_2019 (6.15.1.3,6.5.1.1,6.5.2.1,6.5.2.2)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.1)"],"Standard11":["800_53 Rev5 (RA-2)"],"Standard9":["v1.1 (ID.AM-5,ID.RA-5)"],"Standard7":["(2._Classify_personal_data_by_type)"],"Standard8":["v3.2.1 (9.6.1)","v4.0 (9.4.2)"],"s202":["(CC3.2)"]},"ecc-gcp-025":{"Standard1":["v7 (11.1)","v8 (4.2)"],"BSA":["v3 (GS-10,GS-5,PV-1)"],"CIS GCP Foundations Benchmark":["v1.2.0 (3.1)","v1.3.0 (3.1)","v2.0.0 (3.1)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-a18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.2.6,1.4.2,1.5.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-gcp-001":{"Standard1":["v7 (16.2)","v8 (5.6)"],"BSA":["v3 (GS-6)"],"CIS GCP Foundations Benchmark":["v1.2.0 (1.1)"],"Standard10":["(D3.PC.Am.B.6)"],"Standard3":["Standard11_800_53_Rev5 (AC-02.01)"],"Standard15":["(01.q)"],"Standard14":["27001_2013 (A.9)","27002_2013 (9)","27002_2022 (5.15)","27017_2015 (9)","27018_2019 (9)","27701_2019 (6.6)"],"Standard11":["800_53 Rev5 (AC-2.1)"],"Standard9":["v1.1 (PR.AC-7)"],"s202":["(CC6.1)"]},"ecc-gcp-023":{"Standard1":["v7 (6.2,6.3)","v8 (8.2,8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CIS GCP Foundations Benchmark":["v1.2.0 (2.10)","v1.3.0 (2.10)","v2.0.0 (2.10)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_15.1,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.2,10.2.4,10.2.5,10.3)","v4.0 (10.2.1,10.2.1.2)"],"s202":["(CC7.2)"]},"ecc-gcp-253":{"Standard1":["v7 (4.3)","v8 (5.5)"],"BSA":["v3 (PA-4,PV-5)"],"CIS GKE Benchmark":["v1.2.0 (5.4.2)","v1.3.0 (5.4.2)","v1.4.0 (5.4.2)"],"CJIS":["(5.5.1)"],"Standard4":["v4 (IAM-03)"],"CE":["v2.2 (A7.8,A7.9,uac)"],"Standard10":["(D3.DC.An.B.4)"],"Standard3":["Standard11_800_53_Rev5 (AC-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.15)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard12":["(CIP-007-6_Requirement_R5_Part_5.3)"],"Standard11":["800_53 Rev5 (AC-2)"],"Standard9":["v1.1 (PR.AC-1)"],"Standard8":["v4.0 (7.2.4)"],"s202":["(8.1.1)"]},"ecc-gcp-288":{"Standard1":["v7 (12.4,9.2)","v8 (12.2)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.3.b,Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)","v4.0 (1.2.5,1.2.6,1.3.1)"],"s202":["(CC6.1,CC6.6)"]},"ecc-gcp-184":{"Standard1":["v7 (16.2)","v8 (3.3)"],"BSA":["v3 (AM-4,IM-7,PA-7)"],"CIS GCP Foundations Benchmark":["v1.2.0 (6.3.2)","v1.3.0 (6.3.2)","v2.0.0 (6.3.2)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.3.b)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.3)","27017_2015 (8.1.3,9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)","v4.0 (1.3.1,7.1)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-gcp-172":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CIS GCP Foundations Benchmark":["v1.2.0 (4.8)","v1.3.0 (4.8)","v2.0.0 (4.8)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.2.6)"],"s202":["(CC8.1)"]},"ecc-gcp-092":{"Standard1":["v7 (14.4)","v8 (3.10)"],"BSA":["v3 (DP-3)"],"CJIS":["(5.10.1.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IVS-03)"],"DA":["(Article_8.2,Article_8.3.a)"],"Standard10":["(D3.PC.Am.B.12,D3.PC.Am.B.13)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,SC-08)"],"Standard6":["2016_679 (Article_32.1.a,Article_44)"],"Standard5":["(164.312.e.1)"],"Standard15":["(06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1,A.18.1.3)","27002_2013 (10.1.1,13.2.1,18.1.3)","27002_2022 (5.14,5.33,8.24)","27017_2015 (10.1.1,13.2.1,18.1.3)","27018_2019 (10.1.1,13.2.1,18.1.3)","27701_2019 (6.10.2.1,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.8,3.5.10)","800_53 Rev5 (AC-17.2,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-2)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1.1,4.1,4.1.1,8.2.1)","v4.0 (2.2.7,4.1.1,4.2.2,8.3.2)"],"s202":["(CC6.1,CC6.7)"]},"ecc-gcp-067":{"Standard1":["v7 (13)","v8 (3.1)"],"CJIS":["(5.1.1.1)"],"CMMC":["v2.0 (SC.L2-3.13.10)"],"Standard2":["19 (APO14,DSS06)"],"Standard4":["v4 (DCS-01,DSP-01,DSP-06,DSP-17,GRC-03)"],"DA":["(Article_8.2)"],"Standard10":["(D1.G.IT.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-12,SI-12)"],"Standard6":["2016_679 (Article_5)"],"Standard15":["(00.a)"],"Standard14":["27001_2013 (A.18.1.5,A.6.2.2,A.8.1.3)","27002_2013 (18.1.5,6.2.2,8.1.3)","27002_2022 (5.10,5.9,8.1)","27017_2015 (18.1.5,6.2.2,8.1.3)","27018_2019 (18.1,6.2,8)","27701_2019 (6.15.1.5,6.3.2.2,6.5.1.3)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800_53 Rev5 (CM-12,SI-12)"],"Standard9":["v1.1 (PR.IP-6)"],"Standard7":["(2)"],"s202":["(CC6.1,CC6.7)"]},"ecc-gcp-101":{"Standard1":["v7 (6.2,6.3)","v8 (3.14,8.2,8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-04,LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-06.09,AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AC-6.9,AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.1,10.2.2,10.2.4,10.2.5,10.3,11.5)","v4.0 (10.2.1)"],"s202":["(CC6.1,CC7.2)"]},"ecc-gcp-036":{"Standard1":["v7 (16.2)","v8 (5.6,6.7)"],"BSA":["v3 (GS-6,IM-1,IM-9)"],"CIS GCP Foundations Benchmark":["v1.2.0 (4.4)","v1.3.0 (4.4)","v2.0.0 (4.4)"],"CJIS":["(5.6.2)"],"Standard10":["(D3.PC.Am.B.6)"],"Standard3":["Standard11_800_53_Rev5 (AC-02.01,AC-03)"],"Standard15":["(01.q)"],"Standard14":["27001_2013 (A.9)","27002_2013 (9)","27002_2022 (5.15,5.8)","27017_2015 (9)","27018_2019 (9)","27701_2019 (6.6)"],"Standard12":["(CIP-007-6_Requirement_R5_Part_5.1)"],"Standard11":["800_53 Rev5 (AC-2.1,AC-3)"],"Standard9":["v1.1 (PR.AC-1,PR.AC-7)"],"s202":["(CC6.1)"]},"ecc-gcp-153":{"Standard1":["v7 (1.4)","v8 (3.7)"],"BSA":["v3 (DP-1)"],"Standard2":["19 (APO14)"],"Standard4":["v4 (DSP-01,DSP-04)"],"Standard10":["(D1.G.IT.B.2)"],"Standard3":["Standard11_800_53_Rev5 (RA-02)"],"Standard15":["(00.a)"],"Standard14":["27001_2013 (A.18.1.3,A.8.1.1,A.8.2.1,A.8.2.2)","27002_2013 (18.1.3,8.1.1,8.2.1,8.2.2)","27002_2022 (5.12,5.13,5.33,5.9,8.12)","27017_2015 (18.1.3,8.1.1,8.2.1,8.2.2)","27018_2019 (18.1,8)","27701_2019 (6.15.1.3,6.5.1.1,6.5.2.1,6.5.2.2)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.1)"],"Standard11":["800_53 Rev5 (RA-2)"],"Standard9":["v1.1 (ID.AM-5,ID.RA-5)"],"Standard7":["(2._Classify_personal_data_by_type)"],"Standard8":["v3.2.1 (9.6.1)","v4.0 (9.4.2)"],"s202":["(CC3.2)"]},"ecc-gcp-019":{"Standard1":["v7 (6.2,6.3)","v8 (8.2,8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CIS GCP Foundations Benchmark":["v1.2.0 (2.6)","v1.3.0 (2.6)","v2.0.0 (2.6)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_15.1,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.2,10.2.4,10.2.5,10.3)","v4.0 (10.2.1,10.2.1.2)"],"s202":["(CC7.2)"]},"ecc-gcp-212":{"Standard1":["v7 (13)","v8 (4.1,4.8)"],"BSA":["v3 (AM-2,AM-5,GS-10,GS-5,NS-3,NS-8,PV-1,PV-3)"],"CIS GCP Foundations Benchmark":["v1.2.0 (6.3.6)","v1.3.0 (6.3.6)","v2.0.0 (6.3.6)"],"CJIS":["(5.4.3,5.7.1.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,CM.L2-3.4.8,SC.L2-3.13.6)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (A5.1,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,CM-07,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(01.l,08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (12.5.1,14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (12.5.1,14.2.5)","27018_2019 (12.5,14)","27701_2019 (6.11.2.5,6.9.5.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-007-6_Requirement_R5_Part_5.4,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2,3.4.7)","800_53 Rev5 (CM-2,CM-6,CM-7,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (DE.CM-7,PR.IP-1)"],"Standard8":["v3.2.1 (1.1.6,1.2.1,2.2,2.2.2,2.2.5)","v4.0 (2.2.4,2.2.6)"],"s202":["(CC6.3,CC6.6,CC8.1)"]},"ecc-gcp-050":{"Standard1":["v7 (14.6)","v8 (3.3)"],"BSA":["v3 (AM-4,IM-7,PA-7)"],"CIS GKE Benchmark":["v1.2.0 (5.6.3)","v1.3.0 (5.6.3)","v1.4.0 (5.6.3)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_14.d,Article_8.3.b)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.3)","27017_2015 (8.1.3,9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)","v4.0 (1.3.1,7.1)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-gcp-213":{"Standard1":["v7 (13)","v8 (3.11)"],"BSA":["v3 (DP-4,DP-5)"],"CIS GCP Foundations Benchmark":["v1.2.0 (7.2)","v1.3.0 (7.2)","v2.0.0 (7.2)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.3.2,3.3.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-gcp-229":{"Standard1":["v7 (13.1,5.1)","v8 (3.1,4.1)"],"BSA":["v3 (GS-10,GS-3,GS-5,PV-1,PV-3)"],"CJIS":["(5.1.1.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.10)"],"Standard2":["19 (APO13,APO14,BAI06,BAI10,DSS06)"],"Standard4":["v4 (CCC-01,DCS-01,DSP-01,DSP-06,DSP-17,GRC-03,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.2,Article_8.4.b)"],"Standard10":["(D1.G.IT.B.2,D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,CM-09,CM-12,SA-03,SA-08,SA-10,SI-12)"],"Standard6":["2016_679 (Article_32,Article_5)"],"Standard15":["(00.a,08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5,A.18.1.5,A.6.2.2,A.8.1.3)","27002_2013 (14.2.5,18.1.5,6.2.2,8.1.3)","27002_2022 (5.10,5.9,8.1,8.27,8.9)","27017_2015 (14.2.5,18.1.5,6.2.2,8.1.3)","27018_2019 (14,18.1,6.2,8)","27701_2019 (6.11.2.5,6.15.1.5,6.3.2.2,6.5.1.3)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-12,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8,SI-12)"],"Standard9":["v1.1 (PR.IP-1,PR.IP-6)"],"Standard7":["(2)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.2.6)"],"s202":["(CC6.1,CC6.7,CC8.1)"]},"ecc-gcp-130":{"Standard1":["v7 (9.2)","v8 (13.6)"],"BSA":["v3 (LT-4)"],"CJIS":["(5.10.1.1,5.10.1.3)"],"CMMC":["v2.0 (AC.L2-3.1.3,SI.L2-3.14.3,SI.L2-3.14.6)"],"Standard4":["v4 (IVS-03)"],"Standard10":["(D3.DC.Ev.B.1)"],"Standard3":["Standard11_800_53_Rev5 (SI-04,SI-04.04)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa,09.m)"],"Standard14":["27001_2013 (A.12.4.1,A.13.1.1)","27002_2013 (12.4.1,13.1.1)","27002_2022 (8.15,8.16,8.20)","27017_2015 (12.4.1,13.1.1)","27018_2019 (12.4.1,13.1)","27701_2019 (6.10.1.1,6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.13.1,3.14.6)","800_53 Rev5 (SI-4,SI-4.4)"],"Standard9":["v1.1 (DE.CM-1)"],"s202":["(CC7.2)"]},"ecc-gcp-006":{"Standard1":["v7 (16)","v8 (5)"],"BSA":["v3 (GS-6)"],"CIS GCP Foundations Benchmark":["v1.2.0 (1.7)","v1.3.0 (1.7)","v2.0.0 (1.7)"],"Standard3":["Standard11_800_53_Rev5 (AC-02.01)"],"Standard15":["(01.q)"],"Standard14":["27001_2013 (A.9)","27002_2013 (9)","27002_2022 (5.15)","27017_2015 (9)","27018_2019 (9)","27701_2019 (6.6)"],"Standard11":["800_53 Rev5 (AC-2.1)"],"s202":["(CC6.1)"]},"ecc-gcp-012":{"Standard1":["v7 (16.2)","v8 (5.6)"],"BSA":["v3 (GS-6)"],"CIS GCP Foundations Benchmark":["v1.2.0 (1.14)","v1.3.0 (1.14)","v2.0.0 (1.14)"],"Standard10":["(D3.PC.Am.B.6)"],"Standard3":["Standard11_800_53_Rev5 (AC-02.01)"],"Standard15":["(01.q)"],"Standard14":["27001_2013 (A.9)","27002_2013 (9)","27002_2022 (5.15)","27017_2015 (9)","27018_2019 (9)","27701_2019 (6.6)"],"Standard11":["800_53 Rev5 (AC-2.1)"],"Standard9":["v1.1 (PR.AC-7)"],"s202":["(CC6.1)"]},"ecc-gcp-335":{"Standard1":["v7 (14.6)","v8 (3.3)"],"BSA":["v3 (AM-4,PA-7)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L2-3.1.5)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4)"],"DA":["(Article_8.3.b)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c)"],"Standard14":["27001_2013 (A.9.1.1,A.9.4.1)","27002_2013 (9.1.1,9.4.1)","27002_2022 (5.15,8.3)","27017_2015 (9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)","v4.0 (7.1)"],"s202":["(CC5.2,CC6.3)"]},"ecc-gcp-307":{"Standard1":["v7 (12.4)","v8 (3.3)"],"BSA":["v3 (AM-4,PA-7)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.3.b)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.3)","27017_2015 (8.1.3,9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)","v4.0 (1.3.1,7.1)"],"s202":["(CC5.2,CC6.3)"]},"ecc-gcp-030":{"Standard1":["v7 (12.4,9.2)","v8 (4.4,4.5)"],"BSA":["v3 (GS-9,NS-1,NS-2,NS-3,NS-7,NS-8)"],"CIS GCP Foundations Benchmark":["v1.2.0 (3.6)","v1.3.0 (3.6)","v2.0.0 (3.6)"],"CJIS":["(5.10.1,5.10.4.3,5.13.1.4,5.13.3,5.13.4.3,5.7.1.1,5.7.1.2)"],"CMMC":["v2.0 (AC.L1-3.1.20,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (APO13)"],"Standard4":["v4 (UEM-10)"],"CE":["v2.2 (A4.1,A4.11,A4.5,A4.7,Firewalls)"],"DA":["(Article_8.3.b,Article_8.4.c)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.2,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,SC-07,SC-07.05)"],"Standard6":["2016_679 (Article_32,Article_5.1.f)"],"Standard15":["(09.m)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (6.7,8.1,8.20)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R2,CIP-005-7_Requirement_R1_Part_1.3,CIP-007-6_Requirement_R1_Part_1.1,CIP-010-4_Requirement_R4)"],"Standard11":["800-171 Rev2 (3.13.1)","800_53 Rev5 (CA-9,SC-7,SC-7.5)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (1.1.4,1.3.1,1.4)","v4.0 (1.2.5,1.2.6,1.3.1)"],"s202":["(CC6.6)"]},"ecc-gcp-057":{"Standard1":["v7 (9.2,9.4)","v8 (4.2,12.6)"],"BSA":["v3 (GS-10,GS-5,PV-1)"],"CIS GKE Benchmark":["v1.2.0 (5.6.7)","v1.3.0 (5.6.7)","v1.4.0 (5.6.7)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-a18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.2.6)"],"s202":["(CC5.2,CC6.6)"]},"ecc-gcp-450":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,PV-1)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (APO13,BAI06)"],"Standard4":["v4 (IVS-02)"],"DA":["(Article_8.2)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard5":["(164.308.a.1.ii.B,164.308.a.7.i,164.308.a.7.ii.C,164.312.e.1,164.314.b.2.i)"],"Standard15":["(10.k,12.c)"],"Standard14":["27001_2013 (A.14.2.5,A.17.2.1)","27002_2013 (14.2.5,17.2.1)","27002_2022 (8.14,8.27,8.9)","27017_2015 (14.2.5,17.2.1)","27018_2019 (14,17)","27701_2019 (6.11.2.5,6.14.2.1)"],"Standard12":["(CIP-003-8_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.4.1)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-8)"],"Standard9":["v1.1 (PR.IP-1,PR.PT-5)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-gcp-015":{"Standard1":["v7 (6.2,6.4)","v8 (8.2,8.3)"],"BSA":["v3 (DS-7,LT-3,LT-4,LT-6)"],"CIS GCP Foundations Benchmark":["v1.2.0 (2.2)","v1.3.0 (2.2)","v2.0.0 (2.2)"],"CJIS":["(5.4.1,5.4.6)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-04,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa,09.h)"],"Standard14":["27001_2013 (A.12.1.3,A.12.4.1)","27002_2013 (12.1.3,12.4.1)","27002_2022 (8.15,8.6)","27017_2015 (12.1.3,12.4.1)","27018_2019 (12.1.3,12.4.1)","27701_2019 (6.9.1.3,6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2,AU-4)"],"Standard9":["v1.1 (DE.AE-3,PR.DS-4,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.2,10.3,10.7)","v4.0 (10.2.1)"],"s202":["(A1.1,CC7.2)"]},"ecc-gcp-266":{"Standard1":["v7 (11.1)","v8 (12.2)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)","v4.0 (1.2.5,1.2.6,1.3.1)"],"s202":["(CC6.1,CC6.6)"]},"ecc-gcp-178":{"Standard1":["v7 (6.3)","v8 (8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CIS GCP Foundations Benchmark":["v1.2.0 (6.2.3)","v1.3.0 (6.2.2)","v2.0.0 (6.2.2)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.2,10.2.4,10.2.5,10.3)","v4.0 (10.2.1)"],"s202":["(CC7.2)"]},"ecc-gcp-293":{"Standard1":["v7 (10.1,10.2)","v8 (11.1,11.2)"],"BSA":["v3 (BR-1,GS-8)"],"Standard2":["19 (APO14)"],"Standard4":["v4 (BCR-08)"],"CE":["v2.2 (Back_up_your_data)"],"DA":["(Article_11.1)"],"Standard10":["(D1.RM.RMP.B.1,D5.IR.Pl.B.5,D5.IR.Pl.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CP-09,CP-10)"],"Standard6":["2016_679 (Article_32.1.c)"],"Standard5":["(164.308.a.7.ii.A)"],"Standard15":["(09.l)"],"Standard14":["27001_2013 (A.12.3.1)","27002_2013 (12.3.1)","27002_2022 (8.13)","27017_2015 (12.3.1)","27018_2019 (12.3.1)","27701_2019 (6.9.3.1)"],"Standard12":["(CIP-009-6_Requirement_R1_Part_1.3)"],"Standard11":["800_53 Rev5 (CP-10,CP-9)"],"Standard9":["v1.1 (ID.SC-5,PR.IP-4,PR.IP-9)"],"Standard7":["(6s)"],"Standard8":["v3.2.1 (12.10.1)"],"s202":["(A1.2)"]},"ecc-gcp-291":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (AM-4,DP-4,DP-5,IM-7,PA-7)"],"CJIS":["(5.10.1.2.2,5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.1,MP.L2-3.8.2,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03,IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,IA-05.01,MP-02,SC-28)"],"Standard6":["2016_679 (Article_32.1.a,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.a.2.iv)"],"Standard15":["(01.a,01.c,01.v,06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.10,5.15,5.33,8.24,8.3)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,8,9.1)","27701_2019 (6.15.1.3,6.5.1.3,6.6.1.1,6.6.4.1,6.7.1.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.13.16,3.8.1,3.8.2)","800_53 Rev5 (AC-3,AC-6,IA-5.1,MP-2,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.AC-4,PR.DS-1)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (3.4,3.4.1,7.1,7.1.1,7.1.2,7.1.3,8.2.1)","v4.0 (3.3.2,3.3.3,8.3.2)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-gcp-043":{"Standard1":["v7 (13,14.4,16.5)","v8 (3.10)"],"BSA":["v3 (DP-3)"],"CIS GCP Foundations Benchmark":["v1.2.0 (6.4)","v1.3.0 (6.4)","v2.0.0 (6.4)"],"CJIS":["(5.10.1.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IVS-03)"],"DA":["(Article_8.2,Article_8.3.a)"],"Standard10":["(D3.PC.Am.B.12,D3.PC.Am.B.13)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,SC-08)"],"Standard6":["2016_679 (Article_32.1.a,Article_44)"],"Standard5":["(164.312.e.1)"],"Standard15":["(06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1,A.18.1.3)","27002_2013 (10.1.1,13.2.1,18.1.3)","27002_2022 (5.14,5.33,8.24)","27017_2015 (10.1.1,13.2.1,18.1.3)","27018_2019 (10.1.1,13.2.1,18.1.3)","27701_2019 (6.10.2.1,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.8,3.5.10)","800_53 Rev5 (AC-17.2,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-2)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1.1,4.1,4.1.1,8.2.1)","v4.0 (2.2.7,4.2.2,8.3.2)"],"s202":["(CC6.1,CC6.7)"]},"ecc-gcp-438":{"Standard1":["v7 (5.3)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CIS GKE Benchmark":["v1.2.0 (5.5.5)","v1.3.0 (5.5.5)","v1.4.0 (5.5.5)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-2,CM-6,SA-10,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.2.6)"],"s202":["(CC8.1)"]},"ecc-gcp-039":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (DP-4,DP-5)"],"CIS GCP Foundations Benchmark":["v1.2.0 (4.7)","v1.3.0 (4.7)","v2.0.0 (4.7)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.3.2,3.3.3,3.5.1.2,3.5.1.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-gcp-187":{"Standard1":["v7 (10.1)","v8 (11.2)"],"BSA":["v3 (BR-1)"],"CIS GCP Foundations Benchmark":["v1.2.0 (6.7)","v1.3.0 (6.7)","v2.0.0 (6.7)"],"Standard4":["v4 (BCR-08)"],"CE":["v2.2 (Back_up_your_data)"],"DA":["(Article_11.2,Article_11.4)"],"Standard10":["(D5.IR.Pl.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CP-09,CP-10)"],"Standard6":["2016_679 (Article_32.1.c)"],"Standard5":["(164.308.a.7.ii.A)"],"Standard15":["(09.l)"],"Standard14":["27001_2013 (A.12.3.1)","27002_2013 (12.3.1)","27002_2022 (8.13)","27017_2015 (12.3.1)","27018_2019 (12.3.1)","27701_2019 (6.9.3.1)"],"Standard12":["(CIP-009-6_Requirement_R1_Part_1.3)"],"Standard11":["800_53 Rev5 (CP-10,CP-9)"],"Standard9":["v1.1 (PR.IP-4)"],"Standard7":["(6s)"],"Standard8":["v3.2.1 (12.10.1)"],"s202":["(A1.2)"]},"ecc-gcp-340":{"Standard1":["v7 (18.10)","v8 (4.4)"],"CJIS":["(5.10.1)"],"CMMC":["v2.0 (SC.L1-3.13.1)"],"Standard4":["v4 (UEM-10)"],"CE":["v2.2 (A4.11,Firewalls)"],"DA":["(Article_8.3.b,Article_8.4.c)"],"Standard10":["(D3.PC.Im.B.2)"],"Standard3":["Standard11_800_53_Rev5 (SC-07,SC-07.05)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard15":["(09.m)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.20)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard11":["800-171 Rev2 (3.13.1)","800_53 Rev5 (SC-7,SC-7.5)"],"Standard7":["(6)"],"Standard8":["v4.0 (1.2.6)"],"s202":["(CC6.6)"]},"ecc-gcp-177":{"Standard1":["v7 (6.3)","v8 (8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CIS GCP Foundations Benchmark":["v1.2.0 (6.2.1)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.2,10.2.4,10.2.5,10.3)","v4.0 (10.2.1)"],"s202":["(CC7.2)"]},"ecc-gcp-058":{"Standard1":["v7 (16.5,4.4)","v8 (5.2)"],"CIS GKE Benchmark":["v1.2.0 (5.8.2)","v1.3.0 (5.8.2)","v1.4.0 (5.8.2)"],"CJIS":["(5.13.7.1,5.13.7.2,5.6.2.1.1)"],"CMMC":["v2.0 (IA.L2-3.5.7)"],"Standard4":["v4 (IAM-02)"],"CE":["v2.2 (A4.3,A5.3,A5.5,A7.2,Firewalls,Secure_configuration,uac)"],"DA":["(Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.7)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01)"],"Standard5":["(164.308.a.5.ii.D)"],"Standard15":["(01.f)"],"Standard14":["27001_2013 (A.9.4.3)","27002_2013 (9.4.3)","27002_2022 (5.17)","27017_2015 (9.4.3)","27018_2019 (9.4.3)","27701_2019 (6.6.4.3)"],"Standard12":["(CIP-007-6_Requirement_R5_Part_5.5)"],"Standard11":["800-171 Rev2 (3.5.7)","800_53 Rev5 (IA-5.1)"],"Standard8":["v3.2.1 (2.1,8.2.3,8.2.6)","v4.0 (8.3.5,8.3.6,8.6.3)"],"s202":["(CC6.1)"]},"ecc-gcp-302":{"Standard1":["v7 (18.10)","v8 (4.4)"],"CJIS":["(5.10.1)"],"CMMC":["v2.0 (SC.L1-3.13.1)"],"Standard2":["19 (APO13)"],"Standard4":["v4 (UEM-10)"],"CE":["v2.2 (A4.11,Firewalls)"],"DA":["(Article_8.3.b,Article_8.4.c)"],"Standard10":["(D3.PC.Im.B.2)"],"Standard3":["Standard11_800_53_Rev5 (SC-07,SC-07.05)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard15":["(09.m)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.20)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard11":["800-171 Rev2 (3.13.1)","800_53 Rev5 (SC-7,SC-7.5)"],"Standard7":["(6)"],"Standard8":["v4.0 (1.2.6)"],"s202":["(CC6.6)"]},"ecc-gcp-181":{"Standard1":["v7 (6.3)","v8 (8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CIS GCP Foundations Benchmark":["v1.2.0 (6.2.13)","v1.3.0 (6.2.6)","v2.0.0 (6.2.5)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.2,10.2.4,10.2.5,10.3)","v4.0 (10.2.1)"],"s202":["(CC7.2)"]},"ecc-gcp-264":{"Standard1":["v7 (5.1)","v8 (12.2)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (4.1.1)","v4.0 (1.2.5,1.2.6,1.3.1,1.3.2)"],"s202":["(CC6.1,CC6.6)"]},"ecc-gcp-082":{"Standard1":["v7 (10.1)","v8 (11.2)"],"BSA":["v3 (BR-1)"],"Standard4":["v4 (BCR-08)"],"CE":["v2.2 (Back_up_your_data)"],"DA":["(Article_11.2,Article_11.4)"],"Standard10":["(D5.IR.Pl.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CP-09,CP-10)"],"Standard6":["2016_679 (Article_32.1.c)"],"Standard5":["(164.308.a.7.ii.A)"],"Standard15":["(09.l)"],"Standard14":["27001_2013 (A.12.3.1)","27002_2013 (12.3.1)","27002_2022 (8.13)","27017_2015 (12.3.1)","27018_2019 (12.3.1)","27701_2019 (6.9.3.1)"],"Standard12":["(CIP-009-6_Requirement_R1_Part_1.3)"],"Standard11":["800_53 Rev5 (CP-10,CP-9)"],"Standard9":["v1.1 (PR.IP-4)"],"Standard7":["(6s)"],"Standard8":["v3.2.1 (12.10.1)"],"s202":["(A1.2)"]},"ecc-gcp-209":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CIS GCP Foundations Benchmark":["v1.3.0 (6.3.3)","v2.0.0 (6.3.3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.2.6)"],"s202":["(CC8.1)"]},"ecc-gcp-162":{"Standard1":["v7 (10)","v8 (3.4)"],"CJIS":["(5.3.4,5.4.6)"],"Standard2":["19 (DSS06)"],"Standard4":["v4 (DSP-16)"],"DA":["(Article_8.3.d)"],"Standard3":["Standard11_800_53_Rev5 (SI-12)"],"Standard6":["2016_679 (Article_5.1.e)"],"Standard15":["(13.l)"],"Standard14":["27001_2013 (A.18.1.3)","27002_2013 (18.1.3)","27002_2022 (5.33)","27017_2015 (18.1.3)","27018_2019 (18.1)","27701_2019 (6.15.1.3)"],"Standard11":["800_53 Rev5 (SI-12)"],"Standard7":["(4._Integrate_data_privacy_into_records_retention_practices)"],"Standard8":["v3.2.1 (3.1)","v4.0 (3.2.1)"],"s202":["(C1.1)"]},"ecc-gcp-323":{"Standard1":["v7 (14.6)","v8 (3.3)"],"BSA":["v3 (AM-4,PA-7)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L2-3.1.5)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4)"],"DA":["(Article_8.3.b)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c)"],"Standard14":["27001_2013 (A.9.1.1,A.9.4.1)","27002_2013 (9.1.1,9.4.1)","27002_2022 (5.15,8.3)","27017_2015 (9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)","v4.0 (7.1)"],"s202":["(CC5.2,CC6.3)"]},"ecc-gcp-013":{"Standard1":["v7 (16.2)","v8 (5.6)"],"BSA":["v3 (GS-6)"],"CIS GCP Foundations Benchmark":["v1.2.0 (1.15)","v1.3.0 (1.15)","v2.0.0 (1.15)"],"Standard10":["(D3.PC.Am.B.6)"],"Standard3":["Standard11_800_53_Rev5 (AC-02.01)"],"Standard15":["(01.q)"],"Standard14":["27001_2013 (A.9)","27002_2013 (9)","27002_2022 (5.15)","27017_2015 (9)","27018_2019 (9)","27701_2019 (6.6)"],"Standard11":["800_53 Rev5 (AC-2.1)"],"Standard9":["v1.1 (PR.AC-7)"],"s202":["(CC6.1)"]},"ecc-gcp-386":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (AM-4,DP-4,DP-5,IM-7,PA-7)"],"CJIS":["(5.10.1.2.2,5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.1,MP.L2-3.8.2,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03,IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,IA-05.01,MP-02,SC-28)"],"Standard6":["2016_679 (Article_32.1.a,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.a.2.iv)"],"Standard15":["(01.a,01.c,01.v,06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,5.33,8.24,8.3)","27017_2015 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (10.1.1,18.1.3,8,9.1,9.4.1)","27701_2019 (6.15.1.3,6.5.1.3,6.6.1.1,6.6.4.1,6.7.1.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.13.16,3.8.1,3.8.2)","800_53 Rev5 (AC-3,AC-6,IA-5.1,MP-2,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.AC-4,PR.DS-1)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (3.4,3.4.1,7.1,7.1.1,7.1.2,7.1.3,8.2.1)","v4.0 (8.3.2)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-gcp-077":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.2.6)"],"s202":["(CC8.1)"]},"ecc-gcp-194":{"Standard1":["v7 (16.2)","v8 (5.6,6.7)"],"BSA":["v3 (GS-6,IM-1,IM-9)"],"CIS GCP Foundations Benchmark":["v1.2.0 (4.4)","v1.3.0 (4.4)","v2.0.0 (4.4)"],"CJIS":["(5.6.2)"],"Standard10":["(D3.PC.Am.B.6)"],"Standard3":["Standard11_800_53_Rev5 (AC-02.01,AC-03)"],"Standard15":["(01.q)"],"Standard14":["27001_2013 (A.9)","27002_2013 (9)","27002_2022 (5.15)","27017_2015 (9)","27018_2019 (9)","27701_2019 (6.6)"],"Standard12":["(CIP-007-6_Requirement_R5_Part_5.1)"],"Standard11":["800_53 Rev5 (AC-2.1,AC-3)"],"Standard9":["v1.1 (PR.AC-1,PR.AC-7)"],"s202":["(CC6.1)"]},"ecc-gcp-066":{"Standard1":["v7 (13)","v8 (3.11)"],"BSA":["v3 (DP-4,DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.3.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-gcp-007":{"Standard1":["v7 (14.6)","v8 (3.3)"],"BSA":["v3 (AM-4,IM-7,PA-7)"],"CIS GCP Foundations Benchmark":["v1.2.0 (1.8)","v1.3.0 (1.8)","v2.0.0 (1.8)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_14.d,Article_8.2,Article_8.3.b)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.3)","27017_2015 (8.1.3,9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)","v4.0 (7.1)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-gcp-143":{"Standard1":["v7 (4)","v8 (6.8)"],"BSA":["v3 (PA-1,PA-7)"],"CJIS":["(5.5.1,5.5.2,5.5.2.1,5.5.2.2,5.5.2.3,5.5.2.4)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L2-3.1.4,AC.L2-3.1.5,SC.L2-3.13.3)"],"Standard4":["v4 (IAM-04,IAM-05,IAM-16)"],"CE":["v2.2 (A7.4,A7.8,A7.9)"],"Standard10":["(D1.R.St.B.1,D3.PC.Am.B.1,D3.PC.Am.B.2)"],"Standard3":["Standard11_800_53_Rev5 (AC-02,AC-02.07,AC-03,AC-05,AC-06,AC-06.01,AC-06.07,AU-9.4)"],"Standard6":["2016_679 (Article_25)"],"Standard5":["(164.308.a.3.i,164.312.a.1)"],"Standard15":["(01.c)"],"Standard14":["27001_2013 (A.6.1.2,A.9,A.9.4.1)","27002_2013 (6.1.2,9,9.4.1)","27002_2022 (5.15,5.3,8.2,8.3)","27017_2015 (6.1.2,9,9.4.1)","27018_2019 (6.1.2,9,9.4.1)","27701_2019 (6.10.1.3,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.4,3.1.5)","800_53 Rev5 (AC-2,AC-2.7,AC-3.7,AC-5,AC-6,AC-6.1,AC-6.7,AU-9.4)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v4.0 (7.2.1,7.2.2,7.2.4,7.3.1,7.3.2)"],"s202":["(CC5.2,CC6.3)"]},"ecc-gcp-129":{"Standard1":["v7 (11.1)","v8 (12.2,12.6,3.3)"],"BSA":["v3 (AM-4,GS-4,IM-7,NS-2,PA-7)"],"CJIS":["(5.10.1,5.13.1.1,5.5.2,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.13,AC.L2-3.1.16,AC.L2-3.1.17,AC.L2-3.1.5,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,IA.L1-3.5.2,MP.L2-3.8.2,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.15,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03,DSS05)"],"Standard4":["v4 (DSP-07,IAM-05,IVS-03)"],"CE":["v2.2 (A4.8,A7.4,Firewalls,Secure_configuration,uac)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Im.B.1,D3.PC.Im.B.10,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,AC-18,CM-07,MP-02,PL-08,SA-08,SC-07,SC-08,SC-23)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.e.1)"],"Standard15":["(01.a,01.c,01.i,01.j,01.l,01.m,01.n,01.o,01.v,09.m,09.s,09.y,10.d)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.2,A.13.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (13.1.1,13.1.2,13.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.20,8.21,8.22,8.27,8.3)","27017_2015 (13.1.1,13.1.2,13.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (13.1,8,9.1,9.4.1)","27701_2019 (6.10.1.1,6.10.1.2,6.10.1.3,6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.1.5,3.13.1,3.13.15,3.13.2,3.13.5,3.4.6,3.8.2)","800_53 Rev5 (AC-18,AC-3,AC-6,CM-7,MP-2,PL-8,PM-7,SA-8,SC-23,SC-7,SC-8)"],"Standard9":["v1.1 (PR.AC-4,PR.AC-5,PR.DS-5,PR.IP-1,PR.PT-3,PR.PT-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5,4.1.1,7.1,7.1.1,7.1.2,7.1.3)","v4.0 (1.2.5,1.2.6,1.3.1,1.4.4,7.1)"],"s202":["(CC5.2,CC6.1,CC6.3,CC6.6)"]},"ecc-gcp-248":{"Standard1":["v7 (5.1)","v8 (17.4)"],"BSA":["v3 (GS-7)"],"CJIS":["(5.13.5,5.3.2,5.3.2.1)"],"CMMC":["v2.0 (IR.L2-3.6.1)"],"Standard2":["19 (DSS04)"],"Standard4":["v4 (SEF-01,SEF-02,SEF-03)"],"DA":["(Article_15.3.e,Article_9.2)"],"Standard10":["(D1.G.SP.B.6,D5.DR.Re.B.1,D5.IR.Pl.B.1)"],"Standard3":["Standard11_800_53_Rev5 (IR-01,IR-08,SA-15)"],"Standard5":["(164.308.a.6.i,164.308.a.6.ii,164.308.a.7.i)"],"Standard15":["(00.a,09.e)"],"Standard14":["27001_2013 (A.16.1.5)","27002_2013 (16.1.5)","27002_2022 (5.24,5.26)","27017_2015 (16.1.5)","27018_2019 (16.1.5)","27701_2019 (6.13.1.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-003-8_Requirement_R2,CIP-008-6_Requirement_R1_Part_1.1,CIP-008-6_Requirement_R1_Part_1.4,CIP-008-6_Requirement_R2_Part_2.2)"],"Standard11":["800-171 Rev2 (3.6.1)","800_53 Rev5 (IR-1,IR-8,SA-15.10)"],"Standard9":["v1.1 (PR.IP-9)"],"Standard8":["v3.2.1 (12.10.1)","v4.0 (12.10.1)"],"s202":["(CC7.2,CC7.4)"]},"ecc-gcp-046":{"Standard1":["v7 (14.6,4)","v8 (3.3)"],"BSA":["v3 (AM-4,IM-7,PA-7)"],"CIS GCP Foundations Benchmark":["v1.0.0 (6.4)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_14.d,Article_8.2,Article_8.3.b)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.3)","27017_2015 (8.1.3,9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)","v4.0 (1.3.1,7.1)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-gcp-091":{"Standard1":["v7 (14.4)","v8 (3.10)"],"BSA":["v3 (DP-3)"],"CJIS":["(5.10.1.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IVS-03)"],"DA":["(Article_8.2,Article_8.3.a)"],"Standard10":["(D3.PC.Am.B.12,D3.PC.Am.B.13)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,SC-08)"],"Standard6":["2016_679 (Article_32.1.a,Article_44)"],"Standard5":["(164.312.e.1)"],"Standard15":["(06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1,A.18.1.3)","27002_2013 (10.1.1,13.2.1,18.1.3)","27002_2022 (5.14,5.33,8.24)","27017_2015 (10.1.1,13.2.1,18.1.3)","27018_2019 (10.1.1,13.2.1,18.1.3)","27701_2019 (6.10.2.1,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.8,3.5.10)","800_53 Rev5 (AC-17.2,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-2)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1.1,4.1,4.1.1,8.2.1)","v4.0 (2.2.7,4.2.2,8.3.2)"],"s202":["(CC6.1,CC6.7)"]},"ecc-gcp-088":{"Standard1":["v7 (14.4)","v8 (3.10)"],"BSA":["v3 (DP-3)"],"CJIS":["(5.10.1.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IVS-03)"],"DA":["(Article_8.2,Article_8.3.a)"],"Standard10":["(D3.PC.Am.B.12,D3.PC.Am.B.13)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,SC-08)"],"Standard6":["2016_679 (Article_32.1.a,Article_44)"],"Standard5":["(164.312.e.1)"],"Standard15":["(06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1,A.18.1.3)","27002_2013 (10.1.1,13.2.1,18.1.3)","27002_2022 (5.14,5.33,8.24)","27017_2015 (10.1.1,13.2.1,18.1.3)","27018_2019 (10.1.1,13.2.1,18.1.3)","27701_2019 (6.10.2.1,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.8,3.5.10)","800_53 Rev5 (AC-17.2,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-2)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1.1,4.1,4.1.1,8.2.1)","v4.0 (2.2.7,4.1.1,4.2.2,8.3.2)"],"s202":["(CC6.1,CC6.7)"]},"ecc-gcp-205":{"Standard1":["v7 (6.3)","v8 (8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CIS GCP Foundations Benchmark":["v1.2.0 (6.2.11)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.2,10.2.4,10.2.5,10.3)","v4.0 (10.2.1)"],"s202":["(CC7.2)"]},"ecc-gcp-027":{"Standard1":["v7 (11.1)","v8 (4.2)"],"BSA":["v3 (GS-10,GS-5,PV-1)"],"CIS GCP Foundations Benchmark":["v1.2.0 (3.3)","v1.3.0 (3.3)","v2.0.0 (3.3)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-a18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.2.6,1.4.2,1.5.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-gcp-038":{"Standard1":["v7 (11.1,11.2)","v8 (4.4,4.5)"],"BSA":["v3 (GS-9,NS-1,NS-2,NS-3,NS-7,NS-8)"],"CIS GCP Foundations Benchmark":["v1.2.0 (4.6)","v1.3.0 (4.6)","v2.0.0 (4.6)"],"CJIS":["(5.10.1,5.10.4.3,5.13.1.4,5.13.3,5.13.4.3,5.7.1.1,5.7.1.2)"],"CMMC":["v2.0 (AC.L1-3.1.20,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (APO13)"],"Standard4":["v4 (UEM-10)"],"CE":["v2.2 (A4.1,A4.11,A4.5,A4.7,Firewalls)"],"DA":["(Article_8.3.b,Article_8.4.c)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.2,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,SC-07,SC-07.05)"],"Standard6":["2016_679 (Article_32,Article_5.1.f)"],"Standard15":["(09.m)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (6.7,8.1,8.20)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R2,CIP-005-7_Requirement_R1_Part_1.3,CIP-007-6_Requirement_R1_Part_1.1,CIP-010-4_Requirement_R4)"],"Standard11":["800-171 Rev2 (3.13.1)","800_53 Rev5 (CA-9,SC-7,SC-7.5)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (1.1.4,1.3.1,1.4)","v4.0 (1.2.6)"],"s202":["(CC6.6)"]},"ecc-gcp-138":{"Standard1":["v7 (13)","v8 (12.6)"],"CJIS":["(5.10.1,5.13.1.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,IA.L1-3.5.2,SC.L1-3.13.1,SC.L2-3.13.15)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.10)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,SC-08,SC-23)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.312.e.1)"],"Standard15":["(01.j,09.m,09.s,09.y,10.d)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.2)","27002_2013 (13.1.1,13.1.2)","27002_2022 (8.20,8.21)","27017_2015 (13.1.1,13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.2)"],"Standard11":["800-171 Rev2 (3.1.17,3.13.1,3.13.15)","800_53 Rev5 (AC-18,SC-23,SC-8)"],"Standard9":["v1.1 (PR.DS-2,PR.DS-5,PR.PT-4)"],"Standard8":["v3.2.1 (4.1.1)"],"s202":["(CC5.2,CC6.1)"]},"ecc-gcp-292":{"Standard1":["v7 (4)","v8 (5.4)"],"BSA":["v3 (IM-2,PA-1)"],"CJIS":["(5.5.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.5,AC.L2-3.1.6,AC.L2-3.1.7,SC.L2-3.13.3)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-09)"],"CE":["v2.2 (A7.5,A7.6,A7.7,uac)"],"DA":["(Article_8.4.c)"],"Standard10":["(D3.PC.Am.B.3)"],"Standard3":["Standard11_800_53_Rev5 (AC-02.07,AC-06.02,AC-06.05)"],"Standard6":["2016_679 (Article_25)"],"Standard15":["(01.e)"],"Standard14":["27001_2013 (A.9.2.3)","27002_2013 (9.2.3)","27002_2022 (5.15,8.2)","27017_2015 (9.2.3)","27018_2019 (9.2.3)","27701_2019 (6.6.2.3)"],"Standard11":["800-171 Rev2 (3.1.6,3.1.7)","800_53 Rev5 (AC-2.7,AC-6.2,AC-6.5)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)"],"s202":["(CC6.3)"]},"ecc-gcp-295":{"Standard1":["v7 (4)","v8 (5.4)"],"BSA":["v3 (IM-2,PA-1)"],"CJIS":["(5.5.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.5,AC.L2-3.1.6,AC.L2-3.1.7,SC.L2-3.13.3)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-09)"],"CE":["v2.2 (A7.5,A7.6,A7.7,uac)"],"DA":["(Article_8.4.c)"],"Standard10":["(D3.PC.Am.B.3)"],"Standard3":["Standard11_800_53_Rev5 (AC-02.07,AC-06.02,AC-06.05)"],"Standard6":["2016_679 (Article_25)"],"Standard15":["(01.e)"],"Standard14":["27001_2013 (A.9.2.3)","27002_2013 (9.2.3)","27002_2022 (5.15,8.2)","27017_2015 (9.2.3)","27018_2019 (9.2.3)","27701_2019 (6.6.2.3)"],"Standard11":["800-171 Rev2 (3.1.6,3.1.7)","800_53 Rev5 (AC-2.7,AC-6.2,AC-6.5)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)"],"s202":["(CC6.3)"]},"ecc-gcp-241":{"Standard1":["v7 (4)","v8 (5.4)"],"BSA":["v3 (IM-2,PA-1)"],"CJIS":["(5.5.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.5,AC.L2-3.1.6,AC.L2-3.1.7,SC.L2-3.13.3)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-09)"],"CE":["v2.2 (A7.5,A7.6,A7.7,uac)"],"DA":["(Article_8.4.c)"],"Standard10":["(D3.PC.Am.B.3)"],"Standard3":["Standard11_800_53_Rev5 (AC-02.07,AC-06.02,AC-06.05)"],"Standard6":["2016_679 (Article_25)"],"Standard15":["(01.e)"],"Standard14":["27001_2013 (A.9.2.3)","27002_2013 (9.2.3)","27002_2022 (5.15,8.2)","27017_2015 (9.2.3)","27018_2019 (9.2.3)","27701_2019 (6.6.2.3)"],"Standard11":["800-171 Rev2 (3.1.6,3.1.7)","800_53 Rev5 (AC-2.7,AC-6.2,AC-6.5)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)"],"s202":["(CC6.3)"]},"ecc-gcp-010":{"Standard1":["v7 (16.2)","v8 (5.6)"],"BSA":["v3 (GS-6)"],"CIS GCP Foundations Benchmark":["v1.2.0 (1.12)","v1.3.0 (1.12)","v2.0.0 (1.12)"],"Standard10":["(D3.PC.Am.B.6)"],"Standard3":["Standard11_800_53_Rev5 (AC-02.01)"],"Standard15":["(01.q)"],"Standard14":["27001_2013 (A.9)","27002_2013 (9)","27002_2022 (5.15)","27017_2015 (9)","27018_2019 (9)","27701_2019 (6.6)"],"Standard11":["800_53 Rev5 (AC-2.1)"],"Standard9":["v1.1 (PR.AC-7)"],"s202":["(CC6.1)"]},"ecc-gcp-257":{"Standard1":["v7 (5.1)","v8 (3.3)"],"BSA":["v3 (AM-4,IM-7,PA-7)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.3.b)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.3)","27017_2015 (8.1.3,9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)","v4.0 (7.1)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-gcp-221":{"Standard1":["v7 (14.6)","v8 (3.3)"],"BSA":["v3 (AM-4,IM-7,PA-7)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.3.b)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.3)","27017_2015 (8.1.3,9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)","v4.0 (7.1)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-gcp-005":{"Standard1":["v7 (14.6)","v8 (3.3)"],"BSA":["v3 (AM-4,IM-7,PA-7)"],"CIS GCP Foundations Benchmark":["v1.2.0 (1.6)","v1.3.0 (1.6)","v2.0.0 (1.6)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_14.d,Article_8.2,Article_8.3.b)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.3)","27017_2015 (8.1.3,9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)","v4.0 (7.1)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-gcp-120":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,12.6,3.3)"],"BSA":["v3 (AM-4,GS-4,IM-7,NS-2,PA-7)"],"CJIS":["(5.10.1,5.13.1.1,5.5.2,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.13,AC.L2-3.1.16,AC.L2-3.1.17,AC.L2-3.1.5,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,IA.L1-3.5.2,MP.L2-3.8.2,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.15,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03,DSS05)"],"Standard4":["v4 (DSP-07,IAM-05,IVS-03)"],"CE":["v2.2 (A4.8,A7.4,Firewalls,Secure_configuration,uac)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Im.B.1,D3.PC.Im.B.10,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,AC-18,CM-07,MP-02,PL-08,SA-08,SC-07,SC-08,SC-23)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.e.1)"],"Standard15":["(01.a,01.c,01.i,01.j,01.l,01.m,01.n,01.o,01.v,09.m,09.s,09.y,10.d)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.2,A.13.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (13.1.1,13.1.2,13.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.20,8.21,8.22,8.27,8.3)","27017_2015 (13.1.1,13.1.2,13.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (13.1,8,9.1,9.4.1)","27701_2019 (6.10.1.1,6.10.1.2,6.10.1.3,6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.1.5,3.13.1,3.13.15,3.13.2,3.13.5,3.4.6,3.8.2)","800_53 Rev5 (AC-18,AC-3,AC-6,CM-7,MP-2,PL-8,PM-7,SA-8,SC-23,SC-7,SC-8)"],"Standard9":["v1.1 (PR.AC-4,PR.AC-5,PR.DS-5,PR.IP-1,PR.PT-3,PR.PT-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5,4.1.1,7.1,7.1.1,7.1.2,7.1.3)","v4.0 (1.2.5,1.2.6,1.3.1,1.4.4,7.1)"],"s202":["(CC5.2,CC6.1,CC6.3,CC6.6)"]},"ecc-gcp-300":{"Standard1":["v7 (16.2)","v8 (5.6)"],"BSA":["v3 (GS-6)"],"Standard10":["(D3.PC.Am.B.6)"],"Standard3":["Standard11_800_53_Rev5 (AC-02.01)"],"Standard15":["(01.q)"],"Standard14":["27001_2013 (A.9)","27002_2013 (9)","27002_2022 (5.15)","27017_2015 (9)","27018_2019 (9)","27701_2019 (6.6)"],"Standard11":["800_53 Rev5 (AC-2.1)"],"Standard9":["v1.1 (PR.AC-7)"],"s202":["(CC6.1)"]},"ecc-gcp-283":{"Standard1":["v7 (12.4,9.2)","v8 (12.2)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.3.b,Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)","v4.0 (1.2.5,1.2.6,1.3.1)"],"s202":["(CC6.1,CC6.6)"]},"ecc-gcp-011":{"Standard1":["v7 (16.2)","v8 (5.6)"],"BSA":["v3 (GS-6)"],"CIS GCP Foundations Benchmark":["v1.2.0 (1.13)","v1.3.0 (1.13)","v2.0.0 (1.13)"],"Standard10":["(D3.PC.Am.B.6)"],"Standard3":["Standard11_800_53_Rev5 (AC-02.01)"],"Standard15":["(01.q)"],"Standard14":["27001_2013 (A.9)","27002_2013 (9)","27002_2022 (5.15)","27017_2015 (9)","27018_2019 (9)","27701_2019 (6.6)"],"Standard11":["800_53 Rev5 (AC-2.1)"],"Standard9":["v1.1 (PR.AC-7)"],"s202":["(CC6.1)"]},"ecc-gcp-265":{"Standard1":["v7 (4.3)","v8 (5.4)"],"BSA":["v3 (IM-2,PA-1)"],"CJIS":["(5.5.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.5,AC.L2-3.1.6,AC.L2-3.1.7,SC.L2-3.13.3)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-09)"],"CE":["v2.2 (A7.5,A7.6,A7.7,uac)"],"DA":["(Article_8.4.c)"],"Standard10":["(D3.PC.Am.B.3)"],"Standard3":["Standard11_800_53_Rev5 (AC-02.07,AC-06.02,AC-06.05)"],"Standard6":["2016_679 (Article_25)"],"Standard15":["(01.e)"],"Standard14":["27001_2013 (A.9.2.3)","27002_2013 (9.2.3)","27002_2022 (5.15,8.2)","27017_2015 (9.2.3)","27018_2019 (9.2.3)","27701_2019 (6.6.2.3)"],"Standard11":["800-171 Rev2 (3.1.6,3.1.7)","800_53 Rev5 (AC-2.7,AC-6.2,AC-6.5)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)"],"s202":["(CC6.3)"]},"ecc-gcp-121":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,12.6,3.3)"],"BSA":["v3 (AM-4,GS-4,IM-7,NS-2,PA-7)"],"CJIS":["(5.10.1,5.13.1.1,5.5.2,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.13,AC.L2-3.1.16,AC.L2-3.1.17,AC.L2-3.1.5,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,IA.L1-3.5.2,MP.L2-3.8.2,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.15,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03,DSS05)"],"Standard4":["v4 (DSP-07,IAM-05,IVS-03)"],"CE":["v2.2 (A4.8,A7.4,Firewalls,Secure_configuration,uac)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Im.B.1,D3.PC.Im.B.10,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,AC-18,CM-07,MP-02,PL-08,SA-08,SC-07,SC-08,SC-23)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.e.1)"],"Standard15":["(01.a,01.c,01.i,01.j,01.l,01.m,01.n,01.o,01.v,09.m,09.s,09.y,10.d)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.2,A.13.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (13.1.1,13.1.2,13.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.20,8.21,8.22,8.27,8.3)","27017_2015 (13.1.1,13.1.2,13.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (13.1,8,9.1,9.4.1)","27701_2019 (6.10.1.1,6.10.1.2,6.10.1.3,6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.1.5,3.13.1,3.13.15,3.13.2,3.13.5,3.4.6,3.8.2)","800_53 Rev5 (AC-18,AC-3,AC-6,CM-7,MP-2,PL-8,PM-7,SA-8,SC-23,SC-7,SC-8)"],"Standard9":["v1.1 (PR.AC-4,PR.AC-5,PR.DS-5,PR.IP-1,PR.PT-3,PR.PT-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5,4.1.1,7.1,7.1.1,7.1.2,7.1.3)","v4.0 (1.2.5,1.2.6,1.3.1,1.4.4,7.1)"],"s202":["(CC5.2,CC6.1,CC6.3,CC6.6)"]},"ecc-gcp-245":{"Standard1":["v7 (5)","v8 (3.3,4.7)"],"BSA":["v3 (AM-4,IM-7,PA-7)"],"CJIS":["(5.4.3,5.5.2,5.6.3.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A5.2,A5.3,A7.4,Firewalls,Secure_configuration,uac)"],"DA":["(Article_8.3.b)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Am.B.3,D3.PC.Am.B.8)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,IA-05,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.b,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.2.2,A.9.2.3,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.2.2,9.2.3,9.4.1)","27002_2022 (5.10,5.15,8.2,8.3,8.9)","27017_2015 (8.1.3,9.1.1,9.2.2,9.2.3,9.4.1)","27018_2019 (8,9.1,9.2.2,9.2.3,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.2.2,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-007-6_Requirement_R5_Part_5.2)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,IA-5,IA-5.5,MP-2)"],"Standard9":["v1.1 (PR.AC-1,PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1,2.1.1,7.1,7.1.1,7.1.2,7.1.3)","v4.0 (7.2.1,7.2.2)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-gcp-144":{"Standard1":["v7 (10.1)","v8 (11.2)"],"BSA":["v3 (BR-1)"],"Standard4":["v4 (BCR-08)"],"CE":["v2.2 (Back_up_your_data)"],"DA":["(Article_11.2,Article_11.4)"],"Standard10":["(D5.IR.Pl.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CP-09,CP-10)"],"Standard6":["2016_679 (Article_32.1.c)"],"Standard5":["(164.308.a.7.ii.A)"],"Standard15":["(09.l)"],"Standard14":["27001_2013 (A.12.3.1)","27002_2013 (12.3.1)","27002_2022 (8.13)","27017_2015 (12.3.1)","27018_2019 (12.3.1)","27701_2019 (6.9.3.1)"],"Standard12":["(CIP-009-6_Requirement_R1_Part_1.3)"],"Standard11":["800_53 Rev5 (CP-10,CP-9)"],"Standard9":["v1.1 (PR.IP-4)"],"Standard7":["(6s)"],"Standard8":["v3.2.1 (12.10.1)"],"s202":["(A1.2)"]},"ecc-gcp-009":{"Standard1":["v7 (14.6)","v8 (3.3)"],"BSA":["v3 (AM-4,IM-7,PA-7)"],"CIS GCP Foundations Benchmark":["v1.2.0 (1.11)","v1.3.0 (1.11)","v2.0.0 (1.11)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_14.d,Article_8.2,Article_8.3.b)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.3)","27017_2015 (8.1.3,9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)","v4.0 (7.1)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-gcp-016":{"Standard1":["v7 (6)","v8 (8.3)"],"BSA":["v3 (LT-6)"],"CIS GCP Foundations Benchmark":["v1.0.0 (2.3)"],"CJIS":["(5.4.6)"],"Standard3":["Standard11_800_53_Rev5 (AU-04)"],"Standard15":["(09.h)"],"Standard14":["27001_2013 (A.12.1.3)","27002_2013 (12.1.3)","27002_2022 (8.6)","27017_2015 (12.1.3)","27018_2019 (12.1.3)","27701_2019 (6.9.1.3)"],"Standard11":["800_53 Rev5 (AU-4)"],"Standard9":["v1.1 (PR.DS-4)"],"Standard8":["v3.2.1 (10.7)"],"s202":["(A1.1)"]},"ecc-gcp-059":{"Standard1":["v7 (11,14.1)","v8 (13.4)"],"BSA":["v3 (NS-1)"],"CIS GKE Benchmark":["v1.2.0 (5.6.2)","v1.3.0 (5.6.2)","v1.4.0 (5.6.2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (SC.L2-3.13.6)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CA-09)"],"Standard15":["(01.l,09.m,09.n)"],"Standard14":["27001_2013 (A.13.1.1)","27002_2013 (13.1.1)","27002_2022 (8.16,8.22)","27017_2015 (13.1.1)","27018_2019 (13.1)","27701_2019 (6.10.1.1)"],"Standard12":["(CIP-005-7,Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.13.1)","800_53 Rev5 (CA-9)"],"Standard9":["v1.1 (PR.AC-5)"],"s202":["(CC6.6)"]},"ecc-gcp-337":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,13.4)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,CM.L2-3.4.7,SC.L1-3.13.1,SC.L2-3.13.2,SC.L2-3.13.6)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.3.b,Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.l,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1)","27002_2013 (13.1.1)","27002_2022 (8.20,8.27)","27017_2015 (13.1.1)","27018_2019 (13.1)","27701_2019 (6.10.1.1)"],"Standard12":["(CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.13.1,3.4.6)","800_53 Rev5 (CA-9,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)","v4.0 (1.2.5,1.2.6,1.3.1)"],"s202":["(CC6.1,CC6.6)"]},"ecc-gcp-111":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,12.6,3.3)"],"BSA":["v3 (AM-4,GS-4,IM-7,NS-2,PA-7)"],"CJIS":["(5.10.1,5.13.1.1,5.5.2,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.13,AC.L2-3.1.16,AC.L2-3.1.17,AC.L2-3.1.5,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,IA.L1-3.5.2,MP.L2-3.8.2,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.15,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03,DSS05)"],"Standard4":["v4 (DSP-07,IAM-05,IVS-03)"],"CE":["v2.2 (A4.8,A7.4,Firewalls,Secure_configuration,uac)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Im.B.1,D3.PC.Im.B.10,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,AC-18,CM-07,MP-02,PL-08,SA-08,SC-07,SC-08,SC-23)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.e.1)"],"Standard15":["(01.a,01.c,01.i,01.j,01.l,01.m,01.n,01.o,01.v,09.m,09.s,09.y,10.d)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.2,A.13.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (13.1.1,13.1.2,13.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.20,8.21,8.22,8.27,8.3)","27017_2015 (13.1.1,13.1.2,13.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (13.1,8,9.1,9.4.1)","27701_2019 (6.10.1.1,6.10.1.2,6.10.1.3,6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.1.5,3.13.1,3.13.15,3.13.2,3.13.5,3.4.6,3.8.2)","800_53 Rev5 (AC-18,AC-3,AC-6,CM-7,MP-2,PL-8,PM-7,SA-8,SC-23,SC-7,SC-8)"],"Standard9":["v1.1 (PR.AC-4,PR.AC-5,PR.DS-5,PR.IP-1,PR.PT-3,PR.PT-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5,4.1.1,7.1,7.1.1,7.1.2,7.1.3)","v4.0 (1.2.5,1.2.6,1.3.1,1.4.4,7.1)"],"s202":["(CC5.2,CC6.1,CC6.3,CC6.6)"]},"ecc-gcp-128":{"Standard1":["v7 (18.4,9.2)","v8 (4.8)"],"BSA":["v3 (AM-2,AM-5,NS-3,NS-8)"],"CIS GKE Benchmark":["v1.2.0 (5.4.1)","v1.3.0 (5.4.1)","v1.4.0 (5.4.1)"],"CJIS":["(5.4.3,5.7.1.1)"],"CMMC":["v2.0 (CM.L2-3.4.7,CM.L2-3.4.8,SC.L2-3.13.6)"],"CE":["v2.2 (A5.1,Secure_configuration)"],"Standard10":["(D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CM-06,CM-07)"],"Standard15":["(01.l)"],"Standard14":["27002_2013 (12.5.1)","27002_2022 (8.9)","27017_2015 (12.5.1)","27018_2019 (12.5)","27701_2019 (6.9.5.1)"],"Standard12":["(CIP-007-6_Requirement_R5_Part_5.4)"],"Standard11":["800-171 Rev2 (3.4.7)","800_53 Rev5 (CM-6,CM-7)"],"Standard9":["v1.1 (DE.CM-7)"],"Standard8":["v3.2.1 (1.1.6,1.2.1,2.2.2,2.2.5)","v4.0 (2.2.4)"],"s202":["(CC6.3,CC6.6)"]},"ecc-gcp-263":{"Standard1":["v7 (16)","v8 (4.7)"],"CJIS":["(5.4.3,5.6.3.2)"],"CE":["v2.2 (A5.2,A5.3,Firewalls,Secure_configuration)"],"DA":["(Article_8.3.b)"],"Standard10":["(D3.PC.Am.B.3,D3.PC.Am.B.8)"],"Standard3":["Standard11_800_53_Rev5 (IA-05)"],"Standard15":["(01.b)"],"Standard14":["27001_2013 (A.9.2.2,A.9.2.3)","27002_2013 (9.2.2,9.2.3)","27002_2022 (8.2,8.9)","27017_2015 (9.2.2,9.2.3)","27018_2019 (9.2.2,9.2.3)","27701_2019 (6.6.2.2)"],"Standard12":["(CIP-007-6_Requirement_R5_Part_5.2)"],"Standard11":["800_53 Rev5 (IA-5,IA-5.5)"],"Standard9":["v1.1 (PR.AC-1)"],"Standard8":["v3.2.1 (2.1,2.1.1)","v4.0 (7.2.1,7.2.2)"],"s202":["(CC6.3)"]},"ecc-gcp-031":{"Standard1":["v7 (12.4,9.2)","v8 (4.4,4.5)"],"BSA":["v3 (GS-9,NS-1,NS-2,NS-3,NS-7,NS-8)"],"CIS GCP Foundations Benchmark":["v1.2.0 (3.7)","v1.3.0 (3.7)","v2.0.0 (3.7)"],"CJIS":["(5.10.1,5.10.4.3,5.13.1.4,5.13.3,5.13.4.3,5.7.1.1,5.7.1.2)"],"CMMC":["v2.0 (AC.L1-3.1.20,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (APO13)"],"Standard4":["v4 (UEM-10)"],"CE":["v2.2 (A4.1,A4.11,A4.5,A4.7,Firewalls)"],"DA":["(Article_8.3.b,Article_8.4.c)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.2,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,SC-07,SC-07.05)"],"Standard6":["2016_679 (Article_32,Article_5.1.f)"],"Standard15":["(09.m)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (6.7,8.1,8.20)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R2,CIP-005-7_Requirement_R1_Part_1.3,CIP-007-6_Requirement_R1_Part_1.1,CIP-010-4_Requirement_R4)"],"Standard11":["800-171 Rev2 (3.13.1)","800_53 Rev5 (CA-9,SC-7,SC-7.5)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (1.1.4,1.3.1,1.4)","v4.0 (1.2.5,1.2.6,1.3.1)"],"s202":["(CC6.6)"]},"ecc-gcp-313":{"Standard1":["v7 (6.2,6.3)","v8 (8.2,8.5)"],"BSA":["v3 (LT-3)"],"CIS GCP Foundations Benchmark":["v1.3.0 (2.14)","v2.0.0 (2.14)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1)","v4.0 (10.2.1,5.3.4)"],"s202":["(CC7.2)"]},"ecc-gcp-163":{"Standard1":["v7 (1.4)","v8 (3.7)"],"BSA":["v3 (DP-1)"],"Standard2":["19 (APO14)"],"Standard4":["v4 (DSP-01,DSP-04)"],"Standard10":["(D1.G.IT.B.2)"],"Standard3":["Standard11_800_53_Rev5 (RA-02)"],"Standard15":["(00.a)"],"Standard14":["27001_2013 (A.18.1.3,A.8.1.1,A.8.2.1,A.8.2.2)","27002_2013 (18.1.3,8.1.1,8.2.1,8.2.2)","27002_2022 (5.12,5.13,5.33,5.9,8.12)","27017_2015 (18.1.3,8.1.1,8.2.1,8.2.2)","27018_2019 (18.1,8)","27701_2019 (6.15.1.3,6.5.1.1,6.5.2.1,6.5.2.2)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.1)"],"Standard11":["800_53 Rev5 (RA-2)"],"Standard9":["v1.1 (ID.AM-5,ID.RA-5)"],"Standard7":["(2._Classify_personal_data_by_type)"],"Standard8":["v3.2.1 (9.6.1)","v4.0 (9.4.2)"],"s202":["(CC3.2)"]},"ecc-gcp-442":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"s202":["(CC8.1)"]},"ecc-gcp-334":{"Standard1":["v7 (14.6)","v8 (3.3)"],"BSA":["v3 (AM-4,PA-7)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L2-3.1.5)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4)"],"DA":["(Article_8.3.b)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c)"],"Standard14":["27001_2013 (A.9.1.1,A.9.4.1)","27002_2013 (9.1.1,9.4.1)","27002_2022 (5.15,8.3)","27017_2015 (9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)","v4.0 (7.1)"],"s202":["(CC5.2,CC6.3)"]},"ecc-gcp-060":{"Standard1":["v7 (5.1,5.4)","v8 (4.1,4.2)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CIS GKE Benchmark":["v1.2.0 (5.10.3)","v1.3.0 (5.10.3)","v1.4.0 (5.10.3)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (A4.8,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-a18,CM-02,CM-06,CM-07,CM-07.01,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2,A.14.2.5)","27002_2013 (13.1.2,14.2.5)","27002_2022 (8.21,8.27,8.9)","27017_2015 (13.1.2,14.2.5)","27018_2019 (13.1,14)","27701_2019 (6.10.1.2,6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1,2.2)","v4.0 (1.2.1,1.2.6,1.5.1,2.2.6)"],"s202":["(CC5.2,CC6.6,CC8.1)"]},"ecc-gcp-191":{"Standard1":["v7 (18.2)","v8 (7.5,7.6)"],"BSA":["v3 (DS-6,PV-5)"],"CIS GKE Benchmark":["v1.2.0 (5.5.6)","v1.3.0 (5.5.6)","v1.4.0 (5.5.6)"],"CJIS":["(5.10.1.3)"],"CMMC":["v2.0 (RA.L2-3.11.2,SI.L1-3.14.5)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (TVM-07)"],"DA":["(Article_8.1)"],"Standard10":["(D3.DC.Th.B.1)"],"Standard3":["Standard11_800_53_Rev5 (RA-05)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(10.m)"],"Standard14":["27001_2013 (A.12.6.1)","27002_2013 (12.6.1)","27002_2022 (8.8)","27017_2015 (12.6.1)","27018_2019 (12.6)","27701_2019 (6.9.6.1)"],"Standard11":["800-171 Rev2 (3.11.2)","800_53 Rev5 (RA-5)"],"Standard9":["v1.1 (DE.CM-8)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (11.2)","v4.0 (11.3.1)"],"s202":["(CC6.6,CC7.1)"]},"ecc-gcp-204":{"Standard1":["v7 (6.3)","v8 (8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CIS GCP Foundations Benchmark":["v1.2.0 (6.2.10)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.2,10.2.4,10.2.5,10.3)","v4.0 (10.2.1)"],"s202":["(CC7.2)"]},"ecc-gcp-316":{"Standard1":["v7 (1.4,11.2,16.1)","v8 (1.1,6.6)"],"BSA":["v3 (AM-1,AM-3)"],"CIS GCP Foundations Benchmark":["v1.3.0 (2.13)","v2.0.0 (2.13)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,IAM-01,UEM-04)"],"CE":["v2.2 (A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800_53 Rev5 (CM-8,PM-5)"],"Standard9":["v1.1 (ID.AM-1)"],"Standard8":["v3.2.1 (2.4)","v4.0 (12.5.1)"],"s202":["(CC6.1)"]},"ecc-gcp-198":{"Standard1":["v7 (14.6)","v8 (3.3)"],"BSA":["v3 (AM-4,IM-7,PA-7)"],"CIS GCP Foundations Benchmark":["v1.2.0 (6.1.2)","v1.3.0 (6.1.2)","v2.0.0 (6.1.2)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.3.b)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.3)","27017_2015 (8.1.3,9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)","v4.0 (7.1)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-gcp-029":{"Standard1":["v7 (11.1)","v8 (4.2)"],"BSA":["v3 (GS-10,GS-5,PV-1)"],"CIS GCP Foundations Benchmark":["v1.2.0 (3.5)","v1.3.0 (3.5)","v2.0.0 (3.5)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-a18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.2.6)"],"s202":["(CC5.2,CC6.6)"]},"ecc-gcp-028":{"Standard1":["v7 (11.1)","v8 (4.2)"],"BSA":["v3 (GS-10,GS-5,PV-1)"],"CIS GCP Foundations Benchmark":["v1.2.0 (3.4)","v1.3.0 (3.4)","v2.0.0 (3.4)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-a18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.2.6)"],"s202":["(CC5.2,CC6.6)"]},"ecc-gcp-047":{"Standard1":["v7 (6.2)","v8 (8.2)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CIS GKE Benchmark":["v1.2.0 (5.7.1)","v1.3.0 (5.7.1)","v1.4.0 (5.7.1)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.2,10.3)","v4.0 (10.2.1)"],"s202":["(CC7.2)"]},"ecc-gcp-276":{"Standard1":["v7 (9.5)","v8 (12.2)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)","v4.0 (1.2.5,1.2.6,1.3.1,1.3.2)"],"s202":["(CC6.1,CC6.6)"]},"ecc-gcp-444":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"s202":["(CC8.1)"]},"ecc-gcp-232":{"Standard1":["v7 (16.1,16.3)","v8 (6.4)"],"BSA":["v3 (IM-6,IM-7)"],"CJIS":["(5.13.7.2,5.5.6,5.6.2.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.12,AC.L2-3.1.15,IA.L2-3.5.3,MA.L2-3.7.5)"],"CE":["v2.2 (A4.10,Firewalls)"],"DA":["(Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.15,D3.PC.Am.B.9)"],"Standard3":["Standard11_800_53_Rev5 (AC-19,IA-02.01,IA-02.02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard15":["(01.q)"],"Standard14":["27001_2013 (A.9.3.1,A.9.4.2)","27002_2013 (9.3.1,9.4.2)","27002_2022 (6.7,8.5)","27017_2015 (9.3.1,9.4.2)","27018_2019 (9.3.1,9.4.2)","27701_2019 (6.6.3.1,6.6.4.2)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.3)"],"Standard11":["800-171 Rev2 (3.1.12,3.1.18,3.5.3)","800_53 Rev5 (AC-19,IA-2.1,IA-2.2)"],"Standard9":["v1.1 (PR.AC-3,PR.AC-7)"],"Standard8":["v3.2.1 (2.3,8.3,8.3.2)","v4.0 (8.4.3)"],"s202":["(CC6.1,CC6.6)"]},"ecc-gcp-318":{"Standard1":["v7 (5.1)","v8 (16.5)"],"CE":["v2.2 (A6.3)"],"DA":["(Article_8.4.f)"],"Standard3":["Standard11_800_53_Rev5 (SI-02)"],"Standard15":["(10.k)"],"Standard14":["27002_2022 (8.26)"],"Standard11":["800_53 Rev5 (SI-2)"],"Standard9":["v1.1 (PR.IP-2)"],"Standard8":["v3.2.1 (6.2)","v4.0 (12.3.4,6.3.3)"]},"ecc-gcp-287":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.2.6)"],"s202":["(CC8.1)"]},"ecc-gcp-195":{"Standard1":["v7 (9.2)","v8 (8.11,8.2,8.6)"],"BSA":["v3 (DS-7,LT-1,LT-2,LT-3,LT-4,LT-5)"],"CIS GCP Foundations Benchmark":["v1.2.0 (2.12)","v1.3.0 (2.12)","v2.0.0 (2.12)"],"CJIS":["(5.4.1,5.4.3)"],"CMMC":["v2.0 (AU.L2-3.3.1,AU.L2-3.3.5)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-05,LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-06,AU-06.01,AU-06.05,AU-07,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.308.a.1.ii.D,164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1,A.16.1.4)","27002_2013 (12.4.1,16.1.4)","27002_2022 (5.25,8.15)","27017_2015 (12.4.1,16.1.4)","27018_2019 (12.4.1,6.1.4)","27701_2019 (6.13.1.4,6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1,CIP-007-6_Requirement_R4_Part_4.4)"],"Standard11":["800-171 Rev2 (3.3.1,3.3.3,3.3.5)","800_53 Rev5 (AU-12,AU-2,AU-6,AU-6.1,AU-6.5,AU-7)"],"Standard9":["v1.1 (DE.AE-2,DE.AE-3,PR.PT-1,RS.AN-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.2,10.3,10.6,10.6.1,10.6.2)","v4.0 (10.4.2,10.4.3,5.3.4,10.2.1)"],"s202":["(CC4.1,CC7.2,CC7.3)"]},"ecc-gcp-183":{"Standard1":["v7 (6.3)","v8 (8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CIS GCP Foundations Benchmark":["v1.2.0 (6.2.16)","v1.3.0 (6.2.8)","v2.0.0 (6.2.7)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.2,10.2.4,10.2.5,10.3)","v4.0 (10.2.1)"],"s202":["(CC7.2)"]},"ecc-gcp-165":{"Standard1":["v7 (11.1)","v8 (12.2)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.7,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard11":["800-171 Rev2 (3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"s202":["(CC6.1)"]},"ecc-gcp-037":{"Standard1":["v7 (9.2)","v8 (4.8)"],"BSA":["v3 (AM-2,AM-5,NS-3,NS-8)"],"CIS GCP Foundations Benchmark":["v1.2.0 (4.5)","v1.3.0 (4.5)","v2.0.0 (4.5)"],"CJIS":["(5.4.3,5.7.1.1)"],"CMMC":["v2.0 (CM.L2-3.4.7,CM.L2-3.4.8,SC.L2-3.13.6)"],"CE":["v2.2 (A5.1,Secure_configuration)"],"Standard10":["(D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CM-06,CM-07)"],"Standard15":["(01.l)"],"Standard14":["27002_2013 (12.5.1)","27002_2022 (8.9)","27017_2015 (12.5.1)","27018_2019 (12.5)","27701_2019 (6.9.5.1)"],"Standard12":["(CIP-007-6_Requirement_R5_Part_5.4)"],"Standard11":["800-171 Rev2 (3.4.7)","800_53 Rev5 (CM-6,CM-7)"],"Standard9":["v1.1 (DE.CM-7)"],"Standard8":["v3.2.1 (1.1.6,1.2.1,2.2.2,2.2.5)","v4.0 (1.2.5,2.2.4)"],"s202":["(CC6.3,CC6.6)"]},"ecc-gcp-299":{"Standard1":["v7 (5.1)","v8 (3.4)"],"CJIS":["(5.3.4,5.4.6)"],"Standard2":["19 (DSS06)"],"Standard4":["v4 (DSP-16)"],"DA":["(Article_8.3.d)"],"Standard3":["Standard11_800_53_Rev5 (SI-12)"],"Standard6":["2016_679 (Article_5.1.e)"],"Standard15":["(13.l)"],"Standard14":["27001_2013 (A.18.1.3)","27002_2013 (18.1.3)","27002_2022 (5.33)","27017_2015 (18.1.3)","27018_2019 (18.1)","27701_2019 (6.15.1.3)"],"Standard11":["800_53 Rev5 (SI-12)"],"Standard7":["(4._Integrate_data_privacy_into_records_retention_practices)"],"Standard8":["v3.2.1 (3.1)","v4.0 (3.2.1)"],"s202":["(C1.1)"]},"ecc-gcp-387":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,PV-1)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (APO13,BAI06)"],"Standard4":["v4 (IVS-02)"],"DA":["(Article_8.2)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard5":["(164.308.a.1.ii.B,164.308.a.7.i,164.308.a.7.ii.C,164.312.e.1,164.314.b.2.i)"],"Standard15":["(10.k,12.c)"],"Standard14":["27001_2013 (A.14.2.5,A.17.2.1)","27002_2013 (14.2.5,17.2.1)","27002_2022 (8.14,8.27,8.9)","27017_2015 (14.2.5,17.2.1)","27018_2019 (14,17)","27701_2019 (6.11.2.5,6.14.2.1)"],"Standard12":["(CIP-003-8_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.4.1)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-8)"],"Standard9":["v1.1 (PR.IP-1,PR.PT-5)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-gcp-151":{"Standard1":["v7 (5)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.2.6)"],"s202":["(CC8.1)"]},"ecc-gcp-451":{"Standard1":["v7 (1.4)","v8 (3.7)"],"BSA":["v3 (DP-1)"],"Standard2":["19 (APO14)"],"Standard4":["v4 (DSP-01,DSP-04)"],"Standard10":["(D1.G.IT.B.2)"],"Standard3":["Standard11_800_53_Rev5 (RA-02)"],"Standard15":["(00.a)"],"Standard14":["27001_2013 (A.18.1.3,A.8.1.1,A.8.2.1,A.8.2.2)","27002_2013 (18.1.3,8.1.1,8.2.1,8.2.2)","27002_2022 (5.12,5.13,5.33,5.9,8.12)","27017_2015 (18.1.3,8.1.1,8.2.1,8.2.2)","27018_2019 (18.1,8)","27701_2019 (6.15.1.3,6.5.1.1,6.5.2.1,6.5.2.2)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.1)"],"Standard11":["800_53 Rev5 (RA-2)"],"Standard9":["v1.1 (ID.AM-5,ID.RA-5)"],"Standard7":["(2._Classify_personal_data_by_type)"],"Standard8":["v3.2.1 (9.6.1)","v4.0 (9.4.2)"],"s202":["(CC3.2)"]},"ecc-gcp-201":{"Standard1":["v7 (6.3)","v8 (8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CIS GCP Foundations Benchmark":["v1.2.0 (6.2.7)","v1.3.0 (6.2.4)","v2.0.0 (6.2.4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.2,10.2.4,10.2.5,10.3)","v4.0 (10.2.1)"],"s202":["(CC7.2)"]},"ecc-gcp-385":{"Standard1":["v7 (11.1)","v8 (12.2)"],"DA":["(Article_8.4.b)"],"Standard3":["Standard11_800_53_Rev4 (SC-7)"],"Standard5":["(164.312.e.1)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.2,A.9.1.2)","27002_2013 (13.1.1,13.1.2,9.1.2)","27002_2022 (8.27)","27017_2015 (13.1.1,13.1.2,9.1.2)","27018_2019 (13.1,9.1)","27701_2019 (6.10.1.1,6.10.1.2,6.6.1.2)"],"Standard11":["800_53 Rev5 (SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.PT-4)"],"Standard8":["v3.2.1 (1.2.1,1.3.2)","v4.0 (1.2.5,1.2.6,1.3.1,1.3.2)"]},"ecc-gcp-142":{"Standard1":["v7 (6.1)","v8 (8.2)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.2,10.3)","v4.0 (10.2.1)"],"s202":["(CC7.2)"]},"ecc-gcp-113":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,12.6,3.3)"],"BSA":["v3 (AM-4,GS-4,IM-7,NS-2,PA-7)"],"CJIS":["(5.10.1,5.13.1.1,5.5.2,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.13,AC.L2-3.1.16,AC.L2-3.1.17,AC.L2-3.1.5,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,IA.L1-3.5.2,MP.L2-3.8.2,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.15,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03,DSS05)"],"Standard4":["v4 (DSP-07,IAM-05,IVS-03)"],"CE":["v2.2 (A4.8,A7.4,Firewalls,Secure_configuration,uac)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Im.B.1,D3.PC.Im.B.10,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,AC-18,CM-07,MP-02,PL-08,SA-08,SC-07,SC-08,SC-23)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.e.1)"],"Standard15":["(01.a,01.c,01.i,01.j,01.l,01.m,01.n,01.o,01.v,09.m,09.s,09.y,10.d)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.2,A.13.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (13.1.1,13.1.2,13.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.20,8.21,8.22,8.27,8.3)","27017_2015 (13.1.1,13.1.2,13.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (13.1,8,9.1,9.4.1)","27701_2019 (6.10.1.1,6.10.1.2,6.10.1.3,6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.1.5,3.13.1,3.13.15,3.13.2,3.13.5,3.4.6,3.8.2)","800_53 Rev5 (AC-18,AC-3,AC-6,CM-7,MP-2,PL-8,PM-7,SA-8,SC-23,SC-7,SC-8)"],"Standard9":["v1.1 (PR.AC-4,PR.AC-5,PR.DS-5,PR.IP-1,PR.PT-3,PR.PT-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5,4.1.1,7.1,7.1.1,7.1.2,7.1.3)","v4.0 (1.2.5,1.2.6,1.3.1,1.4.4,7.1)"],"s202":["(CC5.2,CC6.1,CC6.3,CC6.6)"]},"ecc-gcp-024":{"Standard1":["v7 (6.2,6.3)","v8 (8.2,8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CIS GCP Foundations Benchmark":["v1.2.0 (2.11)","v1.3.0 (2.11)","v2.0.0 (2.11)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_15.1,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.2,10.2.4,10.2.5,10.3)","v4.0 (10.2.1,10.2.1.2)"],"s202":["(CC7.2)"]},"ecc-gcp-040":{"Standard1":["v7 (12.4)","v8 (3.3)"],"BSA":["v3 (AM-4,IM-7,PA-7)"],"CIS GCP Foundations Benchmark":["v1.2.0 (5.1)","v1.3.0 (5.1)","v2.0.0 (5.1)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_14.d,Article_8.3.b)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.3)","27017_2015 (8.1.3,9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)","v4.0 (7.1)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-gcp-412":{"Standard1":["v7 (12)","v8 (12.2)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.22,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard3":["Standard11_800_53_Rev5 (PL-08,SA-08)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard11":["800-171 Rev2 (3.13.2,3.13.5)","800_53 Rev5 (PL-8,PM-7,SA-8)"],"Standard8":["v3.2.1 (1.2)","v4.0 (1.2.5,1.2.6,1.3.1,1.3.2)"],"s202":["(CC6.1,CC6.6)"]},"ecc-gcp-107":{"Standard1":["v7 (10.1)","v8 (11.2)"],"BSA":["v3 (BR-1)"],"Standard4":["v4 (BCR-08)"],"CE":["v2.2 (Back_up_your_data)"],"DA":["(Article_11.2,Article_11.4)"],"Standard10":["(D5.IR.Pl.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CP-09,CP-10)"],"Standard6":["2016_679 (Article_32.1.c)"],"Standard5":["(164.308.a.7.ii.A)"],"Standard15":["(09.l)"],"Standard14":["27001_2013 (A.12.3.1)","27002_2013 (12.3.1)","27002_2022 (8.13)","27017_2015 (12.3.1)","27018_2019 (12.3.1)","27701_2019 (6.9.3.1)"],"Standard12":["(CIP-009-6_Requirement_R1_Part_1.3)"],"Standard11":["800_53 Rev5 (CP-10,CP-9)"],"Standard9":["v1.1 (PR.IP-4)"],"Standard7":["(6s)"],"Standard8":["v3.2.1 (12.10.1)"],"s202":["(A1.2)"]},"ecc-gcp-225":{"Standard1":["v7 (12)","v8 (12.2)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,CM.L2-3.4.6,CM.L2-3.4.7,SC,SC.L1-3.13.5)"],"Standard4":["v4 (IVS-03)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.3.b,Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,SA-08)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard15":["(01.i)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CM-7,SA-8)"],"Standard8":["v3.2.1 (1.2)","v4.0 (1.2.5,1.2.6,1.3.1,1.3.2)"],"s202":["(CC6.6)"]},"ecc-gcp-217":{"Standard1":["v7 (9.2)","v8 (12.2)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.3.b,Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)","v4.0 (1.2.5,1.2.6,1.3.1,1.3.2)"],"s202":["(CC6.1,CC6.6)"]},"ecc-gcp-169":{"Standard1":["v7 (14.6)","v8 (3.3)"],"BSA":["v3 (AM-4,IM-7,PA-7)"],"CIS GCP Foundations Benchmark":["v1.2.0 (1.9)","v1.3.0 (1.9)","v2.0.0 (1.9)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.3.b)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.3)","27017_2015 (8.1.3,9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)","v4.0 (7.1)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-gcp-231":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.2.6)"],"s202":["(CC8.1)"]},"ecc-gcp-192":{"Standard1":["v7 (5.3)","v8 (7.5,7.6)"],"BSA":["v3 (DS-6,PV-5)"],"CIS GKE Benchmark":["v1.2.0 (5.5.7)","v1.3.0 (5.5.7)","v1.4.0 (5.5.7)"],"CJIS":["(5.10.1.3)"],"CMMC":["v2.0 (RA.L2-3.11.2,SI.L1-3.14.5)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (TVM-07)"],"DA":["(Article_8.1,Article_8.4.f)"],"Standard10":["(D3.DC.Th.B.1)"],"Standard3":["Standard11_800_53_Rev5 (RA-05)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(10.m)"],"Standard14":["27001_2013 (A.12.6.1)","27002_2013 (12.6.1)","27002_2022 (8.8)","27017_2015 (12.6.1)","27018_2019 (12.6)","27701_2019 (6.9.6.1)"],"Standard11":["800-171 Rev2 (3.11.2)","800_53 Rev5 (RA-5)"],"Standard9":["v1.1 (DE.CM-8)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (11.2)","v4.0 (11.3.1)"],"s202":["(CC6.6,CC7.1)"]},"ecc-gcp-280":{"Standard1":["v7 (12.4,9.2)","v8 (12.2)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.3.b,Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)","v4.0 (1.2.5,1.2.6,1.3.1)"],"s202":["(CC6.1,CC6.6)"]},"ecc-gcp-003":{"Standard1":["v7 (16)","v8 (4.2)"],"BSA":["v3 (GS-10,GS-5,PV-1)"],"CIS GCP Foundations Benchmark":["v1.2.0 (1.4)","v1.3.0 (1.4)","v2.0.0 (1.4)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-a18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)","v4.0 (1.5.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-gcp-315":{"Standard1":["v7 (6.3,6.5)","v8 (8.5,8.9)"],"BSA":["v3 (DS-7,LT-3,LT-5)"],"CIS GCP Foundations Benchmark":["v1.3.0 (6.2.9)","v2.0.0 (6.2.8)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-06.03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1,AU-6.3)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.2,10.2.4,10.2.5,10.3,10.5.3,10.5.4)","v4.0 (10.2.1,10.3.3)"],"s202":["(CC7.2,PL1.4)"]},"ecc-gcp-033":{"Standard1":["v7 (12.8,6.2)","v8 (13.6,8.2)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CIS GCP Foundations Benchmark":["v1.3.0 (3.8)","v2.0.0 (3.8)"],"CJIS":["(5.10.1.1,5.10.1.3,5.4.1)"],"CMMC":["v2.0 (AC.L2-3.1.3,AU.L2-3.3.1,SI.L2-3.14.3,SI.L2-3.14.6)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (IVS-03,LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1,D3.DC.Ev.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-12,SI-04,SI-04.04)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa,09.m)"],"Standard14":["27001_2013 (A.12.4.1,A.13.1.1)","27002_2013 (12.4.1,13.1.1)","27002_2022 (8.15,8.16,8.20)","27017_2015 (12.4.1,13.1.1)","27018_2019 (12.4.1,13.1)","27701_2019 (6.10.1.1,6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.13.1,3.14.6,3.3.1)","800_53 Rev5 (AU-12,AU-2,SI-4,SI-4.4)"],"Standard9":["v1.1 (DE.AE-3,DE.CM-1,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.2,10.3)","v4.0 (10.2.1)"],"s202":["(CC7.2)"]},"ecc-gcp-452":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-gcp-278":{"Standard1":["v7 (12.4,9.2)","v8 (12.2)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.3.b,Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)","v4.0 (1.2.5,1.2.6,1.3.1)"],"s202":["(CC6.1,CC6.6)"]},"ecc-gcp-014":{"Standard1":["v7 (6.2,6.7)","v8 (8.11,8.2)"],"BSA":["v3 (DS-7,LT-1,LT-2,LT-3,LT-4,LT-5)"],"CIS GCP Foundations Benchmark":["v1.2.0 (2.1)","v1.3.0 (2.1)","v2.0.0 (2.1)"],"CJIS":["(5.4.1,5.4.3)"],"CMMC":["v2.0 (AU.L2-3.3.1,AU.L2-3.3.5)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-05,LOG-08)"],"DA":["(Article_10.7,Article_15.1,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-06,AU-06.01,AU-06.05,AU-07,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.308.a.1.ii.D,164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1,A.16.1.4)","27002_2013 (12.4.1,16.1.4)","27002_2022 (5.25,8.15)","27017_2015 (12.4.1,16.1.4)","27018_2019 (12.4.1,6.1.4)","27701_2019 (6.13.1.4,6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1,CIP-007-6_Requirement_R4_Part_4.4)"],"Standard11":["800-171 Rev2 (3.3.1,3.3.3,3.3.5)","800_53 Rev5 (AU-12,AU-2,AU-6,AU-6.1,AU-6.5,AU-7)"],"Standard9":["v1.1 (DE.AE-2,DE.AE-3,PR.PT-1,RS.AN-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.2,10.3,10.6,10.6.1,10.6.2)","v4.0 (10.4.1.1,10.4.2)"],"s202":["(CC4.1,CC7.2,CC7.3)"]},"ecc-gcp-099":{"Standard1":["v7 (1.4)","v8 (3.7)"],"BSA":["v3 (DP-1)"],"Standard2":["19 (APO14)"],"Standard4":["v4 (DSP-01,DSP-04)"],"Standard10":["(D1.G.IT.B.2)"],"Standard3":["Standard11_800_53_Rev5 (RA-02)"],"Standard15":["(00.a)"],"Standard14":["27001_2013 (A.18.1.3,A.8.1.1,A.8.2.1,A.8.2.2)","27002_2013 (18.1.3,8.1.1,8.2.1,8.2.2)","27002_2022 (5.12,5.13,5.33,5.9,8.12)","27017_2015 (18.1.3,8.1.1,8.2.1,8.2.2)","27018_2019 (18.1,8)","27701_2019 (6.15.1.3,6.5.1.1,6.5.2.1,6.5.2.2)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.1)"],"Standard11":["800_53 Rev5 (RA-2)"],"Standard9":["v1.1 (ID.AM-5,ID.RA-5)"],"Standard7":["(2._Classify_personal_data_by_type)"],"Standard8":["v3.2.1 (9.6.1)","v4.0 (9.4.2)"],"s202":["(CC3.2)"]},"ecc-gcp-254":{"Standard1":["v7 (14.4)","v8 (3.10)"],"BSA":["v3 (DP-3)"],"CJIS":["(5.10.1.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17)"],"DA":["(Article_8.2,Article_8.3.a)"],"Standard10":["(D3.PC.Am.B.13)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,SC-08)"],"Standard6":["2016_679 (Article_32.1.a,Article_44)"],"Standard5":["(164.312.e.1)"],"Standard15":["(06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1)","27002_2013 (10.1.1,13.2.1)","27002_2022 (5.14,8.24)","27017_2015 (10.1.1,13.2.1)","27018_2019 (10.1.1,13.2.1)","27701_2019 (6.10.2.1,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.8,3.5.10)","800_53 Rev5 (AC-17.2,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-2)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1.1,4.1,4.1.1,8.2.1)","v4.0 (2.2.7,4.1.1,4.2.2,8.3.2)"],"s202":["(CC6.1,CC6.7)"]},"ecc-gcp-079":{"Standard1":["v7 (12.4)","v8 (12.3)"],"CJIS":["(5.10.1)"],"CMMC":["v2.0 (SC.L2-3.13.15)"],"Standard2":["19 (APO01,APO03)"],"DA":["(Article_8.4.b)"],"Standard3":["Standard11_800_53_Rev5 (CM-06,CM-07,SC-23)"],"Standard6":["2016_679 (Article_32)"],"Standard5":["(164.312.e.1)"],"Standard15":["(09.m)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard11":["800_53 Rev5 (CM-6,CM-7,SC-23)"],"Standard9":["v1.1 (PR.AC-7)"],"Standard8":["v3.2.1 (8.3)"],"s202":["(CC5.2)"]},"ecc-gcp-434":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (DP-4,DP-5)"],"CIS GKE Benchmark":["v1.2.0 (5.9.1)","v1.3.0 (5.9.1)","v1.4.0 (5.9.1)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.3.2,3.3.3,3.5.1.2,3.5.1.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-gcp-240":{"Standard1":["v7 (6.3)","v8 (8.2,8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.2,10.2.4,10.2.5,10.3)","v4.0 (10.2.1,)"],"s202":["(CC7.2)"]},"ecc-gcp-089":{"Standard1":["v7 (14.4)","v8 (3.10)"],"BSA":["v3 (DP-3)"],"CJIS":["(5.10.1.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IVS-03)"],"DA":["(Article_8.2,Article_8.3.a)"],"Standard10":["(D3.PC.Am.B.12,D3.PC.Am.B.13)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,SC-08)"],"Standard6":["2016_679 (Article_32.1.a,Article_44)"],"Standard5":["(164.312.e.1)"],"Standard15":["(06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1,A.18.1.3)","27002_2013 (10.1.1,13.2.1,18.1.3)","27002_2022 (5.14,5.33,8.24)","27017_2015 (10.1.1,13.2.1,18.1.3)","27018_2019 (10.1.1,13.2.1,18.1.3)","27701_2019 (6.10.2.1,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.8,3.5.10)","800_53 Rev5 (AC-17.2,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-2)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1.1,4.1,4.1.1,8.2.1)","v4.0 (2.2.7,4.1.1,4.2.2,8.3.2)"],"s202":["(CC6.1,CC6.7)"]},"ecc-gcp-133":{"Standard1":["v7 (5.1)","v8 (12.2)"],"BSA":["v3 (GS-4,NS-2)"],"CIS GKE Benchmark":["v1.2.0 (5.6.1)","v1.3.0 (5.6.1)","v1.4.0 (5.6.1)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)","v4.0 (1.2.5,1.2.6,1.3.1,1.3.2)"],"s202":["(CC6.1,CC6.6)"]},"ecc-gcp-090":{"Standard1":["v7 (14.4)","v8 (3.10)"],"BSA":["v3 (DP-3)"],"CJIS":["(5.10.1.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IVS-03)"],"DA":["(Article_8.2,Article_8.3.a)"],"Standard10":["(D3.PC.Am.B.12,D3.PC.Am.B.13)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,SC-08)"],"Standard6":["2016_679 (Article_32.1.a,Article_44)"],"Standard5":["(164.312.e.1)"],"Standard15":["(06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1,A.18.1.3)","27002_2013 (10.1.1,13.2.1,18.1.3)","27002_2022 (5.14,5.33,8.24)","27017_2015 (10.1.1,13.2.1,18.1.3)","27018_2019 (10.1.1,13.2.1,18.1.3)","27701_2019 (6.10.2.1,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.8,3.5.10)","800_53 Rev5 (AC-17.2,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-2)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1.1,4.1,4.1.1,8.2.1)","v4.0 (2.2.7,4.1.1,4.2.2,8.3.2)"],"s202":["(CC6.1,CC6.7)"]},"ecc-gcp-109":{"Standard1":["v7 (12.4)","v8 (12.2,12.6,3.3)"],"BSA":["v3 (AM-4,GS-4,IM-7,NS-2,PA-7)"],"CJIS":["(5.10.1,5.13.1.1,5.5.2,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.13,AC.L2-3.1.16,AC.L2-3.1.17,AC.L2-3.1.5,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,IA.L1-3.5.2,MP.L2-3.8.2,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.15,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03,DSS05)"],"Standard4":["v4 (DSP-07,IAM-05,IVS-03)"],"CE":["v2.2 (A4.8,A7.4,Firewalls,Secure_configuration,uac)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Im.B.1,D3.PC.Im.B.10,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,AC-18,CM-07,MP-02,PL-08,SA-08,SC-07,SC-08,SC-23)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.e.1)"],"Standard15":["(01.a,01.c,01.i,01.j,01.l,01.m,01.n,01.o,01.v,09.m,09.s,09.y,10.d)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.2,A.13.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (13.1.1,13.1.2,13.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.20,8.21,8.22,8.27,8.3)","27017_2015 (13.1.1,13.1.2,13.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (13.1,8,9.1,9.4.1)","27701_2019 (6.10.1.1,6.10.1.2,6.10.1.3,6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.1.5,3.13.1,3.13.15,3.13.2,3.13.5,3.4.6,3.8.2)","800_53 Rev5 (AC-18,AC-3,AC-6,CM-7,MP-2,PL-8,PM-7,SA-8,SC-23,SC-7,SC-8)"],"Standard9":["v1.1 (PR.AC-4,PR.AC-5,PR.DS-5,PR.IP-1,PR.PT-3,PR.PT-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5,4.1.1,7.1,7.1.1,7.1.2,7.1.3)","v4.0 (1.2.5,1.2.6,1.3.1,1.4.4,7.1)"],"s202":["(CC5.2,CC6.1,CC6.3,CC6.6)"]},"ecc-gcp-436":{"Standard1":["v7 (9.5)","v8 (4.6)"],"CIS GCP Foundations Benchmark":["v2.0.0 (6.2.9)"],"CJIS":["(5.13.1.1)"],"CMMC":["v2.0 (SC.L2-3.13.15)"],"Standard2":["19 (BAI09)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,MA-04)"],"Standard6":["2016_679 (Article_32.1.b)"],"Standard15":["(07.c)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard9":["v1.1 (PR.PT-4)"],"Standard8":["v4.0 (1.2.5,1.2.6)"],"s202":["(CC5.2)"]},"ecc-gcp-210":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CIS GCP Foundations Benchmark":["v1.2.0 (6.3.4)","v1.3.0 (6.3.4)","v2.0.0 (6.3.4)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.2.6)"],"s202":["(CC8.1)"]},"ecc-gcp-170":{"Standard1":["v7 (11.4)","v8 (12.1,12.6)"],"CIS GCP Foundations Benchmark":["v1.2.0 (3.9)","v1.3.0 (3.9)","v2.0.0 (3.9)"],"CJIS":["(5.10.1,5.10.4.1,5.13.1.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,IA.L1-3.5.2,SC.L1-3.13.1,SC.L2-3.13.15)"],"CE":["v2.2 (A6.1,A6.2,A6.3,A6.6,Security_update_management)"],"DA":["(Article_8.4.b,Article_8.4.f)"],"Standard10":["(D3.CC.Pa.B.1,D3.PC.Im.B.10)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,SC-08,SC-23,SI-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.312.e.1)"],"Standard15":["(01.j,09.m,09.s,09.y,10.d)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.2)","27002_2013 (13.1.1,13.1.2)","27002_2022 (8.20,8.21)","27017_2015 (13.1.1,13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.2)"],"Standard12":["(CIP-007-6_Requirement_R2_Part_2.1)"],"Standard11":["800-171 Rev2 (3.1.17,3.13.1,3.13.15)","800_53 Rev5 (AC-18,SC-23,SC-8,SI-2)"],"Standard9":["v1.1 (PR.DS-2,PR.DS-5,PR.PT-4)"],"Standard8":["v3.2.1 (4.1.1)"],"s202":["(CC5.2,CC6.1,CC8.1)"]},"ecc-gcp-400":{"Standard1":["v7 (4.4)","v8 (5.2)"],"CJIS":["(5.6.2.1.1)"],"CMMC":["v2.0 (IA.L2-3.5.7)"],"Standard4":["v4 (IAM-02)"],"Standard10":["(D3.PC.Am.B.7)"],"Standard3":["Standard11_800_53_Rev5 (IA-05)"],"Standard5":["(164.308.a.5.ii.D)"],"Standard15":["(01.f)"],"Standard14":["27001_2013 (A.9.4.3)","27002_2013 (9.4.3)","27002_2022 (5.17)","27017_2015 (9.4.3)","27018_2019 (9.4.3)","27701_2019 (6.6.4.3)"],"Standard12":["(CIP-007-6_Requirement_R5_Part_5.5)"],"Standard11":["800-171 Rev2 (3.5.7)","800_53 Rev5 (IA-5)"],"Standard8":["v4.0 (8.6.3)"]},"ecc-gcp-134":{"Standard1":["v7 (14.4)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.2.6)"],"s202":["(CC8.1)"]},"ecc-gcp-233":{"Standard1":["v7 (14.6)","v8 (3.3)"],"BSA":["v3 (AM-4,IM-7,PA-7)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.3.b)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.3)","27017_2015 (8.1.3,9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)","v4.0 (1.3.1,7.1)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-gcp-279":{"Standard1":["v7 (12.4,9.2)","v8 (12.2)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.3.b,Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)","v4.0 (1.2.5,1.2.6,1.3.1)"],"s202":["(CC6.1,CC6.6)"]},"ecc-gcp-114":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,12.6,3.3)"],"BSA":["v3 (AM-4,GS-4,IM-7,NS-2,PA-7)"],"CJIS":["(5.10.1,5.13.1.1,5.5.2,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.13,AC.L2-3.1.16,AC.L2-3.1.17,AC.L2-3.1.5,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,IA.L1-3.5.2,MP.L2-3.8.2,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.15,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03,DSS05)"],"Standard4":["v4 (DSP-07,IAM-05,IVS-03)"],"CE":["v2.2 (A4.8,A7.4,Firewalls,Secure_configuration,uac)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Im.B.1,D3.PC.Im.B.10,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,AC-18,CM-07,MP-02,PL-08,SA-08,SC-07,SC-08,SC-23)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.e.1)"],"Standard15":["(01.a,01.c,01.i,01.j,01.l,01.m,01.n,01.o,01.v,09.m,09.s,09.y,10.d)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.2,A.13.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (13.1.1,13.1.2,13.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.20,8.21,8.22,8.27,8.3)","27017_2015 (13.1.1,13.1.2,13.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (13.1,8,9.1,9.4.1)","27701_2019 (6.10.1.1,6.10.1.2,6.10.1.3,6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.1.5,3.13.1,3.13.15,3.13.2,3.13.5,3.4.6,3.8.2)","800_53 Rev5 (AC-18,AC-3,AC-6,CM-7,MP-2,PL-8,PM-7,SA-8,SC-23,SC-7,SC-8)"],"Standard9":["v1.1 (PR.AC-4,PR.AC-5,PR.DS-5,PR.IP-1,PR.PT-3,PR.PT-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5,4.1.1,7.1,7.1.1,7.1.2,7.1.3)","v4.0 (1.2.5,1.2.6,1.3.1,1.4.4,7.1)"],"s202":["(CC5.2,CC6.1,CC6.3,CC6.6)"]},"ecc-gcp-116":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,12.6,3.3)"],"BSA":["v3 (AM-4,GS-4,IM-7,NS-2,PA-7)"],"CJIS":["(5.10.1,5.13.1.1,5.5.2,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.13,AC.L2-3.1.16,AC.L2-3.1.17,AC.L2-3.1.5,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,IA.L1-3.5.2,MP.L2-3.8.2,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.15,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03,DSS05)"],"Standard4":["v4 (DSP-07,IAM-05,IVS-03)"],"CE":["v2.2 (A4.8,A7.4,Firewalls,Secure_configuration,uac)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Im.B.1,D3.PC.Im.B.10,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,AC-18,CM-07,MP-02,PL-08,SA-08,SC-07,SC-08,SC-23)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.e.1)"],"Standard15":["(01.a,01.c,01.i,01.j,01.l,01.m,01.n,01.o,01.v,09.m,09.s,09.y,10.d)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.2,A.13.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (13.1.1,13.1.2,13.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.20,8.21,8.22,8.27,8.3)","27017_2015 (13.1.1,13.1.2,13.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (13.1,8,9.1,9.4.1)","27701_2019 (6.10.1.1,6.10.1.2,6.10.1.3,6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.1.5,3.13.1,3.13.15,3.13.2,3.13.5,3.4.6,3.8.2)","800_53 Rev5 (AC-18,AC-3,AC-6,CM-7,MP-2,PL-8,PM-7,SA-8,SC-23,SC-7,SC-8)"],"Standard9":["v1.1 (PR.AC-4,PR.AC-5,PR.DS-5,PR.IP-1,PR.PT-3,PR.PT-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5,4.1.1,7.1,7.1.1,7.1.2,7.1.3)","v4.0 (1.2.5,1.2.6,1.3.1,1.4.4,7.1)"],"s202":["(CC5.2,CC6.1,CC6.3,CC6.6)"]},"ecc-gcp-305":{"Standard1":["v7 (12.4)","v8 (3.3)"],"BSA":["v3 (AM-4,PA-7)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.3.b)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.3)","27017_2015 (8.1.3,9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)","v4.0 (1.3.1,7.1)"],"s202":["(CC5.2,CC6.3)"]},"ecc-gcp-286":{"Standard1":["v7 (14.4)","v8 (3.10)"],"BSA":["v3 (DP-3)"],"CJIS":["(5.10.1.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IVS-03)"],"DA":["(Article_8.2,Article_8.3.a,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12,D3.PC.Am.B.13)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,SC-08)"],"Standard6":["2016_679 (Article_32.1.a,Article_44)"],"Standard5":["(164.312.e.1)"],"Standard15":["(06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1,A.18.1.3)","27002_2013 (10.1.1,13.2.1,18.1.3)","27002_2022 (5.14,5.33,8.24)","27017_2015 (10.1.1,13.2.1,18.1.3)","27018_2019 (10.1.1,13.2.1,18.1.3)","27701_2019 (6.10.2.1,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.8,3.5.10)","800_53 Rev5 (AC-17.2,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-2)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1.1,4.1,4.1.1,8.2.1)","v4.0 (2.2.7,4.2.2,8.3.2)"],"s202":["(CC6.1,CC6.7)"]},"ecc-gcp-447":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"s202":["(CC8.1)"]},"ecc-gcp-180":{"Standard1":["v7 (6.3)","v8 (8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CIS GCP Foundations Benchmark":["v1.2.0 (6.2.6)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.2,10.2.4,10.2.5,10.3)","v4.0 (10.2.1)"],"s202":["(CC7.2)"]},"ecc-gcp-171":{"Standard1":["v7 (16)","v8 (4.7)"],"CIS GCP Foundations Benchmark":["v1.2.0 (4.1)","v1.3.0 (4.1)","v2.0.0 (4.1)"],"CJIS":["(5.4.3,5.6.3.2)"],"CE":["v2.2 (A5.2,A5.3,Firewalls,Secure_configuration)"],"DA":["(Article_8.3.b)"],"Standard10":["(D3.PC.Am.B.3,D3.PC.Am.B.8)"],"Standard3":["Standard11_800_53_Rev5 (IA-05)"],"Standard15":["(01.b)"],"Standard14":["27001_2013 (A.9.2.2,A.9.2.3)","27002_2013 (9.2.2,9.2.3)","27002_2022 (8.2,8.9)","27017_2015 (9.2.2,9.2.3)","27018_2019 (9.2.2,9.2.3)","27701_2019 (6.6.2.2)"],"Standard12":["(CIP-007-6_Requirement_R5_Part_5.2)"],"Standard11":["800_53 Rev5 (IA-5,IA-5.5)"],"Standard9":["v1.1 (PR.AC-1)"],"Standard8":["v3.2.1 (2.1,2.1.1)","v4.0 (7.2.1,7.2.2)"],"s202":["(CC6.3)"]},"ecc-gcp-062":{"Standard1":["v7 (11,12)","v8 (13.4)"],"BSA":["v3 (NS-1)"],"CIS GCP Foundations Benchmark":["v1.0.0 (7.16)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (SC.L2-3.13.6)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CA-09)"],"Standard15":["(01.l,09.m,09.n)"],"Standard14":["27001_2013 (A.13.1.1)","27002_2013 (13.1.1)","27002_2022 (8.16,8.22)","27017_2015 (13.1.1)","27018_2019 (13.1)","27701_2019 (6.10.1.1)"],"Standard12":["(CIP-005-7,Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.13.1)","800_53 Rev5 (CA-9)"],"Standard9":["v1.1 (PR.AC-5)"],"Standard8":["v4.0 (1.3.1,1.3.2)"],"s202":["(CC6.6)"]},"ecc-gcp-132":{"Standard1":["v7 (14.8)","v8 (3.11)"],"CIS GKE Benchmark":["v1.2.0 (5.3.1)","v1.3.0 (5.3.1)","v1.4.0 (5.3.1)"],"BSA":["v3 (DP-4,DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.3.2,3.3.3,3.5.1.2,3.5.1.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-gcp-110":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,12.6,3.3)"],"BSA":["v3 (AM-4,GS-4,IM-7,NS-2,PA-7)"],"CJIS":["(5.10.1,5.13.1.1,5.5.2,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.13,AC.L2-3.1.16,AC.L2-3.1.17,AC.L2-3.1.5,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,IA.L1-3.5.2,MP.L2-3.8.2,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.15,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03,DSS05)"],"Standard4":["v4 (DSP-07,IAM-05,IVS-03)"],"CE":["v2.2 (A4.8,A7.4,Firewalls,Secure_configuration,uac)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Im.B.1,D3.PC.Im.B.10,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,AC-18,CM-07,MP-02,PL-08,SA-08,SC-07,SC-08,SC-23)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.e.1)"],"Standard15":["(01.a,01.c,01.i,01.j,01.l,01.m,01.n,01.o,01.v,09.m,09.s,09.y,10.d)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.2,A.13.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (13.1.1,13.1.2,13.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.20,8.21,8.22,8.27,8.3)","27017_2015 (13.1.1,13.1.2,13.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (13.1,8,9.1,9.4.1)","27701_2019 (6.10.1.1,6.10.1.2,6.10.1.3,6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.1.5,3.13.1,3.13.15,3.13.2,3.13.5,3.4.6,3.8.2)","800_53 Rev5 (AC-18,AC-3,AC-6,CM-7,MP-2,PL-8,PM-7,SA-8,SC-23,SC-7,SC-8)"],"Standard9":["v1.1 (PR.AC-4,PR.AC-5,PR.DS-5,PR.IP-1,PR.PT-3,PR.PT-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5,4.1.1,7.1,7.1.1,7.1.2,7.1.3)","v4.0 (1.2.5,1.2.6,1.3.1,1.4.4,7.1)"],"s202":["(CC5.2,CC6.1,CC6.3,CC6.6)"]},"ecc-gcp-223":{"Standard1":["v7 (11.1)","v8 (12.2)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)","v4.0 (1.2.5,1.2.6,1.3.1,1.3.2)"],"s202":["(CC6.1,CC6.6)"]},"ecc-gcp-189":{"Standard1":["v7 (10)","v8 (11.1)"],"BSA":["v3 (GS-8)"],"Standard2":["19 (APO14)"],"Standard4":["v4 (BCR-08)"],"CE":["v2.2 (Back_up_your_data)"],"DA":["(Article_11.1)"],"Standard10":["(D1.RM.RMP.B.1,D5.IR.Pl.B.5,D5.IR.Pl.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CP-10)"],"Standard6":["2016_679 (Article_32.1.c)"],"Standard5":["(164.308.a.7.ii.A)"],"Standard15":["(09.l)"],"Standard14":["27001_2013 (A.12.3.1)","27002_2013 (12.3.1)","27002_2022 (8.13)","27017_2015 (12.3.1)","27018_2019 (12.3.1)","27701_2019 (6.9.3.1)"],"Standard12":["(CIP-009-6_Requirement_R1_Part_1.3)"],"Standard11":["800_53 Rev5 (CP-10)"],"Standard9":["v1.1 (ID.SC-5,PR.IP-9)"],"Standard7":["(6s)"],"s202":["(A1.2)"]},"ecc-gcp-190":{"Standard1":["v7 (10)","v8 (3.3)"],"BSA":["v3 (AM-4,IM-7,PA-7)"],"CIS GCP Foundations Benchmark":["v1.2.0 (2.3)","v1.3.0 (2.3)","v2.0.0 (2.3)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.3.b)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.3)","27017_2015 (8.1.3,9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)","v4.0 (7.1)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-gcp-115":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,12.6,3.3)"],"BSA":["v3 (AM-4,GS-4,IM-7,NS-2,PA-7)"],"CJIS":["(5.10.1,5.13.1.1,5.5.2,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.13,AC.L2-3.1.16,AC.L2-3.1.17,AC.L2-3.1.5,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,IA.L1-3.5.2,MP.L2-3.8.2,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.15,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03,DSS05)"],"Standard4":["v4 (DSP-07,IAM-05,IVS-03)"],"CE":["v2.2 (A4.8,A7.4,Firewalls,Secure_configuration,uac)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Im.B.1,D3.PC.Im.B.10,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,AC-18,CM-07,MP-02,PL-08,SA-08,SC-07,SC-08,SC-23)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.e.1)"],"Standard15":["(01.a,01.c,01.i,01.j,01.l,01.m,01.n,01.o,01.v,09.m,09.s,09.y,10.d)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.2,A.13.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (13.1.1,13.1.2,13.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.20,8.21,8.22,8.27,8.3)","27017_2015 (13.1.1,13.1.2,13.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (13.1,8,9.1,9.4.1)","27701_2019 (6.10.1.1,6.10.1.2,6.10.1.3,6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.1.5,3.13.1,3.13.15,3.13.2,3.13.5,3.4.6,3.8.2)","800_53 Rev5 (AC-18,AC-3,AC-6,CM-7,MP-2,PL-8,PM-7,SA-8,SC-23,SC-7,SC-8)"],"Standard9":["v1.1 (PR.AC-4,PR.AC-5,PR.DS-5,PR.IP-1,PR.PT-3,PR.PT-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5,4.1.1,7.1,7.1.1,7.1.2,7.1.3)","v4.0 (1.2.5,1.2.6,1.3.1,1.4.4,7.1)"],"s202":["(CC5.2,CC6.1,CC6.3,CC6.6)"]},"ecc-gcp-176":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CIS GCP Foundations Benchmark":["v1.2.0 (6.1.3)","v1.3.0 (6.1.3)","v2.0.0 (6.1.3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.2.6)"],"s202":["(CC8.1)"]},"ecc-gcp-261":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (DP-4,DP-5)"],"CIS GCP Foundations Benchmark":["v1.3.0 (1.17)","v2.0.0 (1.17)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.3.2,3.3.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-gcp-053":{"Standard1":["v7 (14.7,3.1)","v8 (7.6)"],"BSA":["v3 (DS-6,PV-5)"],"CIS GKE Benchmark":["v1.2.0 (5.5.2)","v1.3.0 (5.5.2)","v1.4.0 (5.5.2)"],"CJIS":["(5.10.1.3)"],"CMMC":["v2.0 (RA.L2-3.11.2,SI.L1-3.14.5)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (TVM-07)"],"DA":["(Article_8.1)"],"Standard10":["(D3.DC.Th.B.1)"],"Standard3":["Standard11_800_53_Rev5 (RA-05)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(10.m)"],"Standard14":["27001_2013 (A.12.6.1)","27002_2013 (12.6.1)","27002_2022 (8.8)","27017_2015 (12.6.1)","27018_2019 (12.6)","27701_2019 (6.9.6.1)"],"Standard11":["800-171 Rev2 (3.11.2)","800_53 Rev5 (RA-5)"],"Standard9":["v1.1 (DE.CM-8)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (11.2)","v4.0 (11.3.2.1)"],"s202":["(CC6.6,CC7.1)"]},"ecc-gcp-303":{"Standard1":["v7 (12)","v8 (12.2)"],"BSA":["v3 (NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.20,CM.L2-3.4.6,SC.L2-3.13.2)"],"Standard4":["v4 (IVS-03)"],"DA":["(Article_8.3.b,Article_8.4.b)"],"Standard3":["Standard11_800_53_Rev5 (SC-07)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard15":["(01.n)"],"Standard14":["27001_2013 (A.13.1.1)","27002_2013 (13.1.1)","27002_2022 (8.20,8.27)","27017_2015 (13.1.1)","27018_2019 (13.1)","27701_2019 (6.10.1.1)"],"Standard11":["800-171 Rev2 (3.13.2)","800_53 Rev5 (SC-7)"],"Standard9":["v1.1 (PR.PT-3)"],"Standard8":["v3.2.1 (2.2.2)","v4.0 (1.2.5,1.2.6,1.3.1)"],"s202":["(CC6.6)"]},"ecc-gcp-236":{"Standard1":["v7 (5.1)","v8 (16.5)"],"CE":["v2.2 (A6.3)"],"DA":["(Article_8.4.f)"],"Standard3":["Standard11_800_53_Rev5 (SR-11)"],"Standard15":["(10.k)"],"Standard14":["27002_2022 (8.26)"],"Standard12":["(CIP-013-2_Requirement_R2)"],"Standard11":["800_53 Rev5 (SR-11)"],"Standard9":["v1.1 (PR.IP-2)"],"Standard8":["v3.2.1 (6.2)","v4.0 (12.3.4,6.3.3)"]},"ecc-gcp-247":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.2.6)"],"s202":["(CC8.1)"]},"ecc-gcp-086":{"Standard1":["v7 (14.4)","v8 (3.10)"],"BSA":["v3 (DP-3)"],"CJIS":["(5.10.1.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IVS-03)"],"DA":["(Article_8.2,Article_8.3.a)"],"Standard10":["(D3.PC.Am.B.12,D3.PC.Am.B.13)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,SC-08)"],"Standard6":["2016_679 (Article_32.1.a,Article_44)"],"Standard5":["(164.312.e.1)"],"Standard15":["(06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1,A.18.1.3)","27002_2013 (10.1.1,13.2.1,18.1.3)","27002_2022 (5.14,5.33,8.24)","27017_2015 (10.1.1,13.2.1,18.1.3)","27018_2019 (10.1.1,13.2.1,18.1.3)","27701_2019 (6.10.2.1,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.8,3.5.10)","800_53 Rev5 (AC-17.2,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-2)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1.1,4.1,4.1.1,8.2.1)","v4.0 (2.2.7,4.1.1,4.2.2,8.3.2)"],"s202":["(CC6.1,CC6.7)"]},"ecc-gcp-222":{"Standard1":["v7 (4.7)","v8 (4.7)"],"CJIS":["(5.4.3,5.6.3.2)"],"CE":["v2.2 (A5.2,A5.3,Firewalls,Secure_configuration)"],"DA":["(Article_8.3.b)"],"Standard10":["(D3.PC.Am.B.3,D3.PC.Am.B.8)"],"Standard3":["Standard11_800_53_Rev5 (IA-05)"],"Standard15":["(01.b)"],"Standard14":["27001_2013 (A.9.2.2,A.9.2.3)","27002_2013 (9.2.2,9.2.3)","27002_2022 (8.2,8.9)","27017_2015 (9.2.2,9.2.3)","27018_2019 (9.2.2,9.2.3)","27701_2019 (6.6.2.2)"],"Standard12":["(CIP-007-6_Requirement_R5_Part_5.2)"],"Standard11":["800_53 Rev5 (IA-5,IA-5.5)"],"Standard9":["v1.1 (PR.AC-1)"],"Standard8":["v3.2.1 (2.1,2.1.1)","v4.0 (7.2.1,7.2.2)"],"s202":["(CC6.3)"]},"ecc-gcp-197":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (DP-4,DP-5)"],"CIS GCP Foundations Benchmark":["v1.2.0 (4.11)","v1.3.0 (4.11)","v2.0.0 (4.11)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (8.3.2)"],"s202":["(CC6.1)"]},"ecc-gcp-048":{"Standard1":["v7 (6.2)","v8 (8.2)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CIS GKE Benchmark":["v1.2.0 (5.7.1)","v1.3.0 (5.7.1)","v1.4.0 (5.7.1)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.2,10.3)","v4.0 (10.2.1)"],"s202":["(CC7.2)"]},"ecc-gcp-274":{"Standard1":["v7 (11.1)","v8 (12.2)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)","v4.0 (1.2.5,1.2.6,1.3.1,1.3.2)"],"s202":["(CC6.1,CC6.6)"]},"ecc-gcp-150":{"Standard1":["v7 (6.4)","v8 (3.3)"],"BSA":["v3 (AM-4,IM-7,PA-7)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.3.b)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.3)","27017_2015 (8.1.3,9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)","v4.0 (1.3.1,7.1)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-gcp-243":{"Standard1":["v7 (9.2)","v8 (12.2)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.3.b,Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)","v4.0 (1.2.5,1.2.6,1.3.1)"],"s202":["(CC6.1,CC6.6)"]},"ecc-gcp-282":{"Standard1":["v7 (12.4,9.2)","v8 (12.2)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.3.b,Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)","v4.0 (1.2.5,1.2.6,1.3.1)"],"s202":["(CC6.1,CC6.6)"]},"ecc-gcp-140":{"Standard1":["v7 (1.4)","v8 (3.7)"],"BSA":["v3 (DP-1)"],"Standard2":["19 (APO14)"],"Standard4":["v4 (DSP-01,DSP-04)"],"Standard10":["(D1.G.IT.B.2)"],"Standard3":["Standard11_800_53_Rev5 (RA-02)"],"Standard15":["(00.a)"],"Standard14":["27001_2013 (A.18.1.3,A.8.1.1,A.8.2.1,A.8.2.2)","27002_2013 (18.1.3,8.1.1,8.2.1,8.2.2)","27002_2022 (5.12,5.13,5.33,5.9,8.12)","27017_2015 (18.1.3,8.1.1,8.2.1,8.2.2)","27018_2019 (18.1,8)","27701_2019 (6.15.1.3,6.5.1.1,6.5.2.1,6.5.2.2)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.1)"],"Standard11":["800_53 Rev5 (RA-2)"],"Standard9":["v1.1 (ID.AM-5,ID.RA-5)"],"Standard7":["(2._Classify_personal_data_by_type)"],"Standard8":["v3.2.1 (9.6.1)","v4.0 (9.4.2)"],"s202":["(CC3.2)"]},"ecc-gcp-055":{"Standard1":["v7 (3.4,5.2)","v8 (4.6)"],"CIS GKE Benchmark":["v1.2.0 (5.5.1)","v1.3.0 (5.5.1)","v1.4.0 (5.5.1)"],"CJIS":["(5.13.1.1)"],"Standard2":["19 (BAI09)"],"DA":["(Article_8.4.b)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,MA-04)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(07.c)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard11":["800_53 Rev5 (CM-7,MA-4)"],"Standard9":["v1.1 (PR.IP-1,PR.IP-2,PR.PT-4)"],"Standard8":["v4.0 (1.2.5)"],"s202":["(CC5.2)"]},"ecc-gcp-239":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.2.6)"],"s202":["(CC8.1)"]},"ecc-gcp-285":{"Standard1":["v7 (12.4,9.2)","v8 (12.2)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.3.b,Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)","v4.0 (1.2.5,1.2.6,1.3.1)"],"s202":["(CC6.1,CC6.6)"]},"ecc-gcp-312":{"Standard1":["v7 (6.2)","v8 (8.2)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.2,10.3)","v4.0 (10.2.1)"],"s202":["(CC7.2)"]},"ecc-gcp-249":{"Standard1":["v7 (18.5)","v8 (12.6)"],"CJIS":["(5.10.1,5.13.1.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,IA.L1-3.5.2,SC.L1-3.13.1,SC.L2-3.13.15)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.10)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,SC-08,SC-23)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.312.e.1)"],"Standard15":["(01.j,09.m,09.s,09.y,10.d)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.2)","27002_2013 (13.1.1,13.1.2)","27002_2022 (8.20,8.21)","27017_2015 (13.1.1,13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.2)"],"Standard11":["800-171 Rev2 (3.1.17,3.13.1,3.13.15)","800_53 Rev5 (AC-18,SC-23,SC-8)"],"Standard9":["v1.1 (PR.DS-2,PR.DS-5,PR.PT-4)"],"Standard8":["v3.2.1 (4.1.1)"],"s202":["(CC5.2,CC6.1)"]},"ecc-gcp-219":{"Standard1":["v7 (2.6)","v8 (2.3,4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.7,CM.L2-3.4.9)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.DC.Ev.B.3,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-07,CM-07.02,CM-08.03,CM-09,CM-10,CM-11,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.h,10.k)"],"Standard14":["27001_2013 (A.12.5.1,A.14.2.5)","27002_2013 (12.5.1,14.2.5)","27002_2022 (8.19,8.27,8.9)","27017_2015 (12.5.1,14.2.5)","27018_2019 (12.5,14)","27701_2019 (6.11.2.5,6.9.5.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-10,CM-11,CM-2,CM-6,CM-7.2,CM-7.4,CM-8.3,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (DE.CM-7,PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (1.2.6,1.5.1)"],"s202":["(CC7.1,CC8.1)"]},"ecc-gcp-018":{"Standard1":["v7 (6.2,6.3)","v8 (8.2,8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CIS GCP Foundations Benchmark":["v1.2.0 (2.5)","v1.3.0 (2.5)","v2.0.0 (2.5)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_15.1,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.2,10.2.4,10.2.5,10.3)","v4.0 (10.2.1,10.2.1.2)"],"s202":["(CC7.2)"]},"ecc-gcp-188":{"Standard1":["v7 (14.6)","v8 (3.3)"],"BSA":["v3 (AM-4,IM-7,PA-7)"],"CIS GCP Foundations Benchmark":["v1.2.0 (7.1)","v1.3.0 (7.1)","v2.0.0 (7.1)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.3.b)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.3)","27017_2015 (8.1.3,9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)","v4.0 (7.1)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-gcp-179":{"Standard1":["v7 (6.3)","v8 (8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CIS GCP Foundations Benchmark":["v1.2.0 (6.2.4)","v1.3.0 (6.2.3)","v2.0.0 (6.2.3)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.2,10.2.4,10.2.5,10.3)","v4.0 (10.2.1)"],"s202":["(CC7.2)"]},"ecc-gcp-042":{"Standard1":["v7 (6.2)","v8 (8.2)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CIS GCP Foundations Benchmark":["v1.0.0 (5.3)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.2,10.3)","v4.0 (10.2.1)"],"s202":["(CC7.2)"]},"ecc-gcp-182":{"Standard1":["v7 (6.3)","v8 (8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CIS GCP Foundations Benchmark":["v1.2.0 (6.2.15)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.2,10.2.4,10.2.5,10.3)","v4.0 (10.2.1)"],"s202":["(CC7.2)"]},"ecc-gcp-071":{"Standard1":["v7 (11.2,14.2)","v8 (12.2,3.3)"],"BSA":["v3 (AM-4,GS-4,IM-7,NS-2,PA-7)"],"CJIS":["(5.10.1,5.5.2,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,AC.L2-3.1.5,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,MP.L2-3.8.2,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03,DSS05)"],"Standard4":["v4 (DSP-07,IAM-05,IVS-03)"],"CE":["v2.2 (A4.8,A7.4,Firewalls,Secure_configuration,uac)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,CM-07,MP-02,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.i,01.l,01.m,01.n,01.o,01.v,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (13.1.1,13.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.20,8.22,8.27,8.3)","27017_2015 (13.1.1,13.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (13.1,8,9.1,9.4.1)","27701_2019 (6.10.1.1,6.10.1.3,6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.1.5,3.13.2,3.13.5,3.4.6,3.8.2)","800_53 Rev5 (AC-3,AC-6,CM-7,MP-2,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-4,PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5,7.1,7.1.1,7.1.2,7.1.3)","v4.0 (1.2.5,1.2.6,1.3.1,1.4.4,7.1)"],"s202":["(CC5.2,CC6.1,CC6.3,CC6.6)"]},"ecc-gcp-137":{"Standard1":["v7 (13,14.4)","v8 (3.10)"],"BSA":["v3 (DP-3)"],"CJIS":["(5.10.1.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IVS-03)"],"DA":["(Article_8.2,Article_8.3.a)"],"Standard10":["(D3.PC.Am.B.12,D3.PC.Am.B.13)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,SC-08)"],"Standard6":["2016_679 (Article_32.1.a,Article_44)"],"Standard5":["(164.312.e.1)"],"Standard15":["(06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1,A.18.1.3)","27002_2013 (10.1.1,13.2.1,18.1.3)","27002_2022 (5.14,5.33,8.24)","27017_2015 (10.1.1,13.2.1,18.1.3)","27018_2019 (10.1.1,13.2.1,18.1.3)","27701_2019 (6.10.2.1,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.8,3.5.10)","800_53 Rev5 (AC-17.2,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-2)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1.1,4.1,4.1.1,8.2.1)","v4.0 (2.2.7,4.2.2,8.3.2)"],"s202":["(CC6.1,CC6.7)"]},"ecc-gcp-298":{"Standard1":["v7 (5.1)","v8 (3.4)"],"CJIS":["(5.3.4,5.4.6)"],"Standard2":["19 (DSS06)"],"Standard4":["v4 (DSP-16)"],"DA":["(Article_8.3.d)"],"Standard3":["Standard11_800_53_Rev5 (SI-12)"],"Standard6":["2016_679 (Article_5.1.e)"],"Standard15":["(13.l)"],"Standard14":["27001_2013 (A.18.1.3)","27002_2013 (18.1.3)","27002_2022 (5.33)","27017_2015 (18.1.3)","27018_2019 (18.1)","27701_2019 (6.15.1.3)"],"Standard11":["800_53 Rev5 (SI-12)"],"Standard7":["(4._Integrate_data_privacy_into_records_retention_practices)"],"Standard8":["v3.2.1 (3.1)","v4.0 (3.2.1)"],"s202":["(C1.1)"]},"ecc-gcp-112":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,12.6,3.3)"],"BSA":["v3 (AM-4,GS-4,IM-7,NS-2,PA-7)"],"CJIS":["(5.10.1,5.13.1.1,5.5.2,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.13,AC.L2-3.1.16,AC.L2-3.1.17,AC.L2-3.1.5,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,IA.L1-3.5.2,MP.L2-3.8.2,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.15,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03,DSS05)"],"Standard4":["v4 (DSP-07,IAM-05,IVS-03)"],"CE":["v2.2 (A4.8,A7.4,Firewalls,Secure_configuration,uac)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Im.B.1,D3.PC.Im.B.10,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,AC-18,CM-07,MP-02,PL-08,SA-08,SC-07,SC-08,SC-23)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.e.1)"],"Standard15":["(01.a,01.c,01.i,01.j,01.l,01.m,01.n,01.o,01.v,09.m,09.s,09.y,10.d)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.2,A.13.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (13.1.1,13.1.2,13.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.20,8.21,8.22,8.27,8.3)","27017_2015 (13.1.1,13.1.2,13.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (13.1,8,9.1,9.4.1)","27701_2019 (6.10.1.1,6.10.1.2,6.10.1.3,6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.1.5,3.13.1,3.13.15,3.13.2,3.13.5,3.4.6,3.8.2)","800_53 Rev5 (AC-18,AC-3,AC-6,CM-7,MP-2,PL-8,PM-7,SA-8,SC-23,SC-7,SC-8)"],"Standard9":["v1.1 (PR.AC-4,PR.AC-5,PR.DS-5,PR.IP-1,PR.PT-3,PR.PT-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5,4.1.1,7.1,7.1.1,7.1.2,7.1.3)","v4.0 (1.2.5,1.2.6,1.3.1,1.4.4,7.1)"],"s202":["(CC5.2,CC6.1,CC6.3,CC6.6)"]},"ecc-gcp-317":{"Standard1":["v7 (13)","v8 (3.11)"],"BSA":["v3 (DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (SC.L2-3.13.16)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16)","800_53 Rev5 (SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,8.2.1)","v4.0 (3.3.2,3.3.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-gcp-242":{"Standard1":["v7 (14.6)","v8 (3.3)"],"BSA":["v3 (AM-4,IM-7,PA-7)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.3.b)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.3)","27017_2015 (8.1.3,9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)","v4.0 (1.3.1,7.1)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-gcp-034":{"Standard1":["v7 (4.7)","v8 (4.7)"],"CIS GCP Foundations Benchmark":["v1.2.0 (4.2)","v1.3.0 (4.2)","v2.0.0 (4.2)"],"CJIS":["(5.4.3,5.6.3.2)"],"CE":["v2.2 (A5.2,A5.3,Firewalls,Secure_configuration)"],"DA":["(Article_8.3.b)"],"Standard10":["(D3.PC.Am.B.3,D3.PC.Am.B.8)"],"Standard3":["Standard11_800_53_Rev5 (IA-05)"],"Standard15":["(01.b)"],"Standard14":["27001_2013 (A.9.2.2,A.9.2.3)","27002_2013 (9.2.2,9.2.3)","27002_2022 (8.2,8.9)","27017_2015 (9.2.2,9.2.3)","27018_2019 (9.2.2,9.2.3)","27701_2019 (6.6.2.2)"],"Standard12":["(CIP-007-6_Requirement_R5_Part_5.2)"],"Standard11":["800_53 Rev5 (IA-5,IA-5.5)"],"Standard9":["v1.1 (PR.AC-1)"],"Standard8":["v3.2.1 (2.1,2.1.1)","v4.0 (7.2.1,7.2.2)"],"s202":["(CC6.3)"]},"ecc-gcp-216":{"Standard1":["v7 (11.1,11.2)","v8 (4.4,4.5)"],"BSA":["v3 (GS-9,NS-1,NS-2,NS-3,NS-7,NS-8)"],"CJIS":["(5.10.1,5.10.4.3,5.13.1.4,5.13.3,5.13.4.3,5.7.1.1,5.7.1.2)"],"CMMC":["v2.0 (AC.L1-3.1.20,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (APO13)"],"Standard4":["v4 (UEM-10)"],"CE":["v2.2 (A4.1,A4.11,A4.5,A4.7,Firewalls)"],"DA":["(Article_8.3.b,Article_8.4.c)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.2,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,SC-07,SC-07.05)"],"Standard6":["2016_679 (Article_32,Article_5.1.f)"],"Standard15":["(09.m)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (6.7,8.1,8.20)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R2,CIP-005-7_Requirement_R1_Part_1.3,CIP-007-6_Requirement_R1_Part_1.1,CIP-010-4_Requirement_R4)"],"Standard11":["800-171 Rev2 (3.13.1)","800_53 Rev5 (CA-9,SC-7,SC-7.5)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (1.1.4,1.3.1,1.4)","v4.0 (1.2.6)"],"s202":["(CC6.6)"]},"ecc-gcp-453":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1,AM-3)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-gcp-211":{"Standard1":["v7 (9.2)","v8 (4.8)"],"BSA":["v3 (AM-2,AM-5,NS-3,NS-8)"],"CIS GCP Foundations Benchmark":["v1.2.0 (6.3.5)","v1.3.0 (6.3.5)","v2.0.0 (6.3.5)"],"CJIS":["(5.4.3,5.7.1.1)"],"CMMC":["v2.0 (CM.L2-3.4.7,CM.L2-3.4.8,SC.L2-3.13.6)"],"CE":["v2.2 (A5.1,Secure_configuration)"],"Standard10":["(D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CM-06,CM-07)"],"Standard15":["(01.l)"],"Standard14":["27002_2013 (12.5.1)","27002_2022 (8.9)","27017_2015 (12.5.1)","27018_2019 (12.5)","27701_2019 (6.9.5.1)"],"Standard12":["(CIP-007-6_Requirement_R5_Part_5.4)"],"Standard11":["800-171 Rev2 (3.4.7)","800_53 Rev5 (CM-6,CM-7)"],"Standard9":["v1.1 (DE.CM-7)"],"Standard8":["v3.2.1 (1.1.6,1.2.1,2.2.2,2.2.5)","v4.0 (2.2.4)"],"s202":["(CC6.3,CC6.6)"]},"ecc-gcp-135":{"Standard1":["v7 (2.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.2.6)"],"s202":["(CC8.1)"]},"ecc-aws-235":{"Standard1":["v7 (5.1,6.4)","v8 (4.1,8.3)"],"BSA":["v3 (GS-10,GS-5,LT-6,PV-1,PV-3)"],"Standard13":["v1.0.0 (3.1.16)"],"CJIS":["(5.4.6,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AU-04,CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.h,10.k)"],"Standard14":["27001_2013 (A.12.1.3,A.14.2.5)","27002_2013 (12.1.3,14.2.5)","27002_2022 (8.27,8.6,8.9)","27017_2015 (12.1.3,14.2.5)","27018_2019 (12.1.3,14)","27701_2019 (6.11.2.5,6.9.1.3)"],"Standard12":["(CIP-003-8_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (AU-4,CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.DS-4,PR.IP-1)"],"Standard8":["v3.2.1 (10.7,2.2)"],"s202":["(A1.1,CC8.1)"]},"ecc-aws-511":{"Standard1":["v7 (12)","v8 (12.2)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-190":{"Standard1":["v7 (14.6)","v8 (3.3)"],"AWS Config":["(ecs-task-definition-user-for-host-mode-check)"],"AWS Foundational Security Best Practices controls":["(ECS.1)"],"BSA":["v3 (AM-4,IM-7,PA-7)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.3.b,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.3.i,164.308.a.3.ii.A,164.308.a.4.i,164.308.a.4.ii.A,164.308.a.4.ii.B,164.308.a.4.ii.C,164.312.a.1,164.314.b.2.i)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.3)","27017_2015 (8.1.3,9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)"],"s202":["(CC6.1,CC6.3)"]},"ecc-aws-091":{"Standard1":["v7 (3.4)","v8 (7.3)"],"AWS Config":["(ec2-managedinstance-patch-compliance-status-check)"],"AWS Foundational Security Best Practices controls":["(SSM.2)"],"BSA":["v3 (PV-6)"],"CJIS":["(5.10.4.1,5.13.3,5.13.4.1)"],"CMMC":["v2.0 (SI.L1-3.14.1)"],"CE":["v2.2 (A6.4,A6.4.1,Security_update_management)"],"DA":["(Article_8.4.f)"],"Standard10":["(D3.CC.Pa.B.1)"],"Standard3":["Standard11_800_53_Rev5 (RA-05,RA-07,SI-02,SI-02.02)"],"Standard5":["(164.308.a.1.ii.B,164.308.a.5.ii.A,164.308.a.5.ii.B)"],"Standard15":["(10.m)"],"Standard14":["27001_2013 (A.12.6.1)","27002_2013 (12.6.1)","27002_2022 (8.8)","27017_2015 (12.6.1)","27018_2019 (12.6)","27701_2019 (6.9.6.1)"],"Standard12":["(CIP-007-6_Requirement_R2_Part_2.1)"],"Standard11":["800_53 Rev5 (RA-5,RA-7,SI-2,SI-2.2,SI-2.4)"],"Standard8":["v3.2.1 (6.2)"],"s202":["(CC7.1,CC8.1)"]},"ecc-aws-295":{"Standard1":["v7 (14.4)","v8 (12.2,12.6,3.10)"],"AWS Config":["(cloudfront-no-deprecated-ssl-protocols)"],"AWS Foundational Security Best Practices controls":["(CloudFront.10)"],"BSA":["v3 (DP-3,GS-4,NS-2)"],"CJIS":["(5.10.1,5.10.1.2.1,5.13.1.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.13,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,IA.L1-3.5.2,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.2,SC.L2-3.13.8)"],"Standard2":["19 (APO01,APO03,DSS05)"],"Standard4":["v4 (CEK-03,DSP-07,DSP-10,DSP-17,IVS-03)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.2,Article_8.3.a,Article_8.3.c,Article_8.4.d,Article_8.4.b)"],"Standard10":["(D3.PC.Am.B.13,D3.PC.Im.B.1,D3.PC.Im.B.10,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,AC-18,CM-07,PL-08,SA-08,SC-07,SC-08,SC-23)"],"Standard6":["2016_679 (Article_32.1.a,Article_32.1.b,Article_44,Article_5.1.f)"],"Standard5":["(164.312.e.1)"],"Standard15":["(01.i,01.j,01.l,01.m,01.n,01.o,06.c,06.d,09.m,09.s,09.v,09.y,10.d)"],"Standard14":["27001_2013 (A.10.1.1,A.13.1.1,A.13.1.2,A.13.1.3,A.13.2.1,A.18.1.3)","27002_2013 (10.1.1,13.1.1,13.1.2,13.1.3,13.2.1,18.1.3)","27002_2022 (5.14,5.33,8.20,8.21,8.22,8.24,8.27)","27017_2015 (10.1.1,13.1.1,13.1.2,13.1.3,13.2.1,18.1.3)","27018_2019 (10.1.1,13.1,13.2.1,18.1.3)","27701_2019 (6.10.1.1,6.10.1.2,6.10.1.3,6.10.2.1,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3,CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.1.17,3.13.1,3.13.15,3.13.2,3.13.8,3.4.6)","800_53 Rev5 (AC-17.2,AC-18,CM-7,PL-8,PM-7,SA-8,SC-23,SC-7,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.AC-5,PR.DS-2,PR.DS-5,PR.IP-1,PR.PT-3,PR.PT-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.1.1,2.2.2,2.2.5,4.1,4.1.1,8.2.1)"],"s202":["(CC5.2,CC6.1,CC6.7)"]},"ecc-aws-286":{"Standard1":["v7 (1.4,13.2)","v8 (1.1)"],"BSA":["v3 (AM-1,AM-3)"],"CAEUC":["v1.0.0 (2.14)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3,PR.IP-1)"],"s202":["(CC6.1)"]},"ecc-aws-529":{"Standard1":["v7 (5.1)","v8 (4.1)"],"CIS AWS Compute Services Benchmark":["v1.0.0 (2.12)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-493":{"Standard1":["v7 (6.2)","v8 (8.5)"],"AWS Config":["(ecs-container-insights-enabled)"],"AWS Foundational Security Best Practices controls":["(ECS.12)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.5,10.3)"],"s202":["(CC7.2)"]},"ecc-aws-483":{"Standard1":["v7 (14.8)","v8 (3.11)"],"AWS Config":["(codebuild-project-s3-logs-encrypted)"],"AWS Foundational Security Best Practices controls":["(CodeBuild.3)"],"BSA":["v3 (DP-4)"],"CJIS":["(5.4.5)"],"CMMC":["v2.0 (AU.L2-3.3.8)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (LOG-04)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-09)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard15":["(09.ac)"],"Standard14":["27001_2013 (A.12.4.2)","27002_2013 (12.4.2)","27002_2022 (8.15)","27017_2015 (12.4.2)","27018_2019 (12.4.2)","27701_2019 (6.9.4.2)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.3.8)","800_53 Rev5 (AU-9)"],"Standard9":["v1.1 (PR.DS-1,PR.PT-1)"],"Standard8":["v3.2.1 (10.5)"],"s202":["(CC6.1)"]},"ecc-aws-383":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-118":{"Standard1":["v7 (14.6)","v8 (3.3,6.8)"],"BSA":["v3 (AM-4,IM-7,PA-1,PA-7)"],"CJIS":["(5.5.1,5.5.2,5.5.2.1,5.5.2.2,5.5.2.3,5.5.2.4)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.4,AC.L2-3.1.5,MP.L2-3.8.2,SC.L2-3.13.3)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-04,IAM-05,IAM-16)"],"CE":["v2.2 (A7.4,A7.8,A7.9,Secure_configuration,uac)"],"DA":["(Article_8.3.b,Article_14.d)"],"Standard10":["(D1.R.St.B.1,D3.PC.Am.B.1,D3.PC.Am.B.2,D3.PC.Am.B.4)"],"Standard3":["Standard11_800_53_Rev5 (AC-02,AC-02.07,AC-03,AC-05,AC-06,AC-06.01,AC-06.07,AU-9.4,MP-02)"],"Standard6":["2016_679 (Article_25,Article_5.1.f)"],"Standard5":["(164.308.a.3.i,164.308.a.4.i,164.308.a.4.ii.B,164.312.a.1)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.6.1.2,A.8.1.3,A.9,A.9.1.1,A.9.4.1)","27002_2013 (6.1.2,8.1.3,9,9.1.1,9.4.1)","27002_2022 (5.10,5.15,5.3,8.2,8.3)","27017_2015 (6.1.2,8.1.3,9,9.1.1,9.4.1)","27018_2019 (6.1.2,8,9,9.1,9.4.1)","27701_2019 (6.10.1.3,6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-004-6_Requirement_R4_Part_4.3,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.1.4,3.1.5,3.8.2)","800_53 Rev5 (AC-2,AC-2.7,AC-3,AC-3.7,AC-5,AC-6,AC-6.1,AC-6.7,AU-9.4,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-aws-435":{"Standard1":["v7 (14.6,14.8)","v8 (3.11,3.3)"],"BSA":["v3 (AM-4,DP-4,DP-5,IM-7,PA-7)"],"CJIS":["(5.10.1.2.2,5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.1,MP.L2-3.8.2,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03,IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.4.d,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,IA-05.01,MP-02,SC-28)"],"Standard6":["2016_679 (Article_32.1.a,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.a.2.iv)"],"Standard15":["(01.a,01.c,01.v,06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,5.33,8.24,8.3)","27017_2015 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (10.1.1,18.1.3,8,9.1,9.4.1)","27701_2019 (6.15.1.3,6.5.1.3,6.6.1.1,6.6.4.1,6.7.1.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.13.16,3.8.1,3.8.2)","800_53 Rev5 (AC-3,AC-6,IA-5.1,MP-2,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.AC-4,PR.DS-1)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (3.4,3.4.1,7.1,7.1.1,7.1.2,7.1.3,8.2.1)"],"s202":["(CC6.1,CC6.3)"]},"ecc-aws-144":{"Standard1":["v7 (6.2,6.3)","v8 (8.5)"],"AWS Config":["(cloudtrail-s3-dataevents-enabled)"],"CIS AWS Foundations Benchmark":["v1.4.0 (3.11)","v1.5.0 (3.11)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_8.4.e,Article_10.7)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.2,10.2.4,10.2.5,10.3)"],"s202":["(CC7.2)"]},"ecc-aws-414":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-156":{"Standard1":["v7 (14.4,9.2)","v8 (12.6,3.10)"],"AWS Config":["(opensearch-https-required)"],"AWS Foundational Security Best Practices controls":["(ES.8,OpenSearch.8)"],"BSA":["v3 (DP-3)"],"CJIS":["(5.10.1,5.10.1.2.1,5.13.1.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,IA.L1-3.5.2,SC.L1-3.13.1,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IVS-03)"],"DA":["(Article_8.4.b,Article_8.3.a,Article_8.3.c,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.13,D3.PC.Im.B.10)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,AC-18,SC-08,SC-23)"],"Standard6":["2016_679 (Article_32.1.a,Article_32.1.b,Article_44,Article_5.1.f)"],"Standard5":["(164.312.e.1)"],"Standard15":["(01.j,06.c,06.d,09.m,09.s,09.v,09.y,10.d)"],"Standard14":["27001_2013 (A.10.1.1,A.13.1.1,A.13.1.2,A.13.2.1,A.18.1.3)","27002_2013 (10.1.1,13.1.1,13.1.2,13.2.1,18.1.3)","27002_2022 (5.14,5.33,8.20,8.21,8.24)","27017_2015 (10.1.1,13.1.1,13.1.2,13.2.1,18.1.3)","27018_2019 (10.1.1,13.1,13.2.1,18.1.3)","27701_2019 (6.10.1.1,6.10.1.2,6.10.2.1,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.1.17,3.13.1,3.13.15,3.13.8)","800_53 Rev5 (AC-17.2,AC-18,SC-23,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-2,PR.DS-5,PR.PT-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1.1,4.1,4.1.1,8.2.1)"],"s202":["(CC5.2,CC6.1,CC6.7)"]},"ecc-aws-363":{"Standard1":["v7 (14.6,14.8)","v8 (3.11,3.3)"],"BSA":["v3 (AM-4,DP-4,DP-5,IM-7,PA-7)"],"CJIS":["(5.10.1.2.2,5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.1,MP.L2-3.8.2,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03,IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.4.d,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,IA-05.01,MP-02,SC-28)"],"Standard6":["2016_679 (Article_32.1.a,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.a.2.iv)"],"Standard15":["(01.a,01.c,01.v,06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,5.33,8.24,8.3)","27017_2015 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (10.1.1,18.1.3,8,9.1,9.4.1)","27701_2019 (6.15.1.3,6.5.1.3,6.6.1.1,6.6.4.1,6.7.1.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.13.16,3.8.1,3.8.2)","800_53 Rev5 (AC-3,AC-6,IA-5.1,MP-2,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.AC-4,PR.DS-1)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (3.4,3.4.1,7.1,7.1.1,7.1.2,7.1.3,8.2.1)"],"s202":["(CC6.1,CC6.3)"]},"ecc-aws-229":{"Standard1":["v7 (14.6,14.8)","v8 (3.11,3.3)"],"BSA":["v3 (AM-4,DP-4,DP-5,IM-7,PA-7)"],"CJIS":["(5.10.1.2.2,5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.1,MP.L2-3.8.2,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03,IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.4.d,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,IA-05.01,MP-02,SC-28)"],"Standard6":["2016_679 (Article_32.1.a,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.a.2.iv)"],"Standard15":["(01.a,01.c,01.v,06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,5.33,8.24,8.3)","27017_2015 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (10.1.1,18.1.3,8,9.1,9.4.1)","27701_2019 (6.15.1.3,6.5.1.3,6.6.1.1,6.6.4.1,6.7.1.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.13.16,3.8.1,3.8.2)","800_53 Rev5 (AC-3,AC-6,IA-5.1,MP-2,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.AC-4,PR.DS-1)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (3.4,3.4.1,7.1,7.1.1,7.1.2,7.1.3,8.2.1)"],"s202":["(CC6.1,CC6.3)"]},"ecc-aws-296":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Security_update_management)"],"DA":["(Article_8.2,Article_8.4.a)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-313":{"Standard1":["v7 (14.6,14.8)","v8 (3.11,3.3)"],"BSA":["v3 (AM-4,DP-4,DP-5,IM-7,PA-7)"],"CJIS":["(5.10.1.2.2,5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.1,MP.L2-3.8.2,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03,IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.4.d,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,IA-05.01,MP-02,SC-28)"],"Standard6":["2016_679 (Article_32.1.a,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.a.2.iv)"],"Standard15":["(01.a,01.c,01.v,06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,5.33,8.24,8.3)","27017_2015 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (10.1.1,18.1.3,8,9.1,9.4.1)","27701_2019 (6.15.1.3,6.5.1.3,6.6.1.1,6.6.4.1,6.7.1.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.13.16,3.8.1,3.8.2)","800_53 Rev5 (AC-3,AC-6,IA-5.1,MP-2,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.AC-4,PR.DS-1)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (3.4,3.4.1,7.1,7.1.1,7.1.2,7.1.3,8.2.1)"],"s202":["(CC6.1,CC6.3)"]},"ecc-aws-378":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-302":{"Standard1":["v7 (5.1,6.2)","v8 (4.1,8.2)"],"BSA":["v3 (DS-7,GS-10,GS-5,LT-3,LT-4,PV-1,PV-3)"],"CJIS":["(5.4.1,5.7.1)"],"CMMC":["v2.0 (AU.L2-3.3.1,CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10,DSS03)"],"Standard4":["v4 (CCC-01,IVS-04,LOG-08)"],"CE":["v2.2 (Secure_configuration)"],"Standard10":["(D1.G.SP.B.2,D2.MA.Ma.B.1,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-12,CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_30,Article_32)"],"Standard5":["(164.312.b)"],"Standard15":["(08.k,09.aa,10.k)"],"Standard14":["27001_2013 (A.12.4.1,A.14.2.5)","27002_2013 (12.4.1,14.2.5)","27002_2022 (8.15,8.27,8.9)","27017_2015 (12.4.1,14.2.5)","27018_2019 (12.4.1,14)","27701_2019 (6.11.2.5,6.9.4.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-007-6_Requirement_R4_Part_4.1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.3.1,3.4.1,3.4.2)","800_53 Rev5 (AU-12,AU-2,CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (DE.AE-3,PR.IP-1,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.2,10.3,2.2)"],"s202":["(CC7.2,CC8.1)"]},"ecc-aws-342":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (PV-1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (BAI06,BAI10)"],"Standard4":["v4 (TVM-01)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32.1)"],"Standard15":["(10.k)"],"Standard14":["27001_2013 (A.12.1.3,A.14.2.5)","27002_2013 (12.1.3,14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (12.1.3,14.2.5)","27018_2019 (12.1.3,14)","27701_2019 (6.9.1.3,6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-2,CM-6,CM-9,SA-3,SA-8,SA-10)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-187":{"Standard1":["v7 (12)","v8 (12.2)"],"AWS Foundational Security Best Practices controls":["(EC2.10)"],"BSA":["v3 (GS-4,NS-2,NS-9)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L2-3.1.14,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.14,3.13.2)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.3.1)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-245":{"Standard1":["v7 (6.3)","v8 (4.1,8.5)"],"BSA":["v3 (DS-7,GS-10,GS-5,LT-3,LT-4,PV-1,PV-3)"],"CJIS":["(5.4.1,5.7.1)"],"CMMC":["v2.0 (AU.L2-3.3.1,CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04,LOG-08)"],"CE":["v2.2 (Secure_configuration)"],"Standard10":["(D1.G.SP.B.2,D2.MA.Ma.B.1,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12,CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_30,Article_32)"],"Standard15":["(08.k,09.aa,10.k)"],"Standard14":["27001_2013 (A.12.4.1,A.14.2.5)","27002_2013 (12.4.1,14.2.5)","27002_2022 (5.28,8.15,8.27,8.9)","27017_2015 (12.4.1,14.2.5)","27018_2019 (12.4.1,14)","27701_2019 (6.11.2.5,6.9.4.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (AU-12,AU-3,AU-3.1,CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (DE.AE-3,PR.IP-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.5,10.3,2.2)"],"s202":["(CC7.2,CC8.1)"]},"ecc-aws-101":{"Standard1":["v7 (12)","v8 (12.2)"],"AWS Config":["(subnet-auto-assign-public-ip-disabled)"],"AWS Foundational Security Best Practices controls":["(EC2.15)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.308.a.3.i,164.308.a.4.ii.A,164.308.a.4.ii.C,164.312.a.1,164.312.e.1)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-189":{"Standard1":["v7 (12)","v8 (12.2)"],"AWS Config":["(ec2-instance-multiple-eni-check)"],"AWS Foundational Security Best Practices controls":["(EC2.17)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.20,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-465":{"Standard1":["v7 (10.1)","v8 (11.2)"],"BSA":["v3 (BR-1)"],"Standard4":["v4 (BCR-08)"],"CE":["v2.2 (Back_up_your_data)"],"DA":["(Article_11.2,Article_11.4)"],"Standard10":["(D5.IR.Pl.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CP-09,CP-10)"],"Standard6":["2016_679 (Article_32.1.c)"],"Standard5":["(164.308.a.7.ii.A)"],"Standard15":["(09.l)"],"Standard14":["27001_2013 (A.12.3.1)","27002_2013 (12.3.1)","27002_2022 (8.13)","27017_2015 (12.3.1)","27018_2019 (12.3.1)","27701_2019 (6.9.3.1)"],"Standard12":["(CIP-009-6_Requirement_R1_Part_1.3)"],"Standard11":["800_53 Rev5 (CP-10,CP-9)"],"Standard9":["v1.1 (PR.IP-4)"],"Standard7":["(6s)"],"Standard8":["v3.2.1 (12.10.1)"],"s202":["(A1.2)"]},"ecc-aws-572":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800_53 Rev5 (CM-8,CM-8.1)"],"Standard9":["v1.1 (PR.DS-3)"]},"ecc-aws-506":{"Standard1":["v7 (5.1)","v8 (4.1)"],"AWS Config":["(redshift-default-db-name-check)"],"AWS Foundational Security Best Practices controls":["(Redshift.9)"],"BSA":["v3 (GS-10,GS-5,NS-8,PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.3.c,Article_8.4.a,Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-07,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32.1.b)"],"Standard15":["(08.k,10.k,10.m)"],"Standard14":["27001_2013 (A.13.1.2,A.9.1.1,A.9.4.1)","27002_2013 (13.1.2,9.1.1,9.4.1)","27002_2022 (5.10,8.21,8.3)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-7,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC6.6)"]},"ecc-aws-214":{"Standard1":["v7 (14.4)","v8 (3.10)"],"AWS Config":["(redshift-require-tls-ssl)"],"AWS Foundational Security Best Practices controls":["(Redshift.2)"],"BSA":["v3 (DP-3)"],"CJIS":["(5.10.1.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IVS-03)"],"DA":["(Article_8.2,Article_8.3.a,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.13)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,SC-08)"],"Standard6":["2016_679 (Article_32.1.a,Article_32.1.b,Article_44)"],"Standard5":["(164.308.a.1.ii.B,164.312.a.2.iv,164.312.e.1,164.312.e.2.i,164.312.e.2.ii,164.314.b.1,164.314.b.2,164.314.b.2.i)"],"Standard15":["(06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1,A.18.1.3)","27002_2013 (10.1.1,13.2.1,18.1.3)","27002_2022 (5.14,5.33,8.24)","27017_2015 (10.1.1,13.2.1,18.1.3)","27018_2019 (10.1.1,13.2.1,18.1.3)","27701_2019 (6.10.2.1,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.8)","800_53 Rev5 (AC-17.2,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-2)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1.1,4.1,4.1.1,8.2.1)"],"s202":["(CC6.1,CC6.7)"]},"ecc-aws-153":{"Standard1":["v7 (6.2,6.3)","v8 (8.2,8.5)"],"AWS Config":["(opensearch-audit-logging-enabled)"],"AWS Foundational Security Best Practices controls":["(ES.5,OpenSearch.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_8.4.e,Article_10.7)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.2,10.2.4,10.2.5,10.3)"],"s202":["(CC7.2)"]},"ecc-aws-083":{"Standard1":["v7 (18.10)","v8 (4.4)"],"AWS Config":["(cloudfront-associated-with-waf,fms-shield-resource-policy-check,fms-webacl-resource-policy-check)"],"AWS Foundational Security Best Practices controls":["(CloudFront.6)"],"BSA":["v3 (GS-9,NS-1,NS-2,NS-3,NS-7,NS-8)"],"CJIS":["(5.10.1,5.7.1.2)"],"CMMC":["v2.0 (AC.L1-3.1.20,CM.L2-3.4.7,SC.L1-3.13.1,SC.L2-3.13.6)"],"Standard2":["19 (APO13)"],"Standard4":["v4 (UEM-10)"],"CE":["v2.2 (A4.11,A4.5,A4.7,Firewalls)"],"DA":["(Article_8.3.b,Article_8.4.b,Article_8.4.c,Article_9.1)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.2,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,SC-07,SC-07.05)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard15":["(09.m)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.20)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard11":["800-171 Rev2 (3.13.1)","800_53 Rev5 (CA-9,SC-7,SC-7.5)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (1.1.4,1.3.1)"],"s202":["(CC6.6)"]},"ecc-aws-122":{"Standard1":["v7 (14.8)","v8 (3.11,3.3)"],"AWS Config":["(dynamodb-table-encrypted-kms)"],"BSA":["v3 (AM-4,DP-4,DP-5,IM-7,PA-7)"],"CJIS":["(5.10.1.2.2,5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.1,MP.L2-3.8.2,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03,IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.4.d,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02,SC-28)"],"Standard6":["2016_679 (Article_32.1.a,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.a.2.iv)"],"Standard15":["(01.a,01.c,01.v,06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3,A.9.1.1,A.9.4.1)","27002_2013 (10.1.1,18.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,5.33,8.24,8.3)","27017_2015 (10.1.1,18.1.3,9.1.1,9.4.1)","27018_2019 (10.1.1,18.1.3,9.1,9.4.1)","27701_2019 (6.15.1.3,6.6.1.1,6.6.4.1,6.7.1.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.13.16,3.8.1,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.AC-4,PR.DS-1)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (3.4,3.4.1,7.1,7.1.1,7.1.2,7.1.3,8.2.1)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-aws-265":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-147":{"Standard1":["v7 (14.8)","v8 (3.11)"],"AWS Config":["(encrypted-volumes)"],"AWS Foundational Security Best Practices controls":["(EC2.3)"],"BSA":["v3 (DP-4,DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.308.a.1.ii.B,164.312.a.2.iv,164.312.e.2.ii,164.314.b.1,164.314.b.2,164.314.b.2.i)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)"],"s202":["(CC6.1)"]},"ecc-aws-129":{"Standard1":["v7 (6.2,6.3)","v8 (3.14,8.2,8.5)"],"AWS Config":["(elb-logging-enabled)"],"AWS Foundational Security Best Practices controls":["(ELB.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (DSP-17,LOG-04,LOG-08)"],"DA":["(Article_10.7,Article_8.3.d,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-06.09,AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AC-6.9,AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.1,10.2.2,10.2.4,10.2.5,10.3,11.5)"],"s202":["(CC6.1,CC7.2)"]},"ecc-aws-401":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-062":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,13.4)"],"CIS AWS Foundations Benchmark":["v1.4.0 (5.2)","v1.5.0 (5.2,5.3)"],"AWS Foundational Security Best Practices controls":["(EC2.13)"],"AWS Config":["(restricted-ssh)"],"BSA":["v3 (GS-4,NS-1,NS-2)"],"CJIS":["(5.10.1,5.13.1.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.13,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,IA.L1-3.5.2,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.15,SC.L2-3.13.2,SC.L2-3.13.6)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.10,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CA-09,CM-07,PL-08,SA-08,SC-07,SC-08,SC-23)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.312.e.1)"],"Standard15":["(01.i,01.j,01.l,01.m,01.n,01.o,09.m,09.n,09.s,09.y,10.d)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.2,A.13.1.3)","27002_2013 (13.1.1,13.1.2,13.1.3)","27002_2022 (8.16,8.20,8.21,8.22,8.27)","27017_2015 (13.1.1,13.1.2,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.2,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.17,3.1.5,3.13.1,3.13.15,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (AC-18,CA-9,CM-7,PL-8,PM-7,SA-8,SC-23,SC-7,SC-8)"],"Standard9":["v1.1 (PR.AC-5,PR.DS-2,PR.DS-5,PR.IP-1,PR.PT-3,PR.PT-4)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5,4.1.1)"],"s202":["(CC5.2,CC6.1,CC6.6)"]},"ecc-aws-250":{"Standard1":["v7 (5.1)","v8 (4.1)"],"AWS Config":["(api-gw-cache-enabled-and-encrypted)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"s202":["(CC8.1)"]},"ecc-aws-188":{"Standard1":["v7 (11,14)","v8 (12.2)"],"AWS Config":["(vpc-network-acl-unused-check)"],"AWS Foundational Security Best Practices controls":["(EC2.16)"],"BSA":["v3 (GS-4,NS-2,NS-3)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.7)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard11":["800-171 Rev2 (3.13.2)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.DS-3,PR.IP-6)"]},"ecc-aws-004":{"Standard1":["v7 (14.4)","v8 (3.10)"],"CIS AWS Foundations Benchmark":["v1.4.0 (2.1.2)","v1.5.0 (2.1.2)"],"AWS Config":["(s3-bucket-ssl-requests-only)"],"AWS Foundational Security Best Practices controls":["(S3.5)"],"BSA":["v3 (DP-3)"],"CJIS":["(5.10.1.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IVS-03)"],"DA":["(Article_8.2,Article_8.3.a,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12,D3.PC.Am.B.13)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,SC-08)"],"Standard6":["2016_679 (Article_32.1.a,Article_44)"],"Standard5":["(164.308.a.1.ii.B,164.312.a.2.iv,164.312.c.2,164.312.e.1,164.312.e.2.i,164.312.e.2.ii,164.314.b.1,164.314.b.2,164.314.b.2.i)"],"Standard15":["(06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1,A.18.1.3)","27002_2013 (10.1.1,13.2.1,18.1.3)","27002_2022 (5.14,5.33,8.24)","27017_2015 (10.1.1,13.2.1,18.1.3)","27018_2019 (10.1.1,13.2.1,18.1.3)","27701_2019 (6.10.2.1,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.8,3.5.10)","800_53 Rev5 (AC-17.2,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-2)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1.1,4.1,4.1.1,8.2.1)"],"s202":["(CC6.1,CC6.7)"]},"ecc-aws-332":{"Standard1":["v7 (11.1,12.2,5.1)","v8 (13.5,4.2)"],"BSA":["v3 (GS-5,IM-7,PA-6,PV-1)"],"CAEUC":["v1.0.0 (2.6)"],"CJIS":["(5.10.1,5.10.1.1)"],"CMMC":["v2.0 (AC.L2-3.1.12,AC.L2-3.1.14,CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (HRS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.3.c,Article_14.d,,Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-17,AC-17.01,AC-18,CM-02,CM-06,CM-07,CM-07.01,SC-07)"],"Standard6":["2016_679 (Article_32)"],"Standard5":["(164.308.a.4.ii.C)"],"Standard15":["(01.y,09.m,09.n)"],"Standard14":["27001_2013 (A.13.1.2,A.9.1.2)","27002_2013 (13.1.2,9.1.2)","27002_2022 (6.7,8.1,8.21,8.3,8.9)","27017_2015 (13.1.2,9.1.2)","27018_2019 (13.1,9.1)","27701_2019 (6.10.1.2,6.6.1.2)"],"Standard12":["(CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.1.12,3.4.2)","800_53 Rev5 (AC-17,AC-17.1,AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9,SC-7)"],"Standard9":["v1.1 (PR.AC-3,PR.IP-1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-aws-040":{"Standard1":["v7 (5.1)","v8 (4.1)"],"AWS Config":["(eks-cluster-supported-version)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.f,Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-347":{"Standard1":["v7 (14.6,14.8)","v8 (3.11,3.3)"],"BSA":["v3 (AM-4,DP-4,DP-5,IM-7,PA-7)"],"CJIS":["(5.10.1.2.2,5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.1,MP.L2-3.8.2,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03,IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.4.d,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,IA-05.01,MP-02,SC-28)"],"Standard6":["2016_679 (Article_32.1.a,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.a.2.iv)"],"Standard15":["(01.a,01.c,01.v,06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,5.33,8.24,8.3)","27017_2015 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (10.1.1,18.1.3,8,9.1,9.4.1)","27701_2019 (6.15.1.3,6.5.1.3,6.6.1.1,6.6.4.1,6.7.1.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.13.16,3.8.1,3.8.2)","800_53 Rev5 (AC-3,AC-6,IA-5.1,MP-2,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.AC-4,PR.DS-1)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (3.4,3.4.1,7.1,7.1.1,7.1.2,7.1.3,8.2.1)"],"s202":["(CC6.1,CC6.3)"]},"ecc-aws-269":{"Standard1":["v7 (14)","v8 (12.2)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.7,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03,IVS-06)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.13.5,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-280":{"Standard1":["v7 (14.6,14.8)","v8 (3.11,3.3)"],"AWS Config":["(opensearch-encrypted-at-rest,elasticsearch-encrypted-at-rest)"],"BSA":["v3 (AM-4,DP-4,DP-5,IM-7,PA-7)"],"CJIS":["(5.10.1.2.2,5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.1,MP.L2-3.8.2,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03,IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.4.d,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,IA-05.01,MP-02,SC-28)"],"Standard6":["2016_679 (Article_32.1.a,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.a.2.iv)"],"Standard15":["(01.a,01.c,01.v,06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,5.33,8.24,8.3)","27017_2015 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (10.1.1,18.1.3,8,9.1,9.4.1)","27701_2019 (6.15.1.3,6.5.1.3,6.6.1.1,6.6.4.1,6.7.1.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.13.16,3.8.1,3.8.2)","800_53 Rev5 (AC-3,AC-6,IA-5.1,MP-2,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.AC-4,PR.DS-1)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (3.4,3.4.1,7.1,7.1.1,7.1.2,7.1.3,8.2.1)"],"s202":["(CC6.1,CC6.3)"]},"ecc-aws-534":{"Standard1":["v7 (5.1)","v8 (4.1)"],"AWS Foundational Security Best Practices controls":["(AutoScaling.9)"],"AWS Config":["(autoscaling-launch-template)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard4":["v4 (AIS-06)"],"Standard3":["Standard11_800_53_Rev5 (CA-07,SA-03)"],"Standard15":["(06.c)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard11":["800-171 Rev2 (3.4.1)","800_53 Rev5 (CA-7,SA-3)"],"s202":["(CC8.1)"]},"ecc-aws-531":{"Standard1":["v7 (14.8)","v8 (3.11)"],"CIS AWS Foundations Benchmark":["v1.4.0 (2.2.1)","v1.5.0 (2.2.1)"],"AWS Config":["(ec2-ebs-encryption-by-default)"],"AWS Foundational Security Best Practices controls":["(EC2.7)"],"CIS AWS Compute Services Benchmark":["v1.0.0 (2.2.1)"],"BSA":["v3 (DP-4,DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.308.a.1.ii.B,164.312.a.2.iv,164.312.e.2.ii,164.314.b.1,164.314.b.2,164.314.b.2.i)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)"],"s202":["(CC6.1)"]},"ecc-aws-471":{"Standard1":["v7 (5.1)","v8 (4.1)"],"AWS Config":["(autoscaling-capacity-rebalancing)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (IVS-02)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,CP-06,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k,12.c)"],"Standard14":["27001_2013 (A.14.2.5,A.17.2.1)","27002_2013 (14.2.5,17.2.1)","27002_2022 (8.14,8.27,8.9)","27017_2015 (14.2.5,17.2.1)","27018_2019 (14,17)","27701_2019 (6.11.2.5,6.14.2.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,CP-6,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR-PT-5,PR.IP-1)"],"s202":["(CC8.1)"]},"ecc-aws-498":{"Standard1":["v7 (5.1)","v8 (4.1)"],"AWS Config":["(elbv2-multiple-az)"],"AWS Foundational Security Best Practices controls":["(ELB.13)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (IVS-02)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.2,Article_8.4.a,Article_11.5)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,CP-06,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k,12.c)"],"Standard14":["27001_2013 (A.14.2.5,A.17.2.1)","27002_2013 (14.2.5,17.2.1)","27002_2022 (8.14,8.27,8.9)","27017_2015 (14.2.5,17.2.1)","27018_2019 (14,17)","27701_2019 (6.11.2.5,6.14.2.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,CP-6,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR-PT-5,PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-447":{"Standard1":["v7 (6.2,6.3)","v8 (4.1,8.2,8.5)"],"BSA":["v3 (DS-7,GS-10,GS-5,LT-3,LT-4,PV-1,PV-3)"],"Standard13":["v1.0.0 (3.1.20)"],"CJIS":["(5.4.1,5.7.1)"],"CMMC":["v2.0 (AU.L2-3.3.1,CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10,DSS03)"],"Standard4":["v4 (CCC-01,IVS-04,LOG-08)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.e,Article_10.7)"],"Standard10":["(D1.G.SP.B.2,D2.MA.Ma.B.1,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-03,AU-12,CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_30,Article_32)"],"Standard5":["(164.312.b)"],"Standard15":["(08.k,09.aa,10.k)"],"Standard14":["27001_2013 (A.12.4.1,A.14.2.5)","27002_2013 (12.4.1,14.2.5)","27002_2022 (5.28,8.15,8.27,8.9)","27017_2015 (12.4.1,14.2.5)","27018_2019 (12.4.1,14)","27701_2019 (6.11.2.5,6.9.4.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-007-6_Requirement_R4_Part_4.1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.3.1,3.4.1,3.4.2)","800_53 Rev5 (AU-12,AU-2,AU-3,AU-3.1,CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (DE.AE-3,PR.IP-1,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.5,10.3,2.2)"],"s202":["(CC7.2,CC8.1)"]},"ecc-aws-395":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1,AM-3)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-548":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-277":{"Standard1":["v7 (6.2,6.3)","v8 (8.2,8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_8.4.e,Article_10.7)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.5,10.3)"],"s202":["(CC7.2)"]},"ecc-aws-204":{"Standard1":["v7 (6.2,6.3)","v8 (3.14,8.2,8.5)"],"AWS Config":["(rds-logging-enabled)"],"AWS Foundational Security Best Practices controls":["(RDS.9)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.3.d,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-06.09,AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.308.a.3.ii.A,164.308.a.5.ii.C,164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AC-6.9,AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.1,10.2.5,10.3)"],"s202":["(CC6.1,CC7.2)"]},"ecc-aws-539":{"Standard1":["v7 (14.6,5.1)","v8 (3.3,4.2)"],"AWS Config":["(cloudfront-s3-origin-access-control-enabled)"],"AWS Foundational Security Best Practices controls":["(CloudFront.13)"],"BSA":["v3 (AM-4,GS-10,GS-5,IM-7,PA-7,PV-1)"],"CJIS":["(5.10.1,5.5.2,5.7.1)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,MP.L2-3.8.2)"],"Standard2":["19 (BAI10,DSS05)"],"Standard4":["v4 (IAM-05,IVS-04)"],"CE":["v2.2 (A4.8,A7.4,Firewalls,Secure_configuration,uac)"],"DA":["(Article_8.3.b,Article_8.3.c,Article_8.4.b,Article_8.4.c)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09,MP-02)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.v,09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (13.1.2,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.21,8.3,8.9)","27017_2015 (13.1.2,8.1.3,9.1.1,9.4.1)","27018_2019 (13.1,8,9.1,9.4.1)","27701_2019 (6.10.1.2,6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-004-6_Requirement_R4_Part_4.1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.4.2,3.8.2)","800_53 Rev5 (AC-18,AC-3,AC-6,CM-2,CM-6,CM-7,CM-7.1,CM-9,MP-2)"],"Standard9":["v1.1 (PR.AC-4,PR.IP-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)","v4.0 (1.3.1,7.1.1,1.2.1,1.2.6,1.4.2,1.5.1,2.1.1,2.2.1)"],"s202":["(CC6.1,CC6.3,CC6.6)"]},"ecc-aws-501":{"Standard1":["v7 (14.6,5.1)","v8 (3.3,4.1)"],"AWS Config":["(opensearch-access-control-enabled)"],"AWS Foundational Security Best Practices controls":["(OpenSearch.7)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CJIS":["(5.7.1,5.8.3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,MP.L1-3.8.3)"],"Standard2":["19 (APO13,APO14,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,DCS-01,DSP-02,DSP-16,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.3.d,Article_8.4.c,Article_14.d)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Am.B.18,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,MP-06,SA-03,SA-08,SA-10,SI-12,SR-12)"],"Standard6":["2016_679 (Article_32)"],"Standard5":["(164.310.d.2.i)"],"Standard15":["(08.k,10.k,13.l)"],"Standard14":["27001_2013 (A.11.2.7,A.14.2.5,A.8.1.3,A.8.3.2)","27002_2013 (11.2.7,14.2.5,8.1.3,8.3.2)","27002_2022 (5.10,7.10,7.14,8.27,8.9)","27017_2015 (11.2.7,14.2.5,8.1.3,8.3.2)","27018_2019 (11.2.7,14,8)","27701_2019 (6.11.2.5,6.5.1.3,6.5.3.2,6.8.2.7)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1,CIP-011-3_Requirement_R2_Part_2.1,CIP-011-3_Requirement_R2_Part_2.2)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2,3.8.3)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,MP-6,SA-10,SA-3,SA-8,SI-12,SR-12)"],"Standard9":["v1.1 (PR.DS-3,PR.IP-1,PR.IP-6)"],"Standard7":["(4)"],"Standard8":["v3.2.1 (2.2,3.1)"],"s202":["(C1.2,CC6.5,CC8.1)"]},"ecc-aws-573":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800_53 Rev5 (CM-8,CM-8.1)"],"Standard9":["v1.1 (PR.DS-3)"]},"ecc-aws-186":{"Standard1":["v7 (12)","v8 (12.2,3.3)"],"AWS Config":["(ec2-instance-no-public-ip)"],"AWS Foundational Security Best Practices controls":["(EC2.9)"],"BSA":["v3 (GS-4,IM-7,NS-2)"],"CJIS":["(5.10.1,5.5.2,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L1-3.1.22,AC.L2-3.1.5,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,MP.L2-3.8.2,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03,DSS05)"],"Standard4":["v4 (DSP-07,IAM-05,IVS-03)"],"CE":["v2.2 (A4.8,A7.4,Firewalls,Secure_configuration,uac)"],"DA":["(Article_8.3.b,Article_14.d)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,CM-07,MP-02,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.308.a.1.ii.B,164.308.a.3.i,164.308.a.4.i,164.308.a.4.ii.A,164.308.a.4.ii.B,164.308.a.4.ii.C,164.312.a.1,164.312.e.1)"],"Standard15":["(01.a,01.c,01.i,01.l,01.m,01.n,01.o,01.v,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (13.1.1,13.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.20,8.22,8.3,8.27)","27017_2015 (13.1.1,13.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (13.1,8,9.1,9.4.1)","27701_2019 (6.10.1.1,6.10.1.3,6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.13.2,3.4.6,3.8.2)","800_53 Rev5 (AC-3,AC-6,CM-7,MP-2,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-4,PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (1.3.6,2.2.2)"],"s202":["(CC6.1,CC6.3,CC6.6)"]},"ecc-aws-485":{"Standard1":["v7 (5.1)","v8 (4.1)"],"AWS Config":["(codedeploy-ec2-minimum-healthy-hosts-configured)"],"BSA":["v3 (GS-5,PV-1)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI06)"],"Standard4":["v4 (CCC-01)"],"DA":["(Article_8.2)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,SA-03)"],"Standard15":["(10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1)","800_53 Rev5 (CM-2,CM-6,SA-3)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-263":{"Standard1":["v7 (9.1)","v8 (1.1,12.2)"],"BSA":["v3 (AM-1,AM-3,GS-4,NS-2)"],"CJIS":["(5.10.1,5.4)"],"CMMC":["v2.0 (AC.L1-3.1.20,CM.L2-3.4.1,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03,BAI09)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (Secure_configuration)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,CM-08,CM-08.01,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_30,Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,07.a,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3,A.8.1.1)","27002_2013 (13.1.1,13.1.3,8.1.1)","27002_2022 (5.9,8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3,8.1.1)","27018_2019 (13.1,8)","27701_2019 (6.10.1.1,6.10.1.3,6.5.1.1)"],"Standard11":["800-171 Rev2 (3.13.2)","800_53 Rev5 (CM-7,CM-8,CM-8.1,PL-8,PM-5,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (ID.AM-1,PR.AC-5,PR.DS-3,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,2.2.2,2.2.5)"]},"ecc-aws-552":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800_53 Rev5 (CM-8,CM-8.1)"],"Standard9":["v1.1 (PR.DS-3)"]},"ecc-aws-032":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,13.4)"],"BSA":["v3 (GS-4,NS-1,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.2,SC.L2-3.13.6)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m,09.n)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.16,8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.1,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CA-9,CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-337":{"Standard1":["v7 (14.6,14.8)","v8 (3.11,3.3)"],"BSA":["v3 (AM-4,DP-4,DP-5,IM-7,PA-7)"],"CJIS":["(5.10.1.2.2,5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.1,MP.L2-3.8.2,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03,IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.4.d,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,IA-05.01,MP-02,SC-28)"],"Standard6":["2016_679 (Article_32.1.a,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.a.2.iv)"],"Standard15":["(01.a,01.c,01.v,06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,5.33,8.24,8.3)","27017_2015 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (10.1.1,18.1.3,8,9.1,9.4.1)","27701_2019 (6.15.1.3,6.5.1.3,6.6.1.1,6.6.4.1,6.7.1.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.13.16,3.8.1,3.8.2)","800_53 Rev5 (AC-3,AC-6,IA-5.1,MP-2,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.AC-4,PR.DS-1)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (3.4,3.4.1,7.1,7.1.1,7.1.2,7.1.3,8.2.1)"],"s202":["(CC6.1,CC6.3)"]},"ecc-aws-439":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-050":{"Standard1":["v7 (4.4)","v8 (5.2)"],"CIS AWS Foundations Benchmark":["v1.2.0 (1.6)"],"AWS Config":["(iam-password-policy)"],"AWS Foundational Security Best Practices controls":["(IAM.15)"],"CJIS":["(5.6.2.1.1)"],"CMMC":["v2.0 (IA.L2-3.5.7)"],"Standard4":["v4 (IAM-02)"],"CE":["v2.2 (Password_based_Authentication)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.7)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01)"],"Standard5":["(164.308.a.4.ii.B,164.308.a.5.ii.D,164.312.d,164.314.b.2.i)"],"Standard15":["(01.f)"],"Standard14":["27001_2013 (A.9.4.3)","27002_2013 (9.4.3)","27002_2022 (5.17)","27017_2015 (9.4.3)","27018_2019 (9.4.3)","27701_2019 (6.6.4.3)"],"Standard12":["(CIP-007-6_Requirement_R5_Part_5.5)"],"Standard11":["800-171 Rev2 (3.5.7)","800_53 Rev5 (IA-5.1)"],"Standard8":["v3.2.1 (2.1,8.2.3,8.2.6)"],"s202":["(CC6.1)"]},"ecc-aws-406":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-230":{"Standard1":["v7 (3.1)","v8 (16.2,7.5)"],"AWS Config":["(ecr-private-image-scanning-enabled)"],"AWS Foundational Security Best Practices controls":["(ECR.1)"],"BSA":["v3 (DS-6,GS-10,PV-5)"],"CJIS":["(5.10.1.3)"],"CMMC":["v2.0 (RA.L2-3.11.2,SI.L1-3.14.1,SI.L1-3.14.5)"],"Standard2":["19 (DSS03,DSS05)"],"Standard4":["v4 (AIS-03,AIS-07,TVM-07)"],"DA":["(Article_7.2,Article_8.1,Article_8.2)"],"Standard10":["(D3.CC.Re.B.1,D3.DC.Th.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CA-05,RA-01,RA-05,RA-07,SI-02)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(10.m)"],"Standard14":["27001_2013 (A.12.6.1)","27002_2013 (12.6.1)","27002_2022 (8.8)","27017_2015 (12.6.1)","27018_2019 (12.6)","27701_2019 (6.9.6.1)"],"Standard11":["800-171 Rev2 (3.11.2)","800_53 Rev5 (CA-5,RA-1,RA-5,RA-7,SI-2)"],"Standard9":["v1.1 (DE.CM-8,RS.AN-5)"],"Standard7":["(6,6)"],"s202":["(CC7.1)"]},"ecc-aws-171":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,13.4)"],"AWS Foundational Security Best Practices controls":["(EC2.19)"],"BSA":["v3 (GS-4,NS-1,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.1,SC.L2-3.13.2,SC.L2-3.13.6)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m,09.n)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.16,8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3,CIP-007-6_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.1,3.13.2,3.13.6,3.4.6)","800_53 Rev5 (CA-9,CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.3.6,2.2.2)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-267":{"Standard1":["v7 (14.4)","v8 (3.10)"],"AWS Foundational Security Best Practices controls":["(ElastiCache.5)"],"AWS Config":["(elasticache-repl-grp-encrypted-in-transit)"],"BSA":["v3 (DP-3)"],"CJIS":["(5.10.1.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IVS-03)"],"DA":["(Article_8.2,Article_8.3.a,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.13)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,SC-08)"],"Standard6":["2016_679 (Article_32.1.a,Article_44)"],"Standard5":["(164.312.e.1)"],"Standard15":["(06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1,A.18.1.3)","27002_2013 (10.1.1,13.2.1,18.1.3)","27002_2022 (5.14,5.33,8.24)","27017_2015 (10.1.1,13.2.1,18.1.3)","27018_2019 (10.1.1,13.2.1,18.1.3)","27701_2019 (6.10.2.1,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.8)","800_53 Rev5 (AC-17.2,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-2)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1.1,4.1,4.1.1,8.2.1)"],"s202":["(CC6.1,CC6.7)"]},"ecc-aws-031":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,13.4)"],"AWS Foundational Security Best Practices controls":["(EC2.19)"],"BSA":["v3 (GS-4,NS-1,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.2,SC.L2-3.13.6)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m,09.n)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.16,8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.1,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CA-9,CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-169":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,13.4)"],"AWS Config":["(restricted-common-ports)"],"AWS Foundational Security Best Practices controls":["(EC2.19)"],"BSA":["v3 (GS-4,NS-1,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.1,SC.L2-3.13.2,SC.L2-3.13.6)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m,09.n)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.16,8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3,CIP-007-6_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.1,3.13.2,3.13.6,3.4.6)","800_53 Rev5 (CA-9,CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.3.6,2.2.2)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-451":{"Standard1":["v7 (6)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (IVS-02)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard3":["Standard11_800_53_Rev5 (SA-03,SA-08,SA-10,SI-04)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,09.ab,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.16,8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (SA-10,SA-3,SA-8,SI-4)"],"Standard9":["v1.1 (DE.CM-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(A1.1,CC7.1)"]},"ecc-aws-208":{"Standard1":["v7 (6.2,6.3)","v8 (3.14,8.2,8.5)"],"AWS Config":["(rds-logging-enabled)"],"AWS Foundational Security Best Practices controls":["(RDS.9)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.3.d,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-06.09,AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.308.a.3.ii.A,164.308.a.5.ii.C,164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AC-6.9,AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.1,10.2.5,10.3)"],"s202":["(CC6.1,CC7.2)"]},"ecc-aws-038":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,13.4)"],"AWS Foundational Security Best Practices controls":["(EC2.19)"],"BSA":["v3 (GS-4,NS-1,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.2,SC.L2-3.13.6)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m,09.n)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.16,8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.1,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CA-9,CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-371":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,13.4)"],"BSA":["v3 (GS-4,NS-1,NS-2)"],"CAEUC":["v1.0.0 (2.15)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.20,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.1,SC.L2-3.13.2,SC.L2-3.13.6)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m,09.n)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.1,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CA-9,CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-049":{"Standard1":["v7 (4.4)","v8 (5.2)"],"CIS AWS Foundations Benchmark":["v1.2.0 (1.8)"],"AWS Config":["(iam-password-policy)"],"AWS Foundational Security Best Practices controls":["(IAM.7,IAM.14)"],"CJIS":["(5.6.2.1.1)"],"CMMC":["v2.0 (IA.L2-3.5.7)"],"Standard4":["v4 (IAM-02)"],"CE":["v2.2 (Password_based_Authentication)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.7)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01)"],"Standard5":["(164.308.a.4.ii.B,164.308.a.5.ii.D,164.312.d,164.314.b.2.i)"],"Standard15":["(01.f)"],"Standard14":["27001_2013 (A.9.4.3)","27002_2013 (9.4.3)","27002_2022 (5.17)","27017_2015 (9.4.3)","27018_2019 (9.4.3)","27701_2019 (6.6.4.3)"],"Standard12":["(CIP-007-6_Requirement_R5_Part_5.5)"],"Standard11":["800-171 Rev2 (3.5.7)","800_53 Rev5 (IA-5.1)"],"Standard8":["v3.2.1 (2.1,8.2.3,8.2.6)"],"s202":["(CC6.1)"]},"ecc-aws-530":{"Standard1":["v7 (14.4)","v8 (3.10)"],"AWS Config":["(cloudfront-viewer-policy-https)"],"AWS Foundational Security Best Practices controls":["(CloudFront.3)"],"BSA":["v3 (DP-3)"],"CJIS":["(5.10.1.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IVS-03)"],"DA":["(Article_8.2,Article_8.3.a,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12,D3.PC.Am.B.13)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,SC-08)"],"Standard6":["2016_679 (Article_32.1.a,Article_44)"],"Standard5":["(164.312.e.1)"],"Standard15":["(06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1,A.18.1.3)","27002_2013 (10.1.1,13.2.1,18.1.3)","27002_2022 (5.14,5.33,8.24)","27017_2015 (10.1.1,13.2.1,18.1.3)","27018_2019 (10.1.1,13.2.1,18.1.3)","27701_2019 (6.10.2.1,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.8,3.5.10)","800_53 Rev5 (AC-17.2,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-2)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1.1,4.1,4.1.1,8.2.1)"],"s202":["(CC6.1,CC6.7)"]},"ecc-aws-407":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-340":{"Standard1":["v7 (6.2,6.3)","v8 (8.2,8.5)"],"AWS Config":["(mq-cloudwatch-audit-logging-enabled)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_8.4.e,Article_10.7)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.5,10.3)"],"s202":["(CC7.2)"]},"ecc-aws-310":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Security_update_management)"],"DA":["(Article_8.4.f)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-257":{"Standard1":["v7 (14.4,16.2,16.5)","v8 (3.10,5.6)"],"AWS Config":["(emr-kerberos-enabled)"],"BSA":["v3 (DP-3,GS-6,IM-4)"],"CJIS":["(5.10.1.2.1,5.6.2)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IAM-14,IVS-03)"],"DA":["(Article_8.2,Article_8.3.a,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.13,D3.PC.Am.B.6)"],"Standard3":["Standard11_800_53_Rev5 (AC-02.01,AC-17.02,SC-08)"],"Standard6":["2016_679 (Article_32.1.a,Article_44)"],"Standard5":["(164.312.e.1)"],"Standard15":["(01.q,06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1,A.18.1.3,A.9.4.2)","27002_2013 (10.1.1,13.2.1,18.1.3,9.4.2)","27002_2022 (5.14,5.15,5.33,8.24,8.5)","27017_2015 (10.1.1,13.2.1,18.1.3,9.4.2)","27018_2019 (10.1.1,13.2.1,18.1.3,9.4.2)","27701_2019 (6.10.2.1,6.15.1.3,6.6,6.6.4.2,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.8)","800_53 Rev5 (AC-17.2,AC-2.1,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.AC-7,PR.DS-2)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1.1,4.1,4.1.1,8.2.1)"],"s202":["(CC6.1,CC6.7)"]},"ecc-aws-180":{"Standard1":["v7 (5.1)","v8 (4.2)"],"AWS Config":["(cloudfront-origin-failover-enabled)"],"AWS Foundational Security Best Practices controls":["(CloudFront.4)"],"BSA":["v3 (GS-10,GS-5,PV-1)"],"CJIS":["(5.10.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-02)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09,CP-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_32.1.c)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2,A.17.2.1)","27002_2013 (13.1.2,17.2.1)","27002_2022 (8.14,8.21,8.9)","27017_2015 (13.1.2,17.2.1)","27018_2019 (13.1,17)","27701_2019 (6.10.1.2,6.14.2.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9,CP-7)"],"Standard9":["v1.1 (PR.IP-5)"],"s202":["(A1.2,CC6.6)"]},"ecc-aws-196":{"Standard1":["v7 (12)","v8 (12.2)"],"AWS Config":["(emr-master-no-public-ip)"],"AWS Foundational Security Best Practices controls":["(EMR.1)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.308.a.3.i,164.308.a.4.ii.A,164.308.a.4.ii.C,164.308a.1.ii.B,164.312.a.1,164.312.e.1)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.13.2,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.3.6,2.2.2)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-251":{"Standard1":["v7 (14.4,14.6,14.8)","v8 (3.10,3.11,3.3)"],"BSA":["v3 (AM-4,DP-3,DP-4,DP-5,IM-7,PA-7)"],"CJIS":["(5.10.1.2.1,5.10.1.2.2,5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.13,AC.L2-3.1.17,AC.L2-3.1.5,MP.L2-3.8.1,MP.L2-3.8.2,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.16,SC.L2-3.13.8)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IAM-05,IVS-03)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.a,Article_8.3.b,Article_8.4.d,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Am.B.13)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,AC-17.02,IA-05.01,MP-02,SC-08,SC-28)"],"Standard6":["2016_679 (Article_32.1.a,Article_44,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.a.2.iv,164.312.e.1)"],"Standard15":["(01.a,01.c,01.v,06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1,A.18.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (10.1.1,13.2.1,18.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.14,5.15,5.33,8.24,8.3)","27017_2015 (10.1.1,13.2.1,18.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (10.1.1,13.2.1,18.1.3,8,9.1,9.4.1)","27701_2019 (6.10.2.1,6.15.1.3,6.5.1.3,6.6.1.1,6.6.4.1,6.7.1.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.13,3.1.2,3.13.16,3.13.8,3.8.1,3.8.2)","800_53 Rev5 (AC-17.2,AC-3,AC-6,IA-5.1,MP-2,SC-28,SC-28.1,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.AC-4,PR.DS-1,PR.DS-2)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (2.1.1,3.4,3.4.1,4.1,4.1.1,7.1,7.1.1,7.1.2,7.1.3,8.2.1)"],"s202":["(CC6.1,CC6.3,CC6.7)"]},"ecc-aws-312":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CJIS":["(5.10.4.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Security_update_management)"],"DA":["(Article_8.4.f)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-386":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-492":{"Standard1":["v7 (5.1)","v8 (4.1)"],"AWS Config":["(ecr-private-lifecycle-policy-configured)"],"AWS Foundational Security Best Practices controls":["(ECR.3)"],"BSA":["v3 (GS-10)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,SA-03,SA-10)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1)","800_53 Rev5 (CM-2,CM-6,SA-10,SA-3)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-124":{"Standard1":["v7 (14.8)","v8 (3.11,3.3)"],"AWS Config":["(efs-encrypted-check)"],"AWS Foundational Security Best Practices controls":["(EFS.1)"],"BSA":["v3 (AM-4,DP-4,DP-5,IM-7,PA-7)"],"CJIS":["(5.10.1.2.2,5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.1,MP.L2-3.8.2,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03,IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.4.d,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02,SC-28)"],"Standard6":["2016_679 (Article_32.1.a,Article_5.1.f)"],"Standard5":["(164.308.a.1.ii.B,164.308.a.4.i,164.308.a.4.ii.B,164.312.a.2.iv,164.312.e.2.ii,164.314.b.2.i)"],"Standard15":["(01.a,01.c,01.v,06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3,A.9.1.1,A.9.4.1)","27002_2013 (10.1.1,18.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,5.33,8.24,8.3)","27017_2015 (10.1.1,18.1.3,9.1.1,9.4.1)","27018_2019 (10.1.1,18.1.3,9.1,9.4.1)","27701_2019 (6.15.1.3,6.6.1.1,6.6.4.1,6.7.1.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.13.16,3.8.1,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.AC-4,PR.DS-1)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (3.4,3.4.1,7.1,7.1.1,7.1.2,7.1.3,8.2.1)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-aws-537":{"Standard1":["v7 (14.6)","v8 (3.3)"],"AWS Config":["(ecs-containers-nonprivileged)"],"AWS Foundational Security Best Practices controls":["(ECS.1)"],"BSA":["v3 (AM-4,IM-7,PA-7)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.3.b,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.3.i,164.308.a.3.ii.A,164.308.a.4.i,164.308.a.4.ii.A,164.308.a.4.ii.B,164.308.a.4.ii.C,164.312.a.1,164.314.b.2.i)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.3)","27017_2015 (8.1.3,9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)"],"s202":["(CC6.1,CC6.3)"]},"ecc-aws-333":{"Standard1":["v7 (14.6,14.8)","v8 (3.11,3.3)"],"BSA":["v3 (AM-4,DP-4,DP-5,IM-7,PA-7)"],"CJIS":["(5.10.1.2.2,5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.1,MP.L2-3.8.2,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03,IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.4.d,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,IA-05.01,MP-02,SC-28)"],"Standard6":["2016_679 (Article_32.1.a,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.a.2.iv)"],"Standard15":["(01.a,01.c,01.v,06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,5.33,8.24,8.3)","27017_2015 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (10.1.1,18.1.3,8,9.1,9.4.1)","27701_2019 (6.15.1.3,6.5.1.3,6.6.1.1,6.6.4.1,6.7.1.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.13.16,3.8.1,3.8.2)","800_53 Rev5 (AC-3,AC-6,IA-5.1,MP-2,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.AC-4,PR.DS-1)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (3.4,3.4.1,7.1,7.1.1,7.1.2,7.1.3,8.2.1)"],"s202":["(CC6.1,CC6.3)"]},"ecc-aws-096":{"Standard1":["v7 (6.2)","v8 (8.11)"],"CIS AWS Foundations Benchmark":["v1.4.0 (4.10)","v1.5.0 (4.10)"],"AWS Foundational Security Best Practices controls":["(CloudWatch.10)"],"BSA":["v3 (DS-7,LT-1,LT-2,LT-5)"],"CJIS":["(5.4.1,5.4.3)"],"CMMC":["v2.0 (AU.L2-3.3.5)"],"Standard4":["v4 (LOG-05)"],"DA":["(Article_9.2,Article_15.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-06,AU-06.01,AU-06.05,AU-07)"],"Standard5":["(164.308.a.1.ii.D,164.312.b)"],"Standard14":["27001_2013 (A.12.4.1,A.16.1.4)","27002_2013 (12.4.1,16.1.4)","27002_2022 (5.25)","27017_2015 (12.4.1,16.1.4)","27018_2019 (12.4.1,6.1.4)","27701_2019 (6.13.1.4,6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.4)"],"Standard11":["800-171 Rev2 (3.3.3,3.3.5)","800_53 Rev5 (AU-6,AU-6.1,AU-6.5,AU-7)"],"Standard9":["v1.1 (DE.AE-2,PR.PT-1,RS.AN-1)"],"Standard8":["v3.2.1 (10.6,10.6.1,10.6.2)"],"s202":["(CC4.1,CC7.3)"]},"ecc-aws-170":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,13.4)"],"AWS Foundational Security Best Practices controls":["(EC2.19)"],"BSA":["v3 (GS-4,NS-1,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.1,SC.L2-3.13.2,SC.L2-3.13.6)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m,09.n)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.16,8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3,CIP-007-6_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.1,3.13.2,3.13.6,3.4.6)","800_53 Rev5 (CA-9,CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.3.6,2.2.2)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-481":{"Standard1":["v7 (14.6)","v8 (3.3)"],"AWS Config":["(codebuild-project-environment-privileged-check)"],"AWS Foundational Security Best Practices controls":["(CodeBuild.5)"],"BSA":["v3 (AM-4,IM-7,PA-7)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.3.b,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.3.i,164.308.a.3.ii.A,164.308.a.4.i,164.308.a.4.ii.A,164.308.a.4.ii.B,164.308.a.4.ii.C,164.312.a.1,164.314.b.2.i)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.3)","27017_2015 (8.1.3,9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)"],"s202":["(CC6.1,CC6.3)"]},"ecc-aws-428":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-036":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,13.4)"],"AWS Foundational Security Best Practices controls":["(EC2.19)"],"BSA":["v3 (GS-4,NS-1,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.2,SC.L2-3.13.6)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m,09.n)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.16,8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.1,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CA-9,CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-314":{"Standard1":["v7 (6.2)","v8 (8.2)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CIS Oracle Database 19 Benchmark":["v1.0.0 (2.2.1)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.2,10.3)"],"s202":["(CC7.2)"]},"ecc-aws-017":{"Standard1":["v7 (16.9)","v8 (5.3)"],"CIS AWS Foundations Benchmark":["v1.4.0 (1.12)","v1.5.0 (1.12)"],"AWS Config":["(iam-user-unused-credentials-check)"],"AWS Foundational Security Best Practices controls":["(IAM.8,IAM.22)"],"BSA":["v3 (PA-4)"],"CJIS":["(5.5.1)"],"Standard4":["v4 (IAM-07,IAM-08)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-17,SC-12)"],"Standard6":["2016_679 (Article_25)"],"Standard5":["(164.308.a.3.i,164.308.a.4.ii.A,164.308.a.4.ii.B,164.308.a.4.ii.C,164.312.a.1,164.312.e.1)"],"Standard15":["(01.l,10.g)"],"Standard14":["27001_2013 (A.10.1.2,A.9.1.1)","27002_2013 (10.1.2,9.1.2)","27002_2022 (5.15,8.24)","27017_2015 (10.1.2,9.1.2)","27018_2019 (10.1.2,9.1)","27701_2019 (6.6.1.1,6.7.1.2)"],"Standard11":["800_53 Rev5 (AC-17,AC-3,SC-12)"],"Standard9":["v1.1 (ID.GV-4,PR.AC-1,PR.DS-5,PR.IP-7)"],"Standard8":["v3.2.1 (8.1.2,8.1.3,8.1.4)"],"s202":["(CC6.1)"]},"ecc-aws-008":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.1,Article_8.2,Article_8.3)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-521":{"Standard1":["v7 (5.1)","v8 (4.1)"],"AWS Config":["(ecs-containers-readonly-access)"],"AWS Foundational Security Best Practices controls":["(ECS.5)"],"BSA":["v3 (GS-10,GS-5,NS-8,PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-07,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32.1.b)"],"Standard5":["(164.308.a.3.i,164.308.a.3.ii.A,164.308.a.3.ii.B,164.308.a.4.i,164.308.a.4.ii.A,164.308.a.4.ii.B,164.308.a.4.ii.C,164.312.a.1,164.314.b.2.i)"],"Standard15":["(08.k,10.k,10.m)"],"Standard14":["27001_2013 (A.13.1.2,A.9.1.1,A.9.4.1)","27002_2013 (13.1.2,9.1.1,9.4.1)","27002_2022 (5.10,8.21,8.3)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-7,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-18)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC6.6)"]},"ecc-aws-335":{"Standard1":["v7 (5.5)","v8 (13.6)"],"BSA":["v3 (LT-4)"],"CJIS":["(5.10.1.1,5.10.1.3)"],"CMMC":["v2.0 (AC.L2-3.1.3,SI.L2-3.14.3,SI.L2-3.14.6)"],"Standard4":["v4 (IVS-03)"],"Standard10":["(D3.DC.Ev.B.1)"],"Standard3":["Standard11_800_53_Rev5 (SI-04,SI-04.04)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa,09.m)"],"Standard14":["27001_2013 (A.12.4.1,A.13.1.1)","27002_2013 (12.4.1,13.1.1)","27002_2022 (8.15,8.16,8.20)","27017_2015 (12.4.1,13.1.1)","27018_2019 (12.4.1,13.1)","27701_2019 (6.10.1.1,6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.13.1,3.14.6)","800_53 Rev5 (SI-4,SI-4.4)"],"Standard9":["v1.1 (DE.CM-1)"],"s202":["(CC7.2)"]},"ecc-aws-014":{"Standard1":["v7 (14.4)","v8 (3.10)"],"AWS Config":["(elb-tls-https-listeners-only)"],"AWS Foundational Security Best Practices controls":["(ELB.3)"],"BSA":["v3 (DP-3)"],"CJIS":["(5.10.1.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IVS-03)"],"DA":["(Article_8.2,Article_8.3.a,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12,D3.PC.Am.B.13)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,SC-08)"],"Standard6":["2016_679 (Article_32.1.a,Article_44)"],"Standard5":["(164.312.a.2.iv,164.312.e.1,164.312.e.2.i,164.312.e.2.ii,164.314.b.1,164.314.b.2,164.314.b.2.i)"],"Standard15":["(06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1,A.18.1.3)","27002_2013 (10.1.1,13.2.1,18.1.3)","27002_2022 (5.14,5.33,8.24)","27017_2015 (10.1.1,13.2.1,18.1.3)","27018_2019 (10.1.1,13.2.1,18.1.3)","27701_2019 (6.10.2.1,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.8,3.5.10)","800_53 Rev5 (AC-17.2,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-2)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1.1,4.1,4.1.1,8.2.1)"],"s202":["(CC6.1,CC6.7)"]},"ecc-aws-139":{"Standard1":["v7 (14,14.6)","v8 (3.3)"],"CIS AWS Foundations Benchmark":["v1.4.0 (1.20)","v1.5.0 (1.20)"],"BSA":["v3 (AM-4,IM-7,PA-7)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.3.b,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.3)","27017_2015 (8.1.3,9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-aws-090":{"Standard1":["v7 (12,14.6)","v8 (3.3,6)"],"AWS Config":["(rds-snapshots-public-prohibited)"],"AWS Foundational Security Best Practices controls":["(RDS.1)"],"BSA":["v3 (AM-4,IM-7,PA-7)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.3.b,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.1.ii.B,164.308.a.3.i,164.308.a.4.i,164.308.a.4.ii.A,164.308.a.4.ii.B,164.308.a.4.ii.C,164.312.a.1,164.312.e.1)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.3)","27017_2015 (8.1.3,9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-aws-355":{"Standard1":["v7 (14.6,14.8)","v8 (3.11,3.3)"],"AWS Config":["(redshift-cluster-configuration-check)","(redshift-cluster-kms-enabled)"],"BSA":["v3 (AM-4,DP-4,DP-5,IM-7,PA-7)"],"CJIS":["(5.10.1.2.2,5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.1,MP.L2-3.8.2,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03,IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.4.d,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,IA-05.01,MP-02,SC-28)"],"Standard6":["2016_679 (Article_32.1.a,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.a.2.iv)"],"Standard15":["(01.a,01.c,01.v,06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,5.33,8.24,8.3)","27017_2015 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (10.1.1,18.1.3,8,9.1,9.4.1)","27701_2019 (6.15.1.3,6.5.1.3,6.6.1.1,6.6.4.1,6.7.1.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.13.16,3.8.1,3.8.2)","800_53 Rev5 (AC-3,AC-6,IA-5.1,MP-2,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.AC-4,PR.DS-1)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (3.4,3.4.1,7.1,7.1.1,7.1.2,7.1.3,8.2.1)"],"s202":["(CC6.1,CC6.3)"]},"ecc-aws-323":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CIS Oracle Database 19 Benchmark":["v1.0.0 (2.2.15)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.3,Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-432":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-490":{"Standard1":["v7 (12)","v8 (4.1)"],"AWS Config":["(ec2-token-hop-limit-check)"],"BSA":["v3 (GS-10,GS-5,NS-8,PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-07,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32.1.b)"],"Standard15":["(08.k,10.k,10.m)"],"Standard14":["27001_2013 (A.13.1.2,A.9.1.1,A.9.4.1)","27002_2013 (13.1.2,9.1.1,9.4.1)","27002_2022 (5.10,8.21,8.3)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-7,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC6.6)"]},"ecc-aws-143":{"Standard1":["v7 (6.2,6.3)","v8 (8.5)"],"AWS Config":["(cloudtrail-s3-dataevents-enabled)"],"CIS AWS Foundations Benchmark":["v1.4.0 (3.10)","v1.5.0 (3.10)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_8.4.e,Article_10.7)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.2,10.2.4,10.2.5,10.3)"],"s202":["(CC7.2)"]},"ecc-aws-437":{"Standard1":["v7 (13)","v8 (3.13)"],"BSA":["v3 (DP-1,DP-2)"],"AWS Foundational Security Best Practices controls":["(S3.15)"],"AWS Config":["(s3-bucket-default-lock-enabled)"],"CJIS":["(5.1.1.1,5.4.3)"],"CMMC":["v2.0 (AC.L2-3.1.3,SC.L1-3.13.1,SI.L2-3.14.6)"],"Standard4":["v4 (DSP-10,DSP-17,UEM-11)"],"DA":["(Article_8.2,Article_8.3.b)"],"Standard3":["Standard11_800_53_Rev5 (CA-07,CM-12,SC-04)"],"Standard6":["2016_679 (Article_32.1.c)"],"Standard5":["(164.312.c.1)"],"Standard15":["(06.c)"],"Standard14":["27001_2013 (A.18.1.3)","27002_2013 (18.1.3)","27002_2022 (5.14,5.33,8.12)","27017_2015 (18.1.3)","27018_2019 (18.1)","27701_2019 (6.15.1.3)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.1)"],"Standard11":["800_53 Rev5 (CA-7,CM-12,SC-4)"],"Standard9":["v1.1 (PR.DS-5)"],"Standard7":["(6._Maintain_a_data_loss_prevention_strategy)"],"Standard8":["v3.2.1 (A3.2.6,A3.2.6.1)"],"s202":["(CC6.7)"]},"ecc-aws-455":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-227":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (AM-4,DP-4,DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)"],"s202":["(CC6.1)"]},"ecc-aws-443":{"Standard1":["v7 (18.10)","v8 (4.4)"],"AWS Config":["(appsync-associated-with-waf)"],"BSA":["v3 (GS-9,NS-1,NS-2,NS-3,NS-7,NS-8)"],"CJIS":["(5.10.1,5.7.1.2)"],"CMMC":["v2.0 (AC.L1-3.1.20,CM.L2-3.4.7,SC.L1-3.13.1,SC.L2-3.13.6)"],"Standard2":["19 (APO13)"],"Standard4":["v4 (UEM-10)"],"CE":["v2.2 (A4.11,A4.5,A4.7,Firewalls)"],"DA":["(Article_8.3.b,Article_8.4.c,Article_9.1)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.2,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,SC-07,SC-07.05)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard15":["(09.m)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.20)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard11":["800-171 Rev2 (3.13.1)","800_53 Rev5 (CA-9,SC-7,SC-7.5)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (1.1.4,1.3.1)"],"s202":["(CC6.6)"]},"ecc-aws-356":{"Standard1":["v7 (14.4)","v8 (12.2,3.10)"],"BSA":["v3 (DP-3,GS-4,NS-2)"],"CJIS":["(5.10.1,5.10.1.2.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.20,AC.L2-3.1.13,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,SC.L1-3.13.5,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.2,SC.L2-3.13.8)"],"Standard2":["19 (APO01,APO03,DSS05)"],"Standard4":["v4 (CEK-03,DSP-07,DSP-10,DSP-17,IVS-03)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.2,Article_8.3.a,Article_8.3.c,Article_8.4.b,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.13,D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,CM-07,PL-08,SA-08,SC-07,SC-08)"],"Standard6":["2016_679 (Article_32.1.a,Article_32.1.b,Article_44,Article_5.1.f)"],"Standard5":["(164.312.e.1)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,06.c,06.d,09.m,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.1.1,A.13.1.3,A.13.2.1,A.18.1.3)","27002_2013 (10.1.1,13.1.1,13.1.3,13.2.1,18.1.3)","27002_2022 (5.14,5.33,8.20,8.22,8.24,8.27)","27017_2015 (10.1.1,13.1.1,13.1.3,13.2.1,18.1.3)","27018_2019 (10.1.1,13.1,13.2.1,18.1.3)","27701_2019 (6.10.1.1,6.10.1.3,6.10.2.1,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3,CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.2,3.13.8,3.4.6)","800_53 Rev5 (AC-17.2,CM-7,PL-8,PM-7,SA-8,SC-7,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.AC-5,PR.DS-2,PR.IP-1,PR.PT-3)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.1.1,2.2.2,2.2.5,4.1,4.1.1,8.2.1)"],"s202":["(CC6.1,CC6.7)"]},"ecc-aws-219":{"Standard1":["v7 (5.1)","v8 (4.1,4.6)"],"AWS Config":["(secretsmanager-scheduled-rotation-success-check)"],"AWS Foundational Security Best Practices controls":["(SecretsManager.2)"],"BSA":["v3 (DP-6,GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.15)"],"Standard2":["19 (APO13,BAI06,BAI09,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b,Article_8.4.e)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(07.c,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.DS-3,PR.IP-1,PR.IP-2)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-311":{"Standard1":["v7 (14.6,14.8)","v8 (3.11,3.3)"],"AWS Config":["(sagemaker-notebook-instance-kms-key-configured)"],"BSA":["v3 (AM-4,DP-4,DP-5,IM-7,PA-7)"],"CJIS":["(5.10.1.2.2,5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.1,MP.L2-3.8.2,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03,IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.4.d,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,IA-05.01,MP-02,SC-28)"],"Standard6":["2016_679 (Article_32.1.a,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.a.2.iv)"],"Standard15":["(01.a,01.c,01.v,06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,5.33,8.24,8.3)","27017_2015 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (10.1.1,18.1.3,8,9.1,9.4.1)","27701_2019 (6.15.1.3,6.5.1.3,6.6.1.1,6.6.4.1,6.7.1.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.13.16,3.8.1,3.8.2)","800_53 Rev5 (AC-3,AC-6,IA-5.1,MP-2,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.AC-4,PR.DS-1)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (3.4,3.4.1,7.1,7.1.1,7.1.2,7.1.3,8.2.1)"],"s202":["(CC6.1,CC6.3)"]},"ecc-aws-053":{"Standard1":["v7 (6)","v8 (8.11)"],"CIS AWS Foundations Benchmark":["v1.4.0 (3.2)","v1.5.0 (3.2)"],"AWS Config":["(cloud-trail-log-file-validation-enabled)"],"AWS Foundational Security Best Practices controls":["(CloudTrail.4)"],"BSA":["v3 (DS-7,LT-1,LT-2,LT-5)"],"CJIS":["(5.4.1,5.4.3)"],"CMMC":["v2.0 (AU.L2-3.3.5)"],"Standard4":["v4 (LOG-05)"],"Standard3":["Standard11_800_53_Rev5 (AU-06,AU-06.01,AU-06.05,AU-07)"],"Standard5":["(164.308.a.1.ii.B,164.308.a.1.ii.D,164.312.b,164.312.c.1,164.312.c.2)"],"Standard14":["27001_2013 (A.12.4.1,A.16.1.4)","27002_2013 (12.4.1,16.1.4)","27002_2022 (5.25)","27017_2015 (12.4.1,16.1.4)","27018_2019 (12.4.1,6.1.4)","27701_2019 (6.13.1.4,6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.4)"],"Standard11":["800-171 Rev2 (3.3.3,3.3.5)","800_53 Rev5 (AU-6,AU-6.1,AU-6.5,AU-7)"],"Standard9":["v1.1 (DE.AE-2,PR.PT-1,RS.AN-1)"],"Standard8":["v3.2.1 (10.6,10.6.1,10.6.2)"],"s202":["(CC4.1,CC7.3)"]},"ecc-aws-290":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (DP-4,DP-5)"],"CAEUC":["v1.0.0 (2.3)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)"],"s202":["(CC6.1)"]},"ecc-aws-106":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-125":{"Standard1":["v7 (14.8)","v8 (3.11)"],"AWS Foundational Security Best Practices controls":["(ElastiCache.4)"],"BSA":["v3 (DP-4,DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)"],"s202":["(CC6.1)"]},"ecc-aws-162":{"Standard1":["v7 (5.5)","v8 (4.1)"],"AWS Foundational Security Best Practices controls":["(RDS.22)"],"BSA":["v3 (GS-5,PV-2)"],"CJIS":["(5.10.4.4,5.4.3)"],"CMMC":["v2.0 (AU.L2-3.3.5,CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-07,LOG-03)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5,A.16.1.2)","27002_2013 (14.2.5,16.1.2)","27002_2022 (6.8,8.27,8.9)","27017_2015 (14.2.5,16.1.2)","27018_2019 (14,16.1.2)","27701_2019 (6.11.2.5,6.13.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-007-6_Requirement_R4_Part_4.2)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (DE.DP-4,PR.IP-1)"],"Standard8":["v3.2.1 (2.2,A.3.3.1)"],"s202":["(CC8.1)"]},"ecc-aws-058":{"Standard1":["v7 (14)","v8 (17.1)"],"CIS AWS Foundations Benchmark":["v1.4.0 (1.17)","v1.5.0 (1.17)"],"AWS Foundational Security Best Practices controls":["(IAM.18)"],"BSA":["v3 (IR-2)"],"CJIS":["(5.3.2)"],"Standard2":["19 (APO13)"],"Standard4":["v4 (BCR-01,SEF-03)"],"DA":["(Article_12.1)"],"Standard10":["(D5.IR.Pl.B.3)"],"Standard3":["Standard11_800_53_Rev5 (IR-01,IR-07,IR-08)"],"Standard5":["(164.308.a.2)"],"Standard15":["(02.a,05.c,11.c)"],"Standard14":["27001_2013 (A.16.1.1)","27002_2013 (16.1.1)","27002_2022 (5.24)","27017_2015 (16.1.1)","27018_2019 (16.1.1)","27701_2019 (6.13.1.1)"],"Standard12":["(CIP-008-6_Requirement_R1_Part_1.3)"],"Standard11":["800_53 Rev5 (IR-1,IR-7,IR-8)"],"Standard9":["v1.1 (DE.DP-1,PR.IP-9)"],"Standard8":["v3.2.1 (12.10.3,12.10.4,12.5.3)"],"s202":["(CC7.4)"]},"ecc-aws-317":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CIS Oracle Database 19 Benchmark":["v1.0.0 (2.2.5)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.2,Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-459":{"Standard1":["v7 (5.1)","v8 (4.1)"],"CIS AWS Compute Services Benchmark":["v1.0.0 (4.8)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CJIS":["(5.10.4.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Security_update_management)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.CC.Pa.B.1,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-007-6_Requirement_R2_Part_2.1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-427":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1,AM-3)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-119":{"Standard1":["v7 (14.4)","v8 (3.10)"],"AWS Config":["(kinesis-stream-encrypted)"],"BSA":["v3 (DP-3)"],"CJIS":["(5.10.1.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IVS-03)"],"DA":["(Article_8.2,Article_8.3.a,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12,D3.PC.Am.B.13)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,SC-08)"],"Standard6":["2016_679 (Article_32.1.a,Article_44)"],"Standard5":["(164.312.e.1)"],"Standard15":["(06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1,A.18.1.3)","27002_2013 (10.1.1,13.2.1,18.1.3)","27002_2022 (5.14,5.33,8.24)","27017_2015 (10.1.1,13.2.1,18.1.3)","27018_2019 (10.1.1,13.2.1,18.1.3)","27701_2019 (6.10.2.1,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.8,3.5.10)","800_53 Rev5 (AC-17.2,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-2)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1.1,4.1,4.1.1,8.2.1)"],"s202":["(CC6.1,CC6.7)"]},"ecc-aws-325":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (IVS-02)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.2,Article_8.4.a,Article_11.5)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,CP-06,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k,12.c)"],"Standard14":["27001_2013 (A.14.2.5,A.17.2.1)","27002_2013 (14.2.5,17.2.1)","27002_2022 (8.14,8.27,8.9)","27017_2015 (14.2.5,17.2.1)","27018_2019 (14,17)","27701_2019 (6.11.2.5,6.14.2.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,CP-6,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR-PT-5,PR.IP-1)"],"s202":["(CC8.1)"]},"ecc-aws-514":{"Standard1":["v7 (14.6)","v8 (3.3)"],"BSA":["v3 (AM-4,IM-7,PA-7)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.3.c,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.3)","27017_2015 (8.1.3,9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-aws-266":{"Standard1":["v7 (10.1)","v8 (11.2)"],"AWS Foundational Security Best Practices controls":["(ElastiCache.1)"],"AWS Config":["(elasticache-redis-cluster-automatic-backup-check)"],"BSA":["v3 (BR-1)"],"Standard4":["v4 (BCR-08)"],"CE":["v2.2 (Back_up_your_data)"],"DA":["(Article_11.2,Article_11.4)"],"Standard10":["(D5.IR.Pl.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CP-09,CP-10)"],"Standard6":["2016_679 (Article_32.1.c)"],"Standard5":["(164.308.a.7.ii.A)"],"Standard15":["(09.l)"],"Standard14":["27001_2013 (A.12.3.1)","27002_2013 (12.3.1)","27002_2022 (8.13)","27017_2015 (12.3.1)","27018_2019 (12.3.1)","27701_2019 (6.9.3.1)"],"Standard12":["(CIP-009-6_Requirement_R1_Part_1.3)"],"Standard11":["800_53 Rev5 (CP-10,CP-9)"],"Standard9":["v1.1 (PR.IP-4)"],"Standard7":["(6s)"],"Standard8":["v3.2.1 (12.10.1)"],"s202":["(A1.2)"]},"ecc-aws-474":{"Standard1":["v7 (5.1)","v8 (4.1)"],"AWS Config":["(clb-multiple-az)"],"AWS Foundational Security Best Practices controls":["(ELB.10)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (IVS-02)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.2,Article_8.4.a,Article_11.5)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,CP-06,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k,12.c)"],"Standard14":["27001_2013 (A.14.2.5,A.17.2.1)","27002_2013 (14.2.5,17.2.1)","27002_2022 (8.14,8.27,8.9)","27017_2015 (14.2.5,17.2.1)","27018_2019 (14,17)","27701_2019 (6.11.2.5,6.14.2.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,CP-6,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR-PT-5,PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-182":{"Standard1":["v7 (5.1)","v8 (4.1)"],"AWS Config":["(dynamodb-autoscaling-enabled)"],"AWS Foundational Security Best Practices controls":["(DynamoDB.1)"],"BSA":["v3 (GS-10,GS-5,PV-1)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (IVS-02)"],"DA":["(Article_8.2,Article_8.4.a,Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard5":["(164.308.a.1.ii.B,164.308.a.7.i,164.308.a.7.ii.C,164.312.e.1,164.314.b.2.i)"],"Standard15":["(08.k,10.k,12.c)"],"Standard14":["27001_2013 (A.14.2.5,A.17.2.1)","27002_2013 (14.2.5,17.2.1)","27002_2022 (8.14,8.27,8.9)","27017_2015 (14.2.5,17.2.1)","27018_2019 (14,17)","27701_2019 (6.11.2.5,6.14.2.1)"],"Standard12":["(CIP-003-8_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.4.1)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-8)"],"Standard9":["v1.1 (PR.IP-1,PR.PT-5)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-175":{"Standard1":["v7 (14.8)","v8 (3.11)"],"CIS AWS Foundations Benchmark":["v1.4.0 (2.3.1)","v1.5.0 (2.3.1)"],"AWS Config":["(rds-storage-encrypted)"],"AWS Foundational Security Best Practices controls":["(RDS.3)"],"BSA":["v3 (DP-4)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a,Article_5.1.f)"],"Standard5":["(164.308.a.1.ii.B,164.312.a.2.iv,164.312.e.2.ii,164.314.b.1,164.314.b.2,164.314.b.2.i)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)"],"s202":["(CC6.1)"]},"ecc-aws-061":{"Standard1":["v7 (14.8)","v8 (3.11,3.3)"],"CIS AWS Foundations Benchmark":["v1.4.0 (3.8)","v1.5.0 (3.8)"],"AWS Foundational Security Best Practices controls":["(KMS.4)"],"AWS Config":["(cmk-backing-key-rotation-enabled)"],"BSA":["v3 (AM-4,DP-4,DP-5,IM-7,PA-7)"],"CJIS":["(5.10.1.2.2,5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.1,MP.L2-3.8.2,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03,IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.4.d,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02,SC-28)"],"Standard6":["2016_679 (Article_32.1.a,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.a.2.iv)"],"Standard15":["(01.a,01.c,01.v,06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3,A.9.1.1,A.9.4.1)","27002_2013 (10.1.1,18.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,5.33,8.24,8.3)","27017_2015 (10.1.1,18.1.3,9.1.1,9.4.1)","27018_2019 (10.1.1,18.1.3,9.1,9.4.1)","27701_2019 (6.15.1.3,6.6.1.1,6.6.4.1,6.7.1.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.13.16,3.8.1,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.AC-4,PR.DS-1)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (3.4,3.4.1,7.1,7.1.1,7.1.2,7.1.3,8.2.1)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-aws-456":{"Standard1":["v7 (12)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,NS-8,PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-07,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32.1.b)"],"Standard15":["(08.k,10.k,10.m)"],"Standard14":["27001_2013 (A.13.1.2,A.9.1.1,A.9.4.1)","27002_2013 (13.1.2,9.1.1,9.4.1)","27002_2022 (5.10,8.21,8.3)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-7,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC6.6)"]},"ecc-aws-489":{"Standard1":["v7 (6)","v8 (4.1)"],"AWS Config":["(ec2-instance-detailed-monitoring-enabled)"],"CIS AWS Compute Services Benchmark":["v1.0.0 (2.6)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (IVS-02)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard3":["Standard11_800_53_Rev5 (SA-03,SA-08,SA-10,SI-04)"],"Standard6":["2016_679 (Article_32)"],"Standard5":["(164.312.b)"],"Standard15":["(08.k,09.ab,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.16,8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (SA-10,SA-3,SA-8,SI-4)"],"Standard9":["v1.1 (DE.CM-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(A1.1,CC7.1)"]},"ecc-aws-042":{"Standard1":["v7 (14.8)","v8 (3.11,3.3)"],"AWS Config":["(s3-default-encryption-kms)"],"AWS Foundational Security Best Practices controls":["(S3.4,S3.17)"],"BSA":["v3 (AM-4,DP-4,DP-5,IM-7,PA-7)"],"CJIS":["(5.10.1.2.2,5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.1,MP.L2-3.8.2,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03,IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.4.d,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02,SC-28)"],"Standard6":["2016_679 (Article_32.1.a,Article_5.1.f)"],"Standard5":["(164.308.a.1.ii.B,164.308.a.4.i,164.308.a.4.ii.B,164.312.a.2.iv,164.312.c.2,164.312.e.2.ii,164.314.b.1,164.314.b.2,164.314.b.2.i)"],"Standard15":["(01.a,01.c,01.v,06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3,A.9.1.1,A.9.4.1)","27002_2013 (10.1.1,18.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,5.33,8.24,8.3)","27017_2015 (10.1.1,18.1.3,9.1.1,9.4.1)","27018_2019 (10.1.1,18.1.3,9.1,9.4.1)","27701_2019 (6.15.1.3,6.6.1.1,6.6.4.1,6.7.1.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.13.16,3.8.1,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.AC-4,PR.DS-1)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (3.4,3.4.1,7.1,7.1.1,7.1.2,7.1.3,8.2.1)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-aws-418":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-502":{"Standard1":["v7 (3.5,5.1)","v8 (4.1,7.4)"],"CIS AWS Foundations Benchmark":["v1.5.0 (2.3.2)"],"AWS Config":["(rds-automatic-minor-version-upgrade-enabled)"],"AWS Foundational Security Best Practices controls":["(RDS.13)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3,PV-6)"],"CJIS":["(5.10.4.1,5.13.4.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,SI.L1-3.14.1)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Security_update_management)"],"DA":["(Article_8.4.f)"],"Standard10":["(D1.G.SP.B.2,D3.CC.Pa.B.1,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,RA-07,SA-03,SA-08,SA-10,SI-02)"],"Standard6":["2016_679 (Article_32)"],"Standard5":["(164.308.a.5.ii.A)"],"Standard15":["(08.k,10.k,10.m)"],"Standard14":["27001_2013 (A.12.6.1,A.14.2.5)","27002_2013 (12.6.1,14.2.5)","27002_2022 (8.27,8.8,8.9)","27017_2015 (12.6.1,14.2.5)","27018_2019 (12.6,14)","27701_2019 (6.11.2.5,6.9.6.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-007-6_Requirement_R2_Part_2.1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,RA-7,SA-10,SA-3,SA-8,SI-2)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2,6.2)"],"s202":["(CC7.1,CC8.1)"]},"ecc-aws-560":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800_53 Rev5 (CM-8,CM-8.1)"],"Standard9":["v1.1 (PR.DS-3)"]},"ecc-aws-543":{"Standard1":["v7 (6.2)","v8 (8.2)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_8.4.e,Article_10.7)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.2,10.3)"],"s202":["(CC7.2)"]},"ecc-aws-519":{"Standard1":["v7 (5.1,11.1)","v8 (12.2,4.2)"],"AWS Config":["(vpc-vpn-2-tunnels-up)"],"AWS Foundational Security Best Practices controls":["(EC2.20)"],"BSA":["v3 (GS-4,NS-9,PV-1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,SC.L2-3.13.2)"],"Standard2":["19 (APO13,BAI06)"],"Standard4":["v4 (DSP-07,IVS-02)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.2,Article_8.4.a)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-17,CM-02,CM-09,SA-08)"],"Standard6":["2016_679 (Article_32.1.b,Article_32.1.c)"],"Standard15":["(10.k,12.c)"],"Standard14":["27001_2013 (A.13.1.1,A.14.2.5)","27002_2013 (13.1.1,14.2.5)","27002_2022 (8.20,8.21,8.27,8.9)","27017_2015 (13.1.1,14.2.5)","27018_2019 (13.1,14)","27701_2019 (6.10.1.1,6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-005-7,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.13.2,3.4.1,3.4.2)","800_53 Rev5 (AC-17,CM-2,CM-9,PM-7,SA-8)"],"Standard9":["v1.1 (PR.IP-1,PR.PT-5)"],"Standard8":["v3.2.1 (1.1.1)"],"s202":["(CC8.1)"]},"ecc-aws-288":{"Standard1":["v7 (5.1)","v8 (4.1,4.6)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI09,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32,Article_5.1.f)"],"Standard15":["(07.c,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1,PR.IP-2)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC5.2,CC8.1)"]},"ecc-aws-350":{"Standard1":["v7 (6.2,6.3)","v8 (8.2,8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_8.4.e,Article_10.7)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.5,10.3)"],"s202":["(CC7.2)"]},"ecc-aws-066":{"Standard1":["v7 (12,13)","v8 (12.2,12.3)"],"AWS Foundational Security Best Practices controls":["(EKS.1)"],"AWS Config":["(eks-endpoint-no-public-access)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.5,SC.L2-3.13.15,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-06,CM-07,PL-08,SA-08,SC-07,SC-23)"],"Standard6":["2016_679 (Article_32,Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.312.e.1)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.2,A.13.1.3)","27002_2013 (13.1.1,13.1.2,13.1.3)","27002_2022 (8.20,8.21,8.22,8.27)","27017_2015 (13.1.1,13.1.2,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.2,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CM-6,CM-7,PL-8,PM-7,SA-8,SC-23,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.AC-7,PR.DS-2,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5,8.3)"],"s202":["(CC5.2,CC6.1,CC6.6)"]},"ecc-aws-370":{"Standard1":["v7 (3,5.1)","v8 (4.1,7.4)"],"BSA":["v3 (GS-5,PV-1,PV-3,PV-6)"],"CAEUC":["v1.0.0 (2.10)"],"CJIS":["(5.10.4.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,SI.L1-3.14.1)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (A6.4,A6.5.1,Secure_configuration,Security_update_management)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.CC.Pa.B.1,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,RA-05,RA-07,SA-03,SA-08,SA-10,SI-02,SI-02.02)"],"Standard6":["2016_679 (Article_32)"],"Standard5":["(164.308.a.1.ii.B)"],"Standard15":["(08.k,10.k,10.m)"],"Standard14":["27001_2013 (A.12.6.1,A.14.2.5)","27002_2013 (12.6.1,14.2.5)","27002_2022 (8.27,8.8,8.9)","27017_2015 (12.6.1,14.2.5)","27018_2019 (12.6,14)","27701_2019 (6.11.2.5,6.9.6.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,RA-5,RA-7,SA-10,SA-3,SA-8,SI-2,SI-2.2,SI-2.4)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2,6.2)"],"s202":["(CC7.1,CC8.1)"]},"ecc-aws-136":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,13.4)"],"BSA":["v3 (GS-4,NS-1,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.2,SC.L2-3.13.6)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m,09.n)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.16,8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.1,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CA-9,CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-298":{"Standard1":["v7 (14.6,14.8)","v8 (3.11,3.3)"],"BSA":["v3 (AM-4,DP-4,DP-5,IM-7,PA-7)"],"CJIS":["(5.10.1.2.2,5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.1,MP.L2-3.8.2,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03,IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.4.d,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,IA-05.01,MP-02,SC-28)"],"Standard6":["2016_679 (Article_32.1.a,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.a.2.iv)"],"Standard15":["(01.a,01.c,01.v,06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,5.33,8.24,8.3)","27017_2015 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (10.1.1,18.1.3,8,9.1,9.4.1)","27701_2019 (6.15.1.3,6.5.1.3,6.6.1.1,6.6.4.1,6.7.1.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.13.16,3.8.1,3.8.2)","800_53 Rev5 (AC-3,AC-6,IA-5.1,MP-2,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.AC-4,PR.DS-1)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (3.4,3.4.1,7.1,7.1.1,7.1.2,7.1.3,8.2.1)"],"s202":["(CC6.1,CC6.3)"]},"ecc-aws-361":{"Standard1":["v7 (6.2,6.3)","v8 (8.2,8.5)"],"AWS Config":["(api-gw-execution-logging-enabled)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_8.4.e,Article_10.7)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.5,10.3)"],"s202":["(CC7.2)"]},"ecc-aws-412":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-025":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-300":{"Standard1":["v7 (14.6,5.1)","v8 (3.3,4.1)"],"BSA":["v3 (GS-10,GS-5,IM-7,PA-7,PV-1,PV-3)"],"CJIS":["(5.5.2,5.7.1)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,MP.L2-3.8.2)"],"Standard2":["19 (APO13,BAI06,BAI10,DSS05)"],"Standard4":["v4 (CCC-01,IAM-05,IVS-04)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.3.c,Article_14.d,Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Am.B.1,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,CM-01,CM-02,CM-06,CM-09,MP-02,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.v,08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (14.2.5,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.27,8.3,8.9)","27017_2015 (14.2.5,8.1.3,9.1.1,9.4.1)","27018_2019 (14,8,9.1,9.4.1)","27701_2019 (6.11.2.5,6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-004-6_Requirement_R4_Part_4.1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.4.1,3.4.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,CM-1,CM-2,CM-6,CM-9,MP-2,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.AC-4,PR.IP-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.2,7.1,7.1.1,7.1.2,7.1.3)"],"s202":["(CC6.1,CC6.3,CC8.1)"]},"ecc-aws-322":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CIS Oracle Database 19 Benchmark":["v1.0.0 (2.2.14)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-499":{"Standard1":["v7 (1.4)","v8 (1.1)"],"AWS Config":["(iam-group-has-users-check)"],"BSA":["v3 (AM-1,AM-3)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"DA":["(Article_8.4.c,Article_14.d)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3,PR.IP-1)"],"s202":["(CC6.1)"]},"ecc-aws-431":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-120":{"Standard1":["v7 (14.8)","v8 (3.11)"],"AWS Config":["(kinesis-stream-encrypted)"],"AWS Foundational Security Best Practices controls":["(Kinesis.1)"],"BSA":["v3 (DP-4,DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv,164.312.e.2.ii,164.314.b.2.i)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)"],"s202":["(CC6.1)"]},"ecc-aws-225":{"Standard1":["v7 (6.2,6.3)","v8 (8.2,8.5)"],"AWS Config":["(eks-cluster-logging-enabled)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CIS AWS EKS Benchmark":["v1.1.0 (2.1.1)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_8.4.e,Article_10.7)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.5,10.3)"],"s202":["(CC7.2)"]},"ecc-aws-151":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,13.4)"],"AWS Config":["(restricted-common-ports)"],"AWS Foundational Security Best Practices controls":["(EC2.19)"],"BSA":["v3 (GS-4,NS-1,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.2,SC.L2-3.13.6)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m,09.n)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.16,8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.1,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CA-9,CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-109":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-415":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.4.1,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-166":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,13.4)"],"AWS Foundational Security Best Practices controls":["(EC2.19)"],"BSA":["v3 (GS-4,NS-1,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.1,SC.L2-3.13.2,SC.L2-3.13.6)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m,09.n)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.16,8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3,CIP-007-6_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.1,3.13.2,3.13.6,3.4.6)","800_53 Rev5 (CA-9,CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.3.6,2.2.2)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-012":{"Standard1":["v7 (13)","v8 (3.1)"],"AWS Config":["(cloudfront-security-policy-check)"],"BSA":["v3 (GS-3)"],"CJIS":["(5.1.1.1)"],"CMMC":["v2.0 (SC.L2-3.13.10)"],"Standard2":["19 (APO14)"],"Standard4":["v4 (DSP-01,DSP-17)"],"DA":["(Article_8.2,Article_8.3)"],"Standard10":["(D1.G.IT.B.2)"],"Standard3":["Standard11_800_53_Rev5 (SI-12)"],"Standard6":["2016_679 (Article_5)"],"Standard15":["(00.a)"],"Standard14":["27001_2013 (A.18.1.5,A.6.2.2,A.8.1.3)","27002_2013 (18.1.5,6.2.2,8.1.3)","27002_2022 (5.33,6.7)","27017_2015 (18.1.5,6.2.2,8.1.3)","27018_2019 (18.1,6.2,8)","27701_2019 (6.15.1.5,6.3.2.2,6.5.1.3)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800_53 Rev5 (CM-12,SI-12)"],"Standard9":["v1.1 (PR.IP-6)"],"Standard7":["(6)"],"s202":["(CC6.1,CC6.7)"]},"ecc-aws-134":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,13.4)"],"BSA":["v3 (GS-4,NS-1,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.2,SC.L2-3.13.6)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m,09.n)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.16,8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.1,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CA-9,CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-052":{"Standard1":["v7 (6.2)","v8 (8.5)"],"CIS AWS Foundations Benchmark":["v1.4.0 (3.1)","v1.5.0 (3.1)"],"AWS Config":["(multi-region-cloudtrail-enabled)"],"AWS Foundational Security Best Practices controls":["(CloudTrail.1)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"DA":["(Article_8.4.d,Article_10.7)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.308.a.3.ii.A,164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.2,10.2.4,10.2.5,10.3)"],"s202":["(CC7.2)"]},"ecc-aws-221":{"Standard1":["v7 (14.6,14.8)","v8 (3.11,3.3)"],"AWS Config":["(sns-encrypted-kms)"],"AWS Foundational Security Best Practices controls":["(SNS.1)"],"BSA":["v3 (DP-4,DP-5,IM-7,PA-7)"],"CJIS":["(5.10.1.2.2,5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.1,MP.L2-3.8.2,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03,IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.4.d,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,IA-05.01,MP-02,SC-28)"],"Standard6":["2016_679 (Article_32.1.a,Article_5.1.f)"],"Standard5":["(164.308.a.1.ii.B,164.308.a.4.i,164.308.a.4.ii.B,164.312.a.2.iv,164.312.e.2.ii,164.314.b.2.i)"],"Standard15":["(01.a,01.c,01.v,06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,5.33,8.24,8.3)","27017_2015 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (10.1.1,18.1.3,8,9.1,9.4.1)","27701_2019 (6.15.1.3,6.5.1.3,6.6.1.1,6.6.4.1,6.7.1.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.13.16,3.8.1,3.8.2)","800_53 Rev5 (AC-3,AC-6,IA-5.1,MP-2,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.AC-4,PR.DS-1)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (3.4,3.4.1,7.1,7.1.1,7.1.2,7.1.3,8.2.1)"],"s202":["(CC6.1,CC6.3)"]},"ecc-aws-400":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-087":{"Standard1":["v7 (12,14.6)","v8 (3.3,6)"],"AWS Config":["(redshift-cluster-public-access-check)"],"AWS Foundational Security Best Practices controls":["(Redshift.1)"],"BSA":["v3 (AM-4,IM-7,PA-7)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.3.b,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.1.ii.B,164.308.a.3.i,164.308.a.4.i,164.308.a.4.ii.A,164.308.a.4.ii.B,164.308.a.4.ii.C,164.312.a.1,164.312.e.1,164.314.b.1,164.314.b.2)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.3)","27017_2015 (8.1.3,9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-aws-233":{"Standard1":["v7 (5.1,6.4)","v8 (4.1,8.3)"],"BSA":["v3 (GS-10,GS-5,LT-6,PV-1,PV-3)"],"Standard13":["v1.0.0 (3.1.14)"],"CJIS":["(5.4.6,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AU-04,CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.h,10.k)"],"Standard14":["27001_2013 (A.12.1.3,A.14.2.5)","27002_2013 (12.1.3,14.2.5)","27002_2022 (8.27,8.6,8.9)","27017_2015 (12.1.3,14.2.5)","27018_2019 (12.1.3,14)","27701_2019 (6.11.2.5,6.9.1.3)"],"Standard12":["(CIP-003-8_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (AU-4,CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.DS-4,PR.IP-1)"],"Standard8":["v3.2.1 (10.7,2.2)"],"s202":["(A1.1,CC8.1)"]},"ecc-aws-441":{"Standard1":["v7 (14.8)","v8 (3.11)"],"AWS Config":["(appsync-cache-encryption-at-rest)"],"BSA":["v3 (DP-4,DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28,SC-28.01)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)"],"s202":["(CC6.1)"]},"ecc-aws-419":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-104":{"Standard1":["v7 (12)","v8 (12.2)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-013":{"Standard1":["v7 (13)","v8 (3.1)"],"BSA":["v3 (GS-3)"],"CJIS":["(5.1.1.1)"],"CMMC":["v2.0 (SC.L2-3.13.10)"],"Standard2":["19 (APO14)"],"Standard4":["v4 (DSP-01,DSP-17)"],"DA":["(Article_8.2,Article_8.3)"],"Standard10":["(D1.G.IT.B.2)"],"Standard3":["Standard11_800_53_Rev5 (SI-12)"],"Standard6":["2016_679 (Article_5)"],"Standard15":["(00.a)"],"Standard14":["27001_2013 (A.18.1.5,A.6.2.2,A.8.1.3)","27002_2013 (18.1.5,6.2.2,8.1.3)","27002_2022 (5.33,6.7)","27017_2015 (18.1.5,6.2.2,8.1.3)","27018_2019 (18.1,6.2,8)","27701_2019 (6.15.1.5,6.3.2.2,6.5.1.3)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800_53 Rev5 (CM-12,SI-12)"],"Standard9":["v1.1 (PR.IP-6)"],"Standard7":["(6)"],"s202":["(CC6.1,CC6.7)"]},"ecc-aws-547":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-308":{"Standard1":["v7 (14.6,5.1)","v8 (3.3,4.1)"],"BSA":["v3 (AM-4,GS-10,GS-5,IM-7,PA-7,PV-1,PV-3)"],"CJIS":["(5.5.2,5.7.1)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,MP.L2-3.8.2)"],"Standard2":["19 (APO13,BAI06,BAI10,DSS05)"],"Standard4":["v4 (CCC-01,IAM-05,IVS-04)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.3.c,Article_14.d,Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Am.B.1,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,CM-01,CM-02,CM-06,CM-09,MP-02,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.v,08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (14.2.5,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.27,8.3,8.9)","27017_2015 (14.2.5,8.1.3,9.1.1,9.4.1)","27018_2019 (14,8,9.1,9.4.1)","27701_2019 (6.11.2.5,6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-004-6_Requirement_R4_Part_4.1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.4.1,3.4.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,CM-1,CM-2,CM-6,CM-9,MP-2,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.AC-4,PR.IP-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.2,7.1,7.1.1,7.1.2,7.1.3)"],"s202":["(CC6.1,CC6.3,CC8.1)"]},"ecc-aws-234":{"Standard1":["v7 (5.1,6.4)","v8 (4.1,8.3)"],"BSA":["v3 (GS-10,GS-5,LT-6,PV-1,PV-3)"],"Standard13":["v1.0.0 (3.1.15)"],"CJIS":["(5.4.6,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AU-04,CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.h,10.k)"],"Standard14":["27001_2013 (A.12.1.3,A.14.2.5)","27002_2013 (12.1.3,14.2.5)","27002_2022 (8.27,8.6,8.9)","27017_2015 (12.1.3,14.2.5)","27018_2019 (12.1.3,14)","27701_2019 (6.11.2.5,6.9.1.3)"],"Standard12":["(CIP-003-8_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (AU-4,CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.DS-4,PR.IP-1)"],"Standard8":["v3.2.1 (10.7,2.2)"],"s202":["(A1.1,CC8.1)"]},"ecc-aws-100":{"Standard1":["v7 (5.5)","v8 (8.11)"],"CIS AWS Foundations Benchmark":["v1.4.0 (4.14)","v1.5.0 (4.14)"],"AWS Foundational Security Best Practices controls":["(CloudWatch.14)"],"BSA":["v3 (DS-7,LT-1,LT-2,LT-5)"],"CJIS":["(5.4.1,5.4.3)"],"CMMC":["v2.0 (AU.L2-3.3.5)"],"Standard4":["v4 (LOG-05)"],"DA":["(Article_9.2,Article_15.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-06,AU-06.01,AU-06.05,AU-07)"],"Standard5":["(164.308.a.1.ii.D,164.312.b)"],"Standard14":["27001_2013 (A.12.4.1,A.16.1.4)","27002_2013 (12.4.1,16.1.4)","27002_2022 (5.25)","27017_2015 (12.4.1,16.1.4)","27018_2019 (12.4.1,6.1.4)","27701_2019 (6.13.1.4,6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.4)"],"Standard11":["800-171 Rev2 (3.3.3,3.3.5)","800_53 Rev5 (AU-6,AU-6.1,AU-6.5,AU-7)"],"Standard9":["v1.1 (DE.AE-2,PR.PT-1,RS.AN-1)"],"Standard8":["v3.2.1 (10.6,10.6.1,10.6.2)"],"s202":["(CC4.1,CC7.3)"]},"ecc-aws-238":{"Standard1":["v7 (6.3,6.4)","v8 (8.2,8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"Standard13":["v1.0.0 (3.1.19)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_8.4.e,Article_10.7)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.5,10.3)"],"s202":["(CC7.2)"]},"ecc-aws-044":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-377":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-382":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-448":{"Standard1":["v7 (6.2,6.3)","v8 (4.1,8.2,8.5)"],"BSA":["v3 (DS-7,GS-10,GS-5,LT-3,LT-4,PV-1,PV-3)"],"Standard13":["v1.0.0 (3.1.20)"],"CJIS":["(5.4.1,5.7.1)"],"CMMC":["v2.0 (AU.L2-3.3.1,CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10,DSS03)"],"Standard4":["v4 (CCC-01,IVS-04,LOG-08)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.e,Article_10.7)"],"Standard10":["(D1.G.SP.B.2,D2.MA.Ma.B.1,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-03,AU-12,CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_30,Article_32)"],"Standard5":["(164.312.b)"],"Standard15":["(08.k,09.aa,10.k)"],"Standard14":["27001_2013 (A.12.4.1,A.14.2.5)","27002_2013 (12.4.1,14.2.5)","27002_2022 (5.28,8.15,8.27,8.9)","27017_2015 (12.4.1,14.2.5)","27018_2019 (12.4.1,14)","27701_2019 (6.11.2.5,6.9.4.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-007-6_Requirement_R4_Part_4.1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.3.1,3.4.1,3.4.2)","800_53 Rev5 (AU-12,AU-2,AU-3,AU-3.1,CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (DE.AE-3,PR.IP-1,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.5,10.3,2.2)"],"s202":["(CC7.2,CC8.1)"]},"ecc-aws-003":{"Standard1":["v7 (12.5,6.2)","v8 (13.6,8.2)"],"CIS AWS Foundations Benchmark":["v1.4.0 (3.9)","v1.5.0 (3.9)"],"AWS Config":["(vpc-flow-logs-enabled)"],"AWS Foundational Security Best Practices controls":["(EC2.6)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.10.1.3,5.4.1)"],"CMMC":["v2.0 (AC.L2-3.1.3,AU.L2-3.3.1,SI.L2-3.14.3,SI.L2-3.14.6)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7)"],"Standard10":["(D2.MA.Ma.B.1,D3.DC.Ev.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-12,SI-04,SI-04.04)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.308.a.3.ii.A,164.312.b)"],"Standard15":["(09.aa,09.m)"],"Standard14":["27001_2013 (A.12.4.1,A.13.1.1)","27002_2013 (12.4.1,13.1.1)","27002_2022 (8.15,8.16,8.20)","27017_2015 (12.4.1,13.1.1)","27018_2019 (12.4.1,13.1)","27701_2019 (6.10.1.1,6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.13.1,3.14.6,3.3.1)","800_53 Rev5 (AU-12,AU-2,SI-4,SI-4.4)"],"Standard9":["v1.1 (DE.AE-3,DE.CM-1,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.2,10.3)"],"s202":["(CC7.2)"]},"ecc-aws-423":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-272":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Security_update_management)"],"DA":["(Article_8.4.f)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-244":{"Standard1":["v7 (6.3)","v8 (4.1,8.5)"],"BSA":["v3 (DS-7,GS-10,GS-5,LT-3,LT-4,PV-1,PV-3)"],"CJIS":["(5.4.1,5.7.1)"],"CMMC":["v2.0 (AU.L2-3.3.1,CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04,LOG-08)"],"CE":["v2.2 (Secure_configuration)"],"Standard10":["(D1.G.SP.B.2,D2.MA.Ma.B.1,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12,CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_30,Article_32)"],"Standard15":["(08.k,09.aa,10.k)"],"Standard14":["27001_2013 (A.12.4.1,A.14.2.5)","27002_2013 (12.4.1,14.2.5)","27002_2022 (5.28,8.15,8.27,8.9)","27017_2015 (12.4.1,14.2.5)","27018_2019 (12.4.1,14)","27701_2019 (6.11.2.5,6.9.4.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (AU-12,AU-3,AU-3.1,CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (DE.AE-3,PR.IP-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.5,10.3,2.2)"],"s202":["(CC7.2,CC8.1)"]},"ecc-aws-168":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,13.4)"],"AWS Foundational Security Best Practices controls":["(EC2.19)"],"BSA":["v3 (GS-4,NS-1,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.1,SC.L2-3.13.2,SC.L2-3.13.6)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m,09.n)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.16,8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3,CIP-007-6_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.1,3.13.2,3.13.6,3.4.6)","800_53 Rev5 (CA-9,CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.3.6,2.2.2)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-174":{"Standard1":["v7 (12.4,9.2)","v8 (12.2)"],"BSA":["v3 (GS-4,NS-1)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-007-6_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.4.2,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.6)"]},"ecc-aws-149":{"Standard1":["v7 (12)","v8 (12.2)"],"AWS Config":["(rds-instance-public-access-check)"],"CIS AWS Foundations Benchmark":["v1.5.0 (2.3.3)"],"AWS Foundational Security Best Practices controls":["(RDS.2)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.308.a.3.i,164.308.a.4.ii.A,164.308.a.4.ii.C,164.312.a.1,164.312.e.1)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-152":{"Standard1":["v7 (5.1)","v8 (4.2)"],"AWS Foundational Security Best Practices controls":["(ELB.7)"],"BSA":["v3 (GS-10,GS-5,PV-1)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-aws-115":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-5,GS-10,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"CE":["v2.2 (Secure_configuration)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-364":{"Standard1":["v7 (12)","v8 (12.2)"],"AWS Config":["(autoscaling-launch-config-public-ip-disabled)"],"AWS Foundational Security Best Practices controls":["(AutoScaling.5)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L1-3.1.22,AC.L2-3.1.5,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,MP.L2-3.8.2,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.308.a.3.i,164.308.a.3.ii.B,164.308.a.4.ii.A,164.308.a.4.ii.C.,164.312.a.1,164.312.e.1)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.3.6,2.2.2)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-137":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,13.4)"],"BSA":["v3 (GS-4,NS-1,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.2,SC.L2-3.13.6)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m,09.n)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.16,8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.1,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CA-9,CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-253":{"Standard1":["v7 (14.6,14.8)","v8 (3.11,3.3)"],"BSA":["v3 (AM-4,DP-4,DP-5,IM-7,PA-7)"],"CJIS":["(5.10.1.2.2,5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.1,MP.L2-3.8.2,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03,IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.4.d,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,IA-05.01,MP-02,SC-28)"],"Standard6":["2016_679 (Article_32.1.a,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.a.2.iv)"],"Standard15":["(01.a,01.c,01.v,06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,5.33,8.24,8.3)","27017_2015 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (10.1.1,18.1.3,8,9.1,9.4.1)","27701_2019 (6.15.1.3,6.5.1.3,6.6.1.1,6.6.4.1,6.7.1.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.13.16,3.8.1,3.8.2)","800_53 Rev5 (AC-3,AC-6,IA-5.1,MP-2,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.AC-4,PR.DS-1)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (3.4,3.4.1,7.1,7.1.1,7.1.2,7.1.3,8.2.1)"],"s202":["(CC6.1,CC6.3)"]},"ecc-aws-246":{"Standard1":["v7 (5.1)","v8 (4.2)"],"BSA":["v3 (GS-10,GS-5,NS-2,PV-1)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (DSP-07,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.13.1,3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-094":{"Standard1":["v7 (14,6.2)","v8 (8.11)"],"CIS AWS Foundations Benchmark":["v1.4.0 (4.8)","v1.5.0 (4.8)"],"AWS Foundational Security Best Practices controls":["(CloudWatch.8)"],"BSA":["v3 (DS-7,LT-1,LT-2,LT-5)"],"CJIS":["(5.4.1,5.4.3)"],"CMMC":["v2.0 (AU.L2-3.3.5)"],"Standard4":["v4 (LOG-05)"],"DA":["(Article_9.2,Article_15.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-06,AU-06.01,AU-06.05,AU-07)"],"Standard5":["(164.308.a.1.ii.D,164.312.b)"],"Standard14":["27001_2013 (A.12.4.1,A.16.1.4)","27002_2013 (12.4.1,16.1.4)","27002_2022 (5.25)","27017_2015 (12.4.1,16.1.4)","27018_2019 (12.4.1,6.1.4)","27701_2019 (6.13.1.4,6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.4)"],"Standard11":["800-171 Rev2 (3.3.3,3.3.5)","800_53 Rev5 (AU-6,AU-6.1,AU-6.5,AU-7)"],"Standard9":["v1.1 (DE.AE-2,PR.PT-1,RS.AN-1)"],"Standard8":["v3.2.1 (10.6,10.6.1,10.6.2)"],"s202":["(CC4.1,CC7.3)"]},"ecc-aws-201":{"Standard1":["v7 (5.1)","v8 (4.1)"],"AWS Config":["(rds-instance-deletion-protection-enabled)"],"AWS Foundational Security Best Practices controls":["(RDS.8)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.13.2,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.DS-3,PR.IP-1,PR.IP-6)"],"s202":["(CC8.1)"]},"ecc-aws-260":{"Standard1":["v7 (6.2,6.4)","v8 (8.2,8.3)"],"BSA":["v3 (DS-7,LT-3,LT-4,LT-6)"],"CJIS":["(5.4.1,5.4.6)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_8.4.e,Article_10.7)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-04,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa,09.h)"],"Standard14":["27001_2013 (A.12.1.3,A.12.4.1)","27002_2013 (12.1.3,12.4.1)","27002_2022 (8.15,8.6)","27017_2015 (12.1.3,12.4.1)","27018_2019 (12.1.3,12.4.1)","27701_2019 (6.9.1.3,6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2,AU-4)"],"Standard9":["v1.1 (DE.AE-3,PR.DS-4,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.2,10.3,10.7)"],"s202":["(A1.1,CC7.2)"]},"ecc-aws-462":{"Standard1":["v7 (12)","v8 (4.1)"],"AWS Config":["(lambda-concurrency-check)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3,NS-8)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-07,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32.1.b)"],"Standard15":["(08.k,10.k,10.m)"],"Standard14":["27001_2013 (A.9.1.1,A.9.4.1,A.13.1.2)","27002_2013 (9.1.1,9.4.1,13.1.2)","27002_2022 (5.10,8.3,8.21)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-7,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC6.6)"]},"ecc-aws-045":{"Standard1":["v7 (4.4)","v8 (5.2)"],"CIS AWS Foundations Benchmark":["v1.2.0 (1.5)"],"AWS Config":["(iam-password-policy)"],"AWS Foundational Security Best Practices controls":["(IAM.7,IAM.11)"],"CJIS":["(5.6.2.1.1)"],"CMMC":["v2.0 (IA.L2-3.5.7)"],"Standard4":["v4 (IAM-02)"],"CE":["v2.2 (Password_based_Authentication)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.7)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01)"],"Standard5":["(164.308.a.4.ii.B,164.308.a.5.ii.D,164.312.d,164.314.b.2.i)"],"Standard15":["(01.f)"],"Standard14":["27001_2013 (A.9.4.3)","27002_2013 (9.4.3)","27002_2022 (5.17)","27017_2015 (9.4.3)","27018_2019 (9.4.3)","27701_2019 (6.6.4.3)"],"Standard12":["(CIP-007-6_Requirement_R5_Part_5.5)"],"Standard11":["800-171 Rev2 (3.5.7)","800_53 Rev5 (IA-5.1)"],"Standard8":["v3.2.1 (2.1,8.2.3,8.2.6)"],"s202":["(CC6.1)"]},"ecc-aws-316":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CIS Oracle Database 19 Benchmark":["v1.0.0 (2.2.3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-133":{"Standard1":["v7 (12.3,12.6,6.3)","v8 (10.7,13.3,8.5)"],"AWS Config":["(guardduty-enabled-centralized)"],"AWS Foundational Security Best Practices controls":["(GuardDuty.1)"],"BSA":["v3 (DS-7,LT-3,LT-4,NS-4)"],"CJIS":["(5.10.1.3,5.13.4,5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1,SI.L2-3.14.6,SI.L2-3.14.7)"],"Standard4":["v4 (IVS-09,LOG-08)"],"CE":["v2.2 (A8.3)"],"DA":["(Article_14.d,Article_8.2,Article_9,Article_8.4.e,Article_10.7)"],"Standard10":["(D2.MA.Ma.B.1,D3.DC.An.B.1,D3.DC.Ev.B.3,D3.PC.Im.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12,SI-04,SI-04.04)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.308.a.5.ii.C,164.308.a.6.ii,164.308.a.8,164.312.b)"],"Standard15":["(09.aa,09.ab,09.m)"],"Standard14":["27001_2013 (A.12.2.1,A.12.4.1,A.13.1.1)","27002_2013 (12.2.1,12.4.1,13.1.1)","27002_2022 (5.28,8.1,8.15,8.16,8.21,8.7)","27017_2015 (12.2.1,12.4.1,13.1.1)","27018_2019 (12.2,12.4.1,13.1)","27701_2019 (6.10.1.1,6.9.2.1,6.9.4.1)"],"Standard12":["(CIP-005-7_Requirement_R1_Part_1.5)"],"Standard11":["800-171 Rev2 (3.14.6)","800_53 Rev5 (AU-12,AU-3,AU-3.1,SI-4,SI-4.4)"],"Standard9":["v1.1 (DE.AE-3,DE.CM-1,DE.CM-4)"],"Standard7":["(11,6)"],"Standard8":["v3.2.1 (10.1,10.2.2,10.2.4,10.2.5,10.3,11.4)"],"s202":["(CC6.6,CC6.8,CC7.2)"]},"ecc-aws-145":{"Standard1":["v7 (6.2,16)","v8 (8.11)"],"CIS AWS Foundations Benchmark":["v1.4.0 (4.15)","v1.5.0 (4.15)"],"BSA":["v3 (DS-7,LT-1,LT-2,LT-5)"],"CJIS":["(5.4.1,5.4.3)"],"CMMC":["v2.0 (AU.L2-3.3.5)"],"Standard4":["v4 (LOG-05)"],"DA":["(Article_9.2,Article_15.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-06,AU-06.01,AU-06.05,AU-07)"],"Standard5":["(164.308.a.1.ii.D,164.312.b)"],"Standard14":["27001_2013 (A.12.4.1,A.16.1.4)","27002_2013 (12.4.1,16.1.4)","27002_2022 (5.25)","27017_2015 (12.4.1,16.1.4)","27018_2019 (12.4.1,6.1.4)","27701_2019 (6.13.1.4,6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.4)"],"Standard11":["800-171 Rev2 (3.3.3,3.3.5)","800_53 Rev5 (AU-6,AU-6.1,AU-6.5,AU-7)"],"Standard9":["v1.1 (DE.AE-2,PR.PT-1,RS.AN-1)"],"Standard8":["v3.2.1 (10.6,10.6.1,10.6.2)"],"s202":["(CC4.1,CC7.3)"]},"ecc-aws-262":{"Standard1":["v7 (5.1,9.2)","v8 (12.2,4.1)"],"BSA":["v3 (GS-10,GS-4,GS-5,NS-2,PV-1,PV-3)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (AC.L1-3.1.20,CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03,APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,DSP-07,IVS-03,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.1,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-07,CM-09,PL-08,SA-03,SA-08,SA-10,SC-07)"],"Standard6":["2016_679 (Article_32,Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,08.k,09.m,10.k)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3,A.14.2.5)","27002_2013 (13.1.1,13.1.3,14.2.5)","27002_2022 (8.20,8.22,8.27,8.9)","27017_2015 (13.1.1,13.1.3,14.2.5)","27018_2019 (13.1,14)","27701_2019 (6.10.1.1,6.10.1.3,6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.4.1,3.4.2,3.4.6)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-7,CM-9,PL-8,PM-7,SA-10,SA-3,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6,CC8.1)"]},"ecc-aws-158":{"Standard1":["v7 (1.4)","v8 (1.1)"],"AWS Foundational Security Best Practices controls":["(RDS.17)"],"BSA":["v3 (AM-1,AM-3)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"DA":["(Article_7.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a,07.e)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"s202":["(CC6.1)"]},"ecc-aws-092":{"Standard1":["v7 (11.3,12,14.6)","v8 (3.3,6,5.3)"],"CIS AWS Compute Services Benchmark":["v1.0.0 (2.1.5)"],"BSA":["v3 (AM-4,IM-7,PA-7)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.3.b,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.3)","27017_2015 (8.1.3,9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-aws-403":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-528":{"Standard1":["v7 (6.2,6.3)","v8 (8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_8.4.e,Article_10.7)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.308.a.3.ii.A,164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.2,10.2.4,10.2.5,10.3)"],"s202":["(CC7.2)"]},"ecc-aws-222":{"Standard1":["v7 (1.4,2,5.1,5.4)","v8 (1.1,4.1,4.6)"],"AWS Config":["(ec2-instance-managed-by-systems-manager)"],"AWS Foundational Security Best Practices controls":["(SSM.1)"],"CIS AWS Compute Services Benchmark":["v1.0.0 (2.9)"],"BSA":["v3 (AM-1,AM-3,GS-10,GS-5,PV-1,PV-2,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.15)"],"Standard2":["19 (APO13,BAI06,BAI09,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_7.1,Article_8.4.b,Article_8.4.e)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-07,CM-08,CM-08.01,CM-09,MA-04,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.308.a.1.ii.B,164.308.a.5.ii.B)"],"Standard15":["(07.a,07.c,08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.2,A.8.1.1)","27002_2013 (14.2.2,8.1.1)","27002_2022 (5.9,8.32,8.9)","27017_2015 (14.2.2,8.1.1)","27018_2019 (14,8)","27701_2019 (6.11.2.2,6.5.1.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-7,CM-8,CM-8.1,CM-9,MA-4,PM-5,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3,PR.IP-1,PR.IP-2)"],"s202":["(A1.1,CC3.4)"]},"ecc-aws-446":{"Standard1":["v7 (6.2,6.3)","v8 (4.1,8.2,8.5)"],"BSA":["v3 (DS-7,GS-10,GS-5,LT-3,LT-4,PV-1,PV-3)"],"Standard13":["v1.0.0 (3.1.20)"],"CJIS":["(5.4.1,5.7.1)"],"CMMC":["v2.0 (AU.L2-3.3.1,CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10,DSS03)"],"Standard4":["v4 (CCC-01,IVS-04,LOG-08)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.e,Article_10.7)"],"Standard10":["(D1.G.SP.B.2,D2.MA.Ma.B.1,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-03,AU-12,CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_30,Article_32)"],"Standard5":["(164.312.b)"],"Standard15":["(08.k,09.aa,10.k)"],"Standard14":["27001_2013 (A.12.4.1,A.14.2.5)","27002_2013 (12.4.1,14.2.5)","27002_2022 (5.28,8.15,8.27,8.9)","27017_2015 (12.4.1,14.2.5)","27018_2019 (12.4.1,14)","27701_2019 (6.11.2.5,6.9.4.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-007-6_Requirement_R4_Part_4.1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.3.1,3.4.1,3.4.2)","800_53 Rev5 (AU-12,AU-2,AU-3,AU-3.1,CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (DE.AE-3,PR.IP-1,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.5,10.3,2.2)"],"s202":["(CC7.2,CC8.1)"]},"ecc-aws-209":{"Standard1":["v7 (6.2,6.3)","v8 (3.14,8.2,8.5)"],"AWS Config":["(rds-logging-enabled)"],"AWS Foundational Security Best Practices controls":["(RDS.9)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (DSP-17,LOG-04,LOG-08)"],"DA":["(Article_10.7,Article_8.3.d,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-06.09,AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.308.a.3.ii.A,164.308.a.5.ii.C,164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AC-6.9,AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.1,10.2.5,10.3)"],"s202":["(CC6.1,CC7.2)"]},"ecc-aws-223":{"Standard1":["v7 (5.1)","v8 (4.1,4.6)"],"AWS Config":["(ec2-managedinstance-association-compliance-status-check)"],"AWS Foundational Security Best Practices controls":["(SSM.3)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-2,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.15)"],"Standard2":["19 (APO13,BAI06,BAI09,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_7.1,Article_8.4.b,Article_8.4.e)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-07,CM-09,MA-04,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.308.a.1.ii.B,164.308.a.5.ii.B)"],"Standard15":["(07.c,08.k,10.k)"],"Standard14":["27001_2013 (A.12.6.1)","27002_2013 (12.6.1)","27002_2022 (8.27,8.8)","27017_2015 (12.6.1)","27018_2019 (12.6)","27701_2019 (6.9.6.1)"],"Standard11":["800-171 Rev2 (3.14.1,3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-7,CM-9,MA-4,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (DE.CM-8,PR.IP-1,PR.IP-2)"],"s202":["(CC2.2,CC7.1)"]},"ecc-aws-055":{"Standard1":["v7 (6.2,6.5)","v8 (8.5,8.9)"],"CIS AWS Foundations Benchmark":["v1.4.0 (3.4)","v1.5.0 (3.4)"],"AWS Config":["(cloud-trail-cloud-watch-logs-enabled)"],"AWS Foundational Security Best Practices controls":["(CloudTrail.5)"],"BSA":["v3 (DS-7,LT-3,LT-4,LT-5)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_8.4.d,Article_10.7)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-06.03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.308.a.3.ii.A,164.312.b,164.312.e.1)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1,AU-6.3)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.2,10.2.4,10.2.5,10.3,10.5.3,10.5.4)"],"s202":["(CC7.2,PL1.4)"]},"ecc-aws-015":{"Standard1":["v7 (4.5)","v8 (6.5)"],"CIS AWS Foundations Benchmark":["v1.4.0 (1.5)","v1.5.0 (1.5)"],"AWS Foundational Security Best Practices controls":["(IAM.9)"],"AWS Config":["(root-account-mfa-enabled)"],"BSA":["v3 (GS-6,IM-2)"],"CJIS":["(5.6.2.2,5.6.2.2.1)"],"CMMC":["v2.0 (IA.L2-3.5.3)"],"Standard4":["v4 (IAM-14)"],"CE":["v2.2 (A7.16,uac)"],"DA":["(Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.3)"],"Standard3":["Standard11_800_53_Rev5 (IA-02.01)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.312.a.1,164.312.d)"],"Standard15":["(01.q)"],"Standard14":["27001_2013 (A.9.3.1,A.9.4.2)","27002_2013 (9.3.1,9.4.2)","27002_2022 (5.17,8.5)","27017_2015 (9.3.1,9.4.2)","27018_2019 (9.3.1,9.4.2)","27701_2019 (6.6.3.1,6.6.4.2)"],"Standard11":["800-171 Rev2 (3.5.3)","800_53 Rev5 (IA-2.1)"],"Standard9":["v1.1 (PR.AC-7)"],"Standard8":["v3.2.1 (8.3,8.3.1,8.3.2)"],"s202":["(CC6.1)"]},"ecc-aws-191":{"Standard1":["v7 (10.1)","v8 (11.2)"],"AWS Config":["(efs-in-backup-plan)"],"AWS Foundational Security Best Practices controls":["(EFS.2)"],"BSA":["v3 (BR-1)"],"Standard4":["v4 (BCR-08)"],"CE":["v2.2 (Back_up_your_data)"],"DA":["(Article_11.2,Article_11.4)"],"Standard10":["(D5.IR.Pl.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CP-09,CP-10)"],"Standard6":["2016_679 (Article_32.1.c)"],"Standard5":["(164.308.a.7.i,164.308.a.7.ii.A,164.308.a.7.ii.B,164.312.a.2.ii)"],"Standard15":["(09.l)"],"Standard14":["27001_2013 (A.12.3.1)","27002_2013 (12.3.1)","27002_2022 (8.13)","27017_2015 (12.3.1)","27018_2019 (12.3.1)","27701_2019 (6.9.3.1)"],"Standard12":["(CIP-009-6_Requirement_R1_Part_1.3)"],"Standard11":["800_53 Rev5 (CP-10,CP-9)"],"Standard9":["v1.1 (PR.IP-4)"],"Standard7":["(6s)"],"Standard8":["v3.2.1 (12.10.1)"],"s202":["(A1.2)"]},"ecc-aws-059":{"Standard1":["v7 (1.4,16.1,5.5)","v8 (1.1)"],"CIS AWS Foundations Benchmark":["v1.4.0 (3.5)","v1.5.0 (3.5)"],"AWS Foundational Security Best Practices controls":["(Config.1)"],"BSA":["v3 (AM-1,AM-3)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-289":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR-PT-5,PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-409":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-268":{"Standard1":["v7 (14.6,14.8)","v8 (3.11,3.3)"],"AWS Config":["(elasticache-repl-grp-encrypted-at-rest)"],"BSA":["v3 (AM-4,DP-4,DP-5,IM-7,PA-7)"],"CJIS":["(5.10.1.2.2,5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.1,MP.L2-3.8.2,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03,IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.4.d,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,IA-05.01,MP-02,SC-28)"],"Standard6":["2016_679 (Article_32.1.a,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.a.2.iv)"],"Standard15":["(01.a,01.c,01.v,06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,5.33,8.24,8.3)","27017_2015 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (10.1.1,18.1.3,8,9.1,9.4.1)","27701_2019 (6.15.1.3,6.5.1.3,6.6.1.1,6.6.4.1,6.7.1.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.13.16,3.8.1,3.8.2)","800_53 Rev5 (AC-3,AC-6,IA-5.1,MP-2,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.AC-4,PR.DS-1)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (3.4,3.4.1,7.1,7.1.1,7.1.2,7.1.3,8.2.1)"],"s202":["(CC6.1,CC6.3)"]},"ecc-aws-334":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (DP-4,DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28,SC-28.01)"],"Standard6":["2016_679 (Article_32.1.a,Article_5.1.f)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)"],"s202":["(CC6.1)"]},"ecc-aws-105":{"Standard1":["v7 (16)","v8 (5)"],"BSA":["v3 (DP-6)"],"CMMC":["v2.0 (SC.L2-3.13.10)"],"Standard4":["v4 (CEK-12)"],"Standard3":["Standard11_800_53_Rev5 (AC-02,AC-03,AC-17,IA-07,SC-12,SC-13)"],"Standard5":["(164.312.a.2.iv)"],"Standard14":["27001_2013 (A.10.1.1,A.10.1.2,A.9.1.1,A.9.1.2,A.9.2.1,A.9.2.5)","27002_2013 (10.1.1,10.1.2,9.1.1,9.1.2,9.2.1,9.2.5)","27002_2022 (5.15,5.18,8.24)","27017_2015 (10.1.1,10.1.2,9.1.1,9.1.2,9.2.1,9.2.5)","27018_2019 (10.1.1,10.1.2,9.1,9.2.1,9.2.5)","27701_2019 (6.6.4.1,6.7.1.1,6.7.1.2)"],"Standard11":["800_53 Rev5 (AC-17,AC-2,AC-3,IA-7,SC-12,SC-13)"],"Standard9":["v1.1 (PR.AC-1,PR.AC-3,PR.AC-4,PR.AC-5)"],"Standard8":["v3.2.1 (10.1,10.2,10.3)"],"s202":["(CC6.1)"]},"ecc-aws-461":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Security_update_management)"],"DA":["(Article_8.2,Article_8.4.a)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-157":{"Standard1":["v7 (1.4)","v8 (1.1)"],"AWS Foundational Security Best Practices controls":["(RDS.16)"],"BSA":["v3 (AM-1,AM-3)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-06)"],"DA":["(Article_7.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a,07.e)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"s202":["(CC6.1)"]},"ecc-aws-540":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Security_update_management)"],"DA":["(Article_8.4.f)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-020":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1,AM-3)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-028":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,13.4)"],"BSA":["v3 (GS-4,NS-1,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.2,SC.L2-3.13.6)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m,09.n)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.16,8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.1,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CA-9,CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-546":{"Standard1":["v7 (6)","v8 (8.10)"],"BSA":["v3 (LT-6)"],"CJIS":["(5.4.6,5.4.7)"],"Standard4":["v4 (LOG-02)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-11)"],"Standard6":["2016_679 (Article_5.1.e)"],"Standard15":["(09.ac)"],"Standard14":["27001_2013 (A.12.4.2,A.18.1.3)","27002_2013 (12.4.2,18.1.3)","27002_2022 (5.28)","27017_2015 (12.4.2,18.1.3)","27018_2019 (12.4.2,18.1)","27701_2019 (6.15.1.3,6.9.4.2)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.3)"],"Standard11":["800_53 Rev5 (AU-11)"],"Standard8":["v3.2.1 (10.7)"],"s202":["(C1.1)"]},"ecc-aws-287":{"Standard1":["v7 (5.1)","v8 (4.1)"],"AWS Config":["(autoscaling-multiple-az)"],"AWS Foundational Security Best Practices controls":["(AutoScaling.2)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (IVS-02)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.2,Article_8.4.a,Article_11.5)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,CP-06,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k,12.c)"],"Standard14":["27001_2013 (A.14.2.5,A.17.2.1)","27002_2013 (14.2.5,17.2.1)","27002_2022 (8.14,8.27,8.9)","27017_2015 (14.2.5,17.2.1)","27018_2019 (14,17)","27701_2019 (6.11.2.5,6.14.2.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,CP-6,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR-PT-5,PR.IP-1)"],"s202":["(CC8.1)"]},"ecc-aws-056":{"Standard1":["v7 (16)","v8 (3.3,5.4)"],"CIS AWS Foundations Benchmark":["v1.4.0 (1.11)","v1.5.0 (1.11)"],"BSA":["v3 (AM-4,IM-2,IM-7,PA-1,PA-7)"],"CJIS":["(5.5.2,5.5.2.1)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,AC.L2-3.1.6,AC.L2-3.1.7,MP.L2-3.8.2,SC.L2-3.13.3)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05,IAM-09)"],"CE":["v2.2 (A7.4,A7.5,A7.6,A7.7,Secure_configuration,uac)"],"DA":["(Article_8.3.b,Article_8.4)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Am.B.3)"],"Standard3":["Standard11_800_53_Rev5 (AC-02.07,AC-03,AC-06,AC-06.02,AC-06.05,MP-02)"],"Standard6":["2016_679 (Article_25,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.e,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.2.3,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.2.3,9.4.1)","27002_2022 (5.10,5.15,8.2,8.3)","27017_2015 (8.1.3,9.1.1,9.2.3,9.4.1)","27018_2019 (8,9.1,9.2.3,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.2.3,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.1.6,3.1.7,3.8.2)","800_53 Rev5 (AC-2.7,AC-3,AC-6,AC-6.2,AC-6.5,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-aws-469":{"Standard1":["v7 (9.2)","v8 (12.2)"],"AWS Config":["(alb-desync-mode-check)"],"AWS Foundational Security Best Practices controls":["(ELB.12)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1)"],"CMMC":["v2.0 (AC.L1-3.1.20,CM.L2-3.4.1,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard11":["800-171 Rev2 (3.13.2,3.4.2)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"]},"ecc-aws-079":{"Standard1":["v7 (16)","v8 (8.11)"],"CIS AWS Foundations Benchmark":["v1.4.0 (4.4)","v1.5.0 (4.4)"],"AWS Foundational Security Best Practices controls":["(CloudWatch.4)"],"BSA":["v3 (DS-7,LT-1,LT-2,LT-5)"],"CJIS":["(5.4.1,5.4.3)"],"CMMC":["v2.0 (AU.L2-3.3.5)"],"Standard4":["v4 (LOG-05)"],"DA":["(Article_9.2,Article_15.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-06,AU-06.01,AU-06.05,AU-07)"],"Standard5":["(164.308.a.1.ii.D,164.312.b)"],"Standard14":["27001_2013 (A.12.4.1,A.16.1.4)","27002_2013 (12.4.1,16.1.4)","27002_2022 (5.25)","27017_2015 (12.4.1,16.1.4)","27018_2019 (12.4.1,6.1.4)","27701_2019 (6.13.1.4,6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.4)"],"Standard11":["800-171 Rev2 (3.3.3,3.3.5)","800_53 Rev5 (AU-6,AU-6.1,AU-6.5,AU-7)"],"Standard9":["v1.1 (DE.AE-2,PR.PT-1,RS.AN-1)"],"Standard8":["v3.2.1 (10.6,10.6.1,10.6.2)"],"s202":["(CC4.1,CC7.3)"]},"ecc-aws-281":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.2,Article_8.4.a)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32.1.b,Article_32.1.c)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.12.1.3,A.14.2.5)","27002_2013 (12.1.3,14.2.5)","27002_2022 (8.27,8.6,8.9)","27017_2015 (12.1.3,14.2.5)","27018_2019 (12.1.3,14)","27701_2019 (6.11.2.5,6.9.1.3)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-460":{"Standard1":["v7 (14.8)","v8 (3.10)"],"CIS AWS Compute Services Benchmark":["v1.0.0 (4.12)"],"BSA":["v3 (DP-3)"],"CJIS":["(5.10.1.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IVS-03)"],"DA":["(Article_8.2,Article_8.3.a,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12,D3.PC.Am.B.13)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,SC-08)"],"Standard6":["2016_679 (Article_32.1.a,Article_44)"],"Standard5":["(164.312.e.1)"],"Standard15":["(06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1,A.18.1.3)","27002_2013 (10.1.1,13.2.1,18.1.3)","27002_2022 (5.14,5.33,8.24)","27017_2015 (10.1.1,13.2.1,18.1.3)","27018_2019 (10.1.1,13.2.1,18.1.3)","27701_2019 (6.10.2.1,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.8,3.5.10)","800_53 Rev5 (AC-17.2,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-2)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1.1,4.1,4.1.1,8.2.1)"],"s202":["(CC6.1,CC6.7)"]},"ecc-aws-477":{"Standard1":["v7 (5.5)","v8 (4.1)"],"AWS Config":["(cloudformation-stack-notification-check)"],"AWS Foundational Security Best Practices controls":["(CloudFormation.1)"],"BSA":["v3 (GS-5,PV-2)"],"CJIS":["(5.10.4.4,5.4.3)"],"CMMC":["v2.0 (AU.L2-3.3.5,CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-07,LOG-03)"],"CE":["v2.2 (Secure_configuration)"],"Standard10":["(D1.G.SP.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-06,SA-03,SA-08)"],"Standard15":["(10.k)"],"Standard14":["27001_2013 (A.14.2.5,A.16.1.2)","27002_2013 (14.2.5,16.1.2)","27002_2022 (6.8,8.27,8.9)","27017_2015 (14.2.5,16.1.2)","27018_2019 (14,16.1.2)","27701_2019 (6.11.2.5,6.13.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-007-6_Requirement_R4_Part_4.2)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-6,SA-3,SA-8)"],"Standard9":["v1.1 (DE.DP-4,PR.IP-1)"],"Standard8":["v3.2.1 (11.5,2.2,A.3.3.1)"],"s202":["(CC8.1)"]},"ecc-aws-388":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-070":{"Standard1":["v7 (14)","v8 (12.2)"],"AWS Foundational Security Best Practices controls":["(EC2.22)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-057":{"Standard1":["v7 (19)","v8 (6.8)"],"CIS AWS Foundations Benchmark":["v1.4.0 (1.18)","v1.5.0 (1.18)"],"AWS Config":["(ec2-instance-profile-attached)"],"BSA":["v3 (PA-1,PA-7)"],"DA":["(Article_8.3.b)"],"CJIS":["(5.5.1,5.5.2,5.5.2.1,5.5.2.2,5.5.2.3,5.5.2.4)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L2-3.1.4,AC.L2-3.1.5,SC.L2-3.13.3)"],"Standard4":["v4 (IAM-04,IAM-05,IAM-16)"],"CE":["v2.2 (A7.4,A7.8,A7.9)"],"Standard10":["(D1.R.St.B.1,D3.PC.Am.B.1,D3.PC.Am.B.2,D3.PC.Am.B.4)"],"Standard3":["Standard11_800_53_Rev5 (AC-02,AC-02.07,AC-03,AC-05,AC-06,AC-06.01,AC-06.07,AU-9.4)"],"Standard6":["2016_679 (Article_25)"],"Standard5":["(164.308.a.3.i,164.312.a.1)"],"Standard15":["(01.c)"],"Standard14":["27001_2013 (A.6.1.2,A.9,A.9.4.1)","27002_2013 (6.1.2,9,9.4.1)","27002_2022 (5.15,5.3,8.2,8.3)","27017_2015 (6.1.2,9,9.4.1)","27018_2019 (6.1.2,9,9.4.1)","27701_2019 (6.10.1.3,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-004-6_Requirement_R4_Part_4.3,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.4,3.1.5)","800_53 Rev5 (AC-2,AC-2.7,AC-3.7,AC-5,AC-6,AC-6.1,AC-6.7,AU-9.4)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"s202":["(CC5.2,CC6.3)"]},"ecc-aws-453":{"Standard1":["v7 (6.2,6.3)","v8 (8.2,8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_8.4.e,Article_10.7)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.5,10.3)"],"s202":["(CC7.2)"]},"ecc-aws-307":{"Standard1":["v7 (5.1,6.3)","v8 (4.1,8.5)"],"BSA":["v3 (DS-7,GS-10,GS-5,LT-3,LT-4,PV-1,PV-3)"],"CJIS":["(5.4.1,5.7.1)"],"CMMC":["v2.0 (AU.L2-3.3.1,CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04,LOG-08)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.e,Article_10.7)"],"Standard10":["(D1.G.SP.B.2,D2.MA.Ma.B.1,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12,CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_30,Article_32)"],"Standard15":["(08.k,09.aa,10.k)"],"Standard14":["27001_2013 (A.12.4.1,A.14.2.5)","27002_2013 (12.4.1,14.2.5)","27002_2022 (5.28,8.15,8.27,8.9)","27017_2015 (12.4.1,14.2.5)","27018_2019 (12.4.1,14)","27701_2019 (6.11.2.5,6.9.4.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (AU-12,AU-3,AU-3.1,CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (DE.AE-3,PR.IP-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.5,10.3,2.2)"],"s202":["(CC7.2,CC8.1)"]},"ecc-aws-473":{"Standard1":["v7 (9.2)","v8 (12.2)"],"AWS Config":["(clb-desync-mode-check)"],"AWS Foundational Security Best Practices controls":["(ELB.14)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1)"],"CMMC":["v2.0 (AC.L1-3.1.20,CM.L2-3.4.1,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard11":["800-171 Rev2 (3.13.2,3.4.2)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"]},"ecc-aws-113":{"Standard1":["v7 (16)","v8 (6)"],"AWS Config":["(iam-no-inline-policy-check)"],"Standard3":["Standard11_800_53_Rev4 (AC-1,AC-18,AC-2,CA-1)"],"Standard5":["(164.312.a.1)"],"Standard14":["27001_2013 (A.9.1.1,A.9.1.2,A.9.2.1,A.9.2.2,A.9.2.3,A.9.2.4)","27002_2013 (9.1.1,9.1.2,9.2.1,9.2.2,9.2.3,9.2.4)","27017_2015 (9.1.1,9.1.2,9.2.1,9.2.2,9.2.3,9.2.4)","27018_2019 (9.1,9.2.1,9.2.2,9.2.3,9.2.4)","27701_2019 (6.6.2,6.6.3,6.6.4)"],"Standard11":["800_53 Rev5 (AC-1,AC-18,AC-2,CA-1)"],"Standard9":["v1.1 (PR.AC-1,PR.AC-3,PR.AC-4,PR.AC-5)"],"Standard8":["v3.2.1 (10.1,10.2,10.3)"]},"ecc-aws-048":{"Standard1":["v7 (4.4)","v8 (5.2)"],"CIS AWS Foundations Benchmark":["v1.2.0 (1.7)"],"AWS Config":["(iam-password-policy)"],"AWS Foundational Security Best Practices controls":["(IAM.7,IAM.13)"],"CJIS":["(5.6.2.1.1)"],"CMMC":["v2.0 (IA.L2-3.5.7)"],"Standard4":["v4 (IAM-02)"],"CE":["v2.2 (Password_based_Authentication)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.7)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01)"],"Standard5":["(164.308.a.4.ii.B,164.308.a.5.ii.D,164.312.d,164.314.b.2.i)"],"Standard15":["(01.f)"],"Standard14":["27001_2013 (A.9.4.3)","27002_2013 (9.4.3)","27002_2022 (5.17)","27017_2015 (9.4.3)","27018_2019 (9.4.3)","27701_2019 (6.6.4.3)"],"Standard12":["(CIP-007-6_Requirement_R5_Part_5.5)"],"Standard11":["800-171 Rev2 (3.5.7)","800_53 Rev5 (IA-5.1)"],"Standard8":["v3.2.1 (2.1,8.2.3,8.2.6)"],"s202":["(CC6.1)"]},"ecc-aws-408":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-495":{"Standard1":["v7 (5.1)","v8 (4.1)"],"AWS Config":["(ecs-task-definition-memory-hard-limit)"],"BSA":["v3 (GS-10,GS-5,NS-8,PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-07,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32.1.b)"],"Standard15":["(08.k,10.k,10.m)"],"Standard14":["27001_2013 (A.13.1.2,A.9.1.1,A.9.4.1)","27002_2013 (13.1.2,9.1.1,9.4.1)","27002_2022 (5.10,8.21,8.3)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-7,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC6.6)"]},"ecc-aws-420":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-479":{"Standard1":["v7 (14.6,14.8)","v8 (3.11,3.3)"],"AWS Config":["(cloudwatch-log-group-encrypted)"],"BSA":["v3 (AM-4,DP-4,DP-5,IM-7,PA-7)"],"CJIS":["(5.10.1.2.2,5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.1,MP.L2-3.8.2,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03,IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.4.d,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,IA-05.01,MP-02,SC-28)"],"Standard6":["2016_679 (Article_32.1.a,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.a.2.iv)"],"Standard15":["(01.a,01.c,01.v,06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,5.33,8.24,8.3)","27017_2015 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (10.1.1,18.1.3,8,9.1,9.4.1)","27701_2019 (6.15.1.3,6.5.1.3,6.6.1.1,6.6.4.1,6.7.1.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.13.16,3.8.1,3.8.2)","800_53 Rev5 (AC-3,AC-6,IA-5.1,MP-2,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.AC-4,PR.DS-1)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (3.4,3.4.1,7.1,7.1.1,7.1.2,7.1.3,8.2.1)"],"s202":["(CC6.1,CC6.3)"]},"ecc-aws-035":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,13.4)"],"BSA":["v3 (GS-4,NS-1,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.2,SC.L2-3.13.6)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m,09.n)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.16,8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.1,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CA-9,CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-577":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800_53 Rev5 (CM-8,CM-8.1)"],"Standard9":["v1.1 (PR.DS-3)"]},"ecc-aws-258":{"Standard1":["v7 (14.4,14.8)","v8 (3.10,3.11)"],"BSA":["v3 (DP-3,DP-4,DP-5)"],"CJIS":["(5.10.1.2.1,5.10.1.2.2)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.16,SC.L2-3.13.8)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IVS-03)"],"DA":["(Article_8.2,Article_8.3.a,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.13)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,IA-05.01,SC-08,SC-28)"],"Standard6":["2016_679 (Article_32.1.a,Article_44)"],"Standard5":["(164.312.a.2.iv,164.312.e.1)"],"Standard15":["(06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1,A.18.1.3)","27002_2013 (10.1.1,13.2.1,18.1.3)","27002_2022 (5.14,5.33,8.24)","27017_2015 (10.1.1,13.2.1,18.1.3)","27018_2019 (10.1.1,13.2.1,18.1.3)","27701_2019 (6.10.2.1,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.16,3.13.8,3.8.1)","800_53 Rev5 (AC-17.2,IA-5.1,SC-28,SC-28.1,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-1,PR.DS-2)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1.1,3.4,3.4.1,4.1,4.1.1,8.2.1)"],"s202":["(CC6.1,CC6.7)"]},"ecc-aws-138":{"Standard1":["v7 (4.3)","v8 (5.4)"],"CIS AWS Foundations Benchmark":["v1.4.0 (1.7)","v1.5.0 (1.7)"],"AWS Foundational Security Best Practices controls":["(IAM.20)"],"BSA":["v3 (IM-2,PA-1)"],"CJIS":["(5.5.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.5,AC.L2-3.1.6,AC.L2-3.1.7,SC.L2-3.13.3)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-09)"],"CE":["v2.2 (A7.5,A7.6,A7.7,uac)"],"DA":["(Article_8.4)"],"Standard10":["(D3.PC.Am.B.3)"],"Standard3":["Standard11_800_53_Rev5 (AC-02.07,AC-06.02,AC-06.05)"],"Standard6":["2016_679 (Article_25)"],"Standard15":["(01.e)"],"Standard14":["27001_2013 (A.9.2.3)","27002_2013 (9.2.3)","27002_2022 (5.15,8.2)","27017_2015 (9.2.3)","27018_2019 (9.2.3)","27701_2019 (6.6.2.3)"],"Standard11":["800-171 Rev2 (3.1.6,3.1.7)","800_53 Rev5 (AC-2.7,AC-6.2,AC-6.5)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)"],"s202":["(CC6.3)"]},"ecc-aws-454":{"Standard1":["v7 (5.5)","v8 (4.1)"],"BSA":["v3 (GS-5,PV-2)"],"CJIS":["(5.10.4.4,5.4.3)"],"CMMC":["v2.0 (AU.L2-3.3.5,CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-07,LOG-03)"],"CE":["v2.2 (Secure_configuration)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5,A.16.1.2)","27002_2013 (14.2.5,16.1.2)","27002_2022 (6.8,8.27,8.9)","27017_2015 (14.2.5,16.1.2)","27018_2019 (14,16.1.2)","27701_2019 (6.11.2.5,6.13.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-007-6_Requirement_R4_Part_4.2)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (DE.DP-4,PR.IP-1)"],"Standard8":["v3.2.1 (2.2,A.3.3.1)"],"s202":["(CC8.1)"]},"ecc-aws-095":{"Standard1":["v7 (1.4,11.2,16.1)","v8 (8.11)"],"CIS AWS Foundations Benchmark":["v1.4.0 (4.9)","v1.5.0 (4.9)"],"AWS Foundational Security Best Practices controls":["(CloudWatch.9)"],"BSA":["v3 (DS-7,LT-1,LT-2,LT-5)"],"CJIS":["(5.4.1,5.4.3)"],"CMMC":["v2.0 (AU.L2-3.3.5)"],"Standard4":["v4 (LOG-05)"],"DA":["(Article_9.2,Article_15.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-06,AU-06.01,AU-06.05,AU-07)"],"Standard5":["(164.308.a.1.ii.D,164.312.b)"],"Standard14":["27001_2013 (A.12.4.1,A.16.1.4)","27002_2013 (12.4.1,16.1.4)","27002_2022 (5.25)","27017_2015 (12.4.1,16.1.4)","27018_2019 (12.4.1,6.1.4)","27701_2019 (6.13.1.4,6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.4)"],"Standard11":["800-171 Rev2 (3.3.3,3.3.5)","800_53 Rev5 (AU-6,AU-6.1,AU-6.5,AU-7)"],"Standard9":["v1.1 (DE.AE-2,PR.PT-1,RS.AN-1)"],"Standard8":["v3.2.1 (10.6,10.6.1,10.6.2)"],"s202":["(CC4.1,CC7.3)"]},"ecc-aws-167":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,13.4)"],"AWS Foundational Security Best Practices controls":["(EC2.19)"],"BSA":["v3 (GS-4,NS-1,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.1,SC.L2-3.13.2,SC.L2-3.13.6)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m,09.n)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.16,8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3,CIP-007-6_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.1,3.13.2,3.13.6,3.4.6)","800_53 Rev5 (CA-9,CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.3.6,2.2.2)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-429":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-413":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-436":{"Standard1":["v7 (6)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (IVS-02)"],"CE":["v2.2 (Secure_configuration)"],"Standard3":["Standard11_800_53_Rev5 (SA-03,SA-08,SA-10,SI-04)"],"Standard6":["2016_679 (Article_32)"],"Standard5":["(164.312.b)"],"Standard15":["(08.k,09.ab,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.16,8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (SA-10,SA-3,SA-8,SI-4)"],"Standard9":["v1.1 (DE.CM-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(A1.1,CC7.1)"]},"ecc-aws-108":{"Standard1":["v7 (6.2,6.3)","v8 (3.14,8.2,8.5)"],"AWS Config":["(cloudfront-accesslogs-enabled)"],"AWS Foundational Security Best Practices controls":["(CloudFront.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (DSP-17,LOG-04,LOG-08)"],"DA":["(Article_10.7,Article_8.3.d,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-06.09,AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AC-6.9,AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.1,10.2.2,10.2.4,10.2.5,10.3,11.5)"],"s202":["(CC6.1,CC7.2)"]},"ecc-aws-007":{"Standard1":["v7 (5.1)","v8 (11.1)"],"AWS Config":["(rds-multi-az-support)"],"AWS Foundational Security Best Practices controls":["(RDS.5)"],"BSA":["v3 (GS-8)"],"Standard2":["19 (APO14)"],"Standard4":["v4 (BCR-08,GRC-03)"],"CE":["v2.2 (Back_up_your_data)"],"DA":["(Article_8.2,Article_8.4.a,Article_11.5)"],"Standard10":["(D1.RM.RMP.B.1,D5.IR.Pl.B.5,D5.IR.Pl.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CP-02,CP-10)"],"Standard6":["2016_679 (Article_32.1.c)"],"Standard5":["(164.308.a.1.ii.B,164.308.a.7.i,164.308.a.7.ii.A,164.308.a.7.ii.C,164.314.b.2.i)"],"Standard15":["(09.l)"],"Standard14":["27001_2013 (A.12.3.1)","27002_2013 (12.3.1)","27002_2022 (8.13)","27017_2015 (12.3.1)","27018_2019 (12.3.1)","27701_2019 (6.9.3.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-009-6_Requirement_R1_Part_1.3)"],"Standard11":["800_53 Rev5 (CP-10,CP-2)"],"Standard9":["v1.1 (ID.SC-5,PR.IP-9)"],"Standard7":["(6s)"],"s202":["(A1.2)"]},"ecc-aws-074":{"Standard1":["v7 (11.7)","v8 (12.2)"],"AWS Config":["(opensearch-in-vpc-only)"],"AWS Foundational Security Best Practices controls":["(ES.2,OpenSearch.2)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.308.a.1.ii.B,164.308.a.3.i,164.308.a.4.ii.A,164.308.a.4.ii.C,164.312.a.1,164.312.e.1)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-224":{"Standard1":["v7 (14.6,12)","v8 (3.3,4.1)"],"AWS Config":["(ec2-imdsv2-check)"],"AWS Foundational Security Best Practices controls":["(EC2.8)"],"CIS AWS Compute Services Benchmark":["v1.0.0 (2.8)"],"BSA":["v3 (GS-10,GS-5,NS-8,PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-07,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32.1.b)"],"Standard5":["(164.308.a.3.i,164.308.a.4.ii.A,164.308.a.4.ii.C,164.312.a.1)"],"Standard15":["(08.k,10.k,10.m)"],"Standard14":["27001_2013 (A.13.1.2,A.9.1.1,A.9.4.1)","27002_2013 (13.1.2,9.1.1,9.4.1)","27002_2022 (5.10,8.21,8.3)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-7,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC6.6)"]},"ecc-aws-226":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,13.4)"],"BSA":["v3 (GS-4,NS-1,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.1,SC.L2-3.13.2,SC.L2-3.13.6)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m,09.n)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.16,8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3,CIP-007-6_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.1,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CA-9,CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-193":{"Standard1":["v7 (9.2)","v8 (12.2)"],"AWS Config":["(alb-http-drop-invalid-header-enabled)"],"AWS Foundational Security Best Practices controls":["(ELB.4)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1)"],"CMMC":["v2.0 (AC.L1-3.1.20,CM.L2-3.4.1,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard11":["800-171 Rev2 (3.13.2,3.4.2)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"]},"ecc-aws-199":{"Standard1":["v7 (6)","v8 (4.1)"],"AWS Config":["(rds-enhanced-monitoring-enabled)"],"AWS Foundational Security Best Practices controls":["(RDS.6)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (IVS-02)"],"CE":["v2.2 (Secure_configuration)"],"Standard3":["Standard11_800_53_Rev5 (SA-03,SA-08,SA-10,SI-04)"],"Standard6":["2016_679 (Article_32)"],"Standard5":["(164.312.b)"],"Standard15":["(08.k,09.ab,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.16,8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (SA-10,SA-3,SA-8,SI-4)"],"Standard9":["v1.1 (DE.CM-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(A1.1,CC7.1)"]},"ecc-aws-328":{"Standard1":["v7 (1.4,13.2)","v8 (1.1)"],"AWS Config":["(ec2-volume-inuse-check)"],"CIS AWS Compute Services Benchmark":["v1.0.0 (2.2.4)"],"BSA":["v3 (AM-1,AM-3)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"DA":["(Article_7.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3,PR.IP-1)"],"s202":["(CC6.1)"]},"ecc-aws-054":{"Standard1":["v7 (4)","v8 (3.3)"],"CIS AWS Foundations Benchmark":["v1.4.0 (1.16)","v1.5.0 (1.16)"],"AWS Config":["(iam-policy-no-statements-with-admin-access)"],"AWS Foundational Security Best Practices controls":["(IAM.1)"],"BSA":["v3 (AM-4,IM-7,PA-7)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.4.c,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.1.ii.B,164.308.a.3.i,164.308.a.3.ii.A,164.308.a.3.ii.B,164.308.a.4.i,164.308.a.4.ii.B,164.308.a.4.ii.C,164.312.a.1,164.314.b.2.i)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.3)","27017_2015 (8.1.3,9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-aws-476":{"Standard1":["v7 (5.1)","v8 (4.1)"],"AWS Config":["(cloudformation-stack-drift-detection-check)"],"BSA":["v3 (PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI06,BAI10)"],"Standard4":["v4 (CCC-01)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,CM-09,SA-03,SA-10)"],"Standard15":["(10.k)"],"Standard14":["27001_2013 (A.14.2.2)","27002_2013 (14.2.2)","27002_2022 (8.32)","27017_2015 (14.2.2)","27018_2019 (14)","27701_2019 (6.11.2.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1)","800_53 Rev5 (CM-2,CM-6,CM-9,SA-10,SA-3)"],"Standard9":["v1.1 (PR.IP-1)"],"s202":["(CC8.1)"]},"ecc-aws-445":{"Standard1":["v7 (6.2,6.3)","v8 (4.1,8.2,8.5)"],"BSA":["v3 (DS-7,GS-10,GS-5,LT-3,LT-4,PV-1,PV-3)"],"Standard13":["v1.0.0 (3.1.20)"],"CJIS":["(5.4.1,5.7.1)"],"CMMC":["v2.0 (AU.L2-3.3.1,CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10,DSS03)"],"Standard4":["v4 (CCC-01,IVS-04,LOG-08)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.e,Article_10.7)"],"Standard10":["(D1.G.SP.B.2,D2.MA.Ma.B.1,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-03,AU-12,CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_30,Article_32)"],"Standard5":["(164.312.b)"],"Standard15":["(08.k,09.aa,10.k)"],"Standard14":["27001_2013 (A.12.4.1,A.14.2.5)","27002_2013 (12.4.1,14.2.5)","27002_2022 (5.28,8.15,8.27,8.9)","27017_2015 (12.4.1,14.2.5)","27018_2019 (12.4.1,14)","27701_2019 (6.11.2.5,6.9.4.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-007-6_Requirement_R4_Part_4.1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.3.1,3.4.1,3.4.2)","800_53 Rev5 (AU-12,AU-2,AU-3,AU-3.1,CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (DE.AE-3,PR.IP-1,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.5,10.3,2.2)"],"s202":["(CC7.2,CC8.1)"]},"ecc-aws-135":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,13.4)"],"BSA":["v3 (GS-4,NS-1,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.2,SC.L2-3.13.6)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m,09.n)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.16,8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.1,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CA-9,CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-405":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-107":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"CE":["v2.2 (Secure_configuration)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-067":{"Standard1":["v7 (6.5,6.7)","v8 (8.11)"],"CIS AWS Foundations Benchmark":["v1.4.0 (4.1)","v1.5.0 (4.1)"],"AWS Foundational Security Best Practices controls":["(CloudWatch.2)"],"BSA":["v3 (DS-7,LT-1,LT-2,LT-5)"],"CJIS":["(5.4.1,5.4.3)"],"CMMC":["v2.0 (AU.L2-3.3.5)"],"Standard4":["v4 (LOG-05)"],"DA":["(Article_9.2,Article_15.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-06,AU-06.01,AU-06.05,AU-07)"],"Standard5":["(164.308.a.1.ii.D,164.312.b)"],"Standard14":["27001_2013 (A.12.4.1,A.16.1.4)","27002_2013 (12.4.1,16.1.4)","27002_2022 (5.25)","27017_2015 (12.4.1,16.1.4)","27018_2019 (12.4.1,6.1.4)","27701_2019 (6.13.1.4,6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.4)"],"Standard11":["800-171 Rev2 (3.3.3,3.3.5)","800_53 Rev5 (AU-6,AU-6.1,AU-6.5,AU-7)"],"Standard9":["v1.1 (DE.AE-2,PR.PT-1,RS.AN-1)"],"Standard8":["v3.2.1 (10.6,10.6.1,10.6.2)"],"s202":["(CC4.1,CC7.3)"]},"ecc-aws-254":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (DP-4,DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)"],"s202":["(CC6.1)"]},"ecc-aws-273":{"Standard1":["v7 (6.2,6.3)","v8 (8.2,8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_8.4.e,Article_10.7)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.5,10.3)"],"s202":["(CC7.2)"]},"ecc-aws-264":{"Standard1":["v7 (9.2)","v8 (12.2)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-007-6_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.4.2,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-433":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Security_update_management)"],"DA":["(Article_8.2,Article_8.4.a)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-203":{"Standard1":["v7 (6.2,6.3)","v8 (3.14,8.2,8.5)"],"AWS Config":["(rds-logging-enabled)"],"AWS Foundational Security Best Practices controls":["(RDS.9)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.3.d,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-06.09,AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.308.a.3.ii.A,164.308.a.5.ii.C,164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AC-6.9,AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.1,10.2.5,10.3)"],"s202":["(CC6.1,CC7.2)"]},"ecc-aws-538":{"Standard1":["v7 (5.1)","v8 (4.1)"],"AWS Foundational Security Best Practices controls":["(CloudFront.12)"],"BSA":["v3 (GS-5,PV-1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (CCC-04,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_7.1,Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,SA-08)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-2,CM-6,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-176":{"Standard1":["v7 (14.8)","v8 (11.3,3.11)"],"AWS Config":["(rds-snapshot-encrypted)"],"AWS Foundational Security Best Practices controls":["(RDS.4)"],"BSA":["v3 (BR-2,DP-4,DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,MP.L2-3.8.9,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (BCR-08,CEK-03)"],"CE":["v2.2 (Back_up_your_data)"],"DA":["(Article_8.3.a,Article_8.4.d,Article_11.5.a,Article_11.5.b)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (CP-09,CP-09.08,IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a,Article_32.1.c,Article_5.1.f)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d,09.l)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (8.12,8.13,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1,3.8.9)","800_53 Rev5 (CP-9,CP-9.8,IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1,PR.IP-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)"],"s202":["(A1.2,CC6.1,CC6.4,CC6.7)"]},"ecc-aws-011":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.1,Article_8.2,Article_8.3)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-482":{"Standard1":["v7 (6.2,6.3)","v8 (3.14,8.2,8.5)"],"AWS Config":["(codebuild-project-logging-enabled)"],"AWS Foundational Security Best Practices controls":["(CodeBuild.4)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_8.4.e,Article_10.7)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-06.09,AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.308.a.3.ii.A,164.308.a.5.ii.C,164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AC-6.9,AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.1,10.2.5,10.3)"],"s202":["(CC6.1,CC7.2)"]},"ecc-aws-544":{"Standard1":["v7 (6.2,6.3)","v8 (8.2,8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_8.4.e,Article_10.7)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.5,10.3)"],"s202":["(CC7.2)"]},"ecc-aws-444":{"Standard1":["v7 (6.2,6.3)","v8 (4.1,8.2,8.5)"],"BSA":["v3 (DS-7,GS-10,GS-5,LT-3,LT-4,PV-1,PV-3)"],"Standard13":["v1.0.0 (3.1.20)"],"CJIS":["(5.4.1,5.7.1)"],"CMMC":["v2.0 (AU.L2-3.3.1,CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10,DSS03)"],"Standard4":["v4 (CCC-01,IVS-04,LOG-08)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.e,Article_10.7)"],"Standard10":["(D1.G.SP.B.2,D2.MA.Ma.B.1,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-03,AU-12,CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_30,Article_32)"],"Standard5":["(164.312.b)"],"Standard15":["(08.k,09.aa,10.k)"],"Standard14":["27001_2013 (A.12.4.1,A.14.2.5)","27002_2013 (12.4.1,14.2.5)","27002_2022 (5.28,8.15,8.27,8.9)","27017_2015 (12.4.1,14.2.5)","27018_2019 (12.4.1,14)","27701_2019 (6.11.2.5,6.9.4.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-007-6_Requirement_R4_Part_4.1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.3.1,3.4.1,3.4.2)","800_53 Rev5 (AU-12,AU-2,AU-3,AU-3.1,CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (DE.AE-3,PR.IP-1,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.5,10.3,2.2)"],"s202":["(CC7.2,CC8.1)"]},"ecc-aws-146":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,13.4)"],"CIS AWS Foundations Benchmark":["v1.4.0 (5.1)","v1.5.0 (5.1)"],"AWS Config":["(nacl-no-unrestricted-ssh-rdp)"],"AWS Foundational Security Best Practices controls":["(EC2.21)"],"BSA":["v3 (GS-4,NS-1,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.2,SC.L2-3.13.6)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m,09.n)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.16,8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.1,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CA-9,CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-504":{"Standard1":["v7 (5.1)","v8 (4.1)"],"AWS Config":["(rds-instance-default-admin-check)"],"AWS Foundational Security Best Practices controls":["(RDS.25)"],"BSA":["v3 (GS-10,GS-5,NS-8,PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.3.c,Article_14.d)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-07,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32.1.b)"],"Standard15":["(08.k,10.k,10.m)"],"Standard14":["27001_2013 (A.13.1.2,A.9.1.1,A.9.4.1)","27002_2013 (13.1.2,9.1.1,9.4.1)","27002_2022 (5.10,8.21,8.3)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-7,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC6.6)"]},"ecc-aws-112":{"Standard1":["v7 (14.6)","v8 (3.3,6.5)"],"CIS AWS Foundations Benchmark":["v1.4.0 (2.1.3)","v1.5.0 (2.1.3)"],"AWS Config":["(s3-bucket-versioning-enabled)"],"BSA":["v3 (AM-4,GS-6,IM-2,IM-7,PA-7)"],"CJIS":["(5.5.2,5.6.2.2,5.6.2.2.1)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.12,AC.L2-3.1.15,AC.L2-3.1.5,IA.L2-3.5.3,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05,IAM-10,IAM-14)"],"CE":["v2.2 (A7.16,A7.4,Secure_configuration,uac)"],"DA":["(Article_8.3.b,,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Am.B.3)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,IA-02.01,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312)"],"Standard15":["(01.a,01.c,01.q,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.3.1,A.9.4.1,A.9.4.2)","27002_2013 (8.1.3,9.1.1,9.3.1,9.4.1,9.4.2)","27002_2022 (5.10,5.15,8.2,8.3,8.5)","27017_2015 (8.1.3,9.1.1,9.3.1,9.4.1,9.4.2)","27018_2019 (8,9.1,9.3.1,9.4.1,9.4.2)","27701_2019 (6.5.1.3,6.6.1.1,6.6.3.1,6.6.4.1,6.6.4.2)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.5.3,3.8.2)","800_53 Rev5 (AC-3,AC-6,IA-2.1,MP-2)"],"Standard9":["v1.1 (PR.AC-4,PR.AC-7)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3,8.3,8.3.1,8.3.2)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-aws-159":{"Standard1":["v7 (5.5)","v8 (4.1)"],"AWS Foundational Security Best Practices controls":["(RDS.19)"],"BSA":["v3 (GS-5,PV-2)"],"CJIS":["(5.10.4.4,5.4.3)"],"CMMC":["v2.0 (AU.L2-3.3.5,CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-07,LOG-03)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_9.2,Article_15.1)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5,A.16.1.2)","27002_2013 (14.2.5,16.1.2)","27002_2022 (6.8,8.27,8.9)","27017_2015 (14.2.5,16.1.2)","27018_2019 (14,16.1.2)","27701_2019 (6.11.2.5,6.13.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-007-6_Requirement_R4_Part_4.2)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (DE.DP-4,PR.IP-1)"],"Standard8":["v3.2.1 (2.2,A.3.3.1)"],"s202":["(CC8.1)"]},"ecc-aws-425":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-185":{"Standard1":["v7 (1.4,5.1)","v8 (1.1,4.1)"],"AWS Config":["(ec2-stopped-instance)"],"AWS Foundational Security Best Practices controls":["(EC2.4)"],"CIS AWS Compute Services Benchmark":["v1.0.0 (2.11)"],"BSA":["v3 (AM-1,AM-3,GS-10,GS-5,PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (APO13,BAI06,BAI09)"],"DA":["(Article_7.1,Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-08,CM-08.01,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard5":["(164.308.a.1.ii.B)"],"Standard15":["(07.a,08.k,10.k)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-8,CM-8.1,CM-9,PM-5,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.DS-3,PR.IP-6)"]},"ecc-aws-417":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-128":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (PV-1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (BAI06,BAI10)"],"Standard4":["v4 (TVM-01)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32.1)"],"Standard15":["(10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-2,CM-6,CM-9,SA-3,SA-8,SA-10)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-399":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-195":{"Standard1":["v7 (15.8,9.2)","v8 (12.3,12.6,3.10)"],"AWS Config":["(alb-http-to-https-redirection-check)"],"AWS Foundational Security Best Practices controls":["(ELB.1)"],"BSA":["v3 (DP-3,NS-8)"],"CJIS":["(5.10.1,5.10.1.2.1,5.13.1.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,IA.L1-3.5.2,SC.L1-3.13.1,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (APO01,APO03,DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IVS-03)"],"DA":["(Article_8.4.b,Article_8.2,Article_8.3,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.13,D3.PC.Im.B.10)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,AC-18,CM-06,CM-07,SC-08,SC-23)"],"Standard6":["2016_679 (Article_32.1.a,Article_32.1.b,Article_44,Article_5.1.f)"],"Standard5":["(164.308.a.1.ii.B.,164.312.a.2.iv.,164.312.e.1,164.312.e.2.i,164.312.e.2.ii,164.314.b.2.i)"],"Standard15":["(01.j,06.c,06.d,09.m,09.s,09.v,09.y,10.d)"],"Standard14":["27001_2013 (A.10.1.1,A.13.1.1,A.13.1.2,A.13.2.1,A.18.1.3)","27002_2013 (10.1.1,13.1.1,13.1.2,13.2.1,18.1.3)","27002_2022 (5.14,5.33,8.20,8.21,8.24)","27017_2015 (10.1.1,13.1.1,13.1.2,13.2.1,18.1.3)","27018_2019 (10.1.1,13.1,13.2.1,18.1.3)","27701_2019 (6.10.1.1,6.10.1.2,6.10.2.1,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.1.17,3.13.1,3.13.15,3.13.8,3.5.10)","800_53 Rev5 (AC-17.2,AC-18,CM-6,CM-7,SC-23,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.AC-7,PR.DS-2,PR.DS-5,PR.PT-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1.1,4.1,4.1.1,8.2.1)"],"s202":["(CC6.7)"]},"ecc-aws-507":{"Standard1":["v7 (6.2,6.3)","v8 (4.1,8.2,8.5)"],"AWS Config":["(sns-topic-message-delivery-notification-enabled)"],"AWS Foundational Security Best Practices controls":["(SNS.2)"],"BSA":["v3 (DS-7,GS-10,GS-5,LT-3,LT-4,PV-1,PV-3)"],"CJIS":["(5.4.1,5.7.1)"],"CMMC":["v2.0 (AU.L2-3.3.1,CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10,DSS03)"],"Standard4":["v4 (CCC-01,IVS-04,LOG-08)"],"CE":["v2.2 (Secure_configuration)"],"Standard10":["(D1.G.SP.B.2,D2.MA.Ma.B.1,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-03,AU-12,CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_30,Article_32)"],"Standard5":["(164.312.b)"],"Standard15":["(08.k,09.aa,10.k)"],"Standard14":["27001_2013 (A.12.4.1,A.14.2.5)","27002_2013 (12.4.1,14.2.5)","27002_2022 (5.28,8.15,8.27,8.9)","27017_2015 (12.4.1,14.2.5)","27018_2019 (12.4.1,14)","27701_2019 (6.11.2.5,6.9.4.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-007-6_Requirement_R4_Part_4.1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.3.1,3.4.1,3.4.2)","800_53 Rev5 (AU-12,AU-2,AU-3,AU-3.1,CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (DE.AE-3,PR.IP-1,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.5,10.3,2.2)"],"s202":["(CC7.2,CC8.1)"]},"ecc-aws-131":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,13.4)"],"AWS Foundational Security Best Practices controls":["(EC2.19)"],"BSA":["v3 (GS-4,NS-1,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.2,SC.L2-3.13.6)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m,09.n)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.16,8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.1,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CA-9,CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-285":{"Standard1":["v7 (14.6,14.8)","v8 (3.11,3.3)"],"BSA":["v3 (AM-4,DP-4,DP-5,IM-7,PA-7)"],"CJIS":["(5.10.1.2.2,5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.1,MP.L2-3.8.2,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03,IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.4.d,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,IA-05.01,MP-02,SC-28)"],"Standard6":["2016_679 (Article_32.1.a,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.a.2.iv)"],"Standard15":["(01.a,01.c,01.v,06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,5.33,8.24,8.3)","27017_2015 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (10.1.1,18.1.3,8,9.1,9.4.1)","27701_2019 (6.15.1.3,6.5.1.3,6.6.1.1,6.6.4.1,6.7.1.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.13.16,3.8.1,3.8.2)","800_53 Rev5 (AC-3,AC-6,IA-5.1,MP-2,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.AC-4,PR.DS-1)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (3.4,3.4.1,7.1,7.1.1,7.1.2,7.1.3,8.2.1)"],"s202":["(CC6.1,CC6.3)"]},"ecc-aws-154":{"Standard1":["v7 (5.1)","v8 (4.1)"],"AWS Config":["(opensearch-data-node-fault-tolerance)"],"AWS Foundational Security Best Practices controls":["(ES.6,OpenSearch.6)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-255":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (DP-4)"],"CJIS":["(5.4.5)"],"CMMC":["v2.0 (AU.L2-3.3.8)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (LOG-04)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-09)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard15":["(09.ac)"],"Standard14":["27001_2013 (A.12.4.2)","27002_2013 (12.4.2)","27002_2022 (8.15)","27017_2015 (12.4.2)","27018_2019 (12.4.2)","27701_2019 (6.9.4.2)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.3.8)","800_53 Rev5 (AU-9)"],"Standard9":["v1.1 (PR.DS-1,PR.PT-1)"],"Standard8":["v3.2.1 (10.5)"],"s202":["(CC6.1)"]},"ecc-aws-496":{"Standard1":["v7 (5.1)","v8 (4.1)"],"AWS Config":["(ecs-task-definition-pid-mode-check)"],"AWS Foundational Security Best Practices controls":["(ECS.3)"],"BSA":["v3 (GS-10,GS-5,NS-8,PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-07,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32.1.b)"],"Standard15":["(08.k,10.k,10.m)"],"Standard14":["27001_2013 (A.13.1.2,A.9.1.1,A.9.4.1)","27002_2013 (13.1.2,9.1.1,9.4.1)","27002_2022 (5.10,8.21,8.3)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-7,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC6.6)"]},"ecc-aws-179":{"Standard1":["v7 (14.6,5.1)","v8 (3.3,4.2)"],"AWS Config":["(cloudfront-default-root-object-configured)"],"AWS Foundational Security Best Practices controls":["(CloudFront.1)"],"BSA":["v3 (GS-10,GS-5,IM-7,PV-1)"],"CJIS":["(5.10.1,5.5.2,5.7.1)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,MP.L2-3.8.2)"],"Standard2":["19 (BAI10,DSS05)"],"Standard4":["v4 (IAM-05,IVS-04)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.3.b,Article_8.3.c,Article_8.4.b,Article_8.4.c)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09,MP-02)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.v,09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (13.1.2,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.21,8.3,8.9)","27017_2015 (13.1.2,8.1.3,9.1.1,9.4.1)","27018_2019 (13.1,8,9.1,9.4.1)","27701_2019 (6.10.1.2,6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-004-6_Requirement_R4_Part_4.1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.4.2,3.8.2)","800_53 Rev5 (AC-18,AC-3,AC-6,CM-2,CM-6,CM-7,CM-7.1,CM-9,MP-2)"],"Standard9":["v1.1 (PR.AC-4,PR.IP-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)"],"s202":["(CC6.1,CC6.3,CC6.6)"]},"ecc-aws-293":{"Standard1":["v7 (10.4,14.6)","v8 (11.3,3.3)"],"BSA":["v3 (AM-4,BR-2,BR-3,DP-5,IM-7,PA-7)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2,MP.L2-3.8.9)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (BCR-08,CEK-03,IAM-05)"],"CE":["v2.2 (A7.4,Back_up_your_data,Secure_configuration,uac)"],"DA":["(Article_11.5.a,Article_11.5.b,Article_8.3.b)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,CP-09,CP-09.08,MP-02,SC-28)"],"Standard6":["2016_679 (Article_32.1.c,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.v,06.c,09.l)"],"Standard14":["27001_2013 (A.18.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (18.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.12,8.13,8.3)","27017_2015 (18.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (18.1.3,8,9.1,9.4.1)","27701_2019 (6.15.1.3,6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2,3.8.9)","800_53 Rev5 (AC-3,AC-6,CP-9,CP-9.8,MP-2,SC-28)"],"Standard9":["v1.1 (PR.AC-4,PR.DS-1,PR.IP-4)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)"],"s202":["(A1.2,CC6.1,CC6.3,CC6.4,CC6.7)"]},"ecc-aws-198":{"Standard1":["v7 (6.2,6.5)","v8 (3.14,8.2,8.5)"],"AWS Config":["(elasticsearch-logs-to-cloudwatch,opensearch-logs-to-cloudwatch)"],"AWS Foundational Security Best Practices controls":["(ES.4,OpenSearch.4)"],"BSA":["v3 (DS-7,LT-3)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (DSP-17,LOG-04,LOG-08)"],"DA":["(Article_10.7,Article_8.3.d,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-06.09,AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.308.a.3.ii.A,164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AC-6.9,AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.1,10.2.5,10.3)"],"s202":["(CC7.2)"]},"ecc-aws-205":{"Standard1":["v7 (6.2,6.3)","v8 (3.14,8.2,8.5)"],"AWS Config":["(rds-logging-enabled)"],"AWS Foundational Security Best Practices controls":["(RDS.9)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.3.d,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-06.09,AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.308.a.3.ii.A,164.308.a.5.ii.C,164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AC-6.9,AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.1,10.2.5,10.3)"],"s202":["(CC6.1,CC7.2)"]},"ecc-aws-016":{"Standard1":["v7 (4.5)","v8 (6.5)"],"CIS AWS Foundations Benchmark":["v1.4.0 (1.6)","v1.5.0 (1.6)"],"AWS Config":["(root-account-hardware-mfa-enabled)"],"AWS Foundational Security Best Practices controls":["(IAM.6)"],"BSA":["v3 (GS-6,IM-2)"],"CJIS":["(5.6.2.2,5.6.2.2.1)"],"CMMC":["v2.0 (IA.L2-3.5.3)"],"Standard4":["v4 (IAM-14)"],"CE":["v2.2 (A7.16,uac)"],"DA":["(Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.3)"],"Standard3":["Standard11_800_53_Rev5 (IA-02.01)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.312.a.1,164.312.d)"],"Standard15":["(01.q)"],"Standard14":["27001_2013 (A.9.3.1,A.9.4.2)","27002_2013 (9.3.1,9.4.2)","27002_2022 (5.17,8.5)","27017_2015 (9.3.1,9.4.2)","27018_2019 (9.3.1,9.4.2)","27701_2019 (6.6.3.1,6.6.4.2)"],"Standard11":["800-171 Rev2 (3.5.3)","800_53 Rev5 (IA-2.1)"],"Standard9":["v1.1 (PR.AC-7)"],"Standard8":["v3.2.1 (8.3,8.3.1,8.3.2)"],"s202":["(CC6.1)"]},"ecc-aws-030":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,13.4)"],"BSA":["v3 (GS-4,NS-1,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.2,SC.L2-3.13.6)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m,09.n)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.16,8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.1,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CA-9,CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-006":{"Standard1":["v7 (10.1)","v8 (11.2)"],"AWS Config":["(db-instance-backup-enabled)"],"AWS Foundational Security Best Practices controls":["(RDS.11)"],"BSA":["v3 (BR-1)"],"Standard4":["v4 (BCR-08)"],"CE":["v2.2 (Back_up_your_data)"],"DA":["(Article_11.2,Article_11.4)"],"Standard10":["(D5.IR.Pl.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CP-09,CP-10)"],"Standard6":["2016_679 (Article_32.1.c)"],"Standard5":["(164.308.a.1.ii.B,164.308.a.7.i,164.308.a.7.ii.A,164.308.a.7.ii.B,164.312.a.2.ii)"],"Standard15":["(09.l)"],"Standard14":["27001_2013 (A.12.3.1)","27002_2013 (12.3.1)","27002_2022 (8.13)","27017_2015 (12.3.1)","27018_2019 (12.3.1)","27701_2019 (6.9.3.1)"],"Standard12":["(CIP-009-6_Requirement_R1_Part_1.3)"],"Standard11":["800_53 Rev5 (CP-10,CP-9)"],"Standard9":["v1.1 (PR.IP-4)"],"Standard7":["(6s)"],"Standard8":["v3.2.1 (12.10.1)"],"s202":["(A1.2)"]},"ecc-aws-142":{"Standard1":["v7 (14.6)","v8 (3.3)"],"CIS AWS Foundations Benchmark":["v1.4.0 (2.1.5)","v1.5.0 (2.1.5)"],"AWS Config":["(s3-bucket-level-public-access-prohibited)","(s3-bucket-public-read-prohibited)","(s3-bucket-public-write-prohibited)"],"AWS Foundational Security Best Practices controls":["(S3.2,S3.3)"],"BSA":["v3 (AM-4,IM-7,PA-7)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.3.b,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.1.ii.B,164.308.a.3.i,164.308.a.4.i,164.308.a.4.ii.A,164.308.a.4.ii.B,164.308.a.4.ii.C,164.312.a.1,164.312.e.1,164.314.b.1,164.314.b.2)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.3)","27017_2015 (8.1.3,9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-aws-114":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,13.4)"],"BSA":["v3 (GS-4,NS-1,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.2,SC.L2-3.13.6)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m,09.n)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.16,8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.1,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CA-9,CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-318":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CIS Oracle Database 19 Benchmark":["v1.0.0 (2.2.10)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-132":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,13.4)"],"AWS Foundational Security Best Practices controls":["(EC2.19)"],"BSA":["v3 (GS-4,NS-1,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.2,SC.L2-3.13.6)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m,09.n)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.16,8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.1,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CA-9,CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-304":{"Standard1":["v7 (14.6,5.1)","v8 (3.3,4.1)"],"BSA":["v3 (AM-4,GS-10,GS-5,IM-7,PA-7,PV-1,PV-3)"],"CJIS":["(5.5.2,5.7.1)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,MP.L2-3.8.2)"],"Standard2":["19 (APO13,BAI06,BAI10,DSS05)"],"Standard4":["v4 (CCC-01,IAM-05,IVS-04)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.3.c,Article_14.d,Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Am.B.1,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,CM-01,CM-02,CM-06,CM-09,MP-02,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.v,08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (14.2.5,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.27,8.3,8.9)","27017_2015 (14.2.5,8.1.3,9.1.1,9.4.1)","27018_2019 (14,8,9.1,9.4.1)","27701_2019 (6.11.2.5,6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-004-6_Requirement_R4_Part_4.1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.4.1,3.4.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,CM-1,CM-2,CM-6,CM-9,MP-2,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.AC-4,PR.IP-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.2,7.1,7.1.1,7.1.2,7.1.3)"],"s202":["(CC6.1,CC6.3,CC8.1)"]},"ecc-aws-387":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-368":{"Standard1":["v7 (10.1)","v8 (11.2)"],"BSA":["v3 (BR-1)"],"Standard4":["v4 (BCR-08)"],"CE":["v2.2 (Back_up_your_data)"],"DA":["(Article_11.2,Article_11.4)"],"Standard10":["(D5.IR.Pl.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CP-09,CP-10)"],"Standard6":["2016_679 (Article_32.1.c)"],"Standard5":["(164.308.a.7.ii.A)"],"Standard15":["(09.l)"],"Standard14":["27001_2013 (A.12.3.1)","27002_2013 (12.3.1)","27002_2022 (8.13)","27017_2015 (12.3.1)","27018_2019 (12.3.1)","27701_2019 (6.9.3.1)"],"Standard12":["(CIP-009-6_Requirement_R1_Part_1.3)"],"Standard11":["800_53 Rev5 (CP-10,CP-9)"],"Standard9":["v1.1 (PR.IP-4)"],"Standard7":["(6s)"],"Standard8":["v3.2.1 (12.10.1)"],"s202":["(A1.2)"]},"ecc-aws-576":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800_53 Rev5 (CM-8,CM-8.1)"],"Standard9":["v1.1 (PR.DS-3)"]},"ecc-aws-194":{"Standard1":["v7 (5.1)","v8 (12.2,4.2)"],"AWS Foundational Security Best Practices controls":["(ELB.6)"],"AWS Config":["(elb-deletion-protection-enabled)"],"BSA":["v3 (GS-10,GS-4,GS-5,NS-2,PV-1)"],"CJIS":["(5.7.1,5.7.1.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03,BAI10)"],"Standard4":["v4 (DSP-07,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32,Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.308.a.1.ii.B,164.308.a.7.i,164.308.a.7.ii.C,164.314.b.2.i)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.2)","27002_2013 (13.1.1,13.1.2)","27002_2022 (8.20,8.21,8.9)","27017_2015 (13.1.1,13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.13.2,3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.DS-3,PR.IP-1,PR.PT-5)"]},"ecc-aws-394":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-218":{"Standard1":["v7 (5.1)","v8 (4.1,4.6)"],"AWS Config":["(secretsmanager-rotation-enabled-check)"],"AWS Foundational Security Best Practices controls":["(SecretsManager.1)"],"BSA":["v3 (DP-6,GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.15)"],"Standard2":["19 (APO13,BAI06,BAI09,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b,Article_8.4.e)"],"Standard10":["(D1.G.SP.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.308.a.4.ii.B)"],"Standard15":["(07.c,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.DS-3,PR.IP-1,PR.IP-2)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-082":{"Standard1":["v7 (16)","v8 (8.11)"],"CIS AWS Foundations Benchmark":["v1.4.0 (4.7)","v1.5.0 (4.7)"],"AWS Foundational Security Best Practices controls":["(CloudWatch.7)"],"BSA":["v3 (DS-7,LT-1,LT-2,LT-5)"],"CJIS":["(5.4.1,5.4.3)"],"CMMC":["v2.0 (AU.L2-3.3.5)"],"Standard4":["v4 (LOG-05)"],"DA":["(Article_9.2,Article_15.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-06,AU-06.01,AU-06.05,AU-07)"],"Standard5":["(164.308.a.1.ii.D,164.312.b)"],"Standard14":["27001_2013 (A.12.4.1,A.16.1.4)","27002_2013 (12.4.1,16.1.4)","27002_2022 (5.25)","27017_2015 (12.4.1,16.1.4)","27018_2019 (12.4.1,6.1.4)","27701_2019 (6.13.1.4,6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.4)"],"Standard11":["800-171 Rev2 (3.3.3,3.3.5)","800_53 Rev5 (AU-6,AU-6.1,AU-6.5,AU-7)"],"Standard9":["v1.1 (DE.AE-2,PR.PT-1,RS.AN-1)"],"Standard8":["v3.2.1 (10.6,10.6.1,10.6.2)"],"s202":["(CC4.1,CC7.3)"]},"ecc-aws-275":{"Standard1":["v7 (6.2)","v8 (8.2)"],"AWS Config":["(rds-logging-enabled)"],"AWS Foundational Security Best Practices controls":["(RDS.9)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_8.4.e,Article_10.7)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.308.a.3.ii.A,164.308.a.5.ii.C,164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.2,10.3)"],"s202":["(CC7.2)"]},"ecc-aws-117":{"Standard1":["v7 (5.1)","v8 (4.2)"],"BSA":["v3 (GS-10,GS-5,PV-1)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-aws-005":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,13.4)"],"BSA":["v3 (GS-4,NS-1,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.2,SC.L2-3.13.6)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m,09.n)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.16,8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.1,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CA-9,CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-034":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,13.4)"],"BSA":["v3 (GS-4,NS-1,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.2,SC.L2-3.13.6)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m,09.n)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.16,8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.1,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CA-9,CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-366":{"Standard1":["v7 (6.2,6.3)","v8 (8.2,8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_8.4.e,Article_10.7)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.5,10.3)"],"s202":["(CC7.2)"]},"ecc-aws-305":{"Standard1":["v7 (5.1,6.2)","v8 (4.1,8.2)"],"BSA":["v3 (DS-7,GS-10,GS-5,LT-3,LT-4,PV-1,PV-3)"],"CJIS":["(5.4.1,5.7.1)"],"CMMC":["v2.0 (AU.L2-3.3.1,CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10,DSS03)"],"Standard4":["v4 (CCC-01,IVS-04,LOG-08)"],"CE":["v2.2 (Secure_configuration)"],"Standard10":["(D1.G.SP.B.2,D2.MA.Ma.B.1,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-12,CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_30,Article_32)"],"Standard5":["(164.312.b)"],"Standard15":["(08.k,09.aa,10.k)"],"Standard14":["27001_2013 (A.12.4.1,A.14.2.5)","27002_2013 (12.4.1,14.2.5)","27002_2022 (8.15,8.27,8.9)","27017_2015 (12.4.1,14.2.5)","27018_2019 (12.4.1,14)","27701_2019 (6.11.2.5,6.9.4.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-007-6_Requirement_R4_Part_4.1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.3.1,3.4.1,3.4.2)","800_53 Rev5 (AU-12,AU-2,CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (DE.AE-3,PR.IP-1,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.2,10.3,2.2)"],"s202":["(CC7.2,CC8.1)"]},"ecc-aws-183":{"Standard1":["v7 (10.1)","v8 (11.2)"],"AWS Config":["(dynamodb-pitr-enabled)"],"AWS Foundational Security Best Practices controls":["(DynamoDB.2)"],"BSA":["v3 (BR-1)"],"Standard4":["v4 (BCR-08)"],"CE":["v2.2 (Back_up_your_data)"],"DA":["(Article_11.2,Article_11.4)"],"Standard10":["(D5.IR.Pl.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CP-09,CP-10)"],"Standard6":["2016_679 (Article_32.1.c)"],"Standard5":["(164.308.a.1.ii.B,164.308.a.7.i.,164.308.a.7.ii.A,164.308.a.7.ii.B,164.312.a.2.ii,164.314.b.2.i)"],"Standard15":["(09.l)"],"Standard14":["27001_2013 (A.12.3.1)","27002_2013 (12.3.1)","27002_2022 (8.13)","27017_2015 (12.3.1)","27018_2019 (12.3.1)","27701_2019 (6.9.3.1)"],"Standard12":["(CIP-009-6_Requirement_R1_Part_1.3)"],"Standard11":["800_53 Rev5 (CP-10,CP-9)"],"Standard9":["v1.1 (PR.IP-4)"],"Standard7":["(6s)"],"Standard8":["v3.2.1 (12.10.1)"],"s202":["(A1.2)"]},"ecc-aws-510":{"Standard1":["v7 (1.4,13.2)","v8 (1.1)"],"BSA":["v3 (AM-1,AM-3)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"DA":["(Article_7.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3,PR.IP-1)"],"s202":["(CC6.1)"]},"ecc-aws-063":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,13.4)"],"CIS AWS Foundations Benchmark":["v1.4.0 (5.2)","v1.5.0 (5.2,5.3)"],"AWS Config":["(restricted-common-ports)"],"AWS Foundational Security Best Practices controls":["(EC2.14, EC2.19)"],"BSA":["v3 (GS-4,NS-1,NS-2)"],"CJIS":["(5.10.1,5.13.1.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.13,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,IA.L1-3.5.2,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.15,SC.L2-3.13.2,SC.L2-3.13.6)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.10,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CA-09,CM-07,PL-08,SA-08,SC-07,SC-08,SC-23)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.312.e.1)"],"Standard15":["(01.i,01.j,01.l,01.m,01.n,01.o,09.m,09.n,09.s,09.y,10.d)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.2,A.13.1.3)","27002_2013 (13.1.1,13.1.2,13.1.3)","27002_2022 (8.16,8.20,8.21,8.22,8.27)","27017_2015 (13.1.1,13.1.2,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.2,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.17,3.1.5,3.13.1,3.13.15,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (AC-18,CA-9,CM-7,PL-8,PM-7,SA-8,SC-23,SC-7,SC-8)"],"Standard9":["v1.1 (PR.AC-5,PR.DS-2,PR.DS-5,PR.IP-1,PR.PT-3,PR.PT-4)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5,4.1.1)"],"s202":["(CC5.2,CC6.1,CC6.6)"]},"ecc-aws-292":{"Standard1":["v7 (6.2,6.3)","v8 (3.14,8.2,8.5)"],"CIS AWS Compute Services Benchmark":["v1.0.0 (6.3)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (DSP-17,LOG-04,LOG-08)"],"DA":["(Article_10.7,Article_8.3.d,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-06.09,AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AC-6.9,AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.1,10.2.2,10.2.4,10.2.5,10.3,11.5)"],"s202":["(CC6.1,CC7.2)"]},"ecc-aws-127":{"Standard1":["v7 (14.8)","v8 (3.11)"],"CIS AWS Foundations Benchmark":["v1.4.0 (2.3.1)","v1.5.0 (2.3.1)"],"BSA":["v3 (DP-4,DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)"],"s202":["(CC6.1)"]},"ecc-aws-148":{"Standard1":["v7 (6.2,6.3)","v8 (8.5)"],"AWS Config":["(s3-bucket-logging-enabled)"],"AWS Foundational Security Best Practices controls":["(S3.9)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_8.4.e,Article_10.7)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.308.a.3.ii.A,164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.2,10.2.4,10.2.5,10.3)"],"s202":["(CC7.2)"]},"ecc-aws-390":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-099":{"Standard1":["v7 (11.3,6.2)","v8 (8.11)"],"CIS AWS Foundations Benchmark":["v1.4.0 (4.13)","v1.5.0 (4.13)"],"AWS Foundational Security Best Practices controls":["(CloudWatch.13)"],"BSA":["v3 (DS-7,LT-1,LT-2,LT-5)"],"CJIS":["(5.4.1,5.4.3)"],"CMMC":["v2.0 (AU.L2-3.3.5)"],"Standard4":["v4 (LOG-05)"],"DA":["(Article_9.2,Article_15.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-06,AU-06.01,AU-06.05,AU-07)"],"Standard5":["(164.308.a.1.ii.D,164.312.b)"],"Standard14":["27001_2013 (A.12.4.1,A.16.1.4)","27002_2013 (12.4.1,16.1.4)","27002_2022 (5.25)","27017_2015 (12.4.1,16.1.4)","27018_2019 (12.4.1,6.1.4)","27701_2019 (6.13.1.4,6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.4)"],"Standard11":["800-171 Rev2 (3.3.3,3.3.5)","800_53 Rev5 (AU-6,AU-6.1,AU-6.5,AU-7)"],"Standard9":["v1.1 (DE.AE-2,PR.PT-1,RS.AN-1)"],"Standard8":["v3.2.1 (10.6,10.6.1,10.6.2)"],"s202":["(CC4.1,CC7.3)"]},"ecc-aws-207":{"Standard1":["v7 (6.2,6.3)","v8 (3.14,8.2,8.5)"],"AWS Config":["(rds-logging-enabled)"],"AWS Foundational Security Best Practices controls":["(RDS.9)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.3.d,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-06.09,AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.308.a.3.ii.A,164.308.a.5.ii.C,164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AC-6.9,AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.1,10.2.5,10.3)"],"s202":["(CC6.1,CC7.2)"]},"ecc-aws-488":{"Standard1":["v7 (6)","v8 (8.10)"],"BSA":["v3 (LT-6)"],"CJIS":["(5.4.6,5.4.7)"],"Standard4":["v4 (LOG-02)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-11)"],"Standard6":["2016_679 (Article_5.1.e)"],"Standard15":["(09.ac)"],"Standard14":["27001_2013 (A.12.4.2,A.18.1.3)","27002_2013 (12.4.2,18.1.3)","27002_2022 (5.28)","27017_2015 (12.4.2,18.1.3)","27018_2019 (12.4.2,18.1)","27701_2019 (6.15.1.3,6.9.4.2)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.3)"],"Standard11":["800_53 Rev5 (AU-11)"],"Standard8":["v3.2.1 (10.7)"],"s202":["(C1.1)"]},"ecc-aws-398":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-217":{"Standard1":["v7 (11.1,12)","v8 (4.2)"],"AWS Config":["(redshift-enhanced-vpc-routing-enabled)"],"AWS Foundational Security Best Practices controls":["(Redshift.7)"],"BSA":["v3 (GS-10,GS-5,PV-1)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)"],"s202":["(CC6.6)"]},"ecc-aws-026":{"Standard1":["v7 (10.1)","v8 (11.2)"],"BSA":["v3 (BR-1)"],"Standard4":["v4 (BCR-08)"],"CE":["v2.2 (Back_up_your_data)"],"DA":["(Article_11.2,Article_11.4)"],"Standard10":["(D5.IR.Pl.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CP-09,CP-10)"],"Standard6":["2016_679 (Article_32.1.c)"],"Standard5":["(164.308.a.7.ii.A)"],"Standard15":["(09.l)"],"Standard14":["27001_2013 (A.12.3.1)","27002_2013 (12.3.1)","27002_2022 (8.13)","27017_2015 (12.3.1)","27018_2019 (12.3.1)","27701_2019 (6.9.3.1)"],"Standard12":["(CIP-009-6_Requirement_R1_Part_1.3)"],"Standard11":["800_53 Rev5 (CP-10,CP-9)"],"Standard9":["v1.1 (PR.IP-4)"],"Standard7":["(6s)"],"Standard8":["v3.2.1 (12.10.1)"],"s202":["(A1.2)"]},"ecc-aws-391":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-421":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-177":{"Standard1":["v7 (14)","v8 (12.2,12.6)"],"AWS Config":["(api-gw-ssl-enabled)"],"AWS Foundational Security Best Practices controls":["(APIGateway.2)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.13.1.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.7,IA.L1-3.5.2,SC.L1-3.13.1,SC.L2-3.13.15,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IAM-14,IVS-03)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.10,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-07,PL-08,SA-08,SC-07,SC-08,SC-23)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.312.a.2.iv,164.312.e.1,164.312.e.2.i,164.312.e.2.ii,164.314.b.2.i)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m,09.s,09.y,10.d)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.2,A.13.1.3)","27002_2013 (13.1.1,13.1.2,13.1.3)","27002_2022 (8.20,8.21,8.22,8.27)","27017_2015 (13.1.1,13.1.2,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.2,6.10.1.3)"],"Standard11":["800-171 Rev2 (3.1.17,3.13.1,3.13.15,3.13.2)","800_53 Rev5 (AC-18,CM-7,PL-8,PM-7,SA-8,SC-23,SC-7,SC-8)"],"Standard9":["v1.1 (PR.AC-5,PR.DS-2,PR.DS-5,PR.IP-1,PR.PT-3,PR.PT-4)"],"Standard8":["v3.2.1 (4.1.1)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-341":{"Standard1":["v7 (12)","v8 (12.2)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.20,AC.L2-3.1.17,CM.L2-3.4.1,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3,PR.PT-4)"],"Standard8":["v3.2.1 (1.1.6,1.2)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-228":{"Standard1":["v7 (1.4)","v8 (1.1)"],"AWS Config":["(ecr-private-tag-immutability-enabled)"],"AWS Foundational Security Best Practices controls":["(ECR.2)"],"BSA":["v3 (AM-1,AM-3)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"DA":["(Article_7.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard15":["(07.a,07.e,09.ab)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1)"]},"ecc-aws-467":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (IVS-02)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.2,Article_8.4.a,Article_11.5)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,CP-06,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k,12.c)"],"Standard14":["27001_2013 (A.14.2.5,A.17.2.1)","27002_2013 (14.2.5,17.2.1)","27002_2022 (8.14,8.27,8.9)","27017_2015 (14.2.5,17.2.1)","27018_2019 (14,17)","27701_2019 (6.11.2.5,6.14.2.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,CP-6,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR-PT-5,PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-438":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-440":{"Standard1":["v7 (6.2,6.3)","v8 (8.2,8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_8.4.e,Article_10.7)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.5,10.3)"],"s202":["(CC7.2)"]},"ecc-aws-571":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800_53 Rev5 (CM-8,CM-8.1)"],"Standard9":["v1.1 (PR.DS-3)"]},"ecc-aws-276":{"Standard1":["v7 (6.2)","v8 (8.2)"],"AWS Config":["(rds-logging-enabled)"],"AWS Foundational Security Best Practices controls":["(RDS.9)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_8.4.e,Article_10.7)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.308.a.3.ii.A,164.308.a.5.ii.C,164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.2,10.3)"],"s202":["(CC7.2)"]},"ecc-aws-344":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (PV-1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (BAI06,BAI10)"],"Standard4":["v4 (TVM-01)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32.1)"],"Standard15":["(10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-2,CM-6,CM-9,SA-3,SA-8,SA-10)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-319":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CIS Oracle Database 19 Benchmark":["v1.0.0 (2.2.11)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-081":{"Standard1":["v7 (16)","v8 (8.11)"],"CIS AWS Foundations Benchmark":["v1.4.0 (4.6)","v1.5.0 (4.6)"],"AWS Foundational Security Best Practices controls":["(CloudWatch.6)"],"BSA":["v3 (DS-7,LT-1,LT-2,LT-5)"],"CJIS":["(5.4.1,5.4.3)"],"CMMC":["v2.0 (AU.L2-3.3.5)"],"Standard4":["v4 (LOG-05)"],"DA":["(Article_9.2,Article_15.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-06,AU-06.01,AU-06.05,AU-07)"],"Standard5":["(164.308.a.1.ii.D,164.312.b)"],"Standard14":["27001_2013 (A.12.4.1,A.16.1.4)","27002_2013 (12.4.1,16.1.4)","27002_2022 (5.25)","27017_2015 (12.4.1,16.1.4)","27018_2019 (12.4.1,6.1.4)","27701_2019 (6.13.1.4,6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.4)"],"Standard11":["800-171 Rev2 (3.3.3,3.3.5)","800_53 Rev5 (AU-6,AU-6.1,AU-6.5,AU-7)"],"Standard9":["v1.1 (DE.AE-2,PR.PT-1,RS.AN-1)"],"Standard8":["v3.2.1 (10.6,10.6.1,10.6.2)"],"s202":["(CC4.1,CC7.3)"]},"ecc-aws-380":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-486":{"Standard1":["v7 (5.1)","v8 (4.1)"],"AWS Config":["(codedeploy-lambda-allatonce-traffic-shift-disabled)"],"BSA":["v3 (GS-5,PV-1)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI06)"],"Standard4":["v4 (CCC-01)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,SA-03)"],"Standard15":["(10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1)","800_53 Rev5 (CM-2,CM-6,SA-3)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-520":{"Standard1":["v7 (12)","v8 (4.1)"],"AWS Config":["(autoscaling-launch-config-hop-limit)"],"AWS Foundational Security Best Practices controls":["(AutoScaling.4)"],"BSA":["v3 (GS-10,GS-5,NS-8,PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-07,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32.1.b)"],"Standard15":["(08.k,10.k,10.m)"],"Standard14":["27001_2013 (A.13.1.2,A.9.1.1,A.9.4.1)","27002_2013 (13.1.2,9.1.1,9.4.1)","27002_2022 (5.10,8.21,8.3)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-7,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC6.6)"]},"ecc-aws-271":{"Standard1":["v7 (14)","v8 (6.1)"],"AWS Foundational Security Best Practices controls":["(ElastiCache.6)"],"AWS Config":["(elasticache-repl-grp-redis-auth-enabled)"],"BSA":["v3 (IM-4,PA-7,PA-8)"],"CJIS":["(5.5.1,5.6.2.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.13,AC.L2-3.1.17,MP.L2-3.8.2,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard4":["v4 (IAM-01,IAM-06,IAM-07)"],"CE":["v2.2 (A7.1,A7.4,A7.5,uac)"],"DA":["(Article_14.d,Article_8.3,Article_8.4.c)"],"Standard10":["(D3.PC.Am.B.5,D3.PC.Am.B.6)"],"Standard3":["Standard11_800_53_Rev5 (AC-01,AC-02,AC-02.01,IA-04,IA-05)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.b,01.q)"],"Standard14":["27001_2013 (A.9.2.1,A.9.2.2,A.9.2.3)","27002_2013 (9.2.1,9.2.2,9.2.3)","27002_2022 (5.15,5.16,5.18)","27017_2015 (9.2.1,9.2.2,9.2.3)","27018_2019 (9.2.1,9.2.2,9.2.3)","27701_2019 (6.6.2.1,6.6.2.2,6.6.2.3)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.5.2)","800_53 Rev5 (AC-1,AC-2,AC-2.1,IA-4,IA-5)"],"Standard9":["v1.1 (PR.AC-1,PR.AC-4:)"],"Standard8":["v3.2.1 (7.2)"],"s202":["(CC6.2)"]},"ecc-aws-309":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-5,PV-1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (CCC-04,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_7.1,Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,SA-08)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-2,CM-6,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-084":{"Standard1":["v7 (14.9,6.2)","v8 (3.14,8.2)"],"CIS AWS Foundations Benchmark":["v1.4.0 (3.6)","v1.5.0 (3.6)"],"AWS Foundational Security Best Practices controls":["(CloudTrail.7)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (DSP-17,LOG-04,LOG-08)"],"DA":["(Article_10.7,Article_8.4.d,,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-06.09,AU-02,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AC-6.9,AU-12,AU-2)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.2,10.2.1,10.3,11.5)"],"s202":["(CC6.1,CC7.2)"]},"ecc-aws-023":{"Standard1":["v7 (6.2,6.3)","v8 (3.14,8.2,8.5)"],"AWS Config":["(elb-logging-enabled)"],"AWS Foundational Security Best Practices controls":["(ELB.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (DSP-17,LOG-04,LOG-08)"],"DA":["(Article_10.7,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-06.09,AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AC-6.9,AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.1,10.2.2,10.2.4,10.2.5,10.3,11.5)"],"s202":["(CC6.1,CC7.2)"]},"ecc-aws-259":{"Standard1":["v7 (11.7,14.6)","v8 (12.2,3.3)"],"BSA":["v3 (GS-4,NS-2,NS-9)"],"CJIS":["(5.10.1,5.5.2,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.20,CM.L2-3.4.1,MP.L2-3.8.2,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03,DSS05)"],"Standard4":["v4 (DSP-07,IAM-05,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,CM-07,MP-02,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.i,01.l,01.m,01.n,01.o,01.v,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (13.1.1,13.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.20,8.22,8.3,8.27)","27017_2015 (13.1.1,13.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (13.1,8,9.1,9.4.1)","27701_2019 (6.10.1.1,6.10.1.3,6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.14,3.13.2,3.13.5,3.8.2)","800_53 Rev5 (AC-3,AC-6,CM-7,MP-2,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-4,PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.2.2)"],"s202":["(CC6.1,CC6.3,CC6.6)"]},"ecc-aws-392":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-475":{"Standard1":["v7 (9.2)","v8 (12.2)"],"AWS Config":["(elb-cross-zone-load-balancing-enabled)"],"AWS Foundational Security Best Practices controls":["(ELB.9)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1)"],"CMMC":["v2.0 (AC.L1-3.1.20,CM.L2-3.4.1,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.2,Article_8.4.a)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.312.a.2.iv,164.312.e.1,164.312.e.2.i,164.312.e.2.ii,164.314.b.2.i)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard11":["800-171 Rev2 (3.13.2,3.4.2)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"]},"ecc-aws-278":{"Standard1":["v7 (3.1,5.1)","v8 (4.1,7.5)"],"BSA":["v3 (DS-6,GS-10,GS-5,PV-1,PV-3,PV-5)"],"CJIS":["(5.10.1.3,5.10.4.4,5.7.1)"],"Standard2":["19 (APO13,BAI06,BAI10,DSS05)"],"Standard4":["v4 (CCC-01,IVS-04,TVM-07)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_7.2,Article_8.1,Article_8.2,Article_8.4.b,Article_8.4.f)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,RA-05,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(06.h,08.k,10.k,10.m)"],"Standard14":["27001_2013 (A.12.6.1,A.14.2.5,A.18.2.3)","27002_2013 (12.6.1,14.2.5,18.2.3)","27002_2022 (5.36,8.27,8.8,8.9)","27017_2015 (12.6.1,14.2.5,18.2.3)","27018_2019 (12.6,14,18.2.3)","27701_2019 (6.11.2.5,6.15.2.3,6.9.6.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.11.2,3.14.1,3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,RA-5,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (DE.CM-8,PR.IP-1)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (11.2,2.2)"],"s202":["(CC7.1,CC8.1)"]},"ecc-aws-360":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (DP-4,DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a,Article_5.1.f)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)"],"s202":["(CC6.1)"]},"ecc-aws-294":{"Standard1":["v7 (5.5)","v8 (4.1)"],"BSA":["v3 (GS-5,PV-2)"],"CJIS":["(5.10.4.4,5.4.3)"],"CMMC":["v2.0 (AU.L2-3.3.5,CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-07,LOG-03)"],"CE":["v2.2 (Secure_configuration)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5,A.16.1.2)","27002_2013 (14.2.5,16.1.2)","27002_2022 (6.8,8.27,8.9)","27017_2015 (14.2.5,16.1.2)","27018_2019 (14,16.1.2)","27701_2019 (6.11.2.5,6.13.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-007-6_Requirement_R4_Part_4.2)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (DE.DP-4,PR.IP-1)"],"Standard8":["v3.2.1 (2.2,A.3.3.1)"],"s202":["(CC8.1)"]},"ecc-aws-022":{},"ecc-aws-150":{"Standard1":["v7 (14.8)","v8 (3.11)"],"AWS Config":["(api-gw-cache-enabled-and-encrypted)"],"AWS Foundational Security Best Practices controls":["(APIGateway.5)"],"BSA":["v3 (DP-4,DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)"],"s202":["(CC6.1)"]},"ecc-aws-121":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,13.4)"],"BSA":["v3 (GS-4,NS-1,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.2,SC.L2-3.13.6)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m,09.n)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.16,8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.1,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CA-9,CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-522":{"Standard1":["v7 (16.4)","v8 (3)"],"AWS Config":["(ecs-no-environment-secrets)"],"AWS Foundational Security Best Practices controls":["(ECS.8)"],"BSA":["v3 (IM-8)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.3.c)"],"Standard3":["Standard11_800_53_Rev5 (AC-02,AC-03,AC-07,AC-17,IA-05,IA-06)"],"Standard5":["(164.312.e.1)"],"Standard14":["27001_2013 (A.9.3.1)","27002_2013 (9.3.1)","27002_2022 (5.17)","27017_2015 (9.3.1)","27018_2019 (9.3.1)","27701_2019 (6.6.3.1)"],"Standard11":["800_53 Rev5 (AC-17,AC-2,AC-3,AC-7,IA-5,IA-6)"],"Standard9":["v1.1 (PR.AC-1,PR.AC-4,PR.AC-7,PR.DS-2,PR.DS-5,PR.IP-7,PR.PT-3)"],"Standard8":["v3.2.1 (10.2.2,8.2.1,8.3.1,8.3.2)"],"s202":["(CC6.1)"]},"ecc-aws-284":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-02,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32.1.b,Article_32.1.c)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5,A.17.2.1)","27002_2013 (14.2.5,17.2.1)","27002_2022 (8.14,8.27,8.9)","27017_2015 (14.2.5,17.2.1)","27018_2019 (14,17)","27701_2019 (6.11.2.5,6.14.2.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR-PT-5,PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-303":{"Standard1":["v7 (6.2)","v8 (8.2)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_8.4.e,Article_10.7)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.2,10.3)"],"s202":["(CC7.2)"]},"ecc-aws-426":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-324":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CIS Oracle Database 19 Benchmark":["v1.0.0 (2.2.16)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-524":{"Standard1":["v7 (9.5,14.2,12.9,18.10)","v8 (4.4,13.4,13.10)"],"AWS Config":["(waf-regional-webacl-not-empty)"],"AWS Foundational Security Best Practices controls":["(WAF.4)"],"BSA":["v3 (GS-9,NS-1,NS-2,NS-3,NS-7,NS-8)"],"CJIS":["(5.10.1,5.7.1.2)"],"CMMC":["v2.0 (AC.L1-3.1.20,CM.L2-3.4.7,SC.L1-3.13.1,SC.L2-3.13.6)"],"Standard2":["19 (APO13)"],"Standard4":["v4 (UEM-10)"],"CE":["v2.2 (A4.11,A4.5,A4.7,Firewalls)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.2,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,SC-07)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard15":["(09.m)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.20)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard11":["800-171 Rev2 (3.13.1)","800_53 Rev5 (CA-9,SC-7)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (1.1.4,1.3.1)"],"s202":["(CC6.6)"]},"ecc-aws-098":{"Standard1":["v7 (11.3,6.2)","v8 (8.11)"],"CIS AWS Foundations Benchmark":["v1.4.0 (4.12)","v1.5.0 (4.12)"],"AWS Foundational Security Best Practices controls":["(CloudWatch.12)"],"BSA":["v3 (DS-7,LT-1,LT-2,LT-5)"],"CJIS":["(5.4.1,5.4.3)"],"CMMC":["v2.0 (AU.L2-3.3.5)"],"Standard4":["v4 (LOG-05)"],"DA":["(Article_9.2,Article_15.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-06,AU-06.01,AU-06.05,AU-07)"],"Standard5":["(164.308.a.1.ii.D,164.312.b)"],"Standard14":["27001_2013 (A.12.4.1,A.16.1.4)","27002_2013 (12.4.1,16.1.4)","27002_2022 (5.25)","27017_2015 (12.4.1,16.1.4)","27018_2019 (12.4.1,6.1.4)","27701_2019 (6.13.1.4,6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.4)"],"Standard11":["800-171 Rev2 (3.3.3,3.3.5)","800_53 Rev5 (AU-6,AU-6.1,AU-6.5,AU-7)"],"Standard9":["v1.1 (DE.AE-2,PR.PT-1,RS.AN-1)"],"Standard8":["v3.2.1 (10.6,10.6.1,10.6.2)"],"s202":["(CC4.1,CC7.3)"]},"ecc-aws-033":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,13.4)"],"AWS Config":["(restricted-common-ports)"],"AWS Foundational Security Best Practices controls":["(EC2.19)"],"BSA":["v3 (GS-4,NS-1,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.2,SC.L2-3.13.6)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m,09.n)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.16,8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.1,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CA-9,CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-197":{"Standard1":["v7 (14.4)","v8 (3.10)"],"AWS Config":["(elasticsearch-node-to-node-encryption-check,opensearch-node-to-node-encryption-check)"],"AWS Foundational Security Best Practices controls":["(ES.3,OpenSearch.3)"],"BSA":["v3 (DP-3)"],"CJIS":["(5.10.1.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IVS-03)"],"DA":["(Article_8.2,Article_8.3.a,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.13)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,SC-08)"],"Standard6":["2016_679 (Article_32.1.a,Article_44)"],"Standard5":["(164.312.a.2.iv,164.312.e.1,164.312.e.2.i,164.312.e.2.ii,164.314.b.1,164.314.b.2,164.314.b.2.i)"],"Standard15":["(06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1,A.18.1.3)","27002_2013 (10.1.1,13.2.1,18.1.3)","27002_2022 (5.14,5.33,8.24)","27017_2015 (10.1.1,13.2.1,18.1.3)","27018_2019 (10.1.1,13.2.1,18.1.3)","27701_2019 (6.10.2.1,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.8)","800_53 Rev5 (AC-17.2,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-2)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1.1,4.1,4.1.1,8.2.1)"],"s202":["(CC6.1,CC6.7)"]},"ecc-aws-069":{"Standard1":["v7 (14.6)","v8 (3.3)"],"BSA":["v3 (AM-4,IM-7,PA-7)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.3.b,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.3)","27017_2015 (8.1.3,9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-aws-068":{"Standard1":["v7 (14.6)","v8 (3.3)"],"CIS AWS Foundations Benchmark":["v1.4.0 (3.3)","v1.5.0 (3.3)"],"AWS Foundational Security Best Practices controls":["(CloudTrail.6)"],"BSA":["v3 (AM-4,IM-7,PA-7)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.3.b,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.3)","27017_2015 (8.1.3,9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-aws-155":{"Standard1":["v7 (5.1)","v8 (4.1)"],"AWS Foundational Security Best Practices controls":["(ES.7)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3,PV-4)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_321.b)"],"Standard15":["(08.k,10.k,12.c)"],"Standard14":["27001_2013 (A.14.2.5,A.17.2.1)","27002_2013 (14.2.5,17.2.1)","27002_2022 (8.14,8.27,8.9)","27017_2015 (14.2.5,17.2.1)","27018_2019 (14,17)","27701_2019 (6.11.2.5,6.14.2.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1,PR.PT-5)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-039":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,13.4)"],"AWS Foundational Security Best Practices controls":["(EC2.19)"],"BSA":["v3 (GS-4,NS-1,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.2,SC.L2-3.13.6)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m,09.n)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.16,8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.1,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CA-9,CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-430":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-527":{"Standard1":["v7 (18.10)","v8 (4.4)"],"AWS Config":["(waf-global-webacl-not-empty)"],"AWS Foundational Security Best Practices controls":["(WAF.8)"],"BSA":["v3 (GS-9,NS-1,NS-2,NS-3,NS-7,NS-8)"],"CJIS":["(5.10.1,5.7.1.2)"],"CMMC":["v2.0 (AC.L1-3.1.20,CM.L2-3.4.7,SC.L1-3.13.1,SC.L2-3.13.6)"],"Standard2":["19 (APO13)"],"Standard4":["v4 (UEM-10)"],"CE":["v2.2 (A4.11,A4.5,A4.7,Firewalls)"],"DA":["(Article_8.3.b,Article_8.4.b,Article_9.1)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.2,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,SC-07,SC-07.05)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard15":["(09.m)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.20)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard11":["800-171 Rev2 (3.13.1)","800_53 Rev5 (CA-9,SC-7,SC-7.5)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (1.1.4,1.3.1)"],"s202":["(CC6.6)"]},"ecc-aws-513":{"Standard1":["v7 (14.4)","v8 (12.2,12.6)"],"AWS Foundational Security Best Practices controls":["(ACM.2)"],"AWS Config":["(acm-certificate-rsa-check)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.13.1.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.13,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,IA.L1-3.5.2,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.15,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.10,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-07,PL-08,SA-08,SC-07,SC-08,SC-23)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.312.e.1)"],"Standard15":["(01.i,01.j,01.l,01.m,01.n,01.o,09.m,09.s,09.y,10.d)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.2,A.13.1.3)","27002_2013 (13.1.1,13.1.2,13.1.3)","27002_2022 (8.20,8.21,8.22,8.27)","27017_2015 (13.1.1,13.1.2,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.2,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.17,3.1.5,3.13.1,3.13.15,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (AC-18,CM-7,PL-8,PM-7,SA-8,SC-23,SC-7,SC-8)"],"Standard9":["v1.1 (PR.AC-5,PR.DS-2,PR.DS-5,PR.IP-1,PR.PT-3,PR.PT-4)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5,4.1.1)"],"s202":["(CC5.2,CC6.1,CC6.6)"]},"ecc-aws-358":{"Standard1":["v7 (14.8,6.2,6.3)","v8 (3.11,8.2,8.5)"],"AWS Config":["(cloudtrail-security-trail-enabled)"],"BSA":["v3 (DP-4,DP-5,LT-3,LT-4)"],"CJIS":["(5.10.1.2.2,5.4.1,5.4.5)"],"CMMC":["v2.0 (AU.L2-3.3.1,AU.L2-3.3.8,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03,LOG-08)"],"DA":["(Article_8.2,Article_8.4.d,Article_8.4.e,Article_10.7)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-03,AU-09,AU-12,SC-28)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(06.c,06.d,09.aa)"],"Standard14":["27001_2013 (A.10.1.1,A.12.4.1,A.12.4.2,A.18.1.3)","27002_2013 (10.1.1,12.4.1,12.4.2,18.1.3)","27002_2022 (5.28,5.33,8.15,8.24)","27017_2015 (10.1.1,12.4.1,12.4.2,18.1.3)","27018_2019 (10.1.1,12.4.1,12.4.2,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1,6.9.4.1,6.9.4.2)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.3.1,3.3.8)","800_53 Rev5 (AU-12,AU-2,AU-3,AU-3.1,AU-9,SC-28,SC-28.1)"],"Standard9":["v1.1 (DE.AE-3,PR.DS-1,PR.PT-1)"],"Standard7":["(11,6)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.2,10.2.4,10.2.5,10.3,10.5)"],"s202":["(CC6.1,CC7.2)"]},"ecc-aws-478":{"Standard1":["v7 (12)","v8 (4.1)"],"AWS Config":["(cloudfront-sni-enabled)"],"AWS Foundational Security Best Practices controls":["(CloudFront.8)"],"BSA":["v3 (GS-10,GS-5,NS-8,PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-07,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32.1.b)"],"Standard15":["(08.k,10.k,10.m)"],"Standard14":["27001_2013 (A.13.1.2,A.9.1.1,A.9.4.1)","27002_2013 (13.1.2,9.1.1,9.4.1)","27002_2022 (5.10,8.21,8.3)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-7,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC6.6)"]},"ecc-aws-002":{"Standard1":["v7 (16)","v8 (5)"],"CIS AWS Foundations Benchmark":["v1.4.0 (1.14)","v1.5.0 (1.14)"],"AWS Config":["(access-keys-rotated)"],"AWS Foundational Security Best Practices controls":["(IAM.3)"],"BSA":["v3 (PA-3)"],"CJIS":["(5.5)"],"Standard4":["v4 (IAM-07)"],"DA":["(Article_8.3.b)"],"Standard3":["Standard11_800_53_Rev5 (SC-12,SC-13)"],"Standard6":["2016_679 (Article_32.1.a.b)"],"Standard5":["(164.308.a.3.ii.C,164.308.a.4.ii.B,164.312.a.1)"],"Standard15":["(01.b,10.g)"],"Standard14":["27001_2013 (A.10.1.2)","27002_2013 (10.1.2)","27002_2022 (8.24)","27017_2015 (10.1.2)","27018_2019 (10.1.2)","27701_2019 (6.7.1.2)"],"Standard11":["800_53 Rev5 (SC-12,SC-13)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard8":["v3.2.1 (8.2.4)"],"s202":["(CC6.1,CC6.3)"]},"ecc-aws-464":{"Standard1":["v7 (6.2)","v8 (8.5)"],"AWS Config":["(ecs-task-definition-log-configuration)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_8.4.e,Article_10.7)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.5,10.3)"],"s202":["(CC7.2)"]},"ecc-aws-037":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,13.4)"],"AWS Foundational Security Best Practices controls":["(EC2.19)"],"BSA":["v3 (GS-4,NS-1,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.2,SC.L2-3.13.6)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m,09.n)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.16,8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.1,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CA-9,CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-500":{"Standard1":["v7 (5.1)","v8 (4.1)"],"AWS Config":["(lambda-vpc-multi-az-check)"],"AWS Foundational Security Best Practices controls":["(Lambda.5)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (IVS-02)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.2,Article_8.4.a,Article_11.5)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,CP-06,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k,12.c)"],"Standard14":["27001_2013 (A.14.2.5,A.17.2.1)","27002_2013 (14.2.5,17.2.1)","27002_2022 (8.14,8.27,8.9)","27017_2015 (14.2.5,17.2.1)","27018_2019 (14,17)","27701_2019 (6.11.2.5,6.14.2.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,CP-6,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR-PT-5,PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-545":{"Standard1":["v7 (6.2,6.3)","v8 (8.2,8.5)"],"AWS Foundational Security Best Practices controls":["(StepFunctions.1)"],"AWS Config":["(step-functions-state-machine-logging-enabled)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_8.4.e,Article_10.7)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.5,10.3)"],"s202":["(CC7.2)"]},"ecc-aws-181":{"Standard1":["v7 (12,14.6)","v8 (12.2,3.3)"],"AWS Config":["(dms-replication-not-public)"],"AWS Foundational Security Best Practices controls":["(DMS.1)"],"BSA":["v3 (AM-4,GS-4,IM-7,NS-2,PA-7)"],"CJIS":["(5.10.1,5.5.2,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L1-3.1.22,AC.L2-3.1.5,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,MP.L2-3.8.2,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03,DSS05)"],"Standard4":["v4 (DSP-07,IAM-05,IVS-03)"],"CE":["v2.2 (A4.8,A7.4,Firewalls,Secure_configuration,uac)"],"DA":["(Article_8.3.b,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,CM-07,MP-02,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.308.a.1.ii.B,164.308.a.3.i,164.308.a.4.i,164.308.a.4.ii.A,164.308.a.4.ii.B,164.308.a.4.ii.C,164.312.a.1,164.312.e.1)"],"Standard15":["(01.a,01.c,01.i,01.l,01.m,01.n,01.o,01.v,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (13.1.1,13.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.20,8.22,8.3,8.27)","27017_2015 (13.1.1,13.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (13.1,8,9.1,9.4.1)","27701_2019 (6.10.1.1,6.10.1.3,6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.1.5,3.13.2,3.4.6,3.8.2)","800_53 Rev5 (AC-3,AC-6,CM-7,MP-2,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-4,PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)"],"s202":["(CC6.1,CC6.3,CC6.6)"]},"ecc-aws-279":{"Standard1":["v7 (16,5.1)","v8 (4.1,4.6,5)"],"BSA":["v3 (DP-6,GS-10,GS-5,PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,SC.L2-3.13.15)"],"Standard2":["19 (APO13,BAI06,BAI09,BAI10)"],"Standard4":["v4 (CCC-01)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(07.c,10.k)"],"Standard14":["27001_2013 (A.14.2.5,A.9.2.4,A.9.4.3)","27002_2013 (14.2.5,9.2.4,9.4.3)","27002_2022 (5.17,8.27,8.9)","27017_2015 (14.2.5,9.2.4,9.4.3)","27018_2019 (14,9.2.4,9.4.3)","27701_2019 (6.11.2.5,6.6.2.4,6.6.4.3)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1,PR.IP-2)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-093":{"Standard1":["v7 (11.7,12)","v8 (12.2,6)"],"BSA":["v3 (GS-4,NS-2)"],"AWS Foundational Security Best Practices controls":["(SageMaker.2)"],"AWS Config":["(sagemaker-notebook-instance-inside-vpc)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.308.a.1.ii.B,164.308.a.3.i,164.308.a.4.ii.A,164.308.a.4.ii.C,164.312.a.1,164.312.e.1)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-164":{"Standard1":["v7 (6.2,6.3)","v8 (8.2,8.5)"],"AWS Config":["(redshift-audit-logging-enabled)","(redshift-cluster-configuration-check)"],"AWS Foundational Security Best Practices controls":["(Redshift.4)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_8.4.e,Article_10.7)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.5,10.3)"],"s202":["(CC7.2)"]},"ecc-aws-354":{"Standard1":["v7 (5.1)","v8 (12.2,4.1)"],"BSA":["v3 (GS-10,GS-4,GS-5,NS-2,PV-1,PV-3)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03,APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,DSP-07,IVS-03,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.1,D3.PC.Im.B.5,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-07,CM-09,PL-08,SA-03,SA-08,SA-10,SC-07)"],"Standard6":["2016_679 (Article_32,Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,08.k,09.m,10.k)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3,A.14.2.5)","27002_2013 (13.1.1,13.1.3,14.2.5)","27002_2022 (8.20,8.22,8.27,8.9)","27017_2015 (13.1.1,13.1.3,14.2.5)","27018_2019 (13.1,14)","27701_2019 (6.10.1.1,6.10.1.3,6.11.2.5)"],"Standard12":["(CIP-007-6_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.4.1,3.4.2,3.4.6)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-7,CM-9,PL-8,PM-7,SA-10,SA-3,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2,2.2.2,2.2.5)"],"s202":["(CC6.6,CC8.1)"]},"ecc-aws-352":{"Standard1":["v7 (14.6,14.8)","v8 (3.11,3.3)"],"AWS Config":["(sns-encrypted-kms)"],"BSA":["v3 (AM-4,DP-4,DP-5,IM-7,PA-7)"],"CJIS":["(5.10.1.2.2,5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.1,MP.L2-3.8.2,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03,IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.4.d,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,IA-05.01,MP-02,SC-28)"],"Standard6":["2016_679 (Article_32.1.a,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.a.2.iv)"],"Standard15":["(01.a,01.c,01.v,06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,5.33,8.24,8.3)","27017_2015 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (10.1.1,18.1.3,8,9.1,9.4.1)","27701_2019 (6.15.1.3,6.5.1.3,6.6.1.1,6.6.4.1,6.7.1.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.13.16,3.8.1,3.8.2)","800_53 Rev5 (AC-3,AC-6,IA-5.1,MP-2,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.AC-4,PR.DS-1)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (3.4,3.4.1,7.1,7.1.1,7.1.2,7.1.3,8.2.1)"],"s202":["(CC6.1,CC6.3)"]},"ecc-aws-085":{"Standard1":["v7 (11.7)","v8 (12.2)"],"AWS Foundational Security Best Practices controls":["(Lambda.3)"],"AWS Config":["(lambda-inside-vpc)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-021":{"Standard1":["v7 (10.1)","v8 (11.2)"],"BSA":["v3 (BR-1)"],"Standard4":["v4 (BCR-08)"],"CE":["v2.2 (Back_up_your_data)"],"DA":["(Article_11.2,Article_11.4)"],"Standard10":["(D5.IR.Pl.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CP-09,CP-10)"],"Standard6":["2016_679 (Article_32.1.c)"],"Standard5":["(164.308.a.7.ii.A)"],"Standard15":["(09.l)"],"Standard14":["27001_2013 (A.12.3.1)","27002_2013 (12.3.1)","27002_2022 (8.13)","27017_2015 (12.3.1)","27018_2019 (12.3.1)","27701_2019 (6.9.3.1)"],"Standard12":["(CIP-009-6_Requirement_R1_Part_1.3)"],"Standard11":["800_53 Rev5 (CP-10,CP-9)"],"Standard9":["v1.1 (PR.IP-4)"],"Standard7":["(6s)"],"Standard8":["v3.2.1 (12.10.1)"],"s202":["(A1.2)"]},"ecc-aws-215":{"Standard1":["v7 (10.1)","v8 (11.2)"],"AWS Config":["(redshift-backup-enabled)"],"AWS Foundational Security Best Practices controls":["(Redshift.3)"],"BSA":["v3 (BR-1)"],"Standard4":["v4 (BCR-08)"],"CE":["v2.2 (Back_up_your_data)"],"DA":["(Article_11.2,Article_11.4)"],"Standard10":["(D5.IR.Pl.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CP-09,CP-10)"],"Standard6":["2016_679 (Article_32.1.c)"],"Standard5":["(164.308.a.7.i,164.308.a.7.ii.A,164.308.a.7.ii.B,164.312.a.2.ii)"],"Standard15":["(09.l)"],"Standard14":["27001_2013 (A.12.3.1)","27002_2013 (12.3.1)","27002_2022 (8.13)","27017_2015 (12.3.1)","27018_2019 (12.3.1)","27701_2019 (6.9.3.1)"],"Standard12":["(CIP-009-6_Requirement_R1_Part_1.3)"],"Standard11":["800_53 Rev5 (CP-10,CP-9)"],"Standard9":["v1.1 (PR.IP-4)"],"Standard7":["(6s)"],"Standard8":["v3.2.1 (12.10.1)"],"s202":["(A1.2)"]},"ecc-aws-102":{"Standard1":["v7 (12)","v8 (12.2,6)"],"AWS Config":["(sagemaker-notebook-no-direct-internet-access)"],"AWS Foundational Security Best Practices controls":["(SageMaker.1)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.308.a.1.ii.B,164.308.a.3.i,164.308.a.4.ii.A,164.308.a.4.ii.C,164.312.a.1,164.312.e.1)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-362":{"Standard1":["v7 (14.6,14.8)","v8 (3.11,3.3)"],"BSA":["v3 (AM-4,DP-4,DP-5,IM-7,PA-7)"],"CJIS":["(5.10.1.2.2,5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.1,MP.L2-3.8.2,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03,IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.4.d,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,IA-05.01,MP-02,SC-28)"],"Standard6":["2016_679 (Article_32.1.a,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.a.2.iv)"],"Standard15":["(01.a,01.c,01.v,06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,5.33,8.24,8.3)","27017_2015 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (10.1.1,18.1.3,8,9.1,9.4.1)","27701_2019 (6.15.1.3,6.5.1.3,6.6.1.1,6.6.4.1,6.7.1.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.13.16,3.8.1,3.8.2)","800_53 Rev5 (AC-3,AC-6,IA-5.1,MP-2,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.AC-4,PR.DS-1)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (3.4,3.4.1,7.1,7.1.1,7.1.2,7.1.3,8.2.1)"],"s202":["(CC6.1,CC6.3)"]},"ecc-aws-365":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (DP-4,DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(01.d,06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)"],"s202":["(CC6.1)"]},"ecc-aws-160":{"Standard1":["v7 (5.5)","v8 (4.1)"],"AWS Foundational Security Best Practices controls":["(RDS.20)"],"BSA":["v3 (GS-5,PV-2)"],"CJIS":["(5.10.4.4,5.4.3)"],"CMMC":["v2.0 (AU.L2-3.3.5,CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-07,LOG-03)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5,A.16.1.2)","27002_2013 (14.2.5,16.1.2)","27002_2022 (6.8,8.27,8.9)","27017_2015 (14.2.5,16.1.2)","27018_2019 (14,16.1.2)","27701_2019 (6.11.2.5,6.13.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-007-6_Requirement_R4_Part_4.2)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (DE.DP-4,PR.IP-1)"],"Standard8":["v3.2.1 (2.2,A.3.3.1)"],"s202":["(CC8.1)"]},"ecc-aws-075":{"Standard1":["v7 (14.8)","v8 (3.11)"],"AWS Config":["(opensearch-encrypted-at-rest,elasticsearch-encrypted-at-rest)"],"AWS Foundational Security Best Practices controls":["(ES.1,OpenSearch.1)"],"BSA":["v3 (DP-4,DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.308.a.1.ii.B,164.312.a.2.iv,164.312.e.2.ii,164.314.b.1,164.314.b.2,164.314.b.2.i)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)"],"s202":["(CC6.1)"]},"ecc-aws-076":{"Standard1":["v7 (13,14.6)","v8 (3.3)"],"AWS Config":["(ebs-snapshot-public-restorable-check)"],"AWS Foundational Security Best Practices controls":["(EC2.1)"],"CIS AWS Compute Services Benchmark":["v1.0.0 (2.2.2)"],"BSA":["v3 (AM-4,IM-7)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (AC.L1-3.1.1)"],"Standard4":["v4 (CCC-04)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-17)"],"Standard5":["(164.308.a.1.ii.B.,164.308.a.3.i,164.308.a.4.ii.A,164.308.a.4.ii.C,164.312.a.1,164.312.e.1)"],"Standard14":["27001_2013 (A.9.4.1)","27002_2013 (9.4.1)","27002_2022 (8.3)","27017_2015 (9.4.1)","27018_2019 (9.4.1)","27701_2019 (6.6.4.1)"],"Standard11":["800_53 Rev5 (AC-17,AC-3)"],"Standard9":["v1.1 (PR.AC-3,PR.AC-4,PR.AC-5)"],"Standard8":["v3.2.1 (10.1,10.2)"],"s202":["(CC6.1)"]},"ecc-aws-029":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,13.4)"],"AWS Config":["(restricted-common-ports)"],"AWS Foundational Security Best Practices controls":["(EC2.19)"],"BSA":["v3 (GS-4,NS-1,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.2,SC.L2-3.13.6)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m,09.n)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.16,8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.1,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CA-9,CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-338":{"Standard1":["v7 (5.1)","v8 (4.1)"],"AWS Config":["(sagemaker-notebook-instance-root-access-check)"],"BSA":["v3 (GS-10,GS-5,PA-7,PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.3.c,Article_14.d,Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(01.c,08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-089":{"Standard1":["v7 (16.4)","v8 (3)"],"AWS Config":["(codebuild-project-envvar-awscred-check)"],"AWS Foundational Security Best Practices controls":["(CodeBuild.2)"],"BSA":["v3 (IM-8)"],"Standard3":["Standard11_800_53_Rev5 (AC-02,AC-03,AC-07,AC-17,IA-05,IA-06)"],"Standard5":["(164.308.a.1.ii.B,164.308.a.3.i,164.308.a.4.ii.A,164.308.a.4.ii.C,164.312.a.1,164.312.e.1)"],"Standard14":["27001_2013 (A.9.3.1)","27002_2013 (9.3.1)","27002_2022 (5.17)","27017_2015 (9.3.1)","27018_2019 (9.3.1)","27701_2019 (6.6.3.1)"],"Standard11":["800_53 Rev5 (AC-17,AC-2,AC-3,AC-7,IA-5,IA-6)"],"Standard9":["v1.1 (PR.AC-1,PR.AC-4,PR.AC-7,PR.DS-2,PR.DS-5,PR.IP-7,PR.PT-3)"],"Standard8":["v3.2.1 (10.2.2,8.2.1,8.3.1,8.3.2)"],"s202":["(CC6.1)"]},"ecc-aws-236":{"Standard1":["v7 (6.3)","v8 (8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"Standard13":["v1.0.0 (3.1.17)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.5,10.3)"],"s202":["(CC7.2)"]},"ecc-aws-343":{"Standard1":["v7 (12)","v8 (12.2)"],"AWS Config":["(mq-no-public-access)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-116":{"Standard1":["v7 (12,14.6)","v8 (12.2,6)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-165":{"Standard1":["v7 (12)","v8 (12.2)"],"AWS Foundational Security Best Practices controls":["(ECS.2)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.13.2,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.3.6,2.2.2)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-270":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (IVS-02)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.2,Article_8.4.a,Article_11.5)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,CP-06,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k,12.c)"],"Standard14":["27001_2013 (A.14.2.5,A.17.2.1)","27002_2013 (14.2.5,17.2.1)","27002_2022 (8.14,8.27,8.9)","27017_2015 (14.2.5,17.2.1)","27018_2019 (14,17)","27701_2019 (6.11.2.5,6.14.2.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,CP-6,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR-PT-5,PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-326":{"Standard1":["v7 (14.6,14.8)","v8 (3.11,3.3)"],"AWS Config":["(encrypted-volumes)"],"BSA":["v3 (AM-4,DP-4,DP-5,IM-7,PA-7)"],"CJIS":["(5.10.1.2.2,5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.1,MP.L2-3.8.2,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03,IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.4.d,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,IA-05.01,MP-02,SC-28)"],"Standard6":["2016_679 (Article_32.1.a,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.a.2.iv)"],"Standard15":["(01.a,01.c,01.v,06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,5.33,8.24,8.3)","27017_2015 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (10.1.1,18.1.3,8,9.1,9.4.1)","27701_2019 (6.15.1.3,6.5.1.3,6.6.1.1,6.6.4.1,6.7.1.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.13.16,3.8.1,3.8.2)","800_53 Rev5 (AC-3,AC-6,IA-5.1,MP-2,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.AC-4,PR.DS-1)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (3.4,3.4.1,7.1,7.1.1,7.1.2,7.1.3,8.2.1)"],"s202":["(CC6.1,CC6.3)"]},"ecc-aws-372":{"Standard1":["v7 (13.3,9)","v8 (12.2)"],"BSA":["v3 (GS-4,NS-2)"],"CAEUC":["v1.0.0 (2.17)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,CM.L2-3.4.1,SC.L2-3.13.2)"],"Standard2":["19 (APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.m,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.13.2)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.IP-1)"],"s202":["(CC6.6)"]},"ecc-aws-336":{"Standard1":["v7 (14.6,14.8)","v8 (3.11,3.3)"],"AWS Config":["(sagemaker-endpoint-configuration-kms-key-configured)"],"BSA":["v3 (AM-4,DP-4,DP-5,IM-7,PA-7)"],"CJIS":["(5.10.1.2.2,5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.1,MP.L2-3.8.2,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03,IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.4.d,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,IA-05.01,MP-02,SC-28)"],"Standard6":["2016_679 (Article_32.1.a,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.a.2.iv)"],"Standard15":["(01.a,01.c,01.v,06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,5.33,8.24,8.3)","27017_2015 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (10.1.1,18.1.3,8,9.1,9.4.1)","27701_2019 (6.15.1.3,6.5.1.3,6.6.1.1,6.6.4.1,6.7.1.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.13.16,3.8.1,3.8.2)","800_53 Rev5 (AC-3,AC-6,IA-5.1,MP-2,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.AC-4,PR.AC-5,PR.DS-1,PR.PT-4)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (3.4,3.4.1,7.1,7.1.1,7.1.2,7.1.3,8.2.1)"],"s202":["(CC6.1,CC6.3)"]},"ecc-aws-536":{"Standard1":["v7 (5.1)","v8 (4.1)"],"AWS Foundational Security Best Practices controls":["(Lambda.2)"],"AWS Config":["(lambda-function-settings-check)"],"CIS AWS Compute Services Benchmark":["v1.0.0 (4.11)"],"BSA":["v3 (GS-5,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.2)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.f)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-09,SA-03,SA-08)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-2,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1,PR.IP-2)"],"Standard8":["v3.2.1 (2.2,6.2)"],"s202":["(CC8.1)"]},"ecc-aws-512":{"Standard1":["v7 (12)","v8 (12.2)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-367":{"Standard1":["v7 (9.2)","v8 (12.2)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.1,SC.L2-3.13.2,SC.L2-3.13.6)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3,CIP-007-6_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-282":{"Standard1":["v7 (14.4,9.2)","v8 (12.2,3.10)"],"BSA":["v3 (DP-3,GS-4,NS-2,NS-8)"],"CJIS":["(5.10.1,5.10.1.2.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.7,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.2,SC.L2-3.13.8)"],"Standard2":["19 (APO01,APO03,DSS05)"],"Standard4":["v4 (CEK-03,DSP-07,DSP-10,DSP-17,IVS-03)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.2,Article_8.3.a,Article_8.3.c,Article_8.4.b,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.13,D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,CM-07,PL-08,SA-08,SC-07,SC-08)"],"Standard6":["2016_679 (Article_32.1.a,Article_32.1.b,Article_44,Article_5.1.f)"],"Standard5":["(164.312.e.1)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,06.c,06.d,09.m,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.1.1,A.13.1.3,A.13.2.1,A.14.1.3,A.18.1.3)","27002_2013 (10.1.1,13.1.1,13.1.3,13.2.1,14.1.3,18.1.3)","27002_2022 (5.14,5.33,8.20,8.22,8.24,8.26,8.27)","27017_2015 (10.1.1,13.1.1,13.1.3,13.2.1,14.1.3,18.1.3)","27018_2019 (10.1.1,13.1,13.2.1,14,18.1.3)","27701_2019 (6.10.1.1,6.10.1.3,6.10.2.1,6.11.1.3,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.2,3.13.8)","800_53 Rev5 (AC-17.2,CM-7,PL-8,PM-7,SA-8,SC-7,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.AC-5,PR.DS-2,PR.IP-1,PR.PT-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.1.1,2.2.2,2.2.5,4.1,4.1.1,8.2.1)"],"s202":["(CC6.1,CC6.3)"]},"ecc-aws-452":{"Standard1":["v7 (5.1)","v8 (4.2)"],"AWS Foundational Security Best Practices controls":["(ELB.7)"],"BSA":["v3 (GS-10,GS-5,PV-1)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-aws-348":{"Standard1":["v7 (14.4)","v8 (3.10)"],"BSA":["v3 (DP-3)"],"CJIS":["(5.10.1.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IVS-03)"],"DA":["(Article_8.2,Article_8.3.a,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.13)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,SC-08)"],"Standard6":["2016_679 (Article_32.1.a,Article_44)"],"Standard5":["(164.312.e.1)"],"Standard15":["(06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1,A.18.1.3)","27002_2013 (10.1.1,13.2.1,18.1.3)","27002_2022 (5.14,5.33,8.24)","27017_2015 (10.1.1,13.2.1,18.1.3)","27018_2019 (10.1.1,13.2.1,18.1.3)","27701_2019 (6.10.2.1,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.8)","800_53 Rev5 (AC-17.2,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-2)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1.1,4.1,4.1.1,8.2.1)"],"s202":["(CC6.1,CC6.7)"]},"ecc-aws-019":{"Standard1":["v7 (4.4)","v8 (5.2)"],"CIS AWS Foundations Benchmark":["v1.4.0 (1.9)","v1.5.0 (1.9)"],"AWS Foundational Security Best Practices controls":["(IAM.10,IAM.16)"],"AWS Config":["(iam-password-policy)"],"CJIS":["(5.6.2.1.1)"],"CMMC":["v2.0 (IA.L2-3.5.7)"],"Standard4":["v4 (IAM-02)"],"CE":["v2.2 (Password_based_Authentication)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.7)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01)"],"Standard5":["(164.308.a.5.ii.D)"],"Standard15":["(01.f)"],"Standard14":["27001_2013 (A.9.4.3)","27002_2013 (9.4.3)","27002_2022 (5.17)","27017_2015 (9.4.3)","27018_2019 (9.4.3)","27701_2019 (6.6.4.3)"],"Standard12":["(CIP-007-6_Requirement_R5_Part_5.5)"],"Standard11":["800-171 Rev2 (3.5.7)","800_53 Rev5 (IA-5.1)"],"Standard8":["v3.2.1 (2.1,8.2.3,8.2.6)"],"s202":["(CC6.1)"]},"ecc-aws-301":{"Standard1":["v7 (5.1)","v8 (4.1)"],"AWS Foundational Security Best Practices controls":["(Lambda.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard4":["v4 (AIS-06)"],"Standard3":["Standard11_800_53_Rev5 (CA-07,SA-03)"],"Standard15":["(06.c)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard11":["800-171 Rev2 (3.4.1)","800_53 Rev5 (CA-7,SA-3)"],"s202":["(CC8.1)"]},"ecc-aws-450":{"Standard1":["v7 (12)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,NS-8,PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-07,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32.1.b)"],"Standard15":["(08.k,10.k,10.m)"],"Standard14":["27001_2013 (A.13.1.2,A.9.1.1,A.9.4.1)","27002_2013 (13.1.2,9.1.1,9.4.1)","27002_2022 (5.10,8.21,8.3)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-7,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC6.6)"]},"ecc-aws-024":{"Standard1":["v7 (14.4)","v8 (3.10)"],"AWS Foundational Security Best Practices controls":["(SQS.1)"],"BSA":["v3 (DP-3)"],"CJIS":["(5.10.1.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IVS-03)"],"DA":["(Article_8.2,Article_8.3.a,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12,D3.PC.Am.B.13)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,SC-08)"],"Standard6":["2016_679 (Article_32.1.a,Article_44)"],"Standard5":["(164.312.e.1)"],"Standard15":["(06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1,A.18.1.3)","27002_2013 (10.1.1,13.2.1,18.1.3)","27002_2022 (5.14,5.33,8.24)","27017_2015 (10.1.1,13.2.1,18.1.3)","27018_2019 (10.1.1,13.2.1,18.1.3)","27701_2019 (6.10.2.1,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.8,3.5.10)","800_53 Rev5 (AC-17.2,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-2)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1.1,4.1,4.1.1,8.2.1)"],"s202":["(CC6.1,CC6.7)"]},"ecc-aws-097":{"Standard1":["v7 (11.3,6.2)","v8 (8.11)"],"CIS AWS Foundations Benchmark":["v1.4.0 (4.11)","v1.5.0 (4.11)"],"AWS Foundational Security Best Practices controls":["(CloudWatch.11)"],"BSA":["v3 (BR-2,BR-3)"],"CMMC":["v2.0 (MP.L2-3.8.9)"],"Standard4":["v4 (BCR-08,CEK-03)"],"CE":["v2.2 (Back_up_your_data)"],"DA":["(Article_9.2,Article_15.1)"],"Standard3":["Standard11_800_53_Rev5 (CP-09,CP-09.08,SC-28)"],"Standard6":["2016_679 (Article_32.1.c)"],"Standard15":["(06.c,09.l)"],"Standard14":["27001_2013 (A.18.1.3)","27002_2013 (18.1.3)","27002_2022 (5.33,8.12)","27017_2015 (18.1.3)","27018_2019 (18.1.3)","27701_2019 (6.15.1.3)"],"Standard11":["800-171 Rev2 (3.8.9)","800_53 Rev5 (CP-9,CP-9.8,SC-28)"],"Standard9":["v1.1 (PR.DS-1,PR.IP-4)"],"Standard7":["(6)"],"s202":["(A1.2,CC6.4,CC6.7)"]},"ecc-aws-103":{"Standard1":["v7 (5.1)","v8 (4.1)"],"AWS Config":["(cloudfront-custom-ssl-certificate)"],"AWS Foundational Security Best Practices controls":["(CloudFront.7)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-424":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-043":{"Standard1":["v7 (10)","v8 (3.4)"],"AWS Config":["(s3-lifecycle-policy-check)"],"AWS Foundational Security Best Practices controls":["(S3.13)"],"CJIS":["(5.4.6)"],"Standard2":["19 (DSS06)"],"Standard4":["v4 (DSP-16)"],"DA":["(Article_8.3.d)"],"Standard3":["Standard11_800_53_Rev5 (SI-12)"],"Standard6":["2016_679 (Article_5.1.e)"],"Standard15":["(13.l)"],"Standard14":["27001_2013 (A.18.1.3)","27002_2013 (18.1.3)","27002_2022 (5.33)","27017_2015 (18.1.3)","27018_2019 (18.1)","27701_2019 (6.15.1.3)"],"Standard11":["800_53 Rev5 (SI-12)"],"Standard7":["(4._Integrate_data_privacy_into_records_retention_practices)"],"Standard8":["v3.2.1 (3.1)"],"s202":["(C1.1)"]},"ecc-aws-051":{"Standard1":["v7 (4.4)","v8 (5.2)"],"CIS AWS Foundations Benchmark":["v1.2.0 (1.11)"],"AWS Foundational Security Best Practices controls":["(IAM.10,IAM.17)"],"AWS Config":["(iam-password-policy)"],"CJIS":["(5.6.2.1.1)"],"CMMC":["v2.0 (IA.L2-3.5.7)"],"Standard4":["v4 (IAM-02)"],"CE":["v2.2 (Password_based_Authentication)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.7)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01)"],"Standard5":["(164.308.a.5.ii.D)"],"Standard15":["(01.f)"],"Standard14":["27001_2013 (A.9.4.3)","27002_2013 (9.4.3)","27002_2022 (5.17)","27017_2015 (9.4.3)","27018_2019 (9.4.3)","27701_2019 (6.6.4.3)"],"Standard12":["(CIP-007-6_Requirement_R5_Part_5.5)"],"Standard11":["800-171 Rev2 (3.5.7)","800_53 Rev5 (IA-5.1)"],"Standard8":["v3.2.1 (2.1,8.2.3,8.2.6)"],"s202":["(CC6.1)"]},"ecc-aws-080":{"Standard1":["v7 (16)","v8 (8.11)"],"CIS AWS Foundations Benchmark":["v1.4.0 (4.5)","v1.5.0 (4.5)"],"AWS Foundational Security Best Practices controls":["(CloudWatch.5)"],"BSA":["v3 (DS-7,LT-1,LT-2,LT-5)"],"CJIS":["(5.4.1,5.4.3)"],"CMMC":["v2.0 (AU.L2-3.3.5)"],"Standard4":["v4 (LOG-05)"],"DA":["(Article_9.2,Article_15.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-06,AU-06.01,AU-06.05,AU-07)"],"Standard5":["(164.308.a.1.ii.D,164.312.b)"],"Standard14":["27001_2013 (A.12.4.1,A.16.1.4)","27002_2013 (12.4.1,16.1.4)","27002_2022 (5.25)","27017_2015 (12.4.1,16.1.4)","27018_2019 (12.4.1,6.1.4)","27701_2019 (6.13.1.4,6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.4)"],"Standard11":["800-171 Rev2 (3.3.3,3.3.5)","800_53 Rev5 (AU-6,AU-6.1,AU-6.5,AU-7)"],"Standard9":["v1.1 (DE.AE-2,PR.PT-1,RS.AN-1)"],"Standard8":["v3.2.1 (10.6,10.6.1,10.6.2)"],"s202":["(CC4.1,CC7.3)"]},"ecc-aws-468":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-434":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Security_update_management)"],"DA":["(Article_8.4.f)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-331":{"Standard1":["v7 (3,5.1)","v8 (4.1,7)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CAEUC":["v1.0.0 (2.13)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k,10.m)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-487":{"Standard1":["v7 (14.6,14.8)","v8 (3.11,3.3)"],"BSA":["v3 (AM-4,DP-4,DP-5,IM-7,PA-7)"],"CJIS":["(5.10.1.2.2,5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.1,MP.L2-3.8.2,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03,IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.4.d,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,IA-05.01,MP-02,SC-28)"],"Standard6":["2016_679 (Article_32.1.a,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.a.2.iv)"],"Standard15":["(01.a,01.c,01.v,06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,5.33,8.24,8.3)","27017_2015 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (10.1.1,18.1.3,8,9.1,9.4.1)","27701_2019 (6.15.1.3,6.5.1.3,6.6.1.1,6.6.4.1,6.7.1.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.13.16,3.8.1,3.8.2)","800_53 Rev5 (AC-3,AC-6,IA-5.1,MP-2,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.AC-4,PR.DS-1)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (3.4,3.4.1,7.1,7.1.1,7.1.2,7.1.3,8.2.1)"],"s202":["(CC6.1,CC6.3)"]},"ecc-aws-484":{"Standard1":["v7 (5.1)","v8 (4.1)"],"AWS Config":["(codedeploy-auto-rollback-monitor-enabled)"],"BSA":["v3 (GS-5,PV-1)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI06)"],"Standard4":["v4 (CCC-01)"],"DA":["(Article_8.2)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,SA-03)"],"Standard15":["(10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1)","800_53 Rev5 (CM-2,CM-6,SA-3)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-389":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-071":{"Standard1":["v7 (16.2)","v8 (6)"],"AWS Config":["(codebuild-project-source-repo-url-check)"],"AWS Foundational Security Best Practices controls":["(CodeBuild.1)"],"Standard3":["Standard11_800_53_Rev4 (AC-17,AC-3)"],"Standard5":["(164.308.a.1.ii.B,164.308.a.3.i,164.308.a.4.ii.A.,164.308.a.4.ii.C,164.312.a.1,164.312.e.1)"],"Standard14":["27001_2013 (A.9.4.1)","27002_2013 (9.4.1)","27017_2015 (9.4.1)","27018_2019 (9.4.1)","27701_2019 (6.6.4.1)"],"Standard11":["800_53 Rev5 (AC-17,AC-3)"],"Standard9":["v1.1 (PR.AC-3,PR.AC-4,PR.DS-5,PR.IP-7)"],"Standard8":["v3.2.1 (1.3,7.1)"],"s202":["(CC6.1)"]},"ecc-aws-410":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-047":{"Standard1":["v7 (4.4)","v8 (5.2)"],"CIS AWS Foundations Benchmark":["v1.2.0 (1.6)"],"AWS Config":["(iam-password-policy)"],"AWS Foundational Security Best Practices controls":["(IAM.7,IAM.12)"],"CJIS":["(5.6.2.1.1)"],"CMMC":["v2.0 (IA.L2-3.5.7)"],"Standard4":["v4 (IAM-02)"],"CE":["v2.2 (Password_based_Authentication)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.7)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01)"],"Standard5":["(164.308.a.4.ii.B,164.308.a.5.ii.D,164.312.d,164.314.b.2.i)"],"Standard15":["(01.f)"],"Standard14":["27001_2013 (A.9.4.3)","27002_2013 (9.4.3)","27002_2022 (5.17)","27017_2015 (9.4.3)","27018_2019 (9.4.3)","27701_2019 (6.6.4.3)"],"Standard12":["(CIP-007-6_Requirement_R5_Part_5.5)"],"Standard11":["800-171 Rev2 (3.5.7)","800_53 Rev5 (IA-5.1)"],"Standard8":["v3.2.1 (2.1,8.2.3,8.2.6)"],"s202":["(CC6.1)"]},"ecc-aws-532":{"Standard1":["v7 (5.1)","v8 (4.1,12.2)"],"AWS Config":["(acm-certificate-expiration-check)"],"AWS Foundational Security Best Practices controls":["(ACM.1)"],"BSA":["v3 (DP-7,GS-5,PV-1)"],"CJIS":["(5.10.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,CM.L2-3.4.1,CM.L2-3.4.2,SC.L2-3.13.8)"],"Standard2":["19 (BAI06,BAI10)"],"Standard4":["v4 (TVM-01,TVM-02)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.1,Article_8.2,Article_8.3)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-09,SA-03,SA-08,SC-12,SC-17,SC-23)"],"Standard6":["2016_679 (Article_32)"],"Standard5":["(164.314.b.2.i)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.1,3.13.2)","800_53 Rev5 (CM-1,CM-9,CM-14,SA-3,SA-8,SC-17,SC-23.5)"],"Standard9":["v1.1 (PR.PT-4,PR.AC-5)"],"Standard8":["v3.2.1 (4.1)"],"s202":["(CC6.1,CC8.1)"]},"ecc-aws-192":{"Standard1":["v7 (6.3)","v8 (8.5)"],"AWS Config":["(beanstalk-enhanced-health-reporting-enabled)"],"AWS Foundational Security Best Practices controls":["(ElasticBeanstalk.1)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (IVS-02)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12)"],"Standard15":["(09.ab)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"s202":["(A1.1,CC7.2)"]},"ecc-aws-220":{"Standard1":["v7 (1.4)","v8 (1.1,4.6)"],"AWS Config":["(secretsmanager-secret-unused)"],"AWS Foundational Security Best Practices controls":["(SecretsManager.3)"],"BSA":["v3 (AM-1,AM-3,DP-6)"],"CMMC":["v2.0 (CM.L2-3.4.1,SC.L2-3.13.15)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(07.a,07.c)"],"Standard14":["27001_2013 (A.14.2.5,A.8.1.1)","27002_2013 (14.2.5,8.1.1)","27002_2022 (5.18,5.9,8.27)","27017_2015 (14.2.5,8.1.1)","27018_2019 (14,8)","27701_2019 (6.11.2.5,6.5.1.1)"],"Standard11":["800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3,PR.IP-1,PR.IP-2)"],"s202":["(CC6.2)"]},"ecc-aws-141":{"Standard1":["v7 (13)","v8 (3.1)"],"CIS AWS Foundations Benchmark":["v1.5.0 (1.19)","v1.4.0 (1.19)"],"BSA":["v3 (GS-3)"],"CJIS":["(5.1.1.1)"],"CMMC":["v2.0 (SC.L2-3.13.10)"],"Standard2":["19 (APO14,DSS06)"],"Standard4":["v4 (DCS-01,DSP-01,DSP-06,DSP-17,GRC-03)"],"Standard10":["(D1.G.IT.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-12,SI-12)"],"Standard6":["2016_679 (Article_5)"],"Standard15":["(00.a)"],"Standard14":["27001_2013 (A.18.1.5,A.6.2.2,A.8.1.3)","27002_2013 (18.1.5,6.2.2,8.1.3)","27002_2022 (5.10,5.9,8.1)","27017_2015 (18.1.5,6.2.2,8.1.3)","27018_2019 (18.1,6.2,8)","27701_2019 (6.15.1.5,6.3.2.2,6.5.1.3)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800_53 Rev5 (CM-12,SI-12)"],"Standard9":["v1.1 (PR.IP-6)"],"Standard7":["(2)"],"s202":["(CC6.1,CC6.7)"]},"ecc-aws-533":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-018":{"Standard1":["v7 (14.1,16)","v8 (6.8)"],"CIS AWS Foundations Benchmark":["v1.4.0 (1.15)","v1.5.0 (1.15)"],"AWS Config":["(iam-user-no-policies-check)"],"AWS Foundational Security Best Practices controls":["(IAM.2)"],"BSA":["v3 (PA-1)"],"CJIS":["(5.5.1,5.5.2,5.5.2.1,5.5.2.2,5.5.2.3,5.5.2.4)"],"CMMC":["v2.0 (AC.L1-3.1.2,SC.L2-3.13.3)"],"Standard4":["v4 (IAM-04,IAM-05,IAM-16)"],"CE":["v2.2 (A7.4,A7.9)"],"Standard10":["(D1.R.St.B.1,D3.PC.Am.B.1,D3.PC.Am.B.2,D3.PC.Am.B.4)"],"Standard3":["Standard11_800_53_Rev5 (AC-02,AC-02.07,AC-03,AU-9.4)"],"Standard6":["2016_679 (Article_25)"],"Standard5":["(164.308.a.3.i,164.308.a.3.ii.A,164.308.a.3.ii.B,164.308.a.4.i,164.308.a.4.ii.A,164.308.a.4.ii.B,164.308.a.4.ii.C,164.312.a.1,164.314.b.2.i)"],"Standard15":["(01.c)"],"Standard14":["27001_2013 (A.9.1.1,A.9.4.1)","27002_2013 (9.1.1,9.4.1)","27002_2022 (5.15,8.3)","27017_2015 (9.1.1,9.4.1)","27018_2019 (9.1,9.4.1)","27701_2019 (6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-004-6_Requirement_R4_Part_4.3,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.4,3.1.5)","800_53 Rev5 (AC-2,AC-2.7,AC-3.7,AU-9.4)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"s202":["(CC5.2,CC6.3)"]},"ecc-aws-470":{"Standard1":["v7 (12,14.6)","v8 (12.2,6)"],"AWS Config":["(api-gw-endpoint-type-check)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-346":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (PV-1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (IVS-02)"],"CE":["v2.2 (Secure_configuration)"],"Standard3":["Standard11_800_53_Rev5 (SA-03,SA-08,SA-10,SI-04)"],"Standard6":["2016_679 (Article_32.1)"],"Standard15":["(08.k,09.ab,10.k)"],"Standard14":["27001_2013 (A.12.1.3,A.14.2.5)","27002_2013 (12.1.3,14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (12.1.3,14.2.5)","27018_2019 (12.1.3,14)","27701_2019 (6.9.1.3,6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (SA-10,SA-3,SA-8,SI-4)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(A1.1,CC8.1,CC7.2)"]},"ecc-aws-239":{"Standard1":["v7 (6.2,6.3)","v8 (4.1,8.2,8.5)"],"BSA":["v3 (DS-7,GS-10,GS-5,LT-3,LT-4,PV-1,PV-3)"],"Standard13":["v1.0.0 (3.1.20)"],"CJIS":["(5.4.1,5.7.1)"],"CMMC":["v2.0 (AU.L2-3.3.1,CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10,DSS03)"],"Standard4":["v4 (CCC-01,IVS-04,LOG-08)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.e,Article_10.7)"],"Standard10":["(D1.G.SP.B.2,D2.MA.Ma.B.1,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-03,AU-12,CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_30,Article_32)"],"Standard5":["(164.312.b)"],"Standard15":["(08.k,09.aa,10.k)"],"Standard14":["27001_2013 (A.12.4.1,A.14.2.5)","27002_2013 (12.4.1,14.2.5)","27002_2022 (5.28,8.15,8.27,8.9)","27017_2015 (12.4.1,14.2.5)","27018_2019 (12.4.1,14)","27701_2019 (6.11.2.5,6.9.4.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-007-6_Requirement_R4_Part_4.1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.3.1,3.4.1,3.4.2)","800_53 Rev5 (AU-12,AU-2,AU-3,AU-3.1,CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (DE.AE-3,PR.IP-1,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.5,10.3,2.2)"],"s202":["(CC7.2,CC8.1)"]},"ecc-aws-060":{"Standard1":["v7 (14.8)","v8 (3.11,3.3)"],"CIS AWS Foundations Benchmark":["v1.4.0 (3.7)","v1.5.0 (3.7)"],"AWS Config":["(cloud-trail-encryption-enabled)"],"AWS Foundational Security Best Practices controls":["(CloudTrail.2)"],"BSA":["v3 (AM-4,DP-4,DP-5,IM-7,PA-7)"],"CJIS":["(5.10.1.2.2,5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.1,MP.L2-3.8.2,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03,IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.4.d,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02,SC-28)"],"Standard6":["2016_679 (Article_32.1.a,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.308a.1.ii.B,164.312.a.2.iv,164.312.e.2.ii,164.314.b.1,164.314.b.2.i)"],"Standard15":["(01.a,01.c,01.v,06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3,A.9.1.1,A.9.4.1)","27002_2013 (10.1.1,18.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,5.33,8.24,8.3)","27017_2015 (10.1.1,18.1.3,9.1.1,9.4.1)","27018_2019 (10.1.1,18.1.3,9.1,9.4.1)","27701_2019 (6.15.1.3,6.6.1.1,6.6.4.1,6.7.1.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.13.16,3.8.1,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.AC-4,PR.DS-1)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (3.4,3.4.1,7.1,7.1.1,7.1.2,7.1.3,8.2.1)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-aws-376":{"Standard1":["v7 (6.2,6.3)","v8 (8.2,8.5)"],"AWS Config":["(api-gwv2-access-logs-enabled)"],"AWS Foundational Security Best Practices controls":["(APIGateway.1,APIGateway.9)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_8.4.e,Article_10.7)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.5,10.3)"],"s202":["(CC7.2)"]},"ecc-aws-232":{"Standard1":["v7 (6.4)","v8 (8.3)"],"BSA":["v3 (LT-6)"],"Standard13":["v1.0.0 (3.1.9)"],"CJIS":["(5.4.6)"],"Standard3":["Standard11_800_53_Rev5 (AU-04)"],"Standard15":["(09.h)"],"Standard14":["27001_2013 (A.12.1.3)","27002_2013 (12.1.3)","27002_2022 (8.6)","27017_2015 (12.1.3)","27018_2019 (12.1.3)","27701_2019 (6.9.1.3)"],"Standard11":["800_53 Rev5 (AU-4)"],"Standard9":["v1.1 (PR.DS-4)"],"Standard8":["v3.2.1 (10.7)"],"s202":["(A1.1)"]},"ecc-aws-009":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.1,Article_8.2,Article_8.3)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-001":{"Standard1":["v7 (4.5)","v8 (6.5)"],"CIS AWS Foundations Benchmark":["v1.4.0 (1.10)","v1.5.0 (1.10)"],"AWS Config":["(mfa-enabled-for-iam-console-access)"],"AWS Foundational Security Best Practices controls":["(IAM.5,IAM.19)"],"BSA":["v3 (GS-6,IM-2)"],"CJIS":["(5.6.2.2,5.6.2.2.1)"],"CMMC":["v2.0 (IA.L2-3.5.3)"],"Standard4":["v4 (IAM-14)"],"CE":["v2.2 (A7.16,uac)"],"DA":["(Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.3)"],"Standard3":["Standard11_800_53_Rev5 (IA-02.01)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.312.a.1,164.312.d)"],"Standard15":["(01.q)"],"Standard14":["27001_2013 (A.9.3.1,A.9.4.2)","27002_2013 (9.3.1,9.4.2)","27002_2022 (5.17,8.5)","27017_2015 (9.3.1,9.4.2)","27018_2019 (9.3.1,9.4.2)","27701_2019 (6.6.3.1,6.6.4.2)"],"Standard11":["800-171 Rev2 (3.5.3)","800_53 Rev5 (IA-2.1)"],"Standard9":["v1.1 (PR.AC-7)"],"Standard8":["v3.2.1 (8.3,8.3.1,8.3.2)"],"s202":["(CC6.1)"]},"ecc-aws-379":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-283":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Security_update_management)"],"DA":["(Article_8.4.f)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-216":{"Standard1":["v7 (5.1)","v8 (4.1)"],"AWS Config":["(redshift-cluster-maintenancesettings-check)"],"AWS Foundational Security Best Practices controls":["(Redshift.6)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CJIS":["(5.10.4.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Security_update_management)"],"DA":["(Article_8.4.f)"],"Standard10":["(D1.G.SP.B.2,D3.CC.Pa.B.1,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard5":["(164.308.a.5.ii.A,164.308.a.7.ii.A)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-007-6_Requirement_R2_Part_2.1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-553":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800_53 Rev5 (CM-8,CM-8.1)"],"Standard9":["v1.1 (PR.DS-3)"]},"ecc-aws-541":{"Standard1":["v7 (6.2,6.3)","v8 (8.2,8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_8.4.e,Article_10.7)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.5,10.3)"],"s202":["(CC7.2)"]},"ecc-aws-480":{"Standard1":["v7 (14.8)","v8 (3.11)"],"AWS Config":["(codebuild-project-artifact-encryption)"],"BSA":["v3 (DP-4,DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28,SC-28.01)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)"],"s202":["(CC6.1)"]},"ecc-aws-237":{"Standard1":["v7 (6.2,6.3)","v8 (8.2,8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"Standard13":["v1.0.0 (3.1.18)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_8.4.e,Article_10.7)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.5,10.3)"],"s202":["(CC7.2)"]},"ecc-aws-353":{"Standard1":["v7 (6.2,6.3)","v8 (3.14,8.2,8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_8.4.e,Article_10.7,Article_8.3.d)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-06.09,AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AC-6.9,AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.1,10.2.5,10.3)"],"s202":["(CC6.1,CC7.2)"]},"ecc-aws-211":{"Standard1":["v7 (14.4,14.6)","v8 (3.10,3.3)"],"AWS Config":["(rds-cluster-iam-authentication-enabled)"],"AWS Foundational Security Best Practices controls":["(RDS.12)"],"BSA":["v3 (AM-4,DP-3,IM-7,PA-7)"],"CJIS":["(5.10.1.2.1,5.5.2,5.6.2.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.13,AC.L2-3.1.17,MP.L2-3.8.2,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IAM-05,IVS-03)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.a,Article_8.3.b,Article_8.4.d,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Am.B.13)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,AC-17.02,MP-02,SC-08)"],"Standard6":["2016_679 (Article_44,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.e.1)"],"Standard15":["(01.a,01.c,01.j,01.v,06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1,A.18.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (10.1.1,13.2.1,18.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.14,5.15,5.33,8.24,8.3)","27017_2015 (10.1.1,13.2.1,18.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (10.1.1,13.2.1,18.1.3,8,9.1,9.4.1)","27701_2019 (6.10.2.1,6.15.1.3,6.5.1.3,6.6.1.1,6.6.4.1,6.7.1.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.13,3.1.2,3.13.8,3.8.2)","800_53 Rev5 (AC-17.2,AC-3,AC-6,MP-2,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.AC-4,PR.AC-7,PR.DS-2)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (2.1.1,4.1,4.1.1,7.1,7.1.1,7.1.2,7.1.3,8.2.1)"],"s202":["(CC6.1,CC6.3,CC6.7)"]},"ecc-aws-422":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-375":{"Standard1":["v7 (14.6,14.8)","v8 (3.11,3.3)"],"BSA":["v3 (AM-4,DP-4,DP-5,IM-7,PA-7)"],"CAEUC":["v1.0.0 (2.3)"],"CJIS":["(5.10.1.2.2,5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.1,MP.L2-3.8.2,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03,IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.4.d,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,IA-05.01,MP-02,SC-28)"],"Standard6":["2016_679 (Article_32.1.a,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.a.2.iv)"],"Standard15":["(01.a,01.c,01.v,06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,5.33,8.24,8.3)","27017_2015 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (10.1.1,18.1.3,8,9.1,9.4.1)","27701_2019 (6.15.1.3,6.5.1.3,6.6.1.1,6.6.4.1,6.7.1.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.13.16,3.8.1,3.8.2)","800_53 Rev5 (AC-3,AC-6,IA-5.1,MP-2,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.AC-4,PR.DS-1)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (3.4,3.4.1,7.1,7.1.1,7.1.2,7.1.3,8.2.1)"],"s202":["(CC6.1,CC6.3)"]},"ecc-aws-542":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (IVS-02)"],"DA":["(Article_8.2,Article_8.4.a,Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard5":["(164.308.a.1.ii.B,164.308.a.7.i,164.308.a.7.ii.C,164.312.e.1,164.314.b.2.i)"],"Standard15":["(08.k,10.k,12.c)"],"Standard14":["27001_2013 (A.14.2.5,A.17.2.1)","27002_2013 (14.2.5,17.2.1)","27002_2022 (8.14,8.27,8.9)","27017_2015 (14.2.5,17.2.1)","27018_2019 (14,17)","27701_2019 (6.11.2.5,6.14.2.1)"],"Standard12":["(CIP-003-8_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.4.1)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-8)"],"Standard9":["v1.1 (PR.IP-1,PR.PT-5)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-163":{"Standard1":["v7 (9)","v8 (12.2)"],"AWS Foundational Security Best Practices controls":["(RDS.23)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-007-6_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.4.2,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-349":{"Standard1":["v7 (6.2,6.3)","v8 (8.2,8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_8.4.e,Article_10.7)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.5,10.3)"],"s202":["(CC7.2)"]},"ecc-aws-526":{"Standard1":["v7 (9.5,14.2,12.9,18.10)","v8 (4.4,13.4,13.10)"],"AWS Config":["(waf-global-rulegroup-not-empty)"],"AWS Foundational Security Best Practices controls":["(WAF.7)"],"BSA":["v3 (GS-9,NS-1,NS-2,NS-3,NS-7,NS-8)"],"CJIS":["(5.10.1,5.7.1.2)"],"CMMC":["v2.0 (AC.L1-3.1.20,CM.L2-3.4.7,SC.L1-3.13.1,SC.L2-3.13.6)"],"Standard2":["19 (APO13)"],"Standard4":["v4 (UEM-10)"],"CE":["v2.2 (A4.11,A4.5,A4.7,Firewalls)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.2,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,SC-07)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard15":["(09.m)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.20)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard11":["800-171 Rev2 (3.13.1)","800_53 Rev5 (CA-9,SC-7)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (1.1.4,1.3.1)"],"s202":["(CC6.6)"]},"ecc-aws-248":{"Standard1":["v7 (9.5)","v8 (4.4)"],"AWS Config":["(api-gw-associated-with-waf,fms-webacl-resource-policy-check)"],"AWS Foundational Security Best Practices controls":["(APIGateway.4)"],"BSA":["v3 (GS-9,NS-1,NS-2,NS-3,NS-6)"],"CJIS":["(5.10.1,5.7.1.2)"],"CMMC":["v2.0 (AC.L1-3.1.20,CM.L2-3.4.7,SC.L1-3.13.1,SC.L2-3.13.6)"],"Standard2":["19 (APO13)"],"Standard4":["v4 (UEM-10)"],"CE":["v2.2 (A4.11,A4.5,A4.7,Firewalls)"],"DA":["(Article_8.3.b,Article_8.4.b,Article_8.4.c,Article_9.1)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.2,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,SC-07,SC-07.05)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.312.e.1)"],"Standard15":["(09.m)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.20)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard11":["800-171 Rev2 (3.13.1)","800_53 Rev5 (CA-9,SC-7,SC-7.5)"],"Standard9":["v1.1 (DE.CM-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (1.1.4,1.3.1)"],"s202":["(CC6.6)"]},"ecc-aws-345":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,13.4)"],"BSA":["v3 (GS-4,NS-1,NS-2)"],"CJIS":["(5.10.1,5.13.1.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.13,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,IA.L1-3.5.2,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.15,SC.L2-3.13.2,SC.L2-3.13.6)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.10,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CA-09,CM-07,PL-08,SA-08,SC-07,SC-08,SC-23)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.312.e.1)"],"Standard15":["(01.i,01.j,01.l,01.m,01.n,01.o,09.m,09.n,09.s,09.y,10.d)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.2,A.13.1.3)","27002_2013 (13.1.1,13.1.2,13.1.3)","27002_2022 (8.16,8.20,8.21,8.22,8.27)","27017_2015 (13.1.1,13.1.2,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.2,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.17,3.1.5,3.13.1,3.13.15,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (AC-18,CA-9,CM-7,PL-8,PM-7,SA-8,SC-23,SC-7,SC-8)"],"Standard9":["v1.1 (PR.AC-5,PR.DS-2,PR.DS-5,PR.IP-1,PR.PT-3,PR.PT-4)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5,4.1.1)"],"s202":["(CC5.2,CC6.1,CC6.6)"]},"ecc-aws-330":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CIS MySQL Enterprise Edition 8.0 Benchmark":["v1.1.0 (4.9)","v1.2.0 (4.9)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-297":{"Standard1":["v7 (5.1,3.5)","v8 (4.1,7.4)"],"AWS Config":["(elastic-beanstalk-managed-updates-enabled)"],"AWS Foundational Security Best Practices controls":["(ElasticBeanstalk.2)"],"CIS AWS Compute Services Benchmark":["v1.0.0 (6.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CJIS":["(5.10.4.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Security_update_management)"],"DA":["(Article_8.2,Article_8.4.a)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard5":["(164.308.a.5.ii.A)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-213":{"Standard1":["v7 (5.1)","v8 (11,4.1)"],"AWS Config":["(rds-cluster-multi-az-enabled)"],"AWS Foundational Security Best Practices controls":["(RDS.15)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (IVS-02)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.2,Article_8.4.a,Article_11.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,CP-06,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32.1.b,Article_32.1.c)"],"Standard15":["(08.k,10.k,12.c)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,CP-6,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1,PR.PT-5)"],"s202":["(CC8.1)"]},"ecc-aws-327":{"Standard1":["v7 (14.8)","v8 (3.11)"],"CIS AWS Compute Services Benchmark":["v1.0.0 (2.2.3)"],"BSA":["v3 (DP-4,DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)"],"s202":["(CC6.1)"]},"ecc-aws-072":{"Standard1":["v7 (5.1)","v8 (4.2)"],"AWS Config":["(autoscaling-group-elb-healthcheck-required)"],"AWS Foundational Security Best Practices controls":["(AutoScaling.1)"],"BSA":["v3 (GS-10,GS-5,PV-1)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard5":["(164.308.a.1.ii.B,164.312.b)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)"],"s202":["(CC5.2,CC6.6)"]},"ecc-aws-202":{"Standard1":["v7 (6.2,6.3)","v8 (3.14,8.2,8.5)"],"AWS Config":["(rds-logging-enabled)"],"AWS Foundational Security Best Practices controls":["(RDS.9)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.3.d,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-06.09,AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.308.a.3.ii.A,164.308.a.5.ii.C,164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AC-6.9,AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.1,10.2.5,10.3)"],"s202":["(CC6.1,CC7.2)"]},"ecc-aws-010":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.1,Article_8.2,Article_8.3)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-299":{"Standard1":["v7 (14.4,14.8)","v8 (3.10,3.11)"],"BSA":["v3 (DP-3,DP-4,DP-5)"],"CJIS":["(5.10.1.2.1,5.10.1.2.2)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.16,SC.L2-3.13.8)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IVS-03)"],"DA":["(Article_8.2,Article_8.3.a,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.13)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,IA-05.01,SC-08,SC-28)"],"Standard6":["2016_679 (Article_32.1.a,Article_44)"],"Standard5":["(164.312.a.2.iv,164.312.e.1)"],"Standard15":["(06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1,A.18.1.3)","27002_2013 (10.1.1,13.2.1,18.1.3)","27002_2022 (5.14,5.33,8.24)","27017_2015 (10.1.1,13.2.1,18.1.3)","27018_2019 (10.1.1,13.2.1,18.1.3)","27701_2019 (6.10.2.1,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.16,3.13.8,3.5.10,3.8.1)","800_53 Rev5 (AC-17.2,IA-5.1,SC-28,SC-28.1,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-1,PR.DS-2)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1.1,3.4,3.4.1,4.1,4.1.1,8.2.1)"],"s202":["(CC6.1,CC6.7)"]},"ecc-aws-525":{"Standard1":["v7 (9.5,14.2,12.9,18.10)","v8 (4.4,13.4,13.10)"],"AWS Config":["(waf-global-rule-not-empty)"],"AWS Foundational Security Best Practices controls":["(WAF.6)"],"BSA":["v3 (GS-9,NS-1,NS-2,NS-3,NS-7,NS-8)"],"CJIS":["(5.10.1,5.7.1.2)"],"CMMC":["v2.0 (AC.L1-3.1.20,CM.L2-3.4.7,SC.L1-3.13.1,SC.L2-3.13.6)"],"Standard2":["19 (APO13)"],"Standard4":["v4 (UEM-10)"],"CE":["v2.2 (A4.11,A4.5,A4.7,Firewalls)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.2,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,SC-07)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard15":["(09.m)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.20)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard11":["800-171 Rev2 (3.13.1)","800_53 Rev5 (CA-9,SC-7)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (1.1.4,1.3.1)"],"s202":["(CC6.6)"]},"ecc-aws-178":{"Standard1":["v7 (5.5)","v8 (13.6)"],"AWS Config":["(api-gw-xray-enabled)"],"AWS Foundational Security Best Practices controls":["(APIGateway.3)"],"BSA":["v3 (LT-4)"],"CJIS":["(5.10.1.1)"],"CMMC":["v2.0 (SI.L2-3.14.6)"],"Standard4":["v4 (IVS-03)"],"Standard3":["Standard11_800_53_Rev5 (SI-04,SI-04.04)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa,09.m)"],"Standard14":["27001_2013 (A.12.4.1,A.13.1.1)","27002_2013 (12.4.1,13.1.1)","27002_2022 (8.15,8.16,8.20)","27017_2015 (12.4.1,13.1.1)","27018_2019 (12.4.1,13.1)","27701_2019 (6.10.1.1,6.9.4.1)"],"Standard11":["800-171 Rev2 (3.13.1,3.14.6)","800_53 Rev5 (SI-4,SI-4.4)"],"Standard9":["v1.1 (DE.CM-1)"],"s202":["(CC7.1)"]},"ecc-aws-315":{"Standard1":["v7 (6.3)","v8 (8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CIS Oracle Database 19 Benchmark":["v1.0.0 (2.2.2)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.5,10.3)"],"s202":["(CC7.2)"]},"ecc-aws-369":{"Standard1":["v7 (6.2,6.5)","v8 (8.2,8.9)"],"BSA":["v3 (DS-7,LT-3,LT-4,LT-5)"],"CAEUC":["v1.0.0 (2.9)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_8.4.e,Article_10.7)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-06.03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2,AU-6.3)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.2,10.3,10.5.3,10.5.4)"],"s202":["(CC7.2,PL1.4)"]},"ecc-aws-472":{"Standard1":["v7 (12)","v8 (4.1)"],"AWS Config":["(autoscaling-launchconfig-requires-imdsv2)"],"AWS Foundational Security Best Practices controls":["(AutoScaling.3)"],"BSA":["v3 (GS-10,GS-5,NS-8,PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-07,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32.1.b)"],"Standard15":["(08.k,10.k,10.m)"],"Standard14":["27001_2013 (A.13.1.2,A.9.1.1,A.9.4.1)","27002_2013 (13.1.2,9.1.1,9.4.1)","27002_2022 (5.10,8.21,8.3)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-7,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC6.6)"]},"ecc-aws-210":{"Standard1":["v7 (14.4,14.6)","v8 (3.10,3.3)"],"AWS Config":["(rds-instance-iam-authentication-enabled)"],"AWS Foundational Security Best Practices controls":["(RDS.10)"],"BSA":["v3 (AM-4,DP-3,IM-7,PA-7)"],"CJIS":["(5.10.1.2.1,5.5.2,5.6.2.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.13,AC.L2-3.1.17,MP.L2-3.8.2,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IAM-05,IVS-03)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.a,Article_8.3.b,Article_8.4.d,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Am.B.13)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,AC-17.02,MP-02,SC-08)"],"Standard6":["2016_679 (Article_44,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.e.1)"],"Standard15":["(01.a,01.c,01.j,01.v,06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1,A.18.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (10.1.1,13.2.1,18.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.14,5.15,5.33,8.24,8.3)","27017_2015 (10.1.1,13.2.1,18.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (10.1.1,13.2.1,18.1.3,8,9.1,9.4.1)","27701_2019 (6.10.2.1,6.15.1.3,6.5.1.3,6.6.1.1,6.6.4.1,6.7.1.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.13,3.1.2,3.13.8,3.8.2)","800_53 Rev5 (AC-17.2,AC-3,AC-6,MP-2,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.AC-4,PR.AC-7,PR.DS-2)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (2.1.1,4.1,4.1.1,7.1,7.1.1,7.1.2,7.1.3,8.2.1)"],"s202":["(CC6.1,CC6.3,CC6.7)"]},"ecc-aws-351":{"Standard1":["v7 (14.6,14.8)","v8 (3.11,3.3)"],"BSA":["v3 (AM-4,DP-4,DP-5,IM-7,PA-7)"],"CJIS":["(5.10.1.2.2,5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.1,MP.L2-3.8.2,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03,IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.4.d,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,IA-05.01,MP-02,SC-28)"],"Standard6":["2016_679 (Article_32.1.a,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.a.2.iv)"],"Standard15":["(01.a,01.c,01.v,06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3,A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,5.33,8.24,8.3)","27017_2015 (10.1.1,18.1.3,8.1.3,9.1.1,9.4.1)","27018_2019 (10.1.1,18.1.3,8,9.1,9.4.1)","27701_2019 (6.15.1.3,6.5.1.3,6.6.1.1,6.6.4.1,6.7.1.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.13.16,3.8.1,3.8.2)","800_53 Rev5 (AC-3,AC-6,IA-5.1,MP-2,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.AC-4,PR.DS-1)"],"Standard7":["(6,6)"],"Standard8":["v3.2.1 (3.4,3.4.1,7.1,7.1.1,7.1.2,7.1.3,8.2.1)"],"s202":["(CC6.1,CC6.3)"]},"ecc-aws-130":{"Standard1":["v7 (11.1,9.2)","v8 (12.2,12.6)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1,5.13.1.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.13,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,IA.L1-3.5.2,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.15,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.10,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-07,PL-08,SA-08,SC-07,SC-08,SC-23)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard5":["(164.312.e.1)"],"Standard15":["(01.i,01.j,01.l,01.m,01.n,01.o,09.m,09.s,09.y,10.d)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.2,A.13.1.3)","27002_2013 (13.1.1,13.1.2,13.1.3)","27002_2022 (8.20,8.21,8.22,8.27)","27017_2015 (13.1.1,13.1.2,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.2,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.17,3.1.5,3.13.1,3.13.15,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (AC-18,CM-7,PL-8,PM-7,SA-8,SC-23,SC-7,SC-8)"],"Standard9":["v1.1 (PR.AC-5,PR.DS-2,PR.DS-5,PR.IP-1,PR.PT-3,PR.PT-4)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5,4.1.1)"],"s202":["(CC5.2,CC6.1,CC6.6)"]},"ecc-aws-200":{"Standard1":["v7 (5.1)","v8 (4.1)"],"AWS Config":["(rds-cluster-deletion-protection-enabled)"],"AWS Foundational Security Best Practices controls":["(RDS.7)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard5":["(164.308.a.7.i,164.308.a.7.ii.C,164.314.b.2.i)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.13.2,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.DS-3,PR.IP-1,PR.IP-6)"],"s202":["(CC8.1)"]},"ecc-aws-252":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (DP-4,DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)"],"s202":["(CC6.1)"]},"ecc-aws-111":{"Standard1":["v7 (18.10)","v8 (4.4)"],"AWS Foundational Security Best Practices controls":["(ELB.16)"],"AWS Config":["(alb-waf-enabled,fms-shield-resource-policy-check,fms-webacl-resource-policy-check)"],"BSA":["v3 (GS-9,NS-1,NS-2,NS-3,NS-7,NS-8)"],"CJIS":["(5.10.1,5.7.1.2)"],"CMMC":["v2.0 (AC.L1-3.1.20,CM.L2-3.4.7,SC.L1-3.13.1,SC.L2-3.13.6)"],"Standard2":["19 (APO13)"],"Standard4":["v4 (UEM-10)"],"CE":["v2.2 (A4.11,A4.5,A4.7,Firewalls)"],"DA":["(Article_8.3.b,Article_8.4.b,Article_8.4.c,Article_9.1)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.2,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,SC-07,SC-07.05)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard15":["(09.m)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.20)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard11":["800-171 Rev2 (3.13.1)","800_53 Rev5 (CA-9,SC-7,SC-7.5)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (1.1.4,1.3.1)"],"s202":["(CC6.6)"]},"ecc-aws-458":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (PV-1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (IVS-02)"],"CE":["v2.2 (Secure_configuration)"],"Standard3":["Standard11_800_53_Rev5 (SA-03,SA-08,SA-10,SI-04)"],"Standard6":["2016_679 (Article_32.1)"],"Standard15":["(08.k,09.ab,10.k)"],"Standard14":["27001_2013 (A.12.1.3,A.14.2.5)","27002_2013 (12.1.3,14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (12.1.3,14.2.5)","27018_2019 (12.1.3,14)","27701_2019 (6.9.1.3,6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (SA-10,SA-3,SA-8,SI-4)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(A1.1,CC8.1,CC7.2)"]},"ecc-aws-321":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CIS Oracle Database 19 Benchmark":["v1.0.0 (2.2.13)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-123":{"Standard1":["v7 (14.8)","v8 (3.11)"],"CIS AWS Foundations Benchmark":["v1.5.0 (2.4.1)"],"AWS Foundational Security Best Practices controls":["(EFS.1)"],"AWS Config":["(efs-encrypted-check)"],"BSA":["v3 (DP-4,DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)"],"s202":["(CC6.1)"]},"ecc-aws-517":{"Standard1":["v7 (14.6)","v8 (3.3)"],"AWS Config":["(s3-bucket-acl-prohibited)"],"AWS Foundational Security Best Practices controls":["(S3.12)"],"BSA":["v3 (AM-4,IM-7,PA-7)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.3.b,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.1.ii.B,164.308.a.3.i,164.308.a.4.i,164.308.a.4.ii.A,164.308.a.4.ii.B,164.308.a.4.ii.C,164.312.a.1,164.312.e.1,164.314.b.1,164.314.b.2)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.3)","27017_2015 (8.1.3,9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-aws-126":{"Standard1":["v7 (14.8)","v8 (3.11)"],"AWS Foundational Security Best Practices controls":["(Redshift.10)"],"AWS Config":["(redshift-cluster-configuration-check)"],"BSA":["v3 (DP-4,DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)"],"s202":["(CC6.1)"]},"ecc-aws-466":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (IVS-02)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.2,Article_8.4.a,Article_11.5)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,CP-06,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k,12.c)"],"Standard14":["27001_2013 (A.14.2.5,A.17.2.1)","27002_2013 (14.2.5,17.2.1)","27002_2022 (8.14,8.27,8.9)","27017_2015 (14.2.5,17.2.1)","27018_2019 (14,17)","27701_2019 (6.11.2.5,6.14.2.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,CP-6,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR-PT-5,PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-396":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-381":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-241":{"Standard1":["v7 (6.3)","v8 (4.1,8.5)"],"BSA":["v3 (DS-7,GS-10,GS-5,LT-3,LT-4,PV-1,PV-3)"],"Standard13":["v1.0.0 (3.1.23)"],"CJIS":["(5.4.1,5.7.1)"],"CMMC":["v2.0 (AU.L2-3.3.1,CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04,LOG-08)"],"CE":["v2.2 (Secure_configuration)"],"Standard10":["(D1.G.SP.B.2,D2.MA.Ma.B.1,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12,CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_30,Article_32)"],"Standard15":["(08.k,09.aa,10.k)"],"Standard14":["27001_2013 (A.12.4.1,A.14.2.5)","27002_2013 (12.4.1,14.2.5)","27002_2022 (5.28,8.15,8.27,8.9)","27017_2015 (12.4.1,14.2.5)","27018_2019 (12.4.1,14)","27701_2019 (6.11.2.5,6.9.4.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (AU-12,AU-3,AU-3.1,CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (DE.AE-3,PR.IP-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.5,10.3,2.2)"],"s202":["(CC7.2,CC8.1)"]},"ecc-aws-357":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (PV-1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (BAI06,BAI10)"],"Standard4":["v4 (TVM-01)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32.1)"],"Standard15":["(10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-2,CM-6,CM-9,SA-3,SA-8,SA-10)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-088":{"Standard1":["v7 (10)","v8 (11.1)"],"AWS Config":["(s3-bucket-replication-enabled)"],"BSA":["v3 (GS-8)"],"Standard2":["19 (APO14)"],"Standard4":["v4 (BCR-08,GRC-03)"],"CE":["v2.2 (Back_up_your_data)"],"DA":["(Article_11.5,Article_8.2,Article_8.4.a)"],"Standard10":["(D1.RM.RMP.B.1,D5.IR.Pl.B.5,D5.IR.Pl.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CP-02,CP-10)"],"Standard6":["2016_679 (Article_32.1.c)"],"Standard5":["(164.308.a.7.ii.A)"],"Standard15":["(09.l)"],"Standard14":["27001_2013 (A.12.3.1)","27002_2013 (12.3.1)","27002_2022 (8.13)","27017_2015 (12.3.1)","27018_2019 (12.3.1)","27701_2019 (6.9.3.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-009-6_Requirement_R1_Part_1.3)"],"Standard11":["800_53 Rev5 (CP-10,CP-2)"],"Standard9":["v1.1 (ID.SC-5,PR.IP-9)"],"Standard7":["(6s)"],"s202":["(A1.2)"]},"ecc-aws-242":{"Standard1":["v7 (6.2,6.3)","v8 (4.1,8.2,8.5)"],"BSA":["v3 (DS-7,GS-10,GS-5,LT-3,LT-4,PV-1,PV-3)"],"Standard13":["v1.0.0 (3.1.2)"],"CJIS":["(5.4.1,5.7.1)"],"CMMC":["v2.0 (AU.L2-3.3.1,CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10,DSS03)"],"Standard4":["v4 (CCC-01,IVS-04,LOG-08)"],"CE":["v2.2 (Secure_configuration)"],"Standard10":["(D1.G.SP.B.2,D2.MA.Ma.B.1,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-03,AU-12,CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_30,Article_32)"],"Standard5":["(164.312.b)"],"Standard15":["(08.k,09.aa,10.k)"],"Standard14":["27001_2013 (A.12.4.1,A.14.2.5)","27002_2013 (12.4.1,14.2.5)","27002_2022 (5.28,8.15,8.27,8.9)","27017_2015 (12.4.1,14.2.5)","27018_2019 (12.4.1,14)","27701_2019 (6.11.2.5,6.9.4.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-007-6_Requirement_R4_Part_4.1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.3.1,3.4.1,3.4.2)","800_53 Rev5 (AU-12,AU-2,AU-3,AU-3.1,CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (DE.AE-3,PR.IP-1,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.5,10.3,2.2)"],"s202":["(CC7.2,CC8.1)"]},"ecc-aws-212":{"Standard1":["v7 (10.1)","v8 (11.2)"],"AWS Config":["(aurora-mysql-backtracking-enabled)"],"AWS Foundational Security Best Practices controls":["(RDS.14)"],"BSA":["v3 (BR-1)"],"Standard4":["v4 (BCR-08)"],"CE":["v2.2 (Back_up_your_data)"],"DA":["(Article_11.2,Article_11.4)"],"Standard10":["(D5.IR.Pl.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CP-09,CP-10)"],"Standard6":["2016_679 (Article_32.1.c)"],"Standard5":["(164.308.a.7.ii.A)"],"Standard15":["(09.l)"],"Standard14":["27001_2013 (A.12.3.1)","27002_2013 (12.3.1)","27002_2022 (8.13)","27017_2015 (12.3.1)","27018_2019 (12.3.1)","27701_2019 (6.9.3.1)"],"Standard12":["(CIP-009-6_Requirement_R1_Part_1.3)"],"Standard11":["800_53 Rev5 (CP-10,CP-9)"],"Standard9":["v1.1 (PR.IP-4)"],"Standard7":["(6s)"],"Standard8":["v3.2.1 (12.10.1)"],"s202":["(A1.2)"]},"ecc-aws-077":{"Standard1":["v7 (16)","v8 (8.11)"],"CIS AWS Foundations Benchmark":["v1.4.0 (4.2)","v1.5.0 (4.2)"],"AWS Foundational Security Best Practices controls":["(CloudWatch.3)"],"BSA":["v3 (DS-7,LT-1,LT-2,LT-5)"],"CJIS":["(5.4.1,5.4.3)"],"CMMC":["v2.0 (AU.L2-3.3.5)"],"Standard4":["v4 (LOG-05)"],"DA":["(Article_9.2,Article_15.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-06,AU-06.01,AU-06.05,AU-07)"],"Standard5":["(164.308.a.1.ii.D,164.312.b)"],"Standard14":["27001_2013 (A.12.4.1,A.16.1.4)","27002_2013 (12.4.1,16.1.4)","27002_2022 (5.25)","27017_2015 (12.4.1,16.1.4)","27018_2019 (12.4.1,6.1.4)","27701_2019 (6.13.1.4,6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.4)"],"Standard11":["800-171 Rev2 (3.3.3,3.3.5)","800_53 Rev5 (AU-6,AU-6.1,AU-6.5,AU-7)"],"Standard9":["v1.1 (DE.AE-2,PR.PT-1,RS.AN-1)"],"Standard8":["v3.2.1 (10.6,10.6.1,10.6.2)"],"s202":["(CC4.1,CC7.3)"]},"ecc-aws-339":{"Standard1":["v7 (5.1)","v8 (4.1)"],"AWS Config":["(mq-automatic-minor-version-upgrade-enabled)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CJIS":["(5.10.4.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Security_update_management)"],"DA":["(Article_8.4.f)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-385":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-515":{"Standard1":["v7 (14,14.6)","v8 (3.3)"],"CIS AWS Foundations Benchmark":["v1.5.0 (4.16)"],"AWS Config":["(securityhub-enabled)"],"BSA":["v3 (AM-4,IM-7,PA-7)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.3.b,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.3)","27017_2015 (8.1.3,9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-aws-173":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,13.4)"],"AWS Foundational Security Best Practices controls":["(EC2.19)"],"BSA":["v3 (GS-4,NS-1,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.1,SC.L2-3.13.2,SC.L2-3.13.6)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m,09.n)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.16,8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3,CIP-007-6_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.1,3.13.2,3.13.6,3.4.6)","800_53 Rev5 (CA-9,CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.3.6,2.2.2)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-110":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (DP-4,DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)"],"s202":["(CC6.1)"]},"ecc-aws-397":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-065":{"Standard1":["v7 (14.4)","v8 (3.10)"],"AWS Config":["(cloudfront-traffic-to-origin-encrypted)"],"AWS Foundational Security Best Practices controls":["(CloudFront.9)"],"BSA":["v3 (DP-3)"],"CJIS":["(5.10.1.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IVS-03)"],"DA":["(Article_8.2,Article_8.3.a,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12,D3.PC.Am.B.13)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,SC-08)"],"Standard6":["2016_679 (Article_32.1.a,Article_44)"],"Standard5":["(164.312.e.1)"],"Standard15":["(06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1,A.18.1.3)","27002_2013 (10.1.1,13.2.1,18.1.3)","27002_2022 (5.14,5.33,8.24)","27017_2015 (10.1.1,13.2.1,18.1.3)","27018_2019 (10.1.1,13.2.1,18.1.3)","27701_2019 (6.10.2.1,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.8,3.5.10)","800_53 Rev5 (AC-17.2,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-2)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1.1,4.1,4.1.1,8.2.1)"],"s202":["(CC6.1,CC6.7)"]},"ecc-aws-508":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Security_update_management)"],"DA":["(Article_8.4.f)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-140":{"Standard1":["v7 (4)","v8 (5)"],"CIS AWS Foundations Benchmark":["v1.4.0 (1.13)","v1.5.0 (1.13)"],"BSA":["v3 (IM-6)"],"CJIS":["(5.5.1,5.5.2)"],"Standard4":["v4 (IAM-06)"],"Standard3":["Standard11_800_53_Rev5 (AC-01,AC-02,AC-03,AC-06,AU-03,CM-05,IA-02.01)"],"Standard5":["(164.316.a)"],"Standard14":["27001_2013 (A.9.1.1,A.9.1.2,A.9.2.2,A.9.2.3,A.9.2.5)","27002_2013 (9.1.1,9.1.2,9.2.2,9.2.3,9.2.5)","27002_2022 (5.15,5.18,8.2,8.3)","27017_2015 (9.1.1,9.1.2,9.2.2,9.2.3,9.2.5)","27018_2019 (9.1,9.2.2,9.2.3,9.2.5)","27701_2019 (6.6.1.1,6.6.1.2,6.6.2.2,6.6.2.3,6.6.2.5)"],"Standard11":["800_53 Rev5 (AC-1,AC-2,AC-24,AC-3,AC-6,AU-3,CM-5,IA-02.1)"],"Standard9":["v1.1 (DE.CM-7,PR.AC-1,PR.AC-4,PR.AC-7,PR.PT-3)"],"Standard8":["v3.2.1 (10.2.4,10.2.5,2.1,2.1.1,2.3,7.1,7.1.1,7.1.2,7.1.3,8.3,8.3.1,8.3.2)"],"s202":["(CC6.1,CC6.3)"]},"ecc-aws-206":{"Standard1":["v7 (6.2,6.3)","v8 (3.14,8.2,8.5)"],"AWS Config":["(rds-logging-enabled)"],"AWS Foundational Security Best Practices controls":["(RDS.9)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_10.7,Article_8.3.d,Article_8.4.e)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-06.09,AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.308.a.3.ii.A,164.308.a.5.ii.C,164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AC-6.9,AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.1,10.2.5,10.3)"],"s202":["(CC6.1,CC7.2)"]},"ecc-aws-416":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.4.1,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-463":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"CE":["v2.2 (Secure_configuration)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-384":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-503":{"Standard1":["v7 (5.1)","v8 (4.1)"],"AWS Foundational Security Best Practices controls":["(RDS.24)"],"BSA":["v3 (GS-10,GS-5,NS-8,PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.3.c,Article_14.d)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-07,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32.1.b)"],"Standard15":["(08.k,10.k,10.m)"],"Standard14":["27001_2013 (A.13.1.2,A.9.1.1,A.9.4.1)","27002_2013 (13.1.2,9.1.1,9.4.1)","27002_2022 (5.10,8.21,8.3)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-7,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC6.6)"]},"ecc-aws-516":{"Standard1":["v7 (5.5)","v8 (4.1)"],"AWS Foundational Security Best Practices controls":["(S3.11)"],"AWS Config":["(s3-event-notifications-enabled)"],"BSA":["v3 (GS-5,DS-7,LT-1,LT-2)"],"CJIS":["(5.10.4.4,5.4.3)"],"CMMC":["v2.0 (AU.L2-3.3.5,CM.L2-3.4.2)"],"Standard2":["19 (APO13)"],"Standard4":["v4 (CCC-07,LOG-032,LOG-05)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard3":["Standard11_800_53_Rev5 (AU-06)"],"Standard5":["(164.312.b)"],"Standard15":["(10.k)"],"Standard14":["27001_2013 (A.14.2.5,A.16.1.2)","27002_2013 (14.2.5,16.1.2)","27002_2022 (6.8,8.27,8.9)","27017_2015 (14.2.5,16.1.2)","27018_2019 (14,16.1.2)","27701_2019 (6.11.2.5,6.13.1.2)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.2)"],"Standard11":["800-171 Rev2 (3.4.1,3.3.5)","800_53 Rev5 (AU-6)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2,A.3.3.1)"],"s202":["(CC8.1)"]},"ecc-aws-161":{"Standard1":["v7 (5.5)","v8 (4.1)"],"AWS Foundational Security Best Practices controls":["(RDS.21)"],"BSA":["v3 (GS-5,PV-2)"],"CJIS":["(5.10.4.4,5.4.3)"],"CMMC":["v2.0 (AU.L2-3.3.5,CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-07,LOG-03)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5,A.16.1.2)","27002_2013 (14.2.5,16.1.2)","27002_2022 (6.8,8.27,8.9)","27017_2015 (14.2.5,16.1.2)","27018_2019 (14,16.1.2)","27701_2019 (6.11.2.5,6.13.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-007-6_Requirement_R4_Part_4.2)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (DE.DP-4,PR.IP-1)"],"Standard8":["v3.2.1 (2.2,A.3.3.1)"],"s202":["(CC8.1)"]},"ecc-aws-359":{"Standard1":["v7 (6.2,6.3)","v8 (8.2,8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_8.4.e,Article_10.7)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.5,10.3)"],"s202":["(CC7.2)"]},"ecc-aws-373":{"Standard1":["v7 (16,16.2)","v8 (12.6)"],"CAEUC":["v1.0.0 (2.18)"],"CJIS":["(5.10.1,5.13.1.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,IA.L1-3.5.2,SC.L1-3.13.1,SC.L2-3.13.15)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.10)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,SC-08,SC-23)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.312.e.1)"],"Standard15":["(01.j,09.m,09.y,10.d)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.2)","27002_2013 (13.1.1,13.1.2)","27002_2022 (8.20,8.21)","27017_2015 (13.1.1,13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.2)"],"Standard11":["800-171 Rev2 (3.1.17,3.13.1,3.13.15)","800_53 Rev5 (AC-18,SC-23,SC-8)"],"Standard9":["v1.1 (PR.DS-2,PR.DS-5,PR.PT-4)"],"Standard8":["v3.2.1 (4.1.1)"],"s202":["(CC5.2,CC6.1)"]},"ecc-aws-509":{"Standard1":["v7 (14.4)","v8 (3.10)"],"BSA":["v3 (DP-3)"],"CJIS":["(5.10.1.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IVS-03)"],"DA":["(Article_8.2,Article_8.3.a,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12,D3.PC.Am.B.13)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,SC-08)"],"Standard6":["2016_679 (Article_32.1.a,Article_44)"],"Standard5":["(164.312.e.1)"],"Standard15":["(06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1,A.18.1.3)","27002_2013 (10.1.1,13.2.1,18.1.3)","27002_2022 (5.14,5.33,8.24)","27017_2015 (10.1.1,13.2.1,18.1.3)","27018_2019 (10.1.1,13.2.1,18.1.3)","27701_2019 (6.10.2.1,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.8,3.5.10)","800_53 Rev5 (AC-17.2,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-2)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1.1,4.1,4.1.1,8.2.1)"],"s202":["(CC6.1,CC6.7)"]},"ecc-aws-575":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800_53 Rev5 (CM-8,CM-8.1)"],"Standard9":["v1.1 (PR.DS-3)"]},"ecc-aws-086":{"Standard1":["v7 (14.6)","v8 (5.4,6.8)"],"CIS AWS Compute Services Benchmark":["v1.0.0 (4.9)"],"BSA":["v3 (IM-2,PA-1)"],"CJIS":["(5.5.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.5,AC.L2-3.1.6,AC.L2-3.1.7,SC.L2-3.13.3)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-09)"],"CE":["v2.2 (A7.5,A7.6,A7.7,uac)"],"DA":["(Article_8.4)"],"Standard10":["(D3.PC.Am.B.3)"],"Standard3":["Standard11_800_53_Rev5 (AC-02.07,AC-06.02,AC-06.05)"],"Standard6":["2016_679 (Article_25)"],"Standard15":["(01.e)"],"Standard14":["27001_2013 (A.9.2.3)","27002_2013 (9.2.3)","27002_2022 (5.15,8.2)","27017_2015 (9.2.3)","27018_2019 (9.2.3)","27701_2019 (6.6.2.3)"],"Standard11":["800-171 Rev2 (3.1.6,3.1.7)","800_53 Rev5 (AC-2.7,AC-6.2,AC-6.5)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)"],"s202":["(CC6.3)"]},"ecc-aws-329":{"Standard1":["v7 (1.4,13.2)","v8 (1.1)"],"BSA":["v3 (AM-1,AM-3)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"DA":["(Article_7.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3,PR.IP-1)"],"s202":["(CC6.1)"]},"ecc-aws-073":{"Standard1":["v7 (9.1)"],"AWS Config":["(eip-attached)"],"AWS Foundational Security Best Practices controls":["(EC2.12)"]},"ecc-aws-523":{"Standard1":["v7 (13)","v8 (3.5)"],"AWS Config":["(kms-cmk-not-scheduled-for-deletion)"],"AWS Foundational Security Best Practices controls":["(KMS.3)"],"CJIS":["(5.8.3)"],"CMMC":["v2.0 (MP.L1-3.8.3)"],"Standard2":["19 (APO14)"],"Standard4":["v4 (DCS-01,DSP-02,DSP-16)"],"Standard10":["(D3.PC.Am.B.18)"],"Standard3":["Standard11_800_53_Rev5 (MP-06,SI-12,SR-12)"],"Standard5":["(164.308.a.1.ii.B,164.310.d.2.i)"],"Standard15":["(13.l)"],"Standard14":["27001_2013 (A.11.2.7,A.8.1.3,A.8.3.2)","27002_2013 (11.2.7,8.1.3,8.3.2)","27002_2022 (5.10,7.10,7.14)","27017_2015 (11.2.7,8.1.3,8.3.2)","27018_2019 (11.2.7,8)","27701_2019 (6.5.1.3,6.5.3.2,6.8.2.7)"],"Standard12":["(CIP-011-3_Requirement_R2_Part_2.1,CIP-011-3_Requirement_R2_Part_2.2)"],"Standard11":["800-171 Rev2 (3.8.3)","800_53 Rev5 (MP-6,SI-12,SR-12)"],"Standard9":["v1.1 (PR.DS-3,PR.IP-6)"],"Standard7":["(4)"],"Standard8":["v3.2.1 (3.1)"],"s202":["(C1.2,CC6.5)"]},"ecc-aws-261":{"Standard1":["v7 (9.1)","v8 (1.1,12.2)"],"BSA":["v3 (AM-1,AM-3,GS-4,NS-2)"],"CJIS":["(5.10.1,5.4)"],"CMMC":["v2.0 (AC.L1-3.1.20,CM.L2-3.4.1,SC.L1-3.13.5,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03,BAI09)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (Secure_configuration)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,CM-08,CM-08.01,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_30,Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,07.a,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3,A.8.1.1)","27002_2013 (13.1.1,13.1.3,8.1.1)","27002_2022 (5.9,8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3,8.1.1)","27018_2019 (13.1,8)","27701_2019 (6.10.1.1,6.10.1.3,6.5.1.1)"],"Standard11":["800-171 Rev2 (3.13.2)","800_53 Rev5 (CM-7,CM-8,CM-8.1,PL-8,PM-5,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (ID.AM-1,PR.AC-5,PR.DS-3,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,2.2.2,2.2.5)"]},"ecc-aws-274":{"Standard1":["v7 (6.2)","v8 (8.2)"],"AWS Config":["(rds-logging-enabled)"],"AWS Foundational Security Best Practices controls":["(RDS.9)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_8.4.e,Article_10.7)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.308.a.3.ii.A,164.308.a.5.ii.C,164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.2,10.3)"],"s202":["(CC7.2)"]},"ecc-aws-184":{"Standard1":["v7 (14.8)","v8 (3.11)"],"AWS Config":["(dax-encryption-enabled)"],"AWS Foundational Security Best Practices controls":["(DynamoDB.3)"],"BSA":["v3 (DP-4,DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a,Article_5.1.f)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)"],"s202":["(CC6.1)"]},"ecc-aws-491":{"Standard1":["v7 (9.2)","v8 (12.2)"],"AWS Config":["(ec2-transit-gateway-auto-vpc-attach-disabled)"],"AWS Foundational Security Best Practices controls":["(EC2.23)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.10.1)"],"CMMC":["v2.0 (AC.L1-3.1.20,CM.L2-3.4.1,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard11":["800-171 Rev2 (3.13.2,3.4.2)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"]},"ecc-aws-393":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-411":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-027":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,13.4)"],"BSA":["v3 (GS-4,NS-1,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,AC.L1-3.1.20,AC.L1-3.1.22,AC.L2-3.1.16,AC.L2-3.1.17,CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.1,SC.L1-3.13.5,SC.L2-3.13.2,SC.L2-3.13.6)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6,D4.C.Co.B.2)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m,09.n)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.16,8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.1,3.13.2,3.13.5,3.4.6)","800_53 Rev5 (CA-9,CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.1.6,1.2,2.2.2,2.2.5)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-374":{"Standard1":["v7 (6.2)","v8 (8.5)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_8.4.e,Article_10.7)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800_53 Rev5 (AU-12,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.5,10.3)"],"s202":["(CC7.2)"]},"ecc-aws-256":{"Standard1":["v7 (14.8)","v8 (3.11)"],"BSA":["v3 (DP-4,DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)"],"s202":["(CC6.1)"]},"ecc-aws-291":{"Standard1":["v7 (10,5.1)","v8 (11.1,4.1)"],"BSA":["v3 (GS-10,GS-5,GS-8,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,APO14,BAI06,BAI10)"],"Standard4":["v4 (BCR-08,CCC-01,GRC-03,IVS-04)"],"CE":["v2.2 (Back_up_your_data,Secure_configuration)"],"DA":["(Article_11.1)"],"Standard10":["(D1.G.SP.B.2,D1.RM.RMP.B.1,D3.PC.Im.B.5,D5.IR.Pl.B.5,D5.IR.Pl.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,CP-02,CP-10,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32,Article_32.1.c)"],"Standard5":["(164.308.a.7.ii.A)"],"Standard15":["(08.k,09.l,10.k)"],"Standard14":["27001_2013 (A.12.3.1,A.14.2.5)","27002_2013 (12.3.1,14.2.5)","27002_2022 (8.13,8.27,8.9)","27017_2015 (12.3.1,14.2.5)","27018_2019 (12.3.1,14)","27701_2019 (6.11.2.5,6.9.3.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-009-6_Requirement_R1_Part_1.3,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,CP-10,CP-2,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1,PR.IP-9)"],"Standard7":["(6s)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(A1.2,CC8.1)"]},"ecc-aws-494":{"Standard1":["v7 (5.1)","v8 (4.1)"],"AWS Config":["(ecs-fargate-latest-platform-version)"],"AWS Foundational Security Best Practices controls":["(ECS.10)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Security_update_management)"],"DA":["(Article_8.4.f)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-497":{"Standard1":["v7 (5.1)","v8 (4.1)"],"AWS Config":["(eks-cluster-oldest-supported-version)"],"AWS Foundational Security Best Practices controls":["(EKS.2)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.f)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC8.1)"]},"ecc-aws-064":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,13.4)"],"CIS AWS Foundations Benchmark":["v1.4.0 (5.3)","v1.5.0 (5.4)"],"AWS Config":["(vpc-default-security-group-closed)"],"AWS Foundational Security Best Practices controls":["(EC2.2)"],"BSA":["v3 (AM-4,IM-7,PA-7)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.e.1)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.3,8.20,8.22,8.27)","27017_2015 (8.1.3,9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-aws-320":{"Standard1":["v7 (5.1,6.2)","v8 (4.1,8.2)"],"BSA":["v3 (DS-7,GS-10,GS-5,LT-3,LT-4,PV-1,PV-3)"],"CIS Oracle Database 19 Benchmark":["v1.0.0 (2.2.12)"],"CJIS":["(5.4.1,5.7.1)"],"CMMC":["v2.0 (AU.L2-3.3.1,CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10,DSS03)"],"Standard4":["v4 (CCC-01,IVS-04,LOG-08)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.e,Article_10.7)"],"Standard10":["(D1.G.SP.B.2,D2.MA.Ma.B.1,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-12,CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_30,Article_32)"],"Standard5":["(164.312.b)"],"Standard15":["(08.k,09.aa,10.k)"],"Standard14":["27001_2013 (A.12.4.1,A.14.2.5)","27002_2013 (12.4.1,14.2.5)","27002_2022 (8.15,8.27,8.9)","27017_2015 (12.4.1,14.2.5)","27018_2019 (12.4.1,14)","27701_2019 (6.11.2.5,6.9.4.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-007-6_Requirement_R4_Part_4.1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.3.1,3.4.1,3.4.2)","800_53 Rev5 (AU-12,AU-2,CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (DE.AE-3,PR.IP-1,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.2,10.3,2.2)"],"s202":["(CC7.2,CC8.1)"]},"ecc-aws-449":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (IVS-02)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.2,Article_8.4.a)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,CP-06,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k,12.c)"],"Standard14":["27001_2013 (A.14.2.5,A.17.2.1)","27002_2013 (14.2.5,17.2.1)","27002_2022 (8.14,8.27,8.9)","27017_2015 (14.2.5,17.2.1)","27018_2019 (14,17)","27701_2019 (6.11.2.5,6.14.2.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,CP-6,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR-PT-5,PR.IP-1)"],"s202":["(CC8.1)"]},"ecc-aws-172":{"Standard1":["v7 (12.4,9.2)","v8 (12.2,13.4)"],"AWS Foundational Security Best Practices controls":["(EC2.19)"],"BSA":["v3 (GS-4,NS-1,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L1-3.13.1,SC.L2-3.13.2,SC.L2-3.13.6)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CA-09,CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,01.o,09.m,09.n)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.3)","27002_2013 (13.1.1,13.1.3)","27002_2022 (8.16,8.20,8.22,8.27)","27017_2015 (13.1.1,13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.3)"],"Standard12":["(CIP-005-7,CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3,CIP-007-6_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.1,3.13.2,3.13.6,3.4.6)","800_53 Rev5 (CA-9,CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.3.6,2.2.2)"],"s202":["(CC6.1,CC6.6)"]},"ecc-aws-231":{"Standard1":["v7 (6.3)","v8 (8.3)"],"BSA":["v3 (LT-6)"],"Standard13":["v1.0.0 (3.1.8)"],"CJIS":["(5.4.6)"],"Standard3":["Standard11_800_53_Rev5 (AU-04)"],"Standard15":["(09.h)"],"Standard14":["27001_2013 (A.12.1.3)","27002_2013 (12.1.3)","27002_2022 (8.6)","27017_2015 (12.1.3)","27018_2019 (12.1.3)","27701_2019 (6.9.1.3)"],"Standard11":["800_53 Rev5 (AU-4)"],"Standard9":["v1.1 (PR.DS-4)"],"Standard8":["v3.2.1 (10.7)"],"s202":["(A1.1)"]},"ecc-aws-535":{"Standard1":["v7 (14.4)","v8 (3.10)"],"AWS Config":["(elb-acm-certificate-required)"],"AWS Foundational Security Best Practices controls":["(ELB.2)"],"BSA":["v3 (DP-3)"],"CJIS":["(5.10.1.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IVS-03)"],"DA":["(Article_8.2,Article_8.3.a,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12,D3.PC.Am.B.13)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,SC-08)"],"Standard6":["2016_679 (Article_32.1.a,Article_44)"],"Standard5":["(164.312.a.2.iv,164.312.e.1,164.312.e.2.i,164.312.e.2.ii,164.314.b.1,164.314.b.2,164.314.b.2.i)"],"Standard15":["(06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1,A.18.1.3)","27002_2013 (10.1.1,13.2.1,18.1.3)","27002_2022 (5.14,5.33,8.24)","27017_2015 (10.1.1,13.2.1,18.1.3)","27018_2019 (10.1.1,13.2.1,18.1.3)","27701_2019 (6.10.2.1,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.8,3.5.10)","800_53 Rev5 (AC-17.2,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-2)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1.1,4.1,4.1.1,8.2.1)"],"s202":["(CC6.1,CC6.7)"]},"ecc-aws-240":{"Standard1":["v7 (5.1)","v8 (4.1,8.2)"],"BSA":["v3 (DS-7,GS-10,GS-5,LT-3,LT-4,PV-1,PV-3)"],"Standard13":["v1.0.0 (3.1.21)"],"CJIS":["(5.4.1,5.7.1)"],"CMMC":["v2.0 (AU.L2-3.3.1,CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10,DSS03)"],"Standard4":["v4 (CCC-01,IVS-04,LOG-08)"],"CE":["v2.2 (Secure_configuration)"],"Standard10":["(D1.G.SP.B.2,D2.MA.Ma.B.1,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-12,CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_30,Article_32)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa,10.k)"],"Standard14":["27001_2013 (A.12.4.1,A.14.2.5)","27002_2013 (12.4.1,14.2.5)","27002_2022 (8.15,8.27,8.9)","27017_2015 (12.4.1,14.2.5)","27018_2019 (12.4.1,14)","27701_2019 (6.11.2.5,6.9.4.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1,3.4.1,3.4.2)","800_53 Rev5 (AU-12,AU-2,CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (DE.AE-3,PR.IP-1,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.2,10.3,2.2)"],"s202":["(CC7.2,CC8.1)"]},"ecc-aws-249":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"s202":["(CC8.1)"]},"ecc-aws-078":{"Standard1":["v7 (4.9)","v8 (8.11)"],"CIS AWS Foundations Benchmark":["v1.4.0 (4.3)","v1.5.0 (4.3)"],"AWS Foundational Security Best Practices controls":["(CloudWatch.1)"],"BSA":["v3 (DS-7,LT-1,LT-2,LT-5)"],"CJIS":["(5.4.1,5.4.3)"],"CMMC":["v2.0 (AU.L2-3.3.5)"],"Standard4":["v4 (LOG-05)"],"DA":["(Article_9.2,Article_15.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-06,AU-06.01,AU-06.05,AU-07)"],"Standard5":["(164.308.a.1.ii.D,164.312.b)"],"Standard14":["27001_2013 (A.12.4.1,A.16.1.4)","27002_2013 (12.4.1,16.1.4)","27002_2022 (5.25)","27017_2015 (12.4.1,16.1.4)","27018_2019 (12.4.1,6.1.4)","27701_2019 (6.13.1.4,6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.4)"],"Standard11":["800-171 Rev2 (3.3.3,3.3.5)","800_53 Rev5 (AU-6,AU-6.1,AU-6.5,AU-7)"],"Standard9":["v1.1 (DE.AE-2,PR.PT-1,RS.AN-1)"],"Standard8":["v3.2.1 (10.6,10.6.1,10.6.2)"],"s202":["(CC4.1,CC7.3)"]},"ecc-aws-457":{"Standard1":["v7 (6)","v8 (4.1)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (IVS-02)"],"CE":["v2.2 (Secure_configuration)"],"Standard3":["Standard11_800_53_Rev5 (SA-03,SA-08,SA-10,SI-04)"],"Standard6":["2016_679 (Article_32)"],"Standard5":["(164.312.b)"],"Standard15":["(08.k,09.ab,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.16,8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (SA-10,SA-3,SA-8,SI-4)"],"Standard9":["v1.1 (DE.CM-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(A1.1,CC7.1)"]},"ecc-aws-404":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-243":{"Standard1":["v7 (6.3)","v8 (4.1,8.5)"],"BSA":["v3 (DS-7,GS-10,GS-5,LT-3,LT-4,PV-1,PV-3)"],"CJIS":["(5.4.1,5.7.1)"],"CMMC":["v2.0 (AU.L2-3.3.1,CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04,LOG-08)"],"CE":["v2.2 (Secure_configuration)"],"Standard10":["(D1.G.SP.B.2,D2.MA.Ma.B.1,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AU-03,AU-12,CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_30,Article_32)"],"Standard15":["(08.k,09.aa,10.k)"],"Standard14":["27001_2013 (A.12.4.1,A.14.2.5)","27002_2013 (12.4.1,14.2.5)","27002_2022 (5.28,8.15,8.27,8.9)","27017_2015 (12.4.1,14.2.5)","27018_2019 (12.4.1,14)","27701_2019 (6.11.2.5,6.9.4.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (AU-12,AU-3,AU-3.1,CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (DE.AE-3,PR.IP-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2.5,10.3,2.2)"],"s202":["(CC7.2,CC8.1)"]},"ecc-aws-046":{"Standard1":["v7 (4.3)","v8 (5.4)"],"CIS AWS Foundations Benchmark":["v1.4.0 (1.4)","v1.5.0 (1.4)"],"AWS Config":["(iam-root-access-key-check)"],"AWS Foundational Security Best Practices controls":["(IAM.4)"],"BSA":["v3 (AM-4,IM-2,IM-7,PA-1,PA-7)"],"CJIS":["(5.5.2,5.5.2.1)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,AC.L2-3.1.6,AC.L2-3.1.7,MP.L2-3.8.2,SC.L2-3.13.3)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05,IAM-09)"],"CE":["v2.2 (A7.4,A7.5,A7.6,A7.7,Secure_configuration,uac)"],"DA":["(Article_8.4)"],"Standard10":["(D3.PC.Am.B.1,D3.PC.Am.B.3)"],"Standard3":["Standard11_800_53_Rev5 (AC-02.07,AC-03,AC-06,AC-06.02,AC-06.05,MP-02)"],"Standard6":["2016_679 (Article_25,Article_5.1.f)"],"Standard5":["(164.308.a.1.ii.B,164.308.a.3.i,164.308.a.4.i,164.308.a.4.ii.A,164.308.a.4.ii.B,164.312.a.1,164.312.a.2.i,164.314.b.2.i)"],"Standard15":["(01.a,01.c,01.e,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.2.3,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.2.3,9.4.1)","27002_2022 (5.10,5.15,8.2,8.3)","27017_2015 (8.1.3,9.1.1,9.2.3,9.4.1)","27018_2019 (8,9.1,9.2.3,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.2.3,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.1.6,3.1.7,3.8.2)","800_53 Rev5 (AC-2.7,AC-3,AC-6,AC-6.2,AC-6.5,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-aws-505":{"Standard1":["v7 (5.1)","v8 (4.1)"],"AWS Config":["(redshift-default-admin-check)"],"AWS Foundational Security Best Practices controls":["(Redshift.8)"],"BSA":["v3 (GS-10,GS-5,NS-8,PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (APO13,BAI06,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.3.c,Article_14.d)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-07,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32.1.b)"],"Standard15":["(08.k,10.k,10.m)"],"Standard14":["27001_2013 (A.13.1.2,A.9.1.1,A.9.4.1)","27002_2013 (13.1.2,9.1.1,9.4.1)","27002_2022 (5.10,8.21,8.3)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-7,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(CC6.6)"]},"ecc-aws-247":{"Standard1":["v7 (5.1)","v8 (4.2)"],"BSA":["v3 (GS-10,GS-5,NS-2,PV-1)"],"CJIS":["(5.10.1,5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,SC.L2-3.13.6)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (DSP-07,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AC-18,CM-02,CM-06,CM-07,CM-07.01,CM-09)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(09.m,09.n,10.k)"],"Standard14":["27001_2013 (A.13.1.2)","27002_2013 (13.1.2)","27002_2022 (8.21,8.9)","27017_2015 (13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.2)"],"Standard12":["(CIP-003-8_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.13.1,3.4.2)","800_53 Rev5 (AC-18,CM-2,CM-6,CM-7,CM-7.1,CM-9)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (1.1.1)"],"s202":["(CC6.6)"]},"ecc-aws-306":{"Standard1":["v7 (5.1,6.2)","v8 (4.1,8.2)"],"BSA":["v3 (DS-7,GS-10,GS-5,LT-3,LT-4,PV-1,PV-3)"],"CJIS":["(5.4.1,5.7.1)"],"CMMC":["v2.0 (AU.L2-3.3.1,CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI06,BAI10,DSS03)"],"Standard4":["v4 (CCC-01,IVS-04,LOG-08)"],"CE":["v2.2 (Secure_configuration)"],"Standard10":["(D1.G.SP.B.2,D2.MA.Ma.B.1,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-12,CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_30,Article_32)"],"Standard5":["(164.312.b)"],"Standard15":["(08.k,09.aa,10.k)"],"Standard14":["27001_2013 (A.12.4.1,A.14.2.5)","27002_2013 (12.4.1,14.2.5)","27002_2022 (8.15,8.27,8.9)","27017_2015 (12.4.1,14.2.5)","27018_2019 (12.4.1,14)","27701_2019 (6.11.2.5,6.9.4.1)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-007-6_Requirement_R4_Part_4.1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.3.1,3.4.1,3.4.2)","800_53 Rev5 (AU-12,AU-2,CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (DE.AE-3,PR.IP-1,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.2,10.3,2.2)"],"s202":["(CC7.2,CC8.1)"]},"ecc-aws-402":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-aws-518":{"Standard1":["v7 (10)","v8 (3.4)"],"AWS Config":["(s3-version-lifecycle-policy-check)"],"AWS Foundational Security Best Practices controls":["(S3.10)"],"CJIS":["(5.4.6)"],"Standard2":["19 (DSS06)"],"Standard4":["v4 (DSP-16)"],"DA":["(Article_8.3.d)"],"Standard3":["Standard11_800_53_Rev5 (SI-12)"],"Standard6":["2016_679 (Article_5.1.e)"],"Standard15":["(13.l)"],"Standard14":["27001_2013 (A.18.1.3)","27002_2013 (18.1.3)","27002_2022 (5.33)","27017_2015 (18.1.3)","27018_2019 (18.1)","27701_2019 (6.15.1.3)"],"Standard11":["800_53 Rev5 (SI-12)"],"Standard7":["(4._Integrate_data_privacy_into_records_retention_practices)"],"Standard8":["v3.2.1 (3.1)"],"s202":["(C1.1)"]},"ecc-aws-442":{"Standard1":["v7 (14.8)","v8 (3.10)"],"BSA":["v3 (DP-3)"],"CJIS":["(5.10.1.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IVS-03)"],"DA":["(Article_8.2,Article_8.3.a,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12,D3.PC.Am.B.13)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,SC-08)"],"Standard6":["2016_679 (Article_32.1.a,Article_44)"],"Standard5":["(164.312.e.1)"],"Standard15":["(06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1,A.18.1.3)","27002_2013 (10.1.1,13.2.1,18.1.3)","27002_2022 (5.14,5.33,8.24)","27017_2015 (10.1.1,13.2.1,18.1.3)","27018_2019 (10.1.1,13.2.1,18.1.3)","27701_2019 (6.10.2.1,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.8,3.5.10)","800_53 Rev5 (AC-17.2,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-2)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1.1,4.1,4.1.1,8.2.1)"],"s202":["(CC6.1,CC6.7)"]},"ecc-aws-041":{"Standard1":["v7 (1.4)","v8 (1.1)"],"BSA":["v3 (AM-1,AM-3)"],"CJIS":["(5.4)"],"CMMC":["v2.0 (CM.L2-3.4.1)"],"Standard2":["19 (BAI09)"],"Standard4":["v4 (DCS-05,UEM-04)"],"CE":["v2.2 (A2.4,A2.5,A2.6,A2.8,A2.9)"],"DA":["(Article_7.1)"],"Standard10":["(D1.G.IT.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-08,CM-08.01)"],"Standard6":["2016_679 (Article_30)"],"Standard15":["(07.a)"],"Standard14":["27001_2013 (A.8.1.1)","27002_2013 (8.1.1)","27002_2022 (5.9)","27017_2015 (8.1.1)","27018_2019 (8)","27701_2019 (6.5.1.1)"],"Standard11":["800-171 Rev2 (3.1.18,3.1.20,3.12.4)","800_53 Rev5 (CM-8,CM-8.1,PM-5)"],"Standard9":["v1.1 (ID.AM-1,PR.DS-3)"],"Standard8":["v3.2.1 (11.1.1,12.3.3,2.4,9.9.1)"],"s202":["(CC3.2,CC6.1)"]},"ecc-k8s-016":{"Standard1":["v7 (6)","v8 (4.8,8)"],"Standard16":["v1.7.0 (1.2.17)"],"BSA":["v3 (AM-2,AM-5,PV-1,PV-3)"],"CJIS":["(5.4.3,5.7.1.1)"],"CMMC":["v2.0 (CM.L2-3.4.7,CM.L2-3.4.2)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A5.1,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,CM-07,SA-08)"],"Standard15":["(01.l)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2,3.4.7)","800_53 Rev5 (CM-2,CM-6,CM-7,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.1.1,2.2.1,2.2.4,2.2.6,6.2.1)"],"s202":["(CC8.1)"]},"ecc-k8s-067":{"Standard1":["v7 (5.2)","v8 (4.8)"],"Standard16":["v1.7.0 (5.2.9)"],"CIS GKE Benchmark":["v1.2.0 (4.2.8)","v1.3.0 (4.2.7)","v1.4.0 (4.2.7)"],"BSA":["v3 (AM-2,AM-5,PV-1,PV-3)"],"CJIS":["(5.7.1.1)"],"CMMC":["v2.0 (CM.L2-3.4.2,CM.L2-3.4.7)"],"CE":["v2.2 (Secure_configuration)"],"Standard10":["(D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CM-06,CM-07)"],"Standard15":["(01.l)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard11":["800-171 Rev2 (3.4.7)","800_53 Rev5 (CM-6,CM-7)"],"Standard8":["v3.2.1 (2.2.2,2.2.5)","v4.0 (2.2.4)"],"s202":["(CC8.1)"]},"ecc-k8s-081":{"Standard1":["v7 (5.2)","v8 (4.8)"],"BSA":["v3 (AM-2,AM-5,PV-1,PV-3)"],"CJIS":["(5.7.1.1)"],"CMMC":["v2.0 (CM.L2-3.4.2,CM.L2-3.4.7)"],"CE":["v2.2 (Secure_configuration)"],"Standard10":["(D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CM-06,CM-07)"],"Standard15":["(01.l)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard11":["800-171 Rev2 (3.4.7)","800_53 Rev5 (CM-6,CM-7)"],"Standard8":["v3.2.1 (2.2.2,2.2.5)","v4.0 (2.2.4)"],"s202":["(CC8.1)"]},"ecc-k8s-062":{"Standard1":["v7 (2.10,14.1)","v8 (3.12)"],"Standard16":["v1.7.0 (5.2.4)"],"CIS GKE Benchmark":["v1.2.0 (4.2.3)","v1.3.0 (4.2.3)","v1.4.0 (4.2.3)"],"BSA":["v3 (GS-2,GS-3,NS-1,NS-2)"],"CJIS":["(5.5.6.1)"],"CMMC":["v2.0 (AC.L2-3.1.3,CM.L2-3.4.6,SC.L1-3.13.5)"],"Standard4":["v4 (DSP-07)"],"CE":["v2.2 (A8.6,Secure_configuration)"],"DA":["(Article_8.4.d)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-02,SC-07.21)"],"Standard14":["27001_2013 (A.13.1.3)","27002_2013 (13.1.3)","27002_2022 (8.20,8.22)","27017_2015 (13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.3)"],"Standard12":["(CIP-005-7_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-2,SC-7.21)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.1.1,2.2.1,2.2.6,7.1)"],"s202":["(CC6.1,CC6.6)"]},"ecc-k8s-044":{"Standard1":["v7 (14.6)","v8 (3.3)"],"Standard16":["v1.7.0 (2.5)"],"BSA":["v3 (AM-4,IM-7,PA-7)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.b,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.3)","27017_2015 (8.1.3,9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1.1,7.1.2,7.1.3)","v4.0 (1.3.1,7.1)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-k8s-049":{"Standard1":["v7 (4,5.1,5.2)","v8 (16.7,4.1)"],"Standard16":["v1.7.0 (5.7.2)"],"CIS RedHat OpenShift Container Platform Benchmark":["v1.4.0 (5.7.2)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CJIS":["(5.7.1.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.6,CM.L2-3.4.7)"],"Standard2":["19 (BAI06,BAI10)"],"Standard4":["v4 (AIS-02,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,CM-07,SA-08)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-2,CM-6,CM-7,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]},"ecc-k8s-024":{"Standard1":["v7 (14.4)","v8 (3.11)"],"Standard16":["v1.7.0 (1.2.25)"],"BSA":["v3 (DP-4,DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.1.1,3.3.2,3.3.3,3.5.1.2,3.5.1.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-k8s-032":{"Standard1":["v7 (9.2)","v8 (4.8)"],"Standard16":["v1.7.0 (1.3.2)"],"BSA":["v3 (AM-2,AM-5,DS-2,GS-10,PV-1,PV-3)"],"CJIS":["(5.4.3,5.7.1.1)"],"CMMC":["v2.0 (CM.L2-3.4.7,CM.L2-3.4.2,CM.L2-3.4.7)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A5.1,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,CM-07,SA-08)"],"Standard15":["(01.l)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2,3.4.7)","800_53 Rev5 (CM-2,CM-6,CM-7,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.1.1,2.2.1,2.2.4,2.2.6,6.2.1)"],"s202":["(CC8.1)"]},"ecc-k8s-038":{"Standard1":["v7 (9.2)","v8 (4.8)"],"Standard16":["v1.7.0 (1.4.1)"],"BSA":["v3 (AM-2,AM-5,DS-2,GS-10,PV-1,PV-3)"],"CJIS":["(5.4.3,5.7.1.1)"],"CMMC":["v2.0 (CM.L2-3.4.7,CM.L2-3.4.2,CM.L2-3.4.7)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (A5.1,Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,CM-07,SA-08)"],"Standard15":["(01.l)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2,3.4.7)","800_53 Rev5 (CM-2,CM-6,CM-7,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.1.1,2.2.1,2.2.4,2.2.6,6.2.1)"],"s202":["(CC8.1)"]},"ecc-k8s-002":{"Standard1":["v7 (16.4)","v8 (4.1,4.6)"],"Standard16":["v1.7.0 (1.2.2)"],"BSA":["v3 (GS-10,GS-5,PV-1,PV-3)"],"CJIS":["(5.5.1,5.5.2)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.3.d,Article_8.4.d)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,SA-08)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard15":["(07.c)"],"Standard14":["27001_2013 (A.9.4.2,A.14.2.5)","27002_2013 (9.4.2,14.2.5)","27002_2022 (8.5,8.27,8.9)","27017_2015 (9.4.2,14.2.5)","27018_2019 (9.4.2,14)","27701_2019 (6.6.4.2,6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,SA-8)"],"Standard9":["v1.1 (PR.IP-1,PR.IP-2)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.1.1,2.2.1,2.2.6)"],"s202":["(CC5.2)"]},"ecc-k8s-013":{"Standard1":["v7 (14.6)","v8 (3.3)"],"Standard16":["v1.7.0 (1.2.13)"],"BSA":["v3 (AM-4,IM-7,PA-7)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.b,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.3)","27017_2015 (8.1.3,9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1.1,7.1.2,7.1.3)","v4.0 (1.3.1,7.1)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-k8s-070":{"Standard1":["v7 (4)","v8 (5.4)"],"Standard16":["v1.7.0 (5.2.7)"],"CIS GKE Benchmark":["v1.2.0 (4.2.6)","v1.3.0 (4.2.6)","v1.4.0 (4.2.6)"],"BSA":["v3 (IM-2,PA-1)"],"CJIS":["(5.5.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.5,AC.L2-3.1.6,AC.L2-3.1.7,SC.L2-3.13.3)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-09)"],"CE":["v2.2 (A7.5,A7.6,A7.7,uac)"],"DA":["(Article_8.4)"],"Standard10":["(D3.PC.Am.B.3)"],"Standard3":["Standard11_800_53_Rev5 (AC-02.07,AC-06.02,AC-06.05)"],"Standard6":["2016_679 (Article_25)"],"Standard15":["(01.e)"],"Standard14":["27001_2013 (A.9.2.3)","27002_2013 (9.2.3)","27002_2022 (5.15,8.2)","27017_2015 (9.2.3)","27018_2019 (9.2.3)","27701_2019 (6.6.2.3)"],"Standard11":["800-171 Rev2 (3.1.6,3.1.7)","800_53 Rev5 (AC-2.7,AC-6.2,AC-6.5)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)"],"s202":["(CC6.3)"]},"ecc-k8s-092":{"Standard1":["v7 (16.4)","v8 (4.1,4.6)"],"CIS RedHat OpenShift Container Platform Benchmark":["v1.4.0 (1.2.2)"],"BSA":["v3 (GS-10,GS-5,PV-1)"],"CJIS":["(5.5.1,5.5.2)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.3.d,Article_8.4.d)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,SA-08)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard14":["27001_2013 (A.9.4.2,A.14.2.5)","27002_2013 (9.4.2,14.2.5)","27002_2022 (8.5,8.27,8.9)","27017_2015 (9.4.2,14.2.5)","27018_2019 (9.4.2,14)","27701_2019 (6.6.4.2,6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,SA-8)"],"Standard9":["v1.1 (PR.IP-1,PR.IP-2)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.1.1,2.2.1,2.2.6)"],"s202":["(CC5.2)"]},"ecc-k8s-012":{"Standard1":["v7 (9)","v8 (16.11)"],"Standard16":["v1.7.0 (1.2.12)"],"BSA":["v3 (DS-2,GS-10,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.2,CM.L2-3.4.7)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,SA-08)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-2,CM-6,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.1.1,2.2.1,2.2.6,6.2.1)"],"s202":["(CC8.1)"]},"ecc-k8s-061":{"Standard1":["v8 (4.1)","v7 (5.1)"],"Standard16":["v1.7.0 (5.2.11)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]},"ecc-k8s-008":{"Standard1":["v7 (14.6)","v8 (6.8)"],"Standard16":["v1.7.0 (1.2.8)"],"BSA":["v3 (IM-7,PA-7)"],"CJIS":["(5.5.1,5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.4,AC.L2-3.1.5,MP.L2-3.8.2,SC.L2-3.13.3)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-04,IAM-05,IAM-16)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.b,Article_14.d,Article_8.4.d)"],"Standard10":["(D1.R.St.B.1,D3.PC.Am.B.1,D3.PC.Am.B.2)"],"Standard3":["Standard11_800_53_Rev5 (AC-02,AC-02.07,AC-03,AC-05,AC-06,AC-06.01,AC-06.07)"],"Standard6":["2016_679 (Article_25,Article_5.1.f)"],"Standard5":["(164.308.a.3.i,164.308.a.4.i,164.308.a.4.ii.B,164.312.a.1)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.6.1.2,A.8.1.3,A.9,A.9.1.1,A.9.4.1)","27002_2013 (6.1.2,8.1.3,9,9.1.1,9.4.1)","27002_2022 (5.10,5.15,5.3,8.2,8.3)","27017_2015 (6.1.2,8.1.3,9,9.1.1,9.4.1)","27018_2019 (6.1.2,8,9,9.1,9.4.1)","27701_2019 (6.10.1.3,6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.1.4,3.1.5,3.8.2)","800_53 Rev5 (AC-2,AC-2.7,AC-3,AC-3.7,AC-5,AC-6,AC-6.1,AC-6.7)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)","v4.0 (1.3.1,10.3.1,7.1,7.1.1,7.2.1,7.2.2,7.2.4,7.2.6,7.3.1,7.3.2)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-k8s-030":{"Standard1":["v7 (5.1)","v8 (4.2,12.6)"],"Standard16":["v1.7.0 (1.2.31)"],"BSA":["v3 (GS-10,GS-4,GS-5,PV-1)"],"CJIS":["(5.10.1.2)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,CM.L2-3.4.7,AC.L2-3.1.13,SC.L2-3.13.11,SC.L2-3.13.2,SC.L2-3.13.8)"],"Standard2":["19 (APO01,BAI10,DSS05)"],"Standard4":["v4 (CEK-03,DSP-07,DSP-10,DSP-17,IVS-03,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.2,Article_8.3.a,Article_8.3.c,Article_8.4.d,Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5,D3.PC.Am.B.13,D3.PC.Im.B.10,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,AC-17.02,PL-08,SA-08,SC-08)"],"Standard6":["2016_679 (Article_32,Article_44)"],"Standard5":["(164.312.e.1)"],"Standard15":["(09.m,09.n,09.m,09.s,09.v,09.y,10.d)"],"Standard14":["27001_2013 (A.13.1.2,A.10.1.1,A.13.2.1)","27002_2013 (13.1.2,10.1.1,13.2.1)","27002_2022 (8.21,8.9,5.14,5.33,8.20,8.24,8.27)","27017_2015 (13.1.2,10.1.1,13.2.1)","27018_2019 (13.1,10.1.1,13.2.1)","27701_2019 (6.10.1.2,6.10.1.1,6.10.2.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.4.2,3.1.13,3.1.17,3.13.2,3.13.8)","800_53 Rev5 (CM-2,CM-6,AC-17.2,PL-8,SA-8,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.IP-1,PR.PT-4,PR.DS-2)"],"Standard8":["v3.2.1 (1.1.6,2.2.2,4.1,4.1.1)","v4.0 (1.2.6,2.1.1,2.2.1)"],"s202":["(CC5.2,CC6.1,CC6.7)"]},"ecc-k8s-005":{"Standard1":["v7 (9)","v8 (16.11)"],"Standard16":["v1.7.0 (1.2.5)"],"BSA":["v3 (IM-4)"],"Standard4":["v4 (IAM-14,CEK-10)"],"CMMC":["v2.0 (AC.L1-3.1.1,IA.L1-3.5.2)"],"DA":["(Article_8.4.d)"],"Standard14":["27001_2013 (A.9.4.2)","27002_2013 (9.4.2)","27002_2022 (8.5)","27017_2015 (9.4.2)","27018_2019 (9.4.2)","27701_2019 (6.6.4.2)"],"Standard11":["800-171 Rev2 (3.5.2)","800_53 Rev5 (IA-9)"],"Standard9":["v1.1 (PR.AC-7,PR.DS-1)"],"s202":["(CC6.1)"],"Standard8":["v4.0 (6.2.1)"]},"ecc-k8s-072":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (DS-2,GS-10,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.2,CM.L2-3.4.7)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,SA-08)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-2,CM-6,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.1.1,2.2.1,2.2.6,6.2.1)"],"s202":["(CC8.1)"]},"ecc-k8s-007":{"Standard1":["v7 (14.6)","v8 (3.3,6.8)"],"Standard16":["v1.7.0 (1.2.7)"],"BSA":["v3 (IM-7,PA-7)"],"CJIS":["(5.5.1,5.5.2,5.5.2.1,5.5.2.2,5.5.2.3,5.5.2.4)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.4,AC.L2-3.1.5,MP.L2-3.8.2,SC.L2-3.13.3)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-04,IAM-05,IAM-16)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.b,Article_14.d,Article_8.4.d)"],"Standard10":["(D1.R.St.B.1,D3.PC.Am.B.1,D3.PC.Am.B.2)"],"Standard3":["Standard11_800_53_Rev5 (AC-02,AC-02.07,AC-03,AC-05,AC-06,AC-06.01,AC-06.07)"],"Standard6":["2016_679 (Article_25,Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B,164.312.a.1)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.6.1.2,A.8.1.3,A.9,A.9.1.1,A.9.4.1)","27002_2013 (6.1.2,8.1.3,9,9.1.1,9.4.1)","27002_2022 (5.10,5.15,5.3,8.2,8.3)","27017_2015 (6.1.2,8.1.3,9,9.1.1,9.4.1)","27018_2019 (6.1.2,8,9,9.1,9.4.1)","27701_2019 (6.10.1.3,6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.1.4,3.1.5,3.8.2)","800_53 Rev5 (AC-2,AC-2.7,AC-3,AC-3.7,AC-5,AC-6,AC-6.1,AC-6.7)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)","v4.0 (10.3.1,7.1,7.1.1,7.2.1,7.2.2,7.2.4,7.2.6,7.3.1,7.3.2)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-k8s-035":{"Standard1":["v7 (14.4)","v8 (3.10)"],"Standard16":["v1.7.0 (1.3.5)"],"BSA":["v3 (DP-3)"],"CJIS":["(5.10.1.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IVS-03)"],"DA":["(Article_8.2,Article_8.3.a,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12,D3.PC.Am.B.13)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,SC-08)"],"Standard6":["2016_679 (Article_32.1.a,Article_44)"],"Standard5":["(164.312.e.1)"],"Standard15":["(06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1,A.18.1.3)","27002_2013 (10.1.1,13.2.1,18.1.3)","27002_2022 (5.14,5.33,8.24)","27017_2015 (10.1.1,13.2.1,18.1.3)","27018_2019 (10.1.1,13.2.1,18.1.3)","27701_2019 (6.10.2.1,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.8,3.5.10)","800_53 Rev5 (AC-17.2,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-2)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1.1,4.1,4.1.1,8.2.1)","v4.0 (2.2.7,4.1.1,4.2.1.2,4.2.2,8.3.2)"],"s202":["(CC6.1,CC6.7)"]},"ecc-k8s-064":{"Standard1":["v7 (14.1)","v8 (3.12)"],"Standard16":["v1.7.0 (5.2.5)"],"CIS GKE Benchmark":["v1.2.0 (4.2.4)","v1.3.0 (4.2.4)","v1.4.0 (4.2.4)"],"BSA":["v3 (GS-2,GS-3,NS-1,NS-2)"],"CJIS":["(5.5.6.1)"],"CMMC":["v2.0 (AC.L2-3.1.3,CM.L2-3.4.6,SC.L1-3.13.5)"],"Standard4":["v4 (DSP-07)"],"CE":["v2.2 (A8.6,Secure_configuration)"],"DA":["(Article_8.4.d)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-02,SC-07.21)"],"Standard14":["27001_2013 (A.13.1.3)","27002_2013 (13.1.3)","27002_2022 (8.20,8.22)","27017_2015 (13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.3)"],"Standard12":["(CIP-005-7_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-2,SC-7.21)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.1.1,2.2.1,2.2.6,7.1)"],"s202":["(CC6.1,CC6.6)"]},"ecc-k8s-052":{"Standard1":["v7 (2.10)","v8 (3.12,12.2)"],"Standard16":["v1.7.0 (5.7.4)"],"CIS RedHat OpenShift Container Platform Benchmark":["v1.4.0 (5.7.4)"],"CIS GKE Benchmark":["v1.2.0 (4.6.4)","v1.3.0 (4.6.4)","v1.4.0 (4.6.4)"],"BSA":["v3 (GS-2,GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,CM.L2-3.4.1,CM.L2-3.4.6,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-02,SC-07)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard15":["(01.m)"],"Standard14":["27001_2013 (A.13.1.3)","27002_2013 (13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.3)"],"Standard12":["(CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-2,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.1.1,2.2.1,2.2.6,7.1)"],"s202":["(CC6.1,CC6.6)"]},"ecc-k8s-066":{"Standard1":["v7 (5.2)","v8 (4.8)"],"Standard16":["v1.7.0 (5.2.8)"],"CIS GKE Benchmark":["v1.2.0 (4.2.7)"],"BSA":["v3 (AM-2,AM-5,PV-1,PV-3)"],"CJIS":["(5.7.1.1)"],"CMMC":["v2.0 (CM.L2-3.4.2,CM.L2-3.4.7)"],"CE":["v2.2 (Secure_configuration)"],"Standard10":["(D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CM-06,CM-07)"],"Standard15":["(01.l)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard11":["800-171 Rev2 (3.4.7)","800_53 Rev5 (CM-6,CM-7)"],"Standard8":["v3.2.1 (2.2.2,2.2.5)","v4.0 (2.2.4)"],"s202":["(CC8.1)"]},"ecc-k8s-031":{"Standard1":["v7 (5.1)","v8 (4.1)"],"Standard16":["v1.7.0 (1.3.1)"],"BSA":["v3 (DS-2,GS-10,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.2,CM.L2-3.4.7)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,SA-08)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-2,CM-6,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.1.1,2.2.1,2.2.6,6.2.1)"],"s202":["(CC8.1)"]},"ecc-k8s-026":{"Standard1":["v7 (14.4)","v8 (3.10)"],"Standard16":["v1.7.0 (1.2.27)"],"BSA":["v3 (DP-3)"],"CJIS":["(5.10.1.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IVS-03)"],"DA":["(Article_8.2,Article_8.3.a,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12,D3.PC.Am.B.13)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,SC-08)"],"Standard6":["2016_679 (Article_32.1.a,Article_44)"],"Standard5":["(164.312.e.1)"],"Standard15":["(06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1,A.18.1.3)","27002_2013 (10.1.1,13.2.1,18.1.3)","27002_2022 (5.14,5.33,8.24)","27017_2015 (10.1.1,13.2.1,18.1.3)","27018_2019 (10.1.1,13.2.1,18.1.3)","27701_2019 (6.10.2.1,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.8,3.5.10)","800_53 Rev5 (AC-17.2,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-2)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1.1,4.1,4.1.1,8.2.1)","v4.0 (2.2.7,4.1.1,4.2.1.2,4.2.2,8.3.2)"],"s202":["(CC6.1,CC6.7)"]},"ecc-k8s-017":{"Standard1":["v7 (6.2)","v8 (8.2)"],"Standard16":["v1.7.0 (1.2.18)"],"BSA":["v3 (DS-7,LT-3,LT-4)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard2":["19 (DSS03)"],"Standard4":["v4 (LOG-08)"],"DA":["(Article_8.4.e,Article_10.7)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-02,AU-03,AU-12)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (5.28,8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-12,AU-2,AU-3,AU-3.1)"],"Standard9":["v1.1 (DE.AE-3,PR.PT-1)"],"Standard7":["(11)"],"Standard8":["v3.2.1 (10.1,10.2,10.2.5,10.3)"],"s202":["(CC7.2)"]},"ecc-k8s-071":{"Standard1":["v7 (5.2)","v8 (4.8)"],"Standard16":["v1.7.0 (5.2.10)"],"CIS GKE Benchmark":["v1.2.0 (4.2.9)","v1.3.0 (4.2.8)","v1.4.0 (4.2.8)"],"BSA":["v3 (AM-2,AM-5,PV-1,PV-3)"],"CJIS":["(5.7.1.1)"],"CMMC":["v2.0 (CM.L2-3.4.2,CM.L2-3.4.7)"],"CE":["v2.2 (Secure_configuration)"],"Standard10":["(D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CM-06,CM-07)"],"Standard15":["(01.l)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard11":["800-171 Rev2 (3.4.7)","800_53 Rev5 (CM-6,CM-7)"],"Standard8":["v3.2.1 (2.2.2,2.2.5)","v4.0 (2.2.4)"],"s202":["(CC8.1)"]},"ecc-k8s-087":{"Standard1":["v7 (5.1,14)","v8 (3,4.1)"],"Standard16":["v1.7.0 (5.1.2)"],"CIS RedHat OpenShift Container Platform Benchmark":["v1.4.0 (5.1.2)"],"CIS GKE Benchmark":["v1.2.0 (4.1.2)","v1.3.0 (4.1.2)","v1.4.0 (4.1.2)"],"BSA":["v3 (PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]},"ecc-k8s-059":{"Standard1":["v7 (13)","v8 (3,6.8)"],"Standard16":["v1.7.0 (5.1.6)"],"CIS RedHat OpenShift Container Platform Benchmark":["v1.4.0 (5.1.6)"],"CIS GKE Benchmark":["v1.2.0 (4.1.6)","v1.3.0 (4.1.6)","v1.4.0 (4.1.6)"],"BSA":["v3 (IM-7,PA-7)"],"CJIS":["(5.5.1,5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.4,AC.L2-3.1.5,MP.L2-3.8.2,SC.L2-3.13.3)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-04,IAM-05,IAM-16)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.R.St.B.1,D3.PC.Am.B.1,D3.PC.Am.B.2)"],"Standard3":["Standard11_800_53_Rev5 (AC-02,AC-02.07,AC-03,AC-05,AC-06,AC-06.01,AC-06.07)"],"Standard6":["2016_679 (Article_25,Article_5.1.f)"],"Standard5":["(164.308.a.3.i,164.308.a.4.i,164.308.a.4.ii.B,164.312.a.1)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.6.1.2,A.8.1.3,A.9,A.9.1.1,A.9.4.1)","27002_2013 (6.1.2,8.1.3,9,9.1.1,9.4.1)","27002_2022 (5.10,5.15,5.3,8.2,8.3)","27017_2015 (6.1.2,8.1.3,9,9.1.1,9.4.1)","27018_2019 (6.1.2,8,9,9.1,9.4.1)","27701_2019 (6.10.1.3,6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.1.4,3.1.5,3.8.2)","800_53 Rev5 (AC-2,AC-2.7,AC-3,AC-3.7,AC-5,AC-6,AC-6.1,AC-6.7)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)","v4.0 (1.3.1,10.3.1,7.1,7.1.1,7.2.1,7.2.2,7.2.4,7.2.6,7.3.1,7.3.2)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-k8s-023":{"Standard1":["v7 (4.4)","v8 (5.2)"],"Standard16":["v1.7.0 (1.2.24)"],"CJIS":["(5.13.7.2,5.6.2.1.1)"],"Standard4":["v4 (IAM-02)"],"CE":["v2.2 (A4.3,A5.3,A7.2,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01)"],"Standard5":["(164.308.a.5.ii.D)"],"Standard15":["(01.f)"],"Standard14":["27001_2013 (A.9.4.3)","27002_2013 (9.4.3)","27002_2022 (5.17)","27017_2015 (9.4.3)","27018_2019 (9.4.3)","27701_2019 (6.6.4.3)"],"Standard11":["800-171 Rev2 (3.5.7)","800_53 Rev5 (IA-5.1)"],"Standard8":["v3.2.1 (2.1,8.2.3)","v4.0 (2.2.2,8.6.3)"],"s202":["(CC6.1)"]},"ecc-k8s-060":{"Standard1":["v7 (5.1)","v8 (4.1)"],"Standard16":["v1.7.0 (5.2.12)"],"BSA":["v3 (PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]},"ecc-k8s-079":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]},"ecc-k8s-053":{"Standard1":["v7 (2.10)","v8 (3.12,12.2)"],"Standard16":["v1.7.0 (5.7.4)"],"CIS RedHat OpenShift Container Platform Benchmark":["v1.4.0 (5.7.4)"],"CIS GKE Benchmark":["v1.2.0 (4.6.4)","v1.3.0 (4.6.4)","v1.4.0 (4.6.4)"],"BSA":["v3 (GS-2,GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,CM.L2-3.4.1,CM.L2-3.4.6,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-02,SC-07)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard15":["(01.m)"],"Standard14":["27001_2013 (A.13.1.3)","27002_2013 (13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.3)"],"Standard12":["(CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-2,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.1.1,2.2.1,2.2.6,7.1)"],"s202":["(CC6.1,CC6.6)"]},"ecc-k8s-074":{"Standard1":["v8 (3)","v7 (13)"],"Standard16":["v1.7.0 (5.4.1)"],"CIS RedHat OpenShift Container Platform Benchmark":["v1.4.0 (5.4.1)"],"CIS GKE Benchmark":["v1.2.0 (4.4.1)","v1.3.0 (4.4.1)","v1.4.0 (4.4.1)"],"BSA":["v3 (PV-1,IM-8)"],"CMMC":["v2.0 (SC.L2-3.13.2)"],"Standard4":["v4 (DSP-08,IVS-04)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.3.c)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,CM-09,SA-08,SA-10)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1)"],"Standard11":["800_53 Rev5 (CM-2,CM-6,CM-9,SA-10,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"s202":["(CC6.1)"]},"ecc-k8s-033":{"Standard1":["v7 (4.4)","v8 (5.2,6.8)"],"Standard16":["v1.7.0 (1.3.3)"],"BSA":["v3 (IM-7,PA-7)"],"CJIS":["(5.5.1,5.5.2,5.13.7.2,5.6.2.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.4,AC.L2-3.1.5,MP.L2-3.8.2,SC.L2-3.13.3)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-04,IAM-05,IAM-16)"],"CE":["v2.2 (A4.3,A5.3,A7.2,A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D1.R.St.B.1,D3.PC.Am.B.1,D3.PC.Am.B.2)"],"Standard3":["Standard11_800_53_Rev5 (AC-02,AC-02.07,AC-03,AC-05,AC-06,AC-06.01,AC-06.07,IA-05.01)"],"Standard6":["2016_679 (Article_25,Article_5.1.f)"],"Standard5":["(164.308.a.3.i,164.308.a.4.i,164.308.a.4.ii.B,164.312.a.1,164.308.a.5.ii.D)"],"Standard15":["(01.a,01.c,01.f,01.v)"],"Standard14":["27001_2013 (A.6.1.2,A.8.1.3,A.9,A.9.1.1,A.9.4.1,A.9.4.3)","27002_2013 (6.1.2,8.1.3,9,9.1.1,9.4.1,9.4.3)","27002_2022 (5.10,5.15,5.3,8.2,8.3,5.17)","27017_2015 (6.1.2,8.1.3,9,9.1.1,9.4.1,9.4.3)","27018_2019 (6.1.2,8,9,9.1,9.4.1,9.4.3)","27701_2019 (6.10.1.3,6.5.1.3,6.6.1.1,6.6.4.1,6.6.4.3)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.1.4,3.1.5,3.5.7,3.8.2)","800_53 Rev5 (AC-2,AC-2.7,AC-3,AC-3.7,AC-5,AC-6,AC-6.1,AC-6.7,IA-5.1)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3,2.1,8.2.3)","v4.0 (1.3.1,10.3.1,7.1,7.1.1,7.2.1,7.2.2,7.2.4,7.2.6,7.3.1,7.3.2,2.2.2,8.6.3)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-k8s-043":{"Standard1":["v7 (14.4)","v8 (3.11)"],"Standard16":["v1.7.0 (2.4)"],"BSA":["v3 (DP-4,DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.1.1,3.3.2,3.3.3,3.5.1.2,3.5.1.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-k8s-027":{"Standard1":["v7 (14.8)","v8 (3.11)"],"Standard16":["v1.7.0 (1.2.28)"],"BSA":["v3 (DP-4,DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (SC-28)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.1.1,3.3.2,3.3.3,3.5.1.2,3.5.1.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-k8s-047":{"Standard1":["v7 (14)","v8 (6.8)"],"Standard16":["v1.7.0 (5.1.3)"],"CIS RedHat OpenShift Container Platform Benchmark":["v1.4.0 (5.1.3)"],"CIS GKE Benchmark":["v1.2.0 (4.1.3)","v1.3.0 (4.1.3)","v1.4.0 (4.1.3)"],"BSA":["v3 (IM-7,PA-7)"],"CJIS":["(5.5.1,5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.4,AC.L2-3.1.5,MP.L2-3.8.2,SC.L2-3.13.3)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-04,IAM-05,IAM-16)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.4.a,Article_14.d)"],"Standard10":["(D1.R.St.B.1,D3.PC.Am.B.1,D3.PC.Am.B.2)"],"Standard3":["Standard11_800_53_Rev5 (AC-02,AC-02.07,AC-03,AC-05,AC-06,AC-06.01,AC-06.07)"],"Standard6":["2016_679 (Article_25,Article_5.1.f)"],"Standard5":["(164.308.a.3.i,164.308.a.4.i,164.308.a.4.ii.B,164.312.a.1)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.6.1.2,A.8.1.3,A.9,A.9.1.1,A.9.4.1)","27002_2013 (6.1.2,8.1.3,9,9.1.1,9.4.1)","27002_2022 (5.10,5.15,5.3,8.2,8.3)","27017_2015 (6.1.2,8.1.3,9,9.1.1,9.4.1)","27018_2019 (6.1.2,8,9,9.1,9.4.1)","27701_2019 (6.10.1.3,6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.1.4,3.1.5,3.8.2)","800_53 Rev5 (AC-2,AC-2.7,AC-3,AC-3.7,AC-5,AC-6,AC-6.1,AC-6.7)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)","v4.0 (1.3.1,10.3.1,7.1,7.1.1,7.2.1,7.2.2,7.2.4,7.2.6,7.3.1,7.3.2)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-k8s-041":{"Standard1":["v7 (14.8)","v8 (3.11)"],"Standard16":["v1.7.0 (2.2)"],"BSA":["v3 (DP-4,DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.1.1,3.3.2,3.3.3,3.5.1.2,3.5.1.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-k8s-075":{"Standard1":["v8 (12.2)","v7 (2.10)"],"Standard16":["v1.7.0 (5.7.4)"],"CIS RedHat OpenShift Container Platform Benchmark":["v1.4.0 (5.7.4)"],"CIS GKE Benchmark":["v1.2.0 (4.6.4)","v1.3.0 (4.6.4)","v1.4.0 (4.6.4)"],"BSA":["v3 (GS-2,GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,CM.L2-3.4.1,CM.L2-3.4.6,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-02,SC-07)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard15":["(01.m)"],"Standard14":["27001_2013 (A.13.1.3)","27002_2013 (13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.3)"],"Standard12":["(CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-2,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.1.1,2.2.1,2.2.6,7.1)"],"s202":["(CC6.1,CC6.6)"]},"ecc-k8s-014":{"Standard1":["v7 (5.1)","v8 (4.1)"],"Standard16":["v1.7.0 (1.2.14)"],"BSA":["v3 (DS-2,GS-10,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.2,CM.L2-3.4.7)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,SA-08)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-2,CM-6,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.1.1,2.2.1,2.2.6,6.2.1)"],"s202":["(CC8.1)"]},"ecc-k8s-080":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]},"ecc-k8s-037":{"Standard1":["v7 (9.2)","v8 (12.2,12.6)"],"Standard16":["v1.7.0 (1.3.7)"],"BSA":["v3 (GS-4)"],"CJIS":["(5.10.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.2,SC.L2-3.13.6)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.2)","27002_2013 (13.1.1,13.1.2)","27002_2022 (8.20,8.21)","27017_2015 (13.1.1,13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.2)"],"Standard11":["800-171 Rev2 (3.4.6,3.13.1)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.2,2.2.2)"],"s202":["(CC6.1)"]},"ecc-k8s-028":{"Standard1":["v7 (14.8)","v8 (3.11)"],"Standard16":["v1.7.0 (1.2.29)"],"BSA":["v3 (DP-4,DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01,SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (IA-5.1,SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.1.1,3.3.2,3.3.3,3.5.1.2,3.5.1.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-k8s-069":{"Standard1":["v7 (5.1)","v8 (4.2)"],"BSA":["v3 (GS-10,PV-1)"],"CJIS":["(5.10.1)"],"CMMC":["v2.0 (CM.L2-3.4.2)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,SA-10,SI-04)"],"Standard6":["2016_679 (Article_32.1.b,Article_32.1.c)"],"Standard15":["(09.ab)"],"Standard14":["27001_2013 (A.12.1.3,A.14.2.5)","27002_2013 (12.1.3,14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (12.1.3,14.2.5)","27018_2019 (12.1.3,14)","27701_2019 (6.9.1.3,6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (CM-2,CM-6,SA-10,SI-4)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(A1.1,CC7.2,CC5.2)"]},"ecc-k8s-054":{"Standard1":["v7 (12.4,9.2)","v8 (12.2)"],"Standard16":["v1.7.0 (5.2.13)"],"BSA":["v3 (GS-4)"],"CJIS":["(5.10.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.2,SC.L2-3.13.6)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.2)","27002_2013 (13.1.1,13.1.2)","27002_2022 (8.20,8.21)","27017_2015 (13.1.1,13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.2)"],"Standard11":["800-171 Rev2 (3.4.6,3.13.1)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.2,2.2.2)"],"s202":["(CC6.1)"]},"ecc-k8s-088":{"Standard1":["v7 (5.1,14)","v8 (3,4.1)"],"Standard16":["v1.7.0 (5.1.2)"],"CIS RedHat OpenShift Container Platform Benchmark":["v1.4.0 (5.1.2)"],"CIS GKE Benchmark":["v1.2.0 (4.1.2)","v1.3.0 (4.1.2)","v1.4.0 (4.1.2)"],"BSA":["v3 (PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]},"ecc-k8s-006":{"Standard1":["v7 (14.6,9.2)","v8 (3.3,6.8)"],"Standard16":["v1.7.0 (1.2.6)"],"BSA":["v3 (AM-4,IM-7,PA-1,PA-7)"],"CJIS":["(5.5.1,5.5.2,5.5.2.1,5.5.2.2,5.5.2.3,5.5.2.4)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.4,AC.L2-3.1.5,MP.L2-3.8.2,SC.L2-3.13.3)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-04,IAM-05,IAM-16)"],"CE":["v2.2 (A7.4,A7.8,A7.9,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.b,Article_14.d,Article_8.4.d)"],"Standard10":["(D1.R.St.B.1,D3.PC.Am.B.1,D3.PC.Am.B.2,D3.PC.Am.B.4)"],"Standard3":["Standard11_800_53_Rev5 (AC-02,AC-02.07,AC-03,AC-05,AC-06,AC-06.01,AC-06.07,AU-9.4,MP-02)"],"Standard6":["2016_679 (Article_25,Article_5.1.f)"],"Standard5":["(164.308.a.3.i,164.308.a.4.i,164.308.a.4.ii.B,164.312.a.1)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.6.1.2,A.8.1.3,A.9,A.9.1.1,A.9.4.1)","27002_2013 (6.1.2,8.1.3,9,9.1.1,9.4.1)","27002_2022 (5.10,5.15,5.3,8.2,8.3)","27017_2015 (6.1.2,8.1.3,9,9.1.1,9.4.1)","27018_2019 (6.1.2,8,9,9.1,9.4.1)","27701_2019 (6.10.1.3,6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-004-6_Requirement_R4_Part_4.3,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.1.4,3.1.5,3.8.2)","800_53 Rev5 (AC-2,AC-2.7,AC-3,AC-3.7,AC-5,AC-6,AC-6.1,AC-6.7,AU-9.4,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)","v4.0 (1.3.1,10.3.1,7.1,7.1.1,7.2.1,7.2.2,7.2.4,7.2.6,7.3.1,7.3.2)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-k8s-003":{"Standard1":["v8 (12.2,4.6)"],"BSA":["v3 (GS-4,NS-2)"],"CJIS":["(5.7.1.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03,BAI09)"],"Standard4":["v4 (DSP-07)"],"CE":["v2.2 (A4.8,Firewalls,Secure_configuration)"],"DA":["(Article_8.4.b,Article_8.4.e)"],"Standard10":["(D3.PC.Im.B.1,D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,MA-04,SA-08)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard15":["(01.i,01.l,01.m,01.n,07.c)"],"Standard14":["27001_2013 (A.13.1.1,A.14.2.5)","27002_2013 (13.1.1,14.2.5)","27002_2022 (8.20,8.27)","27017_2015 (13.1.1,14.2.5)","27018_2019 (13.1,14)","27701_2019 (6.10.1.1,6.11.2.5)"],"Standard12":["(CIP-005-7_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8)"],"Standard9":["v1.1 (PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (2.2.2,2.2.5)","v4.0 (1.2.6,1.4.4)"],"s202":["(CC5.2,CC6.1,CC6.6)"]},"ecc-k8s-042":{"Standard1":["v7 (14.4)","v8 (3.11)"],"Standard16":["v1.7.0 (2.3)"],"BSA":["v3 (DP-4,DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.1.1,3.3.2,3.3.3,3.5.1.2,3.5.1.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-k8s-077":{"Standard1":["v7 (14.6)","v8 (6.8)"],"BSA":["v3 (AM-4,PA-7)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L2-3.1.5)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4)"],"DA":["(Article_8.3.b)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c)"],"Standard14":["27001_2013 (A.9.1.1,A.9.4.1)","27002_2013 (9.1.1,9.4.1)","27002_2022 (5.15,8.3)","27017_2015 (9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)"],"s202":["(CC5.2,CC6.3)"]},"ecc-k8s-040":{"Standard1":["v7 (14.4)","v8 (3.11)"],"Standard16":["v1.7.0 (2.1)"],"BSA":["v3 (DP-4,DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.1.1,3.3.2,3.3.3,3.5.1.2,3.5.1.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-k8s-021":{"Standard1":["v7 (5.1)","v8 (4.1)"],"Standard16":["v1.7.0 (1.2.22)"],"BSA":["v3 (PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]},"ecc-k8s-068":{"Standard1":["v7 (18.1)","v8 (16.7)"],"BSA":["v3 (GS-10,PV-1)"],"CJIS":["(5.10.1)"],"CMMC":["v2.0 (CM.L2-3.4.2)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,SA-10,SI-04)"],"Standard6":["2016_679 (Article_32.1.b,Article_32.1.c)"],"Standard15":["(09.ab)"],"Standard14":["27001_2013 (A.12.1.3,A.14.2.5)","27002_2013 (12.1.3,14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (12.1.3,14.2.5)","27018_2019 (12.1.3,14)","27701_2019 (6.9.1.3,6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.2)","800_53 Rev5 (CM-2,CM-6,SA-10,SI-4)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)"],"s202":["(A1.1,CC7.2,CC5.2)"]},"ecc-k8s-010":{"Standard1":["v7 (14.6)","v8 (3.3)"],"Standard16":["v1.7.0 (1.2.10)"],"BSA":["v3 (AM-4,IM-7,PA-1,PA-7)"],"CJIS":["(5.5.1,5.5.2,5.5.2.1,5.5.2.2,5.5.2.3,5.5.2.4)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.4,AC.L2-3.1.5,MP.L2-3.8.2,SC.L2-3.13.3)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-04,IAM-05,IAM-16)"],"CE":["v2.2 (A7.4,A7.8,A7.9,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.b,Article_14.d)"],"Standard10":["(D1.R.St.B.1,D3.PC.Am.B.1,D3.PC.Am.B.2,D3.PC.Am.B.4)"],"Standard3":["Standard11_800_53_Rev5 (AC-02,AC-02.07,AC-03,AC-05,AC-06,AC-06.01,AC-06.07,AU-9.4,MP-02)"],"Standard6":["2016_679 (Article_25,Article_5.1.f)"],"Standard5":["(164.308.a.3.i,164.308.a.4.i,164.308.a.4.ii.B,164.312.a.1)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.6.1.2,A.8.1.3,A.9,A.9.1.1,A.9.4.1)","27002_2013 (6.1.2,8.1.3,9,9.1.1,9.4.1)","27002_2022 (5.10,5.15,5.3,8.2,8.3)","27017_2015 (6.1.2,8.1.3,9,9.1.1,9.4.1)","27018_2019 (6.1.2,8,9,9.1,9.4.1)","27701_2019 (6.10.1.3,6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-004-6_Requirement_R4_Part_4.3,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.1.4,3.1.5,3.8.2)","800_53 Rev5 (AC-2,AC-2.7,AC-3,AC-3.7,AC-5,AC-6,AC-6.1,AC-6.7,AU-9.4,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)","v4.0 (1.3.1,10.3.1,7.1,7.1.1,7.2.1,7.2.2,7.2.4,7.2.6,7.3.1,7.3.2)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-k8s-051":{"Standard1":["v7 (2.10)","v8 (3.12,12.2)"],"Standard16":["v1.7.0 (5.7.4)"],"CIS RedHat OpenShift Container Platform Benchmark":["v1.4.0 (5.7.4)"],"CIS GKE Benchmark":["v1.2.0 (4.6.4)","v1.3.0 (4.6.4)","v1.4.0 (4.6.4)"],"BSA":["v3 (GS-2,GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,CM.L2-3.4.1,CM.L2-3.4.6,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-02,SC-07)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard15":["(01.m)"],"Standard14":["27001_2013 (A.13.1.3)","27002_2013 (13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.3)"],"Standard12":["(CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-2,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.1.1,2.2.1,2.2.6,7.1)"],"s202":["(CC6.1,CC6.6)"]},"ecc-k8s-063":{"Standard1":["v7 (4)","v8 (5.4)"],"Standard16":["v1.7.0 (5.2.6)"],"CIS GKE Benchmark":["v1.2.0 (4.2.5)","v1.3.0 (4.2.5)","v1.4.0 (4.2.5)"],"BSA":["v3 (IM-2,PA-1)"],"CJIS":["(5.5.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.5,AC.L2-3.1.6,AC.L2-3.1.7,SC.L2-3.13.3)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-09)"],"CE":["v2.2 (A7.5,A7.6,A7.7,uac)"],"DA":["(Article_8.4)"],"Standard10":["(D3.PC.Am.B.3)"],"Standard3":["Standard11_800_53_Rev5 (AC-02.07,AC-06.02,AC-06.05)"],"Standard6":["2016_679 (Article_25)"],"Standard15":["(01.e)"],"Standard14":["27001_2013 (A.9.2.3)","27002_2013 (9.2.3)","27002_2022 (5.15,8.2)","27017_2015 (9.2.3)","27018_2019 (9.2.3)","27701_2019 (6.6.2.3)"],"Standard11":["800-171 Rev2 (3.1.6,3.1.7)","800_53 Rev5 (AC-2.7,AC-6.2,AC-6.5)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)"],"s202":["(CC6.3)"]},"ecc-k8s-019":{"Standard1":["v7 (6)","v8 (8.10)"],"Standard16":["v1.7.0 (1.2.20)"],"BSA":["v3 (LT-6)"],"CJIS":["(5.4.6,5.4.7)"],"Standard4":["v4 (LOG-02)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-11)"],"Standard6":["2016_679 (Article_5.1.e)"],"Standard15":["(09.ac)"],"Standard14":["27001_2013 (A.12.4.2,A.18.1.3)","27002_2013 (12.4.2,18.1.3)","27002_2022 (5.28)","27017_2015 (12.4.2,18.1.3)","27018_2019 (12.4.2,18.1)","27701_2019 (6.15.1.3,6.9.4.2)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.3)"],"Standard11":["800_53 Rev5 (AU-11)"],"Standard8":["v3.2.1 (10.7)","v4.0 (10.5.1)"],"s202":["(C1.1)"]},"ecc-k8s-022":{"Standard1":["v7 (5.1)","v8 (4.1)"],"Standard16":["v1.7.0 (1.2.23)"],"BSA":["v3 (DS-2,GS-10,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.2,CM.L2-3.4.7)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,SA-08)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-2,CM-6,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.1.1,2.2.1,2.2.6,6.2.1)"],"s202":["(CC8.1)"]},"ecc-k8s-025":{"Standard1":["v7 (14.4)","v8 (3.10)"],"Standard16":["v1.7.0 (1.2.26)"],"BSA":["v3 (DP-3)"],"CJIS":["(5.10.1.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.13,AC.L2-3.1.17,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IVS-03)"],"DA":["(Article_8.2,Article_8.3.a,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12,D3.PC.Am.B.13)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,SC-08)"],"Standard6":["2016_679 (Article_32.1.a,Article_44)"],"Standard5":["(164.312.e.1)"],"Standard15":["(06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1,A.18.1.3)","27002_2013 (10.1.1,13.2.1,18.1.3)","27002_2022 (5.14,5.33,8.24)","27017_2015 (10.1.1,13.2.1,18.1.3)","27018_2019 (10.1.1,13.2.1,18.1.3)","27701_2019 (6.10.2.1,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.8,3.5.10)","800_53 Rev5 (AC-17.2,SC-8,SC-8.1)"],"Standard9":["v1.1 (PR.DS-2)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1.1,4.1,4.1.1,8.2.1)","v4.0 (2.2.7,4.1.1,4.2.1.2,4.2.2,8.3.2)"],"s202":["(CC6.1,CC6.7)"]},"ecc-k8s-058":{"Standard1":["v7 (13)","v8 (3,6.8)"],"Standard16":["v1.7.0 (5.1.6)"],"CIS RedHat OpenShift Container Platform Benchmark":["v1.4.0 (5.1.6)"],"CIS GKE Benchmark":["v1.2.0 (4.1.6)","v1.3.0 (4.1.6)","v1.4.0 (4.1.6)"],"BSA":["v3 (IM-7,PA-7)"],"CJIS":["(5.5.1,5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.4,AC.L2-3.1.5,MP.L2-3.8.2,SC.L2-3.13.3)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-04,IAM-05,IAM-16)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.R.St.B.1,D3.PC.Am.B.1,D3.PC.Am.B.2)"],"Standard3":["Standard11_800_53_Rev5 (AC-02,AC-02.07,AC-03,AC-05,AC-06,AC-06.01,AC-06.07)"],"Standard6":["2016_679 (Article_25,Article_5.1.f)"],"Standard5":["(164.308.a.3.i,164.308.a.4.i,164.308.a.4.ii.B,164.312.a.1)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.6.1.2,A.8.1.3,A.9,A.9.1.1,A.9.4.1)","27002_2013 (6.1.2,8.1.3,9,9.1.1,9.4.1)","27002_2022 (5.10,5.15,5.3,8.2,8.3)","27017_2015 (6.1.2,8.1.3,9,9.1.1,9.4.1)","27018_2019 (6.1.2,8,9,9.1,9.4.1)","27701_2019 (6.10.1.3,6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.1.4,3.1.5,3.8.2)","800_53 Rev5 (AC-2,AC-2.7,AC-3,AC-3.7,AC-5,AC-6,AC-6.1,AC-6.7)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)","v4.0 (1.3.1,10.3.1,7.1,7.1.1,7.2.1,7.2.2,7.2.4,7.2.6,7.3.1,7.3.2)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-k8s-001":{"Standard1":["v7 (14.6)","v8 (3.3)"],"Standard16":["v1.7.0 (1.2.1)"],"BSA":["v3 (AM-4,IM-7,PA-7)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.4.d,Article_14.d)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.3)","27017_2015 (8.1.3,9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1.1,7.1.2,7.1.3)","v4.0 (1.3.1,7.1)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-k8s-065":{"Standard1":["v7 (5.1)","v8 (4.1)"],"Standard16":["v1.7.0 (5.2.3)"],"CIS GKE Benchmark":["v1.2.0 (4.2.2)","v1.3.0 (4.2.2)","v1.4.0 (4.2.2)"],"BSA":["v3 (PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]},"ecc-k8s-039":{"Standard1":["v7 (9.2)","v8 (12.2,12.6)"],"Standard16":["v1.7.0 (1.4.2)"],"BSA":["v3 (GS-4)"],"CJIS":["(5.10.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.6,CM.L2-3.4.7,SC.L2-3.13.2,SC.L2-3.13.6)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D3.PC.Im.B.6)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-07)"],"Standard6":["2016_679 (Article_32.1.b,Article_5.1.f)"],"Standard14":["27001_2013 (A.13.1.1,A.13.1.2)","27002_2013 (13.1.1,13.1.2)","27002_2022 (8.20,8.21)","27017_2015 (13.1.1,13.1.2)","27018_2019 (13.1)","27701_2019 (6.10.1.1,6.10.1.2)"],"Standard11":["800-171 Rev2 (3.4.6,3.13.1)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-7)"],"Standard9":["v1.1 (PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (1.2,2.2.2)"],"s202":["(CC6.1)"]},"ecc-k8s-036":{"Standard1":["v7 (14.4)","v8 (3.10)"],"Standard16":["v1.7.0 (1.3.6)"],"BSA":["v3 (DP-3,PV-1)"],"CJIS":["(5.10.1.2.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2,AC.L2-3.1.13,AC.L2-3.1.17,SC.L2-3.13.11,SC.L2-3.13.15,SC.L2-3.13.8)"],"Standard2":["19 (BAI06,DSS05)"],"Standard4":["v4 (CEK-03,DSP-10,DSP-17,IVS-03)"],"DA":["(Article_8.2,Article_8.3.a,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12,D3.PC.Am.B.13)"],"Standard3":["Standard11_800_53_Rev5 (AC-17.02,SC-08,CM-02,CM-06,SA-08)"],"Standard6":["2016_679 (Article_32.1.a,Article_44)"],"Standard5":["(164.312.e.1)"],"Standard15":["(06.c,06.d,09.v,09.y)"],"Standard14":["27001_2013 (A.10.1.1,A.13.2.1,A.18.1.3)","27002_2013 (10.1.1,13.2.1,18.1.3)","27002_2022 (5.14,5.33,8.24)","27017_2015 (10.1.1,13.2.1,18.1.3)","27018_2019 (10.1.1,13.2.1,18.1.3)","27701_2019 (6.10.2.1,6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-005-7_Requirement_R2_Part_2.2,CIP-011-3_Requirement_R1_Part_1.2,CIP-012-1_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.1.13,3.13.8,3.5.10,3.4.2)","800_53 Rev5 (AC-17.2,SC-8,SC-8.1,CM-2,CM-6,SA-8)"],"Standard9":["v1.1 (PR.IP-1,PR.DS-2)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (2.1.1,4.1,4.1.1,8.2.1,2.2)","v4.0 (2.2.7,4.1.1,4.2.1.2,4.2.2,8.3.2)"],"s202":["(CC6.1,CC6.7,CC8.1)"]},"ecc-k8s-048":{"Standard1":["v7 (14)","v8 (6.8)"],"Standard16":["v1.7.0 (5.1.3)"],"CIS RedHat OpenShift Container Platform Benchmark":["v1.4.0 (5.1.3)"],"CIS GKE Benchmark":["v1.2.0 (4.1.3)","v1.3.0 (4.1.3)","v1.4.0 (4.1.3)"],"BSA":["v3 (IM-7,PA-7)"],"CJIS":["(5.5.1,5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.4,AC.L2-3.1.5,MP.L2-3.8.2,SC.L2-3.13.3)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-04,IAM-05,IAM-16)"],"CE":["v2.2 (A7.4,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.b,Article_8.4.a,Article_14.d)"],"Standard10":["(D1.R.St.B.1,D3.PC.Am.B.1,D3.PC.Am.B.2)"],"Standard3":["Standard11_800_53_Rev5 (AC-02,AC-02.07,AC-03,AC-05,AC-06,AC-06.01,AC-06.07)"],"Standard6":["2016_679 (Article_25,Article_5.1.f)"],"Standard5":["(164.308.a.3.i,164.308.a.4.i,164.308.a.4.ii.B,164.312.a.1)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.6.1.2,A.8.1.3,A.9,A.9.1.1,A.9.4.1)","27002_2013 (6.1.2,8.1.3,9,9.1.1,9.4.1)","27002_2022 (5.10,5.15,5.3,8.2,8.3)","27017_2015 (6.1.2,8.1.3,9,9.1.1,9.4.1)","27018_2019 (6.1.2,8,9,9.1,9.4.1)","27701_2019 (6.10.1.3,6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.1.4,3.1.5,3.8.2)","800_53 Rev5 (AC-2,AC-2.7,AC-3,AC-3.7,AC-5,AC-6,AC-6.1,AC-6.7)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)","v4.0 (1.3.1,10.3.1,7.1,7.1.1,7.2.1,7.2.2,7.2.4,7.2.6,7.3.1,7.3.2)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-k8s-015":{"Standard1":["v7 (5.1,9)","v8 (4.1,16.11)"],"Standard16":["v1.7.0 (1.2.15)"],"BSA":["v3 (DS-2,GS-10,PV-1,PV-3)"],"CMMC":["v2.0 (CM.L2-3.4.2,CM.L2-3.4.7)"],"Standard2":["19 (BAI10)"],"Standard4":["v4 (IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"Standard10":["(D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-02,CM-06,SA-08)"],"DA":["(Article_8.4.b)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-2,CM-6,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.1.1,2.2.1,2.2.6,6.2.1)"],"s202":["(CC8.1)"]},"ecc-k8s-057":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]},"ecc-k8s-086":{"Standard1":["v7 (5.1)","v8 (4.1)"],"Standard16":["v1.7.0 (5.7.3)"],"CIS GKE Benchmark":["v1.2.0 (4.6.3)","v1.3.0 (4.6.3)","v1.4.0 (4.6.3)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard4":["v4 (CCC-01)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32)"],"Standard15":["(08.k,10.k)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]},"ecc-k8s-020":{"Standard1":["v7 (6)","v8 (8.1)"],"Standard16":["v1.7.0 (1.2.21)"],"BSA":["v3 (GS-7)"],"CJIS":["(5.4.1)"],"CMMC":["v2.0 (AU.L2-3.3.1)"],"Standard4":["v4 (A&A-01,GRC-03,LOG-01,LOG-07)"],"Standard10":["(D1.RM.Au.B.3)"],"Standard3":["Standard11_800_53_Rev5 (AU-01,AU-02)"],"DA":["(Article_8.4.e,Article_10.7)"],"Standard6":["2016_679 (Article_30)"],"Standard5":["(164.312.b)"],"Standard15":["(09.aa)"],"Standard14":["27001_2013 (A.12.4.1)","27002_2013 (12.4.1)","27002_2022 (8.15)","27017_2015 (12.4.1)","27018_2019 (12.4.1)","27701_2019 (6.9.4.1)"],"Standard11":["800-171 Rev2 (3.3.1)","800_53 Rev5 (AU-1,AU-2)"],"Standard7":["(11)"],"Standard8":["v4.0 (10.1)"],"s202":["(CC7.2)"]},"ecc-k8s-078":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]},"ecc-k8s-034":{"Standard1":["v7 (4.4)","v8 (5.2)"],"Standard16":["v1.7.0 (1.3.4)"],"CJIS":["(5.13.7.2,5.6.2.1.1)"],"Standard4":["v4 (IAM-02)"],"CE":["v2.2 (A4.3,A5.3,A7.2,Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard3":["Standard11_800_53_Rev5 (IA-05.01)"],"Standard5":["(164.308.a.5.ii.D)"],"Standard15":["(01.f)"],"Standard14":["27001_2013 (A.9.4.3)","27002_2013 (9.4.3)","27002_2022 (5.17)","27017_2015 (9.4.3)","27018_2019 (9.4.3)","27701_2019 (6.6.4.3)"],"Standard11":["800-171 Rev2 (3.5.7)","800_53 Rev5 (IA-5.1)"],"Standard8":["v3.2.1 (2.1,8.2.3)","v4.0 (2.2.2,8.6.3)"],"s202":["(CC6.1)"]},"ecc-k8s-056":{"Standard1":["v7 (4)","v8 (5.4)"],"Standard16":["v1.7.0 (5.2.2)"],"BSA":["v3 (IM-2,PA-1)"],"CJIS":["(5.5.2.1)"],"CMMC":["v2.0 (AC.L2-3.1.5,AC.L2-3.1.6,AC.L2-3.1.7,SC.L2-3.13.3)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-09)"],"CE":["v2.2 (A7.5,A7.6,A7.7,uac)"],"DA":["(Article_8.4)"],"Standard10":["(D3.PC.Am.B.3)"],"Standard3":["Standard11_800_53_Rev5 (AC-02.07,AC-06.02,AC-06.05)"],"Standard6":["2016_679 (Article_25)"],"Standard15":["(01.e)"],"Standard14":["27001_2013 (A.9.2.3)","27002_2013 (9.2.3)","27002_2022 (5.15,8.2)","27017_2015 (9.2.3)","27018_2019 (9.2.3)","27701_2019 (6.6.2.3)"],"Standard11":["800-171 Rev2 (3.1.6,3.1.7)","800_53 Rev5 (AC-2.7,AC-6.2,AC-6.5)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)"],"s202":["(CC6.3)"]},"ecc-k8s-045":{"Standard1":["v7 (14.4)","v8 (3.11)"],"Standard16":["v1.7.0 (2.6)"],"BSA":["v3 (DP-4,DP-5)"],"CJIS":["(5.10.1.2.2)"],"CMMC":["v2.0 (MP.L2-3.8.1,SC.L2-3.13.11,SC.L2-3.13.16)"],"Standard2":["19 (DSS05,DSS06)"],"Standard4":["v4 (CEK-03)"],"DA":["(Article_8.2,Article_8.4.d)"],"Standard10":["(D3.PC.Am.B.12)"],"Standard3":["Standard11_800_53_Rev5 (SC-28)"],"Standard6":["2016_679 (Article_32.1.a)"],"Standard5":["(164.312.a.2.iv)"],"Standard15":["(06.c,06.d)"],"Standard14":["27001_2013 (A.10.1.1,A.18.1.3)","27002_2013 (10.1.1,18.1.3)","27002_2022 (5.33,8.24)","27017_2015 (10.1.1,18.1.3)","27018_2019 (10.1.1,18.1.3)","27701_2019 (6.15.1.3,6.7.1.1)"],"Standard12":["(CIP-011-3_Requirement_R1_Part_1.2)"],"Standard11":["800-171 Rev2 (3.13.16,3.8.1)","800_53 Rev5 (SC-28,SC-28.1)"],"Standard9":["v1.1 (PR.DS-1)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (3.4,3.4.1,8.2.1)","v4.0 (3.1.1,3.3.2,3.3.3,3.5.1.2,3.5.1.3,8.3.2)"],"s202":["(CC6.1)"]},"ecc-k8s-076":{"Standard1":["v7 (5.1)","v8 (4.1)"],"BSA":["v3 (PV-1,PV-3)"],"CJIS":["(5.7.1)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,CM-09,SA-03,SA-08,SA-10)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1,CIP-010-4_Requirement_R1_Part_1.1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,CM-9,SA-10,SA-3,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]},"ecc-k8s-004":{"Standard1":["v7 (14.6,9)","v8 (16.11)"],"Standard16":["v1.7.0 (1.2.4)"],"BSA":["v3 (IM-4)"],"Standard4":["v4 (IAM-14,CEK-10)"],"CMMC":["v2.0 (AC.L1-3.1.1,IA.L1-3.5.2)"],"DA":["(Article_8.4.d)"],"Standard14":["27001_2013 (A.9.4.2)","27002_2013 (9.4.2)","27002_2022 (8.5)","27017_2015 (9.4.2)","27018_2019 (9.4.2)","27701_2019 (6.6.4.2)"],"Standard11":["800-171 Rev2 (3.5.2)","800_53 Rev5 (IA-9)"],"Standard9":["v1.1 (PR.AC-7,PR.DS-1)"],"s202":["(CC6.1)"],"Standard8":["v4.0 (6.2.1)"]},"ecc-k8s-082":{"Standard1":["v7 (14.6)","v8 (6.8)"],"BSA":["v3 (AM-4,PA-7)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L2-3.1.5)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (A7.4)"],"DA":["(Article_8.3.b)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c)"],"Standard14":["27001_2013 (A.9.1.1,A.9.4.1)","27002_2013 (9.1.1,9.4.1)","27002_2022 (5.15,8.3)","27017_2015 (9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1,7.1.1,7.1.2,7.1.3)"],"s202":["(CC5.2,CC6.3)"]},"ecc-k8s-018":{"Standard1":["v7 (6)","v8 (8.10)"],"Standard16":["v1.7.0 (1.2.19)"],"BSA":["v3 (LT-6)"],"CJIS":["(5.4.6,5.4.7)"],"Standard4":["v4 (LOG-02)"],"Standard10":["(D2.MA.Ma.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AU-11)"],"Standard6":["2016_679 (Article_5.1.e)"],"Standard15":["(09.ac)"],"Standard14":["27001_2013 (A.12.4.2,A.18.1.3)","27002_2013 (12.4.2,18.1.3)","27002_2022 (5.28)","27017_2015 (12.4.2,18.1.3)","27018_2019 (12.4.2,18.1)","27701_2019 (6.15.1.3,6.9.4.2)"],"Standard12":["(CIP-007-6_Requirement_R4_Part_4.3)"],"Standard11":["800_53 Rev5 (AU-11)"],"Standard8":["v3.2.1 (10.7)","v4.0 (10.5.1)"],"s202":["(C1.1)"]},"ecc-k8s-011":{"Standard1":["v7 (14.6)","v8 (3.3)"],"Standard16":["v1.7.0 (1.2.11)"],"BSA":["v3 (AM-4,IM-7,PA-7)"],"CJIS":["(5.5.2)"],"CMMC":["v2.0 (AC.L1-3.1.1,AC.L1-3.1.2,AC.L2-3.1.5,MP.L2-3.8.2)"],"Standard2":["19 (DSS05)"],"Standard4":["v4 (IAM-05)"],"CE":["v2.2 (Secure_configuration,uac)"],"DA":["(Article_8.2,Article_8.3.b,Article_14.d,Article_8.4.b)"],"Standard10":["(D3.PC.Am.B.1)"],"Standard3":["Standard11_800_53_Rev5 (AC-03,AC-06,MP-02)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard5":["(164.308.a.4.i,164.308.a.4.ii.B)"],"Standard15":["(01.a,01.c,01.v)"],"Standard14":["27001_2013 (A.8.1.3,A.9.1.1,A.9.4.1)","27002_2013 (8.1.3,9.1.1,9.4.1)","27002_2022 (5.10,5.15,8.3)","27017_2015 (8.1.3,9.1.1,9.4.1)","27018_2019 (8,9.1,9.4.1)","27701_2019 (6.5.1.3,6.6.1.1,6.6.4.1)"],"Standard12":["(CIP-004-6_Requirement_R4_Part_4.1)"],"Standard11":["800-171 Rev2 (3.1.1,3.1.2,3.8.2)","800_53 Rev5 (AC-3,AC-6,MP-2)"],"Standard9":["v1.1 (PR.AC-4)"],"Standard7":["(6)"],"Standard8":["v3.2.1 (7.1.1,7.1.2,7.1.3)","v4.0 (1.3.1,7.1)"],"s202":["(CC5.2,CC6.1,CC6.3)"]},"ecc-k8s-050":{"Standard1":["v7 (2.10)","v8 (3.12,12.2)"],"Standard16":["v1.7.0 (5.7.4)"],"CIS RedHat OpenShift Container Platform Benchmark":["v1.4.0 (5.7.4)"],"CIS GKE Benchmark":["v1.2.0 (4.6.4)","v1.3.0 (4.6.4)","v1.4.0 (4.6.4)"],"BSA":["v3 (GS-2,GS-4,NS-2)"],"CJIS":["(5.10.1,5.7.1.1)"],"CMMC":["v2.0 (AC.L1-3.1.2,CM.L2-3.4.1,CM.L2-3.4.6,SC.L2-3.13.2)"],"Standard2":["19 (APO01,APO03)"],"Standard4":["v4 (DSP-07,IVS-03)"],"CE":["v2.2 (Secure_configuration)"],"Standard10":["(D3.PC.Im.B.1)"],"Standard3":["Standard11_800_53_Rev5 (CM-07,PL-08,SA-08,SC-02,SC-07)"],"DA":["(Article_8.4.b)"],"Standard6":["2016_679 (Article_5.1.f)"],"Standard15":["(01.m)"],"Standard14":["27001_2013 (A.13.1.3)","27002_2013 (13.1.3)","27002_2022 (8.20,8.22,8.27)","27017_2015 (13.1.3)","27018_2019 (13.1)","27701_2019 (6.10.1.3)"],"Standard12":["(CIP-005-7_Requirement_R1_Part_1.1,CIP-005-7_Requirement_R1_Part_1.3)"],"Standard11":["800-171 Rev2 (3.1.5,3.13.2,3.4.6)","800_53 Rev5 (CM-7,PL-8,PM-7,SA-8,SC-2,SC-7)"],"Standard9":["v1.1 (PR.AC-5,PR.IP-1,PR.PT-3)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.1.1,2.2.1,2.2.6,7.1)"],"s202":["(CC6.1,CC6.6)"]},"ecc-k8s-009":{"Standard1":["v7 (8.3)","v8 (16.11)"],"Standard16":["v1.7.0 (1.2.9)"],"BSA":["v3 (PV-1,PV-3,IVS-2)"],"CMMC":["v2.0 (CM.L2-3.4.1,CM.L2-3.4.2)"],"Standard2":["19 (APO13,BAI10)"],"Standard4":["v4 (CCC-01,IVS-04)"],"CE":["v2.2 (Secure_configuration)"],"DA":["(Article_8.4.b)"],"Standard10":["(D1.G.SP.B.2,D3.PC.Im.B.5)"],"Standard3":["Standard11_800_53_Rev5 (CM-01,CM-02,CM-06,SA-08,SA-10)"],"Standard6":["2016_679 (Article_32.1.b)"],"Standard14":["27001_2013 (A.14.2.5)","27002_2013 (14.2.5)","27002_2022 (8.27,8.9)","27017_2015 (14.2.5)","27018_2019 (14)","27701_2019 (6.11.2.5)"],"Standard12":["(CIP-003-8_Requirement_R1)"],"Standard11":["800-171 Rev2 (3.4.1,3.4.2)","800_53 Rev5 (CM-1,CM-2,CM-6,SA-10,SA-8)"],"Standard9":["v1.1 (PR.IP-1)"],"Standard8":["v3.2.1 (2.2)","v4.0 (2.1.1,2.2.1,2.2.6)"],"s202":["(CC8.1)"]}} \ No newline at end of file diff --git a/tests/tests_metrics/mock_files/statistics/statistics.json b/tests/tests_metrics/mock_files/statistics/statistics.json new file mode 100644 index 000000000..a6a6e2fb0 --- /dev/null +++ b/tests/tests_metrics/mock_files/statistics/statistics.json @@ -0,0 +1 @@ +[{"policy":"ecc-aws-529","region":"ap-northeast-2","start_time":1699886353.5555925,"end_time":1699886353.6428285,"status":"SUCCEEDED","resources_scanned":2,"elapsed_time":"1.899","tenant_name":"AWS-TEST","customer_name":"EPAM Systems"},{"policy":"ecc-aws-490","region":"ap-northeast-2","start_time":1699886353.5555925,"end_time":1699886353.6428285,"status":"SUCCEEDED","resources_scanned":2,"elapsed_time":"0.005","tenant_name":"AWS-TEST","customer_name":"EPAM Systems"},{"policy":"ecc-aws-489","region":"ap-northeast-2","start_time":1699886353.5555925,"end_time":1699886353.6428285,"status":"SUCCEEDED","resources_scanned":2,"elapsed_time":"0.006","tenant_name":"AWS-TEST","customer_name":"EPAM Systems"},{"policy":"ecc-aws-329","region":"ap-northeast-2","start_time":1699886353.5555925,"end_time":1699886353.6428285,"status":"SUCCEEDED","resources_scanned":7,"elapsed_time":"1.14","tenant_name":"AWS-TEST","customer_name":"EPAM Systems"},{"policy":"ecc-aws-224","region":"ap-northeast-2","start_time":1699886353.5555925,"end_time":1699886353.6428285,"status":"SUCCEEDED","resources_scanned":2,"elapsed_time":"0.006","tenant_name":"AWS-TEST","customer_name":"EPAM Systems"},{"policy":"ecc-aws-223","region":"ap-northeast-2","start_time":1699886353.5555925,"end_time":1699886353.6428285,"status":"SUCCEEDED","resources_scanned":2,"elapsed_time":"1.147","tenant_name":"AWS-TEST","customer_name":"EPAM Systems"},{"policy":"ecc-aws-189","region":"ap-northeast-2","start_time":1699886353.5555925,"end_time":1699886353.6428285,"status":"SUCCEEDED","resources_scanned":2,"elapsed_time":"0.004","tenant_name":"AWS-TEST","customer_name":"EPAM Systems"},{"policy":"ecc-aws-186","region":"ap-northeast-2","start_time":1699886353.5555925,"end_time":1699886353.6428285,"status":"SUCCEEDED","resources_scanned":2,"elapsed_time":"0.004","tenant_name":"AWS-TEST","customer_name":"EPAM Systems"},{"policy":"ecc-aws-185","region":"ap-northeast-2","start_time":1699886353.5555925,"end_time":1699886353.6428285,"status":"SUCCEEDED","resources_scanned":2,"elapsed_time":"0.005","tenant_name":"AWS-TEST","customer_name":"EPAM Systems"},{"policy":"ecc-aws-057","region":"ap-northeast-2","start_time":1699886353.5555925,"end_time":1699886353.6428285,"status":"SUCCEEDED","resources_scanned":2,"elapsed_time":"0.004","tenant_name":"AWS-TEST","customer_name":"EPAM Systems"},{"policy":"ecc-aws-025","region":"ap-northeast-2","start_time":1699886353.5555925,"end_time":1699886353.6428285,"status":"SUCCEEDED","resources_scanned":2,"elapsed_time":"1.517","tenant_name":"AWS-TEST","customer_name":"EPAM Systems"},{"policy":"ecc-aws-020","region":"ap-northeast-2","start_time":1699886353.5555925,"end_time":1699886353.6428285,"status":"SUCCEEDED","resources_scanned":2,"elapsed_time":"0.004","tenant_name":"AWS-TEST","customer_name":"EPAM Systems"},{"policy":"ecc-aws-529","region":"eu-west-2","start_time":1699886353.5555925,"end_time":1699886353.6428285,"status":"SUCCEEDED","resources_scanned":0,"elapsed_time":"0.484","tenant_name":"AWS-TEST","customer_name":"EPAM Systems"},{"policy":"ecc-aws-490","region":"eu-west-2","start_time":1699886353.5555925,"end_time":1699886353.6428285,"status":"SUCCEEDED","resources_scanned":0,"elapsed_time":"0.004","tenant_name":"AWS-TEST","customer_name":"EPAM Systems"},{"policy":"ecc-aws-489","region":"eu-west-2","start_time":1699886353.5555925,"end_time":1699886353.6428285,"status":"SUCCEEDED","resources_scanned":0,"elapsed_time":"0.004","tenant_name":"AWS-TEST","customer_name":"EPAM Systems"},{"policy":"ecc-aws-329","region":"eu-west-2","start_time":1699886353.5555925,"end_time":1699886353.6428285,"status":"SUCCEEDED","resources_scanned":5,"elapsed_time":"0.234","tenant_name":"AWS-TEST","customer_name":"EPAM Systems"},{"policy":"ecc-aws-224","region":"eu-west-2","start_time":1699886353.5555925,"end_time":1699886353.6428285,"status":"SUCCEEDED","resources_scanned":0,"elapsed_time":"0.004","tenant_name":"AWS-TEST","customer_name":"EPAM Systems"},{"policy":"ecc-aws-223","region":"eu-west-2","start_time":1699886353.5555925,"end_time":1699886353.6428285,"status":"SUCCEEDED","resources_scanned":0,"elapsed_time":"0.004","tenant_name":"AWS-TEST","customer_name":"EPAM Systems"},{"policy":"ecc-aws-189","region":"eu-west-2","start_time":1699886353.5555925,"end_time":1699886353.6428285,"status":"SUCCEEDED","resources_scanned":0,"elapsed_time":"0.003","tenant_name":"AWS-TEST","customer_name":"EPAM Systems"},{"policy":"ecc-aws-186","region":"eu-west-2","start_time":1699886353.5555925,"end_time":1699886353.6428285,"status":"SUCCEEDED","resources_scanned":0,"elapsed_time":"0.004","tenant_name":"AWS-TEST","customer_name":"EPAM Systems"},{"policy":"ecc-aws-185","region":"eu-west-2","start_time":1699886353.5555925,"end_time":1699886353.6428285,"status":"SUCCEEDED","resources_scanned":0,"elapsed_time":"0.003","tenant_name":"AWS-TEST","customer_name":"EPAM Systems"},{"policy":"ecc-aws-057","region":"eu-west-2","start_time":1699886353.5555925,"end_time":1699886353.6428285,"status":"SUCCEEDED","resources_scanned":0,"elapsed_time":"0.004","tenant_name":"AWS-TEST","customer_name":"EPAM Systems"},{"policy":"ecc-aws-025","region":"eu-west-2","start_time":1699886353.5555925,"end_time":1699886353.6428285,"status":"SUCCEEDED","resources_scanned":0,"elapsed_time":"0.004","tenant_name":"AWS-TEST","customer_name":"EPAM Systems"},{"policy":"ecc-aws-020","region":"eu-west-2","start_time":1699886353.5555925,"end_time":1699886353.6428285,"status":"SUCCEEDED","resources_scanned":0,"elapsed_time":"0.004","tenant_name":"AWS-TEST","customer_name":"EPAM Systems"},{"policy":"ecc-aws-529","region":"us-east-2","start_time":1699886353.5555925,"end_time":1699886353.6428285,"status":"SUCCEEDED","resources_scanned":0,"elapsed_time":"1.074","tenant_name":"AWS-TEST","customer_name":"EPAM Systems"},{"policy":"ecc-aws-490","region":"us-east-2","start_time":1699886353.5555925,"end_time":1699886353.6428285,"status":"SUCCEEDED","resources_scanned":0,"elapsed_time":"0.004","tenant_name":"AWS-TEST","customer_name":"EPAM Systems"},{"policy":"ecc-aws-489","region":"us-east-2","start_time":1699886353.5555925,"end_time":1699886353.6428285,"status":"SUCCEEDED","resources_scanned":0,"elapsed_time":"0.004","tenant_name":"AWS-TEST","customer_name":"EPAM Systems"},{"policy":"ecc-aws-329","region":"us-east-2","start_time":1699886353.5555925,"end_time":1699886353.6428285,"status":"SUCCEEDED","resources_scanned":4,"elapsed_time":"0.568","tenant_name":"AWS-TEST","customer_name":"EPAM Systems"},{"policy":"ecc-aws-224","region":"us-east-2","start_time":1699886353.5555925,"end_time":1699886353.6428285,"status":"SUCCEEDED","resources_scanned":0,"elapsed_time":"0.004","tenant_name":"AWS-TEST","customer_name":"EPAM Systems"},{"policy":"ecc-aws-223","region":"us-east-2","start_time":1699886353.5555925,"end_time":1699886353.6428285,"status":"SUCCEEDED","resources_scanned":0,"elapsed_time":"0.004","tenant_name":"AWS-TEST","customer_name":"EPAM Systems"},{"policy":"ecc-aws-189","region":"us-east-2","start_time":1699886353.5555925,"end_time":1699886353.6428285,"status":"SUCCEEDED","resources_scanned":0,"elapsed_time":"0.004","tenant_name":"AWS-TEST","customer_name":"EPAM Systems"},{"policy":"ecc-aws-186","region":"us-east-2","start_time":1699886353.5555925,"end_time":1699886353.6428285,"status":"SUCCEEDED","resources_scanned":0,"elapsed_time":"0.004","tenant_name":"AWS-TEST","customer_name":"EPAM Systems"},{"policy":"ecc-aws-185","region":"us-east-2","start_time":1699886353.5555925,"end_time":1699886353.6428285,"status":"SUCCEEDED","resources_scanned":0,"elapsed_time":"0.004","tenant_name":"AWS-TEST","customer_name":"EPAM Systems"},{"policy":"ecc-aws-057","region":"us-east-2","start_time":1699886353.5555925,"end_time":1699886353.6428285,"status":"SUCCEEDED","resources_scanned":0,"elapsed_time":"0.004","tenant_name":"AWS-TEST","customer_name":"EPAM Systems"},{"policy":"ecc-aws-025","region":"us-east-2","start_time":1699886353.5555925,"end_time":1699886353.6428285,"status":"SUCCEEDED","resources_scanned":0,"elapsed_time":"0.004","tenant_name":"AWS-TEST","customer_name":"EPAM Systems"},{"policy":"ecc-aws-020","region":"us-east-2","start_time":1699886353.5555925,"end_time":1699886353.6428285,"status":"SUCCEEDED","resources_scanned":0,"elapsed_time":"0.004","tenant_name":"AWS-TEST","customer_name":"EPAM Systems"}] \ No newline at end of file diff --git a/tests/tests_metrics/test_metrics_aggregation.py b/tests/tests_metrics/test_metrics_aggregation.py new file mode 100644 index 000000000..c27b97df6 --- /dev/null +++ b/tests/tests_metrics/test_metrics_aggregation.py @@ -0,0 +1,615 @@ +import datetime +import json +import os +import pathlib +import pytest +from unittest.mock import patch, MagicMock + +from .test_metrics_updater import (TestMetricsUpdater, + LAST_WEEK_DATE, CLOUDS, S3Client) + +AWS_OVERVIEW_LEN = 486 +AZURE_OVERVIEW_LEN = 258 +GOOGLE_OVERVIEW_LEN = 277 +AWS_RESOURCES_LEN = 546 +AZURE_RESOURCES_LEN = 258 +GOOGLE_RESOURCES_LEN = 278 +AWS_FINOPS_LEN = 131 +AZURE_FINOPS_LEN = 12 +GOOGLE_FINOPS_LEN = 25 +DEPARTMENT_TYPES = ['RESOURCES_BY_CLOUD', 'RESOURCES_BY_TENANT', + 'COMPLIANCE_BY_TENANT', 'COMPLIANCE_BY_CLOUD', + 'ATTACK_BY_TENANT', 'ATTACK_BY_CLOUD'] +CUSTOMER_TYPES = ['OVERVIEW', 'COMPLIANCE', 'ATTACK_VECTOR'] +MONTH_FIRST_DAY = '2023-11-01' + + +class TestMetricsAggregationFlow(TestMetricsUpdater): + def setUp(self) -> None: + super().setUp() + self.tenant_metrics = {} + self.maxDiff = None + + @pytest.mark.skip + def test_happy_path(self): + self.tenant_metrics_happy_path() + self.tenant_groups_metrics_happy_path() + self.customer_metrics_happy_path() + self.difference_metrics_no_prev_happy_path() + self.difference_metrics_with_prev_happy_path() + + @pytest.mark.skip + def test_mark_archive(self): + for t in self.tenants: + t.is_active = False + with (patch.object(self.TENANT_HANDLER, 'today_date', + datetime.datetime(2023, 10, 1)), + patch.object(self.TENANT_HANDLER, 'month_first_day_iso', + datetime.datetime(2023, 10, 1).date().isoformat())): + self.TENANT_HANDLER.process_data({}) + self.assertNotEqual([], S3Client().deleted_items) + + def assertFieldsMatch(self, actual, expected_fields): + """Test that json fields are correct""" + for field in expected_fields: + path, expected_value = field + value = self.get_nested(actual, path) + + self.assertIsNotNone(value, f"Field '{path}' not found in JSON") + if type(value) == dict: + self.assertDictEqual(value, expected_value, + f"Field '{path}' has unexpected value") + else: + self.assertEqual(value, expected_value, + f"Field '{path}' has unexpected value") + + def tenant_metrics_happy_path(self): + with (patch.object(self.TENANT_HANDLER, 'yesterday', + datetime.datetime(2023, 10, 10)), + patch.object(self.TENANT_HANDLER, 'next_month_date', + MONTH_FIRST_DAY)): + tenant_result = self.TENANT_HANDLER.process_data( + {'data_type': 'tenants'}) + + self.assertDictEqual( + {'data_type': 'tenant_groups', 'end_date': None, + 'continuously': None}, + tenant_result) + for tenant in self.tenants: + if tenant.cloud == 'AWS': + self.compare_aws_tenant_metrics( + S3Client().put_object_mapping.get( + ('metrics', + f'{tenant.customer_name}/accounts/2023-10-15/{tenant.project}.json.gz'), + {})) + elif tenant.cloud == 'AZURE': + self.compare_azure_tenant_metrics( + S3Client().put_object_mapping.get( + ('metrics', + f'{tenant.customer_name}/accounts/2023-10-15/{tenant.project}.json.gz'), + {})) + elif tenant.cloud == 'GOOGLE': + self.compare_google_tenant_metrics( + S3Client().put_object_mapping.get( + ('metrics', + f'{tenant.customer_name}/accounts/2023-10-15/{tenant.project}.json.gz'), + {})) + + def tenant_groups_metrics_happy_path(self): + self.TENANT_GROUP_HANDLER.s3_client.gz_get_json = MagicMock() + self.TENANT_GROUP_HANDLER.s3_client.gz_get_json = self.get_tenant_metrics + self.TENANT_GROUP_HANDLER.s3_client.list_dir = self.list_dir + + with patch.object(self.TENANT_GROUP_HANDLER, 'next_month_date', + MONTH_FIRST_DAY): + tenant_group_result = self.TENANT_GROUP_HANDLER.process_data( + {'data_type': 'tenant_groups'}) + + self.assertDictEqual( + {'data_type': 'customer', 'end_date': None, 'continuously': None}, + tenant_group_result) + + self.compare_tenant_groups_metrics(S3Client().put_object_mapping.get( + ('metrics', + f'{self.tenants[0].customer_name}/tenants/2023-10-15/{self.tenants[0].display_name}.json.gz'), + {})) + + def difference_metrics_with_prev_happy_path(self): + self.TENANT_GROUP_HANDLER.s3_client.list_dir = self.list_dir + + difference_result = self.DIFFERENCE_HANDLER.process_data( + {'data_type': 'difference'}) + + self.assertDictEqual({}, difference_result) + + self.compare_difference_metrics( + S3Client().put_object_mapping.get(('metrics', + f'{self.tenants[0].customer_name}/tenants/2023-10-15/{self.tenants[0].display_name}.json.gz'), + {}), prev=True) + + def difference_metrics_no_prev_happy_path(self): + self.TENANT_GROUP_HANDLER.s3_client.list_dir = self.list_dir_no_prev + + difference_result = self.DIFFERENCE_HANDLER.process_data( + {'data_type': 'difference'}) + + self.assertDictEqual({}, difference_result) + + self.compare_difference_metrics(S3Client().put_object_mapping.get(( + 'metrics', + f'{self.tenants[0].customer_name}/tenants/2023-10-15/{self.tenants[0].display_name}.json.gz'), + {}), + prev=False) + + def customer_metrics_happy_path(self): + self.TOP_HANDLER.s3_client.list_dir = self.list_dir + self.TENANT_GROUP_HANDLER.s3_client.gz_get_json = MagicMock() + self.TENANT_GROUP_HANDLER.s3_client.gz_get_json = self.gz_get_json + with patch.object(self.TOP_HANDLER, 'month_first_day', MONTH_FIRST_DAY): + customer_result = self.TOP_HANDLER.process_data( + {'data_type': 'customer'}) + self.assertDictEqual({'data_type': 'difference', 'end_date': None, + 'continuously': None}, customer_result) + self.compare_customer_metrics() + + def compare_aws_tenant_metrics(self, actual_body: dict): + actual_body['resources'] = sorted(actual_body['resources'], + key=lambda d: d['policy']) + with open(os.path.join( + pathlib.Path(__file__).parent.resolve(), + 'expected_metrics', 'aws_tenant_resources.json'), 'r') as f: + expected_resources_body = f.read() + expected_fields = [ + ('resources', json.loads(expected_resources_body)), + ('rule.violated_resources_length', 486) + ] + self.assertEqual(len(actual_body['resources']), AWS_RESOURCES_LEN) + self.assertEqual(21, len(actual_body['finops'])) + self.assertEqual(14, len(actual_body['attack_vector'])) + self.assertFieldsMatch(actual_body, expected_fields) + + self.tenant_metrics[actual_body['id']] = actual_body + + def compare_azure_tenant_metrics(self, actual_body: dict): + actual_body['resources'] = sorted(actual_body['resources'], + key=lambda d: d['policy']) + with open(os.path.join( + pathlib.Path(__file__).parent.resolve(), + 'expected_metrics', 'azure_tenant_resources.json'), 'r') as f: + expected_resources_body = f.read() + expected_fields = [ + ('resources', json.loads(expected_resources_body)), + ('rule.violated_resources_length', 258) + ] + self.assertEqual(len(actual_body['resources']), AZURE_RESOURCES_LEN) + self.assertEqual(len(actual_body['finops']), 1) + self.assertEqual(len(actual_body['attack_vector']), 10) + self.assertFieldsMatch(actual_body, expected_fields) + + self.tenant_metrics[actual_body['id']] = actual_body + + def compare_google_tenant_metrics(self, actual_body: dict): + actual_body['resources'] = sorted(actual_body['resources'], + key=lambda d: d['policy']) + with open(os.path.join( + pathlib.Path(__file__).parent.resolve(), + 'expected_metrics', 'google_tenant_resources.json'), 'r') as f: + expected_resources_body = f.read() + expected_fields = [ + ('rule.violated_resources_length', 277), + ('resources', json.loads(expected_resources_body)) + ] + self.assertEqual(len(actual_body['resources']), GOOGLE_RESOURCES_LEN) + self.assertEqual(len(actual_body['finops']), 1) + self.assertEqual(len(actual_body['attack_vector']), 14) + self.assertFieldsMatch(actual_body, expected_fields) + + self.tenant_metrics[actual_body['id']] = actual_body + + def compare_tenant_groups_metrics(self, actual_body: dict): + # overview + overview = actual_body['overview']['aws'][0] + expected_fields = [ + ('resources_violated', AWS_OVERVIEW_LEN), + ('regions_data.multiregion.severity_data', + {'Low': 25, 'Info': 6, 'High': 14, 'Medium': 8}), + ('regions_data.multiregion.resource_types_data', + {'AWS Identity and Access Management': 14, 'Amazon S3': 14, + 'Amazon CloudFront': 16, 'AWS Account': 2, 'Amazon Route 53': 4, + 'AWS Web Application Firewall': 3}), + ('regions_data.eu-west-1.severity_data', + {'Low': 158, 'High': 173, 'Info': 44, 'Medium': 59}), + ('regions_data.eu-west-1.resource_types_data', + {'Amazon Virtual Private Cloud': 17, + 'Amazon Relational Database Service': 68, 'Amazon Route 53': 2, + 'Amazon Elastic Load Balancing': 15, 'AWS CloudFormation': 3, + 'Amazon EC2': 50, 'Amazon Elastic Block Store': 10, + 'Amazon Simple Queue Service': 4, + 'Amazon Elastic Kubernetes Service': 8, 'AWS CloudTrail': 9, + 'AWS Key Management Service': 3, 'AWS Account': 2, + 'AWS CodeBuild': 7, 'Amazon EC2 Auto Scaling': 11, + 'Amazon OpenSearch Service': 14, 'AWS Lambda': 10, + 'Amazon Redshift': 15, 'Amazon SageMaker': 7, + 'Amazon Kinesis': 7, 'AWS Certificate Manager': 8, + 'Amazon Elastic Container Service': 13, 'Amazon API Gateway': 12, + 'Amazon DynamoDB': 3, 'Amazon Elastic File System': 5, + 'Amazon ElastiCache': 14, 'AWS Database Migration Service': 6, + 'Amazon DynamoDB Accelerator': 3, 'AWS Elastic Beanstalk': 7, + 'Amazon EMR': 8, 'AWS Secrets Manager': 3, + 'Amazon Simple Notification Service': 4, + 'Amazon Elastic Container Registry': 4, 'AWS Transit Gateway': 4, + 'Amazon AppFlow': 2, 'AWS Glue': 11, 'Amazon DocumentDB': 1, + 'Amazon WorkSpaces Family': 11, 'AWS Backup': 2, + 'Amazon EventBridge': 1, 'Amazon S3 Glacier': 2, 'AWS Config': 1, + 'Amazon FSx': 8, 'Amazon MQ': 6, + 'Amazon Managed Streaming for Apache Kafka': 4, + 'Amazon Managed Workflows for Apache Airflow': 8, + 'AWS Directory': 1, 'Amazon Data Lifecycle Manager': 1, + 'Amazon Lightsail': 1, 'Amazon CloudWatch': 3, 'Amazon QLDB': 3, + 'AWS AppSync': 4, 'AWS CodeDeploy': 3, 'AWS CodePipeline': 1, + 'AWS Identity and Access Management': 1, + 'AWS Identity and Access Management Access Analyzer': 1, + 'AWS Web Application Firewall': 1, 'AWS Step Functions': 1}) + ] + self.assertFieldsMatch(overview, expected_fields) + + overview = actual_body['overview']['azure'][0] + expected_fields = [ + ('resources_violated', AZURE_OVERVIEW_LEN), + ('regions_data.multiregion.severity_data', + {'Low': 158, 'Medium': 13, 'Info': 34, 'High': 53}), + ('regions_data.multiregion.resource_types_data', + {'Azure RBAC': 1, 'Microsoft Defender for Cloud': 18, + 'Azure Storage Accounts': 18, 'Azure SQL Database': 16, + 'Azure Database for PostgreSQL': 20, + 'Azure Database for MySQL': 14, 'Key Vault': 7, + 'Azure Subscription': 12, 'Network security groups': 18, + 'Azure Disk Storage': 3, 'Azure Kubernetes Service': 12, + 'App Service': 28, 'Virtual Machines': 17, 'Virtual Network': 5, + 'API Management': 3, 'Azure Cosmos DB': 4, + 'Cognitive Services': 3, 'Azure Container Registry': 6, + 'Azure Database for MariaDB': 3, 'App Configuration': 1, + 'Azure Cache for Redis': 4, 'Event Grid': 2, + 'Azure Machine Learning': 3, 'Azure SignalR Service': 1, + 'Azure Spring Apps': 1, 'Application Gateway': 4, + 'Azure Front Door': 2, 'Azure Service Fabric': 2, + 'Azure SQL Managed Instance': 3, 'Automation': 1, + 'Azure Data Lake Storage': 1, 'Azure Stream Analytics': 1, + 'Batch': 2, 'Data Lake Analytics': 1, 'Azure IoT Hub': 2, + 'Azure Logic Apps': 1, 'Azure Cognitive Search': 1, + 'Service Bus': 1, 'Azure Virtual Machine Scale Sets': 7, + 'Azure Data Explorer': 3, 'Azure Data Factory': 2, + 'Azure Databricks': 1, 'Azure Synapse Analytics': 2, + 'Azure Monitor': 1}), + ('regions_data.westeurope.severity_data', + {'Low': 158, 'Medium': 13, 'Info': 34, 'High': 53}), + ('regions_data.westeurope.resource_types_data', + {'Azure RBAC': 1, 'Microsoft Defender for Cloud': 18, + 'Azure Storage Accounts': 18, 'Azure SQL Database': 16, + 'Azure Database for PostgreSQL': 20, + 'Azure Database for MySQL': 14, 'Key Vault': 7, + 'Azure Subscription': 12, 'Network security groups': 18, + 'Azure Disk Storage': 3, 'Azure Kubernetes Service': 12, + 'App Service': 28, 'Virtual Machines': 17, 'Virtual Network': 5, + 'API Management': 3, 'Azure Cosmos DB': 4, + 'Cognitive Services': 3, 'Azure Container Registry': 6, + 'Azure Database for MariaDB': 3, 'App Configuration': 1, + 'Azure Cache for Redis': 4, 'Event Grid': 2, + 'Azure Machine Learning': 3, 'Azure SignalR Service': 1, + 'Azure Spring Apps': 1, 'Application Gateway': 4, + 'Azure Front Door': 2, 'Azure Service Fabric': 2, + 'Azure SQL Managed Instance': 3, 'Automation': 1, + 'Azure Data Lake Storage': 1, 'Azure Stream Analytics': 1, + 'Batch': 2, 'Data Lake Analytics': 1, 'Azure IoT Hub': 2, + 'Azure Logic Apps': 1, 'Azure Cognitive Search': 1, + 'Service Bus': 1, 'Azure Virtual Machine Scale Sets': 7, + 'Azure Data Explorer': 3, 'Azure Data Factory': 2, + 'Azure Databricks': 1, 'Azure Synapse Analytics': 2, + 'Azure Monitor': 1}) + ] + self.assertFieldsMatch(overview, expected_fields) + + overview = actual_body['overview']['google'][0] + expected_fields = [ + ('resources_violated', GOOGLE_OVERVIEW_LEN), + ('regions_data.multiregion.severity_data', + {'High': 99, 'Low': 121, 'Medium': 10, 'Info': 47}), + ('regions_data.multiregion.resource_types_data', + {'Cloud IAM': 12, 'Cloud KMS': 5, 'Cloud APIs': 4, + 'Cloud Logging': 10, 'Cloud Storage': 14, + 'Virtual Private Cloud': 39, 'Cloud DNS': 4, + 'Compute Engine': 38, 'Cloud SQL': 46, + 'Google Kubernetes Engine': 39, 'Secret Manager': 2, + 'Cloud Load Balancing': 12, 'BigQuery': 4, 'Cloud Functions': 10, + 'App Engine': 3, 'Cloud Bigtable': 3, 'Dataproc': 4, + 'Cloud Run': 6, 'Cloud Armor': 4, 'Pub/Sub': 3, + 'Cloud Spanner': 5, 'Cloud Memorystore': 2, 'Dataflow': 1, + 'Vertex AI Workbench': 1, 'Cloud Data Fusion': 3, + 'Access Transparency': 1, 'Access Approval': 1, + 'Cloud Asset Inventory': 1}), + ] + self.assertFieldsMatch(overview, expected_fields) + + # resource + self.assertEqual(AWS_RESOURCES_LEN, + len(actual_body['resources']['aws'][0]['data'])) + self.assertEqual(AZURE_RESOURCES_LEN, + len(actual_body['resources']['azure'][0]['data'])) + self.assertEqual(GOOGLE_RESOURCES_LEN, + len(actual_body['resources']['google'][0]['data'])) + + # finops + for c in CLOUDS: + c = c.lower() + actual_body['finops'][c][0]['service_data'] = sorted( + actual_body['finops'][c][0]['service_data'], + key=lambda d: d['service_section']) + with open(os.path.join( + pathlib.Path(__file__).parent.resolve(), + 'expected_metrics', f'{c}_tenant_group_finops.json'), + 'r') as f: + expected_finops_body = f.read() + self.assertEqual(actual_body['finops'][c][0]['service_data'], + json.loads(expected_finops_body)) + + def compare_customer_metrics(self): + # department + tenant_items = \ + self.TOP_HANDLER.tenant_metrics_service.batch_save.call_args.args[0] + for t in tenant_items: + if t.type in DEPARTMENT_TYPES: + DEPARTMENT_TYPES.remove(t.type) + self.assertListEqual(DEPARTMENT_TYPES, [], + f'Not all department report types were created: ' + f'{DEPARTMENT_TYPES} are missing') + # TODO What is that and other files in mock_file? + with open(os.path.join( + pathlib.Path(__file__).parent, + 'expected_metrics', 'department_metrics.json'), 'r') as f: + expected_department_items = f.read() + for item in tenant_items: + for c in CLOUDS: + if item.attribute_values.get(c.lower()) == '{}': + continue + if 'ATTACK_BY_CLOUD' == item.type: + self.assertCountEqual( + item.attribute_values.get(c.lower(), {}).get('data', + []), + json.loads(expected_department_items).get(item.type, + {}).get( + c.lower()), + f'Invalid structure for {item.type} department item') + elif 'ATTACK_BY_TENANT' in item.type: + self.assertCountEqual( + item.attribute_values.get(c.lower()), + json.loads(expected_department_items).get(item.type, + {}).get( + c.lower()), + f'Invalid structure for {item.type} department item') + elif 'COMPLIANCE' in item.type: + self.assertCountEqual( + item.attribute_values.get(c.lower()).get( + 'average_data'), + json.loads(expected_department_items).get(item.type, + {}).get( + c.lower()).get('average_data'), + f'Invalid structure for {item.type} department item') + elif 'RESOURCES' in item.type: + self.assertDictEqual( + item.attribute_values.get(c.lower(), {}).get( + 'resource_types_data'), + json.loads(expected_department_items).get(item.type, + {}).get( + c.lower(), {}).get('resource_types_data'), + f'Invalid structure for {item.type} department item') + self.assertDictEqual( + item.attribute_values.get(c.lower(), {}).get( + 'severity_data'), + json.loads(expected_department_items).get(item.type, + {}).get( + c.lower(), {}).get('severity_data'), + f'Invalid structure for {item.type} department item') + else: + self.assertDictEqual( + item.attribute_values.get(c.lower()), + json.loads(expected_department_items).get(item.type, + {}).get( + c.lower()), + f'Invalid structure for {item.type} department item') + + # c-level + customer_items = \ + self.TOP_HANDLER.customer_metrics_service.batch_save.call_args.args[0] + for c in customer_items: + if c.type in CUSTOMER_TYPES: + CUSTOMER_TYPES.remove(c.type) + self.assertListEqual(CUSTOMER_TYPES, [], + f'Not all c-level report types were created: ' + f'{CUSTOMER_TYPES} are missing') + + with open(os.path.join( + pathlib.Path(__file__).parent, + 'expected_metrics', f'customer_metrics.json'), 'r') as f: + expected_customer_items = f.read() + for item in customer_items: + for c in CLOUDS: + if item.type == 'OVERVIEW': + self.assertEqual( + json.loads(item.to_json()).get(c.lower()), + json.loads(expected_customer_items).get(item.type, + {}).get( + c.lower()), + f'Invalid structure for {item.type} c-level item') + elif item.type == 'COMPLIANCE': + self.assertCountEqual( + json.loads(item.to_json()).get(c.lower(), {}).get( + 'average_data'), + json.loads(expected_customer_items).get(item.type, + {}).get( + c.lower(), {}).get('average_data'), + f'Invalid structure for {item.type} c-level item') + elif item.type == 'ATTACK_VECTOR': + self.assertCountEqual( + json.loads(item.to_json()).get(c.lower(), {}).get( + 'data'), + json.loads(expected_customer_items).get(item.type, + {}).get( + c.lower(), {}).get('data'), + f'Invalid structure for {item.type} c-level item') + + def compare_difference_metrics(self, actual_body: dict, prev: bool): + self.assertEqual(len(actual_body['resources']['aws'][0]['data']), + AWS_RESOURCES_LEN) + self.assertEqual(len(actual_body['resources']['azure'][0]['data']), + AZURE_RESOURCES_LEN) + self.assertEqual(len(actual_body['resources']['google'][0]['data']), + GOOGLE_RESOURCES_LEN) + + for cloud in CLOUDS: + c = cloud.lower() + # compliance + if not actual_body['compliance'][c][0].get('average_data'): + for item in actual_body['compliance'][c][0]['regions_data'][0][ + 'standards_data']: + self.assert_diff_compliance_structure(item, + is_diff_none=prev) + else: + for item in actual_body['compliance'][c][0]['average_data']: + self.assert_diff_compliance_structure(item, + is_diff_none=prev) + # resources + for data in actual_body['resources'][c][0]['data']: + for region, item in data['regions_data'].items(): + self.assert_diff_resource_structure( + item['total_violated_resources'], is_diff_none=prev) + # finops + for service in actual_body['finops'][c][0]['service_data']: + for rule in service['rules_data']: + for region, item in rule['regions_data'].items(): + self.assert_diff_finops_structure( + item['total_violated_resources'], + is_diff_none=prev) + # overview + for region, item in actual_body['overview'][c][0][ + 'regions_data'].items(): + self.assert_diff_overview_structure(item, is_diff_none=prev) + + def assert_diff_compliance_structure(self, container, is_diff_none=True): + self.assertIn('diff', container, + 'Invalid compliance diff structure: No \'diff\' field') + self.assertIn('value', container, + 'Invalid compliance diff structure: No \'value\' field') + self.assertIn('name', container, + 'Invalid compliance diff structure: No \'name\' field') + self.assertIn(type(container['diff']), (int, float, type(None)), + 'Invalid compliance diff value: Difference must be a ' + 'number or None') + self.assertIn(type(container['value']), (int, float), + 'Invalid compliance diff value: Value must be number') + self.assertEqual(type(container['name']), str, + 'Invalid compliance diff value: Name must be int or' + ' None') + if not is_diff_none: + self.assertIsNone(container['diff'], + 'Invalid compliance diff value: Must be ' + '\'None\', because there are no previous metrics') + + def assert_diff_resource_structure(self, container, is_diff_none=True): + self.assertIn('diff', container, + 'Invalid resource diff structure: No \'diff\' field') + self.assertIn('value', container, + 'Invalid resource diff structure: No \'value\' field') + self.assertIn(type(container['diff']), (int, type(None)), + 'Invalid resource diff value: Difference must be int ' + 'or None') + self.assertEqual(type(container['value']), int, + 'Invalid resource diff value: Value must be int') + if not is_diff_none: + self.assertIsNone(container['diff'], + 'Invalid resource diff value: Must be \'None\', ' + 'because there are no previous metrics') + + def assert_diff_finops_structure(self, container, is_diff_none=True): + self.assertIn('diff', container, + 'Invalid finops diff structure: No \'diff\' field') + self.assertIn('value', container, + 'Invalid finops diff structure: No \'value\' field') + self.assertIn(type(container['diff']), (int, type(None)), + 'Invalid finops diff value: Difference must be int ' + 'or None') + self.assertEqual(type(container['value']), int, + 'Invalid finops diff value: Value must be int') + if not is_diff_none: + self.assertIsNone(container['diff'], + 'Invalid finops diff value: Must be \'None\', ' + 'because there are no previous metrics') + + def assert_diff_overview_structure(self, container, is_diff_none=True): + for t in ['severity_data', 'resource_types_data']: + for severity, data in container[t].items(): + self.assertIn('diff', data, f'Invalid overview {t} diff ' + f'structure: No \'diff\' field') + self.assertIn('value', data, + f'Invalid overview {t} diff structure: ' + f'No \'value\' field') + self.assertIn(type(data['diff']), (int, type(None)), + f'Invalid overview {t} diff value: Difference ' + f'must be int or None') + self.assertEqual(type(data['value']), int, + f'Invalid overview {t} diff value: Value ' + f'must be int') + if not is_diff_none: + self.assertIsNone( + data['diff'], + f'Invalid overview {t} diff value: Must be \'None\', ' + f'because there are no previous metrics') + + def get_tenant_metrics(self, bucket, key): + name = key.replace('.json.gz', '').split('/')[-1] + if 'GOOGLE' in name: + for t in self.tenants: + if t.cloud == 'GOOGLE': + return self.tenant_metrics.get(t.account_number, {}) + + return self.tenant_metrics.get(name, {}) + + def list_dir(self, bucket_name, key=None): + if key is None: + yield 'EPAM Systems/' + elif 'tenants' in key: + yield f'EPAM Systems/tenants/2023-10-15/{self.tenants[0].display_name}.json.gz' + else: + for tenant in self.tenants: + yield f'EPAM Systems/accounts/2023-10-15/{tenant.project}.json.gz' + + def list_dir_no_prev(self, bucket_name, key=None): + if key is None: + yield 'EPAM Systems/' + elif 'tenants' in key and LAST_WEEK_DATE in key: + yield '' + elif 'tenants' in key: + yield f'EPAM Systems/tenants/2023-10-15/{self.tenants[0].display_name}.json.gz' + + def gz_get_json(self, bucket, key): + if LAST_WEEK_DATE in key: + with open(os.path.join( + pathlib.Path(__file__).parent.resolve(), + 'mock_files', 'old_metrics.json'), 'r') as file: + parsed_json = json.load(file) + return parsed_json + else: + parsed_json = S3Client().put_object_mapping.get((bucket, key), {}) + return parsed_json + + @staticmethod + def get_nested(json_obj, path): + keys = path.split('.') + value = json_obj + + for key in keys: + if key in value: + value = value[key] + else: + return None + + return value diff --git a/tests/tests_metrics/test_metrics_updater.py b/tests/tests_metrics/test_metrics_updater.py new file mode 100644 index 000000000..4d8f9e292 --- /dev/null +++ b/tests/tests_metrics/test_metrics_updater.py @@ -0,0 +1,307 @@ +import importlib +import json +import logging +import os +import pathlib +import uuid +from typing import Dict, List +from unittest import TestCase +from unittest.mock import MagicMock + +from modular_sdk.models.tenant import Tenant + +from helpers import SingletonMeta +from models.batch_results import BatchResults +from models.customer_metrics import CustomerMetrics +from models.job import Job +from models.job_statistics import JobStatistics +from models.tenant_metrics import TenantMetrics + +CURRENT_WEEK_DATE = '2023-10-15' +LAST_WEEK_DATE = '2023-10-09' +CLOUDS = ['AWS', 'AZURE', 'GOOGLE'] +regions = { + 'AWS': [{'native_name': 'eu-west-1'}, {'native_name': 'multiregion'}], + 'AZURE': [{'native_name': 'westeurope'}, {'native_name': 'multiregion'}], + 'GOOGLE': [{'native_name': 'multiregion'}] +} +manual_jobs = [] +ed_jobs = [] +tenants = [] + +# mock job items +for cloud in CLOUDS: + manual_jobs.append(Job(**{'id': str(uuid.uuid4()), + 'batch_job_id': str(uuid.uuid4()), + 'tenant_name': f'{cloud}-TEST', + 'customer_name': 'EPAM Systems', + 'created_at': '2023-10-10T10:10:10', + 'started_at': '2023-10-10T10:10:10', + 'submitted_at': '2023-10-10T10:10:10', + 'status': 'FAILED' + })) + + for _ in range(2): + ed_jobs.append(BatchResults(**{ + 'id': str(uuid.uuid4()), + 'status': 'SUCCEEDED', + 'tenant_name': f'{cloud}-TEST', + 'customer_name': 'EPAM Systems', + 'registration_start': '2023-10-11T11:11:00', + 'submitted_at': '2023-10-11T11:11:00' + })) + + # mock tenant + tenants.append(Tenant(**{ + 'name': f'{cloud}-TEST', + 'display_name': 'test', + 'display_name_to_lower': 'test', + 'is_active': True, + 'customer_name': 'EPAM Systems', + 'cloud': cloud, + 'activation_date': '2022-01-01T10:00:00', + 'project': f'{cloud}-1234567890123', + 'regions': regions[cloud], + 'account_number': '109876543210' if cloud == 'GOOGLE' else None + })) + + +class TestMetricsUpdater(TestCase): + HANDLER_IMPORT_PATH = 'lambdas.custodian_metrics_updater.handler' + TENANT_IMPORT_PATH = \ + 'lambdas.custodian_metrics_updater.processors.tenant_metrics_processor' + TENANT_GROUP_IMPORT_PATH = 'lambdas.custodian_metrics_updater.processors.tenant_group_metrics_processor' + TOP_IMPORT_PATH = \ + 'lambdas.custodian_metrics_updater.processors.top_metrics_processor' + DIFFERENCE_IMPORT_PATH = 'lambdas.custodian_metrics_updater.processors.metric_difference_processor' + SERVICE_PROVIDER_PATH = 'services.service_provider' + + def setUp(self) -> None: + super().setUp() + logging.disable(logging.NOTSET) + + self.tenants = tenants + self.mappings = {} + + self.service_provider = importlib.import_module( + self.SERVICE_PROVIDER_PATH).ServiceProvider() + self.service_provider.settings_service = MagicMock() + self.service_provider.settings_service.get_report_date_marker = MagicMock( + return_value={'current_week_date': CURRENT_WEEK_DATE, + 'last_week_date': LAST_WEEK_DATE} + ) + self.tenant_handler = importlib.import_module( + self.TENANT_IMPORT_PATH) + self.tenant_group_handler = importlib.import_module( + self.TENANT_GROUP_IMPORT_PATH) + self.top_handler = importlib.import_module(self.TOP_IMPORT_PATH) + self.difference_handler = importlib.import_module( + self.DIFFERENCE_IMPORT_PATH) + self.handler = importlib.import_module(self.HANDLER_IMPORT_PATH) + + mocked_response = MagicMock(last_evaluated_key=None) + mocked_response.last_evaluated_key = None + tenants_item = MagicMock() + tenants_item.as_dict = MagicMock(return_value={ + "AWS-1234567890123": { + "failed_scans": 5, + "succeeded_scans": 5 + }} + ) + job_stats = MagicMock() + job_stats.cloud = 'aws' + job_stats.tenants.attribute_values = { + "AWS-1234567890123": { + "failed_scans": 5, + "succeeded_scans": 5 + } + } + job_stats.attribute_values = { + "id": str(uuid.uuid4()), + "cloud": "aws", + "customer_name": "EPAM Systems", + "failed": 5, + "from_date": "2023-10-10", + "last_scan_date": "2023-09-26T16:18:00.185909Z", + "succeeded": 5, + "tenants": tenants_item, + "to_date": "2023-10-17" + } + self.service_provider.modular_client.customer_service().i_get_customer = MagicMock( + return_value=[self.default_customer()] + ) + + TenantMetrics.customer_date_index.query = MagicMock(return_value=[]) + CustomerMetrics.customer_date_index.query = MagicMock(return_value=[]) + + JobStatistics.customer_name_from_date_index.query = mocked_response() + JobStatistics.customer_name_from_date_index.query.return_value = mocked_response + + self.service_provider.report_service.s3_client = S3Client() + self.service_provider.mappings_collector._s3_settings_service._s3 = S3Client() + self.service_provider.modular_client.tenant_service().get = MagicMock() + self.service_provider.modular_client.tenant_service().get.side_effect = self.get_tenant # todo seems like side_effect is used for other purposes + self.service_provider.modular_client.tenant_service().i_get_by_acc = MagicMock() + self.service_provider.modular_client.tenant_service().i_get_by_acc.side_effect = self.get_tenant_by_acc + self.service_provider.modular_client.tenant_service().i_get_by_accN = MagicMock() + self.service_provider.modular_client.tenant_service().i_get_by_accN.side_effect = self.get_tenant_by_accN + self.service_provider.batch_results_service. \ + get_between_period_by_customer = MagicMock(return_value=ed_jobs) + self.service_provider.job_service.get_customer_jobs_between_period = \ + MagicMock(return_value=manual_jobs) + self.service_provider.job_service.get_customer_jobs = MagicMock( + return_value=manual_jobs + ) + self.service_provider.job_statistics_service.save = MagicMock( + return_value={} + ) + self.service_provider.job_statistics_service. \ + get_by_customer_and_date = MagicMock(return_value=[job_stats]) + self.service_provider.lambda_client.invoke_function_async = MagicMock() + self.service_provider.lambda_client.invoke_function_async.side_effect = self.invoke_lambda + self.service_provider.tenant_metrics_service.batch_save = MagicMock( + return_value={} + ) + self.service_provider.customer_metrics_service.batch_save = \ + MagicMock(return_value={}) + self.service_provider.environment_service.get_metrics_bucket_name = \ + MagicMock(return_value='metrics') + self.service_provider.batch_results_service.get_by_customer_name = \ + MagicMock(return_value=list(ed_jobs)) + self.service_provider.job_service.get_by_customer_name = MagicMock( + return_value=list(manual_jobs)) + self.service_provider.batch_results_service.get_by_tenant_name = \ + MagicMock(return_value=list(ed_jobs)) + self.service_provider.job_service.get_by_tenant_name = MagicMock( + return_value=list(manual_jobs)) + + self.HANDLER = self.handler.MetricsUpdater( + lambda_client=MagicMock() + ) + self.TENANT_HANDLER = self.tenant_handler.TenantMetrics( + ambiguous_job_service=self.service_provider.ambiguous_job_service, + report_service=self.service_provider.report_service, + s3_client=S3Client(), + environment_service=self.service_provider.environment_service, + settings_service=self.service_provider.settings_service, + modular_client=self.service_provider.modular_client, + coverage_service=self.service_provider.coverage_service, + mappings_collector=self.service_provider.mappings_collector, + metrics_service=self.service_provider.metrics_service, + job_statistics_service=self.service_provider.job_statistics_service, + license_service=MagicMock(), + platform_service=MagicMock() + ) + self.TENANT_GROUP_HANDLER = self.tenant_group_handler.TenantGroupMetrics( + s3_client=S3Client(), + environment_service=self.service_provider.environment_service, + settings_service=self.service_provider.settings_service, + modular_client=self.service_provider.modular_client, + mappings_collector=self.service_provider.mappings_collector + ) + self.TOP_HANDLER = self.top_handler.TopMetrics( + s3_client=S3Client(), + environment_service=self.service_provider.environment_service, + modular_client=MagicMock(), + job_statistics_service=self.service_provider.job_statistics_service, + tenant_metrics_service=self.service_provider.tenant_metrics_service, + customer_metrics_service=self.service_provider.customer_metrics_service + ) + self.DIFFERENCE_HANDLER = self.difference_handler.TenantMetricsDifference( + s3_client=S3Client(), + environment_service=self.service_provider.environment_service, + settings_service=self.service_provider.settings_service + ) + + @staticmethod + def default_customer(): + customer = MagicMock() + customer.name = 'EPAM Systems' + return customer + + @staticmethod + def get_tenant(tenant: str): + for t in tenants: + if t.name == tenant: + return t + return + + def get_tenant_by_acc(self, acc: str, active: bool = True, + attributes_to_get=None): + for t in self.tenants: + if t.project == acc: + return [t] + return + + def get_tenant_by_accN(self, acc: str): + for t in self.tenants: + if t.account_number == acc: + yield t + yield + + def invoke_lambda(self, event): + if event.get('data_type') == 'tenant_groups': + return self.TENANT_GROUP_HANDLER.lambda_handler() + elif event.get('data_type') == 'customer': + return self.TOP_HANDLER.lambda_handler() + elif event.get('data_type') == 'difference': + return self.TOP_HANDLER.lambda_handler() + + +class S3Client(metaclass=SingletonMeta): + def __init__(self): + self.put_object_mapping = {} + self.deleted_items = [] + + def gz_get_json(self, bucket: str, key: str) -> Dict | List | None: + # TODO we should not change inner logic, only mock the data. + if bucket == 'statistics': + with open(os.path.join(pathlib.Path(__file__).parent.resolve(), + 'mock_files', bucket, 'statistics.json'), + 'r') as j: + data = json.loads(j.read()) + else: + key = key.split('/') + with open(os.path.join(pathlib.Path(__file__).parent.resolve(), + 'mock_files', bucket, *key), 'r') as j: + data = json.loads(j.read()) + return data + + def gz_get_object(self, bucket: str, key: str) -> Dict | List | None: + # TODO we should not change inner logic, only mock the data. + if bucket == 'statistics': + with open(os.path.join(pathlib.Path(__file__).parent.resolve(), + 'mock_files', bucket, 'statistics.json'), + 'r') as j: + data = json.loads(j.read()) + else: + key = key.split('/') + with open(os.path.join(pathlib.Path(__file__).parent.resolve(), + 'mock_files', bucket, *key), 'r') as j: + data = json.loads(j.read()) + return data + + def put_object(self, bucket: str, key: str, body, + content_type: str = None, content_encoding: str = None): + if '.gz' not in key: + key += '.gz' + self.put_object_mapping.update({(bucket, key): body}) + + def gz_put_object(self, bucket: str, key: str, body, + gz_buffer=None, content_type: str = None, + content_encoding: str = None): + if '.gz' not in key: + key += '.gz' + self.put_object_mapping.update({(bucket, key): body}) + + def gz_put_json(self, bucket: str, key: str, obj): + if '.gz' not in key: + key += '.gz' + self.put_object_mapping.update({(bucket, key): obj}) + + def gz_delete_object(self, bucket: str, key: str): + self.deleted_items.append(key) + + def list_dir(self, bucket_name: str, key=None, page_size=None, + limit=None, start_after=None): + return [] diff --git a/tests/tests_models/__init__.py b/tests/tests_models/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/tests_models/test_rule_comment.py b/tests/tests_models/test_rule_comment.py new file mode 100644 index 000000000..6f4414c03 --- /dev/null +++ b/tests/tests_models/test_rule_comment.py @@ -0,0 +1,41 @@ +from helpers.constants import Cloud +from models.rule import RuleIndex + + +class TestRuleIndex: + def test_cloud(self): + assert RuleIndex('010000000000').cloud == Cloud.AWS + assert RuleIndex('020000000000').cloud == Cloud.AZURE + assert RuleIndex('030000000000').cloud == Cloud.GOOGLE + + assert RuleIndex('000100000000').cloud == Cloud.KUBERNETES + assert RuleIndex('000200000000').cloud == Cloud.KUBERNETES + assert RuleIndex('000300000000').cloud == Cloud.KUBERNETES + + def test_category(self): + assert RuleIndex('010000000000').category == 'FinOps' + assert RuleIndex('010030000000').category == 'Network security' + assert RuleIndex('010050000000').category == 'High availability' + + def test_service_section(self): + assert RuleIndex('010050220000').service_section == 'General Policies' + assert (RuleIndex('010050140000').service_section == + 'Application Integration') + assert (RuleIndex('010050180000').service_section == + 'Microsoft Defender for Cloud') + + def test_source(self): + assert (RuleIndex('010050221700').source == + 'CIS Oracle Database 19 Benchmark v1.0.0') + assert (RuleIndex('010050222400').source == + 'CIS RedHat OpenShift Container Platform Benchmark v1.4.0') + + def test_has_customization(self): + assert RuleIndex('010050221710').has_customization + assert not RuleIndex('010050221700').has_customization + + def test_global(self): + assert not RuleIndex('010050221710').is_global + assert RuleIndex('010050221701').is_global + assert RuleIndex('0100502217').is_global + assert RuleIndex('').is_global diff --git a/tests/tests_services/__init__.py b/tests/tests_services/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/tests_services/policies.json b/tests/tests_services/policies.json new file mode 100644 index 000000000..70ccd51d1 --- /dev/null +++ b/tests/tests_services/policies.json @@ -0,0 +1,14 @@ +[ + { + "customer": "EPAM Systems", + "effect": "allow", + "permissions": ["*:*"], + "tenants": ["AWS-MSTR-QA"] + }, + { + "customer": "EPAM Systems", + "effect": "deny", + "permissions": ["job:submit"], + "tenants": ["AWS-EPMC-EOOS"] + } +] \ No newline at end of file diff --git a/tests/tests_services/test_bucket_keys_builders.py b/tests/tests_services/test_bucket_keys_builders.py new file mode 100644 index 000000000..5c79dbb6c --- /dev/null +++ b/tests/tests_services/test_bucket_keys_builders.py @@ -0,0 +1,168 @@ +from datetime import datetime, timezone + +import pytest +from modular_sdk.models.parent import Parent +from modular_sdk.models.tenant import Tenant + +from helpers.constants import Cloud +from models.batch_results import BatchResults +from models.job import Job +from services.platform_service import Platform +from services.reports_bucket import TenantReportsBucketKeysBuilder, \ + ReportsBucketKeysBuilder, PlatformReportsBucketKeysBuilder, \ + StatisticsBucketKeysBuilder + + +@pytest.fixture +def aws_tenant() -> Tenant: + return Tenant( + name='TEST-TENANT', + display_name='Test tenant', + is_active=True, + customer_name='TEST-CUSTOMER', + cloud='AWS', + project='123123123123' + ) + + +@pytest.fixture +def k8s_platform() -> Platform: + return Platform( + parent=Parent( + parent_id='platform_id', + customer_id='TEST-CUSTOMER', + type='PLATFORM_K8S', + description='Test platform', + meta={ + 'name': 'test', + 'region': 'eu-west-1', + 'type': 'EKS' + }, + is_deleted=False, + type_scope='PLATFORM_K8S#SPECIFIC#TEST-TENANT' + ) + ) + + +@pytest.fixture +def standard_job() -> Job: + return Job( + id='job_id', + tenant_name='TEST-TENANT', + customer_name='TEST-CUSTOMER', + submitted_at='2023-11-27T14:29:08.694447Z', + status='SUCCEEDED', + ) + + +@pytest.fixture +def platform_job() -> Job: + return Job( + id='job_id', + tenant_name='TEST-TENANT', + customer_name='TEST-CUSTOMER', + submitted_at='2023-11-27T14:29:08.694447Z', + status='SUCCEEDED', + platform_id='platform_id' + ) + + +@pytest.fixture +def ed_job() -> BatchResults: + return BatchResults( + id='job_id', + tenant_name='TEST-TENANT', + customer_name='TEST-CUSTOMER', + submitted_at='2023-11-27T14:29:08.694447Z', + status='SUCCEEDED', + ) + + +@pytest.fixture +def tenant_reports_builder(aws_tenant) -> TenantReportsBucketKeysBuilder: + return TenantReportsBucketKeysBuilder(aws_tenant) + + +@pytest.fixture +def platform_reports_builder(k8s_platform) -> PlatformReportsBucketKeysBuilder: + return PlatformReportsBucketKeysBuilder(k8s_platform) + + +class TestTenantReportsBucketKeyBuilder: + def test_cloud(self, tenant_reports_builder): + assert tenant_reports_builder.cloud == Cloud.AWS + + def test_job_result(self, tenant_reports_builder, standard_job): + res = tenant_reports_builder.job_result(standard_job) + assert res == 'raw/TEST-CUSTOMER/AWS/123123123123/jobs/standard/2023-11-27-14/job_id/result/' + + def test_ed_job_result(self, tenant_reports_builder, ed_job): + res = tenant_reports_builder.ed_job_result(ed_job) + assert res == 'raw/TEST-CUSTOMER/AWS/123123123123/jobs/event-driven/2023-11-27-14/job_id/result/' + + def test_ed_job_difference(self, tenant_reports_builder, ed_job): + res = tenant_reports_builder.ed_job_difference(ed_job) + assert res == 'raw/TEST-CUSTOMER/AWS/123123123123/jobs/event-driven/2023-11-27-14/job_id/difference/' + + def test_latest_key(self, tenant_reports_builder): + res = tenant_reports_builder.latest_key() + assert res == 'raw/TEST-CUSTOMER/AWS/123123123123/latest/' + + def test_snapshots_folder(self, tenant_reports_builder): + res = tenant_reports_builder.snapshots_folder() + assert res == 'raw/TEST-CUSTOMER/AWS/123123123123/snapshots/' + + def test_snapshot_key(self, tenant_reports_builder): + now = datetime.now(tz=timezone.utc) + res = tenant_reports_builder.snapshot_key(now) + assert res == f'raw/TEST-CUSTOMER/AWS/123123123123/snapshots/{now.strftime("%Y-%m-%d-%H")}/' + + def test_one_time_on_demand(self): + res = ReportsBucketKeysBuilder.one_time_on_demand() + assert res.startswith('on-demand/') + + +class TestPlatformReportsBucketKeyBuilder: + def test_cloud(self, platform_reports_builder): + assert platform_reports_builder.cloud == Cloud.KUBERNETES + + def test_job_result(self, platform_reports_builder, platform_job): + res = platform_reports_builder.job_result(platform_job) + assert res == 'raw/TEST-CUSTOMER/KUBERNETES/test-eu-west-1/jobs/standard/2023-11-27-14/job_id/' + + def test_ed_job(self, platform_reports_builder, ed_job): + with pytest.raises(NotImplementedError): + platform_reports_builder.ed_job_result(ed_job) + with pytest.raises(NotImplementedError): + platform_reports_builder.ed_job_difference(ed_job) + + def test_latest_key(self, platform_reports_builder): + res = platform_reports_builder.latest_key() + assert res == 'raw/TEST-CUSTOMER/KUBERNETES/test-eu-west-1/latest/' + + def test_snapshots_folder(self, platform_reports_builder): + res = platform_reports_builder.snapshots_folder() + assert res == 'raw/TEST-CUSTOMER/KUBERNETES/test-eu-west-1/snapshots/' + + +class TestStatisticsBucketKeyBuilder: + def test_job_statistics(self, standard_job): + res = StatisticsBucketKeysBuilder.job_statistics(standard_job) + assert res == 'job-statistics/standard/job_id/statistics.json' + + def test_ed_job_statistics(self, ed_job): + res = StatisticsBucketKeysBuilder.job_statistics(ed_job) + assert res == 'job-statistics/event-driven/job_id/statistics.json' + + def test_report_statistics(self): + now = datetime.now(timezone.utc) + res = StatisticsBucketKeysBuilder.report_statistics( + now=now, + customer='TEST-CUSTOMER' + ) + assert res == f'report-statistics/diagnostic/TEST-CUSTOMER/{now.strftime("%Y-%m")}/diagnostic_report.json' + + def test_xray_log(self): + now = datetime.now(timezone.utc) + res = StatisticsBucketKeysBuilder.xray_log('job_id') + assert res == f'xray/executor/{now.year}/{now.month}/{now.day}/job_id.log' diff --git a/tests/tests_services/test_jwt_management_client.py b/tests/tests_services/test_jwt_management_client.py new file mode 100644 index 000000000..31e4e998b --- /dev/null +++ b/tests/tests_services/test_jwt_management_client.py @@ -0,0 +1,85 @@ +import base64 +import json +import time + +import pytest +from jwcrypto import jwk, jwt + +from services.clients.jwt_management_client import JWTManagementClient + + +@pytest.fixture +def ec_key() -> jwk.JWK: + return jwk.JWK.generate(kty='EC', crv='P-521') + + +@pytest.fixture +def rsa_key() -> jwk.JWK: + return jwk.JWK.generate(kty='RSA', size=2048) + + +@pytest.fixture +def ec_key_pem(ec_key) -> bytes: + return ec_key.export_to_pem(private_key=True, password=None) + + +@pytest.fixture +def rsa_key_pem(rsa_key) -> bytes: + return rsa_key.export_to_pem(private_key=True, password=None) + + +@pytest.fixture +def ec_key_pem_b64(ec_key_pem) -> str: + return base64.b64encode(ec_key_pem).decode() + + +@pytest.fixture +def rsa_key_pem_b64(rsa_key_pem) -> str: + return base64.b64encode(rsa_key_pem).decode() + + +def test_init(rsa_key, ec_key): + cl = JWTManagementClient(rsa_key) + assert cl.key_type == 'RSA' + assert cl.key_alg == 'PS256' + cl = JWTManagementClient(ec_key) + assert cl.key_type == 'EC' + assert cl.key_alg == 'ES512' + + with pytest.raises(AssertionError): + JWTManagementClient(jwk.JWK.generate(kty='oct', size=256)) + + +def test_from_pem(ec_key_pem, rsa_key_pem): + assert JWTManagementClient.from_pem(ec_key_pem).key_type == 'EC' + assert JWTManagementClient.from_pem(rsa_key_pem).key_type == 'RSA' + + +def test_from_pem_b64(ec_key_pem_b64, rsa_key_pem_b64): + assert JWTManagementClient.from_b64_pem(ec_key_pem_b64).key_type == 'EC' + assert JWTManagementClient.from_b64_pem(rsa_key_pem_b64).key_type == 'RSA' + + +def test_sign_verify(ec_key_pem_b64): + cl = JWTManagementClient.from_b64_pem(ec_key_pem_b64) + exp = int(time.time()) + 100 + signed = cl.sign({'key': 'value'}, exp=exp) + dct = json.loads(cl.verify(signed).claims) + assert dct['key'] == 'value' + assert dct['exp'] == exp + assert 'iat' in dct + + +def test_verify_expired(ec_key_pem_b64): + cl = JWTManagementClient.from_b64_pem(ec_key_pem_b64) + exp = int(time.time()) - 100 + signed = cl.sign({'key': 'value'}, exp=exp) + with pytest.raises(jwt.JWTExpired) as e: + cl.verify(signed) + + +def test_encrypt_decrypt_dict(ec_key_pem_b64): + cl = JWTManagementClient.from_b64_pem(ec_key_pem_b64) + assert cl.decrypt_dict(cl.encrypt_dict({'key': 'value'})) == { + 'key': 'value' + } diff --git a/tests/tests_services/test_license_manager_token.py b/tests/tests_services/test_license_manager_token.py new file mode 100644 index 000000000..b9d2bd3b4 --- /dev/null +++ b/tests/tests_services/test_license_manager_token.py @@ -0,0 +1,84 @@ +import time +import random +import os + +import pytest +import json +import base64 + +from services.license_manager_token import LicenseManagerToken +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives import serialization + +_pycryptodome_installed = True +try: + # Rule engine does not use Pycryptodome, but License manager does. + # It requires custom jwt token format, so here we sign a token using + # Cryptography and verify it using Pycryptodome + from Crypto.Hash import SHA256 + from Crypto.PublicKey import ECC + from Crypto.Signature import DSS +except ImportError: + _pycryptodome_installed = False + SHA256, ECC, DSS = None, None, None + + +@pytest.fixture +def private_key() -> bytes: + """ + Generates elliptic curve P-521 private key using cryptography module and + returns it in PEM format + :return: + """ + return ec.generate_private_key(ec.SECP521R1()).private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=serialization.NoEncryption() + ) + + +def test_token_payload(private_key: bytes): + customer = 'Example' + lifetime = 10 + kid = 'kid' + token = LicenseManagerToken( + customer=customer, + lifetime=lifetime, + kid=kid, + private_pem=private_key + ).produce() + assert token + header, payload, _ = token.split('.') + header = json.loads(base64.urlsafe_b64decode(header)) + payload = json.loads(base64.urlsafe_b64decode(payload)) + assert header['kid'] == kid + assert header['alg'] == 'ECC:p521_DSS_SHA:256' + assert header['typ'] == 'client-token' + assert payload['customer'] == customer + assert payload['exp'] <= int(time.time()) + lifetime * 60 + + +@pytest.mark.skipif(not _pycryptodome_installed, + reason='Pycryptodome is not installed') +def test_token_verify(private_key: bytes): + """ + Verifies the token the way LM does + """ + token = LicenseManagerToken( + customer='some data', + kid='example', + lifetime=10, + private_pem=private_key + ) + + verifier = DSS.new(ECC.import_key(private_key), 'deterministic-rfc6979') + for _ in range(50): + # next line does not matter, just sets some random data + token.customer = base64.b64encode(os.urandom(random.randint(0, 50))).decode() # noqa + message, signature = map(str.encode, + token.produce().rsplit('.', maxsplit=1)) + h = SHA256.new(message) + try: + verifier.verify(h, base64.urlsafe_b64decode(signature)) + except ValueError: + pytest.fail(f'Signature is invalid for {token.customer}') diff --git a/tests/tests_services/test_license_service.py b/tests/tests_services/test_license_service.py new file mode 100644 index 000000000..cd1f3f9fe --- /dev/null +++ b/tests/tests_services/test_license_service.py @@ -0,0 +1,62 @@ +from services.license_service import LicenseService, PERMITTED_ATTACHMENT, \ + PROHIBITED_ATTACHMENT + + +def test_is_subject_applicable(): + class License: + __slots__ = ('customers',) + + def __init__(self, customers: dict): + self.customers = customers + + lic = License({ + 'customer': { + 'tenant_license_key': 'mock', + 'tenants': ['tenant1', 'tenant2'], + 'attachment_model': PERMITTED_ATTACHMENT + } + }) + assert LicenseService.is_subject_applicable(lic, 'customer', 'tenant1') + assert LicenseService.is_subject_applicable(lic, 'customer', 'tenant2') + assert LicenseService.is_subject_applicable(lic, 'customer') + assert not LicenseService.is_subject_applicable(lic, 'customer', 'tenant3') + assert not LicenseService.is_subject_applicable(lic, 'cst', 'tenant3') + + lic = License({ + 'customer': { + 'tenant_license_key': 'mock', + 'tenants': [], + 'attachment_model': PERMITTED_ATTACHMENT + } + }) + assert LicenseService.is_subject_applicable(lic, 'customer', 'tenant1') + assert LicenseService.is_subject_applicable(lic, 'customer', 'tenant2') + assert LicenseService.is_subject_applicable(lic, 'customer') + assert LicenseService.is_subject_applicable(lic, 'customer', 'tenant3') + assert not LicenseService.is_subject_applicable(lic, 'cst', 'tenant3') + + lic = License({ + 'customer': { + 'tenant_license_key': 'mock', + 'tenants': ['tenant1', 'tenant2'], + 'attachment_model': PROHIBITED_ATTACHMENT + } + }) + assert not LicenseService.is_subject_applicable(lic, 'customer', 'tenant1') + assert not LicenseService.is_subject_applicable(lic, 'customer', 'tenant2') + assert LicenseService.is_subject_applicable(lic, 'customer') + assert LicenseService.is_subject_applicable(lic, 'customer', 'tenant3') + assert not LicenseService.is_subject_applicable(lic, 'cst', 'tenant3') + + lic = License({ + 'customer': { + 'tenant_license_key': 'mock', + 'tenants': [], + 'attachment_model': PROHIBITED_ATTACHMENT + } + }) + assert not LicenseService.is_subject_applicable(lic, 'customer', 'tenant1') + assert not LicenseService.is_subject_applicable(lic, 'customer', 'tenant2') + assert LicenseService.is_subject_applicable(lic, 'customer') + assert not LicenseService.is_subject_applicable(lic, 'customer', 'tenant3') + assert not LicenseService.is_subject_applicable(lic, 'cst', 'tenant3') diff --git a/tests/tests_services/test_modular_helpers.py b/tests/tests_services/test_modular_helpers.py new file mode 100644 index 000000000..c358f2a3e --- /dev/null +++ b/tests/tests_services/test_modular_helpers.py @@ -0,0 +1,218 @@ +import uuid +from unittest.mock import Mock, create_autospec + +import pytest +from modular_sdk.commons.constants import ParentScope, ParentType +from modular_sdk.models.parent import Parent +from modular_sdk.services.parent_service import ParentService + +from services.modular_helpers import (ResolveParentsPayload, + split_into_to_keep_to_delete, + get_activation_dto, get_main_scope) + + +@pytest.fixture +def parent_service() -> ParentService: + """ + Returns a mocked parent service instance + :return: + """ + cl = create_autospec(ParentService) + return cl( + tenant_service=Mock(), + customer_service=Mock() + ) + + +@pytest.fixture +def parent_factory(): + def _build(scope, tenant_cloud=None): + tp = ParentType.CUSTODIAN_LICENSES.value # just an example + match scope: + case ParentScope.SPECIFIC | ParentScope.DISABLED: + assert tenant_cloud, 'tenant is required' + type_scope = f'{tp}#{scope.value}#{tenant_cloud}' + case ParentScope.ALL if tenant_cloud: + type_scope = f'{tp}#{scope.value}#{tenant_cloud}' + case _: # ALL and no cloud + type_scope = f'{tp}#{scope.value}#' + return Parent( + parent_id=str(uuid.uuid4()), + customer_id='mock', + application_id='mock', + type=tp, + created_by='mock', + description='mock', + meta='mock', + is_deleted=False, + creation_timestamp=123, + type_scope=type_scope + ) + + return _build + + +def test_split_parents(parent_factory): + p1 = parent_factory(ParentScope.SPECIFIC, 'tenant1') + p2 = parent_factory(ParentScope.SPECIFIC, 'tenant2') + p3 = parent_factory(ParentScope.SPECIFIC, 'tenant3') + p4 = parent_factory(ParentScope.DISABLED, 'tenant4') + p5 = parent_factory(ParentScope.DISABLED, 'tenant5') + p6 = parent_factory(ParentScope.ALL, 'AWS') + p7 = parent_factory(ParentScope.ALL, 'AZURE') + p8 = parent_factory(ParentScope.ALL) + + payload = ResolveParentsPayload( + parents=[p1, p2, p3], + tenant_names=set(), + exclude_tenants=set(), + clouds=set(), + all_tenants=True + ) + to_keep, to_delete = split_into_to_keep_to_delete(payload) + assert len(to_keep) == 0 + assert to_delete == {p1, p2, p3} + assert payload.all_tenants + + payload = ResolveParentsPayload( + parents=[p1, p2], + tenant_names={'tenant1', 'tenant3'}, + exclude_tenants=set(), + clouds=set(), + all_tenants=False + ) + to_keep, to_delete = split_into_to_keep_to_delete(payload) + assert to_keep == {p1} + assert to_delete == {p2} + assert payload.tenant_names == {'tenant3'} + + payload = ResolveParentsPayload( + parents=[p6], + tenant_names=set(), + exclude_tenants={'tenant5'}, + clouds=set(), + all_tenants=True + ) + to_keep, to_delete = split_into_to_keep_to_delete(payload) + assert len(to_keep) == 0 + assert to_delete == {p6} + assert payload.exclude_tenants == {'tenant5'} + assert payload.all_tenants + + payload = ResolveParentsPayload( + parents=[p5, p6, p7], + tenant_names=set(), + exclude_tenants=set(), + clouds={'AWS'}, + all_tenants=True + ) + to_keep, to_delete = split_into_to_keep_to_delete(payload) + assert to_keep == {p6} + assert to_delete == {p5, p7} + assert payload.clouds == set() + assert not payload.all_tenants + + payload = ResolveParentsPayload( + parents=[p4, p8], + tenant_names={'tenant1', 'tenant2'}, + exclude_tenants=set(), + clouds=set(), + all_tenants=False + ) + to_keep, to_delete = split_into_to_keep_to_delete(payload) + assert len(to_keep) == 0 + assert to_delete == {p4, p8} + assert payload.tenant_names == {'tenant1', 'tenant2'} + assert not payload.all_tenants + + +def test_get_activation_dto(parent_factory): + p1 = parent_factory(ParentScope.SPECIFIC, 'tenant1') + p2 = parent_factory(ParentScope.SPECIFIC, 'tenant2') + p3 = parent_factory(ParentScope.SPECIFIC, 'tenant3') + p4 = parent_factory(ParentScope.DISABLED, 'tenant4') + p5 = parent_factory(ParentScope.DISABLED, 'tenant5') + p6 = parent_factory(ParentScope.ALL, 'AWS') + p7 = parent_factory(ParentScope.ALL, 'AZURE') + p8 = parent_factory(ParentScope.ALL) + assert get_activation_dto([p1, p2, p3]) == { + 'activated_for_all': False, + 'excluding': [], + 'activated_for': ['tenant1', 'tenant2', 'tenant3'] + } + assert get_activation_dto([p6]) == { + 'activated_for_all': True, + 'within_clouds': ['AWS'], + 'excluding': [], + } + assert get_activation_dto([p6, p7]) == { + 'activated_for_all': True, + 'within_clouds': ['AWS', 'AZURE'], + 'excluding': [], + } + assert get_activation_dto([p6, p7, p5]) == { + 'activated_for_all': True, + 'within_clouds': ['AWS', 'AZURE'], + 'excluding': ['tenant5'], + } + assert get_activation_dto([p8]) == { + 'activated_for_all': True, + 'excluding': [] + } + assert get_activation_dto([p8, p4, p5]) == { + 'activated_for_all': True, + 'excluding': ['tenant4', 'tenant5'] + } + + +def test_parents_payload_from_parents_list(parent_factory): + p1 = parent_factory(ParentScope.SPECIFIC, 'tenant1') + p2 = parent_factory(ParentScope.SPECIFIC, 'tenant2') + p3 = parent_factory(ParentScope.SPECIFIC, 'tenant3') + p4 = parent_factory(ParentScope.DISABLED, 'tenant4') + p5 = parent_factory(ParentScope.DISABLED, 'tenant5') + p6 = parent_factory(ParentScope.ALL, 'AWS') + p7 = parent_factory(ParentScope.ALL, 'AZURE') + p8 = parent_factory(ParentScope.ALL) + payload = ResolveParentsPayload.from_parents_list([ + p1, p2, p3 + ]) + assert not payload.all_tenants + assert payload.tenant_names == {'tenant1', 'tenant2', 'tenant3'} + assert payload.exclude_tenants == set() + assert payload.clouds == set() + to_keep, to_delete = split_into_to_keep_to_delete(payload) + assert to_delete == set() + assert to_keep == {p1, p2, p3} + assert not payload.tenant_names + assert not payload.exclude_tenants + assert not payload.clouds + assert not payload.all_tenants + + payload = ResolveParentsPayload.from_parents_list([ + p4, p5, p6, p7 + ]) + assert payload.all_tenants + assert payload.tenant_names == set() + assert payload.exclude_tenants == {'tenant4', 'tenant5'} + assert payload.clouds == {'AWS', 'AZURE'} + to_keep, to_delete = split_into_to_keep_to_delete(payload) + assert to_delete == set() + assert to_keep == {p4, p5, p6, p7} + assert not payload.tenant_names + assert not payload.exclude_tenants + assert not payload.clouds + assert not payload.all_tenants # bug not in test + + +def test_get_main_scope(parent_factory): + p1 = parent_factory(ParentScope.SPECIFIC, 'tenant1') + p2 = parent_factory(ParentScope.SPECIFIC, 'tenant2') + p5 = parent_factory(ParentScope.DISABLED, 'tenant5') + p6 = parent_factory(ParentScope.ALL, 'AWS') + p8 = parent_factory(ParentScope.ALL) + assert get_main_scope([]) == ParentScope.SPECIFIC + assert get_main_scope([p1, p2]) == ParentScope.SPECIFIC + assert get_main_scope([p6, p5]) == ParentScope.ALL + assert get_main_scope([p8]) == ParentScope.ALL + assert get_main_scope([p8]) == ParentScope.ALL diff --git a/tests/tests_services/test_openapi_spec_generator.py b/tests/tests_services/test_openapi_spec_generator.py new file mode 100644 index 000000000..73c87e2d6 --- /dev/null +++ b/tests/tests_services/test_openapi_spec_generator.py @@ -0,0 +1,291 @@ +from http import HTTPStatus + +from pydantic import BaseModel, Field + +from helpers.constants import HTTPMethod +from services.openapi_spec_generator import OpenApiGenerator, EndpointInfo + + +def test_generate(): + class User(BaseModel): + name: str + age: int = Field(ge=0) + + class Users(BaseModel): + items: list[User] + next_token: str | None + + class UsersList(BaseModel): + limit: int = Field(None, ge=1, le=50, + description='Limit number of returned users', + examples=[49, 50]) + next_token: str = Field(None) + + generator = OpenApiGenerator( + title='title', + description='description', + url=['http://127.0.0.1:8080'], + stages='dev', + version='1.2.3', + auto_tags=True, + endpoints=[ + EndpointInfo( + path='/users/{id}', + method=HTTPMethod.GET, + summary='Get a specific user', + description='long description', + responses=[(HTTPStatus.OK, User, 'User model'), + (HTTPStatus.NOT_FOUND, None, 'User not found')], + auth=False + ), + EndpointInfo( + path='/users', + method=HTTPMethod.GET, + summary='List all users', + description='long description', + request_model=UsersList, + responses=[(HTTPStatus.OK, Users, 'Many users')], + auth=False + ), + EndpointInfo( + path='/users', + method=HTTPMethod.POST, + summary='user-summary', + description='user-description', + request_model=User, + responses=[(HTTPStatus.CREATED, User, 'User model')], + tags=['Admin api', 'Users'] + ), + ] + ) + res = generator.generate() + assert res == { + 'openapi': '3.0.3', + 'info': { + 'title': 'title', + 'description': 'description', + 'version': '1.2.3', + 'license': { + 'name': 'Apache 2.0', + 'url': 'http://www.apache.org/licenses/LICENSE-2.0.html' + } + }, + 'servers': [ + { + 'url': 'http://127.0.0.1:8080/{stage}', + 'description': 'Main url', + 'variables': { + 'stage': { + 'default': 'dev', + 'description': 'Main stage' + } + } + } + ], + 'paths': { + '/users/{id}': { + 'get': { + 'tags': [ + 'Users' + ], + 'parameters': [ + { + 'name': 'id', + 'in': 'path', + 'required': True, + 'schema': { + 'type': 'string' + } + } + ], + 'summary': 'Get a specific user', + 'description': 'long description', + 'responses': { + '200': { + 'description': 'User model', + 'content': { + 'application/json': { + 'schema': { + '$ref': '#/components/schemas/User' + } + } + } + }, + '404': { + 'description': 'User not found' + } + } + } + }, + '/users': { + 'get': { + 'tags': [ + 'Users' + ], + 'summary': 'List all users', + 'description': 'long description', + 'parameters': [ + { + 'name': 'limit', + 'in': 'query', + 'required': False, + 'description': 'Limit number of returned users', + 'example': 49, + 'schema': { + 'default': None, + 'description': 'Limit number of returned users', + 'examples': [ + 49, + 50 + ], + 'maximum': 50, + 'minimum': 1, + 'title': 'Limit', + 'type': 'integer' + } + }, + { + 'name': 'next_token', + 'in': 'query', + 'required': False, + 'schema': { + 'default': None, + 'title': 'Next Token', + 'type': 'string' + } + } + ], + 'responses': { + '200': { + 'description': 'Many users', + 'content': { + 'application/json': { + 'schema': { + '$ref': '#/components/schemas/Users' + } + } + } + } + } + }, + 'post': { + 'tags': [ + 'Admin api', + 'Users' + ], + 'summary': 'user-summary', + 'description': 'user-description', + 'requestBody': { + 'content': { + 'application/json': { + 'schema': { + '$ref': '#/components/schemas/User' + } + } + }, + 'required': True + }, + 'security': [ + { + 'access_token': [] + } + ], + 'responses': { + '201': { + 'description': 'User model', + 'content': { + 'application/json': { + 'schema': { + '$ref': '#/components/schemas/User' + } + } + } + } + } + } + } + }, + 'tags': [ + { + 'name': 'Admin api' + }, + { + 'name': 'Users' + } + ], + 'components': { + 'schemas': { + 'User': { + 'properties': { + 'name': { + 'title': 'Name', + 'type': 'string' + }, + 'age': { + 'minimum': 0, + 'title': 'Age', + 'type': 'integer' + } + }, + 'required': [ + 'name', + 'age' + ], + 'title': 'User', + 'type': 'object' + }, + 'Users': { + 'properties': { + 'items': { + 'items': { + 'properties': { + 'name': { + 'title': 'Name', + 'type': 'string' + }, + 'age': { + 'minimum': 0, + 'title': 'Age', + 'type': 'integer' + } + }, + 'required': [ + 'name', + 'age' + ], + 'title': 'User', + 'type': 'object' + }, + 'title': 'Items', + 'type': 'array' + }, + 'next_token': { + 'anyOf': [ + { + 'type': 'string' + }, + { + 'type': 'null' + } + ], + 'title': 'Next Token' + } + }, + 'required': [ + 'items', + 'next_token' + ], + 'title': 'Users', + 'type': 'object' + } + }, + 'securitySchemes': { + 'access_token': { + 'type': 'apiKey', + 'description': 'Simple token authentication. The same as AWS Cognito and AWS Api gateway integration has', + 'name': 'Authorization', + 'in': 'header' + } + } + } + } diff --git a/tests/tests_services/test_rbac.py b/tests/tests_services/test_rbac.py new file mode 100644 index 000000000..e79423b2b --- /dev/null +++ b/tests/tests_services/test_rbac.py @@ -0,0 +1,203 @@ +from helpers.constants import Permission +from services.rbac_service import PolicyStruct, PolicyEffect, TenantsAccessPayload, TenantAccess + + +def test_is_forbidden(): + p = PolicyStruct( + customer='customer', + name='name', + effect=PolicyEffect.DENY, + permissions={Permission.CUSTOMER_DESCRIBE.value, + Permission.TENANT_SET_EXCLUDED_RULES.value}, + tenants={'*'}, + description='test' + ) + assert p.forbids(Permission.CUSTOMER_DESCRIBE) + assert p.forbids(Permission.TENANT_SET_EXCLUDED_RULES) + + p = PolicyStruct( + customer='customer', + name='name', + effect=PolicyEffect.DENY, + permissions={'tenant:*', 'customer:*'}, + tenants={'*'}, + description='test' + ) + assert p.forbids(Permission.CUSTOMER_DESCRIBE) + assert p.forbids(Permission.TENANT_SET_EXCLUDED_RULES) + + p = PolicyStruct( + customer='customer', + name='name', + effect=PolicyEffect.DENY, + permissions={'*:*'}, + tenants={'*'}, + description='test' + ) + assert p.forbids(Permission.CUSTOMER_DESCRIBE) + assert p.forbids(Permission.TENANT_SET_EXCLUDED_RULES) + + p = PolicyStruct( + customer='customer', + name='name', + effect=PolicyEffect.DENY, + permissions={'tenant:*'}, + tenants={'tenant1'}, + description='test' + ) + assert not p.forbids(Permission.CUSTOMER_DESCRIBE) + assert not p.forbids(Permission.TENANT_SET_EXCLUDED_RULES) + + p = PolicyStruct( + customer='customer', + name='name', + effect=PolicyEffect.DENY, + permissions={'tenant:*'}, + tenants={'tenant1'}, + description='test' + ) + assert not p.forbids(Permission.CUSTOMER_DESCRIBE) + assert not p.forbids(Permission.TENANT_SET_EXCLUDED_RULES) + + +def test_is_allowed(): + p = PolicyStruct( + customer='customer', + name='name', + effect=PolicyEffect.ALLOW, + permissions={Permission.CUSTOMER_DESCRIBE.value, + Permission.TENANT_SET_EXCLUDED_RULES.value}, + tenants={'*'}, + description='test' + ) + assert p.allows(Permission.CUSTOMER_DESCRIBE) + assert p.allows(Permission.TENANT_SET_EXCLUDED_RULES) + + p = PolicyStruct( + customer='customer', + name='name', + effect=PolicyEffect.ALLOW, + permissions={'tenant:*', 'customer:*'}, + tenants={'*'}, + description='test' + ) + assert p.allows(Permission.CUSTOMER_DESCRIBE) + assert p.allows(Permission.TENANT_SET_EXCLUDED_RULES) + + p = PolicyStruct( + customer='customer', + name='name', + effect=PolicyEffect.ALLOW, + permissions={'*:*'}, + tenants={'*'}, + description='test' + ) + assert p.allows(Permission.CUSTOMER_DESCRIBE) + assert p.allows(Permission.TENANT_SET_EXCLUDED_RULES) + + +def test_access_payload_all_allowed(): + p = TenantsAccessPayload((), False) + assert p.is_allowed_for_all_tenants() + assert p.is_allowed_for('tenant1') and p.is_allowed_for('tenant2') + allow, deny = p.allowed_denied() + assert allow is TenantsAccessPayload.ALL + assert deny == () + + +def test_access_payload_specific_allowed(): + p = TenantsAccessPayload(('tenant1', 'tenant2'), True) + assert not p.is_allowed_for_all_tenants() + assert p.is_allowed_for('tenant1') and p.is_allowed_for('tenant2') + assert not p.is_allowed_for('tenant3') + allow, deny = p.allowed_denied() + assert sorted(allow) == ['tenant1', 'tenant2'] + assert deny == () + + +def test_access_payload_specific_denied(): + p = TenantsAccessPayload(('tenant1', 'tenant2'), False) + assert not p.is_allowed_for_all_tenants() + assert not p.is_allowed_for('tenant1') and not p.is_allowed_for('tenant2') + assert p.is_allowed_for('tenant3') and p.is_allowed_for('tenant4') + allow, deny = p.allowed_denied() + assert allow is TenantsAccessPayload.ALL + assert sorted(deny) == ['tenant1', 'tenant2'] + + +def test_resolve_access_payload1(): + rb = TenantAccess() + rb.add(PolicyStruct( + customer='customer', + name='name', + effect=PolicyEffect.ALLOW, + permissions={Permission.CUSTOMER_DESCRIBE.value, + Permission.TENANT_SET_EXCLUDED_RULES.value}, + tenants={'*'}, + )) + rb.add(PolicyStruct( + customer='customer', + name='name', + effect=PolicyEffect.DENY, + permissions={Permission.TENANT_SET_EXCLUDED_RULES.value, + Permission.JOB_POST_K8S.value}, + tenants={'tenant1', 'tenant2'}, + )) + p = rb.resolve_payload(Permission.TENANT_SET_EXCLUDED_RULES) + assert p.is_allowed_for('tenant3') + assert not p.is_allowed_for('tenant1') and not p.is_allowed_for('tenant2') + allow, deny = p.allowed_denied() + assert allow is TenantsAccessPayload.ALL + assert sorted(deny) == ['tenant1', 'tenant2'] + + +def test_resolve_access_payload2(): + rb = TenantAccess() + rb.add(PolicyStruct( + customer='customer', + name='name', + effect=PolicyEffect.ALLOW, + permissions={Permission.TENANT_SET_EXCLUDED_RULES.value}, + tenants={'tenant1', 'tenant2'}, + )) + rb.add(PolicyStruct( + customer='customer', + name='name', + effect=PolicyEffect.DENY, + permissions={'*:*'}, + tenants={'tenant2'}, + )) + p = rb.resolve_payload(Permission.TENANT_SET_EXCLUDED_RULES) + assert not p.is_allowed_for('tenant2') + assert not p.is_allowed_for('tenant3') + assert p.is_allowed_for('tenant1') + assert not p.is_allowed_for_all_tenants() + allow, deny = p.allowed_denied() + assert sorted(allow) == ['tenant1'] + assert deny == () + + +def test_resolve_access_payload3(): + rb = TenantAccess() + rb.add(PolicyStruct( + customer='customer', + name='name', + effect=PolicyEffect.ALLOW, + permissions={Permission.TENANT_SET_EXCLUDED_RULES.value}, + tenants={'tenant1', 'tenant2'}, + )) + rb.add(PolicyStruct( + customer='customer', + name='name', + effect=PolicyEffect.DENY, + permissions={Permission.JOB_POST_K8S.value}, + tenants={'tenant2'}, + )) + p = rb.resolve_payload(Permission.JOB_POST_LICENSED) + assert not p.is_allowed_for('tenant2') + assert not p.is_allowed_for('tenant3') + assert not p.is_allowed_for('tenant1') + assert not p.is_allowed_for_all_tenants() + allow, deny = p.allowed_denied() + assert sorted(allow) == [] + assert deny == () diff --git a/tests/tests_services/test_rule_meta_service.py b/tests/tests_services/test_rule_meta_service.py new file mode 100644 index 000000000..0f1fcc2e1 --- /dev/null +++ b/tests/tests_services/test_rule_meta_service.py @@ -0,0 +1,34 @@ +from helpers.constants import RuleDomain +from services.rule_meta_service import RuleName + + +def test_rule_name(): + rule = RuleName('ecc-aws-001-blabla') + assert rule.vendor == 'ecc' + assert rule.cloud_raw == 'aws' + assert rule.cloud == RuleDomain.AWS + assert rule.number == '001' + assert rule.human_name == 'blabla' + assert rule.raw == 'ecc-aws-001-blabla' + + rule = RuleName('ecc') + assert rule.vendor == 'ecc' + assert rule.cloud_raw is None + assert rule.cloud is None + assert rule.number is None + assert rule.human_name is None + + rule = RuleName('ecc-azure') + assert rule.vendor == 'ecc' + assert rule.cloud_raw == 'azure' + assert rule.cloud == RuleDomain.AZURE + assert rule.number is None + assert rule.human_name is None + + rule = RuleName('ecc-gcp') + assert rule.cloud == RuleDomain.GCP + + rule = RuleName('ecc-k8s') + assert rule.cloud == RuleDomain.KUBERNETES + + assert RuleName('').vendor == '' diff --git a/tests/tests_services/test_sharding.py b/tests/tests_services/test_sharding.py new file mode 100644 index 000000000..7a3e068c9 --- /dev/null +++ b/tests/tests_services/test_sharding.py @@ -0,0 +1,259 @@ +import io +import operator +from unittest.mock import create_autospec, MagicMock + +import pytest + +from services.clients.s3 import S3Client +from services.sharding import (SingleShardDistributor, ShardPart, + AWSRegionDistributor, Shard, ShardsIterator, + ShardsS3IO, ShardsCollection) + + +@pytest.fixture +def make_shard_part(): + def _make_shard_part(location='global', + policy='example-policy', resources=None): + return ShardPart( + policy=policy, + location=location, + resources=resources or [] + ) + + return _make_shard_part + + +@pytest.fixture +def make_shard(make_shard_part): + def _make_shard() -> Shard: + shard = Shard() + part1 = make_shard_part('global', 'policy1', [{'k1': 'v1'}]) + part2 = make_shard_part('global', 'policy1', [{'k2': 'v2'}]) + part3 = make_shard_part('global', 'policy2', [{'k3': 'v3'}]) + shard.put(part1) + shard.put(part2) + shard.put(part3) + return shard + + return _make_shard + + +class TestShardDistributors: + def test_single(self, make_shard_part): + item = SingleShardDistributor() + assert item.distribute_part(make_shard_part('global')) == 0 + assert item.distribute_part(make_shard_part('eu-central-1')) == 0 + assert item.distribute_part(make_shard_part('eu-west-1')) == 0 + + def test_aws_region_1(self, make_shard_part): + item = AWSRegionDistributor(1) + assert item.distribute_part(make_shard_part('global')) == 0 + assert item.distribute_part(make_shard_part('eu-central-1')) == 0 + assert item.distribute_part(make_shard_part('eu-west-1')) == 0 + + def test_aws_region_2(self, make_shard_part): + item = AWSRegionDistributor(2) + assert item.distribute_part(make_shard_part('global')) == 0 + assert item.distribute_part(make_shard_part('eu-central-1')) == 0 + assert item.distribute_part(make_shard_part('eu-west-1')) == 1 + + def test_aws_region_10(self, make_shard_part): + item = AWSRegionDistributor(10) + assert item.distribute_part(make_shard_part('global')) == 0 + assert item.distribute_part(make_shard_part('eu-central-1')) == 2 + assert item.distribute_part(make_shard_part('eu-west-2')) == 4 + assert item.distribute_part(make_shard_part('eu-west-1')) == 3 + + def test_aws_region_31(self, make_shard_part): + item = AWSRegionDistributor(31) + assert item.distribute_part(make_shard_part('global')) == 0 + assert item.distribute_part(make_shard_part('us-east-1')) == 1 + assert item.distribute_part(make_shard_part('eu-central-1')) == 12 + assert item.distribute_part(make_shard_part('eu-west-2')) == 14 + assert item.distribute_part(make_shard_part('eu-west-1')) == 13 + + +class TestShardPart: + def test_serialize_deserialize(self, make_shard_part): + part = make_shard_part('eu-central-1') + serialized = part.serialize() + assert 'p' in serialized + assert 'l' in serialized + assert 'r' in serialized + assert 't' in serialized + + def test_ts(self, make_shard_part): + part1 = make_shard_part() + part2 = make_shard_part() + assert part1.timestamp <= part2.timestamp + + +class TestShard: + def test_put_priority(self, make_shard_part): + part1 = make_shard_part('global', 'policy', [{'k1': 'v1'}]) + part2 = make_shard_part('global', 'policy', [{'k2': 'v2'}]) + shard = Shard() + shard.put(part1) + shard.put(part2) + assert len(shard) == 1 + assert next(iter(shard)) is part2 + + def test_update_priority(self, make_shard_part): + part1 = make_shard_part('global', 'policy1', [{'k1': 'v1'}]) + part2 = make_shard_part('global', 'policy1', [{'k2': 'v2'}]) + part3 = make_shard_part('global', 'policy2', [{'k3': 'v3'}]) + shard1 = Shard() + shard2 = Shard() + shard1.put(part1) + shard2.put(part2) + shard2.put(part3) + shard1.update(shard2) + assert len(shard1) == 2 + assert tuple(shard1) == (part2, part3) + + +class TestShardIterator: + def test_it(self, make_shard): + shard1 = make_shard() + shard2 = make_shard() + shard3 = make_shard() + it = ShardsIterator({0: shard1, 1: shard2, 2: shard3}, 3) + assert list(it) == [(0, shard1), (1, shard2), (2, shard3)] + + it = ShardsIterator({0: shard1, 1: shard2, 2: shard3}, 1) + assert list(it) == [(0, shard1)] + + it = ShardsIterator({1: shard2, 2: shard3}, 1) + assert list(it) == [] + + it = ShardsIterator({1: shard2, 2: shard3}, 2) + assert list(it) == [(1, shard2)] + + +class TestShardsS3IO: + @staticmethod + def create_writer() -> tuple[ShardsS3IO, MagicMock]: + client = create_autospec(S3Client) + writer = ShardsS3IO( + bucket='reports', + key='one/two/three', + client=client + ) + return writer, client + + def test_write(self, make_shard): + shard = make_shard() + writer, client = self.create_writer() + writer.write(1, shard) + client.gz_put_object.assert_called() + + def test_write_meta(self): + writer, client = self.create_writer() + writer.write_meta({}) + client.gz_put_json.assert_called_with( + bucket='reports', + key='one/two/three/meta.json', + obj={} + ) + + def test_read_raw(self): + writer, client = self.create_writer() + client.gz_get_object.return_value = io.BytesIO( + b'[{"p":"policy","l":"global","t":1711309249.0,"r":[]}]' + ) + res = writer.read_raw(1) + assert res == [ShardPart(policy='policy', location='global', + timestamp=1711309249.0, resources=[])] + client.gz_get_object.assert_called() + + def test_read_meta(self): + writer, client = self.create_writer() + client.gz_get_json.return_value = {'policy': {'description': 'data'}} + assert writer.read_meta() == {'policy': {'description': 'data'}} + client.gz_get_json.assert_called_with( + bucket='reports', + key='one/two/three/meta.json' + ) + + +class TestShardCollection: + @staticmethod + def create_collection() -> ShardsCollection: + return ShardsCollection( + distributor=AWSRegionDistributor(2) + ) + + def test_put_part(self, make_shard_part): + collection = self.create_collection() + part1 = make_shard_part(location='global') + part2 = make_shard_part(location='eu-central-1') + part3 = make_shard_part(location='eu-west1') + collection.put_parts((part1, part2, part3)) + assert len(collection) == 2 + assert (part1, part2, part3) == tuple(collection.iter_parts()) + + assert all(isinstance(i[1], Shard) for i in collection) + assert list(i[0] for i in collection) == [0, 1] + + def test_update(self, make_shard_part): + """ + Tests that newer findings are kept in case two similar are found + :return: + """ + part1 = make_shard_part( + location='global', + policy='policy1', + resources=[{'k1': 'v1'}, {'k2': 'v2'}] + ) + part2 = make_shard_part( + location='global', + policy='policy1', + resources=[{'k1': 'v1'}] + ) + part3 = make_shard_part( + location='eu-west-1', + policy='policy2', + resources=[{'k3': 'v3'}] + ) + c1 = self.create_collection() + c2 = self.create_collection() + + c1.put_part(part1) + c1.put_part(part3) + c2.put_part(part2) + + c1.update(c2) + assert (part2, part3) == tuple(c1.iter_parts()) + + def test_subtract(self, make_shard_part): + part1 = make_shard_part( + location='global', + policy='policy1', + resources=[{'k1': 'v1'}] + ) + part2 = make_shard_part( + location='global', + policy='policy1', + resources=[{'k1': 'v1'}, {'k2': 'v2'}, {'k3': 'v3'}] + ) + part3 = make_shard_part( + location='eu-west-1', + policy='policy2', + resources=[{'k3': 'v3'}] + ) + c1 = self.create_collection() + c2 = self.create_collection() + + c1.put_part(part1) + c2.put_part(part2) + c2.put_part(part3) + + diff = c2 - c1 + assert isinstance(diff, ShardsCollection) + assert isinstance(diff.distributor, SingleShardDistributor) + parts = sorted(diff.iter_parts(), key=operator.attrgetter('policy')) + assert len(parts) == 2 + p1, p2 = parts + assert (len(p1.resources) == 2 and {'k2': 'v2'} in p1.resources + and {'k3': 'v3'} in p1.resources) + assert p2.resources == [{'k3': 'v3'}] diff --git a/tests/tests_services/test_xlsx_writer.py b/tests/tests_services/test_xlsx_writer.py new file mode 100644 index 000000000..3b4ae1ea9 --- /dev/null +++ b/tests/tests_services/test_xlsx_writer.py @@ -0,0 +1,63 @@ +from unittest.mock import create_autospec, call + +import pytest +from xlsxwriter.worksheet import Worksheet + +from services.xlsx_writer import CellContent, Table, XlsxRowsWriter, Cell + + +@pytest.fixture +def table() -> Table: + t = Table() + for i in range(3): + t.new_row() + t.add_cells(CellContent(i)) + t.add_cells(CellContent(i + 1), CellContent(i + 2)) + t.add_cells(CellContent(i + 3)) + t.add_cells(CellContent(None)) + + return t + + +def test_cell_content(): + c = CellContent('data') + assert c.data == 'data' + c = CellContent({'key': 'value'}) + assert c.data == '{"key": "value"}' + assert bool(c) + assert not CellContent(None) + + +def test_table(table): + assert table.rows == 3 + assert table.cols == 4 + assert len(table.buffer) == 3 + + +def test_write_table(table): + wsh = create_autospec(Worksheet) + start = Cell(1, 1) + writer = XlsxRowsWriter() + writer.write(wsh, table, start) + wsh.write.assert_has_calls([ + call(1, 1, '0'), + call(1, 2, '1'), + call(2, 2, '2'), + call(1, 3, '3'), + call(3, 1, '1'), + call(3, 2, '2'), + call(4, 2, '3'), + call(3, 3, '4'), + call(5, 1, '2'), + call(5, 2, '3'), + call(6, 2, '4'), + call(5, 3, '5') + ]) + wsh.merge_range.assert_has_calls([ + call(1, 1, 2, 1, ''), + call(1, 3, 2, 3, ''), + call(3, 1, 4, 1, ''), + call(3, 3, 4, 3, ''), + call(5, 1, 6, 1, ''), + call(5, 3, 6, 3, '') + ]) From bed7682be5a29a8341642708b8d2c1e255ffc94d Mon Sep 17 00:00:00 2001 From: Dmytro Afanasiev Date: Thu, 9 May 2024 23:48:30 +0300 Subject: [PATCH 3/4] Remove executor --- executor/CHANGELOG.md | 62 - executor/Dockerfile | 66 - executor/README.md | 34 - executor/__version__.py | 1 - executor/executor.py | 1260 ----------------- executor/helpers/__init__.py | 99 -- executor/helpers/constants.py | 171 --- executor/helpers/exception.py | 8 - executor/helpers/log_helper.py | 84 -- executor/helpers/profiling.py | 92 -- executor/helpers/resource_map_generator.py | 111 -- executor/helpers/security.py | 88 -- executor/helpers/time_helper.py | 28 - executor/helpers/timeit.py | 17 - executor/integrations/__init__.py | 0 executor/integrations/abstract_adapter.py | 7 - executor/integrations/defect_dojo_adapter.py | 468 ------ .../integrations/security_hub/__init__.py | 0 .../security_hub/dump_findings_policy.py | 94 -- .../security_hub/security_hub_adapter.py | 80 -- executor/integrations/ses_adapter.py | 97 -- executor/models/__init__.py | 0 executor/models/batch_results.py | 89 -- executor/models/credentials_manager.py | 61 - executor/models/job.py | 35 - executor/models/modular/__init__.py | 40 - executor/models/modular/application.py | 55 - executor/models/modular/customer.py | 1 - executor/models/modular/parents.py | 23 - executor/models/modular/tenant_settings.py | 1 - executor/models/modular/tenants.py | 1 - executor/models/ruleset.py | 76 - executor/models/scheduled_job.py | 93 -- executor/models/setting.py | 16 - executor/requirements.txt | 14 - .../resources_mapping/resources_map_aws.json | 1 - .../resources_map_azure.json | 1 - .../resources_map_google.json | 1 - executor/services/__init__.py | 3 - executor/services/batch_service.py | 55 - executor/services/clients/__init__.py | 82 -- .../clients/abstract_key_management.py | 85 -- executor/services/clients/ap_sheduler.py | 80 -- executor/services/clients/dojo_client.py | 142 -- executor/services/clients/eks_client.py | 33 - executor/services/clients/event_bridge.py | 42 - executor/services/clients/iam.py | 21 - executor/services/clients/license_manager.py | 173 --- executor/services/clients/modular.py | 3 - executor/services/clients/s3.py | 265 ---- executor/services/clients/scheduler.py | 79 -- executor/services/clients/ssm.py | 178 --- .../clients/standalone_key_management.py | 428 ------ executor/services/clients/sts.py | 91 -- executor/services/credentials_service.py | 206 --- executor/services/environment_service.py | 192 --- executor/services/integration_service.py | 83 -- executor/services/job_updater_service.py | 265 ---- executor/services/license_manager_service.py | 255 ---- executor/services/modular_service.py | 190 --- executor/services/notification_service.py | 195 --- executor/services/os_service.py | 61 - executor/services/policy_service.py | 206 --- executor/services/report_service.py | 625 -------- executor/services/ruleset_service.py | 90 -- executor/services/s3_service.py | 59 - executor/services/s3_settings_service.py | 74 - executor/services/scheduler_service.py | 19 - executor/services/service_provider.py | 264 ---- executor/services/setting_service.py | 59 - executor/services/ssm_service.py | 22 - executor/services/statistics_service.py | 223 --- executor/services/token_service.py | 54 - src/main.py | 18 - 74 files changed, 8295 deletions(-) delete mode 100644 executor/CHANGELOG.md delete mode 100644 executor/Dockerfile delete mode 100644 executor/README.md delete mode 100644 executor/__version__.py delete mode 100644 executor/executor.py delete mode 100644 executor/helpers/__init__.py delete mode 100644 executor/helpers/constants.py delete mode 100644 executor/helpers/exception.py delete mode 100644 executor/helpers/log_helper.py delete mode 100644 executor/helpers/profiling.py delete mode 100644 executor/helpers/resource_map_generator.py delete mode 100644 executor/helpers/security.py delete mode 100644 executor/helpers/time_helper.py delete mode 100644 executor/helpers/timeit.py delete mode 100644 executor/integrations/__init__.py delete mode 100644 executor/integrations/abstract_adapter.py delete mode 100644 executor/integrations/defect_dojo_adapter.py delete mode 100644 executor/integrations/security_hub/__init__.py delete mode 100644 executor/integrations/security_hub/dump_findings_policy.py delete mode 100644 executor/integrations/security_hub/security_hub_adapter.py delete mode 100644 executor/integrations/ses_adapter.py delete mode 100644 executor/models/__init__.py delete mode 100644 executor/models/batch_results.py delete mode 100644 executor/models/credentials_manager.py delete mode 100644 executor/models/job.py delete mode 100644 executor/models/modular/__init__.py delete mode 100644 executor/models/modular/application.py delete mode 100644 executor/models/modular/customer.py delete mode 100644 executor/models/modular/parents.py delete mode 100644 executor/models/modular/tenant_settings.py delete mode 100644 executor/models/modular/tenants.py delete mode 100644 executor/models/ruleset.py delete mode 100644 executor/models/scheduled_job.py delete mode 100644 executor/models/setting.py delete mode 100644 executor/requirements.txt delete mode 100644 executor/resources_mapping/resources_map_aws.json delete mode 100644 executor/resources_mapping/resources_map_azure.json delete mode 100644 executor/resources_mapping/resources_map_google.json delete mode 100644 executor/services/__init__.py delete mode 100644 executor/services/batch_service.py delete mode 100644 executor/services/clients/__init__.py delete mode 100644 executor/services/clients/abstract_key_management.py delete mode 100644 executor/services/clients/ap_sheduler.py delete mode 100644 executor/services/clients/dojo_client.py delete mode 100644 executor/services/clients/eks_client.py delete mode 100644 executor/services/clients/event_bridge.py delete mode 100644 executor/services/clients/iam.py delete mode 100644 executor/services/clients/license_manager.py delete mode 100644 executor/services/clients/modular.py delete mode 100644 executor/services/clients/s3.py delete mode 100644 executor/services/clients/scheduler.py delete mode 100644 executor/services/clients/ssm.py delete mode 100644 executor/services/clients/standalone_key_management.py delete mode 100644 executor/services/clients/sts.py delete mode 100644 executor/services/credentials_service.py delete mode 100644 executor/services/environment_service.py delete mode 100644 executor/services/integration_service.py delete mode 100644 executor/services/job_updater_service.py delete mode 100644 executor/services/license_manager_service.py delete mode 100644 executor/services/modular_service.py delete mode 100644 executor/services/notification_service.py delete mode 100644 executor/services/os_service.py delete mode 100644 executor/services/policy_service.py delete mode 100644 executor/services/report_service.py delete mode 100644 executor/services/ruleset_service.py delete mode 100644 executor/services/s3_service.py delete mode 100644 executor/services/s3_settings_service.py delete mode 100644 executor/services/scheduler_service.py delete mode 100644 executor/services/service_provider.py delete mode 100644 executor/services/setting_service.py delete mode 100644 executor/services/ssm_service.py delete mode 100644 executor/services/statistics_service.py delete mode 100644 executor/services/token_service.py diff --git a/executor/CHANGELOG.md b/executor/CHANGELOG.md deleted file mode 100644 index 087f9bb50..000000000 --- a/executor/CHANGELOG.md +++ /dev/null @@ -1,62 +0,0 @@ -# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -# [4.2.0] - 2023-10-11 -* added aws xray profiling. Some jobs are sampled and the statistics will be - pushed to statistics bucket; - -# [4.1.0] - 2023-08-11 -* removed `report_fields`, `severity` and `standard_points` from findings -* get impact, article, remediation for dojo reports from meta - -# [4.0.0] - 2023-02-03 - 2023-03-07 -- removed CaaSAccounts and all the bound code. - Now Tenants are used instead of accounts. - -# [3.3.1] 2023-01-29 - 2023-02-03 -- add a missing attribute to BatchResults; - -# [3.3.0] 2022-12-23 - ... -- split event-driven and standard job flows; -- integrate new event-driven and BatchResults; - -# [3.2.3] 2022-12-16 - 2022-12-22 -- add to statistics the policies that were skipped due to the time threshold -- fix job lifetime threshold (use job's `startedAt` instead of `createdAt`) - -# [3.2.2] 2022-12-14 - 2022-12-16 -- update notification content; -- replace settings passing through environment variables with settings service - -# [3.2.1] 2022-12-09 - 2022-12-13 -- make some optimizations: - - upload all the report files to S3 bucket concurrently; - - use `libyaml` from C in order to speed up Yaml files loading and dumping; - - send requests to LM in job_updater_service only if the job is licensed; - - increase the number of retry attempts in boto3 S3 config; - -# [3.2.0] 2022-11-30 - 2022-12-09 -- remove PynamoDBToMongoDBAdapter and BaseModel and use the ones from MCDM SDK -- add one more report `user_detailed_report.json`. It's the same - as `detailed_report.json` but does not contain standards -- `TARGET_RULESET` env now must contains rulesets' ids instead of names. - -# [3.0.0] 2022-09-14 - 2022-11-02 -- integrate Maestro common domain model (MCDM) and its SDK; -- Refactored to allow to execute the docker without command but only with envs. -- Refactored the main flow and split to separate classes. Added an ability to - execute policies in ThreadPoolExecutor. Set `EXECUTOR_MODE` env to - either `consistent` or `concurrent` -- If job item does not exist in DB after the executor has started, - it will be created; - - -## [2.0.0] - 2022-06-24 -- Added integration with Custodian License Manager. - - -## [1.0.0] - 2021-04-08 -Initial version of Maestro Custodian Service Docker Image. \ No newline at end of file diff --git a/executor/Dockerfile b/executor/Dockerfile deleted file mode 100644 index a98b639ae..000000000 --- a/executor/Dockerfile +++ /dev/null @@ -1,66 +0,0 @@ -FROM public.ecr.aws/docker/library/python:3.10 as executor-compile-image - -# default values expect that user have cloned core to the Custodian's -# root and execute build with build context pointing to Custodian's root: -# pwd -# ../custodian-as-a-service -# docker build -f executor/Dockerfile . -ARG custodian=. -ARG core=custodian-custom-core -# ARG mcdm=maestro-common-domain-model -# it's not enough to just add a provider here, you must copy its files below -ARG providers="gcp azure kube" - -ARG POETRY_VERSION="1.4.0" -# to import c7n_azure.resources.insights.DiagnosticSettings -ENV AZURE_SUBSCRIPTION_ID=" " - -RUN pip install "poetry==$POETRY_VERSION" && \ - python -m venv /root/.local - -WORKDIR /build/custodian-custom-core - -# Core's root -COPY $core/pyproject.toml $core/poetry.lock $core/README.md ./ -RUN . /root/.local/bin/activate && poetry install --no-interaction --without dev --no-root - -# Core's providers -COPY $core/tools/c7n_gcp/pyproject.toml $core/tools/c7n_gcp/poetry.lock tools/c7n_gcp/ -COPY $core/tools/c7n_azure/pyproject.toml $core/tools/c7n_azure/poetry.lock tools/c7n_azure/ -COPY $core/tools/c7n_kube/pyproject.toml $core/tools/c7n_kube/poetry.lock tools/c7n_kube/ - -RUN for pkg in $providers; do . /root/.local/bin/activate && cd tools/c7n_$pkg && poetry install --no-interaction --without dev --no-root && cd ../..; done - -# executor requirements -COPY $custodian/executor/requirements.txt ../executor/requirements.txt -RUN . /root/.local/bin/activate && pip install -r ../executor/requirements.txt - - -COPY $core/c7n c7n/ -# "poetry install --only-root" installs in editable mode -# (like pip install -e .). Due to multi-stage build we do not need this. -# To install in non-editable mode with Poetry you have to build a dist and -# then install which is quite a work so I use "pip install --no-deps ." -# https://github.com/python-poetry/poetry/issues/1382 -RUN . /root/.local/bin/activate && pip install --no-deps . - -COPY $core/tools/c7n_gcp tools/c7n_gcp/ -COPY $core/tools/c7n_azure tools/c7n_azure/ -COPY $core/tools/c7n_kube tools/c7n_kube/ -RUN for pkg in $providers; do . /root/.local/bin/activate && cd tools/c7n_$pkg && pip install --no-deps . && cd ../..; done - - -# COPY $mcdm/mcdm_sdk ../mcdm_sdk -# RUN . /root/.local/bin/activate && pip install ../mcdm_sdk - -COPY $custodian/executor ../executor -# by here we have executor built in /root/.local - -# 3.10-alpine works as well -FROM public.ecr.aws/docker/library/python:3.10-slim AS build-image - -COPY --from=executor-compile-image /root/.local /root/.local -COPY --from=executor-compile-image /build/executor /executor -ENV PATH=/root/.local/bin:$PATH - -WORKDIR /executor diff --git a/executor/README.md b/executor/README.md deleted file mode 100644 index 2df10308a..000000000 --- a/executor/README.md +++ /dev/null @@ -1,34 +0,0 @@ -![Custodian Service logo](../docs/pics/cs_logo.png) - -### Custodian Service - -The application provides ability to -perform [custodian](https://cloudcustodian.io) -scans for AWS, Azure and GCP accounts. - -[Custodian](https://cloudcustodian.io) is a tool for automation cloud security -compliance. It is based on open-source tool Cloud Custodian enhanced by EPAM -Team. The tool allows users to check their infrastructure resources for -compliance to the security policies and standards. Custodian applies the defined -sets of rules against the infrastructure and provides information on the -resources that break the policies. The rulesets are designed specifically for -each of the clouds – AWS, Azure, and GCP. - -### Notice - -All the technical details described below are actual for the particular version, -or a range of versions of the software. - -### Actual for versions: 1.0.0 - -## Custodian Service diagram - -Build docker image (directory custodian-as-a-service/docker) and put it to - Elastic Container Registry field set to `true`. - -## Exit codes -Container can end its execution with the following status codes: -- `0` if code execution completed successfully; -- `2` if it is not allowed to start the job due to the license manager restrictions; -- `126` if retry needed (example - any problems with credentials); -- `1` all other execeptions and errors that do not require a retry. \ No newline at end of file diff --git a/executor/__version__.py b/executor/__version__.py deleted file mode 100644 index ea5d65fc7..000000000 --- a/executor/__version__.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = '4.2.0' diff --git a/executor/executor.py b/executor/executor.py deleted file mode 100644 index 676dea883..000000000 --- a/executor/executor.py +++ /dev/null @@ -1,1260 +0,0 @@ -""" -[Up-to-date description] - -Available environment variables: -- ... -- ... - -Usage: python executor.py - -Exit codes: -- 0: success; -- 1: unexpected system error; -- 2: Job execution is not granted by the License Manager; -- 126: Job is event-driven and cannot be executed in consequence of invalid - credentials or conceivably some other temporal reason. Retry is allowed. -""" -import gzip -import io -import json -import os -import shutil -import sys -import threading -import traceback -from concurrent.futures import ThreadPoolExecutor, as_completed, Future -from copy import deepcopy -from itertools import chain -from pathlib import Path -from pathlib import PurePosixPath -from typing import List, Union, Tuple, Dict, Optional, Set - -from botocore.exceptions import ClientError -from c7n.commands import load_policies -from c7n.config import Config -from c7n.policy import Policy -from google.auth.exceptions import GoogleAuthError -from googleapiclient.errors import HttpError -from modular_sdk.commons.constants import ENV_KUBECONFIG, ParentType, \ - ApplicationType -from modular_sdk.models.parent import Parent -from modular_sdk.models.customer import Customer - -from modular_sdk.models.tenant import Tenant -from modular_sdk.services.environment_service import EnvironmentContext -from modular_sdk.services.impl.maestro_credentials_service import \ - K8SServiceAccountCredentials, K8SServiceAccountApplicationMeta -from msrestazure.azure_exceptions import CloudError - -from helpers.constants import * -from helpers.exception import ExecutorException -from helpers.log_helper import get_logger -from helpers.profiling import xray_recorder as _XRAY, BytesEmitter -from helpers.time_helper import utc_datetime, utc_iso -from integrations.security_hub.dump_findings_policy import DumpFindingsPolicy -from models.batch_results import BatchResults -from models.job import Job -from models.modular.application import CustodianLicensesApplicationMeta -from models.modular.parents import ParentMeta -from services import SP -from services.batch_service import BatchService -from services.clients import Boto3ClientFactory -from services.clients.eks_client import EKSClient -from services.clients.sts import TokenGenerator -from services.credentials_service import CredentialsService -from services.environment_service import EnvironmentService -from services.integration_service import IntegrationService -from services.job_updater_service import JobUpdaterService, \ - TenantSettingJobLock -from services.license_manager_service import LicenseManagerService, \ - BalanceExhaustion, InaccessibleAssets -from services.modular_service import ModularService -from services.modular_service import TenantService -from services.notification_service import NotificationService -from services.policy_service import PolicyService -from services.report_service import FindingsCollection, DETAILED_REPORT_FILE, \ - REPORT_FILE, DIFFERENCE_FILE -from services.report_service import ReportService, JobResult -from services.ruleset_service import RulesetService -from services.s3_service import S3Service -from services.scheduler_service import SchedulerService -from services.setting_service import SettingService -from services.ssm_service import SSMService -from services.statistics_service import StatisticsService, \ - STATISTICS_FILE, API_CALLS_FILE - -environment_service: Optional[EnvironmentService] = None -ssm_service: Optional[SSMService] = None -credentials_service: Optional[CredentialsService] = None -policy_service: Optional[PolicyService] = None -ruleset_service: Optional[RulesetService] = None -s3_service: Optional[S3Service] = None -batch_service: Optional[BatchService] = None -report_service: Optional[ReportService] = None -statistics_service: Optional[StatisticsService] = None -job_updater_service: Optional[JobUpdaterService] = None -license_manager_service: Optional[LicenseManagerService] = None -modular_service: Optional[ModularService] = None -tenant_service: Optional[TenantService] = None -scheduler_service: Optional[SchedulerService] = None -setting_service: Optional[SettingService] = None -notification_service: Optional[NotificationService] = None -integration_service: Optional[IntegrationService] = None -SERVICES = {'environment_service', 'ssm_service', 'credentials_service', - 'policy_service', 'ruleset_service', 's3_service', - 'batch_service', 'report_service', 'statistics_service', - 'job_updater_service', - 'notification_service', 'license_manager_service', - 'modular_service', - 'tenant_service', 'scheduler_service', 'setting_service', - 'integration_service'} - -_LOG = get_logger(__name__) -CACHE_FILE = 'cloud-custodian.cache' - - -def init_services(services: Optional[set] = None): - """ - Assigns services instances to their corresponding variables in the - global scope. If `services` param is not given, all the services - will be instantiated. - :parameter services: Optional[set] - :return: None - """ - services = services or SERVICES - assert services.issubset(SERVICES), \ - f'{SERVICES - services} are not available' - for s in services: - _globals: dict = globals() - if not _globals.get(s): - _LOG.info(f'Initializing {s.replace("_", " ")}') - _globals[s] = getattr(SP, s, lambda _: None)() - - -init_services({'environment_service'}) - -TIME_THRESHOLD: Optional[float] = None -WORK_DIR: Optional[str] = None -CLOUD_COMMON_REGION = { - AWS: environment_service.aws_default_region(), - AZURE: AZURE_COMMON_REGION, - GOOGLE: GCP_COMMON_REGION, - KUBERNETES: None -} -ERROR_WHILE_LOADING_POLICIES_MESSAGE = 'unexpected error occurred while ' \ - 'loading policies files: {policies}' - - -class Runner: - @property - def cloud(self): - return None - - def __init__(self, policies: List[Policy], findings_dir: str): - self._policies = policies - self._findings_dir = findings_dir - self._skipped, self._failed = {}, {} - self._is_ongoing = False - self._finishing_reason = None - self._lock = threading.Lock() - - @property - def skipped(self) -> dict: - return self._skipped - - @property - def failed(self) -> dict: - return self._failed - - def start(self): - self._is_ongoing = True - for policy in self._policies: - self._handle_errors(policy=policy) - self._is_ongoing = False - - def start_threads(self): - self._is_ongoing = True - with ThreadPoolExecutor() as executor: - future_policy = { - executor.submit(self._call_policy, policy): policy - for policy in self._policies - } - for future in as_completed(future_policy): - self._handle_errors(policy=future_policy[future], - future=future) - self._is_ongoing = False - - def _call_policy(self, policy: Policy): - if TIME_THRESHOLD <= utc_datetime().timestamp(): - if self._is_ongoing: - _LOG.warning('Job time threshold has been exceeded. ' - 'All the consequent rules will be skipped.') - self._is_ongoing = False - self._finishing_reason = \ - 'Job time exceeded the maximum possible ' \ - 'execution time. The rule was not executed ' \ - 'and was skipped' - if not self._is_ongoing: - self._add_skipped_item(self.get_policy_region(policy), - policy.name, self._finishing_reason) - return - # policy() - DumpFindingsPolicy(policy=policy, output_dir=self._findings_dir)() - - @staticmethod - def _skipped_item(name: str, reason: str) -> dict: - return {name: {'reason': reason}} - - @staticmethod - def _failed_item(name: str, error: Exception) -> dict: - e = error.response['Error']['Message'] if isinstance( - error, ClientError) else str(error) - return {name: [e, ''.join(traceback.format_exception( - type(error), error, error.__traceback__)) - ]} - - def _add_skipped_item(self, region: str, name: str, reason: str): - with self._lock: - self._skipped.setdefault(region, {}).update( - self._skipped_item(name, reason)) - - def _add_failed_item(self, region: str, name: str, error: Exception): - with self._lock: - self._failed.setdefault(region, {}).update( - self._failed_item(name, error) - ) - - @staticmethod - def get_policy_region(policy: Policy) -> str: - """Returns the policy's region which will be used for reports: if the - policy is multi-regional, 'multiregion' is returned, if - the policy is not global, its real region is returned""" - region = policy.options.region - return MULTIREGION if str(policy.data.get( - 'metadata', {}).get('multiregional')).lower() in [ - 'true', 't', 'yes'] or not region else region - - def _handle_errors(self, policy: Policy, future: Future = None): - raise NotImplementedError - - -class AWSRunner(Runner): - @property - def cloud(self) -> str: - return AWS - - def _handle_errors(self, policy: Policy, future: Future = None): - name, region = policy.name, self.get_policy_region(policy) - try: - future.result() if future else self._call_policy(policy) - except ClientError as error: - error_code = error.response['Error']['Code'] - error_reason = error.response['Error']['Message'] - - if error_code in ACCESS_DENIED_ERROR_CODE.get(self.cloud): - _LOG.warning(f'Policy \'{name}\' is skipped. ' - f'Reason: \'{error_reason}\'') - self._add_skipped_item(region, name, error_reason) - elif error_code in INVALID_CREDENTIALS_ERROR_CODES.get(self.cloud): - _LOG.warning( - f'Policy \'{name}\' is skipped due to invalid ' - f'credentials. All the subsequent rules will be skipped') - self._add_skipped_item(region, name, error_reason) - self._is_ongoing = False - self._finishing_reason = error_reason - else: - _LOG.warning(f'Policy \'{name}\' has failed. ' - f'Client error occurred. ' - f'Code: \'{error_code}\'. ' - f'Reason: {error_reason}') - self._add_failed_item(region, name, error) - except Exception as error: - _LOG.error(f'Policy \'{name}\' has failed. Unexpected ' - f'error has occurred: \'{error}\'') - self._add_failed_item(region, name, error) - - -class AZURERunner(Runner): - @property - def cloud(self) -> str: - return AZURE - - def _handle_errors(self, policy: Policy, future: Future = None): - name, region = policy.name, self.get_policy_region(policy) - try: - future.result() if future else self._call_policy(policy) - except CloudError as error: - error_code = error.error - error_reason = error.message.split(':')[-1].strip() - if error_code in INVALID_CREDENTIALS_ERROR_CODES.get(self.cloud): - _LOG.warning( - f'Policy \'{name}\' is skipped due to invalid ' - f'credentials. All the subsequent rules will be skipped') - self._add_skipped_item(region, name, error_reason) - self._is_ongoing = False - self._finishing_reason = error_reason - else: - _LOG.warning(f'Policy \'{name}\' has failed. ' - f'Client error occurred. ' - f'Code: \'{error_code}\'. ' - f'Reason: {error_reason}') - self._add_failed_item(region, name, error) - except Exception as error: - _LOG.error(f'Policy \'{name}\' has failed. Unexpected ' - f'error has occurred: \'{error}\'') - self._add_failed_item(region, name, error) - - -class GCPRunner(Runner): - @property - def cloud(self) -> str: - return GOOGLE - - def _handle_errors(self, policy: Policy, future: Future = None): - name, region = policy.name, self.get_policy_region(policy) - try: - future.result() if future else self._call_policy(policy) - except GoogleAuthError as error: - error_reason = str(error.args[-1]) - _LOG.warning( - f'Policy \'{name}\' is skipped due to invalid ' - f'credentials. All the subsequent rules will be skipped') - self._add_skipped_item(region, name, error_reason) - self._is_ongoing = False - self._finishing_reason = error_reason - except HttpError as error: - if error.status_code == 403: - self._add_skipped_item(region, name, error.reason) - else: - self._add_failed_item(region, name, error) - except Exception as error: - _LOG.error(f'Policy \'{name}\' has failed. Unexpected ' - f'error has occurred: \'{error}\'') - self._add_failed_item(region, name, error) - - -class K8SRunner(Runner): - @property - def cloud(self) -> str: - return KUBERNETES - - def _handle_errors(self, policy: Policy, future: Future = None): - name, region = policy.name, self.get_policy_region(policy) - try: - future.result() if future else self._call_policy(policy) - except Exception as error: - _LOG.error(f'Policy \'{name}\' has failed. Unexpected ' - f'error has occurred: \'{error}\'') - self._add_failed_item(region, name, error) - - -class Scan: - def __init__(self, policies_files: list, cloud: str, - work_dir: str, findings_dir: str = None, - cache_period: int = 30): - self._job_id = environment_service.batch_job_id() - self._work_dir = work_dir - self._policies_files = policies_files - self._cloud = cloud - self._cache_period = cache_period - self._findings_dir = findings_dir - - self._policies: List[Policy] = [] - self._runner: Union[type(None), Runner] = None - - @staticmethod - def _load_policies_by_one(config: Config) -> List[Policy]: - """In case we want to continue the scan even if one policy file is - invalid""" - _LOG.info('Loading policies files one by one') - _all_policies = [] - _files = config.get('configs') or [] - for _file in _files: - config.update({'configs': [_file, ]}) - try: - _policies = load_policies(config) - _all_policies.extend(_policies) - except (Exception, SystemExit) as error: - _message = ERROR_WHILE_LOADING_POLICIES_MESSAGE.format( - policies=Path(_file).name) - _LOG.warning(_message[0].upper() + _message[1:] + f'; {error}') - return _all_policies - - @_XRAY.capture('Load policies files') - @staticmethod - def _load_policies(config: Config) -> List[Policy]: - _LOG.info('Loading all the policies files at once') - try: - return load_policies(config) - except (Exception, SystemExit) as error: - # TODO for Custom-Core: raise something more informative - # instead of sys.exit(1) - _message = ERROR_WHILE_LOADING_POLICIES_MESSAGE.format( - policies=", ".join(Path(_file).name for _file in - config.get('configs') or [])) - _LOG.error(_message[0].upper() + _message[1:] + f'; {error}') - raise ExecutorException( - step_name=STEP_LOAD_POLICIES, - reason=_message - ) - - @staticmethod - def _is_multiregional(policy: Policy) -> bool: - return policy.data.get('metadata', {}).get('multiregional') == 'true' - - def _set_policy_output(self, policy: Policy) -> None: - if self._is_multiregional(policy): - policy.options.output_dir = self._output_dir() - else: - policy.options.output_dir = self._output_dir(policy.options.region) - - def load_from_regions_to_rules(self, mapping: dict) -> List[Policy]: - """ - Slow option due to multiple executions of load_policies for the - same yaml files, but easy to write and understand - Expected mapping: - { - 'eu-central-1': {'epam-aws-005..', 'epam-aws-006..'}, - 'eu-west-1': {'epam-aws-006..', 'epam-aws-007..'} - } - """ - multiregional = set() # track already loaded multi-regional policies - all_policies = [] - config = self._config() - for region, rules in mapping.items(): - config.regions = [region, ] - config.policy_filters = list(rules - multiregional) - - _policies = self._load_policies(config) - all_policies.extend(_policies) - multiregional.update(p.name for p in filter(self._is_multiregional, - _policies)) - for policy in all_policies: - self._set_policy_output(policy) - return all_policies - - def load_from_rules_to_regions(self, mapping: dict) -> List[Policy]: - """ - Probably faster solution than the one above but a bit tricky since I - use deepcopy, and I'm actually not quite sure whether it does no harm - for Custom-Core's policies or memory. - - The thing is: we must avoid using Custom-Core's `load_policies` - (still once it must be used), because it's too slow, especially - for large YAMLs, and we are aware of precedents where it occupied - too much RAM and did not free it (the container used to say `Adios` - in the middle of execution). - Here we invoke that method only once and then distribute policies - across different regions using deepcopy - Expected mapping: - { - 'epam-aws-005..': {'eu-central-1'}, - 'epam-aws-006..': {'eu-west-1', 'eu-central-1'}, - 'epam-aws-007..': {'eu-west-1'} - } - """ - all_policies = [] - config = self._config() # multi-regional output dir - config.regions = [] # One instance of each rule for default region - loaded = self._load_policies(config) - for policy in loaded: - if self._is_multiregional(policy): - all_policies.append(policy) - continue - - demanded_regions = mapping.get(policy.name) or set() - pr = policy.options.region - if pr in demanded_regions: - self._set_policy_output(policy) - all_policies.append(policy) - for left_region in demanded_regions - {pr}: - policy_copy = deepcopy(policy) - policy_copy.options.region = left_region - self._set_policy_output(policy_copy) - all_policies.append(policy_copy) - return all_policies - - @property - def policies(self) -> List[Policy]: - if not self._policies: - _LOG.debug('Loading policies from files using \'load_policies\' ' - 'Custom-Core\'s function') - config = self._config() - # self._policies = self._load_policies_by_one(config) - self._policies = self._load_policies(config) - _LOG.debug('The policies were loaded from YAML to ' - 'Custom Core collection or Policies') - _LOG.info(f'Setting \'{MULTIREGION}\' output dir for global ' - f'policies') - for policy in self._policies: - self._set_policy_output(policy) - return self._policies - - @policies.setter - def policies(self, value: List[Policy]): - self._policies = value - - @property - def runner(self) -> Runner: - if not self._runner: - _cloud_to_runner_class = { - AWS: AWSRunner, - AZURE: AZURERunner, - GOOGLE: GCPRunner, - KUBERNETES: K8SRunner - } - runner_class = _cloud_to_runner_class.get(self._cloud) - self._runner = runner_class( - self.policies, self._findings_dir - ) - return self._runner - - @property - def regions(self) -> list: - regions = environment_service.target_regions() - if self._cloud == AWS: - return regions - return [] # GOOGLE, AZURE, KUBERNETES - - def _output_dir(self, region: str = None) -> str: - """ - Default output dir is multiregional. - """ - return str(Path(self._work_dir, region or MULTIREGION).absolute()) - - def _config(self, regions: Optional[List] = None) -> Config: - options = { - 'region': CLOUD_COMMON_REGION.get(self._cloud), - 'regions': regions or self.regions, - 'cache': CACHE_FILE, - 'cache_period': self._cache_period, - 'command': 'c7n.commands.run', - 'config': None, - 'configs': self._policies_files, - 'output_dir': self._output_dir(), # multi-regional - 'subparser': 'run', - 'policy_filters': [], - 'resource_types': [], - 'verbose': None, - 'quiet': False, - 'debug': False, - 'skip_validation': False, - 'vars': None, - } - return Config.empty(**options) - - @_XRAY.capture('Execute scan') - def execute(self) -> Tuple[Dict, Dict]: - is_concurrent = environment_service.is_concurrent() - _LOG.info(f'Starting {"concurrent" if is_concurrent else ""} ' - f'scan for job \'{self._job_id}\'') - self.runner.start_threads() if is_concurrent else self.runner.start() - _LOG.info(f'Scan for job \'{self._job_id}\' has ended') - return self.runner.skipped, self.runner.failed - - -def _exception_to_str(e: Exception) -> str: - e_str = str(e) - return e_str if isinstance( - e, ExecutorException) else f'{type(e).__name__}: {e_str}' - - -def fetch_licensed_ruleset_list(tenant: Tenant, licensed: dict): - """ - Designated to execute preliminary licensed Job instantiation, which - verifies permissions to create a demanded entity. - :parameter tenant: Tenant of the issuer - :parameter licensed: Dict - non-empty collection of licensed rulesets - :raises: ExecutorException - given parameter absence or prohibited action - :return: List[Dict] - """ - job_id = environment_service.batch_job_id() - - lm_service = license_manager_service - payload = dict( - job_id=job_id, - customer=tenant.customer_name, - tenant=tenant.name, - ruleset_map=licensed - ) - _iterator = (item[0] for item in payload.items() if item[1] is None) - absent = next(_iterator, None) - if absent is not None: - raise ExecutorException(reason=f'\'{absent}\' has not been assigned', - step_name=STEP_GRANT_JOB) - - _LOG.debug(f'Going to license a Job:\'{job_id}\'.') - - licensed_job, issue = None, '' - - try: - licensed_job: Optional[dict] = lm_service.instantiate_licensed_job_dto( - **payload - ) - except BalanceExhaustion as fj: - issue = str(fj) - - except InaccessibleAssets as ij: - issue = str(ij) - rulesets = list(ij) - - customer_name = tenant.customer_name - customer: Customer = modular_service.get_customer( - customer=customer_name) - - scheduled_job_name = environment_service.scheduled_job_name() - mail_configuration = setting_service.get_mail_configuration() - - if scheduled_job_name and mail_configuration and rulesets and customer: - header = f'Scheduled-Job:\'{scheduled_job_name}\' of ' \ - f'\'{customer_name}\' customer' - - _LOG.info(f'{header} - is going to be retrieved.') - job = scheduler_service.get( - name=scheduled_job_name, customer=customer_name - ) - if not job: - _LOG.error(f'{header} - could not be found.') - - if not scheduler_service.update_job(item=job, is_enabled=False): - _LOG.error(f'{header} - could not be deactivated.') - else: - _LOG.info(f'{header} - has been deactivated') - subject = f'{tenant.name} job-rescheduling notice' - if not notification_service.send_rescheduling_notice_notification( - recipients=customer.admins, subject=subject, - tenant=tenant, scheduled_job_name=scheduled_job_name, - ruleset_list=rulesets, - customer=customer_name - ): - _LOG.error('Job-Rescheduling notice was not sent.') - else: - _LOG.info('Job-Rescheduling notice has been sent.') - - elif not mail_configuration: - _LOG.warning( - 'No mail configuration has been attached, skipping ' - f' job-rescheduling notice of \'{scheduled_job_name}\'.' - ) - - if not licensed_job: - reason = 'Job execution could not be granted.' - if issue: - reason += f' {issue}' - raise ExecutorException(reason=reason, step_name=STEP_GRANT_JOB) - - _LOG.info(f'Job {job_id} has been permitted to be commenced.') - return lm_service.instantiate_job_sourced_ruleset_list( - licensed_job_dto=licensed_job - ) - - -@_XRAY.capture('Fetch licensed ruleset') -def get_licensed_ruleset_dto_list(tenant: Tenant) -> list: - """ - Preliminary step, given an affected license and respective ruleset(s) - """ - affected_license = environment_service.affected_licenses() or [] - licensed_rulesets = environment_service.licensed_ruleset_map( - license_key_list=affected_license - ) - licensed_ruleset_dto_list = [] - if affected_license and licensed_rulesets: - licensed_ruleset_dto_list = fetch_licensed_ruleset_list( - tenant=tenant, licensed=licensed_rulesets - ) - return licensed_ruleset_dto_list - - -@_XRAY.capture('Upload to SIEM') -def upload_to_siem(tenant: Tenant, started_at: str, - detailed_report: Dict[str, List[Dict]], - findings_dir: str, job_id: str): - # dojo - dojo_adapters = integration_service.get_dojo_adapters(tenant) - if dojo_adapters: - dojo_report = report_service.formatted_to_dojo_policy_report( - detailed_report=detailed_report, cloud=tenant.cloud - ) - # TODO push in thread in case multiple available - for adapter in dojo_adapters: - try: - adapter.push_notification( - job_id=job_id, - started_at=started_at, - customer_display_name=tenant.customer_name, - tenant_display_name=tenant.name, - policy_report=dojo_report - ) - except Exception as e: - _LOG.warning( - f'Unexpected error occurred pushing findings to dojo {e}') - # SH - for adapter in integration_service.get_security_hub_adapters(tenant): - try: - adapter.push_notification( - findings_folder=findings_dir - ) - except Exception as e: - _LOG.warning( - f'Unexpected error occurred pushing findings to SH {e}') - - -@_XRAY.capture('Generate reports') -def load_reports(work_dir: str, skipped_policies: dict, - failed_policies: dict, tenant: Tenant, cloud: str - ) -> Tuple[Dict, Dict, Dict, Dict]: - """ - Just common hunk of code. Maybe someday it will be refactored - :param tenant: - :param work_dir: - :param skipped_policies: - :param failed_policies:ram cloud: - :param cloud - :return: - """ - statistics, api_calls = statistics_service.collect_statistics( - work_dir=work_dir, - failed_policies=failed_policies, - skipped_policies=skipped_policies, - tenant=tenant - ) - res = JobResult(work_dir, cloud) - detailed_report = res.detailed_report() - report = res.digest_report(detailed_report) - return report, detailed_report, statistics, api_calls - - -def retrieve_batch_result(br_uuid: str) -> BatchResults: - _LOG.info(f'The job is event-driven. Querying BatchResults ' - f'item with id: `{br_uuid}`') - _batch_results = BatchResults.get_nullable(hash_key=br_uuid) - if not _batch_results: - raise ExecutorException( - step_name=STEP_GET_BATCH_RESULTS_ED, - reason=f'BatchResults item with id {br_uuid} not found ' - f'for the event-driven job' - ) - elif _batch_results.status == JobState.SUCCEEDED.value: - raise ExecutorException( - step_name=STEP_BATCH_RESULT_ALREADY_SUCCEEDED, - reason=f'BatchResults item with id {br_uuid} has status `SUCCEEDED`' - ) - return _batch_results - - -@_XRAY.capture('Get credentials') -def get_credentials(tenant: Tenant, - batch_results: Optional[BatchResults] = None) -> dict: - """ - Tries to retrieve credentials to scan the given tenant with such - priorities: - 1. env "CREDENTIALS_KEY" - gets key name and then gets credentials - from SSM. This is the oldest solution, in can sometimes be used if - the job is standard and a user has given credentials directly; - The SSM parameter is removed after the creds are received. - 2. Only for event-driven jobs. Gets credentials_key (SSM parameter name) - from "batch_result.credentials_key". Currently, the option is obsolete. - API in no way will set credentials key there. But maybe some time... - 3. 'CUSTODIAN_ACCESS' key in the tenant's parent_map. It points to the - parent with type 'CUSTODIAN_ACCESS' as well. That parent is linked - to an application with credentials - 4. Customer's custodian application - access_application_id. When a - Custodian application is activated for a certain customer, - access_application_id can be set for each CLOUD (AWS, AZURE, GCP). If - the application exists and access_application_id exists, its creds - are used. - 5. Credentials from Custodian's CredentialsManager. It's also kind of - an old solution, native to Custodian-as-a-service. It has right to live. - 6. Maestro management_parent_id -> management creds. Tries to resolve - management parent from tenant and then management credentials. This - option can be used only if the corresponding env is set to 'true'. - Must be explicitly allowed because the option is not safe. - """ - mcs = modular_service.modular_client.maestro_credentials_service() - _log_start = 'Trying to get credentials from ' - credentials: dict = {} - # 1. - if not credentials: - _LOG.info(_log_start + '\'CREDENTIALS_KEY\' env') - credentials = credentials_service.get_credentials_from_ssm() - if credentials and tenant.cloud == GOOGLE: - credentials = credentials_service.google_credentials_to_file( - credentials) - # 2. - if not credentials and batch_results and batch_results.credentials_key: - _LOG.info(_log_start + 'batch_results.credentials_key') - credentials = credentials_service.get_credentials_from_ssm( - batch_results.credentials_key) - if credentials and tenant.cloud == GOOGLE: - credentials = credentials_service.google_credentials_to_file( - credentials) - # 3. - if not credentials: - _LOG.info(_log_start + '`CUSTODIAN_ACCESS` parent') - application = modular_service.get_tenant_application( - tenant, ParentType.CUSTODIAN_ACCESS - ) - if application: - _creds = mcs.get_by_application(application, tenant) - if _creds: - credentials = _creds.dict() - # 4. - if not credentials: - _LOG.info(_log_start + 'customer`s access_application_id for cloud') - application = modular_service.get_tenant_application( - tenant, ParentType.CUSTODIAN_LICENSES - ) - if application: - assert application.type == CUSTODIAN_LICENSES_TYPE, \ - 'Something wrong with applications configuration' - meta = CustodianLicensesApplicationMeta.from_dict( - application.get_json().get('meta') or {}) - access_application_id = meta.access_application_id(tenant.cloud) - if access_application_id: - _creds = mcs.get_by_application(access_application_id, tenant) - if _creds: # not a dict - credentials = _creds.dict() - # 5. - if not credentials: - _LOG.info(_log_start + 'CredentialsManager') - credentials = credentials_service.get_credentials_for_tenant(tenant) - if credentials and tenant.cloud == GOOGLE: - credentials = credentials_service.google_credentials_to_file( - credentials) - # 6. - if not credentials and environment_service.is_management_creds_allowed(): - _LOG.info(_log_start + 'Maestro management parent & application') - _creds = mcs.get_by_tenant(tenant=tenant) - if _creds: # not a dict - credentials = _creds.dict() - - if credentials: - credentials = mcs.complete_credentials_dict( - credentials=credentials, - tenant=tenant - ) - return credentials - - -def get_platform_credentials(platform: Parent) -> dict: - """ - Credentials for platform (k8s) only. This should be refactored somehow - :param platform: - :return: - """ - mcs = modular_service.modular_client.maestro_credentials_service() - app = modular_service.get_application(platform.application_id) - _eks = app.type == ApplicationType.AWS_CREDENTIALS or app.type == ApplicationType.AWS_ROLE - token = credentials_service.get_credentials_from_ssm() - if token and app.type == ApplicationType.K8S_SERVICE_ACCOUNT: - meta = K8SServiceAccountApplicationMeta.from_dict(app.meta.as_dict()) - creds = K8SServiceAccountCredentials( - endpoint=meta.endpoint, ca=meta.ca, token=token - ) - return {ENV_KUBECONFIG: str(creds.save())} - elif token and _eks: - pass - elif app.type == ApplicationType.K8S_SERVICE_ACCOUNT: - return {ENV_KUBECONFIG: str(mcs.get_by_application(app).save())} - elif _eks: - mcs = modular_service.modular_client.maestro_credentials_service() - creds = mcs.get_by_application(platform.application_id) - name, region = platform.meta['name'], platform.meta['region'] - cluster = EKSClient.factory().from_keys( - aws_access_key_id=creds.AWS_ACCESS_KEY_ID, - aws_secret_access_key=creds.AWS_SECRET_ACCESS_KEY, - region_name=platform.meta['region'] - ).describe_cluster(name) - if not cluster: - _LOG.error(f'No cluster with name: {name} in region: {region}') - return {} - sts = Boto3ClientFactory('sts').from_keys( - aws_access_key_id=creds.AWS_ACCESS_KEY_ID, - aws_secret_access_key=creds.AWS_SECRET_ACCESS_KEY, - aws_session_token=creds.AWS_SESSION_TOKEN, - region_name=AWS_DEFAULT_REGION - ) - creds = K8SServiceAccountCredentials( - endpoint=cluster['endpoint'], - ca=cluster['certificateAuthority']['data'], - token=TokenGenerator(sts).get_token(name) - ) - return {ENV_KUBECONFIG: str(creds.save())} - return {} - - -@_XRAY.capture('Get rules to exclude') -def get_rules_to_exclude(tenant: Tenant) -> Set[str]: - """ - Returns a set of rules to exclude for the given tenant. - :param tenant: - :return: - """ - exclude = set() - tenant_setting = modular_service.get_tenant_bound_setting(tenant) - if tenant_setting: - _LOG.info('Updating rule to exclude with ones from tenant setting') - exclude.update( - tenant_setting.value.as_dict().get(RULES_TO_EXCLUDE) or [] - ) - parent = modular_service.modular_client.parent_service().get_linked_parent_by_tenant( # noqa - tenant=tenant, type_=ParentType.CUSTODIAN_LICENSES - ) - if parent: - meta = ParentMeta.from_dict(parent.meta.as_dict()) - exclude.update(meta.rules_to_exclude or []) - return exclude - - -@_XRAY.capture('Batch results job') -def batch_results_job(batch_results: BatchResults): - _XRAY.put_annotation('batch_results_id', batch_results.id) - - started_at = utc_iso() - work_dir = Path(WORK_DIR, batch_results.id) - work_dir.mkdir(parents=True, exist_ok=True) - findings_dir = os.path.join(work_dir, FINDINGS_FOLDER) - tenant: Tenant = tenant_service.get_tenant(batch_results.tenant_name) - cloud: str = tenant.cloud.upper() - credentials = get_credentials(tenant, batch_results) - if not credentials: - raise ExecutorException( - step_name=STEP_ASSERT_CREDENTIALS, - reason=f'Could not resolve credentials ' - f'for account: {tenant.project}' - ) - - whole_ruleset = policy_service.assure_event_driven_ruleset(cloud) - prepared_ruleset = policy_service.separate_ruleset( - from_=whole_ruleset, - work_dir=work_dir, - rules_to_exclude=get_rules_to_exclude(tenant), - rules_to_keep=set( - chain.from_iterable(batch_results.regions_to_rules().values())) - # or set(batch_results.rules_to_regions().keys()) - ) - scan = Scan(policies_files=[str(prepared_ruleset)], cloud=cloud, - work_dir=str(work_dir), findings_dir=findings_dir) - with EnvironmentContext(credentials, reset_all=False): - _LOG.info('Loading policies for multiple regions') - scan.policies = scan.load_from_regions_to_rules( - batch_results.regions_to_rules() - ) - _LOG.info('Loading has finished') - skipped_policies, failed_policies = scan.execute() - - report, detailed_report, statistics, api_calls = load_reports( - str(work_dir), skipped_policies, failed_policies, tenant, cloud - ) - _LOG.debug('Preparing a user detailed report.') - findings_path = report_service.tenant_findings_path(tenant) - saved_findings = s3_service.get_json_file_content( - bucket_name=environment_service.statistics_bucket_name(), - path=findings_path - ) - received_findings = FindingsCollection.from_detailed_report( - detailed_report) - - if saved_findings: - _LOG.info('Saved findings were found. ' - 'Deserializing them to a FindingsCollection') - saved_findings = FindingsCollection.deserialize(saved_findings) - else: - _LOG.info('Saved findings were not found. ' - 'Creating an empty FindingsCollection') - saved_findings = FindingsCollection() - _LOG.debug('Retrieving difference between received and saved findings') - difference: FindingsCollection = received_findings - saved_findings - _LOG.debug(f'Difference received with {len(difference)} items') - _LOG.debug('Updating the existing findings with the received ones') - saved_findings.update(received_findings) - - executor = ThreadPoolExecutor(max_workers=10) - statistics_b = environment_service.statistics_bucket_name() - reports_b = environment_service.reports_bucket_name() - _LOG.debug(f'Statistics bucket: {statistics_b}\n' - f'Reports bucket: {reports_b}') - - def _upload(bucket, key, body, trace): - _XRAY.set_trace_entity(trace) - s3_service.client.put_object(bucket, key, body) - _XRAY.clear_trace_entities() - - _path = lambda *_files: str(PurePosixPath(batch_results.id, *_files)) - - _LOG.debug('Submitting thread pool executor jobs') - current_trace = _XRAY.get_trace_entity() - executor.submit(_upload, statistics_b, findings_path, - saved_findings.json(), current_trace) - executor.submit(_upload, reports_b, _path(DETAILED_REPORT_FILE), - json.dumps(detailed_report, separators=(',', ':')), - current_trace) - executor.submit(_upload, reports_b, _path(DIFFERENCE_FILE), - difference.json(), current_trace) - executor.submit(_upload, reports_b, _path(REPORT_FILE), - json.dumps(report, separators=(',', ':')), - current_trace) - executor.submit(_upload, statistics_b, _path(STATISTICS_FILE), - json.dumps(statistics, separators=(',', ':')), - current_trace) - executor.submit(_upload, statistics_b, _path(API_CALLS_FILE), - json.dumps(api_calls, separators=(',', ':')), - current_trace) - _LOG.info('All "upload to s3" threads were submitted. Moving on ' - 'keeping the executor open. We will wait for ' - 'it to finish in the end') - upload_to_siem( - tenant, started_at, detailed_report, findings_dir, batch_results.id - ) - _LOG.debug('Waiting for "upload to S3" executor to finish') - executor.shutdown(wait=True) - _LOG.debug('Executor was shutdown') - - -def multi_account_event_driven_job() -> int: - for br_uuid in environment_service.batch_results_ids(): - batch_results: Optional[BatchResults] = None - try: - batch_results = retrieve_batch_result(br_uuid) - _LOG.info(f'Starting job for batch result {br_uuid}') - batch_results_job(batch_results) - _LOG.info(f'Job for batch result {br_uuid} has finished') - _LOG.info('Updating batch results item') - batch_results.rules = {} # in order to reduce the size of the item - batch_results.rulesets = [] - batch_results.credentials_key = None - batch_results.status = JobState.SUCCEEDED.value # is set in job-updater - except ExecutorException as ex_exception: - _LOG.error(f'An error \'{ex_exception}\' occurred during the job. ' - f'Setting job failure reason.') - if isinstance(batch_results, BatchResults): # may be none - batch_results.status = JobState.FAILED.value - batch_results.reason = _exception_to_str(ex_exception) - except Exception as exception: - _LOG.error(f'An unexpected error \'{exception}\' occurred during ' - f'the job. Setting job failure reason.') - if isinstance(batch_results, BatchResults): - batch_results.status = JobState.FAILED.value - batch_results.reason = _exception_to_str(exception) - if isinstance(batch_results, BatchResults): - _LOG.info('Saving batch results item') - batch_results.stopped_at = utc_iso() - batch_results.save() - if environment_service.is_docker() and WORK_DIR: - shutil.rmtree(WORK_DIR, ignore_errors=True) - _LOG.debug(f'Workdir for {WORK_DIR} successfully cleaned') - Path(CACHE_FILE).unlink(missing_ok=True) - return 0 - - -@_XRAY.capture('Standard job') -def standard_job() -> int: - work_dir = WORK_DIR - exit_code: int = 0 - findings_dir = os.path.join(work_dir, FINDINGS_FOLDER) - try: - job_updater_service.set_created_at() - - _tenant: Tenant = tenant_service.get_tenant() - platform = None - _cloud: str # not cloud but rather domain - if environment_service.platform_id(): # platform scan - platform = modular_service.modular_client.parent_service(). \ - get_parent_by_id(environment_service.platform_id()) - if platform.type == ParentType.PLATFORM_K8S: - _cloud = KUBERNETES - else: - raise RuntimeError('Something is wrong. Not supported ' - 'platform type') - else: # tenant scan - _cloud: str = _tenant.cloud.upper() - _job: Job = job_updater_service.job - _LOG.info( - f'{environment_service.job_type().capitalize()} job ' - f'\'{_job.job_id}\' has started;\n' - f'Cloud: \'{_cloud}\';\n' - f'Tenant: \'{_tenant.name}\';\n' - f'Platform: \'{platform.parent_id if platform else "-"}\';') - _LOG.debug(f'Entire sys.argv: {sys.argv}\n' - f'Environment: {environment_service}') - _XRAY.put_annotation('tenant_name', _tenant.name) - _XRAY.put_metadata('cloud', _cloud) - - job_updater_service.set_started_at() - - licensed_ruleset_dto_list = get_licensed_ruleset_dto_list(_tenant) - standard_ruleset_dto_list = list( - r.get_json() for r in ruleset_service.target_rulesets()) - - job_updater_service.update_scheduled_job() - - if platform: - credentials = get_platform_credentials(platform) - else: - credentials = get_credentials(_tenant) - if not credentials: - raise ExecutorException( - step_name=STEP_ASSERT_CREDENTIALS, - reason=f'Could not resolve credentials for account: ' - f'{_tenant.project}' - ) - policies_files = policy_service.get_policies( - work_dir=work_dir, - ruleset_list=standard_ruleset_dto_list + licensed_ruleset_dto_list, - rules_to_exclude=get_rules_to_exclude(_tenant), - rules_to_keep=_job.rules_to_scan - ) - - scan = Scan(policies_files=policies_files, cloud=_cloud, - work_dir=work_dir, findings_dir=findings_dir) - - with EnvironmentContext(credentials, reset_all=False): - skipped_policies, failed_policies = scan.execute() - - report, detailed_report, statistics, api_calls = load_reports( - str(work_dir), skipped_policies, failed_policies, _tenant, _cloud - ) - if platform: - findings_path = report_service.platform_findings_path(platform) - else: - findings_path = report_service.tenant_findings_path(_tenant) - - received_findings = FindingsCollection.from_detailed_report( - detailed_report - ) - saved_findings = s3_service.get_json_file_content( - bucket_name=environment_service.statistics_bucket_name(), - path=findings_path - ) - if saved_findings: - _LOG.info('Saved findings were found. ' - 'Deserializing them to a FindingsCollection') - saved_findings = FindingsCollection.deserialize(saved_findings) - else: - _LOG.info('Saved findings were not found. ' - 'Creating an empty FindingsCollection') - saved_findings = FindingsCollection() - - _LOG.debug('Updating the existing findings with the received ones') - saved_findings.update(received_findings) - - # force 10 workers because the used node has 1 vCPU, but we need - # more of them in order to upload multiple files concurrently - executor = ThreadPoolExecutor(max_workers=10) - statistics_b = environment_service.statistics_bucket_name() - reports_b = environment_service.reports_bucket_name() - _LOG.debug(f'Statistics bucket: {statistics_b}\n' - f'Reports bucket: {reports_b}') - - def _upload(bucket, key, body, trace): - _XRAY.set_trace_entity(trace) - s3_service.client.put_object(bucket, key, body) - _XRAY.clear_trace_entities() - - _path = lambda *_files: str(PurePosixPath(_job.job_id, *_files)) - - _LOG.debug('Submitting thread pool executor jobs') - current_trace = _XRAY.get_trace_entity() - executor.submit(_upload, statistics_b, findings_path, - saved_findings.json(), current_trace) - executor.submit(_upload, statistics_b, _path(STATISTICS_FILE), - json.dumps(statistics, separators=(',', ':')), - current_trace) - executor.submit(_upload, statistics_b, _path(API_CALLS_FILE), - json.dumps(api_calls, separators=(',', ':')), - current_trace) - executor.submit(_upload, reports_b, _path(DETAILED_REPORT_FILE), - json.dumps(detailed_report, separators=(',', ':')), - current_trace) - executor.submit(_upload, reports_b, _path(REPORT_FILE), - json.dumps(report, separators=(',', ':')), - current_trace) - _LOG.info('All "upload to s3" threads were submitted. Moving on ' - 'keeping the executor open.') - - _LOG.info('Going to upload to SIEM') - upload_to_siem( - tenant=_tenant, started_at=_job.started_at, - detailed_report=detailed_report, findings_dir=findings_dir, - job_id=_job.job_id - ) - - _LOG.debug('Waiting for "upload to S3" executor to finish') - executor.shutdown(wait=True) - _LOG.debug('Executor was shutdown') - - job_updater_service.set_succeeded_at() - _LOG.info(f'Job \'{_job.job_id}\' has ended') - except ExecutorException as ex_exception: - _LOG.exception(f'ExecutorException occurred during the job. ' - f'Setting job failure reason.') - reason = _exception_to_str(ex_exception) - job_updater_service.set_failed_at(reason) - - exit_code = 1 - _log = 'An error occurred on step `{step}`. ' \ - 'Setting error code to {code}' - if ex_exception.step_name == STEP_GRANT_JOB: - exit_code = 2 - _LOG.debug(_log.format(step=STEP_GRANT_JOB, code=str(exit_code))) - except Exception as exception: - _LOG.exception(f'Unexpected error occurred during ' - f'the job. Setting job failure reason.') - reason = _exception_to_str(exception) - job_updater_service.set_failed_at(reason) - exit_code = 1 - finally: - if environment_service.is_docker() and work_dir: - shutil.rmtree(work_dir, ignore_errors=True) - _LOG.debug(f'Workdir for {work_dir} successfully cleaned') - Path(CACHE_FILE).unlink(missing_ok=True) - TenantSettingJobLock(environment_service.tenant_name()).release() - return exit_code - - -def main(command: Optional[list] = None, environment: Optional[dict] = None): - """ - :parameter command: Optional[list] - :parameter environment: Optional[dict] - :return: None - """ - buffer = io.BytesIO() - _XRAY.configure(emitter=BytesEmitter(buffer)) # noqa - - _XRAY.begin_segment('AWS Batch job') - sampled = _XRAY.is_sampled() - _LOG.info(f'Batch job is {"" if sampled else "NOT "}sampled') - _XRAY.put_annotation('job_id', environment_service.batch_job_id()) - - global TIME_THRESHOLD, WORK_DIR - command = command or [] - environment = environment or {} - environment_service.override_environment(environment) - init_services() - TIME_THRESHOLD = batch_service.get_time_left() - WORK_DIR = Path(__file__).parent / environment_service.batch_job_id() - WORK_DIR.mkdir(parents=True, exist_ok=True) - - function = standard_job - if environment_service.is_multi_account_event_driven(): - function = multi_account_event_driven_job - - code = function() - _LOG.info(f'Function: \'{function.__name__}\' finished with code {code}') - _XRAY.end_segment() - - if sampled: - now = utc_datetime() - _LOG.debug('Writing xray data to S3') - s3_service.client.client.put_object( - Bucket=environment_service.statistics_bucket_name(), - Key=f'xray/executor/{now.year}/{now.month}/{now.day}/{environment_service.batch_job_id()}.log.gz', # noqa - Body=gzip.compress(buffer.getbuffer()) - ) - _LOG.info('Finished') - sys.exit(code) - - -if __name__ == '__main__': - main(command=sys.argv) diff --git a/executor/helpers/__init__.py b/executor/helpers/__init__.py deleted file mode 100644 index d1e19760a..000000000 --- a/executor/helpers/__init__.py +++ /dev/null @@ -1,99 +0,0 @@ -import re -from functools import reduce -from itertools import islice -from typing import Union, Generator, Iterable, List, Optional, Any - - -def filter_dict(d: dict, keys: set) -> dict: - if keys: - return {k: v for k, v in d.items() if k in keys} - return d - - -class HashableDict(dict): - def __hash__(self): - return hash(frozenset(self.items())) - - -def hashable(item: Union[dict, list, str, float, int, type(None)]): - """Makes hashable from the given item - >>> d = {'q': [1,3,5, {'h': 34, 'c': ['1', '2']}], 'v': {1: [1,2,3]}} - >>> d1 = {'v': {1: [1,2,3]}, 'q': [1,3,5, {'h': 34, 'c': ['1', '2']}]} - >>> hash(hashable(d)) == hash(hashable(d1)) - True - """ - if isinstance(item, dict): - h_dict = HashableDict() - for k, v in item.items(): - h_dict[k] = hashable(v) - return h_dict - elif isinstance(item, list): - h_list = [] - for i in item: - h_list.append(hashable(i)) - return tuple(h_list) - else: # str, int, bool, None (all hashable) - return item - - -def deep_get(dct, path): - return reduce( - lambda d, key: d.get(key, None) if isinstance(d, dict) else None, - path, dct) - - -def batches(iterable: Iterable, n: int) -> Generator[List, None, None]: - """ - Batch data into lists of length n. The last batch may be shorter. - """ - if n < 1: - raise ValueError('n must be >= 1') - it = iter(iterable) - batch = list(islice(it, n)) - while batch: - yield batch - batch = list(islice(it, n)) - - -class SingletonMeta(type): - _instances = {} - - def __call__(cls, *args, **kwargs): - if cls not in cls._instances: - instance = super().__call__(*args, **kwargs) - cls._instances[cls] = instance - return cls._instances[cls] - - -JSON_PATH_LIST_INDEXES = re.compile(r'\w*\[(-?\d+)\]') - - -def json_path_get(d: Union[dict, list], path: Optional[str] = None) -> Any: - """ - Simple json paths with only basic operations supported - >>> json_path_get({'a': 'b', 'c': [1,2,3, [{'b': 'c'}]]}, 'c[-1][0].b') - 'c' - >>> json_path_get([-1, {'one': 'two'}], 'c[-1][0].b') is None - True - >>> json_path_get([-1, {'one': 'two'}], '[-1].one') - 'two' - """ - if path.startswith('$'): - path = path[1:] - if path.startswith('.'): - path = path[1:] - parts = path.split('.') - - item = d - for part in parts: - try: - _key = part.split('[')[0] - _indexes = re.findall(JSON_PATH_LIST_INDEXES, part) - if _key: - item = item.get(_key) - for i in _indexes: - item = item[int(i)] - except (IndexError, TypeError, AttributeError): - item = None - break - return item diff --git a/executor/helpers/constants.py b/executor/helpers/constants.py deleted file mode 100644 index 6998ef33d..000000000 --- a/executor/helpers/constants.py +++ /dev/null @@ -1,171 +0,0 @@ -from enum import Enum - -DEFAULT_JOB_LIFETIME_MIN = 55 - -ENV_AWS_ACCESS_KEY_ID = 'AWS_ACCESS_KEY_ID' -ENV_AWS_SECRET_ACCESS_KEY = 'AWS_SECRET_ACCESS_KEY' -ENV_AWS_SESSION_TOKEN = 'AWS_SESSION_TOKEN' -ENV_AWS_DEFAULT_REGION = 'AWS_DEFAULT_REGION' - -ENV_AZURE_CLIENT_SECRET = 'AZURE_CLIENT_SECRET' - -ENV_DEFAULT_BUCKET_NAME = 'DEFAULT_REPORTS_BUCKET_NAME' -ENV_JOB_ID = 'AWS_BATCH_JOB_ID' -ENV_BATCH_RESULTS_ID = 'BATCH_RESULTS_ID' -ENV_BATCH_RESULTS_IDS = 'BATCH_RESULTS_IDS' - -ENV_TARGET_REGIONS = 'TARGET_REGIONS' -ENV_TARGET_RULESETS = 'TARGET_RULESETS' -ENV_TARGET_RULESETS_VIEW = 'TARGET_RULESETS_VIEW' -ENV_LICENSED_RULESETS = 'LICENSED_RULESETS' -ENV_VAR_REGION = 'AWS_REGION' -ENV_VAR_CREDENTIALS = 'CREDENTIALS_KEY' -ENV_VAR_JOB_LIFETIME_MIN = 'JOB_LIFETIME_MIN' -ENV_EVENT_DRIVEN = 'EVENT_DRIVEN' -ENV_SUBMITTED_AT = 'SUBMITTED_AT' -ENV_AFFECTED_LICENSES = 'AFFECTED_LICENSES' -ENV_EXECUTOR_MODE = 'EXECUTOR_MODE' -ENV_SCHEDULED_JOB_NAME = 'SCHEDULED_JOB_NAME' -ENV_JOB_TYPE = 'JOB_TYPE' -ENV_SYSTEM_CUSTOMER_NAME = 'SYSTEM_CUSTOMER_NAME' -ENV_TENANT_NAME = 'TENANT_NAME' -ENV_PLATFORM_ID = 'PLATFORM_ID' -ENV_VAR_STATS_S3_BUCKET_NAME = 'STATS_S3_BUCKET_NAME' -ENV_VAR_RULESETS_BUCKET_NAME = 'RULESETS_BUCKET_NAME' -ENV_ALLOW_MANAGEMENT_CREDS = 'ALLOW_MANAGEMENT_CREDENTIALS' - -STANDARD_JOB_TYPE = 'standard' -SCHEDULED_JOB_TYPE_FOR_ENV = 'scheduled' # ask me -MULTI_ACCOUNT_EVENT_DRIVEN_JOB_TYPE = 'event-driven-multi-account' - -CONSISTENT_EXECUTOR_MODE = 'consistent' -CONCURRENT_EXECUTOR_MODE = 'concurrent' - -AWS = 'AWS' -AZURE = 'AZURE' -GOOGLE = 'GOOGLE' -GCP = 'GCP' -KUBERNETES = 'KUBERNETES' - -ENV_GOOGLE_APPLICATION_CREDENTIALS = 'GOOGLE_APPLICATION_CREDENTIALS' -ENV_CLOUDSDK_CORE_PROJECT = 'CLOUDSDK_CORE_PROJECT' - -READY_TO_SCAN_CODE = 'READY_TO_SCAN' - -STEP_GET_BATCH_RESULTS_ED = 'get BatchResults for event-driven job' -STEP_BATCH_RESULT_ALREADY_SUCCEEDED = 'batch result has already succeeded' -STEP_GET_TENANT = 'get tenant' -STEP_GRANT_JOB = 'grant job execution' -STEP_DOWNLOAD_RULES = 'download rules content' -STEP_GET_CREDENTIALS_CONFIGURATION = 'get credentials configuration' -STEP_ASSERT_CREDENTIALS = 'assert temporary cloud credentials' -STEP_ASSUME_ROLE = 'assume role' -STEP_EXPORT_CREDENTIALS = 'export temporary cloud credentials' -STEP_LOAD_POLICIES = 'load policies' -STEP_COLLECT_STATISTICS = 'collect statistics' - -INVALID_CREDENTIALS_ERROR_CODES = { - AWS: {'AuthFailure', 'InvalidToken', - 'UnrecognizedClientException', 'ExpiredToken', - 'ExpiredTokenException'}, # add 'InvalidClientTokenId' - AZURE: {'InvalidAuthenticationTokenTenant', - 'AuthorizationFailed', 'ClientAuthenticationError', - 'Azure Error: AuthorizationFailed'}, - GOOGLE: set() -} -ACCESS_DENIED_ERROR_CODE = { - AWS: { - 'AccessDenied', 'AccessDeniedException', 'UnauthorizedOperation', - 'AuthorizationError', 'AccessDeniedException, AccessDeniedException' - }, # epam-aws-399-appflow_encrypted_with_kms_cm - AZURE: set(), - GOOGLE: set() -} -AWS_DEFAULT_REGION = 'us-east-1' -AZURE_COMMON_REGION = 'AzureCloud' -GCP_COMMON_REGION = 'us-central1' -MULTIREGION = 'multiregion' -FINDINGS_FOLDER = 'findings' - -CUSTOMER_ATTR = 'customer' -TENANT_ATTR = 'tenant' -RULESETS_ATTR = 'rulesets' -RULESET_CONTENT_ATTR = 'ruleset_content' - -CUSTODIAN_TYPE = 'CUSTODIAN' -CUSTODIAN_LICENSES_TYPE = 'CUSTODIAN_LICENSES' # contains licenses -SCHEDULED_JOB_TYPE = 'SCHEDULED_JOB' -TENANT_ENTITY_TYPE = 'TENANT' - -RULES_TO_EXCLUDE = 'rules_to_exclude' - -KID_ATTR = 'kid' -ALG_ATTR = 'alg' -TYP_ATTR = 'typ' -CLIENT_TOKEN_ATTR = 'client-token' - -PRODUCT_TYPE_NAME_ATTR = 'product_type_name' -PRODUCT_NAME_ATTR = 'product_name' -ENGAGEMENT_NAME_ATTR = 'engagement_name' -TEST_TITLE_ATTR = 'test_title' - -POST_METHOD = 'POST' -PATCH_METHOD = 'PATCH' - -ITEMS_PARAM = 'items' -MESSAGE_PARAM = 'message' -AUTHORIZATION_PARAM = 'authorization' - -# on-prem -ENV_SERVICE_MODE = 'SERVICE_MODE' -DOCKER_SERVICE_MODE, SAAS_SERVICE_MODE = 'docker', 'saas' - -ENV_MONGODB_USER = 'MONGO_USER' -ENV_MONGODB_PASSWORD = 'MONGO_PASSWORD' -ENV_MONGODB_URL = 'MONGO_URL' # host:port -ENV_MONGODB_DATABASE = 'MONGO_DATABASE' # custodian_as_a_service - -ENV_MINIO_HOST = 'MINIO_HOST' -ENV_MINIO_PORT = 'MINIO_PORT' -ENV_MINIO_ACCESS_KEY = 'MINIO_ACCESS_KEY' -ENV_MINIO_SECRET_ACCESS_KEY = 'MINIO_SECRET_ACCESS_KEY' - -ENV_VAULT_TOKEN = 'VAULT_TOKEN' -ENV_VAULT_HOST = 'VAULT_URL' -ENV_VAULT_PORT = 'VAULT_SERVICE_SERVICE_PORT' # env from Kubernetes - -ENVS_TO_HIDE = { - 'PS1', 'PS2', 'PS3', 'PS4', ENV_MONGODB_USER, ENV_MONGODB_PASSWORD, - ENV_MINIO_ACCESS_KEY, ENV_MINIO_SECRET_ACCESS_KEY, ENV_VAULT_TOKEN, - ENV_AWS_SECRET_ACCESS_KEY, ENV_AWS_SESSION_TOKEN, ENV_AZURE_CLIENT_SECRET -} -HIDDEN_ENV_PLACEHOLDER = '****' - - -class JobState(str, Enum): - """ - https://docs.aws.amazon.com/batch/latest/userguide/job_states.html - """ - SUBMITTED = 'SUBMITTED' - PENDING = 'PENDING' - RUNNABLE = 'RUNNABLE' - STARTING = 'STARTING' - RUNNING = 'RUNNING' - FAILED = 'FAILED' - SUCCEEDED = 'SUCCEEDED' - - -# custodian parent -ALL = 'ALL' -SCOPE_ATTR = 'scope' -CLOUDS_ATTR = 'clouds' - -COMPOUND_KEYS_SEPARATOR = '#' - -ED_AWS_RULESET_NAME = '_ED_AWS' -ED_AZURE_RULESET_NAME = '_ED_AZURE' -ED_GOOGLE_RULESET_NAME = '_ED_GOOGLE' - -KEY_RULES_TO_SEVERITY = 'RULES_TO_SEVERITY' -KEY_RULES_TO_STANDARDS = 'RULES_TO_STANDARDS' -KEY_HUMAN_DATA = 'HUMAN_DATA' diff --git a/executor/helpers/exception.py b/executor/helpers/exception.py deleted file mode 100644 index 29b4c4d2d..000000000 --- a/executor/helpers/exception.py +++ /dev/null @@ -1,8 +0,0 @@ -class ExecutorException(Exception): - - def __init__(self, step_name, reason): - self.step_name = step_name - self.reason = reason - - def __str__(self): - return f'Error occurred on \'{self.step_name}\' step: {self.reason}' diff --git a/executor/helpers/log_helper.py b/executor/helpers/log_helper.py deleted file mode 100644 index 038d918a2..000000000 --- a/executor/helpers/log_helper.py +++ /dev/null @@ -1,84 +0,0 @@ -import logging -import os -import re -import sys -from functools import cached_property -from typing import Dict - -LOG_FORMAT = '%(asctime)s - %(levelname)s - %(name)s - %(message)s' - - -class SensitiveFormatter(logging.Formatter): - """Formatter that removes sensitive information.""" - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self._param_to_regex: Dict[str, re.Pattern] = {} - - @cached_property - def secured_params(self) -> set: - return { - 'refresh_token', 'id_token', 'password', 'authorization', 'secret', - 'AWS_SECRET_ACCESS_KEY', 'AWS_SESSION_TOKEN', 'git_access_secret', - 'api_key', 'AZURE_CLIENT_ID', 'AZURE_CLIENT_SECRET', - 'GOOGLE_APPLICATION_CREDENTIALS', 'private_key', 'private_key_id', - 'Authorization', 'Authentication' - } - - @staticmethod - def _compile_param_regex(param: str) -> re.Pattern: - """ - It searches for values in JSON objects where key is $param: - If param is "password" the string '{"password": "blabla"}' will be - printed as '{"password": "****"}' - [\'"] - single or double quote; [ ]* - zero or more spaces - """ - return re.compile(f'[\'"]{param}[\'"]:[ ]*[\'"](.*?)[\'"]') - - def get_param_regex(self, param: str) -> re.Pattern: - if param not in self._param_to_regex: - self._param_to_regex[param] = self._compile_param_regex(param) - return self._param_to_regex[param] - - def _filter(self, string): - # Hoping that this regex substitutions do not hit performance... - for param in self.secured_params: - string = re.sub(self.get_param_regex(param), - f'\'{param}\': \'****\'', string) - return string - - def format(self, record): - original = logging.Formatter.format(self, record) - return self._filter(original) - - -custodian_logger = logging.getLogger('custodian') -custodian_logger.propagate = False -logging.captureWarnings(True) - -log_level = os.getenv('LOG_LEVEL') or 'DEBUG' -try: - custodian_logger.setLevel(log_level) -except ValueError: # not valid log level name - custodian_logger.setLevel(logging.DEBUG) - -console_handler = logging.StreamHandler(stream=sys.stdout) -console_handler.setFormatter(SensitiveFormatter(LOG_FORMAT)) -custodian_logger.addHandler(console_handler) - - -def get_logger(log_name: str, level: str = log_level): - """ - :param level: CRITICAL = 50 - ERROR = 40 - WARNING = 30 - INFO = 20 - DEBUG = 10 - NOTSET = 0 - :type log_name: str - :type level: int - """ - module_logger = custodian_logger.getChild(log_name) - if level: - module_logger.setLevel(level) - return module_logger diff --git a/executor/helpers/profiling.py b/executor/helpers/profiling.py deleted file mode 100644 index 9eb7d3bf8..000000000 --- a/executor/helpers/profiling.py +++ /dev/null @@ -1,92 +0,0 @@ -import json -import logging -import os -from abc import ABC, abstractmethod -from pathlib import Path -from typing import BinaryIO - -from aws_xray_sdk.core import xray_recorder, patch -from aws_xray_sdk.core.models.entity import Entity -from aws_xray_sdk.core.sampling.local.sampler import LocalSampler - -from helpers.log_helper import get_logger - -_LOG = get_logger(__name__) - -patch(('requests', 'pymongo', 'botocore')) - - -class Emitter(ABC): - @abstractmethod - def send_entity(self, entity: Entity): - """ - Must write this entity data somewhere. Entity is a segment - or subsegment - :param entity: - :return: - """ - - -class BytesEmitter(Emitter): - """ - Writes JSONs in bytes line by line to the given buffer - """ - - def __init__(self, buffer: BinaryIO): - self._buffer = buffer - - def send_entity(self, entity: Entity): - try: - self._buffer.write(json.dumps( - obj=entity.to_dict(), - separators=(',', ':'), - default=str - ).encode()) - self._buffer.write(b'\n') - except Exception: # noqa - _LOG.exception('Failed to write entity to bytes buffer') - - -class FileEmitter(Emitter): - """ - Custom emitter which sends segments to a file instead of sending them - to x-ray daemon. Writes JSON segments split by newline to a file - """ - - def __init__(self, filename: Path): - self._filename = filename - - def send_entity(self, entity: Entity): - try: - with open(self._filename, 'a') as fp: - fp.write(json.dumps( - obj=entity.to_dict(), - separators=(',', ':'), - default=str - ) + os.linesep) - except Exception: # noqa - _LOG.exception('Failed to write entity to json') - - @property - def filename(self) -> Path: - return self._filename - - -SAMPLING_RULES = { - "version": 2, - "default": { - "fixed_target": 0, - "rate": .5 # 50% of jobs are sampled - }, - "rules": [ - ] -} - -logging.getLogger('aws_xray_sdk').setLevel(logging.ERROR) -xray_recorder.configure( - context_missing='IGNORE_ERROR', - sampling=True, - sampler=LocalSampler(SAMPLING_RULES), - service='custodian-executor', - streaming_threshold=1000 -) diff --git a/executor/helpers/resource_map_generator.py b/executor/helpers/resource_map_generator.py deleted file mode 100644 index 75f6d0c40..000000000 --- a/executor/helpers/resource_map_generator.py +++ /dev/null @@ -1,111 +0,0 @@ -import importlib -import json -import logging -import os -import sys -from pathlib import Path - -MAPPING_FOLDER = 'resources_mapping' -MAPPING_PATH = Path(__file__).parent.parent / MAPPING_FOLDER -os.makedirs(MAPPING_PATH, exist_ok=True) - -AWS = 'AWS' -AZURE = 'AZURE' -GOOGLE = 'GOOGLE' - -AVAILABLE_CLOUDS = {AWS, AZURE, GOOGLE} - -RESOURCE_MAP = 'ResourceMap' -RESOURCE_TYPE_ATTRIBUTE = 'resource_type' -RESOURCE_META_REQUIRED_ATTRIBUTES = ('id', 'name', 'date') - -CLOUD_RESOURCE_MAP_PATH_MAPPING = { - AWS: 'c7n.resources.resource_map', - AZURE: 'c7n_azure.resources.resource_map', - GOOGLE: 'c7n_gcp.resources.resource_map' -} - -_LOG = logging.getLogger('custodian_resource_map_generator') -handler = logging.StreamHandler(stream=sys.stdout) -handler.setFormatter( - logging.Formatter('%(asctime)s - %(levelname)s - %(name)s - %(message)s')) -_LOG.addHandler(handler) -_LOG.setLevel(logging.DEBUG) - - -def import_resource_map(cloud: str): - resource_map_path = CLOUD_RESOURCE_MAP_PATH_MAPPING.get(cloud) - _LOG.debug(f'Going to import module \'{resource_map_path}\'') - if resource_map_path: - module = importlib.import_module(resource_map_path) - if hasattr(module, RESOURCE_MAP): - _LOG.debug(f'Module {resource_map_path} ' - f'has been imported successfully') - return getattr(module, RESOURCE_MAP) - _LOG.debug(f'Failed to import module: \'{resource_map_path}\'') - - -def build_resources_mapping(resource_map: dict): - result_map = {} - - for resource_type, resource_path in resource_map.items(): - type_meta = get_resource_type_meta(resource_path=resource_path) - if type_meta: - result_map[resource_type] = type_meta - return result_map - - -def get_resource_type_meta(resource_path: str): - *module_path, cls_to_import = resource_path.split('.') - module = importlib.import_module('.'.join(module_path)) - if not hasattr(module, cls_to_import): - _LOG.error(f'Failed to import \'{cls_to_import}\' class ' - f'from \'{module.__name__}\' module.') - return - cls = getattr(module, cls_to_import) - if not hasattr(cls, RESOURCE_TYPE_ATTRIBUTE): - _LOG.error(f'Missing \'{RESOURCE_TYPE_ATTRIBUTE}\' subclass' - f'from {resource_path}') - return - meta = getattr(cls, RESOURCE_TYPE_ATTRIBUTE) - if meta: - type_meta = {} - for attr in RESOURCE_META_REQUIRED_ATTRIBUTES: - attr_value = getattr(meta, attr) if hasattr(meta, attr) else None - if attr_value: - type_meta[attr] = attr_value - return type_meta - - -def save_resources_map(resources_map: dict, cloud: str): - with open(MAPPING_PATH / f'resources_map_{cloud.lower()}.json', 'w') as f: - json.dump(resources_map, f) - - -def main(target_cloud: str): - _LOG.debug(f'Importing resource map for cloud: {target_cloud}') - cloud_resource_mapping = import_resource_map(cloud=target_cloud) - _LOG.debug(f'ResourceMap: {cloud_resource_mapping}') - _LOG.debug('Building resources_mapping') - resources_mapping = build_resources_mapping( - resource_map=cloud_resource_mapping) - _LOG.debug(f'Resources Mapping: {resources_mapping}') - _LOG.debug('Reformatting detailed report.') - - save_resources_map(resources_mapping, target_cloud) - - -if __name__ == '__main__': - if len(sys.argv) > 1: - target_cloud = sys.argv[1] - target_cloud = target_cloud.upper() - if target_cloud not in AVAILABLE_CLOUDS: - _LOG.error(f'Specified cloud must be one of the following: ' - f'{AVAILABLE_CLOUDS}, got \'{target_cloud}\'') - exit(1) - main(target_cloud=target_cloud) - else: - _LOG.warning(f'{sys.argv[0]} did not receive any cloud argument. ' - f'Generating for all available clouds') - for cloud in AVAILABLE_CLOUDS: - main(target_cloud=cloud) diff --git a/executor/helpers/security.py b/executor/helpers/security.py deleted file mode 100644 index 93e7de355..000000000 --- a/executor/helpers/security.py +++ /dev/null @@ -1,88 +0,0 @@ -from abc import ABC, abstractmethod -from helpers.constants import KID_ATTR, ALG_ATTR, TYP_ATTR -from services.clients.abstract_key_management import \ - AbstractKeyManagementClient - -from base64 import urlsafe_b64encode -from json import dumps - -from datetime import datetime - -EXPIRATION_ATTR = 'exp' -ENCODING = 'utf-8' - - -class AbstractTokenEncoder(ABC): - - # Token: Header fields - kid: str - typ: str - alg: str - - # Token: Production fields - key_management: AbstractKeyManagementClient - - # Key Management: reference fields - prk_id: str - - def __init__(self): - self._reset() - - @abstractmethod - def __setitem__(self, key, value): - raise NotImplemented - - @abstractmethod - def expire(self, dnt: datetime): - raise NotImplemented - - @abstractmethod - def _reset(self): - raise NotImplemented - - @property - @abstractmethod - def product(self): - message = 'Could not produce a Token, due to improper {} attribute.' - for key, required_type in self.__annotations__.items(): - obj = getattr(self, key, None) - assert isinstance(obj, required_type), message.format(key) - - -class TokenEncoder(AbstractTokenEncoder): - - def __setitem__(self, key, value): - self._payload[key] = value - - def expire(self, dnt: datetime): - self._payload[EXPIRATION_ATTR] = dnt.timestamp() - - def _reset(self): - self._payload = {} - - @property - def product(self): - _ = super().product - - # Allows to inject header-data, independently. - header = { - TYP_ATTR: self.typ, ALG_ATTR: self.alg, KID_ATTR: self.kid - } - - payload = self._payload - - message = b'.'.join( - self._encode(dumps(each, separators=(",", ":")).encode(ENCODING)) - for each in (header, payload) - ) - - signature = self.key_management.sign( - key_id=self.prk_id, message=message, algorithm=self.alg - ) - token = message + b'.' + self._encode(signature) - self._reset() - return token.decode(ENCODING) - - @staticmethod - def _encode(data: bytes): - return urlsafe_b64encode(data).replace(b"=", b"") diff --git a/executor/helpers/time_helper.py b/executor/helpers/time_helper.py deleted file mode 100644 index 623b49403..000000000 --- a/executor/helpers/time_helper.py +++ /dev/null @@ -1,28 +0,0 @@ -from datetime import datetime, timezone -from typing import Optional - -from dateutil.parser import isoparse - - -def utc_datetime(_from: Optional[str] = None) -> datetime: - """ - Returns time-zone aware datetime object in UTC. You can optionally pass - an existing ISO string. The function will parse it to object and make - it UTC if it's not - :params _from: Optional[str] - :returns: datetime - """ - obj = datetime.now(timezone.utc) if not _from else isoparse(_from) - return obj.astimezone(timezone.utc) - - -def utc_iso(_from: Optional[datetime] = None) -> str: - """ - Returns time-zone aware datetime ISO string in UTC with military suffix. - You can optionally pass datetime object. The function will make it - UTC if it's not and serialize to string - :param _from: Optional[datetime] - :returns: str - """ - obj = _from or utc_datetime() - return obj.astimezone(timezone.utc).isoformat().replace('+00:00', 'Z') diff --git a/executor/helpers/timeit.py b/executor/helpers/timeit.py deleted file mode 100644 index 4a01892d9..000000000 --- a/executor/helpers/timeit.py +++ /dev/null @@ -1,17 +0,0 @@ -from datetime import datetime -from functools import wraps - -from helpers.log_helper import get_logger - -_LOG = get_logger(__name__) - - -def timeit(handler_func): - @wraps(handler_func) - def timed(*args, **kwargs): - ts = datetime.now() - result = handler_func(*args, **kwargs) - te = datetime.now() - _LOG.info(f'Stage {handler_func.__name__}, elapsed time: {te - ts}') - return result - return timed diff --git a/executor/integrations/__init__.py b/executor/integrations/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/executor/integrations/abstract_adapter.py b/executor/integrations/abstract_adapter.py deleted file mode 100644 index 075b9f7b2..000000000 --- a/executor/integrations/abstract_adapter.py +++ /dev/null @@ -1,7 +0,0 @@ -from abc import ABC, abstractmethod - - -class AbstractAdapter(ABC): - @abstractmethod - def push_notification(self, *args, **kwargs): - raise NotImplementedError() diff --git a/executor/integrations/defect_dojo_adapter.py b/executor/integrations/defect_dojo_adapter.py deleted file mode 100644 index 55543deb3..000000000 --- a/executor/integrations/defect_dojo_adapter.py +++ /dev/null @@ -1,468 +0,0 @@ -import copy -import io -import json -import uuid -from base64 import b64encode -from datetime import timedelta -from typing import List, Dict - -from helpers.constants import PRODUCT_TYPE_NAME_ATTR, PRODUCT_NAME_ATTR, \ - ENGAGEMENT_NAME_ATTR, TEST_TITLE_ATTR -from helpers.log_helper import get_logger -from helpers.time_helper import utc_datetime -from integrations.abstract_adapter import AbstractAdapter -from services.clients.dojo_client import DojoClient - -_LOG = get_logger(__name__) - -PRODUCT_TYPE_DTO_ATTRS = ['name', 'description', 'created', 'updated'] -PRODUCT_DTO_ATTRS = ['name', 'findings_count', 'description', 'created'] -FINDINGS_DTO_ATTRS = ['title', 'date', 'severity', 'sla_days_remaining', - 'mitigation', 'impact'] -TEMP_FOLDER = '/tmp' -NB_SPACE = ' ' # non-breaking space -TIME_FORMAT = "%H:%M:%S" - - -class CustodianToDefectDojoEntitiesMapper: - defaults = { - PRODUCT_TYPE_NAME_ATTR: 'Custodian scan for {customer}', - PRODUCT_NAME_ATTR: '{tenant}', - ENGAGEMENT_NAME_ATTR: '{day_scope}', - TEST_TITLE_ATTR: '{job_scope}: {job_id}' - } - - class _SkipKeyErrorDict(dict): - def __missing__(self, key): - return '{' + key + '}' - - def __init__(self, mapping: dict, **kwargs): - """The kwargs are used to substitute keys from the given mapping. - Currently, a set of possible keys to substitute is not defined and - depends only on the kwargs which were given.""" - self.mapping = mapping.copy() - for key, value in self.defaults.items(): - self.mapping.setdefault(key, value) - for key in set(self.mapping) - set(self.defaults): - self.mapping.pop(key) - self.dictionary = self._SkipKeyErrorDict(kwargs) - - def generate(self) -> dict: - for key, value in self.mapping.items(): - self.mapping[key] = value.format_map(self.dictionary) - return self.mapping - - -class DefectDojoAdapter(AbstractAdapter): - def __init__(self, host: str, api_key: str, entities_mapping: dict = None, - display_all_fields: bool = False, upload_files: bool = False, - resource_per_finding: bool = False): - self.entities_mapping = entities_mapping or {} - self.display_all_fields = display_all_fields if isinstance(display_all_fields, bool) else False - self.upload_files = upload_files if isinstance(upload_files, bool) else False - self.resource_per_finding = resource_per_finding if isinstance(resource_per_finding, bool) else False - self.client = DojoClient( - host=host, - api_key=api_key, - ) - super().__init__() - - def push_notification(self, job_id: str, started_at: str, - tenant_display_name: str, - customer_display_name: str, - policy_report: List[Dict]): - - _LOG.info(f'Job:\'{job_id}\' - formatting compatible policy reports.') - formatted_findings = self.format_policy_report_list( - policy_report_list=policy_report - ) - started_at_obj = utc_datetime(_from=started_at) - stopped_at_obj = utc_datetime() - job_scope = f'{started_at_obj.strftime(TIME_FORMAT)} - ' \ - f'{stopped_at_obj.strftime(TIME_FORMAT)}' - day_scope = \ - f'{stopped_at_obj.date().isoformat()} - ' \ - f'{(stopped_at_obj.date() + timedelta(days=1)).isoformat()}' - _LOG.info(f'Importing job {job_id}') - self.upload_scan( - formatted_findings=formatted_findings, - **CustodianToDefectDojoEntitiesMapper( - self.entities_mapping, - customer=customer_display_name, - tenant=tenant_display_name, - day_scope=day_scope, - job_scope=job_scope, - job_id=job_id - ).generate() - ) - - def upload_scan(self, test_title: str, engagement_name: str, - product_name: str, - product_type_name: str, formatted_findings): - product = self.client.get_product_by_name(product_name) - engagement = self.client.get_engagement( - name=engagement_name, - product_id=product.get('id') if product else None - ) - - if engagement and product: - reimport = True - _LOG.info(f'Engagement "{engagement_name}" inside product ' - f'{product_name} already exists, reimporting...') - else: - reimport = False - _LOG.info(f'Engagement "{engagement_name}" inside product ' - f'{product_name} does not exist, importing...') - - formatted_report_buffer = io.BytesIO( - json.dumps(formatted_findings).encode() - ) - _LOG.debug(f'Importing test: {test_title}') - self.client.import_scan( - product_type_name=product_type_name, - product_name=product_name, - engagement_name=engagement_name, - buffer=formatted_report_buffer, - reimport=reimport, - test_title=test_title - ) - formatted_report_buffer.close() - - def format_policy_report_list( - self, policy_report_list: List[Dict] - ): - """ - Returns EPAM`s Defect Dojo compatible findings-object, derived - out of policy report(s), each of which is either resource-specific or - accumulated - based on the one_res_per_finding. - :param policy_report_list: List[Dict] - :return: List[Dict] - """ - findings = [] - one_res_per_finding = self.resource_per_finding - helper_keys = ('report_fields',) - for policy_report in policy_report_list: - vuln_id = policy_report.get('vuln_id_from_tool') # policy-name. - - helper_values = [] - # Removes helper key-value pairs. - for key in helper_keys: - value = None - if key in policy_report: - value = policy_report.pop(key) - helper_values.append(value) - - report_fields, *_ = helper_values - report_fields = report_fields or [] - - if not report_fields and not self.display_all_fields: - _LOG.warning( - f'Policy:\'{vuln_id}\' maintains no \'report_fields\' ' - f'- going to default reporting all keys.' - ) - - resources = [] - for _resource in policy_report.get('resources', []): - resource = {} - if not self.display_all_fields and report_fields: - for field in report_fields: - resource[field] = _resource.get(field) - else: - resource = _resource - - if resource: - resources.append(resource) - - resource_type = policy_report.get('service') - - # v3.3.1 Date, Description is self set-up on the DefectDojo side. - # todo table-markdown may not be required, as it given within - # `description`, which may be used, for non-multi-regional titles. - - if one_res_per_finding: - - for resource in resources: - _report = copy.deepcopy(policy_report) - _report['resources'] = [resource] - _report['resources #'] = 1 - - if self.upload_files: - _report['files'] = { - 'title': f'{resource_type}-{str(uuid.uuid4())[:8]}' - f'.json', - 'data': b64encode(json.dumps(resource).encode()). - decode() - } - - if 'Arn' in resource: - _report["component_name"] = resource["Arn"] - - elif 'component_name' in _report: - del _report["component_name"] - - findings.append(_report) - - else: - policy_report['resources'] = resources - if self.upload_files: - policy_report['files'] = [ - { - 'title': f'{resource_type}-{str(uuid.uuid4())[:8]}' - f'.json', - 'data': b64encode(json.dumps(resource).encode()). - decode() - } - for resource in resources - ] - - findings.append(policy_report) - - return findings - - # Pre v3.3.1 methods. - - # def deactivate_previous_engagements(self, engagement_id): - # engagements = self.client.list_engagements() - # engagements = engagements.get('results', []) - # engagements = [eng for eng in engagements if - # eng.get('id') != engagement_id] - # - # for eng in engagements: - # eng_tests = self.client.list_tests(engagement_id=eng.get('id')) - # eng_tests = eng_tests.get('results', []) - # for test in eng_tests: - # active_findings = self.client.list_findings( - # test_id=test.get('id'), active=True) - # active_findings = active_findings.get('results', []) - # for finding in active_findings: - # self.client.deactivate_finding( - # finding=finding - # ) - # self.client.close_engagement( - # engagement_id=eng.get('id') - # ) - - # def format_detailed_report(self, detailed_report): - # _LOG.debug('Formatting report to Dojo format') - # formatted_findings = [] - # for region_name, region_policies in detailed_report.items(): - # for policy in region_policies: - # policy_meta = policy.get('policy', {}) - # resources = policy.get('resources', []) - # - # mitigation = policy_meta.get('mitigation', '') - # mitigation = self.format_mitigation(mitigation=mitigation) - # - # for resource in resources: - # description = policy_meta.get('name') + '\n' + \ - # policy_meta.get('article') - # finding_data = { - # 'title': policy_meta.get('description', ''), - # 'description': description, - # 'severity': policy_meta.get('severity', '').title(), - # 'mitigation': mitigation, - # 'impact': policy_meta.get('impact'), - # 'nb_occurences': 1, - # 'file_upload': resource - # } - # formatted_findings.append(finding_data) - # return formatted_findings - # - # @staticmethod - # def format_mitigation(mitigation): - # updated_mitigation = mitigation - # try: - # points = re.findall(r'\s(\d\.){1,2}\s', updated_mitigation) - # for point in points: - # updated_mitigation = updated_mitigation.replace( - # point, f'\n{point[1:]}') - # except Exception as e: - # _LOG.debug(f'Failed to format mitigation, error: {e}') - # return updated_mitigation - # - # def list_connected_tenants(self): - # response = self.client.list_product_types() - # product_types = response.get('results', []) - # return self._get_dojo_entities_dto( - # entities=product_types, - # dto_attrs=PRODUCT_TYPE_DTO_ATTRS - # ) - # - # def list_accounts(self, tenant_name=None): - # response = self.client.list_products(name=tenant_name) - # products = response.get('results', []) - # return self._get_dojo_entities_dto( - # entities=products, - # dto_attrs=PRODUCT_DTO_ATTRS - # ) - # - # def list_findings(self, account_name=None, severity=None, limit=None, - # offset=None): - # response = self.client.list_findings(severity=severity, limit=limit, - # offset=offset) - # findings = response.get('results', []) - # return self._get_dojo_entities_dto( - # entities=findings, - # dto_attrs=FINDINGS_DTO_ATTRS - # ) - - # @staticmethod - # def _save_report(formatted_findings, account_name, target_start): - # file_name = f'{account_name}_detailed_report_{target_start}.json' - # file_path = os.path.join(TEMP_FOLDER, file_name) - # with open(file_path, 'w') as f: - # json.dump(formatted_findings, f) - # return file_path - # - # @staticmethod - # def _delete_report(file_path): - # try: - # os.remove(file_path) - # except OSError: - # pass - # - # @staticmethod - # def _get_dojo_entities_dto(entities: list, dto_attrs: list): - # result = [] - # for entity in entities: - # entity_dto = {} - # for key, value in entity.items(): - # if key not in dto_attrs: - # continue - # value = value if value else '' - # entity_dto[key] = value - # result.append(entity_dto) - # return result - - # def _pre_v3_3_0_format_detailed_report(self, detailed_report, - # one_res_per_finding=True, - # finding_date: str = None): - # """Intended to transform CaaS detailed report to a format which can - # be understood and parsed by EPAM DefectDojo's fork with custom CaaS - # parser""" - # findings = [] - # for region, vulnerabilities in detailed_report.items(): - # for vulnerability in vulnerabilities: - # policy = vulnerability.get('policy') - # resources = vulnerability.get('resources', []) - # if len(resources) == 0: - # continue - # report_fields = policy.get('report_fields', []) - # references = [] - # for standard, versions in policy.get( - # 'standard_points', {}).items(): - # references.append(f"{standard}: {', '.join(versions)}") - # new_finding = { - # 'title': f'{policy.get("description", "No title")}', - # 'date': finding_date or utc_datetime().date().isoformat(), - # 'mitigation': policy.get('mitigation'), - # 'impact': policy.get('impact'), - # 'severity': policy.get('severity', 'Medium').title(), - # 'references': "\n".join(references), - # 'service': policy.get("resourceType"), - # 'component_name': "", - # 'tags': ','.join([region]), - # 'finding_groups': [policy.get('service_section', - # 'No service section')], - # 'vuln_id_from_tool': policy.get('name') - # } - # if one_res_per_finding: - # for res in resources: - # f_copied = copy.deepcopy(new_finding) - # table_str = self.make_markdown_table([res], - # report_fields) - # f_copied.update({ - # 'description': f'{policy.get("article")}\nRegion: ' - # f'**{region.lower()}**' - # f'\n\n{table_str}', - # }) - # if self.upload_files: - # f_copied.update({ - # 'files': [{ - # 'title': f'{policy.get("resourceType")}-' - # f'{str(uuid.uuid4())[:8]}.json', - # 'data': b64encode( - # json.dumps(res).encode()).decode()}] - # }) - # - # if 'Arn' in res: - # f_copied.update({"component_name": res.get( - # "Arn")}) - # else: - # del f_copied["component_name"] - # - # findings.append(f_copied) - # else: - # table_str = self.make_markdown_table(resources, - # report_fields) - # - # new_finding.update({ - # 'description': f'{policy.get("article")}\nRegion: ' - # f'**{region.lower()}**' - # f'\nNumber of resources found: ' - # f'**{len(resources)}**\n\n{table_str}', - # }) - # if self.upload_files: - # new_finding.update({ - # 'files': [{ - # 'title': f'{policy.get("resourceType")}-' - # f'{str(uuid.uuid4())[:8]}.json', - # 'data': b64encode( - # json.dumps(res).encode()).decode() - # } for res in resources] - # }) - # findings.append(new_finding) - # return {'findings': findings} - - # def make_markdown_table(self, detailed_resources, report_fields): - # display_all_fields = self.display_all_fields - # key_report = report_fields[0] if report_fields else None - # try: - # # TODO make it decent - # if key_report: - # detailed_resources_sorted = sorted( - # detailed_resources, key=lambda d: d[key_report]) - # detailed_resources = detailed_resources_sorted - # except KeyError: - # _LOG.warning(f'Invalid detailed_report.json!!! ' - # f'Fields: {report_fields}, ' - # f'resources :{detailed_resources}') - # display_all_fields = True - # resources = [] - # for detailed_resource in detailed_resources: - # resource = {} - # for key in detailed_resource: - # if report_fields and not display_all_fields: - # if key in report_fields: - # resource[key] = detailed_resource.get(key) - # continue - # if isinstance(detailed_resource[key], (str, int, float)): - # resource[key] = detailed_resource[key] - # resources.append(resource if resource else detailed_resource) - # try: - # # the crazy line below helps DefectDojo print table indents - # # properly, plus it makes the first letters of headers uppercase :) - # resources = [ - # {f'{key[0].upper() + key[1:]}{NB_SPACE}': f'{value}{NB_SPACE}' - # for key, value in resource.items()} for resource in resources - # ] - # fieldnames = [] - # for resource in resources: - # fieldnames.extend([field for field in resource.keys() - # if field not in fieldnames]) - # table_writer = MarkdownTableWriter() - # with io.StringIO() as buffer: - # dict_writer = csv.DictWriter(buffer, sorted(fieldnames)) - # dict_writer.writeheader() - # dict_writer.writerows(resources) - # table_writer.from_csv(buffer.getvalue()) - # table_str = table_writer.dumps() - # except Exception as e: - # _LOG.warning( - # 'Something went wrong while making resources table for ' - # f'DefectDojo\'s finding: {e}. Dumping resources to JSON') - # table_str = json.dumps( - # resources, sort_keys=True, indent=4).translate( - # {ord(c): None for c in '{[]}",'} - # ) - # return table_str diff --git a/executor/integrations/security_hub/__init__.py b/executor/integrations/security_hub/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/executor/integrations/security_hub/dump_findings_policy.py b/executor/integrations/security_hub/dump_findings_policy.py deleted file mode 100644 index 881b2016e..000000000 --- a/executor/integrations/security_hub/dump_findings_policy.py +++ /dev/null @@ -1,94 +0,0 @@ -import json -import os -from pathlib import Path - -from c7n.policy import Policy - -from helpers import batches -from helpers.log_helper import get_logger -from helpers.time_helper import utc_iso - -_LOG = get_logger(__name__) - - -class DumpFindingsPolicy: - def __init__(self, policy: Policy, output_dir: str): - self._policy = policy - self._output_dir = Path(output_dir) - self._output_dir.mkdir(parents=True, exist_ok=True) - - def __call__(self, *args, **kwargs): - resources = self._policy(*args, **kwargs) - if resources: - self.create_findings(resources) - return resources - - def create_findings(self, resources): - action_registry = self._policy.resource_manager.action_registry - finding_class = action_registry._factories.get('post-finding') - if not finding_class: - # not an aws cloud scan - return - finding_instance = finding_class(manager=self._policy.resource_manager) - - finding_instance.data["types"] = [ - "Software and Configuration Checks/Vulnerabilities/CVE" - ] - # DEFAULT_BATCH_SIZE = 1 # max is 32 - # # will show only DEFAULT_BATCH_SIZE resource in each finding - # - # finding_instance.data['batch_size'] = DEFAULT_BATCH_SIZE - - self.process(finding_instance, resources) - - def process(self, finding_instance, resources): - try: - findings = [] - now = utc_iso() - # default batch size to one to work around security hub console issue - # which only shows a single resource in a finding. - batch_size = finding_instance.data.get('batch_size', 32) - - for resource_set in batches(resources, batch_size): - for key, grouped_resources in \ - finding_instance.group_resources(resource_set).items(): - for resource in grouped_resources: - finding_id, created_at, updated_at = \ - self.resolve_id_and_time(finding_instance, - now, - key, - resource) - - finding = finding_instance.get_finding( - [resource], finding_id, created_at, updated_at) - - findings.append(finding) - - file_path = self.resolve_path_path() - - with open(file_path, 'w') as f: - json.dump(findings, f, separators=(',', ':')) - - except Exception as e: - _LOG.warning(f"error with finding '{finding_instance.name}' - {e}") - - @staticmethod - def resolve_id_and_time(finding_instance, now, key, resource): - if key == finding_instance.NEW_FINDING: - finding_id = None - created_at = now - updated_at = now - else: - try: - finding_id, created_at = finding_instance.get_finding_tag( - resource).split(':', 1) - updated_at = now - except Exception as e: - finding_id, created_at, updated_at = \ - None, now, now - return finding_id, created_at, updated_at - - def resolve_path_path(self): - policy_name = self._policy.data.get('name') - file_name = f'{policy_name}.json' - return os.path.join(self._output_dir, file_name) diff --git a/executor/integrations/security_hub/security_hub_adapter.py b/executor/integrations/security_hub/security_hub_adapter.py deleted file mode 100644 index 0f2765534..000000000 --- a/executor/integrations/security_hub/security_hub_adapter.py +++ /dev/null @@ -1,80 +0,0 @@ -import json -import os -import re -from typing import Optional - -import boto3 - -from helpers import batches -from helpers.log_helper import get_logger -from integrations.abstract_adapter import AbstractAdapter - -CREDENTIALS = 'Credentials' - -_LOG = get_logger(__name__) - - -class SecurityHubAdapter(AbstractAdapter): - def __init__(self, aws_region: str, product_arn: str, - aws_access_key_id: str, - aws_secret_access_key: str, - aws_session_token: Optional[str] = None, - aws_default_region: Optional[str] = None): - self.region = aws_region or aws_default_region - self.product_arn = product_arn - self.client = boto3.client( - 'securityhub', - aws_access_key_id=aws_access_key_id, - aws_secret_access_key=aws_secret_access_key, - aws_session_token=aws_session_token, - region_name=self.region - ) - super().__init__() - - def push_notification(self, **kwargs): - _LOG.debug(f'Going to push notifications to Security Hub') - - folder_path = kwargs.get('findings_folder') - file_paths = ( - os.path.join(folder_path, file) - for file in os.listdir(folder_path) if file.endswith('.json') - ) - findings = [] - for file_path in file_paths: - with open(file_path, 'r') as f: - file_findings = json.load(f) - findings.extend(file_findings) - - for finding in findings: - # todo resolve this (add AwsAccountId to configuration or get - # from findings) - finding["ProductArn"] = self.product_arn - finding["AwsAccountId"] = re.findall( - '/(\d+)/', self.product_arn)[0] - _LOG.debug(f'Going to upload {len(findings)} findings') - responses = self.upload_findings(findings=findings) - _LOG.debug(f'Security hub responses: {responses}') - - def batch_import_findings(self, findings): - return self.client.batch_import_findings(Findings=findings) - - def get_findings(self, findings): - return self.client.get_findings(findings) - - def upload_findings(self, findings: list): - """ - Upload findings to Security Hub by batches - """ - _LOG.info(f'Going to upload {len(findings)} findings ') - max_findings_in_request = 100 - - responses = [] - for batch in batches(findings, max_findings_in_request): - if len(batch) == 0: - continue - responses.append(self.client.batch_import_findings( - Findings=batch - )) - _LOG.debug(f'Security hub responses: {responses}') - - return responses diff --git a/executor/integrations/ses_adapter.py b/executor/integrations/ses_adapter.py deleted file mode 100644 index d5e884465..000000000 --- a/executor/integrations/ses_adapter.py +++ /dev/null @@ -1,97 +0,0 @@ -import json -from email.mime.application import MIMEApplication -from email.mime.multipart import MIMEMultipart -from email.mime.text import MIMEText - -import boto3 - -from helpers.log_helper import get_logger -from integrations.abstract_adapter import AbstractAdapter - -SIEM_SES_TYPE = 'ses' - -KEY_RECIPIENTS = 'recipients' -CHARSET = "UTF-8" -_LOG = get_logger(__name__) - -MESSAGE_SUBJECT_TEMPLATE = "Custodian Service Notification: \'{account}\' account" -MESSAGE_TEMPLATE = \ - """

Customer: {customer}

-

Account: {account}

-

Job id: {job_id}

- -

- Total Checks Performed: {total_checks_performed}
- Failed Checks: {failed_checks}
- Successful Checks: {successful_checks}
- Total resources with violations: {total_resources_violated_rules} -

- """ - - -class SESAdapter(AbstractAdapter): - def __init__(self, configuration: dict, region): - if not configuration.get('type') == SIEM_SES_TYPE: - return - self.configuration = configuration.get('configuration') - self.client = boto3.client('ses', region_name=region) - - def push_notification(self, **kwargs): - _LOG.debug(f'Going to push email notifications') - recipients = self.configuration.get(KEY_RECIPIENTS, []) - _LOG.debug(f'Recipients to notify: {recipients}') - detailed_report = kwargs.get('detailed_report') - report = kwargs.get('report') - account_name = kwargs.get('account_display_name') - customer_name = kwargs.get('customer_display_name') - job_id = kwargs.get('job_id') - - email_body = MESSAGE_TEMPLATE.format( - customer=customer_name, - account=account_name, - job_id=job_id, - **report - ) - subject = MESSAGE_SUBJECT_TEMPLATE.format( - account=account_name - ) - response = self.send_raw_email( - sender='bohdan_onsha@epam.com', - recipients=recipients, - title=subject, - html=email_body, - attachment=json.dumps(detailed_report, indent=4).encode('utf-8') - ) - _LOG.debug(f'ses response: {response}') - - def send_raw_email(self, sender: str, recipients: list, title: str, - text: str = None, html: str = None, - attachment=None) -> MIMEMultipart: - multipart_content_subtype = 'alternative' if text and html else 'mixed' - msg = MIMEMultipart(multipart_content_subtype) - msg['Subject'] = title - msg['From'] = sender - msg['To'] = ', '.join(recipients) - - # Record the MIME types of both parts - text/plain and text/html. - # According to RFC 2046, the last part of a multipart message, - # in this case the HTML message, is best and preferred. - if text: - part = MIMEText(text, 'plain') - msg.attach(part) - if html: - part = MIMEText(html, 'html') - msg.attach(part) - - # Add attachments - if attachment: - part = MIMEApplication(attachment) - part.add_header('Content-Disposition', 'attachment', - filename='detailed_report.json') - msg.attach(part) - - return self.client.send_raw_email( - Source=sender, - Destinations=recipients, - RawMessage={'Data': msg.as_string()} - ) diff --git a/executor/models/__init__.py b/executor/models/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/executor/models/batch_results.py b/executor/models/batch_results.py deleted file mode 100644 index fac8cdd2e..000000000 --- a/executor/models/batch_results.py +++ /dev/null @@ -1,89 +0,0 @@ -import os -from typing import Dict, Set - -from pynamodb.attributes import UnicodeAttribute, MapAttribute, ListAttribute -from pynamodb.indexes import AllProjection - -from helpers.constants import ENV_VAR_REGION -from models.modular import BaseModel, BaseGSI - -BR_ID = 'id' -BR_JOB_ID = 'jid' -BR_RULES_TO_SCAN = 'r' -BR_BUCKET_NAME = 'bn' -BR_BUCKET_PATH = 'bp' -BR_STATUS = 's' -BR_CLOUD_ID = 'cid' -BR_TENANT_NAME = 't' -BR_CUSTOMER_NAME = 'c' -BR_EVENT_REGISTRATION_START = 'ers' -BR_EVENT_REGISTRATION_END = 'ere' -BR_JOB_SUBMITTED_AT = 'jsa' -BR_JOB_STOPPED_AT = 'jsta' -BR_FAILURE_REASON = 'fr' -BR_CREDENTIALS_KEY = 'cr' - - -class JobIdIndex(BaseGSI): - class Meta: - index_name = f'{BR_JOB_ID}-index' - read_capacity_units = 1 - write_capacity_units = 1 - projection = AllProjection() - - job_id = UnicodeAttribute(hash_key=True, attr_name=BR_JOB_ID) - - -class BatchResults(BaseModel): - class Meta: - table_name = "CaaSBatchResults" - region = os.environ.get(ENV_VAR_REGION) - - id = UnicodeAttribute(hash_key=True, attr_name=BR_ID) # uuid - job_id = UnicodeAttribute(attr_name=BR_JOB_ID, null=True) # Batch job id - rules = MapAttribute(default=dict, attr_name=BR_RULES_TO_SCAN, null=True) - - credentials_key = UnicodeAttribute(null=True, attr_name=BR_CREDENTIALS_KEY) - status = UnicodeAttribute(null=True, - attr_name=BR_STATUS) # the same as in CaaSJobs - reason = UnicodeAttribute(null=True, attr_name=BR_FAILURE_REASON) - cloud_identifier = UnicodeAttribute(null=True, attr_name=BR_CLOUD_ID) - tenant_name = UnicodeAttribute(null=True, attr_name=BR_TENANT_NAME) - customer_name = UnicodeAttribute(null=True, attr_name=BR_CUSTOMER_NAME) - registration_start = UnicodeAttribute(null=True, - attr_name=BR_EVENT_REGISTRATION_START) - registration_end = UnicodeAttribute(null=True, - attr_name=BR_EVENT_REGISTRATION_END) - submitted_at = UnicodeAttribute(null=True, attr_name=BR_JOB_SUBMITTED_AT) - stopped_at = UnicodeAttribute(null=True, attr_name=BR_JOB_STOPPED_AT) - - job_id_index = JobIdIndex() - - def regions_to_rules(self) -> Dict[str, Set[str]]: - """ - Retrieves rules attribute from self and transforms in to a mapping: - { - 'eu-central-1': {'epam-aws-005..', 'epam-aws-006..'}, - 'eu-west-1': {'epam-aws-006..', 'epam-aws-007..'} - } - """ - ref = {} - for regions, rules in self.rules.as_dict().items(): - for region in regions.split(','): - ref.setdefault(region, set()).update(rules) - return ref - - def rules_to_regions(self) -> Dict[str, Set[str]]: - """ - Retrieves rules attribute from self and transforms in to a mapping: - { - 'epam-aws-005..': {'eu-central-1'}, - 'epam-aws-006..': {'eu-west-1', 'eu-central-1'}, - 'epam-aws-007..': {'eu-west-1'} - } - """ - ref = {} - for regions, rules in self.rules.as_dict().items(): - for rule in rules: - ref.setdefault(rule, set()).update(regions.split(',')) - return ref diff --git a/executor/models/credentials_manager.py b/executor/models/credentials_manager.py deleted file mode 100644 index 6ecc4de24..000000000 --- a/executor/models/credentials_manager.py +++ /dev/null @@ -1,61 +0,0 @@ -import os - -from pynamodb.attributes import ( - UnicodeAttribute, BooleanAttribute, NumberAttribute -) - -from pynamodb.indexes import AllProjection -from models.modular import BaseModel, BaseGSI -from helpers.constants import ENV_VAR_REGION - - -CM_CLOUD_IDENTIFIER = 'cid' -CM_CLOUD = 'c' -CM_ROLE_ARN = 'ra' -CM_ENABLED = 'e' -CM_EXPIRATION = 'ex' -CM_CREDENTIALS_KEY = 'ck' -CM_CUSTOMER = 'cn' -CM_TENANT = 'tn' - - -class TenantCloudIdentifierIndex(BaseGSI): - class Meta: - index_name = f'{CM_TENANT}-{CM_CLOUD}-index' - read_capacity_units = 1 - write_capacity_units = 1 - projection = AllProjection() - - tenant = UnicodeAttribute(hash_key=True, attr_name=CM_TENANT) - cloud = UnicodeAttribute(range_key=True, attr_name=CM_CLOUD) - - -class CustomerCloudIdentifierIndex(BaseGSI): - class Meta: - index_name = f'{CM_CUSTOMER}-{CM_CLOUD}-index' - read_capacity_units = 1 - write_capacity_units = 1 - projection = AllProjection() - - customer = UnicodeAttribute(hash_key=True, attr_name=CM_CUSTOMER) - cloud = UnicodeAttribute(range_key=True, attr_name=CM_CLOUD) - - -class CredentialsManager(BaseModel): - class Meta: - table_name = 'CaaSCredentialsManager' - region = os.environ.get(ENV_VAR_REGION) - - cloud_identifier = UnicodeAttribute(hash_key=True, - attr_name=CM_CLOUD_IDENTIFIER) - cloud = UnicodeAttribute(range_key=True, attr_name=CM_CLOUD) - trusted_role_arn = UnicodeAttribute(null=True, attr_name=CM_ROLE_ARN) - enabled = BooleanAttribute(null=True, default=False, attr_name=CM_ENABLED) - expiration = NumberAttribute(null=True, attr_name=CM_EXPIRATION) # timestamp - credentials_key = UnicodeAttribute(null=True, attr_name=CM_CREDENTIALS_KEY) - - customer = UnicodeAttribute(null=True, attr_name=CM_CUSTOMER) - tenant = UnicodeAttribute(null=True, attr_name=CM_TENANT) - - customer_cloud_identifier_index = CustomerCloudIdentifierIndex() - tenant_cloud_identifier_index = TenantCloudIdentifierIndex() diff --git a/executor/models/job.py b/executor/models/job.py deleted file mode 100644 index 856a8280d..000000000 --- a/executor/models/job.py +++ /dev/null @@ -1,35 +0,0 @@ -import os - -from pynamodb.attributes import UnicodeAttribute, ListAttribute, \ - TTLAttribute - -from helpers.constants import ENV_VAR_REGION -from models.modular import BaseModel - - -class Job(BaseModel): - """ - Model that represents job entity. - """ - - class Meta: - table_name = "CaaSJobs" - region = os.environ.get(ENV_VAR_REGION) - - job_id = UnicodeAttribute(hash_key=True) - tenant_display_name = UnicodeAttribute(null=True) - customer_display_name = UnicodeAttribute(null=True) - created_at = UnicodeAttribute(null=True) - started_at = UnicodeAttribute(null=True) - stopped_at = UnicodeAttribute(null=True) - submitted_at = UnicodeAttribute(null=True) - status = UnicodeAttribute(null=True) - job_queue = UnicodeAttribute(null=True) - job_definition = UnicodeAttribute(null=True) - job_owner = UnicodeAttribute(null=True) - scan_regions = ListAttribute(null=True) - scan_rulesets = ListAttribute(null=True) - reason = UnicodeAttribute(null=True) - scheduled_rule_name = UnicodeAttribute(null=True) - ttl = TTLAttribute(null=True) - rules_to_scan = ListAttribute(default=list) diff --git a/executor/models/modular/__init__.py b/executor/models/modular/__init__.py deleted file mode 100644 index 5fc16e5ff..000000000 --- a/executor/models/modular/__init__.py +++ /dev/null @@ -1,40 +0,0 @@ -import os - -from modular_sdk.models.pynamodb_extension.base_model import ABCMongoDBHandlerMixin, \ - build_mongodb_uri, RawBaseModel, RawBaseGSI -from modular_sdk.models.pynamodb_extension.base_safe_update_model import \ - BaseSafeUpdateModel as ModularSafeUpdateModel - -from helpers.constants import ENV_MONGODB_USER, ENV_MONGODB_PASSWORD, \ - ENV_MONGODB_URL, ENV_MONGODB_DATABASE - - -class CustodianMongoDBHandlerMixin(ABCMongoDBHandlerMixin): - @classmethod - def mongodb_handler(cls): - if not cls._mongodb: - from modular_sdk.connections.mongodb_connection import MongoDBConnection - from modular_sdk.models.pynamodb_extension.pynamodb_to_pymongo_adapter \ - import PynamoDBToPyMongoAdapter - user = os.environ.get(ENV_MONGODB_USER) - password = os.environ.get(ENV_MONGODB_PASSWORD) - url = os.environ.get(ENV_MONGODB_URL) - db = os.environ.get(ENV_MONGODB_DATABASE) - cls._mongodb = PynamoDBToPyMongoAdapter( - mongodb_connection=MongoDBConnection( - build_mongodb_uri(user, password, url), db - ) - ) - return cls._mongodb - - -class BaseModel(CustodianMongoDBHandlerMixin, RawBaseModel): - pass - - -class BaseGSI(CustodianMongoDBHandlerMixin, RawBaseGSI): - pass - - -class BaseSafeUpdateModel(CustodianMongoDBHandlerMixin, ModularSafeUpdateModel): - pass diff --git a/executor/models/modular/application.py b/executor/models/modular/application.py deleted file mode 100644 index d6643a9d9..000000000 --- a/executor/models/modular/application.py +++ /dev/null @@ -1,55 +0,0 @@ -import dataclasses -from typing import Optional - -from modular_sdk.models.application import Application - -from helpers.constants import AWS, AZURE, GOOGLE, KUBERNETES - -Application = Application - -ALLOWED_CLOUDS = set(map(str.lower, (AWS, AZURE, GOOGLE, KUBERNETES))) | \ - {AWS, AZURE, GOOGLE, KUBERNETES} - - -# use dataclass instead of pydantic in order not to add Pydantic to docker's -# dependencies -@dataclasses.dataclass(frozen=True) -class CustodianLicensesApplicationMeta: - """ - Application with type 'CUSTODIAN_LICENSES' meta - """ - awsAid: Optional[str] = None - azureAid: Optional[str] = None - googleAid: Optional[str] = None - kubernetesAid: Optional[str] = None - awsLk: Optional[str] = None - azureLk: Optional[str] = None - googleLk: Optional[str] = None - kubernetesLk: Optional[str] = None - - @classmethod - def from_dict(cls, dct: dict) -> 'CustodianLicensesApplicationMeta': - """ - Ignoring extra kwargs - :param dct: - :return: - """ - return cls(**{ - k.name: dct.get(k.name) for k in dataclasses.fields(cls) - }) - - @staticmethod - def _license_key_attr(cloud: str) -> str: - return f'{cloud.lower()}Lk' - - @staticmethod - def _access_application_id_attr(cloud: str) -> str: - return f'{cloud.lower()}Aid' - - def license_key(self, cloud: str) -> Optional[str]: - assert cloud in ALLOWED_CLOUDS - return getattr(self, self._license_key_attr(cloud), None) - - def access_application_id(self, cloud: str) -> Optional[str]: - assert cloud in ALLOWED_CLOUDS - return getattr(self, self._access_application_id_attr(cloud), None) diff --git a/executor/models/modular/customer.py b/executor/models/modular/customer.py deleted file mode 100644 index f3315747c..000000000 --- a/executor/models/modular/customer.py +++ /dev/null @@ -1 +0,0 @@ -from modular_sdk.models.customer import Customer diff --git a/executor/models/modular/parents.py b/executor/models/modular/parents.py deleted file mode 100644 index 868291333..000000000 --- a/executor/models/modular/parents.py +++ /dev/null @@ -1,23 +0,0 @@ -import dataclasses -from typing import List, Dict - -from modular_sdk.commons import DataclassBase -from modular_sdk.models.parent import Parent - -Parent = Parent - - -@dataclasses.dataclass(frozen=True) -class ParentMeta(DataclassBase): - """ - Common meta - """ - rules_to_exclude: List[str] - - -@dataclasses.dataclass(frozen=True) -class DefectDojoParentMeta(DataclassBase): - entities_mapping: Dict[str, str] = dataclasses.field(default_factory=dict) - display_all_fields: bool = False - upload_files: bool = False - resource_per_finding: bool = False diff --git a/executor/models/modular/tenant_settings.py b/executor/models/modular/tenant_settings.py deleted file mode 100644 index 3d439dfe1..000000000 --- a/executor/models/modular/tenant_settings.py +++ /dev/null @@ -1 +0,0 @@ -from modular_sdk.models.tenant_settings import TenantSettings \ No newline at end of file diff --git a/executor/models/modular/tenants.py b/executor/models/modular/tenants.py deleted file mode 100644 index f71598d44..000000000 --- a/executor/models/modular/tenants.py +++ /dev/null @@ -1 +0,0 @@ -from modular_sdk.models.tenant import Tenant diff --git a/executor/models/ruleset.py b/executor/models/ruleset.py deleted file mode 100644 index a69f33d7f..000000000 --- a/executor/models/ruleset.py +++ /dev/null @@ -1,76 +0,0 @@ -import os - -from pynamodb.attributes import UnicodeAttribute, BooleanAttribute, \ - ListAttribute, MapAttribute -from pynamodb.indexes import AllProjection - -from helpers.constants import ENV_VAR_REGION, COMPOUND_KEYS_SEPARATOR -from models.modular import BaseModel, BaseGSI - -RULESET_LICENSES = 'L' -RULESET_STANDARD = 'S' - - -class S3PathAttribute(MapAttribute): - bucket_name = UnicodeAttribute(null=True) - path = UnicodeAttribute(null=True) - - -class RulesetStatusAttribute(MapAttribute): - code = UnicodeAttribute(null=True) # READY_TO_SCAN, ASSEMBLING - last_update_time = UnicodeAttribute(null=True) # ISO8601 - reason = UnicodeAttribute(null=True) - - -class LicenseManagerIdIndex(BaseGSI): - class Meta: - index_name = 'license_manager_id-index' - read_capacity_units = 1 - write_capacity_units = 1 - projection = AllProjection() - - license_manager_id = UnicodeAttribute(hash_key=True) - - -class CustomerIdIndex(BaseGSI): - class Meta: - index_name = 'customer-id-index' - read_capacity_units = 1 - write_capacity_units = 1 - projection = AllProjection() - - customer = UnicodeAttribute(hash_key=True) - id = UnicodeAttribute(range_key=True) - - -class Ruleset(BaseModel): - class Meta: - table_name = 'CaaSRulesets' - region = os.environ.get(ENV_VAR_REGION) - - id = UnicodeAttribute(hash_key=True) # "customer#L|S#name#version" - customer = UnicodeAttribute() - cloud = UnicodeAttribute() - active = BooleanAttribute(default=True) - event_driven = BooleanAttribute(null=True, default=False) - rules = ListAttribute(of=UnicodeAttribute, default=list) - s3_path = S3PathAttribute(default=dict) - status = RulesetStatusAttribute(default=dict) - allowed_for = ListAttribute(default=list) - license_keys = ListAttribute(of=UnicodeAttribute, default=list) - license_manager_id = UnicodeAttribute(null=True) - - customer_id_index = CustomerIdIndex() - license_manager_id_index = LicenseManagerIdIndex() - - @property - def name(self) -> str: - return self.id.split(COMPOUND_KEYS_SEPARATOR)[2] - - @property - def version(self) -> str: - return self.id.split(COMPOUND_KEYS_SEPARATOR)[3] - - @property - def licensed(self) -> bool: - return self.id.split(COMPOUND_KEYS_SEPARATOR)[1] == RULESET_LICENSES diff --git a/executor/models/scheduled_job.py b/executor/models/scheduled_job.py deleted file mode 100644 index 6f8523711..000000000 --- a/executor/models/scheduled_job.py +++ /dev/null @@ -1,93 +0,0 @@ -import os -from typing import Optional - -from pynamodb.attributes import UnicodeAttribute, ListAttribute, \ - MapAttribute, BinaryAttribute, BooleanAttribute -from pynamodb.indexes import AllProjection - -from helpers.constants import ENV_VAR_REGION, CUSTODIAN_TYPE, \ - SCHEDULED_JOB_TYPE -from helpers.time_helper import utc_iso -from models.modular import BaseGSI, BaseSafeUpdateModel - -SCHEDULED_JOBS_TABLE_NAME = 'CaaSScheduledJobs' - -SJ_ID_ATTR = 'id' -SJ_TYPE_ATTR = 'type' -# SJ_PRINCIPAL_ATTR = 'principal' -SJ_TENANT_NAME = 'tenant_name' -SJ_CREATION_DATE_ATTR = 'creation_date' -SJ_LAST_EXECUTION_TIME_ATTR = 'last_execution_time' -SJ_CONTEXT_ATTR = 'context' -SJ_CUSTOMER_NAME_ATTR = 'customer_name' - - -class ScheduledJobContext(MapAttribute): - schedule = UnicodeAttribute(null=True) - job_state = BinaryAttribute(null=True) - scan_regions = ListAttribute(null=True, of=UnicodeAttribute) - scan_rulesets = ListAttribute(null=True, of=UnicodeAttribute) - is_enabled = BooleanAttribute(null=True, default_for_new=True) - - -class CustomerNamePrincipalIndex(BaseGSI): - class Meta: - index_name = f'{SJ_CUSTOMER_NAME_ATTR}-{SJ_TENANT_NAME}-index' - read_capacity_units = 1 - write_capacity_units = 1 - projection = AllProjection() - - customer_name = UnicodeAttribute(hash_key=True, - attr_name=SJ_CUSTOMER_NAME_ATTR) - tenant_name = UnicodeAttribute(range_key=True, attr_name=SJ_TENANT_NAME) - - -class ScheduledJob(BaseSafeUpdateModel): - """ - Model that represents job entity. - """ - default_type = f'{CUSTODIAN_TYPE}:{SCHEDULED_JOB_TYPE}' - - class Meta: - table_name = SCHEDULED_JOBS_TABLE_NAME - region = os.environ.get(ENV_VAR_REGION) - - id = UnicodeAttribute(hash_key=True, attr_name=SJ_ID_ATTR) - type = UnicodeAttribute(range_key=True, default=default_type, - attr_name=SJ_TYPE_ATTR) - tenant_name = UnicodeAttribute(null=True, attr_name=SJ_TENANT_NAME) - customer_name = UnicodeAttribute(null=True, - attr_name=SJ_CUSTOMER_NAME_ATTR) - creation_date = UnicodeAttribute( - null=True, attr_name=SJ_CREATION_DATE_ATTR, - default_for_new=lambda: utc_iso()) - last_execution_time = UnicodeAttribute( - null=True, attr_name=SJ_LAST_EXECUTION_TIME_ATTR) - context = ScheduledJobContext(default=dict, attr_name=SJ_CONTEXT_ATTR) - customer_name_principal_index = CustomerNamePrincipalIndex() - - @classmethod - def get_nullable(cls, hash_key, range_key=None, attributes_to_get=None - ) -> Optional['ScheduledJob']: - return super().get_nullable( - hash_key, range_key or cls.default_type, attributes_to_get) - - def update_with(self, customer: str = None, tenant: str = None, - schedule: str = None, scan_regions: list = None, - scan_rulesets: list = None, - is_enabled: bool = True): - """ - Sets some common parameters to the object - """ - if customer: - self.customer_name = customer - if tenant: - self.tenant_name = tenant - if schedule: - self.context.schedule = schedule - if scan_regions: - self.context.scan_regions = scan_regions - if scan_rulesets: - self.context.scan_rulesets = scan_rulesets - if is_enabled is not None: - self.context.is_enabled = is_enabled diff --git a/executor/models/setting.py b/executor/models/setting.py deleted file mode 100644 index f562ea2c5..000000000 --- a/executor/models/setting.py +++ /dev/null @@ -1,16 +0,0 @@ -import os - -from pynamodb.attributes import UnicodeAttribute - -from models.modular import BaseModel -from modular_sdk.models.pynamodb_extension.base_model import DynamicAttribute -from helpers.constants import ENV_VAR_REGION - - -class Setting(BaseModel): - class Meta: - table_name = 'CaaSSettings' - region = os.environ.get(ENV_VAR_REGION) - - name = UnicodeAttribute(hash_key=True, attr_name='name') - value = DynamicAttribute(attr_name='value') diff --git a/executor/requirements.txt b/executor/requirements.txt deleted file mode 100644 index abb86e4e7..000000000 --- a/executor/requirements.txt +++ /dev/null @@ -1,14 +0,0 @@ -boto3==1.26.80 -botocore==1.29.80 -pynamodb==5.3.2 -ruamel.yaml==0.17.16 -ruamel.yaml.clib==0.2.6 -dynamodb-json==1.3 -pymongo==4.5.0 -hvac==1.2.1 -requests==2.31.0 -python-dateutil==2.8.2 -pycryptodome==3.19.0 -jinja2==3.1.2 -modular-sdk==3.3.2 -aws-xray-sdk==2.12.0 \ No newline at end of file diff --git a/executor/resources_mapping/resources_map_aws.json b/executor/resources_mapping/resources_map_aws.json deleted file mode 100644 index 0403d05be..000000000 --- a/executor/resources_mapping/resources_map_aws.json +++ /dev/null @@ -1 +0,0 @@ -{"aws.account": {"id": "account_id", "name": "account_name"}, "aws.appsync-graphql-api": {"id": "apiId", "name": "apiId"}, "aws.acm-certificate": {"id": "CertificateArn", "name": "DomainName", "date": "CreatedAt"}, "aws.airflow": {"id": "Name", "name": "Name"}, "aws.alarm": {"id": "AlarmName", "name": "AlarmName", "date": "AlarmConfigurationUpdatedTimestamp"}, "aws.ami": {"id": "ImageId", "name": "Name", "date": "CreationDate"}, "aws.apigw-domain-name": {"id": "domainName", "name": "domainName", "date": "createdDate"}, "aws.app-elb": {"id": "LoadBalancerArn", "name": "LoadBalancerName", "date": "CreatedTime"}, "aws.app-elb-target-group": {"id": "TargetGroupArn", "name": "TargetGroupName"}, "aws.app-flow": {"id": "flowName", "name": "flowName"}, "aws.api-stage": {"id": "StageName", "name": "StageName", "date": "createdDate"}, "aws.asg": {"id": "AutoScalingGroupName", "name": "AutoScalingGroupName", "date": "CreatedTime"}, "aws.backup-plan": {"id": "BackupPlanName", "name": "BackupPlanId"}, "aws.backup-vault": {"id": "BackupVaultName", "name": "BackupVaultName"}, "aws.batch-compute": {"id": "computeEnvironmentName", "name": "computeEnvironmentName"}, "aws.batch-definition": {"id": "jobDefinitionName", "name": "jobDefinitionName"}, "aws.batch-queue": {"id": "jobQueueName", "name": "jobQueueName"}, "aws.cache-cluster": {"id": "CacheClusterId", "name": "CacheClusterId", "date": "CacheClusterCreateTime"}, "aws.cache-snapshot": {"id": "SnapshotName", "name": "SnapshotName", "date": "StartTime"}, "aws.cache-subnet-group": {"id": "CacheSubnetGroupName", "name": "CacheSubnetGroupName"}, "aws.catalog-portfolio": {"id": "Id", "name": "DisplayName", "date": "CreatedTime"}, "aws.cfn": {"id": "StackName", "name": "StackName", "date": "CreationTime"}, "aws.cloud-directory": {"id": "DirectoryArn", "name": "Name"}, "aws.cloudhsm-cluster": {"id": "ClusterId", "name": "ClusterId"}, "aws.cloudsearch": {"id": "DomainName", "name": "DomainName"}, "aws.cloudtrail": {"id": "TrailARN", "name": "Name"}, "aws.artifact-domain": {"id": "name", "name": "name"}, "aws.artifact-repo": {"id": "name", "name": "name"}, "aws.codebuild": {"id": "name", "name": "name", "date": "created"}, "aws.codecommit": {"id": "repositoryName", "name": "repositoryName", "date": "creationDate"}, "aws.codedeploy-app": {"id": "applicationId", "name": "applicationName", "date": "createTime"}, "aws.codedeploy-deployment": {"id": "deploymentId", "name": "deploymentId", "date": "createTime"}, "aws.codedeploy-group": {"id": "deploymentGroupId", "name": "deploymentGroupName"}, "aws.codepipeline": {"id": "name", "name": "name", "date": "created"}, "aws.config-recorder": {"id": "name", "name": "name"}, "aws.config-rule": {"id": "ConfigRuleName", "name": "ConfigRuleName"}, "aws.customer-gateway": {"id": "CustomerGatewayId", "name": "CustomerGatewayId"}, "aws.datapipeline": {"id": "pipelineId", "name": "name"}, "aws.dax": {"id": "ClusterName", "name": "ClusterName"}, "aws.directconnect": {"id": "connectionId", "name": "connectionName"}, "aws.directory": {"id": "DirectoryId", "name": "Name"}, "aws.distribution": {"id": "Id", "name": "DomainName", "date": "LastModifiedTime"}, "aws.dlm-policy": {"id": "PolicyId", "name": "PolicyId"}, "aws.dms-endpoint": {"id": "EndpointArn", "name": "EndpointIdentifier"}, "aws.dms-instance": {"id": "ReplicationInstanceIdentifier", "name": "ReplicationInstanceIdentifier", "date": "InstanceCreateTime"}, "aws.dynamodb-backup": {"id": "BackupArn", "name": "BackupName", "date": "BackupCreationDateTime"}, "aws.dynamodb-stream": {"id": "StreamArn", "name": "TableName", "date": "CreationDateTime"}, "aws.dynamodb-table": {"id": "TableName", "name": "TableName", "date": "CreationDateTime"}, "aws.ebs": {"id": "VolumeId", "name": "VolumeId", "date": "createTime"}, "aws.ebs-snapshot": {"id": "SnapshotId", "name": "SnapshotId", "date": "StartTime"}, "aws.ec2": {"id": "InstanceId", "name": "PublicDnsName", "date": "LaunchTime"}, "aws.ec2-reserved": {"id": "ReservedInstancesId", "name": "ReservedInstancesId", "date": "Start"}, "aws.ec2-host": {"id": "HostId", "name": "HostId", "date": "AllocationTime"}, "aws.ec2-spot-fleet-request": {"id": "SpotFleetRequestId", "name": "SpotFleetRequestId", "date": "CreateTime"}, "aws.ecr": {"id": "repositoryName", "name": "repositoryName"}, "aws.ecr-image": {"id": "imageDigest", "name": "repositoryName"}, "aws.ecs": {"id": "clusterArn", "name": "clusterName"}, "aws.ecs-container-instance": {"id": "containerInstanceArn", "name": "containerInstanceArn"}, "aws.ecs-service": {"id": "serviceArn", "name": "serviceName"}, "aws.ecs-task": {"id": "taskArn", "name": "taskArn"}, "aws.ecs-task-definition": {"id": "taskDefinitionArn", "name": "taskDefinitionArn"}, "aws.efs": {"id": "FileSystemId", "name": "Name", "date": "CreationTime"}, "aws.efs-mount-target": {"id": "MountTargetId", "name": "MountTargetId"}, "aws.eks": {"id": "name", "name": "name", "date": "createdAt"}, "aws.eks-nodegroup": {"id": "nodegroupArn", "name": "nodegroupName", "date": "createdAt"}, "aws.elasticbeanstalk": {"id": "ApplicationName", "name": "ApplicationName"}, "aws.elasticbeanstalk-environment": {"id": "EnvironmentName", "name": "EnvironmentName"}, "aws.elasticache-group": {"id": "ReplicationGroupId", "name": "ReplicationGroupId"}, "aws.elasticsearch": {"id": "DomainName", "name": "Name"}, "aws.elasticsearch-reserved": {"id": "ReservedElasticsearchInstanceId", "name": "ReservedElasticsearchInstanceId", "date": "StartTime"}, "aws.elb": {"id": "LoadBalancerName", "name": "DNSName", "date": "CreatedTime"}, "aws.emr": {"id": "Id", "name": "Name", "date": "Status.Timeline.CreationDateTime"}, "aws.emr-security-configuration": {"id": "Name", "name": "Name"}, "aws.eni": {"id": "NetworkInterfaceId", "name": "NetworkInterfaceId"}, "aws.event-bus": {"id": "Name", "name": "Name"}, "aws.event-rule": {"id": "Name", "name": "Name"}, "aws.event-rule-target": {"id": "Id", "name": "Id"}, "aws.firewall": {"id": "FirewallName", "name": "FirewallName"}, "aws.firehose": {"id": "DeliveryStreamName", "name": "DeliveryStreamName", "date": "CreateTimestamp"}, "aws.fsx": {"id": "FileSystemId", "name": "FileSystemId", "date": "CreationTime"}, "aws.fsx-backup": {"id": "BackupId", "name": "BackupId", "date": "CreationTime"}, "aws.gamelift-build": {"id": "BuildId", "name": "Name", "date": "CreationTime"}, "aws.gamelift-fleet": {"id": "FleetId", "name": "Name", "date": "CreationTime"}, "aws.glacier": {"id": "VaultName", "name": "VaultName"}, "aws.glue-connection": {"id": "Name", "name": "Name", "date": "CreationTime"}, "aws.glue-crawler": {"id": "Name", "name": "Name", "date": "CreatedOn"}, "aws.glue-catalog": {"id": "CatalogId", "name": "CatalogId"}, "aws.glue-database": {"id": "Name", "name": "Name", "date": "CreatedOn"}, "aws.glue-dev-endpoint": {"id": "EndpointName", "name": "EndpointName", "date": "CreatedTimestamp"}, "aws.glue-job": {"id": "Name", "name": "Name", "date": "CreatedOn"}, "aws.glue-classifier": {"id": "Name", "name": "Name", "date": "CreationTime"}, "aws.glue-ml-transform": {"id": "TransformId", "name": "Name"}, "aws.glue-security-configuration": {"id": "Name", "name": "Name", "date": "CreatedTimeStamp"}, "aws.glue-trigger": {"id": "Name", "name": "Name"}, "aws.glue-workflow": {"id": "Name", "name": "Name"}, "aws.glue-table": {"id": "Name", "name": "Name", "date": "CreatedOn"}, "aws.health-event": {"id": "arn", "name": "eventTypeCode", "date": "startTime"}, "aws.healthcheck": {"id": "Id", "name": "Id"}, "aws.hostedzone": {"id": "Id", "name": "Name"}, "aws.hsm": {"id": "HsmArn", "name": "Name"}, "aws.hsm-client": {"id": "ClientArn", "name": "Label"}, "aws.hsm-hapg": {"id": "HapgArn", "name": "HapgSerial", "date": "LastModifiedTimestamp"}, "aws.iam-certificate": {"id": "ServerCertificateName", "name": "ServerCertificateName", "date": "Expiration"}, "aws.iam-group": {"id": "GroupName", "name": "GroupName", "date": "CreateDate"}, "aws.iam-policy": {"id": "PolicyId", "name": "PolicyName", "date": "CreateDate"}, "aws.iam-policy-all": {"id": "PolicyId", "name": "PolicyName", "date": "CreateDate"}, "aws.iam-profile": {"id": "InstanceProfileName", "name": "InstanceProfileName", "date": "CreateDate"}, "aws.iam-role": {"id": "RoleName", "name": "RoleName", "date": "CreateDate"}, "aws.iam-role-light": {"id": "RoleName", "name": "RoleName", "date": "CreateDate"}, "aws.iam-user": {"id": "UserName", "name": "UserName", "date": "CreateDate"}, "aws.iam-saml-provider": {"id": "Arn", "name": "Arn"}, "aws.iam-oidc-provider": {"id": "Arn", "name": "Arn"}, "aws.identity-pool": {"id": "IdentityPoolId", "name": "IdentityPoolName"}, "aws.insight-rule": {"id": "Name", "name": "Name"}, "aws.internet-gateway": {"id": "InternetGatewayId", "name": "InternetGatewayId"}, "aws.iot": {"id": "thingName", "name": "thingName"}, "aws.kafka": {"id": "ClusterArn", "name": "ClusterName", "date": "CreationTime"}, "aws.key-pair": {"id": "KeyPairId", "name": "KeyName"}, "aws.kinesis": {"id": "StreamName", "name": "StreamName"}, "aws.kinesis-analytics": {"id": "ApplicationARN", "name": "ApplicationName"}, "aws.kinesis-analyticsv2": {"id": "ApplicationARN", "name": "ApplicationName"}, "aws.kinesis-video": {"id": "StreamName", "name": "StreamName"}, "aws.kms": {"id": "AliasArn", "name": "AliasName"}, "aws.kms-key": {"id": "KeyId", "name": "KeyId"}, "aws.lambda": {"id": "FunctionName", "name": "FunctionName", "date": "LastModified"}, "aws.lambda-layer": {"id": "LayerName", "name": "LayerName", "date": "CreatedDate"}, "aws.launch-config": {"id": "LaunchConfigurationName", "name": "LaunchConfigurationName", "date": "CreatedTime"}, "aws.launch-template-version": {"id": "LaunchTemplateId", "name": "LaunchTemplateName", "date": "CreateTime"}, "aws.lightsail-db": {"id": "arn", "name": "name", "date": "createdAt"}, "aws.lightsail-elb": {"id": "arn", "name": "name", "date": "createdAt"}, "aws.lightsail-instance": {"id": "arn", "name": "name", "date": "createdAt"}, "aws.log-group": {"id": "logGroupName", "name": "logGroupName", "date": "creationTime"}, "aws.log-metric": {"id": "filterName", "name": "filterName", "date": "creationTime"}, "aws.message-broker": {"id": "BrokerId", "name": "BrokerName"}, "aws.message-config": {"id": "Id", "name": "Name"}, "aws.mirror-session": {"id": "TrafficMirrorSessionId", "name": "TrafficMirrorSessionId"}, "aws.mirror-target": {"id": "TrafficMirrorTargetId", "name": "TrafficMirrorTargetId"}, "aws.ml-model": {"id": "MLModelId", "name": "Name", "date": "CreatedAt"}, "aws.nat-gateway": {"id": "NatGatewayId", "name": "NatGatewayId", "date": "CreateTime"}, "aws.network-acl": {"id": "NetworkAclId", "name": "NetworkAclId"}, "aws.elastic-ip": {"id": "AllocationId", "name": "PublicIp"}, "aws.network-addr": {"id": "AllocationId", "name": "PublicIp"}, "aws.ops-item": {"id": "OpsItemId", "name": "Title"}, "aws.opswork-cm": {"id": "ServerName", "name": "ServerName", "date": "CreatedAt"}, "aws.opswork-stack": {"id": "StackId", "name": "Name", "date": "CreatedAt"}, "aws.prefix-list": {"id": "PrefixListId", "name": "PrefixListName"}, "aws.peering-connection": {"id": "VpcPeeringConnectionId", "name": "VpcPeeringConnectionId"}, "aws.qldb": {"id": "Name", "name": "Name", "date": "CreationDateTime"}, "aws.r53domain": {"id": "DomainName", "name": "DomainName"}, "aws.rds": {"id": "DBInstanceIdentifier", "name": "Endpoint.Address", "date": "InstanceCreateTime"}, "aws.rds-cluster": {"id": "DBClusterIdentifier", "name": "DBClusterIdentifier"}, "aws.rds-cluster-param-group": {"id": "DBClusterParameterGroupName", "name": "DBClusterParameterGroupName"}, "aws.rds-cluster-snapshot": {"id": "DBClusterSnapshotIdentifier", "name": "DBClusterSnapshotIdentifier", "date": "SnapshotCreateTime"}, "aws.rds-param-group": {"id": "DBParameterGroupName", "name": "DBParameterGroupName"}, "aws.rds-reserved": {"id": "ReservedDBInstanceId", "name": "ReservedDBInstanceId", "date": "StartTime"}, "aws.rds-snapshot": {"id": "DBSnapshotIdentifier", "name": "DBSnapshotIdentifier", "date": "SnapshotCreateTime"}, "aws.rds-subnet-group": {"id": "DBSubnetGroupName", "name": "DBSubnetGroupName"}, "aws.rds-subscription": {"id": "CustSubscriptionId", "name": "CustSubscriptionId", "date": "SubscriptionCreateTime"}, "aws.redshift": {"id": "ClusterIdentifier", "name": "ClusterIdentifier", "date": "ClusterCreateTime"}, "aws.redshift-snapshot": {"id": "SnapshotIdentifier", "name": "SnapshotIdentifier", "date": "SnapshotCreateTime"}, "aws.redshift-subnet-group": {"id": "ClusterSubnetGroupName", "name": "ClusterSubnetGroupName"}, "aws.redshift-reserved": {"id": "ReservedNodeId", "name": "ReservedNodeId", "date": "StartTime"}, "aws.rest-account": {"id": "account_id", "name": "account_id"}, "aws.rest-api": {"id": "id", "name": "name", "date": "createdDate"}, "aws.rest-api-v2": {"id": "ApiId", "name": "Name", "date": "CreatedDate"}, "aws.rest-resource": {"id": "id", "name": "path"}, "aws.rest-stage": {"id": "stageName", "name": "stageName", "date": "createdDate"}, "aws.rest-vpclink": {"id": "id", "name": "name"}, "aws.rest-client-certificate": {"id": "clientCertificateId", "name": "client_certificate_id"}, "aws.route-table": {"id": "RouteTableId", "name": "RouteTableId"}, "aws.rrset": {"id": "Name", "name": "Name"}, "aws.s3": {"id": "Name", "name": "Name", "date": "CreationDate"}, "aws.s3-light": {"id": "Name", "name": "Name", "date": "CreationDate"}, "aws.s3-access-point": {"id": "Name", "name": "Name"}, "aws.s3-access-point-multi": {"id": "Name", "name": "Name"}, "aws.sagemaker-endpoint": {"id": "EndpointArn", "name": "EndpointName", "date": "CreationTime"}, "aws.sagemaker-endpoint-config": {"id": "EndpointConfigArn", "name": "EndpointConfigName", "date": "CreationTime"}, "aws.sagemaker-job": {"id": "TrainingJobArn", "name": "TrainingJobName", "date": "CreationTime"}, "aws.sagemaker-model": {"id": "ModelArn", "name": "ModelName", "date": "CreationTime"}, "aws.sagemaker-notebook": {"id": "NotebookInstanceArn", "name": "NotebookInstanceName", "date": "CreationTime"}, "aws.sagemaker-transform-job": {"id": "TransformJobArn", "name": "TransformJobName", "date": "CreationTime"}, "aws.scaling-policy": {"id": "PolicyName", "name": "PolicyName", "date": "CreatedTime"}, "aws.secrets-manager": {"id": "Name", "name": "Name"}, "aws.security-group": {"id": "GroupId", "name": "GroupName"}, "aws.serverless-app": {"id": "ApplicationId", "name": "Name"}, "aws.service-quota-request": {"id": "Id", "name": "Id"}, "aws.service-quota": {"id": "QuotaCode", "name": "QuotaName"}, "aws.shield-attack": {"id": "AttackId", "name": "AttackId", "date": "StartTime"}, "aws.shield-protection": {"id": "Id", "name": "Name"}, "aws.simpledb": {"id": "DomainName", "name": "DomainName"}, "aws.snowball": {"id": "JobId", "name": "Description", "date": "CreationDate"}, "aws.snowball-cluster": {"id": "ClusterId", "name": "Description", "date": "CreationDate"}, "aws.sns": {"id": "TopicArn", "name": "DisplayName"}, "aws.sns-subscription": {"id": "SubscriptionArn", "name": "SubscriptionArn"}, "aws.sqs": {"id": "QueueUrl", "name": "QueueUrl", "date": "CreatedTimestamp"}, "aws.ssm-document": {"id": "Name", "name": "Name", "date": "RegistrationDate"}, "aws.ssm-data-sync": {"id": "DataSync", "name": "Title"}, "aws.ssm-activation": {"id": "ActivationId", "name": "Description", "date": "CreatedDate"}, "aws.ssm-managed-instance": {"id": "InstanceId", "name": "Name", "date": "RegistrationDate"}, "aws.ssm-parameter": {"id": "Name", "name": "Name"}, "aws.step-machine": {"id": "stateMachineArn", "name": "name", "date": "creationDate"}, "aws.storage-gateway": {"id": "GatewayARN", "name": "GatewayName"}, "aws.streaming-distribution": {"id": "Id", "name": "DomainName", "date": "LastModifiedTime"}, "aws.subnet": {"id": "SubnetId", "name": "SubnetId"}, "aws.support-case": {"id": "caseId", "name": "displayId", "date": "timeCreated"}, "aws.swf-domain": {"id": "name", "name": "name"}, "aws.transit-attachment": {"id": "TransitGatewayAttachmentId", "name": "TransitGatewayAttachmentId"}, "aws.transit-gateway": {"id": "TransitGatewayId", "name": "TransitGatewayId"}, "aws.user-pool": {"id": "Id", "name": "Name"}, "aws.vpc": {"id": "VpcId", "name": "VpcId"}, "aws.vpc-endpoint-service": {"id": "ServiceId", "name": "ServiceId"}, "aws.vpc-endpoint": {"id": "VpcEndpointId", "name": "VpcEndpointId", "date": "CreationTimestamp"}, "aws.vpn-connection": {"id": "VpnConnectionId", "name": "VpnConnectionId"}, "aws.vpn-gateway": {"id": "VpnGatewayId", "name": "VpnGatewayId"}, "aws.waf": {"id": "WebACLId", "name": "Name"}, "aws.waf-regional": {"id": "WebACLId", "name": "Name"}, "aws.workspaces": {"id": "WorkspaceId", "name": "WorkspaceId"}, "aws.workspaces-directory": {"id": "DirectoryId", "name": "DirectoryName"}, "aws.workspaces-image": {"id": "ImageId", "name": "ImageId"}} \ No newline at end of file diff --git a/executor/resources_mapping/resources_map_azure.json b/executor/resources_mapping/resources_map_azure.json deleted file mode 100644 index f76ed2265..000000000 --- a/executor/resources_mapping/resources_map_azure.json +++ /dev/null @@ -1 +0,0 @@ -{"azure.advisor-recommendation": {"id": "id", "name": "name"}, "azure.aks": {"id": "id", "name": "name"}, "azure.activity-log-alert": {"id": "id", "name": "name"}, "azure.activity-log": {"id": "id", "name": "name"}, "azure.api-management": {"id": "id", "name": "name"}, "azure.app-configuration": {"id": "id", "name": "name"}, "azure.appserviceplan": {"id": "id", "name": "name"}, "azure.application-gateway": {"id": "id", "name": "name"}, "azure.armresource": {"id": "id", "name": "name"}, "azure.automation-account": {"id": "id", "name": "name"}, "azure.batch": {"id": "id", "name": "name"}, "azure.cdnprofile": {"id": "id", "name": "name"}, "azure.cognitiveservice": {"id": "id", "name": "name"}, "azure.container-group": {"id": "id", "name": "name"}, "azure.containerregistry": {"id": "id", "name": "name"}, "azure.container-registry": {"id": "id", "name": "name"}, "azure.containerservice": {"id": "id", "name": "name"}, "azure.cosmosdb": {"id": "id", "name": "name"}, "azure.cosmosdb-collection": {"id": "id"}, "azure.cosmosdb-database": {"id": "id"}, "azure.cost-management-export": {"id": "id"}, "azure.databricks": {"id": "id", "name": "name"}, "azure.datafactory": {"id": "id", "name": "name"}, "azure.datalake": {"id": "id", "name": "name"}, "azure.datalake-analytics": {"id": "id", "name": "name"}, "azure.datalake-account": {"id": "id", "name": "name"}, "azure.diagnostic-settings": {"id": "id", "name": "name"}, "azure.disk": {"id": "id", "name": "name"}, "azure.dnszone": {"id": "id", "name": "name"}, "azure.event-grid-domain": {"id": "id"}, "azure.event-grid-topic": {"id": "id"}, "azure.defender-autoprovisioning": {"id": "id", "name": "name"}, "azure.defender-pricing": {"id": "id", "name": "name"}, "azure.defender-setting": {"id": "id", "name": "name"}, "azure.eventhub": {"id": "id", "name": "name"}, "azure.eventsubscription": {"id": "id"}, "azure.front-door": {"id": "id", "name": "name"}, "azure.hdinsight": {"id": "id", "name": "name"}, "azure.image": {"id": "id", "name": "name"}, "azure.iothub": {"id": "id", "name": "name"}, "azure.keyvault": {"id": "id", "name": "name"}, "azure.keyvault-by-subscription": {"id": "id", "name": "name"}, "azure.keyvault-certificate": {"id": "id"}, "azure.keyvault-key": {"id": "kid"}, "azure.keyvault-keys": {"id": "kid"}, "azure.keyvault-secret": {"id": "id", "name": "id"}, "azure.keyvault-secrets": {"id": "id", "name": "id"}, "azure.kusto": {"id": "id", "name": "name"}, "azure.loadbalancer": {"id": "id", "name": "name"}, "azure.logic-app-workflow": {"id": "id", "name": "name"}, "azure.machine-learning-workspace": {"id": "id"}, "azure.mysql": {"id": "id", "name": "name"}, "azure.namespace": {"id": "id", "name": "name"}, "azure.networkinterface": {"id": "id", "name": "name"}, "azure.networksecuritygroup": {"id": "id", "name": "name"}, "azure.network-watcher": {"id": "id", "name": "name"}, "azure.mariadb-server": {"id": "id", "name": "name"}, "azure.mysql-server": {"id": "id", "name": "name"}, "azure.policyassignments": {"id": "id", "name": "name"}, "azure.postgresql-database": {"id": "id"}, "azure.postgresql-server": {"id": "id", "name": "name"}, "azure.publicip": {"id": "id", "name": "name"}, "azure.recordset": {"id": "id", "name": "name"}, "azure.redis": {"id": "id", "name": "name"}, "azure.resourcegroup": {"id": "id", "name": "name"}, "azure.roleassignment": {"id": "id"}, "azure.roledefinition": {"id": "id"}, "azure.routetable": {"id": "id", "name": "name"}, "azure.security-assessments": {"id": "id"}, "azure.security-pricing": {"id": "id"}, "azure.security-contacts": {"id": "id"}, "azure.security-auto-provisioning-settings": {"id": "id"}, "azure.security-settings": {"id": "id"}, "azure.security-jit-policies": {"id": "id"}, "azure.search": {"id": "id", "name": "name"}, "azure.service-fabric-cluster": {"id": "id", "name": "name"}, "azure.service-fabric-cluster-managed": {"id": "id", "name": "name"}, "azure.signalr": {"id": "id", "name": "name"}, "azure.stream-job": {"id": "id", "name": "name"}, "azure.sql-auditing-settings": {"id": "id", "name": "id"}, "azure.sqlauditingsettings": {"id": "id", "name": "id"}, "azure.sql-database": {"id": "id", "name": "name"}, "azure.sqldatabase": {"id": "id", "name": "name"}, "azure.sql-managed-instance": {"id": "id", "name": "name"}, "azure.sqlmanagedinstance": {"id": "id", "name": "name"}, "azure.sql-server": {"id": "id", "name": "name"}, "azure.sqlserver": {"id": "id", "name": "name"}, "azure.sql-server-vulnerability-assessments": {"id": "id", "name": "name"}, "azure.storage": {"id": "id", "name": "name"}, "azure.storage-container": {"id": "id"}, "azure.spring-cloud": {"id": "id", "name": "name"}, "azure.subscription": {"id": "subscriptionId", "name": "displayName"}, "azure.traffic-manager-profile": {"id": "id", "name": "name"}, "azure.vm": {"id": "id", "name": "name"}, "azure.vmss": {"id": "id", "name": "name"}, "azure.vnet": {"id": "id", "name": "name"}, "azure.webapp": {"id": "id", "name": "name"}, "azure.webapp-auth-settings": {"id": "id", "name": "id"}} \ No newline at end of file diff --git a/executor/resources_mapping/resources_map_google.json b/executor/resources_mapping/resources_map_google.json deleted file mode 100644 index f5ecf44b9..000000000 --- a/executor/resources_mapping/resources_map_google.json +++ /dev/null @@ -1 +0,0 @@ -{"gcp.gcp-apikeys": {"id": "name", "name": "name"}, "gcp.app-engine": {"id": "id", "name": "name"}, "gcp.app-engine-certificate": {"id": "id", "name": "displayName"}, "gcp.app-engine-domain": {"id": "id", "name": "name"}, "gcp.app-engine-domain-mapping": {"id": "id", "name": "name"}, "gcp.app-engine-firewall-ingress-rule": {"id": "priority", "name": "priority"}, "gcp.artifactregistry-repository": {"id": "id", "name": "id"}, "gcp.audit-config": {"id": "name", "name": "name"}, "gcp.autoscaler": {"id": "name", "name": "name"}, "gcp.bigtable-instance": {"id": "id", "name": "id"}, "gcp.bigtable-instance-cluster": {"id": "clusters", "name": "clusters"}, "gcp.bigtable-instance-table": {"id": "tables", "name": "tables"}, "gcp.bigtable-instance-cluster-backup": {"id": "backups", "name": "backups"}, "gcp.bq-dataset": {"id": "id", "name": "friendlyName"}, "gcp.bq-dataset-extended": {"id": "id", "name": "friendlyName"}, "gcp.bq-job": {"id": "id", "name": "id"}, "gcp.bq-table": {"id": "id", "name": "friendlyName"}, "gcp.bucket": {"id": "name", "name": "name"}, "gcp.bucket-access-control-list": {"id": "buckets", "name": "buckets"}, "gcp.bucket-iam-policy": {"id": "id", "name": "name"}, "gcp.build": {"id": "id", "name": "id"}, "gcp.cloudbilling-account": {"id": "name", "name": "name"}, "gcp.dataflow-job": {"id": "name", "name": "name"}, "gcp.datafusion-instance": {"id": "name", "name": "name"}, "gcp.disk": {"id": "name", "name": "name"}, "gcp.dm-deployment": {"id": "name", "name": "name"}, "gcp.dns-managed-zone": {"id": "id", "name": "name"}, "gcp.dns-policy": {"id": "id", "name": "name"}, "gcp.dns-resource-records-sets": {"id": "name", "name": "name"}, "gcp.firewall": {"id": "name", "name": "name"}, "gcp.folder": {"id": "name", "name": "name"}, "gcp.function": {"id": "name", "name": "name"}, "gcp.gce-project": {"id": "name", "name": "name"}, "gcp.gke-cluster": {"id": "name", "name": "name"}, "gcp.gke-nodepool": {"id": "name", "name": "name"}, "gcp.project-iam-policy-bindings": {"id": "role", "name": "role"}, "gcp.project-iam-policy-bindings-by-members": {"id": "member", "name": "member"}, "gcp.iam-role": {"id": "name", "name": "name"}, "gcp.image": {"id": "name", "name": "name"}, "gcp.instance": {"id": "name", "name": "name"}, "gcp.instance-group-managers": {"id": "name", "name": "name"}, "gcp.instance-template": {"id": "name", "name": "name"}, "gcp.interconnect": {"id": "name", "name": "name"}, "gcp.interconnect-attachment": {"id": "name", "name": "name"}, "gcp.kms-cryptokey": {"id": "name", "name": "name"}, "gcp.kms-cryptokey-version": {"id": "name", "name": "name"}, "gcp.kms-keyring": {"id": "name", "name": "name"}, "gcp.kms-location": {"id": "name", "name": "name"}, "gcp.kms-keyring-iam-policy-bindings": {"id": "role", "name": "role"}, "gcp.loadbalancer-address": {"id": "name", "name": "name"}, "gcp.loadbalancer-backend-bucket": {"id": "name", "name": "name"}, "gcp.loadbalancer-backend-frontend": {"id": "name", "name": "name"}, "gcp.loadbalancer-backend-frontend-ssl": {"id": "name", "name": "name"}, "gcp.loadbalancer-backend-service": {"id": "name", "name": "name"}, "gcp.loadbalancer-forwarding-rule": {"id": "name", "name": "name"}, "gcp.loadbalancer-global-address": {"id": "name", "name": "name"}, "gcp.loadbalancer-global-forwarding-rule": {"id": "name", "name": "name"}, "gcp.loadbalancer-health-check": {"id": "name", "name": "name"}, "gcp.loadbalancer-http-health-check": {"id": "name", "name": "name"}, "gcp.loadbalancer-https-health-check": {"id": "name", "name": "name"}, "gcp.loadbalancer-ssl-certificate": {"id": "name", "name": "name"}, "gcp.loadbalancer-ssl-policy": {"id": "name", "name": "name"}, "gcp.loadbalancer-target-http-proxy": {"id": "name", "name": "name"}, "gcp.loadbalancer-target-https-proxy": {"id": "name", "name": "name"}, "gcp.loadbalancer-target-https-proxy-ssl-policy": {"id": "name", "name": "name"}, "gcp.loadbalancer-target-instance": {"id": "name", "name": "name"}, "gcp.loadbalancer-target-pool": {"id": "name", "name": "name"}, "gcp.loadbalancer-target-ssl-proxy": {"id": "name", "name": "name"}, "gcp.loadbalancer-target-tcp-proxy": {"id": "name", "name": "name"}, "gcp.loadbalancer-url-map": {"id": "name", "name": "name"}, "gcp.log-exclusion": {"id": "name", "name": "name"}, "gcp.log-project-metric": {"id": "name", "name": "name"}, "gcp.log-project-sink": {"id": "name", "name": "name"}, "gcp.logging-alert-policy": {"id": "name", "name": "name"}, "gcp.logging-metrics": {"id": "metricName", "name": "metricName"}, "gcp.logging-sink": {"id": "name", "name": "name"}, "gcp.logging-sink-bucket": {"id": "name", "name": "name"}, "gcp.liens": {"id": "name", "name": "name"}, "gcp.ml-job": {"id": "jobId", "name": "jobId"}, "gcp.ml-model": {"id": "name", "name": "name"}, "gcp.namespace-service": {"id": "id", "name": "id"}, "gcp.namespace-revision": {"id": "id", "name": "id"}, "gcp.notebook-instance": {"id": "id", "name": "id"}, "gcp.organization": {"id": "name", "name": "displayName"}, "gcp.patch-deployment": {"id": "id", "name": "id"}, "gcp.project": {"id": "projectId", "name": "projectId"}, "gcp.project-role": {"id": "name", "name": "name"}, "gcp.pubsub-snapshot": {"id": "name", "name": "name"}, "gcp.pubsub-subscription": {"id": "name", "name": "name"}, "gcp.pubsub-topic": {"id": "name", "name": "name"}, "gcp.redis-instance": {"id": "id", "name": "id"}, "gcp.route": {"id": "name", "name": "name"}, "gcp.router": {"id": "name", "name": "name"}, "gcp.service": {"id": "name", "name": "name"}, "gcp.service-account": {"id": "name", "name": "email"}, "gcp.service-account-bindings": {"id": "name", "name": "name"}, "gcp.service-account-key": {"id": "name", "name": "name"}, "gcp.service-account-key-user": {"id": "name", "name": "name"}, "gcp.security-policy": {"id": "id", "name": "id"}, "gcp.snapshot": {"id": "name", "name": "name"}, "gcp.sourcerepo": {"id": "name", "name": "name"}, "gcp.spanner-database-instance": {"id": "name", "name": "name"}, "gcp.spanner-instance": {"id": "name", "name": "name"}, "gcp.spanner-instance-backup": {"id": "backups", "name": "backups"}, "gcp.sql-backup-run": {"id": "id", "name": "id"}, "gcp.sql-instance": {"id": "name", "name": "name"}, "gcp.sql-ssl-cert": {"id": "sha1Fingerprint", "name": "commonName"}, "gcp.sql-user": {"id": "name", "name": "name"}, "gcp.subnet": {"id": "name", "name": "name"}, "gcp.vpc": {"id": "name", "name": "name"}, "gcp.web-security-scanner": {"id": "name", "name": "name"}, "gcp.gcp-secret": {"id": "name", "name": "name"}, "gcp.gcp-zones": {"id": "name", "name": "name"}, "gcp.gcp-regions": {"id": "name", "name": "name"}, "gcp.dataproc-clusters": {"id": "name", "name": "name"}, "gcp.gke-cluster-beta-api": {"id": "name", "name": "name"}} \ No newline at end of file diff --git a/executor/services/__init__.py b/executor/services/__init__.py deleted file mode 100644 index c92e8d7f8..000000000 --- a/executor/services/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from services.service_provider import ServiceProvider - -SERVICE_PROVIDER = SP = ServiceProvider() diff --git a/executor/services/batch_service.py b/executor/services/batch_service.py deleted file mode 100644 index e2b02a157..000000000 --- a/executor/services/batch_service.py +++ /dev/null @@ -1,55 +0,0 @@ -from datetime import datetime, timedelta - -import boto3 - -from helpers.log_helper import get_logger -from helpers.time_helper import utc_datetime -from services.environment_service import EnvironmentService - -_LOG = get_logger(__name__) - - -class BatchService: - def __init__(self, environment_service: EnvironmentService): - self._environment = environment_service - self._client = None - - @property - def is_docker(self) -> bool: - return self._environment.is_docker() - - @property - def client(self): - if not self._client: - self._client = boto3.client( - 'batch', self._environment.aws_region()) - return self._client - - def get_time_left(self, job_lifetime_min=None): - job_lifetime_min = job_lifetime_min or \ - self._environment.job_lifetime_min() - _LOG.debug('Retrieving \'startedAt\' timestamp') - job_id = self._environment.batch_job_id() - if self.is_docker: - created_at = utc_datetime().timestamp() * 1e3 - else: - job = self._get_job_by_id(job_id=self._environment.batch_job_id()) - created_at = None - if job: - created_at = job.get('startedAt') - if not created_at: - _LOG.warning(f'Can\'t find job with id {job_id}. Take current ' - f'time as value of \'startedAt\' parameter') - created_at = utc_datetime().timestamp() * 1e3 - threshold = datetime.timestamp(datetime.fromtimestamp( - created_at / 1e3) + timedelta(minutes=job_lifetime_min)) - _LOG.debug(f'Threshold: {threshold}, ' - f'{datetime.fromtimestamp(threshold)}') - return threshold - - def _get_job_by_id(self, job_id): - response = self.client.describe_jobs(jobs=[job_id]) - jobs = response.get('jobs', []) - if jobs: - return jobs[0] - _LOG.warning(f'Can\'t find job with id {job_id}.') diff --git a/executor/services/clients/__init__.py b/executor/services/clients/__init__.py deleted file mode 100644 index abefa7517..000000000 --- a/executor/services/clients/__init__.py +++ /dev/null @@ -1,82 +0,0 @@ -from abc import abstractmethod, ABC -from typing import Optional, Generic, TypeVar, TYPE_CHECKING - -from boto3.session import Session -from botocore.client import BaseClient - -if TYPE_CHECKING: - from typing_extensions import Self - - -class Boto3ClientFactory: - _session = Session() # class variable - - def __init__(self, service: str): - self._service = service - - def build(self, region_name: Optional[str] = None) -> BaseClient: - return self._session.client( - service_name=self._service, - region_name=region_name - ) - - def from_keys(self, aws_access_key_id: str, aws_secret_access_key: str, - aws_session_token: Optional[str] = None, - region_name: Optional[str] = None) -> BaseClient: - return self._session.client( - service_name=self._service, - aws_access_key_id=aws_access_key_id, - aws_secret_access_key=aws_secret_access_key, - aws_session_token=aws_session_token, - region_name=region_name - ) - - -T = TypeVar('T') - - -class Boto3ClientWrapperFactory(Generic[T]): - def __init__(self, client_class: T): - self._wrapper = client_class - - def build(self, region_name: Optional[str] = None) -> T: - instance = self._wrapper.build() - instance.client = Boto3ClientFactory(instance.service_name).build( - region_name) - return instance - - def from_keys(self, aws_access_key_id: str, aws_secret_access_key: str, - aws_session_token: Optional[str] = None, - region_name: Optional[str] = None) -> T: - instance = self._wrapper.build() - instance.client = Boto3ClientFactory(instance.service_name).from_keys( - aws_access_key_id=aws_access_key_id, - aws_secret_access_key=aws_secret_access_key, - aws_session_token=aws_session_token, - region_name=region_name - ) - return instance - - -class Boto3ClientWrapper(ABC): - _client = None - service_name: str = None - - @classmethod - @abstractmethod - def build(cls) -> 'Self': - ... - - @property - def client(self) -> BaseClient: - return self._client - - @client.setter - def client(self, value: BaseClient): - assert isinstance(value, BaseClient) and \ - value.meta.service_model.service_name == self.service_name - self._client = value - - @classmethod - def factory(cls) -> Boto3ClientWrapperFactory['Self']: - return Boto3ClientWrapperFactory(cls) diff --git a/executor/services/clients/abstract_key_management.py b/executor/services/clients/abstract_key_management.py deleted file mode 100644 index bb6fe3182..000000000 --- a/executor/services/clients/abstract_key_management.py +++ /dev/null @@ -1,85 +0,0 @@ -from abc import ABC, abstractmethod -from typing import Union, Optional, TypeVar, Generic, Dict -from re import match -from helpers.log_helper import get_logger - -K = TypeVar('K') - -KEY_TYPE_ATTR = 'key_typ' -KEY_STD_ATTR = 'key_std' -SIG_SCHEME_ATTR = 'sig_scheme' -HASH_TYPE_ATTR = 'hash_type' -HASH_STD_ATTR = 'hash_std' - -ALG_PATTERN = f'(?P<{KEY_TYPE_ATTR}>.+):(?P<{KEY_STD_ATTR}>.+)_' \ - f'(?P<{SIG_SCHEME_ATTR}>.+)_' \ - f'(?P<{HASH_TYPE_ATTR}>.+):(?P<{HASH_STD_ATTR}>.+)' - - -_LOG = get_logger(__name__) - - -class IKey(Generic[K]): - - @classmethod - def import_key(cls, encoded: Union[str, bytes], **kwargs) -> K: - ... - - def export_key(self, format: str, **kwargs) -> Union[str, bytes]: - ... - - def public_key(self) -> K: - ... - - -class AbstractKeyManagementClient(ABC): - - @abstractmethod - def sign(self, key_id, message: Union[str, bytes], algorithm: str, - encoding='utf-8') -> Optional[bytes]: - raise NotImplementedError() - - @abstractmethod - def generate(self, key_type: str, key_std: str, **data) -> IKey: - raise NotImplementedError() - - @abstractmethod - def get_key(self, key_type: str, key_std: str, key_data: dict) -> \ - Optional[IKey]: - raise NotImplementedError() - - @abstractmethod - def get_key_data(self, key_id: str) -> Optional[dict]: - raise NotImplementedError() - - @classmethod - @abstractmethod - def construct(cls, key_type: str, key_std: str, key_value: str, **data): - raise NotImplementedError() - - @classmethod - @abstractmethod - def is_signature_scheme_accessible( - cls, sig_scheme: str, key_type: str, key_std: str, hash_type: str, - hash_std: str - ): - raise NotImplementedError() - - @staticmethod - def dissect_alg(alg: str) -> Optional[Dict[str, str]]: - """ - Returns `alg` value dissected dict-object, with the following keys: - - key_type: str - - key_std: str - - sig_scheme: str - - hash_type: str - - hash_std: str - :return: Optional[Dict[str, str]] - """ - mapped = None - try: - m = match(pattern=ALG_PATTERN, string=alg) - mapped = m.groupdict() - except (ValueError, Exception) as e: - _LOG.warning(f'Alg:\'{alg}\' could not be dissected due to "{e}".') - return mapped diff --git a/executor/services/clients/ap_sheduler.py b/executor/services/clients/ap_sheduler.py deleted file mode 100644 index abcd300d4..000000000 --- a/executor/services/clients/ap_sheduler.py +++ /dev/null @@ -1,80 +0,0 @@ -import os -from apscheduler.jobstores.base import JobLookupError -from apscheduler.jobstores.mongodb import MongoDBJobStore -from apscheduler.schedulers.background import BackgroundScheduler -from helpers.constants import ENV_MONGODB_DATABASE -from helpers.log_helper import get_logger -from models.modular import BaseModel -from models.scheduled_job import ScheduledJob, SCHEDULED_JOBS_TABLE_NAME -from services.clients.scheduler import AbstractJobScheduler - -_LOG = get_logger(__name__) - - -class CustomMongoDBJobStore(MongoDBJobStore): - """ - Custom representation of APScheduler's MongoDBJobStore. It brings no - new logic, just changes the structure of the document which is saved to - MongoDB according to the Custodian's & Modular requirements. - The base class has the following structure: - { - _id: $job_id, - next_run_time: $timestamp, - job_state: Binary($job_obj_pickled) - } - Current structure: - { - _id: ObjectId("uuid"), # native MongoDB's id, - id: $job_id, - next_run_time: $timestamp, - creation_date: $iso_format_utc, - type: "CUSTODIAN:SCHEDULED_JOB", # const - context: { - job_state: Binary($job_obj_pickled), - ... - } - } - Some other attributes are added to the document afterwards. - """ - ... - - -class APJobScheduler(AbstractJobScheduler): - def __init__(self): - self._scheduler = None - - @property - def scheduler(self) -> BackgroundScheduler: - """ - Mock this in order not to init MONGODB_HANDLER in tests - """ - if not self._scheduler: - self._scheduler = BackgroundScheduler() - self._scheduler.configure(jobstores={ - 'default': CustomMongoDBJobStore( - database=os.getenv(ENV_MONGODB_DATABASE), - collection=SCHEDULED_JOBS_TABLE_NAME, - client=BaseModel.mongodb_handler().mongodb.client - ) - }) - return self._scheduler - - def update_job(self, item: ScheduledJob, is_enabled: bool): - _id = item.id - _LOG.info(f'Updating scheduled job with id \'{_id}\'') - item.update_with(is_enabled=is_enabled) - item.save() - try: - if is_enabled: - self.scheduler.resume_job(_id) - else: - self.scheduler.pause_job(_id) - except JobLookupError: - _LOG.warning(f'Job with id \'{_id}\' was not found') - item = None - - _LOG.info( - f'Scheduled job with name \'{_id}\' was successfully updated' - ) - - return item diff --git a/executor/services/clients/dojo_client.py b/executor/services/clients/dojo_client.py deleted file mode 100644 index 52f3afcfc..000000000 --- a/executor/services/clients/dojo_client.py +++ /dev/null @@ -1,142 +0,0 @@ -import io - -import requests - -from helpers.log_helper import get_logger - -# DOJO_CAAS_SCAN_TYPE = 'CaaS Scan' -DOJO_CAAS_SCAN_TYPE = 'Cloud Custodian Scan' - -_LOG = get_logger(__name__) - -ENGAGEMENT_NAME_TEMPLATE = '{target_start} - {target_end} scan' -ENGAGEMENT_START_END_FORMAT = '%Y-%m-%d' -POST_METHOD = 'POST' -DELETE_METHOD = 'DELETE' -GET_METHOD = 'GET' - -RESPONSE_FORBIDDEN_CODE = 403 - - -class DojoClient: - def __init__(self, host: str, api_key: str): - """ - :param host: assuming that host came from modular AccessMeta - :param api_key: - """ - _LOG.info('Init Dojo client') - self.host = host - self.api_key = api_key - self.headers = {'Authorization': "Token " + self.api_key} - self._check_connection() - - def _check_connection(self): - _LOG.info('Checking DefectDojo\'s connection') - self._request( - method=GET_METHOD, - url='/user_profile/', - timeout=4 - ) - _LOG.info('Connected successfully') - - def list_products(self, name=None): - query = {} - if name: - query['name'] = name - return self._request( - method=GET_METHOD, - url='/products/', - params=query - ) - - def get_product_by_name(self, name): - products = self.list_products(name=name) - products = products.get('results', []) - - for product in products: - if product.get('name') == name: - return product - - def list_engagements(self, product_id=None, name=None, target_start=None, - target_end=None): - query = {} - if product_id: - query['product'] = product_id - if name: - query['name'] = name - if target_start: - query['target_start'] = target_start - if target_end: - query['target_end'] = target_end - return self._request( - method=GET_METHOD, - url='/engagements/', - params=query - ) - - def get_engagement(self, name, product_id=None): - if product_id: - engagements = self.list_engagements( - name=name, - product_id=product_id - ) - else: - engagements = self.list_engagements(name=name) - engagements = engagements.get('results', []) - if len(engagements) > 0: - return engagements[0] - - def import_scan(self, product_type_name, product_name, engagement_name, - buffer: io.BytesIO, reimport=False, test_title=None): - data = { - "product_type_name": product_type_name, - "product_name": product_name, - "engagement_name": engagement_name, - "scan_type": DOJO_CAAS_SCAN_TYPE, - "auto_create_context": True, - } - if test_title: - data['test_title'] = test_title - files = { - "file": ("report.json", buffer) - } - url = '/reimport-scan/' if reimport else '/import-scan/' - return self._request( - method=POST_METHOD, - url=url, - data=data, - files=files - ) - - def _request(self, method, url, params=None, data=None, files=None, - timeout=None): - url = self.host + url - _LOG.info(f"Making a request to DefectDojo's url: {url}") - try: - response = requests.request( - method=method, - url=url, - params=params, - data=data, - files=files, - headers=self.headers, - timeout=timeout - ) - response.raise_for_status() - _LOG.info('The request to DefectDojo was successful!') - return response.json() - except ValueError: # JsonDecodeError (either simplejson or json) - _LOG.warning(f'The request is successful but without JSON') - return {} - except requests.HTTPError as e: - _LOG.error(e) - error = f'An error {e.response.status_code} occurred ' \ - f'while making a request to DefectDojo server.' - if e.response.status_code == RESPONSE_FORBIDDEN_CODE: - error = f'{error} Forbidden - invalid token' - raise requests.RequestException(error) - except requests.exceptions.ConnectionError as e: - error = f'Could not connect to the DefectDojo ' \ - f'server {self.host}' - _LOG.error(f'{error}: {e}') - raise requests.RequestException(error) diff --git a/executor/services/clients/eks_client.py b/executor/services/clients/eks_client.py deleted file mode 100644 index a2be7f27d..000000000 --- a/executor/services/clients/eks_client.py +++ /dev/null @@ -1,33 +0,0 @@ -from typing import TypedDict, Optional - -from botocore.exceptions import ClientError - -from services.clients import Boto3ClientWrapper - - -class _CertificateAuthority(TypedDict): - data: str - - -class Cluster(TypedDict): - name: str - arn: str - certificateAuthority: _CertificateAuthority - endpoint: str - # and other params - - -class EKSClient(Boto3ClientWrapper): - service_name = 'eks' - - @classmethod - def build(cls): - return cls() - - def describe_cluster(self, name: str) -> Optional[Cluster]: - try: - return self._client.describe_cluster(name=name)['cluster'] - except ClientError as e: - if e.response['Error']['Code'] == 'ResourceNotFoundException': - return - raise e diff --git a/executor/services/clients/event_bridge.py b/executor/services/clients/event_bridge.py deleted file mode 100644 index 15abcf647..000000000 --- a/executor/services/clients/event_bridge.py +++ /dev/null @@ -1,42 +0,0 @@ -import boto3 -from botocore.exceptions import ClientError - -from services.environment_service import EnvironmentService - - -class EventBridgeClient: - def __init__(self, environment_service: EnvironmentService): - self._environment = environment_service - self._client = None - - @staticmethod - def sifted(request: dict) -> dict: - return {k: v for k, v in request.items() if isinstance( - v, (bool, int)) or v} - - @property - def client(self): - if not self._client: - self._client = boto3.client( - 'events', self._environment.aws_region()) - return self._client - - def enable_rule(self, rule_name) -> bool: - params = dict(Name=rule_name) - try: - self.client.enable_rule(**params) - return True - except ClientError as e: - if e.response['Error']['Code'] == 'ResourceNotFoundException': - return False - raise - - def disable_rule(self, rule_name) -> bool: - params = dict(Name=rule_name) - try: - self.client.disable_rule(**params) - return True - except ClientError as e: - if e.response['Error']['Code'] == 'ResourceNotFoundException': - return False - raise diff --git a/executor/services/clients/iam.py b/executor/services/clients/iam.py deleted file mode 100644 index 543df401b..000000000 --- a/executor/services/clients/iam.py +++ /dev/null @@ -1,21 +0,0 @@ -import re -from typing import Optional - -from services.clients.sts import StsClient - - -class IAMClient: - def __init__(self, sts_client: StsClient): - self._sts_client = sts_client - - def build_role_arn(self, maybe_arn: str, - account_id: Optional[str] = None) -> str: - if self.is_role_arn(maybe_arn): - return maybe_arn - account_id = account_id or self._sts_client.get_account_id() - return f'arn:aws:iam::{account_id}:role/{maybe_arn}' - - @staticmethod - def is_role_arn(arn: str) -> bool: - return bool(re.match(r'^arn:aws:iam::\d{12}:role/[A-Za-z0-9_-]+$', - arn)) diff --git a/executor/services/clients/license_manager.py b/executor/services/clients/license_manager.py deleted file mode 100644 index 9bdb4060a..000000000 --- a/executor/services/clients/license_manager.py +++ /dev/null @@ -1,173 +0,0 @@ -from functools import cached_property -from json import JSONDecodeError -from typing import Union, List, Type, Dict, Optional - -from modular_sdk.services.impl.maestro_credentials_service import AccessMeta -from requests import request, Response -from requests.exceptions import RequestException - -from helpers.constants import JobState -from helpers.constants import POST_METHOD, PATCH_METHOD, \ - RULESETS_ATTR, CUSTOMER_ATTR, AUTHORIZATION_PARAM, TENANT_ATTR -from helpers.log_helper import get_logger -from services.environment_service import EnvironmentService -from services.setting_service import SettingService - -JOB_PATH = '/jobs' - -HOST_KEY = 'host' - -JOB_ID_ATTR = 'job_id' -CREATED_AT_ATTR = 'created_at' -STARTED_AT_ATTR = 'started_at' -STOPPED_AT_ATTR = 'stopped_at' -STATUS_ATTR = 'status' - -SERVICE_TYPE_ATTR = 'service_type' -SERVICE_TYPE_CUSTODIAN = 'CUSTODIAN' - -_LOG = get_logger(__name__) - - -class LicenseManagerClient: - - def __init__(self, environment_service: EnvironmentService, - setting_service: SettingService): - self.environment_service = environment_service - self.setting_service = setting_service - self._access_data = None - self._client_key_data = None - - @cached_property - def access_data(self) -> dict: - return self.setting_service.get_license_manager_access_data() or {} - - @property - def host(self) -> Optional[str]: - return AccessMeta.from_dict(self.access_data).url - - @property - def client_key_data(self): - if not self._client_key_data: - self._client_key_data = \ - self.setting_service.get_custodian_service_private_key() - self._client_key_data = self._client_key_data or {} - return self._client_key_data - - def post_job(self, job_id: str, customer: str, tenant: str, - ruleset_map: Dict[str, List[str]], auth: str): - """ - Delegated to instantiate a licensed Job, bound to a tenant within a - customer utilizing rulesets which are grouped by tenant-license-keys, - allowing to request for a ruleset-content-source collection. - :parameter job_id: str - :parameter customer: str - :parameter tenant: str - :parameter auth: str, authorization token - :parameter ruleset_map: Dict[str, List[str]] - :return: Union[Response, Type[None]] - """ - host, method = self.host, POST_METHOD - if not host: - _LOG.error('CustodianLicenceManager access data has not been' - ' provided.') - return None - - host = host.strip('/') - url = host + JOB_PATH - - payload = { - SERVICE_TYPE_ATTR: SERVICE_TYPE_CUSTODIAN, - JOB_ID_ATTR: job_id, - CUSTOMER_ATTR: customer, - TENANT_ATTR: tenant, - RULESETS_ATTR: ruleset_map - } - - headers = { - AUTHORIZATION_PARAM: auth - } - - return self._send_request( - url=url, method=method, payload=payload, headers=headers - ) - - def patch_job(self, job_id: str, auth: str, created_at: str = None, - started_at: str = None, stopped_at: str = None, - status: JobState = None): - host = self.host - if not any([created_at, started_at, stopped_at, status]): - _LOG.warning('No attributes to update provided. Skipping') - return - if not host: - _LOG.error('CustodianLicenceManager access data has not been' - ' provided.') - return - url = host.strip('/') + JOB_PATH - payload = { - JOB_ID_ATTR: job_id, - CREATED_AT_ATTR: created_at, - STARTED_AT_ATTR: started_at, - STOPPED_AT_ATTR: stopped_at, - STATUS_ATTR: status - } - - headers = { - AUTHORIZATION_PARAM: auth - } - - payload = {k: v for k, v in payload.items() - if isinstance(v, (bool, int)) or v} - return self._send_request( - url=url, method=PATCH_METHOD, payload=payload, headers=headers - ) - - @classmethod - def _send_request(cls, url: str, method: str, payload: dict, - headers: Optional[dict] = None) -> Optional[Response]: - """ - Meant to commence a request to a given url, by deriving a - proper delegated handler. Apart from that, catches any risen - request related exception. - :parameter url: str - :parameter method:str - :parameter payload: dict - :return: Union[Response, Type[None]] - """ - _injectable_payload = cls._request_payload_injector(method, payload) - try: - _input = f'data - {_injectable_payload}' - if headers: - _input += f', headers: {headers}' - - _LOG.debug(f'Going to send \'{method}\' request to \'{url}\'' - f' with the following {_input}.') - - response = request( - url=url, method=method, headers=headers, **_injectable_payload - ) - _LOG.debug(f'Response from {url}: {response}') - return response - except (RequestException, Exception) as e: - _LOG.error(f'Error occurred while executing request. Error: {e}') - return - - @classmethod - def _request_payload_injector(cls, method: str, payload: dict): - _map = cls._define_method_injection_map(payload) - return _map.get(method, payload) if method in _map else None - - @staticmethod - def retrieve_json(response: Response) -> Union[Dict, Type[None]]: - _json = None - try: - _json = response.json() - except JSONDecodeError as je: - _LOG.warning(f'JSON response from \'{response.url}\' not be ' - f'decoded. An exception has occurred: {je}') - return _json - - @staticmethod - def _define_method_injection_map(payload): - return {POST_METHOD: dict(json=payload), - PATCH_METHOD: dict(json=payload)} diff --git a/executor/services/clients/modular.py b/executor/services/clients/modular.py deleted file mode 100644 index 7f30261a0..000000000 --- a/executor/services/clients/modular.py +++ /dev/null @@ -1,3 +0,0 @@ -from modular_sdk.modular import Modular - -ModularClient = Modular diff --git a/executor/services/clients/s3.py b/executor/services/clients/s3.py deleted file mode 100644 index 2f56bad50..000000000 --- a/executor/services/clients/s3.py +++ /dev/null @@ -1,265 +0,0 @@ -import io -import json -import os -from concurrent.futures import ThreadPoolExecutor, as_completed -from gzip import GzipFile -from typing import Union, Iterable, Tuple - -import boto3 -from botocore.config import Config -from botocore.exceptions import ClientError - -from helpers.constants import ENV_MINIO_HOST, ENV_MINIO_PORT, \ - ENV_MINIO_ACCESS_KEY, ENV_MINIO_SECRET_ACCESS_KEY -from helpers.log_helper import get_logger -from helpers.profiling import xray_recorder as _XRAY -from services.environment_service import EnvironmentService - -GZIP_EXTENSION = '.gz' -UTF_8_ENCODING = 'utf-8' - -_LOG = get_logger(__name__) - - -class S3Client: - def __init__(self, environment_service: EnvironmentService): - self._environment = environment_service - self._client = None - self._resource = None - - @property - def is_docker(self) -> bool: - return self._environment.is_docker() - - def build_config(self) -> Config: - config = Config(retries={ - 'max_attempts': 10, - 'mode': 'standard' - }) - if self.is_docker: - config = config.merge(Config(s3={ - 'signature_version': 's3v4', - 'addressing_style': 'path' - })) - return config - - def _init_clients(self): - config = self.build_config() - if self.is_docker: - host, port = os.getenv(ENV_MINIO_HOST), os.getenv(ENV_MINIO_PORT) - access_key = os.getenv(ENV_MINIO_ACCESS_KEY) - secret_access_key = os.getenv(ENV_MINIO_SECRET_ACCESS_KEY) - assert (host and port and access_key and secret_access_key), \ - f"\'{ENV_MINIO_HOST}\', \'{ENV_MINIO_PORT}\', " \ - f"\'{ENV_MINIO_ACCESS_KEY}\', " \ - f"\'{ENV_MINIO_SECRET_ACCESS_KEY}\' envs must be specified " \ - f"for on-prem" - url = f'http://{host}:{port}' - session = boto3.Session( - aws_access_key_id=access_key, - aws_secret_access_key=secret_access_key - ) - self._client = session.client('s3', endpoint_url=url, - config=config) - self._resource = session.resource('s3', endpoint_url=url, - config=config) - _LOG.info('Minio connection was successfully initialized') - else: # saas - self._client = boto3.client( - 's3', self._environment.aws_region(), config=config) - self._resource = boto3.resource( - 's3', self._environment.aws_region(), config=config) - _LOG.info('S3 connection was successfully initialized') - - @property - def client(self): - if not self._client: - self._init_clients() - return self._client - - @property - def resource(self): - if not self._resource: - self._init_clients() - return self._resource - - @staticmethod - def _gz_key(key: str) -> str: - if not key.endswith(GZIP_EXTENSION): - key = key.strip('.') + GZIP_EXTENSION - return key - - def file_exists(self, bucket_name, key): - """Checks if object with the given key exists in bucket, if not - compressed version exists, compresses and rewrites""" - if self._file_exists(bucket_name, self._gz_key(key)): - _LOG.info(f'Gzipped version of the file \'{key}\' exists') - return True - elif self._file_exists(bucket_name, key): - _LOG.warning(f'Not gzipped version of file \'{key}\' exists. ' - f'Compressing') - self._gzip_object(bucket_name, key) - return True - else: - return False - - def _file_exists(self, bucket_name, key): - response = self.client.list_objects_v2( - Bucket=bucket_name, - Prefix=key, - MaxKeys=1 - ) - for obj in response.get('Contents', []): - if obj['Key'] == key: - return True - return False - - def _gzip_object(self, bucket_name: str, key: str) -> io.BytesIO: - """Replaces a file with gzipped version. - And incidentally returns file's content""" - response = self.client.get_object( - Bucket=bucket_name, - Key=key - ) - buf = io.BytesIO(response.get('Body').read()) - _LOG.info(f'Putting the compressed version of file \'{key}\'') - self.put_object(bucket_name, key, buf.read()) - _LOG.info(f'Removing the old not compressed version of the ' - f'file \'{key}\'') - self.resource.Object(bucket_name, key).delete() - buf.seek(0) - return buf - - @_XRAY.capture('Put object to S3') - def put_object(self, bucket_name: str, object_name: str, - body: Union[str, bytes]): - _XRAY.put_annotation('bucket_name', bucket_name) - object_name = self._gz_key(object_name) - _XRAY.put_metadata('key', object_name) - s3_object = self.resource.Object(bucket_name, object_name) - buf = io.BytesIO() - with GzipFile(fileobj=buf, mode='wb') as f: - f.write(body.encode() if not isinstance(body, bytes) else body) - buf.seek(0) - try: - return s3_object.put(Body=buf) - except ClientError as e: - _LOG.error(f'An error occurred trying to put ' - f'object {object_name} to s3') - raise e - - def put_objects_batch(self, bucket_name: str, - key_body: Iterable[Tuple[str, Union[str, bytes]]]): - with ThreadPoolExecutor() as executor: - futures = { - executor.submit(self.put_object, bucket_name, *pair): pair[0] - for pair in key_body - } - for future in as_completed(futures): - key = futures[future] - try: - future.result() - except (ClientError, Exception) as e: - _LOG.warning(f'Cloud not upload file \'{key}\': {e}') - - def is_bucket_exists(self, bucket_name): - """ - Check if specified bucket exists. - :param bucket_name: name of the bucket to check; - :return: True is exists, otherwise - False - """ - existing_buckets = self._list_buckets() - return bucket_name in existing_buckets - - def _list_buckets(self): - response = self.client.list_buckets() - return [bucket['Name'] for bucket in response.get("Buckets")] - - def get_json_file_content(self, bucket_name: str, - full_file_name: str) -> dict: - """ - Returns content of the object. - :param bucket_name: name of the bucket. - :param full_file_name: name of the file including its folders. - Example: /folder1/folder2/file_name.json - :return: content of the file loaded to json - """ - return json.loads(self.get_file_content( - bucket_name, full_file_name, decode=True)) - - def get_file_content(self, bucket_name: str, full_file_name: str, - decode: bool = False) -> Union[str, bytes]: - """ - Returns content of the object. - :param bucket_name: name of the bucket. - :param full_file_name: name of the file including its folders. - Example: /folder1/folder2/file_name.json - :param decode: flag - :return: content of the file - """ - response_stream = self.get_decompressed_stream(bucket_name, - full_file_name) - if not response_stream: - _LOG.warning( - f'No gzip file found for \'{self._gz_key(full_file_name)}\'. ' - f'Trying to reach not the gzipped version') - response_stream = self._gzip_object(bucket_name, full_file_name) - if decode: - return response_stream.read().decode(UTF_8_ENCODING) - return response_stream.read() - - @_XRAY.capture('Get object from S3') - def get_file_stream(self, bucket_name: str, - full_file_name: str) -> Union[io.IOBase, None]: - """Returns boto3 stream with file content. If the file is not found, - returns None. The given file name is not converted to gz. - Use this method as a raw version based on which you build your - own logic""" - _XRAY.put_annotation('bucket_name', bucket_name) - _XRAY.put_metadata('key', full_file_name) - try: - response = self.client.get_object( - Bucket=bucket_name, - Key=full_file_name - ) - return response.get('Body') - except ClientError as e: - if e.response['Error']['Code'] == 'NoSuchKey': - return None - raise e - - def list_objects(self, bucket_name, prefix=None): - result_keys = [] - params = dict(Bucket=bucket_name) - if prefix: - params['Prefix'] = prefix - response = self.client.list_objects_v2(**params) - if not response.get('Contents'): - return None - result_keys.extend(item for item in response['Contents']) - while response['IsTruncated'] is True: - token = response['NextContinuationToken'] - params['ContinuationToken'] = token - response = self.client.list_objects_v2(**params) - result_keys.extend(item for item in response['Contents']) - return result_keys - - def delete_file(self, bucket_name: str, file_key: str): - # TODO https://github.com/boto/boto3/issues/759, when the bug is - # fixed, use response statusCode instead of client.file_exists - gzip_file_key = self._gz_key(file_key) - if self._file_exists(bucket_name, gzip_file_key): - self.resource.Object(bucket_name, gzip_file_key).delete() - else: - _LOG.warning(f'File {gzip_file_key} was not found during ' - f'removing. Maybe it has not been compressed yet. ' - f'Trying to remove the not compressed version') - self.resource.Object(bucket_name, file_key).delete() - - def get_decompressed_stream(self, bucket_name: str, - full_file_name: str) -> Union[GzipFile, None]: - stream = self.get_file_stream(bucket_name, - self._gz_key(full_file_name)) - if not stream: - return - return GzipFile(fileobj=stream) diff --git a/executor/services/clients/scheduler.py b/executor/services/clients/scheduler.py deleted file mode 100644 index 11be95159..000000000 --- a/executor/services/clients/scheduler.py +++ /dev/null @@ -1,79 +0,0 @@ -from abc import ABC, abstractmethod - -from helpers.log_helper import get_logger -from models.scheduled_job import ScheduledJob -from services.clients.event_bridge import EventBridgeClient - -_LOG = get_logger(__name__) - -TARGET_ID = 'custodian-batch-job-target' - -RATE_EXPRESSION_UNITS = { - 'minute', 'minutes', 'hour', 'hours', 'day', 'days', 'week', 'weeks', - 'second', 'seconds', -} - - -class AbstractJobScheduler(ABC): - - @abstractmethod - def update_job(self, item: ScheduledJob, is_enabled: bool): - """ - Updates the data of registered job - """ - - -class APJobScheduler(AbstractJobScheduler): - def __init__(self): - self._scheduler = None - - @property - def scheduler(self): - """Not needed here""" - raise NotImplementedError() - - def update_job(self, item: ScheduledJob, is_enabled: bool): - _id = item.id - _LOG.info(f'Updating scheduled job with id \'{_id}\'') - item.update_with(is_enabled=is_enabled) - # hack because job's target function is situated not in docker but - # in Custodian itself. We cannot pause it using "scheduler.pause_job" - if not is_enabled: - item._additional_data['next_run_time'] = None - else: - _LOG.warning('You are simply not supposed to use this ' - 'functional in docker.') - # item._additional_data['next_run_time'] = time() + ... - item.save() - _LOG.info( - f'Scheduled job with name \'{_id}\' was successfully updated' - ) - - return item - - -class EventBridgeJobScheduler(AbstractJobScheduler): - - def __init__(self, client: EventBridgeClient): - self._client = client - - def update_job(self, item: ScheduledJob, is_enabled: bool): - _id = item.id - enabling_rule_map = { - True: self._client.enable_rule, - False: self._client.disable_rule - } - _LOG.info(f'Updating scheduled job with id \'{_id}\'') - params = dict(rule_name=_id) - if enabling_rule_map.get(is_enabled)(**params): - _LOG.info(f'Rule`s status was changed to \'{is_enabled}\'') - else: - return None - - item.update_with(is_enabled=is_enabled) - item.save() - _LOG.debug('Scheduled job`s data was saved to Dynamodb') - _LOG.info( - f'Scheduled job with name \'{_id}\' was successfully updated' - ) - return item diff --git a/executor/services/clients/ssm.py b/executor/services/clients/ssm.py deleted file mode 100644 index 49cae74ae..000000000 --- a/executor/services/clients/ssm.py +++ /dev/null @@ -1,178 +0,0 @@ -import json -import os -from abc import ABC, abstractmethod -from typing import Optional, List, Dict -from typing import Union - -import boto3 -from botocore.client import ClientError - -from helpers.constants import ENV_VAULT_HOST, ENV_VAULT_PORT, \ - ENV_VAULT_TOKEN, ENV_SERVICE_MODE, DOCKER_SERVICE_MODE -from helpers.log_helper import get_logger -from services.environment_service import EnvironmentService - -_LOG = get_logger(__name__) - -SecretValue = Union[Dict, List, str] - - -class AbstractSSMClient(ABC): - def __init__(self, environment_service: EnvironmentService): - self._environment_service = environment_service - - @abstractmethod - def get_secret_value(self, secret_name: str) -> Optional[SecretValue]: - ... - - @abstractmethod - def create_secret(self, secret_name: str, secret_value: SecretValue, - secret_type='SecureString') -> bool: - ... - - @abstractmethod - def delete_parameter(self, secret_name: str) -> bool: - ... - - @abstractmethod - def get_secret_values(self, secret_names: List[str] - ) -> Optional[Dict[str, SecretValue]]: - ... - - @abstractmethod - def enable_secrets_engine(self, mount_point=None): - ... - - -class VaultSSMClient(AbstractSSMClient): - mount_point = 'kv' - key = 'data' - - def __init__(self, environment_service: EnvironmentService): - super().__init__(environment_service) - self._client = None # hvac.Client - - def _init_client(self): - assert os.getenv(ENV_SERVICE_MODE) == DOCKER_SERVICE_MODE, \ - "You can init vault handler only if SERVICE_MODE=docker" - import hvac - vault_token = os.getenv(ENV_VAULT_TOKEN) - vault_host = os.getenv(ENV_VAULT_HOST) - vault_port = os.getenv(ENV_VAULT_PORT) - _LOG.info('Initializing hvac client') - self._client = hvac.Client( - url=f'http://{vault_host}:{vault_port}', - token=vault_token - ) - _LOG.info('Hvac client was initialized') - - @property - def client(self): - if not self._client: - self._init_client() - return self._client - - def get_secret_value(self, secret_name: str) -> Optional[SecretValue]: - try: - response = self.client.secrets.kv.v2.read_secret_version( - path=secret_name, mount_point=self.mount_point) or {} - except Exception: # hvac.InvalidPath - return - return response.get('data', {}).get('data', {}).get(self.key) - - def create_secret(self, secret_name: str, secret_value: SecretValue, - secret_type='SecureString') -> bool: - return self.client.secrets.kv.v2.create_or_update_secret( - path=secret_name, - secret={self.key: secret_value}, - mount_point=self.mount_point - ) - - def delete_parameter(self, secret_name: str) -> bool: - return bool(self.client.secrets.kv.v2.delete_metadata_and_all_versions( - path=secret_name, mount_point=self.mount_point)) - - def get_secret_values(self, secret_names: List[str] - ) -> Optional[Dict[str, SecretValue]]: - return {name: self.get_secret_value(name) for name in secret_names} - - def enable_secrets_engine(self, mount_point=None): - try: - self.client.sys.enable_secrets_engine( - backend_type='kv', path=mount_point, options={'version': 2}) - return True - except Exception: # hvac.exceptions.InvalidRequest - return False # already exists - - -class SSMClient(AbstractSSMClient): - def __init__(self, environment_service: EnvironmentService): - super().__init__(environment_service) - self._client = None - - @property - def client(self): - if not self._client: - self._client = boto3.client( - 'ssm', self._environment_service.aws_region()) - return self._client - - def get_secret_value(self, secret_name): - try: - response = self.client.get_parameter( - Name=secret_name, - WithDecryption=True - ) - value_str = response['Parameter']['Value'] - try: - return json.loads(value_str) - except json.decoder.JSONDecodeError: - return value_str - except ClientError as e: - error_code = e.response['Error']['Code'] - _LOG.error(f'Can\'t get secret for name \'{secret_name}\', ' - f'error code: \'{error_code}\'') - - def get_secret_values(self, secret_names: list): - try: - response = self.client.get_parameters( - Names=secret_names, - WithDecryption=True) - parameters = {item.get('Name'): item.get('Value') for item in - response.get('Parameters')} - return parameters - except ClientError as e: - error_code = e.response['Error']['Code'] - _LOG.error(f'Can\'t get secret for names \'{secret_names}\', ' - f'error code: \'{error_code}\'') - - def create_secret(self, secret_name: str, - secret_value: Union[str, list, dict], - secret_type='SecureString') -> bool: - try: - if isinstance(secret_value, (list, dict)): - secret_value = json.dumps(secret_value) - self.client.put_parameter( - Name=secret_name, - Value=secret_value, - Overwrite=True, - Type=secret_type) - return True - except ClientError as e: - error_code = e.response['Error']['Code'] - _LOG.error(f'Can\'t put secret for name \'{secret_name}\', ' - f'error code: \'{error_code}\'') - return False - - def delete_parameter(self, secret_name: str) -> bool: - try: - self.client.delete_parameter(Name=secret_name) - return True - except ClientError as e: - error_code = e.response['Error']['Code'] - _LOG.error(f'Can\'t delete secret name \'{secret_name}\', ' - f'error code: \'{error_code}\'') - return False - - def enable_secrets_engine(self, mount_point=None): - """No need to implement""" diff --git a/executor/services/clients/standalone_key_management.py b/executor/services/clients/standalone_key_management.py deleted file mode 100644 index 15cad1f67..000000000 --- a/executor/services/clients/standalone_key_management.py +++ /dev/null @@ -1,428 +0,0 @@ -from services.clients.abstract_key_management import \ - AbstractKeyManagementClient, IKey, \ - KEY_TYPE_ATTR, KEY_STD_ATTR, HASH_TYPE_ATTR, HASH_STD_ATTR, SIG_SCHEME_ATTR - -from typing import Union, Callable, Optional, Dict - -from services.clients.ssm import SSMClient - -from Crypto import Hash -from Crypto.PublicKey import ECC -from Crypto.Signature import DSS - -from helpers.log_helper import get_logger - -ECC_KEY_ATTR = 'ECC' -SHA_HASH_ATTR = 'SHA' -DSS_SIGN_ATTR = 'DSS' - -VALUE_ATTR = 'value' - -# SHA attributes -TFS_SHA_BIT_MODE = 256 -BIT_MODE_ATTR = 'bit_mode' - -ATTR_DELIMITER = '_' - -_LOG = get_logger(__name__) - -# DSS attributes -DSS_MODE_ATTR = 'mode' -DSS_FIPS_MODE = 'fips-186-3' -DSS_RFC6979_MODE = 'deterministic-rfc6979' - -KEY_ATTR = 'key' -HASH_ATTR = 'hash' - -# Key standard attr(s): -# .Ecc: -ECC_NIST_CURVES = ('p521', 'p384', 'p256', 'p224') -ECDSA_NIST_CURVES = ('p521', 'p384', 'p256', 'p224') - -# Hash standard attr(s): -# .Sha: -SHA_BIT_MODES = ('256', '512') -SHA2_MODES = ('256', '512') - - -class StandaloneKeyManagementClient(AbstractKeyManagementClient): - """ - Provides necessary cryptographic behaviour for the following actions: - - signature verification - - signature production - - key construction - - key generation - - key persistence - adhering to the self-declared algorithm(s), which is bound to the next - format: - `$key-type:$key-standard-label`_`$scheme`_`$hash-type:$hash-standard-label` - Note: - Such extended formatting approach would allow to provide stateless, - verification if required. - """ - def __init__(self, ssm_client: SSMClient): - self._ssm_client = ssm_client - - def sign(self, key_id: str, message: Union[str, bytes], algorithm: str, - encoding='utf-8') -> Optional[bytes]: - """ - Mandates signature production computed using a private-key, retrieved - from a manager store, and an algorithm string, segments of which, - split by `_`, explicitly state: - - key-type standard`:`standard-data-label - - signature-scheme standard - - hashing mode`:`standard-data-label - - Note: standard-data-label is meant to provide stateless - configuration of standards, denoting labels, which are supported - i.e. key data - ECC:p521. - - :parameter key_id: str - :parameter message: Union[str, bytes] - :parameter algorithm: str - :parameter encoding: str - :return: Union[bytes, Type[None]] - """ - is_bytes = isinstance(message, bytes) - message = message if is_bytes else bytes(message, encoding) - - _LOG.debug(f'Going to split \'{algorithm}\' algorithm into standards.') - alg: Optional[Dict[str, str]] = self.dissect_alg(alg=algorithm) - if not algorithm: - return - - # Retrieve type and standard data of a key, hash and signature scheme. - key_type, key_std, hash_type, hash_std, sig_scheme = map( - alg.get, ( - KEY_TYPE_ATTR, KEY_STD_ATTR, HASH_TYPE_ATTR, HASH_STD_ATTR, - SIG_SCHEME_ATTR - ) - ) - - _LOG.debug(f'Checking \'{algorithm}\' signature protocol support.') - if key_type not in self._key_construction_map(): - _LOG.warning(f'\'{key_type}\' construction is not supported') - return None - - if not self.is_signature_scheme_accessible( - sig_scheme=sig_scheme, key_type=key_type, key_std=key_std, - hash_type=hash_type, hash_std=hash_std - ): - _LOG.warning( - f'\'{algorithm}\' signature-protocol is not supported.' - ) - return None - - _LOG.debug(f'Going to retrieve raw \'{key_id}\' key data.') - key_data: dict = self.get_key_data(key_id=key_id) - if not key_data: - return None - - key = self.get_key( - key_type=key_type, key_std=key_std, key_data=key_data - ) - if not key: - return None - - hash_obj = self._get_hash_client( - message=message, hash_type=hash_type, hash_std=hash_std, - **(key_data.get(hash_type) or {}) - ) - if not hash_obj: - return None - - signer = self._get_signature_client( - key=key, sig_scheme=sig_scheme, **(key_data.get(sig_scheme) or {}) - ) - if not signer: - return None - - return signer.sign(hash_obj) - - def generate(self, key_type: str, key_std: str, **data): - """ - Produces a random key, based on a given key-type and respective - standard-label. - :param key_type: str - :param key_std: str - :return: Optional[IKey] - """ - reference = self._key_generation_map() - generator = reference.get(key_type, {}).get(key_std) - if not generator: - _LOG.warning(f'\'{key_type}\':{key_std} generator is not' - f' supported') - return - try: - return generator(key_std, **data) - except (TypeError, Exception) as e: - _LOG.warning(f'\'{key_type}\' generator could not be invoked, ' - f'due to: "{e}".') - return - - def get_key(self, key_type: str, key_std: str, key_data: dict): - """ - Mediates cryptographic key instance derivation, based on a key_type - and a respective key_data. - :parameter key_type: str - :parameter key_std: str, type-respective standard data label - :parameter key_data: dict, any store-persisted key data - :return: Optional[IKey] - """ - key_value = key_data.pop(VALUE_ATTR) - - try: - key = self.construct( - key_type=key_type, key_std=key_std, key_value=key_value, - **key_data - ) - except (ValueError, Exception) as e: - _LOG.error( - f'Could not instantiate {key_type}:{key_std} due to "{e}".') - return None - - return key - - def get_key_data(self, key_id: str) -> Optional[dict]: - """ - Mandates raw cryptographic-key retrieval referencing management store. - :parameter key_id: str - :return: Union[dict, Type[None]] - """ - item = self._ssm_client.get_secret_value(secret_name=key_id) - item = _load_json(item) if isinstance(item, str) else item - is_dict = isinstance(item, dict) - predicate = not is_dict or VALUE_ATTR not in item - if predicate: - header = f'\'{key_id}\' key: {item}' - _LOG.error(f'{header} ' + 'is not a dictionary' if not is_dict - else 'does not contain a \'value\' key.') - return None - return item - - @classmethod - def construct( - cls, key_type: str, key_std: str, key_value: str, **data - ): - """ - Head cryptographic key construction mediator, which derives a - key type - raw value type constructor, given one has been found. - :parameter key_type: str, cryptographic key-type - :parameter key_std: str, cryptographic key-type standard label - :parameter key_value: str, raw key value - :parameter data: dict, any store-persisted data, related to the key. - :return: Union[object, Type[None]] - """ - mediator_map = cls._key_construction_map() - _map: dict = mediator_map.get(key_type, {}) - if not _map: - _LOG.warning(f'No {key_type} key constructor could be found.') - return None - - mediator: Callable = _map.get(key_std) - if not mediator: - _LOG.warning(f'{key_type} key does not support {key_std} ' - 'construction.') - return None - - try: - built = mediator(value=key_value, key_std=key_std, **data) - except (ValueError, Exception) as e: - _LOG.warning(f'Key of {key_type}:{key_std} standard ' - f'could not be constructed due to: "{e}".') - built = None - return built - - @classmethod - def is_signature_scheme_accessible( - cls, sig_scheme: str, key_type: str, key_std: str, hash_type: str, - hash_std: str - ): - ref = cls._signature_scheme_reference().get(sig_scheme, {}) - return hash_std in ref.get(key_type, {}).get(key_std, {}).get( - hash_type, [] - ) - - @classmethod - def _get_hash_client( - cls, message: bytes, hash_type: str, hash_std: str, **data - ): - """ - Mandates message-hash resolution based on provided type and - optional standard data. - :parameter message: bytes - :parameter hash_type: str - :parameter hash_std: str, cryptographic hash-type wise standard label - :parameter data: dict, any store-persisted data, related to the hash. - :return: Type[object, None] - """ - resolver = cls._hash_construction_map().get(hash_type, []).get( - hash_std - ) - return resolver(message, hash_std, **data) if resolver else None - - @classmethod - def _get_signature_client(cls, key, sig_scheme: str, **data): - """ - Resolves key signature actor based on provided type and optional - standard data. - :parameter key: object - :parameter sig_scheme: str, cryptographic signature scheme label - :parameter data: dict, any persisted data, related to the hash. - :return: Type[object, None] - """ - resolver = cls._signature_construction_map().get(sig_scheme) - return resolver(key, **data) if resolver else None - - @classmethod - def _key_construction_map(cls): - """ - Declares a construction key-map, which follows the structure: - { - $key_type: { - $key_std: Callable[value: str, key_std: std, **kwargs] - } - } - :return: Dict[str, Dict[str, Callable]] - """ - reference = {ECC_KEY_ATTR: {}} - for curve in ECC_NIST_CURVES: - reference[ECC_KEY_ATTR][curve] = cls._import_ecc_key - return reference - - @classmethod - def _key_generation_map(cls) -> Dict[str, Dict[str, Callable]]: - reference = {ECC_KEY_ATTR: {}} - for curve in ECC_NIST_CURVES: - reference[ECC_KEY_ATTR][curve] = cls._generate_ecc_key - return reference - - @classmethod - def _hash_construction_map(cls): - reference = {SHA_HASH_ATTR: {}} - for bit_mode in SHA_BIT_MODES: - reference[SHA_HASH_ATTR][bit_mode] = cls._get_sha - return reference - - @classmethod - def _signature_construction_map(cls): - return { - DSS_SIGN_ATTR: cls._get_dss - } - - @staticmethod - def _signature_scheme_reference(): - """ - Returns a reference map accessible signature schemes, based on the - following structure: - { - $signature_scheme: { - $key_type: { - $key_std: { - $hash_type: Iterable[$hash_standard] - } - } - } - } - :return: Dict[str, Dict[str, Dict[str, Dict[str, Iterable[str]]]]] - """ - # Declares DSS scheme accessible protocols. - dss = dict() - dss[ECC_KEY_ATTR] = { - curve: { - SHA_HASH_ATTR: SHA2_MODES - } - for curve in ECDSA_NIST_CURVES - } - return { - DSS_SIGN_ATTR: dss - } - - @staticmethod - def _import_ecc_key(value: str, key_std: str, **kwargs): - """ - Delegated to import an Elliptic Curve key. - :parameter value: str - :parameter key_std: standard-label of an ECC key - :parameter kwargs: dict - :return: EccKey - """ - # Declares optional parameters - parameters = ['passphrase'] - payload = _filter_items(source=kwargs, include_list=parameters) - payload['curve_name'] = key_std - payload['encoded'] = value - return ECC.import_key(**payload) - - @staticmethod - def _generate_ecc_key(key_std: str, **kwargs): - """ - Delegated to construct an Elliptic Curve key, based on a - given standard-curve. - :param key_std: str, standard-label, which denotes curve on default - :param kwargs: dict, any additionally allowed data to inject - :return: EccKey - """ - parameters = ['curve', 'rand_func'] - payload = _filter_items(source=kwargs, include_list=parameters) - payload['curve'] = key_std or payload.get('curve') - return ECC.generate(**payload) - - @staticmethod - def _get_sha(message: bytes, hash_std: str, **data): - """ - Delegated to instantiate a hasher bound to the SHA standard, - deriving a bit-mode-parameter, which by default is set to 256-bits. - :parameter message: bytes - :param hash_std: cryptographic type-wise standard label - - denotes bit-mode - :parameter data: dict, any store-persisted data, related to the hash. - :return: Union[object, Type[None]] - """ - bit_mode = hash_std or data.get(BIT_MODE_ATTR) - module = Hash.__dict__.get(SHA_HASH_ATTR + bit_mode) - if not module: - _LOG.warning(f'SHA does not support {bit_mode} mode.') - return None - return module.new(message) - - @classmethod - def _get_dss(cls, key, **data): - """ - Delegated to instantiate a signer bound to the Digital Signature - standard, deriving optional bit-mode-attribute, which by default is - set to deterministic-rfc6979. - :parameter key: Union[DsaKey, EccKey] - :parameter data: dict - :return: Union[object, Type[None]] - """ - parameters = dict(key=key) - - default = DSS_RFC6979_MODE - raw_mode = data.get(DSS_MODE_ATTR, default) - try: - parameters['mode'] = str(raw_mode) - except (ValueError, Exception) as e: - _LOG.warning(f'Improper DSS mode value: \'{raw_mode}\'.') - return None - - try: - signer = DSS.new(**parameters) - except (TypeError, ValueError, Exception) as e: - _LOG.warning(f'Could not instantiate a DSS signer: {e}') - signer = None - - return signer - - -def _filter_items(source: dict, include_list: list) -> dict: - return {key: value for key, value in source.items() if key in include_list} - - -def _load_json(data: str): - from json import loads, JSONDecodeError - try: - loaded = loads(data) - except (ValueError, JSONDecodeError): - loaded = data - return loaded diff --git a/executor/services/clients/sts.py b/executor/services/clients/sts.py deleted file mode 100644 index b71cc04d8..000000000 --- a/executor/services/clients/sts.py +++ /dev/null @@ -1,91 +0,0 @@ -import base64 -from datetime import datetime -from time import time -from typing import TypedDict - -from botocore.client import BaseClient - -from helpers.log_helper import get_logger -from services.clients import Boto3ClientWrapper - -_LOG = get_logger(__name__) -TOKEN_PREFIX = 'k8s-aws-v1.' -CLUSTER_NAME_HEADER = 'x-k8s-aws-id' - - -class _AssumeRoleCredentials(TypedDict): - AccessKeyId: str - SecretAccessKey: str - SessionToken: str - Expiration: datetime - - -class AsuumeRoleResult(TypedDict): - Credentials: _AssumeRoleCredentials - AssumedRoleUser: dict - PackedPolicySize: int - SourceIdentity: str - - -class StsClient(Boto3ClientWrapper): - service_name = 'sts' - - @classmethod - def build(cls): - return cls() - - def assume_role(self, role_arn: str, duration: int = 3600, - role_session_name: str = None) -> AsuumeRoleResult: - role_session_name = role_session_name or f'Custodian-scan-{time()}' - params = { - 'RoleArn': role_arn, - 'RoleSessionName': role_session_name, - 'DurationSeconds': duration - } - return self.client.assume_role(**params) - - -class TokenGenerator: - """ - From python AWS CLI - """ - - def __init__(self, sts_client: BaseClient): - self._sts_client = sts_client - self._register_cluster_name_handlers(self._sts_client) - - def get_token(self, cluster_name: str): - """ - Generate a presigned url token to pass to kubectl - """ - url = self._get_presigned_url(cluster_name) - token = TOKEN_PREFIX + base64.urlsafe_b64encode( - url.encode('utf-8')).decode('utf-8').rstrip('=') - return token - - def _get_presigned_url(self, cluster_name: str): - return self._sts_client.generate_presigned_url( - 'get_caller_identity', - Params={'ClusterName': cluster_name}, - ExpiresIn=60, - HttpMethod='GET', - ) - - def _register_cluster_name_handlers(self, sts_client): - sts_client.meta.events.register( - 'provide-client-params.sts.GetCallerIdentity', - self._retrieve_cluster_name - ) - sts_client.meta.events.register( - 'before-sign.sts.GetCallerIdentity', - self._inject_cluster_name_header - ) - - def _retrieve_cluster_name(self, params, context, **kwargs): - if 'ClusterName' in params: - context['eks_cluster'] = params.pop('ClusterName') - - def _inject_cluster_name_header(self, request, **kwargs): - if 'eks_cluster' in request.context: - request.headers[ - CLUSTER_NAME_HEADER] = request.context['eks_cluster'] diff --git a/executor/services/credentials_service.py b/executor/services/credentials_service.py deleted file mode 100644 index 20d1983e9..000000000 --- a/executor/services/credentials_service.py +++ /dev/null @@ -1,206 +0,0 @@ -import json -import re -import tempfile -from datetime import datetime, timedelta, timezone -from functools import cached_property -from typing import Tuple, Dict, Callable, Optional, Union - -from botocore.exceptions import ClientError -from modular_sdk.models.tenant import Tenant - -from helpers.constants import AWS, GOOGLE, AZURE, \ - ENV_AWS_ACCESS_KEY_ID, ENV_AWS_SECRET_ACCESS_KEY, \ - ENV_AWS_SESSION_TOKEN, ENV_AWS_DEFAULT_REGION, \ - ENV_GOOGLE_APPLICATION_CREDENTIALS, ENV_CLOUDSDK_CORE_PROJECT -from helpers.log_helper import get_logger -from helpers.time_helper import utc_datetime -from models.credentials_manager import CredentialsManager -from services.clients.sts import StsClient -from services.environment_service import EnvironmentService -from services.ssm_service import SSMService - -_LOG = get_logger(__name__) - -VALID_CREDENTIALS_THRESHOLD_MINUTES = 15 - - -class CredentialsService: - def __init__(self, ssm_service: SSMService, - environment_service: EnvironmentService, - sts_client: StsClient): - self.ssm_service = ssm_service - self.environment_service = environment_service - self.sts_client = sts_client - - @cached_property - def credentials_cm_getter(self) -> Dict[str, Callable]: - """ - Credentials manager getter. For historical reasons we keep lowercase - cloud in credentials manager. + We keep `gcp` instead of `google` - """ - return { - AWS: self._cm_get_aws, - AZURE: self._cm_get_azure, - GOOGLE: self._cm_get_google, - 'aws': self._cm_get_aws, - 'azure': self._cm_get_azure, - 'google': self._cm_get_google, - 'gcp': self._cm_get_google - } - - def _cm_get_aws(self, configuration: CredentialsManager) -> dict: - cloud_identifier = configuration.cloud_identifier - if configuration.credentials_key and configuration.expiration: - _LOG.debug(f'Credentials key and expiration exist in config. ' - f'Checking expiration') - time_in_a_while = utc_datetime() + timedelta( - minutes=VALID_CREDENTIALS_THRESHOLD_MINUTES) - if (datetime.fromtimestamp(configuration.expiration, - timezone.utc) > time_in_a_while): - _LOG.debug(f'The account {cloud_identifier} credentials will' - f' expire in more than ' - f'{VALID_CREDENTIALS_THRESHOLD_MINUTES} minutes. ' - f'Acceptable :|') - return self.get_credentials_from_ssm( - configuration.credentials_key, remove=False) - - _LOG.debug(f'Getting new credentials for account {cloud_identifier}') - - credentials_dict, expiration = self._get_temp_aws_credentials( - configuration.trusted_role_arn) - if not credentials_dict: - return {} - _LOG.debug('Going to save credentials to SSM') - new_credentials_key = self._save_temp_credentials( - credentials=credentials_dict, - cloud_identifier=cloud_identifier - ) - _LOG.debug(f'Temporary credentials key: {new_credentials_key}') - if configuration.credentials_key: - _LOG.info(f'Removing the old SSM parameter ' - f'\'{configuration.credentials_key}\' with credentials') - self.ssm_service.delete_secret_value(configuration.credentials_key) - - _LOG.debug(f'Updating credentialsManagerConfig for account ' - f'{cloud_identifier} with new credentials key') - configuration.credentials_key = new_credentials_key - configuration.expiration = expiration.timestamp() - configuration.save() - return credentials_dict - - def _cm_get_azure(self, configuration: CredentialsManager) -> dict: - if not configuration.credentials_key: - _LOG.warning(f'No credentials key in: {configuration}') - return {} - return self.get_credentials_from_ssm(configuration.credentials_key, - remove=False) - - def _cm_get_google(self, configuration: CredentialsManager) -> dict: - """ - Not implemented - """ - if not configuration.credentials_key: - _LOG.warning(f'No credentials key in: {configuration}') - return {} - return self.get_credentials_from_ssm(configuration.credentials_key, - remove=False) - - def _get_temp_aws_credentials(self, role_arn: str - ) -> Tuple[dict, Optional[datetime]]: - """ - AWS temp credentials - """ - _LOG.debug(f'Assuming trusted role: {role_arn}') - try: - assume_role_result = self.sts_client.assume_role(role_arn=role_arn) - except ClientError as e: - _LOG.exception(f"Can't assume role with specified {role_arn}:") - return {}, None - credentials = assume_role_result['Credentials'] - return { - ENV_AWS_ACCESS_KEY_ID: credentials['AccessKeyId'], - ENV_AWS_SECRET_ACCESS_KEY: credentials['SecretAccessKey'], - ENV_AWS_SESSION_TOKEN: credentials['SessionToken'], - ENV_AWS_DEFAULT_REGION: self.environment_service.aws_region() - }, credentials['Expiration'] - - def _save_temp_credentials(self, credentials, cloud_identifier): - not_available = r'[^a-zA-Z0-9\/_.-]' - account_display_name = str(re.sub(not_available, '-', - cloud_identifier)) - timestamp = utc_datetime().strftime('%m.%d.%Y.%H.%M.%S') - credentials_key = f'caas.scan.{account_display_name}.{timestamp}' - _LOG.debug(f'Saving temporary credentials to {credentials_key}') - self.ssm_service.create_secret_value( - secret_name=credentials_key, - secret_value=credentials) - return credentials_key - - @staticmethod - def _adjust_cloud(cloud: str) -> str: - """ - Backward compatibility. We use GCP everywhere, but Maestro - Tenants use GOOGLE - """ - cloud = cloud.lower() - return 'gcp' if cloud == 'google' else cloud - - def get_credentials_for_tenant(self, tenant: Tenant) -> dict: - """ - Credentials manager keeps clouds in lowercase :( historical reasons - """ - cloud = self._adjust_cloud(tenant.cloud) - configuration = CredentialsManager.get_nullable( - hash_key=tenant.project, range_key=cloud) - if not configuration or not configuration.enabled: - _LOG.warning(f'Enabled credentials configurations does not ' - f'exist for cloud: {cloud} and ' - f'account {tenant.project}') - return {} - return self.get_credentials_from_cm(configuration) - - def get_credentials_from_cm(self, configuration: CredentialsManager - ) -> dict: - """ - Get credentials from credentials manager - """ - getter = self.credentials_cm_getter.get(configuration.cloud.lower()) - if not getter: - _LOG.warning(f'No available cloud: {configuration.cloud}') - return {} - return getter(configuration) - - def get_credentials_from_ssm(self, credentials_key: Optional[str] = None, - remove: Optional[bool] = True - ) -> Union[str, dict]: - """ - Get our (not maestro) credentials from ssm. For AWS and AZURE - these are already valid credentials envs. Must be just exported. - For GOOGLE a file must be created additionally. - :param credentials_key: - :param remove: - :return: - """ - if not credentials_key: - _LOG.info('Credentials key not provided to ' - 'get_credentials_from_ssm function. Using key from env') - credentials_key = self.environment_service.credentials_key() - if not credentials_key: - return {} - try: - value = self.ssm_service.get_secret_value(credentials_key) or {} - if remove: - _LOG.info(f'Removing secret {credentials_key}') - self.ssm_service.delete_secret_value(credentials_key) - return value - except json.JSONDecodeError: - return {} - - @staticmethod - def google_credentials_to_file(credentials: dict) -> dict: - with tempfile.NamedTemporaryFile('w', delete=False) as fp: - json.dump(credentials, fp) - return { - ENV_GOOGLE_APPLICATION_CREDENTIALS: fp.name, - ENV_CLOUDSDK_CORE_PROJECT: credentials.get('project_id') - } diff --git a/executor/services/environment_service.py b/executor/services/environment_service.py deleted file mode 100644 index e800b4bc5..000000000 --- a/executor/services/environment_service.py +++ /dev/null @@ -1,192 +0,0 @@ -import os -from typing import Optional - -from helpers.constants import ENV_DEFAULT_BUCKET_NAME, ENV_JOB_ID, \ - ENV_TARGET_RULESETS, ENV_AWS_DEFAULT_REGION, ENV_VAR_CREDENTIALS, \ - ENV_VAR_REGION, ENV_VAR_JOB_LIFETIME_MIN, ENV_TARGET_RULESETS_VIEW, \ - ENV_AFFECTED_LICENSES, ENV_LICENSED_RULESETS, ENV_TARGET_REGIONS, \ - ENV_SUBMITTED_AT, ENV_SERVICE_MODE, DOCKER_SERVICE_MODE, \ - AWS_DEFAULT_REGION, ENV_EXECUTOR_MODE, CONCURRENT_EXECUTOR_MODE, \ - CONSISTENT_EXECUTOR_MODE, DEFAULT_JOB_LIFETIME_MIN, \ - ENVS_TO_HIDE, HIDDEN_ENV_PLACEHOLDER, ENV_SCHEDULED_JOB_NAME, \ - ENV_JOB_TYPE, STANDARD_JOB_TYPE, SCHEDULED_JOB_TYPE_FOR_ENV, \ - ENV_BATCH_RESULTS_ID, ENV_BATCH_RESULTS_IDS, \ - MULTI_ACCOUNT_EVENT_DRIVEN_JOB_TYPE, ENV_SYSTEM_CUSTOMER_NAME, \ - ENV_TENANT_NAME, ENV_VAR_STATS_S3_BUCKET_NAME, ENV_ALLOW_MANAGEMENT_CREDS, \ - ENV_VAR_RULESETS_BUCKET_NAME, ENV_PLATFORM_ID - - -class EnvironmentService: - def __init__(self): - self._environment = os.environ - - def override_environment(self, environs: dict) -> None: - self._environment.update(environs) - - def reports_bucket_name(self): - return self._environment.get(ENV_DEFAULT_BUCKET_NAME) - - def statistics_bucket_name(self): - return self._environment.get(ENV_VAR_STATS_S3_BUCKET_NAME) - - def rulesets_bucket_name(self): - return self._environment.get(ENV_VAR_RULESETS_BUCKET_NAME) - - def batch_job_id(self): - return self._environment.get(ENV_JOB_ID) - - def batch_results_id(self) -> str: - """ - Returns an id of BatchResults DB item. The job will get rules to - scan from there and will put its results in there. Currently, - it's intended to be used with event-driven jobs only - """ - return self._environment.get(ENV_BATCH_RESULTS_ID) - - def batch_results_ids(self) -> set: - env = self._environment.get(ENV_BATCH_RESULTS_IDS) - if not env: - return set() - return set(env.split(',')) - - def target_regions(self) -> list: - regions = self._environment.get(ENV_TARGET_REGIONS) - if regions: - return [region.strip() for region in regions.split(',')] - return [] - - def target_rulesets(self) -> list: - env = self._environment.get(ENV_TARGET_RULESETS) - if isinstance(env, str): - return env.split(',') - return [] - - def target_rulesets_view(self) -> list: - """ - Returns target rulesets in human-readable view in case the - necessary env exists. If it doesn't - returns the same as - self.target_rulesets - """ - rulesets_view = self._environment.get(ENV_TARGET_RULESETS_VIEW) - if not isinstance(rulesets_view, str): - return self.target_rulesets() - return rulesets_view.split(',') - - def licensed_ruleset_map(self, license_key_list: list): - reference_map = {} - rulesets = self._environment.get(ENV_LICENSED_RULESETS) - if rulesets: - for each in rulesets.split(','): - index, *ruleset_id = each.split(':', 1) - try: - index = int(index) - except ValueError: - continue - if ruleset_id and 0 <= index < len(license_key_list): - key = license_key_list[index] - referenced = reference_map.setdefault(key, []) - referenced.append(ruleset_id[0]) - return reference_map - - def licensed_ruleset_list(self): - rulesets = self._environment.get(ENV_LICENSED_RULESETS) or '' - output = [] - for each in rulesets.split(','): - _, *ruleset_id = each.split(':', 1) - if ruleset_id: - output.append(ruleset_id[0]) - return output - - def affected_licenses(self): - license_keys = self._environment.get(ENV_AFFECTED_LICENSES) - if license_keys: - return [each.strip() for each in license_keys.split(',')] - - def is_licensed_job(self) -> bool: - """ - Returns true in case the job is licensed. A licensed job is the - one which involves at least one licensed ruleset. - """ - return bool(self.affected_licenses()) - - def aws_default_region(self): - return self._environment.get(ENV_AWS_DEFAULT_REGION, - AWS_DEFAULT_REGION) - - def aws_region(self): - return self._environment.get(ENV_VAR_REGION) or \ - self.aws_default_region() - - def credentials_key(self): - return self._environment.get(ENV_VAR_CREDENTIALS) - - def job_lifetime_min(self): - return int(self._environment.get( - ENV_VAR_JOB_LIFETIME_MIN, DEFAULT_JOB_LIFETIME_MIN)) - - def is_docker(self): - return self._environment.get(ENV_SERVICE_MODE) == DOCKER_SERVICE_MODE - - def job_type(self) -> str: - """ - Default job type is `standard` - """ - return self._environment.get(ENV_JOB_TYPE) or STANDARD_JOB_TYPE - - def is_standard(self) -> bool: - return self.job_type() == STANDARD_JOB_TYPE - - def is_multi_account_event_driven(self) -> bool: - return self.job_type() == MULTI_ACCOUNT_EVENT_DRIVEN_JOB_TYPE - - def is_scheduled(self) -> bool: - return self.job_type() == SCHEDULED_JOB_TYPE_FOR_ENV - - def submitted_at(self): - return self._environment.get(ENV_SUBMITTED_AT) - - def is_concurrent(self) -> bool: - mode = self._environment.get(ENV_EXECUTOR_MODE) or \ - CONSISTENT_EXECUTOR_MODE - return mode == CONCURRENT_EXECUTOR_MODE - - def scheduled_job_name(self) -> Optional[str]: - return self._environment.get(ENV_SCHEDULED_JOB_NAME) or None - - def system_customer(self) -> Optional[str]: - """ - Currently used only for event-driven scans in order to retrieve - ED system rulesets. - """ - return self._environment.get(ENV_SYSTEM_CUSTOMER_NAME) - - def tenant_name(self) -> Optional[str]: - """ - Standard (not event-driven) scans involve one tenant per job. - This env contains this tenant's name - """ - return self._environment.get(ENV_TENANT_NAME) - - def platform_id(self) -> Optional[str]: - """ - We can scan platforms within tenants. In case platform id is - provided, this specific platform must be scanned - :return: - """ - return self._environment.get(ENV_PLATFORM_ID) - - def is_management_creds_allowed(self) -> bool: - """ - Specifies whether it's allowed to use Maestro's management - credentials to scan a tenant. Default if False because it's not safe. - Those creds have not only read access - """ - return str( - self._environment.get(ENV_ALLOW_MANAGEMENT_CREDS) - ).lower() == 'true' - - def __repr__(self): - return ', '.join([ - f'{k}={v if k not in ENVS_TO_HIDE else HIDDEN_ENV_PLACEHOLDER}' - for k, v in self._environment.items() - ]) diff --git a/executor/services/integration_service.py b/executor/services/integration_service.py deleted file mode 100644 index 6320ce651..000000000 --- a/executor/services/integration_service.py +++ /dev/null @@ -1,83 +0,0 @@ -from typing import List - -import requests -from modular_sdk.commons.constants import ParentType -from modular_sdk.models.tenant import Tenant -from modular_sdk.services.impl.maestro_credentials_service import \ - DefectDojoApplicationSecret, DefectDojoApplicationMeta - -from helpers.log_helper import get_logger -from integrations.defect_dojo_adapter import DefectDojoAdapter -from integrations.security_hub.security_hub_adapter import SecurityHubAdapter -from models.modular.parents import DefectDojoParentMeta -from services.modular_service import ModularService -from services.ssm_service import SSMService - -_LOG = get_logger(__name__) - - -class IntegrationService: - def __init__(self, modular_service: ModularService, - ssm_service: SSMService): - self._modular_service = modular_service - self._ssm_service = ssm_service - - def get_dojo_adapters(self, tenant: Tenant) -> List[DefectDojoAdapter]: - adapters = [] - # todo maybe in future we will be able to have multiple dojo - # integrations for one tenant - parent = self._modular_service.modular_client.parent_service().get_linked_parent_by_tenant( # noqa - tenant, ParentType.SIEM_DEFECT_DOJO - ) - if not parent: - return adapters - parent_meta = DefectDojoParentMeta.from_dict(parent.meta.as_dict()) - application = self._modular_service.get_parent_application(parent) - if not application or not application.secret: - return adapters - - raw_secret = self._modular_service.modular_client.assume_role_ssm_service().get_parameter(application.secret) - if not raw_secret or not isinstance(raw_secret, dict): - _LOG.debug(f'SSM Secret by name {application.secret} not found') - return adapters - meta = DefectDojoApplicationMeta.from_dict(application.meta.as_dict()) - secret = DefectDojoApplicationSecret.from_dict(raw_secret) - try: - _LOG.info('Initializing dojo client') - adapters.append(DefectDojoAdapter( - host=meta.url, - api_key=secret.api_key, - entities_mapping=parent_meta.entities_mapping, - display_all_fields=parent_meta.display_all_fields, - upload_files=parent_meta.upload_files, - resource_per_finding=parent_meta.resource_per_finding - )) - except requests.RequestException as e: - _LOG.warning( - f'Error occurred trying to initialize dojo adapter: {e}') - return adapters - - def get_security_hub_adapters(self, tenant: Tenant - ) -> List[SecurityHubAdapter]: - adapters = [] - # -------- - # TODO here must be code to retrieve application with access - # to SH (aws account), currently just mock - # -------- - application = self._modular_service.get_application( - '9f993a95-01b3-4554-8ba3-4b427f20730f') # AWS_ROLE - if not application: - return adapters - mcs = self._modular_service.modular_client.maestro_credentials_service() - creds = mcs.get_by_application(application) - if not creds: - return adapters - adapters.append(SecurityHubAdapter( - aws_region=creds.AWS_DEFAULT_REGION, - product_arn='', # TODO from parent ? - aws_access_key_id=creds.AWS_ACCESS_KEY_ID, - aws_secret_access_key=creds.AWS_SECRET_ACCESS_KEY, - aws_session_token=creds.AWS_SESSION_TOKEN, - aws_default_region=creds.AWS_DEFAULT_REGION - )) - return adapters diff --git a/executor/services/job_updater_service.py b/executor/services/job_updater_service.py deleted file mode 100644 index 2b4e5ad0e..000000000 --- a/executor/services/job_updater_service.py +++ /dev/null @@ -1,265 +0,0 @@ -import time -from abc import ABC, abstractmethod -from typing import Optional - -from modular_sdk.models.tenant_settings import TenantSettings -from modular_sdk.services.tenant_settings_service import TenantSettingsService -from pynamodb.exceptions import UpdateError - -from helpers.constants import JobState -from helpers.log_helper import get_logger -from helpers.time_helper import utc_iso -from models.job import Job -from models.scheduled_job import ScheduledJob -from services.environment_service import EnvironmentService -from services.license_manager_service import LicenseManagerService -from services.modular_service import TenantService - -CREATED_AT_ATTR = 'created_at' -STARTED_AT_ATTR = 'started_at' -STOPPED_AT_ATTR = 'stopped_at' - -STATUS_ATTR = 'status' - -SCAN_RULESETS_ATTR = 'scan_rulesets' -SCAN_REGIONS_ATTR = 'scan_regions' - -ALLOWED_TIMESTAMP_ATTRIBUTES = ( - CREATED_AT_ATTR, STARTED_AT_ATTR, STOPPED_AT_ATTR) - -_LOG = get_logger(__name__) - - -class AbstractJobLock(ABC): - - @abstractmethod - def acquire(self, *args, **kwargs): - pass - - @abstractmethod - def release(self): - pass - - @abstractmethod - def locked(self) -> bool: - pass - - -class TenantSettingJobLock(AbstractJobLock): - TYPE = 'CUSTODIAN_JOB_LOCK' # tenant_setting type - EXPIRATION = 3600 * 1.5 # in seconds, 1.5h - - def __init__(self, tenant_name: str): - """ - >>> lock = TenantSettingJobLock('MY_TENANT') - >>> lock.locked() - False - >>> lock.acquire('job-1') - >>> lock.locked() - True - >>> lock.job_id - 'job-1' - >>> lock.release() - >>> lock.locked() - False - >>> lock.release() - >>> lock.locked() - False - :param tenant_name: - """ - self._tenant_name = tenant_name - - self._item = None # just cache - - @property - def tss(self) -> TenantSettingsService: - """ - Tenant settings service - :return: - """ - from services import SP - return SP.modular_service().modular_client.tenant_settings_service() - - @property - def job_id(self) -> Optional[str]: - """ - ID of a job the lock is locked with - :return: - """ - if not self._item: - return - return self._item.value.as_dict().get('jid') - - @property - def tenant_name(self) -> str: - return self._tenant_name - - def acquire(self, job_id: str): - """ - You must check whether the lock is locked before calling acquire(). - :param job_id: - :return: - """ - item = self.tss.create( - tenant_name=self._tenant_name, - key=self.TYPE - ) - self.tss.update(item, actions=[ - TenantSettings.value.set({ - 'exp': time.time() + self.EXPIRATION, - 'jid': job_id, - 'locked': True - }) - ]) - self._item = item - - def release(self): - item = self.tss.create( - tenant_name=self._tenant_name, - key=self.TYPE - ) - try: - self.tss.update(item, actions=[ - TenantSettings.value['locked'].set(False) - ]) - except UpdateError: - # it's normal. It means that item.value['locked'] simply - # does not exist and update action cannot perform its update. - # DynamoDB raises UpdateError if you try to update not existing - # nested key - pass - self._item = item - - def locked(self) -> bool: - item = self.tss.get(self._tenant_name, self.TYPE) - if not item: - return False - self._item = item - value = item.value.as_dict() - if not value.get('locked'): - return False - # locked = True - if not value.get('exp'): - return True # no expiration, we locked - return value.get('exp') > time.time() - - -class JobUpdaterService: - def __init__(self, environment_service: EnvironmentService, - license_manager_service: LicenseManagerService, - tenant_service: TenantService): - self._environment_service = environment_service - self._license_manager_service = license_manager_service - self._tenant_service = tenant_service - self._job = None - - @property - def is_docker(self) -> bool: - return self._environment_service.is_docker() - - def _create_job(self, **kwargs) -> Job: - _tenant = self._tenant_service.get_tenant() - params = dict( - job_id=self._environment_service.batch_job_id(), - job_owner=_tenant.customer_name, - # because we cannot access user_id - tenant_display_name=_tenant.name, - customer_display_name=_tenant.customer_name, - submitted_at=self._environment_service.submitted_at(), - scheduled_rule_name=self._environment_service.scheduled_job_name()) - params.update(kwargs) - return Job(**params) - - @property - def job(self) -> Job: - if not self._job: - job_id = self._environment_service.batch_job_id() - _LOG.info(f'Querying a job with id \'{job_id}\'') - self._job = Job.get_nullable(hash_key=job_id) - if not self._job: - _LOG.warning(f'Job with id \'{job_id}\' does not exist in ' - f'DB. Creating one') - self._job = self._create_job() - return self._job - - def _save(self): - if isinstance(self._job, Job): - self._job.save() - - def set_created_at(self): - if self.is_docker: - self.job.update(actions=[ - Job.created_at.set(utc_iso()), - Job.status.set(JobState.STARTING.value) - ]) - - def set_started_at(self): - if self.is_docker: - self.job.update(actions=[ - Job.started_at.set(utc_iso()), - Job.status.set(JobState.RUNNING.value), - Job.scan_rulesets.set( - (self._environment_service.target_rulesets_view() or [] + - self._environment_service.licensed_ruleset_list()) - ), - Job.scan_regions.set( - self._environment_service.target_regions()) - ]) - - def set_failed_at(self, reason): - self._set_stopped_at(JobState.FAILED, reason) - - def set_succeeded_at(self): - if self.is_docker: - self._set_stopped_at(JobState.SUCCEEDED) - - def _set_stopped_at(self, status: JobState, reason: str = None): - assert status in {JobState.SUCCEEDED, JobState.FAILED} - actions = [Job.stopped_at.set(utc_iso()), Job.status.set(status.value)] - if reason: - actions.append(Job.reason.set(reason)) - self.job.update(actions=actions) - _job = self.job - self.update_job_in_lm( - job_id=_job.job_id, - created_at=_job.created_at, - started_at=_job.started_at, - stopped_at=_job.stopped_at, - status=_job.status - ) - - def update_job_in_lm(self, job_id: str, created_at: Optional[str] = None, - started_at: Optional[str] = None, - stopped_at: Optional[str] = None, - status: Optional[JobState] = None): - # for saas the job in LM will be updated in caas-job-updater - if (self._environment_service.is_licensed_job() and - self._environment_service.is_docker()): - _LOG.info( - 'The job is licensed on premises. Updating the job in LM') - self._license_manager_service.update_job_in_license_manager( - job_id=job_id, - created_at=created_at, - started_at=started_at, - stopped_at=stopped_at, - status=status - ) - - def update_scheduled_job(self): - """ - Updates 'last_execution_time' in scheduled job item if - this is a scheduled job. - """ - _LOG.info('Updating scheduled job item in DB') - scheduled_job_name = self._environment_service.scheduled_job_name() - if scheduled_job_name: - _LOG.info('The job is scheduled. Updating the ' - '\'last_execution_time\' in scheduled job item') - item = ScheduledJob(id=scheduled_job_name, - type=ScheduledJob.default_type) - item.update(actions=[ - ScheduledJob.last_execution_time.set(utc_iso()) - ]) - else: - _LOG.info('The job is not scheduled. No scheduled job ' - 'item to update. Skipping') diff --git a/executor/services/license_manager_service.py b/executor/services/license_manager_service.py deleted file mode 100644 index 8166b872a..000000000 --- a/executor/services/license_manager_service.py +++ /dev/null @@ -1,255 +0,0 @@ -from datetime import timedelta -from http import HTTPStatus -from typing import List, Dict, Optional - -from helpers.constants import RULESET_CONTENT_ATTR, \ - READY_TO_SCAN_CODE, ITEMS_PARAM, MESSAGE_PARAM, CLIENT_TOKEN_ATTR, \ - KID_ATTR, ALG_ATTR, JobState -from helpers.log_helper import get_logger -from helpers.time_helper import utc_datetime -from services.clients.license_manager import LicenseManagerClient -from services.token_service import TokenService - -_LOG = get_logger(__name__) - -GENERIC_JOB_LICENSING_ISSUE = 'Job:\'{id}\' could not be granted by the ' \ - 'License Manager Service.' - - -class BalanceExhaustion(Exception): - def __init__(self, message: str): - self.message = message - - def __str__(self): - return self.message - - -class InaccessibleAssets(Exception): - def __init__( - self, message: str, assets: Dict[str, List[str]], - hr_sep: str, ei_sep: str, i_sep: str, i_wrap: Optional[str] = None - ): - self._assets = self._dissect( - message=message, assets=assets, hr_sep=hr_sep, ei_sep=ei_sep, - i_sep=i_sep, i_wrap=i_wrap - ) - - @staticmethod - def _dissect( - message: str, assets: Dict[str, List[str]], - hr_sep: str, ei_sep: str, i_sep: str, i_wrap: Optional[str] = None - ): - """ - Dissects License Manager response of entity(ies)-not-found message. - Such as: TenantLicense or Ruleset(s):$id(s) - $reason. - param message: str - maintains the raw response message - param assets: Dict[str, List[str]] - source of assets to - param hr_sep: str - head-reason separator, within the response message - param ei_sep: str - entity type - id(s) separator, within the head of - the message - param i_sep: str - separator of entity-identifier(s), within the raw - id(s). - param i_wrap: Optional[str] - quote-type wrapper of each identifier. - """ - each_template = 'Each of {} license-subscription' - head, *_ = message.rsplit(hr_sep, maxsplit=1) - head = head.strip(' ') - if not head: - _LOG.error(f'Response message is not separated by a \'{hr_sep}\'.') - return - - entity, *ids = head.split(ei_sep, maxsplit=1) - ids = ids[0] if len(ids) == 1 else '' - if 's' in entity and entity.index('s') == len(entity) - 1: - ids = ids.split(i_sep) - - ids = [each.strip(i_wrap or '') for each in ids.split(i_sep)] - - if 'TenantLicense' in entity: - ids = [ - asset - for tlk in ids - if tlk in assets - for asset in assets[tlk] or [each_template.format(tlk)] - ] - - return ids - - def __str__(self): - head = 'Ruleset' - - if len(self._assets) > 1: - head += 's' - scope = ', '.join(f'"{each}"' for each in self._assets) - reason = 'are' if len(self._assets) > 1 else 'is' - reason += ' no longer accessible' - return f'{head}:{scope} - {reason}.' - - def __iter__(self): - return iter(self._assets) - - -class LicenseManagerService: - - def __init__( - self, license_manager_client: LicenseManagerClient, - token_service: TokenService - ): - self.license_manager_client = license_manager_client - self.token_service = token_service - - def update_job_in_license_manager( - self, job_id: str, created_at: str = None, started_at: str = None, - stopped_at: str = None, status: JobState = None, - expires: dict = None): - - auth = self._get_client_token(expires or dict(hours=1)) - if not auth: - _LOG.warning('Client authorization token could be established.') - return None - - response = self.license_manager_client.patch_job( - job_id=job_id, created_at=created_at, started_at=started_at, - stopped_at=stopped_at, status=status, auth=auth - ) - - if response and response.status_code == HTTPStatus.OK.value: - return self.license_manager_client.retrieve_json(response) - return - - def instantiate_licensed_job_dto( - self, job_id: str, customer: str, tenant: str, - ruleset_map: Dict[str, List[str]], expires: dict = None - ): - """ - Mandates licensed Job data transfer object retrieval, - by successfully interacting with LicenseManager providing the - following parameters. - - :parameter job_id: str - :parameter customer: str - :parameter tenant: str - :parameter ruleset_map: Union[Type[None], List[str]] - :parameter expires: dict, denotes auth-token expiration - - :raises: InaccessibleAssets, given the requested content is not - accessible - :raises: BalanceExhaustion, given the job-balance has been exhausted - :return: Optional[Dict] - """ - auth = self._get_client_token(expires or dict(hours=1)) - if not auth: - _LOG.warning('Client authorization token could be established.') - return None - response = self.license_manager_client.post_job( - job_id=job_id, customer=customer, tenant=tenant, - ruleset_map=ruleset_map, auth=auth - ) - if response is None: - return - - decoded = self.license_manager_client.retrieve_json(response) or {} - if response.status_code == HTTPStatus.OK.value: - items = decoded.get(ITEMS_PARAM, []) - if len(items) != 1: - _LOG.warning(f'Unexpected License Manager response: {items}.') - item = None - else: - item = items.pop() - return item - - else: - message = decoded.get(MESSAGE_PARAM) - if response.status_code == HTTPStatus.NOT_FOUND.value: - raise InaccessibleAssets( - message=message, assets=ruleset_map, - hr_sep='-', ei_sep=':', i_sep=', ', i_wrap='\'' - ) - elif response.status_code == HTTPStatus.FORBIDDEN.value: - raise BalanceExhaustion(message) - - def instantiate_job_sourced_ruleset_list(self, licensed_job_dto: dict): - """ - Mandates production of ruleset dto list, items of which have been - attached to a licensed job. Aforementioned data is retrieved from - a response object of a `Job` instantiation request, denoted - `license_job_dto`. - :parameter licensed_job_dto: dict - :return: List[Dict] - """ - _default = self._default_instance - licensed_job_dto = _default(licensed_job_dto, dict) - content = _default(licensed_job_dto.get(RULESET_CONTENT_ATTR), dict) - - return [ - self._instantiate_licensed_ruleset_data(ruleset_id=ruleset_id, - source=source) - for ruleset_id, source in content.items() - ] - - def _get_client_token(self, expires: dict, **payload): - """ - Delegated to derive a custodian-service-token, encoding any given - payload key-value pairs into the claims. - :parameter expires: dict, meant to store timedelta kwargs - :parameter payload: dict - :return: Union[str, Type[None]] - """ - token_type = CLIENT_TOKEN_ATTR - key_data = self.license_manager_client.client_key_data - kid, alg = key_data.get(KID_ATTR), key_data.get(ALG_ATTR) - if not (kid and alg): - _LOG.warning('LicenseManager Client-Key data is missing.') - return - - t_head = f'\'{token_type}\'' - encoder = self.token_service.derive_encoder( - token_type=CLIENT_TOKEN_ATTR, **payload - ) - - if not encoder: - return None - - # Establish a kid reference to a key. - encoder.prk_id = self.derive_client_private_key_id( - kid=kid - ) - _LOG.info(f'{t_head} - {encoder.prk_id} private-key id has been ' - f'assigned.') - - encoder.kid = kid - _LOG.info(f'{t_head} - {encoder.kid} token \'kid\' has been assigned.') - - encoder.alg = alg - _LOG.info(f'{t_head} - {encoder.alg} token \'alg\' has been assigned.') - - encoder.expire(utc_datetime() + timedelta(**expires)) - try: - token = encoder.product - except (Exception, BaseException) as e: - _LOG.error(f'{t_head} could not be encoded, due to: {e}.') - token = None - - if not token: - _LOG.warning(f'{t_head} token could not be encoded.') - return token - - @staticmethod - def derive_client_private_key_id(kid: str): - return f'cs_lm_client_{kid}_prk' - - @staticmethod - def _instantiate_licensed_ruleset_data(ruleset_id: str, source: str): - """ - Designated to produce an ambiguously licensed ruleset data, including - a given `ruleset_id` and URI `source`. - :parameter ruleset_id: str - :parameter source: str - :return: Dict - """ - return dict(id=ruleset_id, licensed=True, s3_path=source, - active=True, status=dict(code=READY_TO_SCAN_CODE)) - - @staticmethod - def _default_instance(value, _type: type, *args, **kwargs): - return value if isinstance(value, _type) else _type(*args, **kwargs) diff --git a/executor/services/modular_service.py b/executor/services/modular_service.py deleted file mode 100644 index 816fd7165..000000000 --- a/executor/services/modular_service.py +++ /dev/null @@ -1,190 +0,0 @@ -from functools import cached_property -from typing import Union, Callable, Optional, Dict, Set, Iterator - -from modular_sdk.commons.constants import ParentType -from modular_sdk.models.customer import Customer -from modular_sdk.models.tenant import Tenant -from modular_sdk.models.tenant_settings import TenantSettings -from pynamodb.expressions.condition import Condition - -from helpers.constants import CUSTODIAN_TYPE, \ - TENANT_ENTITY_TYPE -from helpers.constants import STEP_GET_TENANT -from helpers.exception import ExecutorException -from helpers.log_helper import get_logger -from models.modular.application import Application -from models.modular.parents import Parent -from services.clients.modular import ModularClient -from services.environment_service import EnvironmentService - -_LOG = get_logger(__name__) - - -class ModularService: - def __init__(self, client: ModularClient): - self._client: ModularClient = client - - @property - def modular_client(self) -> ModularClient: - return self._client - - @cached_property - def available_types(self) -> Set[str]: - return { - TENANT_ENTITY_TYPE, - 'RULESET_LICENSE_PRIORITY', # this one is built dynamically :(, - None # if none, just prefix is returned - } - - def entity_type(self, _type: Optional[str] = None) -> str: - """ - Builds type string for Maestro Parent and key string for - Maestro TenantSetting associated with Custodian - """ - assert _type in self.available_types, \ - 'Not available type. Add it to the set and tell about it Maestro' - if _type: - return f'{CUSTODIAN_TYPE}_{_type}' - return CUSTODIAN_TYPE - - def get_customer(self, customer: Union[str, Customer]): - """ - Returns either a bare Customer, Customer-complemented-Parent or None. - :parameter customer: Union[str, Customer] - :return: Union[Customer, Complemented, Type[None]] - """ - return self._get_customer(customer=customer) - - def _get_customer(self, customer: Union[str, Customer]): - """ - Returns either a bare Customer or Customer-complemented-Parent, - produced by a `fetcher`, adhering to the type of given `customer` - parameter. - Given no aforementioned `fetcher` could be established, returns None. - :parameter customer: Union[str, Customer] - :return: Union[Customer, Complemented, Type[None]] - """ - fetcher: Callable = self._customer_fetcher_map.get(customer.__class__) - return fetcher(customer=customer) if fetcher else None - - @property - def _customer_fetcher_map(self): - return { - str: self._fetch_customer, - } - - def _fetch_customer(self, customer: str): - service = self.modular_client.customer_service() - return service.get(name=customer) if service else None - - def get_tenant(self, tenant: str) -> Optional[Tenant]: - return self.modular_client.tenant_service().get(tenant) - - def get_parent(self, parent_id: str) -> Optional[Parent]: - return self.modular_client.parent_service().get_parent_by_id(parent_id) - - def get_applications(self, customer: Optional[str] = None, - _type: Optional[str] = None, - deleted: Optional[bool] = False, - limit: Optional[int] = None - ) -> Iterator[Application]: - return self.modular_client.application_service().list( - customer=customer, - _type=_type, - deleted=deleted, - limit=limit - ) - - def get_customer_bound_parents(self, customer: Union[str, Customer], - parent_type: Optional[str] = None, - is_deleted: Optional[bool] = None, - meta_conditions: Optional[Condition] = None, - limit: Optional[int] = None - ) -> Iterator[Parent]: - name = customer.name if isinstance(customer, Customer) else customer - return self.modular_client.parent_service().i_get_parent_by_customer( - customer_id=name, - parent_type=parent_type, - is_deleted=is_deleted, - meta_conditions=meta_conditions, - limit=limit - ) - - def get_tenant_bound_setting(self, tenant: Union[str, Tenant], - complemented_type: str = TENANT_ENTITY_TYPE - ) -> Optional[TenantSettings]: - """ - Returns tenant-bound setting with given type - """ - name = tenant.name if isinstance(tenant, Tenant) else tenant - return self._fetch_bare_tenant_settings( - tenant=name, complement_type=complemented_type - ) - - def get_parent_application(self, parent: Parent) -> Optional[Application]: - if not parent.application_id: - return - application = self.get_application(parent.application_id) - if not application or application.is_deleted: - return - return application - - def get_tenant_application(self, tenant: Tenant, _type: ParentType - ) -> Optional[Application]: - """ - Resolved application from tenant - :param tenant: - :param _type: parent type, not tenant type - :return: - """ - parent = self.modular_client.parent_service().get_linked_parent_by_tenant(tenant, _type) # noqa - if not parent: - return - return self.get_parent_application(parent) - - def get_application(self, application: str) -> Optional[Application]: - return self.modular_client.application_service().get_application_by_id( - application - ) - - def _fetch_bare_tenant_settings(self, tenant: str, complement_type: str - ) -> Optional[TenantSettings]: - """ - Returns a TenantSettings entity of a given complement type, - related to a respective Tenant. - :parameter tenant: str, index reference. - :parameter complement_type: str - :return: Optional[TenantSettings] - """ - name_reference = tenant - # type reference - key_reference = self.entity_type(complement_type) - return next(self.modular_client.tenant_settings_service().i_get_by_tenant( - tenant=name_reference, key=key_reference - ), None) - - -class TenantService: - def __init__(self, modular_service: ModularService, - environment_service: EnvironmentService): - self._modular_service = modular_service - self._environment_service = environment_service - - self._cache: Dict[str, Tenant] = {} - - def get_tenant(self, name: Optional[str] = None, - allow_none=False) -> Optional[Tenant]: - name = name or self._environment_service.tenant_name() - if name in self._cache: - _LOG.info(f'Tenant {name} found in cache. Returning') - return self._cache[name] - _LOG.info(f'Tenant {name} not found in cache. Querying') - tenant = self._modular_service.get_tenant(name) - if not tenant and not allow_none: - raise ExecutorException( - step_name=STEP_GET_TENANT, - reason=f'Could not get tenant by name: {name}' - ) - if tenant: - self._cache[name] = tenant - return tenant diff --git a/executor/services/notification_service.py b/executor/services/notification_service.py deleted file mode 100644 index 7e791034b..000000000 --- a/executor/services/notification_service.py +++ /dev/null @@ -1,195 +0,0 @@ -import smtplib -from email import encoders -from email.mime.base import MIMEBase -from email.mime.multipart import MIMEMultipart -from email.mime.text import MIMEText -from typing import List -from typing import Optional - -from jinja2 import Environment, BaseLoader -from modular_sdk.models.tenant import Tenant - -from helpers.log_helper import get_logger -from services.s3_service import S3Service -from services.setting_service import SettingService -from services.ssm_service import SSMService - -DEFAULT_SENDER_ATTR = 'default_sender' -USE_TLS_ATTR = 'use_tls' -MAX_EMAILS_ATTR = 'max_emails' - -CUSTODIAN_FOLDER_NAME = 'custodian' - -_LOG = get_logger(__name__) - - -class NotificationService: - def __init__(self, setting_service: SettingService, - ssm_service: SSMService, s3_service: S3Service): - self.setting_service = setting_service - self.ssm_service = ssm_service - self.s3_service = s3_service - - self._settings = None - self._password = None - self._client, self._num_emails = None, 0 - self._close_session() - - @property - def setting(self) -> dict: - if not self._settings: - self._settings = \ - self.setting_service.get_mail_configuration() or {} - return self._settings - - @property - def host(self): - return self.setting.get('host') or 'localhost' - - @property - def port(self): - return self.setting.get('port') or 25 - - @property - def username(self): - return self.setting.get('username') - - @property - def password(self): - if not self._password: - self._password = self.ssm_service.get_secret_value( - self.setting.get('password')) - return self._password - - @property - def default_sender(self): - return self.setting.get(DEFAULT_SENDER_ATTR) - - @property - def use_tls(self): - return self.setting.get(USE_TLS_ATTR) is not False # True default - - @property - def max_emails(self): - return self.setting.get(MAX_EMAILS_ATTR) or 1 - - def __del__(self): - self._close_session() - - @staticmethod - def build_message(sender_email: str, recipients: list, subject: str, - text: str = None, html: str = None, attachment=None, - attachment_filename='attachment') -> str: - multipart_content_subtype = 'alternative' if text and html else 'mixed' - msg = MIMEMultipart(multipart_content_subtype) - msg['Subject'] = subject - msg['From'] = sender_email - msg['To'] = ', '.join(recipients) - - if text: - part = MIMEText(text, 'plain') - msg.attach(part) - if html: - part = MIMEText(html, 'html') - msg.attach(part) - - # Add attachments - if attachment: - part = MIMEBase('application', 'octet-stream') - part.set_payload(open('temp_workbook.csv', 'rb').read()) - encoders.encode_base64(part) - part.add_header('Content-Disposition', 'attachment', - filename=f'{attachment_filename}.csv') - msg.attach(part) - return msg.as_string() - - def _get_template(self, filename: str) -> Optional[str]: - bucket_name = self.setting_service.get_template_bucket() - if not bucket_name: - _LOG.warning('Template bucket name is not set in CaaSSettings') - return - content = self.s3_service.get_file_content( - bucket_name=bucket_name, - path=f'{CUSTODIAN_FOLDER_NAME}/{filename}' - ) - if not content: - _LOG.warning('Application Service is not configured properly: ' - f'cannot get `{filename}` from `{bucket_name}`') - return content - - def _get_findings_template(self) -> Optional[str]: - return self._get_template('findings.html') - - def _get_schedule_deactivate_template(self): - return self._get_template('schedule_deactivate.html') - - def _init_session(self): - self._num_emails = 0 - _LOG.info('Going to init SMTP connection') - client = smtplib.SMTP(host=self.host, port=self.port) - if self.use_tls: - _LOG.info('Starting tls connection') - client.starttls() - if self.username and self.password: - _LOG.info(f'Username \'{self.username}\' and password were ' - f'given. Logging in..') - client.login(self.username, self.password) - self._client = client - - def _close_session(self): - if isinstance(self._client, smtplib.SMTP): - _LOG.info('Going to close SMTP connection') - try: - self._client.quit() - except smtplib.SMTPException: - pass - self._client = None - self._num_emails = 0 - - def send_rescheduling_notice_notification( - self, recipients: list, subject: str, tenant: Tenant, - scheduled_job_name: str, - ruleset_list: List[str], customer: str, sender_email: str = None): - sender_email = sender_email or self.default_sender or self.username - _LOG.info(f'Going to push rescheduling-notice notification from' - f' {sender_email} to {recipients}. Subject: \'{subject}\'') - template_content = self._get_schedule_deactivate_template() - if template_content: - _body = self._render_template( - template_content=template_content, - data={'scheduled_job_name': scheduled_job_name, - 'account_name': tenant.name, - 'account_id': tenant.project, - 'customer': customer, - 'ruleset_list': ruleset_list}) - return self.send_email( - sender_email=sender_email, recipients=recipients, - message=self.build_message( - sender_email=sender_email, recipients=recipients, - subject=subject, html=_body - ) - ) - - def send_email(self, sender_email, recipients, message): - if not self._client: - try: - self._init_session() - except (smtplib.SMTPException, ConnectionError) as e: - _LOG.error(f'An error occurred while initializing ' - f'connection to SMTP server: {e}') - return False - try: - response = self._client.sendmail(sender_email, recipients, message) - _LOG.info(f'The mail was sent. Response received after ' - f'sending: {response}') - except smtplib.SMTPException as e: - _LOG.error(f'An error occurred while sending an email: {e}') - self._close_session() - return False - return True - - @staticmethod - def _render_template(template_content: str, data: dict): - env = Environment(loader=BaseLoader()).from_string(template_content) - result = env.render(**data) - return result diff --git a/executor/services/os_service.py b/executor/services/os_service.py deleted file mode 100644 index a745a7c0d..000000000 --- a/executor/services/os_service.py +++ /dev/null @@ -1,61 +0,0 @@ -import os -import pathlib -import shutil -import json - -from pathlib import Path -from helpers.constants import STEP_GENERATE_REPORT -from helpers.exception import ExecutorException -from helpers.log_helper import get_logger - -MAPPING_FOLDER = 'resources_mapping' -MAPPING_PATH = Path(__file__).parent.parent / MAPPING_FOLDER - -_LOG = get_logger(__name__) - - -class OSService: - def __init__(self): - self.c7n_workdir = str(pathlib.Path(__file__).parent.parent.absolute()) - - def create_workdir(self, job_id): - temp_dir = str(Path(self.c7n_workdir, job_id)) - os.chdir(str(pathlib.Path(temp_dir).parent)) - pathlib.Path(temp_dir).mkdir(exist_ok=True) - return temp_dir - - @staticmethod - def clean_workdir(work_dir): - shutil.rmtree(work_dir, ignore_errors=True) - _LOG.debug(f'Workdir for {work_dir} successfully cleaned') - - @staticmethod - def list_policies_in_dir(cloud_policy_dir): - yml_ = [file for file in os.listdir(cloud_policy_dir) if - file.endswith('.yml')] - yml_.sort() - return yml_ - - @staticmethod - def read_file(file_path, json_content=True): - if not os.path.exists(file_path): - _LOG.error(f'File does not exist by path: {file_path}') - raise ExecutorException( - reason=f'Expected path does not exist: {file_path}', - step_name=STEP_GENERATE_REPORT) - with open(file_path) as file_desc: - return json.loads(file_desc.read()) \ - if json_content else file_desc.read() - - @staticmethod - def get_resource_mapping(cloud_name: str): - resource_map_filename = \ - MAPPING_PATH / f'resources_map_{cloud_name.lower()}.json' - if not os.path.exists(resource_map_filename): - raise ExecutorException( - reason=f'Invalid path: {resource_map_filename}', - step_name='get resource mapping') - _LOG.debug(f'Extracting resource map from file: ' - f'\'{resource_map_filename}\'') - with open(resource_map_filename, 'r') as f: - return json.load(f) diff --git a/executor/services/policy_service.py b/executor/services/policy_service.py deleted file mode 100644 index 396678311..000000000 --- a/executor/services/policy_service.py +++ /dev/null @@ -1,206 +0,0 @@ -import json -import os -import tempfile -from pathlib import Path -from typing import Union, Optional, List, Dict, Set -from uuid import uuid4 - -from ruamel.yaml import YAML -from ruamel.yaml import __with_libyaml__ - -from helpers.constants import AWS, AZURE, GOOGLE -from helpers.log_helper import get_logger -from services.environment_service import EnvironmentService -from services.ruleset_service import RulesetService -from services.s3_service import S3Service - -_LOG = get_logger(__name__) - -_LOG.info(f'Initializing YAML object. ' - f'Using CLoader & CDumper: {__with_libyaml__}') -yaml = YAML(typ='safe', pure=False) -yaml.default_flow_style = False -_LOG.info('Yaml object was initialized') - - -class PolicyService: - def __init__(self, environment_service: EnvironmentService, - s3_service: S3Service, ruleset_service: RulesetService): - self.environment_service = environment_service - self.s3_service = s3_service - self.ruleset_service = ruleset_service - - def assure_event_driven_ruleset(self, cloud: str) -> Path: - """ - For event-driven scans we use full system event-driven rulesets - created beforehand. But only rules that are allowed for tenant - are will be loaded. - Returns local path to event-driven ruleset loading it in case - it has not been loaded yet - """ - assert cloud in {AWS, AZURE, GOOGLE} - cloud = cloud.upper() - filename = Path(tempfile.gettempdir(), f'{cloud}.json') - if filename.exists(): - _LOG.info(f'Event-driven ruleset for cloud {cloud} has already ' - f'been downloaded. Returning path to it.') - return filename - - item = self.ruleset_service.get_ed_ruleset(cloud) - - if item: - data = yaml.load(self._download_ruleset_content(item.get_json())) - else: - _LOG.warning(f'Event-driven ruleset item for cloud {cloud} not ' - f'found in DB. Creating an empty mock') - data = {'policies': []} - _LOG.debug(f'Dumping event-driven ruleset for cloud {cloud}') - with open(filename, 'w') as file: - json.dump(data, file) - return filename - - def separate_ruleset(self, from_: Path, work_dir: Path, - rules_to_keep: Optional[Set] = None, - rules_to_exclude: Optional[Set] = None) -> Path: - """ - Creates new ruleset file in work_dir filtering the ruleset - in `from_` variable (keeping and excluding specific rules). - This is done in order to reduce the size of rule-sets for event-driven - scans before they are loaded by Custom-Core. - """ - rules_to_keep = rules_to_keep or set() - rules_to_exclude = rules_to_exclude or set() - with open(from_, 'r') as file: - policies = json.load(file) - filtered = self.filter_policy_by_rules( - policies, rules_to_keep, rules_to_exclude - ) - filename = work_dir / f'{uuid4()}.json' - with open(filename, 'w') as file: - json.dump(filtered, file) - return filename - - def get_policies(self, work_dir, ruleset_list: List[Dict] = None, - rules_to_keep: Optional[set] = None, - rules_to_exclude: Optional[set] = None) -> list: - """ - The fewer rules in yaml, the faster Custom Core will cope with it. - That is why we exclude a lot of rules for event-driven - :param work_dir: dir to put rule-sets files in - :param ruleset_list: list of dicts (ruleset DTOs). - Both licensed rule-sets DTOs (from LM) and standard - :param rules_to_keep: rules to keep in YAMLs - :param rules_to_exclude: rules to exclude in YAMLs. - :return: - """ - rules_to_keep = rules_to_keep or set() - rules_to_exclude = rules_to_exclude or set() - region_dependent_rules_number, region_independent_rules_number = 0, 0 - - policy_files = [] - _rules_names = set() - for ruleset in ruleset_list: - content = self._download_ruleset_content(ruleset=ruleset) - if not content: - continue - - _LOG.info(f'Loading yaml string to dict for ' - f'ruleset: {ruleset.get("id")}') - policies = yaml.load(content) - _LOG.info('Yaml string was loaded.') - - policies = self.filter_policy_by_rules( - policies, rules_to_keep, rules_to_exclude) - _to_remove = [] - for rule in policies['policies']: - _name = rule.get('name') - if _name in _rules_names: - _LOG.warning(f'Duplicated rule \'{_name}\' was found. ' - f'Removing from ruleset ') - _to_remove.append(rule) - else: - _rules_names.add(_name) - for rule in _to_remove: - policies['policies'].remove(rule) - - # dumping to JSON is important here because we use ruamel.yaml - # (YAML 1.2), Custom-Core uses pyyaml (YAML 1.1). The important - # difference between them is 1.1 consider "on" and "off" without - # quotes to be booleans. For 1.2 "on" and "off" are always strings. - # Some Azure rules contain 'on' and 'off' as strings. When we - # load them, and dump again using safe C-Dumper, the quotes are - # removed and then Custom Core thinks that on and off are - # booleans ... and fails with validation error 'cause it - # expects strings. - _filename = os.path.join(work_dir, f'{str(uuid4())}.json') - with open(_filename, 'w') as file: - _LOG.info(f'Dumping policies json to JSON file {_filename}') - json.dump(policies, file) - _LOG.info('Policies were dumped') - policy_files.append(_filename) - - for policy in policies['policies']: - if policy.get('metadata', {}).get('multiregional'): - region_independent_rules_number += 1 - else: - region_dependent_rules_number += 1 - - _LOG.debug(f'Global: {region_independent_rules_number};\n' - f'Non-global: {region_dependent_rules_number}\n' - f'Policies files: {policy_files}') - - return policy_files - - def _download_ruleset_content(self, ruleset: dict): - s3_path: Union[str, dict] = ruleset.get('s3_path') - if not s3_path: - _LOG.warning(f'There is no path to S3 for ruleset ' - f'"{ruleset.get("name")}"') - return - dispatcher = self._instantiate_ruleset_collector().get(type(s3_path)) - if dispatcher: - return dispatcher(path=s3_path) - - def _collect_ruleset_content_bucket(self, path: dict): - """ - Mandates ruleset content collection from a bucket - and respective key-path. - :parameter path: dict - :return: Union[str, Type[None]] - """ - content = self.s3_service.get_file_content( - bucket_name=path.get('bucket_name'), - path=path.get('path') - ) - return content - - def _collect_ruleset_content_uri(self, path: str): - """ - Mandates ruleset content collection from a source URI path. - :parameter path: str - :return: Union[str, Type[None]] - """ - return self.ruleset_service.pull_ruleset_content(path, 'utf-8') - - def _instantiate_ruleset_collector(self) -> dict: - return { - dict: self._collect_ruleset_content_bucket, - str: self._collect_ruleset_content_uri - } - - @staticmethod - def filter_policy_by_rules(policy: dict, rules_to_keep: set = None, - rules_to_exclude: set = None) -> dict: - """If rules_to_keep is empty, all the rules are kept""" - rules_to_keep = rules_to_keep or set() - rules_to_exclude = rules_to_exclude or set() - result = {'policies': []} - for rule in policy.get('policies', []): - name = rule.get('name') - # version = str(rule.get('metadata', {}).get('version', '1.0')) - if (rules_to_keep and name not in rules_to_keep) or \ - (name in rules_to_exclude): - _LOG.warning(f'Skipping {name}') - continue - result['policies'].append(rule) - return result diff --git a/executor/services/report_service.py b/executor/services/report_service.py deleted file mode 100644 index f21dc8aaa..000000000 --- a/executor/services/report_service.py +++ /dev/null @@ -1,625 +0,0 @@ -import json -from concurrent.futures import ThreadPoolExecutor, as_completed -from functools import cached_property -from itertools import chain -from pathlib import Path, PurePosixPath -from typing import Dict, Generator, List, Optional, TypedDict, Tuple, Iterable - -from c7n.provider import get_resource_class -from c7n.resources import load_resources -from modular_sdk.models.parent import Parent -from modular_sdk.models.tenant import Tenant - -from helpers import filter_dict, hashable, json_path_get -from helpers.constants import FINDINGS_FOLDER, \ - AZURE_COMMON_REGION, MULTIREGION, AWS, AZURE, GOOGLE, KUBERNETES -from helpers.log_helper import get_logger -from helpers.time_helper import utc_datetime -from services.environment_service import EnvironmentService -from services.s3_settings_service import S3SettingsService - -_LOG = get_logger(__name__) - -DETAILED_REPORT_FILE = 'detailed_report.json' -REPORT_FILE = 'report.json' # `digest-report` -DIFFERENCE_FILE = 'difference.json' - -REPORT_FIELDS = {'id', 'name', 'arn'} # + date - - -class PolicyReportItem(TypedDict): - description: str - region: str - multiregional: str # "true" or "false" - resources: List[Dict] - remediation: Optional[str] - impact: Optional[str] - standard: Optional[Dict] - severity: Optional[str] - article: Optional[str] - service: str - vuln_id_from_tool: Optional[str] - tags: List[str] - - -class ReportFieldsLoader: - """ - Each resource type has its own class in Custom Core. That class has - resource_type inner class with some resource_type meta attributes. - There is always `id` attribute which points to the fields that is ID for - this particular type. Also, there are such attributes as: name, arn - (for aws). - """ - _fields = REPORT_FIELDS - _mapping = {} - - @classmethod - def _load_for_resource_type(cls, rt: str) -> Optional[dict]: - """ - Updates mapping for the given resource type. - It must be loaded beforehand - :param rt: - :return: - """ - try: - factory = get_resource_class(rt) - except (KeyError, AssertionError) as e: - _LOG.warning(f'Could not load resource type: {rt}') - return - resource_type = getattr(factory, 'resource_type', None) - if not resource_type: - _LOG.warning('Somehow resource type factory does not contain ' - 'inner resource_type class') - return - kwargs = {} - for field in cls._fields: - f = getattr(resource_type, field, None) - if not f: - continue - kwargs[field] = f - return kwargs - - @classmethod - def get(cls, rt: str) -> dict: - if rt not in cls._mapping: - fields = cls._load_for_resource_type(rt) - if not isinstance(fields, dict): - return {} - cls._mapping[rt] = fields - return cls._mapping[rt] - - @classmethod - def load(cls, resource_types: tuple = ('*',)): - """ - Loads all the modules. In theory, we must use this class after - performing scan. Till that moment all the necessary resources must be - already loaded - :param resource_types: - :return: - """ - load_resources(set(resource_types)) - - -class JobResult: - class RuleRawOutput(TypedDict): - metadata: dict - resources: List[Dict] - - class FormattedItem(TypedDict): # our detailed report item - policy: dict - resources: List[Dict] - - class DigestReport(TypedDict): - total_checks_performed: int - successful_checks: int - failed_checks: int - total_resources_violated_rules: int - - RegionRuleOutput = Tuple[str, str, RuleRawOutput] - - def __init__(self, work_dir: str, cloud: str): - self._work_dir = Path(work_dir) - self._cloud = cloud - - @cached_property - def environment_service(self) -> EnvironmentService: - from services import SP - return SP.environment_service() - - @staticmethod - def cloud_to_resource_type_prefix() -> dict: - return { - AWS: 'aws', - AZURE: 'azure', - GOOGLE: 'gcp', - KUBERNETES: 'k8s' - } - - def adjust_resource_type(self, rt: str) -> str: - rt = rt.split('.', maxsplit=1)[-1] - return '.'.join(( - self.cloud_to_resource_type_prefix()[self._cloud], rt - )) - - @staticmethod - def _load_raw_rule_output(root: Path) -> Optional[RuleRawOutput]: - """ - Folder with rule output contains three files: - 'custodian-run.log' -> logs in text - 'metadata.json' -> dict - 'resources.json' -> list or resources - In case resources.json files does not exist we deem this execution - invalid and do not load it - :param root: - :return: - """ - logs = root / 'custodian-run.log' - metadata = root / 'metadata.json' - resources = root / 'resources.json' - - if not all(map(Path.exists, [logs, metadata, resources])): - _LOG.debug(f'{root} will not be loaded. Its execution ' - f'is invalid: {list(map(str, root.iterdir()))}') - return - with open(metadata, 'r') as file: - metadata_data = json.load(file) - with open(resources, 'r') as file: - resources_data = json.load(file) - return { - 'metadata': metadata_data, - 'resources': resources_data - } - - def _format_item(self, item: RuleRawOutput) -> FormattedItem: - """ - Keeps only description, name and resource type from metadata. - Inserts Custom Core report fields in resources (id, name, arn) - :param item: - :return: - """ - policy = item['metadata'].get('policy') - rt = self.adjust_resource_type(policy.get('resource')) - ReportFieldsLoader.load((rt,)) # should be loaded before - fields = ReportFieldsLoader.get(rt) - updated_resources = [] - for res in item['resources']: - report_fields = { - field: json_path_get(res, path) - for field, path in fields.items() - } - updated_resources.append({**res, **report_fields}) - return { - 'policy': { - 'name': policy.get('name'), - 'resourceType': policy.get('resource'), - 'description': policy.get('description') - }, - 'resources': updated_resources - } - - def iter_raw(self) -> Generator[RegionRuleOutput, None, None]: - dirs = filter( - lambda x: x.name != FINDINGS_FOLDER, - filter(Path.is_dir, self._work_dir.iterdir()) - ) - for region in dirs: - for rule in filter(Path.is_dir, region.iterdir()): - loaded = self._load_raw_rule_output(rule) - if not loaded: - continue - yield region.name, rule.name, loaded - - def iter_raw_threads(self) -> Generator[RegionRuleOutput, None, None]: - """ - The same as previous but reads file in multiple threads - :return: - """ - dirs = filter( - lambda x: x.name != FINDINGS_FOLDER, - filter(Path.is_dir, self._work_dir.iterdir()) - ) - with ThreadPoolExecutor() as executor: - futures = {} - for region in dirs: - for rule in filter(Path.is_dir, region.iterdir()): - fut = executor.submit(self._load_raw_rule_output, rule) - futures[fut] = (region, rule) - for future in as_completed(futures): - output = future.result() - if output: - region, rule = futures[future] - yield region.name, rule.name, output - - @staticmethod - def resolve_azure_locations(it: Iterable[RegionRuleOutput] - ) -> Generator[RegionRuleOutput, None, None]: - """ - The thing is: Custodian Custom Core cannot scan Azure - region-dependently. A rule covers the whole subscription - (or whatever, i don't know) and then each found resource has - 'location' field with its real location. - In order to adhere to AWS logic, when a user wants to receive - reports only for regions he activated, we need to filter out only - appropriate resources. - Also note that Custom Core has such a thing as `AzureCloud`. From - my point of view it's like a mock for every region (because, - I believe, in the beginning Core was designed for AWS and therefore - there are regions). With the current scanner implementation - (3.3.1) incoming `detailed_report` will always have one key: - `AzureCloud` with a list of all the scanned rules. We must remap it. - All the resources that does not contain - 'location' will be congested to 'multiregion' region. - :return: - """ - for _, rule, item in it: - if not item['resources']: # we cannot know - yield MULTIREGION, rule, item - continue - # resources exist - _loc_res = {} - for res in item['resources']: - loc = res.get('location') or MULTIREGION - _loc_res.setdefault(loc, []).append(res) - for location, resources in _loc_res.items(): - yield location, rule, { - 'metadata': item['metadata'], 'resources': resources - } - - def build_default_iterator(self) -> Iterable[RegionRuleOutput]: - it = self.iter_raw_threads() - if self._cloud == AZURE: - it = self.resolve_azure_locations(it) - regions = self.environment_service.target_regions() - if regions: - it = filter(lambda x: x[0] in regions, it) - return it - - def raw_detailed_report(self) -> dict: - """ - Produces region-specific object of un-formatted custodian detailed - reports, found within a working directory. - Note: does not retain error (failed to execute) reports. - :return: Dict[str, List[Dict]] { - $region: [ - { - 'metadata': Dict, - 'resources': List[Dict] - } - ] - } - """ - it = self.build_default_iterator() # get iterator from outside - res = {} - for region, rule, output in it: - res.setdefault(region, []).append(output) - return res - - def detailed_report(self) -> dict: - it = self.build_default_iterator() # get iterator from outside - res = {} - for region, rule, output in it: - res.setdefault(region, []).append(self._format_item(output)) - return res - - @staticmethod - def digest_report(detailed_report: dict) -> DigestReport: - total_checks = 0 - failed_checks = 0 - successful_checks = 0 - total_resources = set() - for region, items in detailed_report.items(): - _total = len(items) - _failed = len(list( - item for item in items if item.get('resources') - )) - total_checks += _total - failed_checks += _failed - successful_checks += (_total - _failed) - - resources = chain.from_iterable( - item.get('resources') or [] for item in items - ) - for res in resources: - total_resources.add(hashable(filter_dict(res, REPORT_FIELDS))) - return { - 'total_checks_performed': total_checks, - 'successful_checks': successful_checks, - 'failed_checks': failed_checks, - 'total_resources_violated_rules': len(total_resources) - } - - -class ReportService: - def __init__(self, s3_settings_service: S3SettingsService): - self.s3_settings_service = s3_settings_service - - @classmethod - def raw_to_dojo_policy_reports( - cls, detailed_report: Dict[str, List[Dict]], cloud: str - ): - """ - v3.3.1 Returns a Dojo compatible resources report out of a single - non-formatted detailed report. - :param detailed_report: Dict[str, List[Dict]] - :param cloud: str - :return: List[Dict] - """ - report = [] - for region, detailed_reports in detailed_report.items(): - for detailed_report in detailed_reports: - # detailed_custodian_run_log = detailed_report.get( - # 'custodian-run', '' - # ) - - detailed_metadata = detailed_report.get('metadata', {}) - detailed_resources = detailed_report.get('resources', []) - # detailed_errors = detailed_report.get('errors', []) - - policy = detailed_metadata.get("policy", {}) - - resource_type = policy.get("resource", "") - policy_metadata = policy.get("metadata", {}) - report_fields = policy_metadata.get("report_fields", []) - - # run_result = 'No errors' - # if 'ERROR' in detailed_custodian_run_log: - # run_result = "Errors were found" - - _policy_name = policy.get('name', '') - if region and region != "default": - _policy_name += ":" + region - - _multi_regional = policy_metadata.get("multiregional") - if not _multi_regional: - _multi_regional = 'false' - - resources = cls._derive_policy_report_resources( - report_fields=report_fields, - resource_type=resource_type, - resources=detailed_resources, - name=_policy_name - ) - - severity = policy_metadata.get("severity", 'Medium').title() - - entity = { - "description": policy.get("description", "no description"), - "resources #": len(resources), - "region": region, - "multiregional": _multi_regional, - "resources": resources, - "remediation": policy_metadata.get("remediation"), - "impact": policy_metadata.get("impact"), - "standard": policy_metadata.get("standard"), - "severity": severity, - "article": policy_metadata.get("article"), - - # DefectDojo: Finding Model unhandled/non-proxied keys - # "policy_name": _policy_name, - # "run_result": run_result, - # "errors": detailed_errors, - - # DefectDojo: Finding Model safe-to-provide fields - # pre v3.3.1 - "service": resource_type, - "vuln_id_from_tool": _policy_name, - "tags": [region], - - # DefectDojo: Report-helper keys, omitted when pushed - "report_fields": report_fields - } - if cloud.upper() != 'AWS': - del entity["region"] - del entity["multiregional"] - del entity["tags"] - - report.append(entity) - - return report - - def formatted_to_dojo_policy_report(self, - detailed_report: Dict[str, List[Dict]], - cloud: Optional[str] = None - ) -> List[PolicyReportItem]: - """ - Returns a dojo policy report out of formatted, region-specific - policy report. - :param detailed_report: Dict[str, List[Dict]] - :param cloud: Optional[str] = None - :return: List[Dict] - """ - policy_report: List[Dict] = [] - _human = self.s3_settings_service.human_data() or {} - _severity = self.s3_settings_service.rules_to_severity() or {} - for region, policies in detailed_report.items(): - for policy_scope in policies: - policy = policy_scope.get('policy') or {} - name = policy.get('name') - resources = policy_scope.get('resources') or [] - - _multi_regional = policy.get("multiregional") - if not _multi_regional: - _multi_regional = 'false' - - resources = self._derive_policy_report_resources( - report_fields=_human.get(name, {}).get('report_fields'), - resource_type=policy.get('resourceType'), - resources=resources, - name=name - ) - policy_report.append({ - "description": policy.get("description"), - # "region": region, - # "multiregional": _multi_regional, - "resources": resources, - "remediation": _human.get(name, {}).get('remediation'), - "impact": _human.get(name, {}).get('impact'), - "standard": {}, - "severity": _severity.get(name), - "article": _human.get(name, {}).get('article'), - "service": policy.get('resourceType'), - "vuln_id_from_tool": name, - "tags": [region], - "report_fields": _human.get(name, {}).get('report_fields') - }) - return policy_report - - @staticmethod - def _derive_policy_report_resources( - name: str, resource_type: str, resources: List, - report_fields: List[str] - ): - - # No `custodian-run-log` to check within. - # run_result = 'Unknown' - - skey = report_fields[0] if report_fields else None - if skey: - try: - _resources = sorted(resources, key=lambda r: r[skey]) - except (BaseException, Exception) as e: - msg = f'Sorting of resources, bound to \'{name}\' policy' - msg += f', by {skey} has run into an issue: {e}.' - msg += ' Using the unsorted ones.' - _LOG.warning(msg) - - _resources = [] - for resource in resources: - _resource = {} - for resource_key, resource_value in resource.items(): - - # `report_fields` are toggled during the Dojo upload. - # ergo, keeping key-values pairs of all fields. - - if isinstance(resource_value, (str, int, float)): - _resource[resource_key] = resource_value - - elif resource_type.startswith("gcp."): - if type(resource_value) is dict: - for inner_key in resource_value: - _key = resource_key + "_" + inner_key - _resource[_key] = resource_value[inner_key] - - elif type(resource_value) is list: - _len = len(resource_value) > 0 - _target = resource_value[0] - _predicate = not isinstance(_target, dict) - # v3.3.1 todo check for all non-str values? - if _len and _predicate: - _resource[resource_key] = "\n".join( - resource_value - ) - - _resources.append(_resource or resource) - - return _resources - - @staticmethod - def tenant_findings_path(tenant: Tenant) -> str: - return str(PurePosixPath( - FINDINGS_FOLDER, utc_datetime().date().isoformat(), - f'{tenant.project}.json' - )) - - @staticmethod - def platform_findings_path(platform: Parent) -> str: - """ - Platform is a parent with type PLATFORM_K8S currently. It's meta - can contain name and possibly region in case we talk about EKS - :param platform: - :return: - """ - meta = platform.meta.as_dict() - name = meta.get('name') - region = meta.get('region') or 'no-region' - return str(PurePosixPath( - FINDINGS_FOLDER, utc_datetime().date().isoformat(), 'k8s', - region, f'{name}.json' - )) - - -class FindingsCollection: - keys_to_keep: set = {'description', 'resourceType'} - # currently rules does not contain report fields - only_report_fields: bool = True - - def __init__(self, data: dict = None, rules_data: dict = None): - self._data: Dict[tuple, set] = data or {} - self._rules_data: Dict[str, Dict] = rules_data or {} - - @property - def rules_data(self) -> dict: - return self._rules_data - - @classmethod - def from_detailed_report(cls, report: dict) -> 'FindingsCollection': - """Imports data from detailed_report's format. In addition, collects - the descriptions and other info about a rule.""" - result, rules_data = {}, {} - for region, region_policies in report.items(): - for policy in region_policies: - p_data = policy['policy'] - if p_data['name'] not in rules_data: # retrieve rules info - rules_data[p_data['name']] = filter_dict( - p_data, cls.keys_to_keep) - result.setdefault((p_data['name'], region), set()) - for resource in policy.get('resources', []): - result[(p_data['name'], region)].add( - hashable(filter_dict(resource, set())) - ) - return cls(result, rules_data) - - @classmethod - def deserialize(cls, report: dict) -> 'FindingsCollection': - """Deserializes from a standard dict to the inner fancy one""" - result, rules_data = {}, {} - for rule, data in report.items(): - rules_data[rule] = filter_dict(data, cls.keys_to_keep) - for region, resources in data.get('resources', {}).items(): - result.setdefault((rule, region), set()) - for resource in resources: - result[(rule, region)].add(hashable(resource)) - return cls(result, rules_data) - - def serialize(self) -> dict: - """Serializes to a dict acceptable for json.dump""" - result = {} - for k, v in self._data.items(): - rule, region = k - if rule not in result: - result[rule] = filter_dict(self._rules_data.get(rule, {}), - self.keys_to_keep) - result[rule].setdefault('resources', {})[region] = list(v) - return result - - def json(self) -> str: - return json.dumps(self.serialize(), separators=(',', ':')) - - def update(self, other: 'FindingsCollection') -> None: - self._data.update(other._data) - self._rules_data.update(other._rules_data) - - # ----- patch ----- - # removing AzureCloud from findings - for rule, region in list(self._data.keys()): - if region == AZURE_COMMON_REGION: - self._data.pop((rule, region)) - # ----- patch ----- - - def __sub__(self, other: 'FindingsCollection') -> 'FindingsCollection': - result = {} - for k, v in self._data.items(): - found_resource = v - other._data.get(k, set()) - if found_resource: - result[k] = found_resource - return FindingsCollection(result, - {**other._rules_data, **self._rules_data}) - - def __len__(self) -> int: - length = 0 - for v in self._data.values(): - length += len(v) - return length - - def __bool__(self) -> bool: - return bool(self._data) diff --git a/executor/services/ruleset_service.py b/executor/services/ruleset_service.py deleted file mode 100644 index c7eae3c3c..000000000 --- a/executor/services/ruleset_service.py +++ /dev/null @@ -1,90 +0,0 @@ -from functools import cached_property -from typing import Generator, Iterable, Optional - -from requests import RequestException, get - -from helpers.constants import GOOGLE, GCP, ED_AWS_RULESET_NAME, \ - ED_GOOGLE_RULESET_NAME, ED_AZURE_RULESET_NAME, AWS, AZURE, \ - COMPOUND_KEYS_SEPARATOR -from helpers.log_helper import get_logger -from models.ruleset import Ruleset, RULESET_STANDARD -from services.environment_service import EnvironmentService - -_LOG = get_logger(__name__) - - -class RulesetService: - def __init__(self, environment_service: EnvironmentService): - self._environment = environment_service - - def pull_ruleset_content(self, path: str, encoding: str = None): - """ - Pulls streamable content from a given URI path and respectively - decodes the data according to the provided notation - :return: Union[str, Type[None]] - """ - content = self._download_ruleset_content(path) - if content is None: - return - try: - content = content.decode(encoding) if encoding else content - except (UnicodeDecodeError, UnicodeError, Exception) as e: - _LOG.warning( - f'Ruleset content from \'{path}\' could not be \'{encoding}\'' - f' decoded, due to the exception "{e}".' - ) - return content - - @staticmethod - def _download_ruleset_content(path: str): - """Mandates action of requesting streamable data from a given path.""" - content = None - try: - with get(path, stream=True) as response: - content = response.content - except RequestException as e: - _LOG.warning(f'Ruleset content from \'{path}\' could not be ' - f'pulled, due to the following "{e}".') - return content - - @staticmethod - def get_ruleset_by_id(ruleset_id: str, attributes_to_get: list = None): - return Ruleset.get_nullable(ruleset_id, - attributes_to_get=attributes_to_get) - - def target_rulesets(self) -> Generator[Ruleset, None, None]: - yield from self.i_rulesets_by_ids(self._environment.target_rulesets()) - - def i_rulesets_by_ids(self, ids: Iterable[str] - ) -> Generator[Ruleset, None, None]: - for _id in ids: - item = self.get_ruleset_by_id(_id) - if not item: - _LOG.warning(f'Ruleset with id {_id} not found') - yield item - - @cached_property - def cloud_to_ed_ruleset_name(self) -> dict: - return { - GCP: ED_GOOGLE_RULESET_NAME, - GOOGLE: ED_GOOGLE_RULESET_NAME, - AWS: ED_AWS_RULESET_NAME, - AZURE: ED_AZURE_RULESET_NAME - } - - def get_ed_ruleset(self, cloud: str) -> Optional[Ruleset]: - """ - Event driven rule-sets belong to SYSTEM. - """ - # backward compatibility. Cloud comes from tenant - name = self.cloud_to_ed_ruleset_name[cloud.upper()] - sort_key = f'{self._environment.system_customer()}' \ - f'{COMPOUND_KEYS_SEPARATOR}{RULESET_STANDARD}' \ - f'{COMPOUND_KEYS_SEPARATOR}{name}{COMPOUND_KEYS_SEPARATOR}' - return next(Ruleset.customer_id_index.query( - hash_key=self._environment.system_customer(), - range_key_condition=Ruleset.id.startswith(sort_key), - filter_condition=(Ruleset.event_driven == True), - limit=1, - scan_index_forward=False - ), None) diff --git a/executor/services/s3_service.py b/executor/services/s3_service.py deleted file mode 100644 index 91dc15772..000000000 --- a/executor/services/s3_service.py +++ /dev/null @@ -1,59 +0,0 @@ -import json -import os -from pathlib import PurePosixPath, Path - -from botocore.exceptions import ClientError -from services.clients.s3 import S3Client - - -class S3Service: - def __init__(self, client: S3Client): - self.client = client - - def get_file_content(self, bucket_name, path): - """Get nullable""" - try: - return self.client.get_file_content( - bucket_name=bucket_name, - full_file_name=path, - decode=True - ) - except ClientError as e: - if isinstance(e, ClientError) and \ - e.response['Error']['Code'] == 'NoSuchKey': - return None - raise e - - def get_json_file_content(self, bucket_name, path): - """Get nullable""" - try: - return self.client.get_json_file_content( - bucket_name=bucket_name, - full_file_name=path - ) - except ClientError as e: - if isinstance(e, ClientError) and \ - e.response['Error']['Code'] == 'NoSuchKey': - return None - raise e - - def put_json_object(self, bucket_name, content, file_key, indent=None): - content_json = json.dumps(content, indent=indent) - return self.client.put_object( - bucket_name=bucket_name, - object_name=file_key, - body=content_json - ) - - def upload_directory(self, bucket_name, source_dir, target_dir): - for root, dirs, files in os.walk(source_dir): - for file in files: - full_path = Path(root, file) - with open(full_path, 'r') as obj: - body = obj.read() - relative_path = full_path.relative_to(source_dir) - self.client.put_object( - bucket_name=bucket_name, - object_name=str(PurePosixPath(target_dir, relative_path)), - body=body - ) diff --git a/executor/services/s3_settings_service.py b/executor/services/s3_settings_service.py deleted file mode 100644 index af775380f..000000000 --- a/executor/services/s3_settings_service.py +++ /dev/null @@ -1,74 +0,0 @@ -import gzip -import json -from pathlib import PurePosixPath, Path -from typing import Union, Optional - -from helpers.constants import KEY_RULES_TO_STANDARDS, \ - KEY_RULES_TO_SEVERITY, KEY_HUMAN_DATA -from helpers.log_helper import get_logger -from services.clients.s3 import S3Client -from services.environment_service import EnvironmentService - -_LOG = get_logger(__name__) - - -class S3SettingsService: - SETTINGS_PREFIX = 'caas-settings' - FILE_SUFFIX = '.json.gz' - - def __init__(self, s3_client: S3Client, - environment_service: EnvironmentService): - self._s3 = s3_client - self._environment_service = environment_service - - @property - def bucket_name(self): - return self._environment_service.rulesets_bucket_name() - - def key_from_name(self, key: str) -> str: - if not key.endswith(self.FILE_SUFFIX): - key = key.strip('.') + self.FILE_SUFFIX - return str(PurePosixPath(self.SETTINGS_PREFIX, key)) - - def name_from_key(self, key: str) -> str: - return str(Path(key).name).rstrip(self.FILE_SUFFIX) - - def get(self, key: str, - bucket_name: Optional[str] = None) -> Optional[Union[dict, str]]: - key = self.key_from_name(key) - obj = self._s3.get_file_stream(bucket_name or self.bucket_name, key) - if not obj: - return - content = gzip.decompress(obj.read()) - try: - return json.loads(content) - except json.JSONDecodeError: - return content.decode() - - def rules_to_severity(self) -> dict: - return self.get(KEY_RULES_TO_SEVERITY) or {} - - def rules_to_standards(self) -> dict: - return self.get(KEY_RULES_TO_STANDARDS) or {} - - def human_data(self) -> dict: - return self.get(KEY_HUMAN_DATA) or {} - - -class CachedS3SettingsService(S3SettingsService): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - # not adjusted key to value - self._cache = {} - - def get(self, key: str, - bucket_name: Optional[str] = None) -> Optional[Union[dict, str]]: - if key in self._cache: - _LOG.info(f'Returning cached s3 setting by key {key}') - return self._cache[key] - _LOG.info(f'Getting s3 setting by key {key}') - value = super().get(key, bucket_name) - if not value: - return - self._cache[key] = value - return value diff --git a/executor/services/scheduler_service.py b/executor/services/scheduler_service.py deleted file mode 100644 index ccec7c9c8..000000000 --- a/executor/services/scheduler_service.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import Optional -from models.scheduled_job import ScheduledJob -from services.clients.scheduler import AbstractJobScheduler - - -class SchedulerService: - def __init__(self, client: AbstractJobScheduler): - self._client = client - - @staticmethod - def get(name: str, - customer: Optional[str] = None) -> Optional[ScheduledJob]: - item = ScheduledJob.get_nullable(hash_key=name) - if not item or customer and item.customer_name != customer: - return - return item - - def update_job(self, item: ScheduledJob, is_enabled: bool): - return self._client.update_job(item, is_enabled) diff --git a/executor/services/service_provider.py b/executor/services/service_provider.py deleted file mode 100644 index 0a7c103ad..000000000 --- a/executor/services/service_provider.py +++ /dev/null @@ -1,264 +0,0 @@ -from helpers import SingletonMeta -from services.batch_service import BatchService -from services.clients.event_bridge import EventBridgeClient -from services.clients.license_manager import LicenseManagerClient -from services.clients.modular import ModularClient -from services.clients.s3 import S3Client -from services.clients.ssm import SSMClient, VaultSSMClient, AbstractSSMClient -from services.clients.sts import StsClient -from services.credentials_service import CredentialsService -from services.environment_service import EnvironmentService -from services.integration_service import IntegrationService -from services.job_updater_service import JobUpdaterService -from services.license_manager_service import LicenseManagerService -from services.modular_service import ModularService, TenantService -from services.notification_service import NotificationService -from services.policy_service import PolicyService -from services.report_service import ReportService -from services.ruleset_service import RulesetService -from services.s3_service import S3Service -from services.s3_settings_service import CachedS3SettingsService -from services.scheduler_service import SchedulerService -from services.setting_service import SettingService -from services.ssm_service import SSMService -from services.statistics_service import StatisticsService -from services.token_service import TokenService - - -class ServiceProvider(metaclass=SingletonMeta): - __ssm_conn = None - __s3_conn = None - __sts_conn = None - __modular_conn = None - - __license_manager_client = None - __event_bridge_client = None - __ap_job_scheduler = None - __event_bridge_scheduler = None - __standalone_key_management = None - - __environment_service = None - __configuration_service = None - __modular_service = None - __ssm_service = None - __credentials_service = None - __s3_service = None - __ruleset_service = None - __policy_service = None - __batch_service = None - __report_service = None - __statistics_service = None - __job_updater_service = None - __license_manager_service = None - __tenant_service = None - __scheduler_service = None - __token_service = None - __setting_service = None - __s3_setting_service = None - __notification_service = None - __integration_service = None - - def __str__(self): - return id(self) - - def ssm(self) -> AbstractSSMClient: - if not self.__ssm_conn: - _env = self.environment_service() - if _env.is_docker(): - self.__ssm_conn = VaultSSMClient(environment_service=_env) - else: - self.__ssm_conn = SSMClient(environment_service=_env) - return self.__ssm_conn - - def s3(self) -> S3Client: - if not self.__s3_conn: - self.__s3_conn = S3Client( - environment_service=self.environment_service()) - return self.__s3_conn - - def sts_client(self) -> StsClient: - if not self.__sts_conn: - self.__sts_conn = StsClient.factory().build( - region_name=self.environment_service().aws_region() - ) - return self.__sts_conn - - def modular_client(self) -> ModularClient: - if not self.__modular_conn: - self.__modular_conn = ModularClient() - return self.__modular_conn - - def license_manager_client(self) -> LicenseManagerClient: - if not self.__license_manager_client: - self.__license_manager_client = LicenseManagerClient( - environment_service=self.environment_service(), - setting_service=self.setting_service() - ) - return self.__license_manager_client - - def event_bridge_client(self) -> EventBridgeClient: - if not self.__event_bridge_client: - self.__event_bridge_client = EventBridgeClient( - environment_service=self.environment_service(), - ) - return self.__event_bridge_client - - def ap_job_scheduler(self): - if not self.__ap_job_scheduler: - from services.clients.scheduler import APJobScheduler - self.__ap_job_scheduler = APJobScheduler() - return self.__ap_job_scheduler - - def event_bridge_scheduler(self): - if not self.__event_bridge_scheduler: - from services.clients.scheduler import EventBridgeJobScheduler - self.__event_bridge_scheduler = EventBridgeJobScheduler( - client=self.event_bridge_client() - ) - return self.__event_bridge_scheduler - - def standalone_key_management(self): - if not self.__standalone_key_management: - from services.clients.standalone_key_management import \ - StandaloneKeyManagementClient - self.__standalone_key_management = \ - StandaloneKeyManagementClient(ssm_client=self.ssm()) - return self.__standalone_key_management - - def environment_service(self) -> EnvironmentService: - if not self.__environment_service: - self.__environment_service = EnvironmentService() - return self.__environment_service - - def modular_service(self) -> ModularService: - if not self.__modular_service: - self.__modular_service = ModularService( - client=self.modular_client()) - return self.__modular_service - - def ssm_service(self) -> SSMService: - if not self.__ssm_service: - self.__ssm_service = SSMService( - client=self.ssm(), - environment_service=self.environment_service()) - return self.__ssm_service - - def credentials_service(self) -> CredentialsService: - if not self.__credentials_service: - self.__credentials_service = CredentialsService( - ssm_service=self.ssm_service(), - environment_service=self.environment_service(), - sts_client=self.sts_client()) - return self.__credentials_service - - def s3_service(self) -> S3Service: - if not self.__s3_service: - self.__s3_service = S3Service(client=self.s3()) - return self.__s3_service - - def ruleset_service(self) -> RulesetService: - if not self.__ruleset_service: - self.__ruleset_service = RulesetService( - environment_service=self.environment_service() - ) - return self.__ruleset_service - - def policy_service(self) -> PolicyService: - if not self.__policy_service: - self.__policy_service = PolicyService( - environment_service=self.environment_service(), - s3_service=self.s3_service(), - ruleset_service=self.ruleset_service() - ) - return self.__policy_service - - def batch_service(self) -> BatchService: - if not self.__batch_service: - self.__batch_service = BatchService( - environment_service=self.environment_service()) - return self.__batch_service - - def report_service(self) -> ReportService: - if not self.__report_service: - self.__report_service = ReportService( - s3_settings_service=self.s3_setting_service() - ) - return self.__report_service - - def statistics_service(self) -> StatisticsService: - if not self.__statistics_service: - self.__statistics_service = StatisticsService( - s3_service=self.s3_service(), - environment_service=self.environment_service() - ) - return self.__statistics_service - - def job_updater_service(self) -> JobUpdaterService: - if not self.__job_updater_service: - self.__job_updater_service = JobUpdaterService( - environment_service=self.environment_service(), - license_manager_service=self.license_manager_service(), - tenant_service=self.tenant_service() - ) - return self.__job_updater_service - - def license_manager_service(self) -> LicenseManagerService: - if not self.__license_manager_service: - self.__license_manager_service = LicenseManagerService( - license_manager_client=self.license_manager_client(), - token_service=self.token_service() - ) - return self.__license_manager_service - - def tenant_service(self) -> TenantService: - if not self.__tenant_service: - self.__tenant_service = TenantService( - modular_service=self.modular_service(), - environment_service=self.environment_service() - ) - return self.__tenant_service - - def scheduler_service(self) -> SchedulerService: - if not self.__scheduler_service: - self.__scheduler_service = SchedulerService( - client=self.ap_job_scheduler() - if self.environment_service().is_docker() else - self.event_bridge_scheduler() - ) - return self.__scheduler_service - - def token_service(self) -> TokenService: - if not self.__token_service: - self.__token_service = TokenService( - client=self.standalone_key_management() - ) - return self.__token_service - - def setting_service(self) -> SettingService: - if not self.__setting_service: - self.__setting_service = SettingService() - return self.__setting_service - - def s3_setting_service(self) -> CachedS3SettingsService: - if not self.__s3_setting_service: - self.__s3_setting_service = CachedS3SettingsService( - s3_client=self.s3(), - environment_service=self.environment_service() - ) - return self.__s3_setting_service - - def notification_service(self) -> NotificationService: - if not self.__notification_service: - self.__notification_service = NotificationService( - setting_service=self.setting_service(), - ssm_service=self.ssm_service(), - s3_service=self.s3_service()) - return self.__notification_service - - def integration_service(self) -> IntegrationService: - if not self.__integration_service: - self.__integration_service = IntegrationService( - modular_service=self.modular_service(), - ssm_service=self.ssm_service() - ) - return self.__integration_service diff --git a/executor/services/setting_service.py b/executor/services/setting_service.py deleted file mode 100644 index 8524791b5..000000000 --- a/executor/services/setting_service.py +++ /dev/null @@ -1,59 +0,0 @@ -from typing import Optional, Union - -from helpers.log_helper import get_logger -from models.setting import Setting - -KEY_CONTACTS = 'CONTACTS' -KEY_MAIL_CONFIGURATION = 'MAIL_CONFIGURATION' -KEY_SYSTEM_CUSTOMER = 'SYSTEM_CUSTOMER_NAME' -KEY_CUSTODIAN_SERVICE_PRIVATE_KEY = 'LM_CLIENT_KEY' -KEY_STATS_S3_BUCKET_NAME = 'STATS_S3_BUCKET_NAME' -KEY_ACCESS_DATA_LM = 'ACCESS_DATA_LM' -KEY_TEMPLATE_BUCKET = 'TEMPLATES_S3_BUCKET_NAME' - -_LOG = get_logger(__name__) - - -class SettingService: - - @staticmethod - def get(name, value: bool = True) -> Optional[Union[Setting, dict]]: - setting = Setting.get_nullable(hash_key=name) - if setting and value: - return setting.value - elif setting: - return setting - - @staticmethod - def get_contacts(): - """ - Returns Custodian Team contacts (DL) - """ - contacts = SettingService.get(name=KEY_CONTACTS) - return contacts if contacts else '' - - @staticmethod - def get_mail_configuration(value: bool = True) -> Optional[ - Union[Setting, dict] - ]: - return SettingService.get( - name=KEY_MAIL_CONFIGURATION, value=value - ) - - @staticmethod - def get_custodian_service_private_key(): - """ - Returns `custodian service key` data, including key-id and algorithm. - :return: dict - """ - key = SettingService.get(name=KEY_CUSTODIAN_SERVICE_PRIVATE_KEY) - return key if key else {} - - @staticmethod - def get_license_manager_access_data(value: bool = True): - return SettingService.get(name=KEY_ACCESS_DATA_LM, value=value) - - @staticmethod - def get_template_bucket() -> Optional[str]: - bucket = SettingService.get(name=KEY_TEMPLATE_BUCKET) - return bucket or None diff --git a/executor/services/ssm_service.py b/executor/services/ssm_service.py deleted file mode 100644 index 3b65ea658..000000000 --- a/executor/services/ssm_service.py +++ /dev/null @@ -1,22 +0,0 @@ -from helpers.log_helper import get_logger -from services.environment_service import EnvironmentService -from services.clients.ssm import AbstractSSMClient - -_LOG = get_logger(__name__) - - -class SSMService: - def __init__(self, client: AbstractSSMClient, - environment_service: EnvironmentService): - self.client = client - self.environment_service = environment_service - - def delete_secret_value(self, secret_name: str): - return self.client.delete_parameter(secret_name=secret_name) - - def get_secret_value(self, secret_name): - return self.client.get_secret_value(secret_name=secret_name) - - def create_secret_value(self, secret_name, secret_value): - self.client.create_secret(secret_name=secret_name, - secret_value=secret_value) \ No newline at end of file diff --git a/executor/services/statistics_service.py b/executor/services/statistics_service.py deleted file mode 100644 index 478499ea7..000000000 --- a/executor/services/statistics_service.py +++ /dev/null @@ -1,223 +0,0 @@ -import json -import os -from collections import Counter -from datetime import datetime -from pathlib import PurePosixPath - -from modular_sdk.models.tenant import Tenant - -from helpers.constants import STEP_COLLECT_STATISTICS, FINDINGS_FOLDER -from helpers.exception import ExecutorException -from helpers.log_helper import get_logger -from helpers.time_helper import utc_iso -from services.environment_service import EnvironmentService -from services.s3_service import S3Service - -_LOG = get_logger(__name__) -STATISTICS_FILE = 'statistics.json' -API_CALLS_FILE = 'api_calls.json' - - -class StatisticsService: - def __init__(self, s3_service: S3Service, - environment_service: EnvironmentService): - self.s3_service = s3_service - self.environment_service = environment_service - - @property - def statistics_bucket(self) -> str: - return self.environment_service.statistics_bucket_name() - - def collect_statistics(self, work_dir, failed_policies, skipped_policies, - tenant: Tenant): - items = [] - resources = {} - result_api_calls = {} - - folders = [folder for folder in os.listdir(work_dir) - if folder != FINDINGS_FOLDER] - for folder in folders: - if os.path.isfile(os.path.join(work_dir, folder)): - continue - folder_items, folder_resources, api_calls = \ - self._collect_statistics_in_folder( - work_dir=work_dir, - folder=folder, - failed_policies=failed_policies, - skipped_policies=skipped_policies, - tenant=tenant - ) - items.extend(folder_items) - for resource, count in folder_resources.items(): - if resource in resources: - resources[resource] += count - else: - resources[resource] = count - result_api_calls = dict( - Counter(result_api_calls) + Counter(api_calls)) - _LOG.debug(f'Result API calls dict: {result_api_calls}') - - # Go through the list of skipped policies in case they were skipped - # before the policy was processed, so the metadata folder was not - # created - for region, rule in skipped_policies.items(): - if not rule: - continue - for rule_id, reason in rule.items(): - item = { - 'id': rule_id, - 'region': region, - 'status': 'SKIPPED', - 'reason': reason.get('reason'), - 'tenant_display_name': tenant.name, - 'customer_display_name': tenant.customer_name - } - items.append(item) - - # we used to save resources amount to account (not tenant) here, - # but we can calculate it dynamically from findings, so need - # new_acc = self.account_service.get_by_uuid(account.id) - # new_acc.resources_amount = resources - # new_acc.save() - return items, result_api_calls - - def upload_statistics(self, statistics): - stats_path = str(PurePosixPath( - self.environment_service.batch_job_id(), 'statistics.json')) - - stats_log = [f'{item.get("id", "None")}:{item.get("status", "None")}' - for item in statistics] - _LOG.debug(F"Statistics short content: {stats_log}") - self.s3_service.put_json_object( - bucket_name=self.statistics_bucket, - file_key=stats_path, - content=statistics - ) - _LOG.debug(f'The file \'statistics.json\' was uploaded ' - f'to the bucket {self.statistics_bucket}: {stats_path}') - - def upload_api_calls_statistics(self, api_calls): - api_calls_path = str(PurePosixPath( - self.environment_service.batch_job_id(), 'api_calls.json')) - - _LOG.debug(f'API calls content: {api_calls}') - self.s3_service.put_json_object( - bucket_name=self.statistics_bucket, - file_key=api_calls_path, - content=api_calls - ) - _LOG.debug(f'The file \'api_calls.json\' was uploaded ' - f'to the bucket {self.statistics_bucket}: {api_calls_path}') - - def _collect_statistics_in_folder(self, work_dir, folder, - failed_policies, skipped_policies, - tenant: Tenant): - items = [] - resources = {} - temp_path = os.path.join(work_dir, str(folder)) - _LOG.debug(f'Processing folder: {temp_path}') - for rule_id in os.listdir(temp_path): - rule_item, rule_resources = self._collect_statistics_in_rule( - rule_id=rule_id, - folder=folder, - temp_path=temp_path, - failed_policies=failed_policies, - skipped_policies=skipped_policies, - tenant=tenant - ) - items.append(rule_item) - resources.update(rule_resources) - - api_calls = self._count_api_calls(temp_path=temp_path) - return items, resources, api_calls - - def _collect_statistics_in_rule(self, rule_id, folder, temp_path, - failed_policies, skipped_policies, - tenant: Tenant): - _LOG.debug(f'Adding rule {rule_id} to statistics') - _LOG.debug(f'Rule folder content: ' - f'{os.listdir(os.path.join(temp_path, rule_id))}') - resources_scanned = 0 - metadata_path = os.path.join(temp_path, rule_id, - 'metadata.json') - with open(metadata_path, 'r') as file: - metadata_json = json.loads(file.read()) - - execution = metadata_json.get('execution') - if not execution: - raise ExecutorException( - reason='Invalid metadata.json format: missing execution ' - 'block', - step_name=STEP_COLLECT_STATISTICS) - duration = round(execution.get('duration'), 3) - end_time = utc_iso(datetime.fromtimestamp(execution.get('end_time'))) - start_time = utc_iso(datetime.fromtimestamp(execution.get('start'))) - failed_resources = None - resource = metadata_json.get('policy', {}).get('resource') - resource_path = os.path.join(temp_path, rule_id, - 'resources.json') - for metric in metadata_json.get('metrics'): - if metric.get('MetricName') == 'AllScannedResourcesCount': - resources_scanned = int(metric.get('Value')) - if metric.get('MetricName') == 'ResourceCount' and \ - metric.get('Value') and os.path.exists(resource_path): - with open(resource_path, 'r') as resources_file: - failed_resources = json.loads( - resources_file.read()) - else: - _LOG.debug( - f'Resource path {resource_path} does not exist, ' - f'failed rules will be empty') - item = { - 'id': rule_id, - 'region': folder, - 'started_at': start_time, - 'finished_at': end_time, - 'status': 'SUCCEEDED', - 'resources_scanned': resources_scanned, - 'elapsed_time': str(duration), - 'failed_resources': failed_resources, - 'tenant_display_name': tenant.name, - 'customer_display_name': tenant.customer_name - } - if skipped_policies.get(folder) and rule_id in skipped_policies.get( - folder).keys(): - item = { - 'id': rule_id, - 'region': folder, - 'status': 'SKIPPED', - 'reason': skipped_policies.get(folder).get(rule_id).get( - 'reason'), - 'tenant_display_name': tenant.name, - 'customer_display_name': tenant.customer_name - } - skipped_policies.get(folder).pop(rule_id) - if failed_policies.get(folder) and rule_id in failed_policies.get( - folder).keys(): - item.update({ - 'status': 'FAILED', - 'reason': failed_policies.get(folder).get(rule_id)[0], - 'traceback': failed_policies.get(folder).get(rule_id)[1] - }) - return item, {resource: resources_scanned} - - @staticmethod - def _count_api_calls(temp_path): - _LOG.debug('Counting API calls') - api_calls = {} - for folder in os.listdir(temp_path): - metadata_path = os.path.join(temp_path, folder, 'metadata.json') - _LOG.debug(f'Opening file {metadata_path}') - with open(metadata_path, 'r') as file: - metadata_json = json.loads(file.read()) - - api_stats = metadata_json.get('api-stats') or {} - if not api_stats: - _LOG.warning(f'Invalid metadata.json format: missing \'' - f'api-stats\' block in {metadata_path}') - for key, value in api_stats.items(): - if key in api_calls: - api_calls[key] = api_calls[key] + int(value) - else: - api_calls[key] = value - return api_calls diff --git a/executor/services/token_service.py b/executor/services/token_service.py deleted file mode 100644 index 8c4a5f9d1..000000000 --- a/executor/services/token_service.py +++ /dev/null @@ -1,54 +0,0 @@ -from services.clients.abstract_key_management import \ - AbstractKeyManagementClient -from helpers.security import AbstractTokenEncoder, TokenEncoder - -from helpers.constants import CLIENT_TOKEN_ATTR - -from helpers.log_helper import get_logger - -from typing import Union, Type - -_LOG = get_logger(__name__) - -EXPIRATION_ATTR = 'exp' -ENCODING = 'utf-8' - - -class TokenService: - def __init__(self, client: AbstractKeyManagementClient): - self._client = client - - def derive_encoder(self, token_type: str, **payload) -> \ - Union[AbstractTokenEncoder, Type[None]]: - """ - Mandates preliminary token-encoder preparation, based on a respective - token type, given there is such one. For any injectable data, - attaches said payload to the token as claims. - :parameter token_type: str - :return: Union[AbstractTokenEncoder, Type[None]] - """ - t_head = f'\'{token_type}\'' - _LOG.debug(f'Going to instantiate a {t_head} encoder.') - - reference_map = self._token_type_builder_map() - _Encoder: Type[AbstractTokenEncoder] = reference_map.get(token_type) - if not _Encoder: - _LOG.warning(f'{t_head.capitalize()} encoder does not exist.') - return None - - encoder, key = _Encoder(), None - encoder.key_management = self._client - encoder.typ = token_type - for key, value in payload.items(): - encoder[key] = value - else: - if key: - _LOG.debug(f'{t_head} encoder has been attached ' - f'with {payload} claims.') - return encoder - - @staticmethod - def _token_type_builder_map(): - return { - CLIENT_TOKEN_ATTR: TokenEncoder - } diff --git a/src/main.py b/src/main.py index 8ff8b088a..b325e4e2a 100644 --- a/src/main.py +++ b/src/main.py @@ -34,14 +34,6 @@ ) from onprem.api.deployment_resources_parser import \ DeploymentResourcesApiGatewayWrapper -from onprem.scripts.parse_rule_source import ( - init_parser as init_parser_rule_source_cli_parser, - main as parse_rule_source, -) -from onprem.scripts.rules_table_generator import ( - init_parser as init_rules_table_generator_cli_parser, - main as generate_rules_table, -) from services import SP from services.clients.xlsx_standard_parser import ( init_parser as init_xlsx_cli_parser, @@ -171,14 +163,6 @@ def __call__(self, item: str) -> tuple[str, str]: PARSE_XLSX_STANDARD_ACTION, help='Parses Custom Core\'s xlsx with standards' )) - init_parser_rule_source_cli_parser(sub_parsers.add_parser( - PARSE_RULE_SOURCE_ACTION, - help='Parses Rule source and extracts some data' - )) - init_rules_table_generator_cli_parser(sub_parsers.add_parser( - GENERATE_RULES_TABLE_ACTION, - help='Generates xlsx table with rules data from local dir with rules' - )) parser_run = sub_parsers.add_parser(RUN_ACTION, help='Run on-prem server') parser_run.add_argument( '-g', '--gunicorn', action='store_true', default=False, @@ -692,8 +676,6 @@ def main(args: list[str] | None = None): (GENERATE_OPENAPI_ACTION,): GenerateOpenApi(), (RUN_ACTION,): Run(), (PARSE_XLSX_STANDARD_ACTION,): parse_xlsx_standard, - (PARSE_RULE_SOURCE_ACTION,): parse_rule_source, - (GENERATE_RULES_TABLE_ACTION,): generate_rules_table, (UPDATE_API_GATEWAY_MODELS_ACTION,): UpdateApiGatewayModels(), (SHOW_PERMISSIONS_ACTION,): ShowPermissions(), From 7f7840faf95d545f498fade3c20005a930ec30ef Mon Sep 17 00:00:00 2001 From: Dmytro Afanasiev Date: Tue, 14 May 2024 16:14:40 +0300 Subject: [PATCH 4/4] Add entrypoint script --- src/onprem/scripts/entrypoint.sh | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/onprem/scripts/entrypoint.sh diff --git a/src/onprem/scripts/entrypoint.sh b/src/onprem/scripts/entrypoint.sh new file mode 100644 index 000000000..f44f9f088 --- /dev/null +++ b/src/onprem/scripts/entrypoint.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +echo "Creating the necessary buckets in Minio" +python main.py create_buckets + +echo "Creating indexes in MongoDB" +python main.py create_indexes + +echo "Creating the necessary engine and token in Vault" +python main.py init_vault + +echo "Initializing" +python main.py init + +echo "Starting server" +python main.py run --gunicorn --swagger \ No newline at end of file